ギターが優しく泣いている2 (ソフト設計編)
引き続き、12弦ギターをシミュレートするプログラムを説明します。
■プログラムの設計
●ダウン・ストロークとアップ・ストローク
当たり前なのですが、ギターの演奏にはダウン・ストローク(上から下へ弾く)だけでなく、アップ・ストローク(下から上へ弾く)も必要です。そのアップ・ストロークは単純に1弦から6弦の方向に発音すればいいと考えていました。プログラムの構築がほぼ出来た段階でこの事実に気付いたので、実際にmidi出力してみると、おかしな音になってしまいました。各弦がT時間のズレを持っているので、後から発音するはずの音が先に発音するという不思議なものになったのでした。
●ギターのコード表
ギターを演奏するときに必ず目にするのがコード表です。これをリストのテーブルに落としこむ時に、コード表はTAB譜にも見えるなぁと思いました。TAB譜については詳しい人に教えてもらうとして、
●ダウン・ストロークとアップ・ストローク
当たり前なのですが、ギターの演奏にはダウン・ストローク(上から下へ弾く)だけでなく、アップ・ストローク(下から上へ弾く)も必要です。そのアップ・ストロークは単純に1弦から6弦の方向に発音すればいいと考えていました。プログラムの構築がほぼ出来た段階でこの事実に気付いたので、実際にmidi出力してみると、おかしな音になってしまいました。各弦がT時間のズレを持っているので、後から発音するはずの音が先に発音するという不思議なものになったのでした。
●ギターのコード表
ギターを演奏するときに必ず目にするのがコード表です。これをリストのテーブルに落としこむ時に、コード表はTAB譜にも見えるなぁと思いました。TAB譜については詳しい人に教えてもらうとして、
キーワードは、ギター TAB譜
今回作成したプログラムも、T時間を変えてやると弦の発音順番を変えることが出来るはずで、それならばアップ/ダウン・ストロークだけでなく、アルペジオ演奏もシミュレート出来るのでは思いつきました。さらにタイミングさえ揃えれば(みんな大好き)スリーフィンガーピッキングも思いのままさと発展しました。
キーワードは、アルペジオ演奏、スリーフィンガーピッキング
プログラムはそれぞれコンセプトが違っているので(つまり、作っているうちに)別々のものになりました。このブログは備忘録を兼ねていますので、それぞれの方法のそのままプログラムリストに出しています。
●ソフトウェア・タイマーについて
T時間、G時間、S時間のタイマーは合計36個あります。といっても各々直列的に動作するので実際にカウント動作しているのは12個です。これらのタイマーが扱う時間は数msから数秒と幅広いものになります。割り込み周期を1msにしたので、変数は16ビットサイズを使うと1msから65535msを計数できます。16ビットサイズの変数を12個も用意するのはマイコンの容量ではかなりしんどいところなので、タイマー制御はビット操作で行なうことにしました。プログラムを作成する時はフローチャートは作らず、フラグの機能割当てリストだけを見ながら、いきあたりばったりなので、フラグのビットがずれたりして、おかしな動作を引き起こしたりすると、デバッグ作業はほんとうに大変です。とはいえ、ビット演算は好きなんです。パズルを解くような感覚があります。余談になりますが、アセンブラであれこれ作業をしていた頃、アキュムレータの内容をゼロにしようと苦心していたのが、「XOR A」の一行で出来ると知った時にはとても感激しました。
●タイマー割り込み
今回作成したプログラムも、T時間を変えてやると弦の発音順番を変えることが出来るはずで、それならばアップ/ダウン・ストロークだけでなく、アルペジオ演奏もシミュレート出来るのでは思いつきました。さらにタイミングさえ揃えれば(みんな大好き)スリーフィンガーピッキングも思いのままさと発展しました。
キーワードは、アルペジオ演奏、スリーフィンガーピッキング
プログラムはそれぞれコンセプトが違っているので(つまり、作っているうちに)別々のものになりました。このブログは備忘録を兼ねていますので、それぞれの方法のそのままプログラムリストに出しています。
●ソフトウェア・タイマーについて
T時間、G時間、S時間のタイマーは合計36個あります。といっても各々直列的に動作するので実際にカウント動作しているのは12個です。これらのタイマーが扱う時間は数msから数秒と幅広いものになります。割り込み周期を1msにしたので、変数は16ビットサイズを使うと1msから65535msを計数できます。16ビットサイズの変数を12個も用意するのはマイコンの容量ではかなりしんどいところなので、タイマー制御はビット操作で行なうことにしました。プログラムを作成する時はフローチャートは作らず、フラグの機能割当てリストだけを見ながら、いきあたりばったりなので、フラグのビットがずれたりして、おかしな動作を引き起こしたりすると、デバッグ作業はほんとうに大変です。とはいえ、ビット演算は好きなんです。パズルを解くような感覚があります。余談になりますが、アセンブラであれこれ作業をしていた頃、アキュムレータの内容をゼロにしようと苦心していたのが、「XOR A」の一行で出来ると知った時にはとても感激しました。
●タイマー割り込み
割り込み時間は1msとして、T時間、G時間から16ビットのtimer1を使用することにしました。 弦と弦の時間差を扱うT時間は正確なところ不明です。 Youtubeの演奏風景の動画や実際の曲を波形で観測したりして、おおよその時間を割当てました。 1回のストロークで90msなので6等分して15ms。 12弦なので奇数弦と偶数弦の時間差は5msとすることにしました。 割り込みルーチン内でのタイマーカウントはT[]→G[]→S[]の順で行っていて、12個の配列要素ごとにカウントします。 ルーチン内では割り込みSWが入っていればカウントし、設定値になればカウントアップフラグを立てています。
■使用している主な変数(フラグ)の説明
ST[] :ソフトウェアタイマーの状態を管理しています
bit 76 54 32 10 S G T L 00:T 01:G 10:S 11:END L 00:OFF 01:ON 10:UP 11:Next(END) L 00:OFF 01:ON 10:UP 11:Next(END) L 00:OFF 01:ON 10:UP 11:Next(END) F[] :ギターのフレットで、F[]=0なら開放弦を意味しています N[] :開放弦でのmidi音階 V[] :midiのVelocity値 M[] :↓(Down) ↑ (Up) やアルペジオなどの演奏モード
0:OFF 1:Down 2:Up 3:Arpe1 4:Arpe2 5:Arpe3
tmrT,tmrG,tmrS :タイマー設定値
timer :割り込み内タイマー
measure :音符の順番
capo :ギターのカポタスト(カポスタトと覚えていた)
arpe_cnt :アルペジオの回数
EF 0xF0 // EndFile
NP 0xF1 // NotPlay
●共通テーブルの説明
const uint8_t code[18][12] = { //ギターコード表
// 6a b 5a b 4a b 3a b 2a b 1a b
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }, // 0:開放弦
{ NP, NP, 3, 3, 2, 2, 0, 0, 1, 1, 0, 0, }, // 1:C
{ NP, NP, 3, 3, 2, 2, 0, 0, 0, 0, 0, 0, }, // 2:CM7
{ NP, NP, 4, 4, 6, 6, 4, 4, 6, 6, 4, 4, }, // 3:C#7
{ NP, NP, NP, NP, 0, 0, 2, 2, 3, 3, 2, 2, }, // 4:D
{ NP, NP, NP, NP, 0, 0, 2, 2, 1, 1, 2, 2, }, // 5:D7
{ NP, NP, NP, NP, 0, 0, 2, 2, 3, 3, 1, 1, }, // 6:Dm
{ 0, 0, 2, 2, 2, 2, 1, 1, 0, 0, 0, 0, }, // 7:E
{ 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, }, // 8:Em
{ 1, 1, 3, 3, 3, 3, 2, 2, 1, 1, 1, 1, }, // 9:F
{ 2, 2, 4, 4, 4, 4, 2, 2, 2, 2, 2, 2, }, // 10:F#m
{ 3, 3, 2, 2, 0, 0, 0, 0, 0, 0, 3, 3, }, // 11:G
{ 3, 3, 2, 2, 0, 0, 0, 0, 0, 0, 1, 1, }, // 12:G7
{ NP, NP, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, }, // 13:A
{ NP, NP, 0, 0, 2, 2, 2, 2, 1, 1, 0, 0, }, // 14:Am
{ NP, NP, 2, 2, 4, 4, 4, 4, 4, 4, 2, 2, }, // 15:B
{ NP, NP, 2, 2, 1, 1, 2, 2, 0, 0, 2, 2, }, // 16:B7
{ NP, NP, 2, 2, 4, 4, 2, 2, 5, 5, 2, 2, }, // 17:B7sus4
};
const uint8_t open[12] = //開放弦のmidi音階
// E E A A D D G G B B E E
{ 40,52,45,57,50,62,55,67,59,59,64,64,};
const uint16_t T[12] = //各弦の時間差(ms単位)
{ 0, 5, 15, 20, 30, 35, 45, 50, 60, 65, 75, 80,};
const uint16_t G[12] = //G時間のテーブル
{ 200,200,500,500,200,200,200,200,200,200,200,200,};
const uint16_t S[12] = //S時間のテーブル
{ 0, 0, 0, 0, 50, 50, 50, 50, 50, 50, 50, 50,};
const uint16_t Tbl[24] = //ms単位
{ 0, 5, 15, 20, 30, 35, 45, 50, 60, 65, 75, 80,
90, 150,300,500,750,800,1000,1250,1500, 0, 0, 0,};
const uint8_t V[12] = // Velocity
{120,100,100,100,100,100,100,100,100,100,100,100,};
演奏にはいくつかのパターンがあり、それぞれで変数テーブルが異なっています。
テーブルの曲データは著作権の問題がありますので想定のものではありません。
●ストローク グループ
const uint8_t P[1][14] = { //曲データ code[][] =
{ 1, 9, 1, 9, 1, 1, 9, 11, 14, EF},};
const uint8_t M[] = //Down(1) Up(2)でリズムを刻みます
{ 1, 1, 1, 2, 1, 1, 1, 2, 1, EF,};
●アルペジオ1 グループ
const uint8_t P[1][9] = //曲データのコード
{ 2, 3, 2, 3, 2, 3, 2, 3,EF};
const uint8_t M[14] = //3はArpe1です
{ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, EF,};
const uint8_t CT[12] = // Timing from TBL[] 弦毎のタイミング
{NP,NP, 0, 0,NP,NP,14,14,15,15,16,16, };
●アルペジオ2 グループ
const uint8_t P[12][7] = {
{NP,NP,NP,NP,NP,NP,EF,}, // 6弦a
{NP,NP,NP,NP,NP,NP,EF,}, // 6弦b
{ 3,NP,NP, 3,NP,NP,EF,}, // 5弦a
{ 3,NP,NP, 3,NP,NP,EF,}, // 5弦b
{NP,NP,NP,NP,NP,NP,EF,}, // 4弦a
{NP,NP,NP,NP,NP,NP,EF,}, // 4弦b
{NP, 0,NP,NP,NP, 0,EF,}, // 3弦a
{NP, 0,NP,NP,NP, 0,EF,}, // 3弦b
{ 1,NP, 1,NP,NP,NP,EF,}, // 2弦a
{ 1,NP, 1,NP,NP,NP,EF,}, // 2弦b
{ 0,NP,NP,NP, 0,NP,EF,}, // 1弦a
{ 0,NP,NP,NP, 0,NP,EF,}, // 1弦b
};
const uint8_t Tap[12][7] = { // For Arp2 S:上位4bit G:下位4bit
{NP,NP,NP,NP,NP,NP,EF,}, // 6弦a
{NP,NP,NP,NP,NP,NP,EF,}, // 6弦b
{ 6,NP,NP, 5,NP,NP,EF,}, // 5弦a
{ 6,NP,NP, 5,NP,NP,EF,}, // 5弦b
{NP,NP,NP,NP,NP,NP,EF,}, // 4弦a
{NP,NP,NP,NP,NP,NP,EF,}, // 4弦b
{NP, 5,NP,NP,NP, 6,EF,}, // 3弦a
{NP, 5,NP,NP,NP, 6,EF,}, // 3弦b
{ 6,NP, 5,NP,NP,NP,EF,}, // 2弦a
{ 6,NP, 5,NP,NP,NP,EF,}, // 2弦b
{ 6,NP,NP,NP, 5,NP,EF,}, // 1弦a
{ 6,NP,NP,NP, 5,NP,EF,}, // 1弦b
};
●アルペジオ3 グループ
const uint8_t pattern[6] = { //3-finger pattern
// 112233445566 1b1a---6b6a is string no
0b0000001100001100, // 2-5
0b0000000011000000, // 3
0b0000001100000000, // 2
0b0000000000001100, // 5
0b0000110000000000, // 1
0b0000000011000000, // 3
};
今日の作業はここまで。暑かった夏もちょっとマシになって、よく眠れます。寝る前はもちろん赤ワインのオンザロックで乾杯。
※このブログで書かれた内容によって生じた損害等の一切の責任を負いかねますので、予めご了承下さい。