ホーム > 音楽 > MIDI Chord Helper > MIDI Chord Helper ハードウェア化計画 > 電子楽器 CAmiDion 2号機

電子楽器 CAmiDion 2号機

CAmiDion 2号機

CAmiDion blog もあわせて参照 → 電子楽器 CAmiDion 2号機

音源の実装

PWM出力

マイコンに音を出させるには、一定のタイミングで常に変化させることのできる 何らかの出力が必要です。

デジタル出力では2値しか出せませんが、これでも単音の矩形波なら比較的簡単に出せます。 ところが、和音を出そうとすると、単にANDやORのような論理演算で重ねても 音がつぶれてしまうだけなので、アナログ値を出力できる何らかの仕組みが必要になってきます。

Arduino では analogWrite() を使うことでアナログ値を PWM 出力できるようになっています。 PWM はパルス幅を変えているだけなので、どの瞬間も 0v と 5v しか出ませんが、 積分回路(ローパスフィルタ)を通すとパルス幅に合わせて電圧が変化し、元の波形を再現することができます。 つまり、D/Aコンバータ(DAC)のように働くわけです。

出力デバイスによっては積分回路なしで実装できることもあります。 例えばモーター制御であれば回転の勢いで動きが「積分」されます。 LED の明るさ制御であれば、PWMで光が高速点滅しても目で「積分」されるので、 単に明るさがゆっくり変わっただけのように見えます。

analogWrite() のマニュアルによると、Arduino ではデフォルトのPWM(キャリアの) 周波数は約488Hzに設定されています。これはモーター制御や LED の明るさ制御であれば十分ですが、音としては「ラ」の音 440Hz よりも 少し高い音の周波数なので、音楽の音を乗せるキャリアとして使うには不十分です。 キャリアの周波数をもっと上げることはできないのでしょうか?

実は、できるのです。

ATMEGA328 の PWM 周波数は「クロック周波数をプリスケーラ(分周器)で何回半分にするか?」を 設定することによってコントロールできます。

PWM はタイマー(Timer0/1/2)のカウンタによってコントロールされていますが、 Phase correct PWM(位相基準PWM)にした場合、このカウンタ(Timer2 用であれば TCNT2)は1クロックごとに


 0(=BOTTOM) 1 2 … 253 254 255(=TOP) 254 253 … 3 2 1

の繰り返しで三角波を描くように変化します。 0〜255 なので一見 256 * 2 = 512 クロックか?…と思いきや、 実は TOP と BOTTOM だけは1回しか出現せず、それ以外の値が2回ずつ出現するので、 PWMの1周期は 510 クロックということになります。 BOTTOM にぶつかったときに Overflow (OVF) 割り込みが発生するようになっているので、 ちょうど1周期ごとに割り込みが発生することになります。

したがって割り込み周期は、クロック16MHzの場合 (1 / 16MHz) * 510 = 31.875μ秒となります。 周波数にすると約31.37kHzです。これなら可聴周波数を超えているため聞こえることはありませんし、 数kHz程度の高い音でも PWM で変調をかけられるようになります。

分周比を変えるには、ATMEGA328 のレジスタ設定を変更します(参考:Adjusting PWM FrequenciesSecrets of Arduino PWM)。

SPI経由でDACチップへ送る方法は?

もう一つのアプローチとして、SPI経由でD/Aコンバータ(DAC)に 音の大きさを次々と送って音を出す、という方法もあります。 ただ、この方法では PWM のように 8bit ではなく 12bit の値を転送しなければならないため、 ソフトウェア的な負荷(CPU使用率)が増すようです。 外付け IC をつけたのに CPU により大きな負荷がかかる、というのだけは避けたかったので、 結局、CAmiDion 2号機では PWM 方式に落ち着きました。

参考:AVRマイコン自身でサウンド生成を実現した例

出力ピン

音の PWM 出力ピンには Arduino のデジタル出力3番(ATMEGA328の5番ピン、OC2B)を使いました。 Timer2 の割り込みでタイミングをとっています。

音程と減衰音のコントロール

割り込み処理1回ごとに、波形テーブル上でどれくらい位相を進めるかによって、 音の高さ(周波数)が決まります。

音程テーブルは、オクターブが一番高い12音についてのみ、 PWMの1周期=31.875μ秒で進めるべき位相の値を持たせます。 1オクターブ下げるには周波数を半分にすればよいので、時間当たりの位相の値も半分にします。 半分にするだけなら右シフト演算だけで簡単、高速にできます。

位相は、音程の精度を重視するため 32bitの unsigned long 型にした整数値で表します。 桁あふれがおきるたびに一周期、という形にすれば最小限の計算で済みます。

単なる正弦波だけだと「ピーーー」っていう連続音ですが、 ADSR方式のエンベロープ制御により 「ポーン」っていう減衰音も出せるようになっています。

減衰音にするには、ボリュームを加味した計算を追加する必要があります。 ここで割り込み処理の中で / を使った除算を行うと、CPU クロックを多く消費して処理が間に合いません。 ATMEGA328 には乗算もシフト演算も CPU クロック1個分で済む命令が用意されているので、 これを有効活用すべく、* を使った乗算と、>> を使ったシフト演算 (これだけで 1/2 ずつ除算したことになる)を使っています。

ボリュームの下げ方はゆっくりでよく、しかも正確なタイミングは必要ないので、loop() の中で

        if( anrp->volume > 0 ) {
          byte dv = anrp->volume >> 5;
          if( dv == 0 ) dv = 1;
          anrp->volume -= dv;
        }
のようにして、毎回元のボリュームの何分の1かを引き算しながら減衰させるといった工夫をしています。

OSDN の CAmiDion blog もあわせて参照 → 電子楽器 CAmiDion 2号機


[ページ先頭へ戻る]