やっぱりWindows上のソフトウェアだけじゃなくてマイコン上でも鳴らしたいよね、ということで、Arduinoでも微妙にパルス波っぽい拡張矩形波を鳴らしてみました。
曲は前にやったのと同じく、ファミコンのMOTHERより「Eight Melodies」です。
1音ごとにランダムで波形を選ぶようにしてあります。
ファミコン・ゲームボーイの矩形波の場合、パルス幅は「12.5%」「25%」「50%」「75%」の4種類ですが、このスケッチでは「6.25%」「87.5%」「93.75%」を加えて7種類にしてみました。
Arduino標準のtone()関数ではタイマを使って精密に計算していますが、このスケッチではmillis()関数とdelay(),delayMicroseconds()関数を使って、かなり大雑把に時間を計っています。
そのため、聞きようによっては味のある(悪く言えば一癖も二癖もある)うねりのある音になっています。
……まあそれはそれで「らしい」感じがするから良いかなと。
#include <mml.h>
#include "notes.h"
const byte SND_OUT = 11;
const char DUTY_LIST[] = {-16, -8, -4, 2, 4, 8, 16};
const byte DUTY_CNT = 7;
MML mml;
MML_OPTION mmlopt;
char *text = "o5 l8 def+ae2 >dc+<bf+a2 b>c+d<a4d4. gf+d<a2r b4>c+4d4g4 f+gaf+ef+ge <b>gf+<a>e4c+4 d4<a>ed2";
static void mml_callback(MML_INFO *p, void *extobj)
{
switch (p->type) {
case MML_TYPE_NOTE:
{
MML_ARGS_NOTE *args = &(p->args.note);
snd(notes[args->number], args->ticks * 2, random(0, DUTY_CNT));
}
break;
case MML_TYPE_REST:
{
MML_ARGS_REST *args = &(p->args.rest);
delay(args->ticks);
}
break;
}
}
void pulse(byte pin, unsigned int us, int duty)
{
unsigned int h_us;
if (duty < 0){
duty = 0 - duty;
h_us = us - (us / duty);
}else{
h_us = us / duty;
}
digitalWrite(pin, HIGH);
delayMicroseconds(h_us);
digitalWrite(pin, LOW);
delayMicroseconds(us - h_us);
}
void snd(int hz, int ms, int duty)
{
int len = ms / 4 * 3;
int us = 1000000 / hz;
unsigned long end_ms = millis() + len;
while (millis() < end_ms){
pulse(SND_OUT, us, DUTY_LIST[duty]);
}
delay(ms - len);
}
void setup()
{
mml_init(&mml, mml_callback, 0);
MML_OPTION_INITIALIZER_DEFAULT(&mmlopt);
pinMode(SND_OUT, OUTPUT);
}
void loop()
{
mml_setup(&mml, &mmlopt, text);
while (mml_fetch(&mml) == MML_RESULT_OK);
}
※このスケッチの実行にはA tiny MML parserが必要となります。
こないだの記事を参考に導入してみてください
スケッチの最初の方にあるDUTY_LIST[]を{-8,4,2,4}にして、DUTY_CNTを4にすれば、ファミコンと同じ4種類のパルス波が出るようになります。気になる方は試してみましょう。