sound
gamebuinoライブラリのサウンドAPIは、Amiga modのような仕組みを取り入れていてメモリをあまり消費しないように作られている。
公式のフォーラムなどを見ても憶測が飛び交ったりしていてリファレンスも用意されないまま、利用が困難なためあまり使いこなしている例を見かけないサウンドAPI、コードを読んでビット構成を調べた結果を反映した。まだ間違いがあるかもしれない、引き続き調査する。
HISTORY
マクロを修正 11.07.17
Sound API
パターン
パターンセット
トラック
この3つを使って曲を組み立てる。
パターン
パターンは、楽譜でいうと あるパートの1小節とかに相当。長さは自由。
パターンセット
パターンセットは存在するパターンにIDを付けるためのものと思うとわかりやすい。
トラック
パターンセットに登録したパターンIDを並べて曲を組み立てるためのもの。
和音
最大4音となっているけれど初期状態では何をしても1音しか出ない。
gamebuinoライブラリのsetting.cにある
#define NUM_CHANNELS 1
この数字を0~4で編集すると最大4音まで出せるようになる。
予備知識として、MCUで音を出すというのは重労働なので処理速度とのトレードオフだと考えておくと良い。
では実際にパターン、パターンセット、トラックを使って音を出す一連のコードを作ってみよう。
ここでは2チャンネル使って音を出してみることにする。
その前に便利なマクロ。
#define NOTE(pitch, duration) ((uint16_t)duration << 8) + ((uint16_t)pitch << 2)
#define PAUSE(duration) NOTE(63, duration)
#define END() 0x0000
#define EFFECT(cmd,x,y) ((0x0F & cmd)<<2)|((0x1f & x)<<6)|((0x1f & y)<<11)| 1
enum NOTE
{
_Bb2, _B2, _C3, _Db3, _D3, _Eb3, _E3, _F3, _Gb3, _G3, _Ab3, _A3,
_Bb3, _B3, _C4, _Db4, _D4, _Eb4, _E4, _F4, _Gb4, _G4, _Ab4, _A4,
_Bb4, _B4, _C5, _Db5, _D5, _Eb5, _E5, _F5, _Gb5, _G5, _Ab5, _A5,
_Bb5, _B5, _C6, _Db6, _D6, _Eb6, _E6, _F6, _Gb6, _G6, _Ab6, _A6,
_Bb6, _B6, _C7, _Db7, _D7, _Eb7, _E7, _F7, _Gb7, _G7, _Ab7, _A7,
_Bb7, _B7, _OFF
};
これはgamebuinoのフォーラムのポストで見かけた超冴えたコードを引用、エフェクトのマクロを追加したものになってる。これを使って音を出してみよう。
※もともと半音をフラットで扱っていた。わたしはどっちかというとシャープの方がわかりやすい。これは完全に好みの問題だと思うけどここは使う人が使いやすい様に各自直す。表記の問題だけなので。
エフェクトは
/*
EFFECT
0:setVolume x: 0~9
1:Instrument x: 0=square wave 1=noise
2:VolumeSlide x: step duration y: step depth
3:Arpeggio x: step duration y: step depth
4:Tremolo x: step duration y: step depth
*/
こういう内容。
yはマイナスも扱える。ただ、sign + 4bitなので普通の書き方では書けない。
5bitのうちMSBはサインフラグなのでマイナスを扱いたい場合0x10を足し込むようにする。主にvolume slideやArpeggioで利用することになると思われる。
まずパターン。PAUSEというのは休符の扱い。
const uint16_t pt1[] PROGMEM = {
EFFECT(0,8,0),EFFECT(4,1,2),
NOTE(_C4,4),
NOTE(_D4,4),
NOTE(_E4,3),PAUSE(1),
NOTE(_D4,4),
NOTE(_E4,4),
NOTE(_F4,3),PAUSE(1),
END()
};
const uint16_t pt2[] PROGMEM ={
EFFECT(0,5,0),EFFECT(4,1,7),
NOTE(_E5,4),
NOTE(_F5,4),
NOTE(_G5,4),
NOTE(_F5,4),
NOTE(_G5,4),
NOTE(_A5,4),
END()
};
次にパターンセット
const uint16_t* const ps[] PROGMEM={pt1,pt2};
パターンを登録したpt1とpt2を登録、これはTrackから 0、1 というIDに見えるようになる。
で、肝心のトラック
const uint16_t t1[] PROGMEM ={0,1,0xFFFF};
const uint16_t t2[] PROGMEM ={1,0,0xFFFF};
パターンセット0と1を鳴らして0xFFFFは終端。
逆のパターンで鳴るt2というのも作っておいた。
では実際にこれで音を出すためのコードが次のとおり。
gb.sound.changePatternSet(ps,0); //ch1 patternset
gb.sound.changePatternSet(ps,1); //ch2 patternset
gb.sound.playTrack(t2, 0); //play channel 1
gb.sound.playTrack(t1, 1); //play channel 2
各チャンネルにパターンセットを登録、そしてトラックを再生。という流れ。
実際に鳴らしてみると単音の時とは雲泥の差、という(気がする)音が出る。少しうれしい。
デュレーションについて。
gamebuinoライブラリに限らず、トラッカーというものは垂直同期を1として、何回同期するまでをデュレーション(音の長さ)とするか、という作りになっている。のでわかりやすい16部音符、とかいう概念がない。
gamebuinoの場合、何回updateが来たら、というような考え方になる。
一番短い音を、どれくらいの長さで鳴らすか、を基準にして2^nで長さを決定してゆく。
たとえば一番短い音を16分音符として、デュレーション2として扱うとすると、
8分=4
4分=8
2分=16
全音符=32
という具合にデュレーションが決定する。
このあたりは面倒なのでマクロを書いた方がいいかもしれない。
ここからは調査中の事項
パターンのビット構成について。
0000 0000 0000 000x
xが1の場合エフェクト、そうでなければノート。またすべてゼロならパターンエンド。
エフェクトのビット構成
YYYY YXXX XXCC CC01
C=cmd
X=X
Y=Y (msbはサインフラグらしい)
最初からもっとしっかりコードを読めば良かった。
ノートのビット構成
DDDD DDDD NNNN NN00
N=NOTE
D=Duration