Arduinoでファミコン風矩形波を鳴らす

やっぱり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種類のパルス波が出るようになります。気になる方は試してみましょう。

コメントを残す