無限音階

無限音階(シェパードトーン/Shapard Tone)

この言葉を知ったのはこの作品(nanorgan)がきっかけでした。

▲1オクターブしか鍵盤がないのに両端が連続しているように聞こえるオルガンです。

同じ音名でオクターブ違いの音を重ねているので、オクターブの違いが覆い隠されているわけです。これはコンパクトでいいですね。

一方、CAmiDion のほうは…

CAmiDion3号機でBm7(-5)のコードを鳴らした直後

オクターブの違いを左端の直線VRで操作し、MIDIがサポートする全ての音域をカバーしています。もしこれを無限音階にしたら…

さっそくやってみた。

波形はExcelで作っているので、シートに追加。ワークシート関数の計算式はこんな感じ:

=TRUNC(
	(( SIN(PI()*A1/128)
	 + SIN(PI()*A1/64)
	 + SIN(PI()*A1/32)
	 + SIN(PI()*A1/16)
	 + SIN(PI()*A1/8)
	 + SIN(PI()*A1/4)
	 + SIN(PI()*A1/2)
	 + SIN(PI()*A1)
	) * 31 + 127) / $B$35, 0
) & ","

ここで A1 は 0 ~ 255 を並べたセルの一つ、$B$35 は同時発音数=6です。この数で同時発音しても合計が 0 ~ 255 をはみ出さないように作ります。同時発音数が6なら 0 ~ 42 の範囲です。

セルを fill していくと、”数字,” の羅列ができるので、そのままArduinoスケッチへコピー&ペースト。

PROGMEM const byte shepardToneWavetable[] = {
/* Excelからここへペースト */
};

結果はみごと成功!オクターブ調整VRを変えても変化した感じがほとんどしないという不思議な音色になりました。

…ということは、オクターブ調整VRなしでもいける!

そして出来上がったのがこれ。CAmiDion 4号機です。

CAmiDion 4号機の基板

オクターブ調整VRをなくし、ATMEGA328のピンをめいっぱい使って外付けデジタルICを使わずに実装。これによりプルアップ抵抗もATMEGA328に内蔵されたものを有効化するだけでよくなったので外付け不要。部品点数を極限まで減らすことができました(ICの下にはリセットピンのプルアップ抵抗10kΩと、バイパスコンデンサ0.1μFが入っています)。

あとは電池やアンプをつけてケースに入れられれば、コンパクトな CAmiDion 4号機が出来上がりそうです。

参考までにスケッチをいつもの作業部屋に上げてあります。4号機の回路図はまだ載せていませんが、キーマトリクスは3号機と全く同じで、スケッチを見れば使用するピン番号が書いてあるのでだいたい想像がつくと思います。3号機/2号機用のスケッチも無限音階の波形を追加したバージョンにしてあります。

【2012/11/21追記】12月1日~2日に行われる Maker Faire Tokyo 2012 での電子楽器 CAmiDion の出展場所が判明したのでお知らせします。7Fb 交流サロンです。上の動画にある無限音階オルガン nanorgan の方も同じ部屋で beatnic合同出展するそうです。

【2012/11/25追記】動画を作りました。

剰余を高速に求める

MIDIや音階の処理をしていると、% 演算子で剰余を計算したくなるときがあります。
例えば x % 12 はMIDIノート番号 0 ~ 127 からオクターブ分を抜いて 0 ~ 11 の値に置き換えたいときによく使います。

ところが、C言語の剰余演算子を負数と一緒に使うと、結果も負数になってしまいます。
MIDIノート番号には負数が出てこないので影響はないのですが、五度圏を文字盤に見立てた「時刻」の値(調号の♯の数)など、一時的に負数が出てくる場合、結果が負数になったら +12 するといった対応が必要になります。

ここでふと思ったのですが、割る数を固定にした剰余の計算なら高速化できるのではないか?
そう、例えば x & 3 のようにビットAND演算子を使って二進数の11でマスクすれば、どんな値でも下2ビットだけになって、結果的に4で割った余りが出せますよね。しかもこの方法なら割られる数が負数の場合であっても結果は負数にならず 0 ~ 3 の値が返されます。10進数でも、100で割った余りは下2桁を取り出すだけで簡単に求まりますよね!あれと全く同じ原理です。

では、他の値はどうか? CAmiDion では 12 の剰余、7 の剰余が使われますが、これはどうやって実現すればよいのでしょう?

調べてみたら、特定の剰余演算(mod)をトリッキーに行う方法という記事を発見。これを参考にしてみたところ、案外簡単に高速化できそうなことがわかりました。

まずは 7 の剰余。

byte PWMDACSynth::musicalMod7(char x) {
  while( x & 0xF8 ) x = (x >> 3) + (x & 7);
  if(x==7) return 0;
  return x;
}

これは、かけ算九九の 9 の段で十の位と一の位を足すといつも 9 になるという10進数の性質と全く同じ原理です。99や999でも桁が2桁や3桁に増えるだけで全く同じ性質を持ちます(例:99 * 2 = 198 → 1 + 98 = 99)。これを二進数に置き換えると、ビット1が全部並ぶ 3、7、15、31、… のような数値が、10進数でいう9とか99とか999に相当するわけです。このような数の剰余はビットシフトとビットANDと加算の繰り返しだけで求まり、しかも繰り返し回数は極めて少なくて済みます。

負数の場合も、while の条件で2の補数だからいつまでもループ抜けない?…と思いきや、実際は加算が行われるので必ずいつか正数になり、ビット0に転じてループを抜けるので永久ループにはなりませんし、結果に負数が返されることもありません。

7 の剰余は、五度圏の「時刻」値から FCGDAEB(ファドソレラミシ)の法則で並んだ音階を導き出すのに役立ちます。

使用例:五度圏での位置を音名(例:C、F#、Bb)に変換。F# と Gb の違いもはっきり区別できますし、B#、Cb、のような音名や、ダブルシャープ、ダブルフラットのつく音名にも対応できます。

    void setNote(char co5) {
      *bufp++ = "FCGDAEB"[PWMDACSynth::musicalMod7(++co5)];
      if( co5 < 0 ) *bufp++ = 'b'; // flat or double flat
      if( co5 >= 14 ) *bufp++ = 'x'; // double sharp
      else if( co5 >= 7 ) *bufp++ = '#'; // sharp
      if( co5 < -7 ) *bufp++ = 'b'; // double flat
    }

次に 12 の剰余。

byte PWMDACSynth::musicalMod12(char x) {
  char n = x >> 2;
  while( n & 0xFC ) n = (n >> 2) + (n & 3);
  x &= 3;
  if(n==3||n==0) return x;
  return x + (n << 2);
}

これは 12 = 3 * 4 であることを利用し、下2ビットはANDでマスクをかけて4の剰余として求め、その上の2ビットは 3 の剰余を求めた結果を継ぎ足しているだけです。もちろんこれも負数を引数に与えても常に0または正数が返されます。

この関数は最近作業部屋にあるPWMDAC_Synthのユーティリティとして実装しました。

Arduino のコンパイラとして使われていると思われる gcc はここまで最適化してくれるのかわかりませんが、少なくとも、剰余の結果として負数を返さないようにしたいときに有効な手段の一つだと思います。