ギターが優しく泣いている
イントロの素晴らしい曲というと、ジョージ・ハリスンの「マイ・スウィート・ロード」が思い浮かびます。このイントロをシンセサイザーで演奏してみても、どうもしっくりいきません。それはギターには弦と弦の間に時間差があるからではないかと思い当たりました。ではと、DX7でギターをシミュレートしてやろうと考えたのです。デジタルシンセサイザーのDX7は同時発音数が16音もあります。だったら、6弦ギターではなく、12弦ギターも可能なはずです。わりと簡単な動機ですが、ギターのストローク奏法を実現しようとしたのが発端です。
■12弦ギターのチューニング
6弦の場合のチューニングは、多分、万国共通で、6弦からEADGBEですが、12弦の場合はいろいろな方法があるようです。ネットで調べると、ビートルズが愛用していたリッケンバッカー社の12弦ギターはEeAaDdGgBBEEの調弦になっていたので、今回はこの調弦方法を使いました。(他のはeEaAdDgGBBEE だそうです)。なお、12弦は低い方から、6a、6b、5a、5bという風に表しています。
6弦の場合のチューニングは、多分、万国共通で、6弦からEADGBEですが、12弦の場合はいろいろな方法があるようです。ネットで調べると、ビートルズが愛用していたリッケンバッカー社の12弦ギターはEeAaDdGgBBEEの調弦になっていたので、今回はこの調弦方法を使いました。(他のはeEaAdDgGBBEE だそうです)。なお、12弦は低い方から、6a、6b、5a、5bという風に表しています。
ギターのフレット番号(下の0から15)とmidiノートとの関連
6a
E
|
6b
e
|
5a
A
|
5b
a
|
4a
D
|
4b
d
|
3a
G
|
3b
g
|
2a
B
|
2b
B
|
1a
E
|
1b
E
|
|
---|---|---|---|---|---|---|---|---|---|---|---|---|
0
|
40 | 52 | 45 | 57 | 50 | 62 | 55 | 67 | 59 | 59 | 64 | 64 |
1
|
41 | 53 | 46 | 58 | 51 |
63
|
56
|
68 | 60 | 60 | 65 | 65 |
2 | 42 | 54 | 47 | 59 | 52 | 64 | 57 | 69 | 61 | 61 | 66 | 66 |
3 | 43 | 55 | 48 | 60 | 53 | 65 | 58 | 70 | 62 | 62 | 67 | 67 |
4 | 44 | 56 | 49 | 61 | 54 | 66 | 59 | 71 | 63 | 63 | 68 | 68 |
5 | 45 | 57 | 50 | 62 | 55 | 67 | 60 | 72 | 64 | 64 | 69 | 69 |
6 | 46 | 58 | 51 | 63 | 56 | 68 | 61 | 73 | 65 | 65 | 70 | 70 |
7 | 47 | 59 | 52 | 64 | 57 | 69 | 62 | 74 | 66 | 66 | 71 | 71 |
8 | 48 | 60 | 53 | 65 | 58 | 70 | 63 | 75 | 67 | 67 | 72 | 72 |
9 | 49 | 61 | 54 | 66 | 59 | 71 | 64 | 76 | 68 | 68 | 73 | 73 |
10 | 50 | 62 | 55 | 67 | 60 | 72 | 65 | 77 | 69 | 69 | 74 | 74 |
11 | 51 | 63 | 56 | 68 | 61 | 73 | 66 | 78 | 70 | 70 | 75 | 75 |
12 | 52 | 64 | 57 | 69 | 62 | 74 | 67 | 79 | 71 | 71 | 76 | 76 |
13 | 53 | 65 | 58 | 70 | 63 | 75 | 68 | 80 | 72 | 72 | 77 | 77 |
14 | 54 | 66 | 59 | 71 | 64 | 76 | 69 | 81 | 73 | 73 | 78 | 78 |
15 | 55 | 67 | 60 | 72 | 65 | 77 | 70 | 82 | 74 | 74 | 79 | 79 |
主弦は大文字、副弦は小文字で示します。
フレット番号が0というのは開放弦を表しています。
ノート番号は10進数表記です。
このリストを眺めているうちに重大な事実に気付きました。構想段階では、ギターの弦、フレットと音階の関連をテーブルデータに持つ必要があると考えていましたが、単純に考えて、大きなテーブルのデータを持つ必要はなく、演算で対応ができるとわかってしまったのです。(ギター弾きならあたりまえですけど)、
MIDIノート = 開放弦のデータ+フレット番号
実際に作成したテーブルデータは、MIDIノート番号の他、周波数(82Hz〜)や、音階記号(C2〜)をフレットごとに載せたものだったので、この努力の結果はいつか役に立つ時がくるのでしょう。
フレット番号が0というのは開放弦を表しています。
ノート番号は10進数表記です。
このリストを眺めているうちに重大な事実に気付きました。構想段階では、ギターの弦、フレットと音階の関連をテーブルデータに持つ必要があると考えていましたが、単純に考えて、大きなテーブルのデータを持つ必要はなく、演算で対応ができるとわかってしまったのです。(ギター弾きならあたりまえですけど)、
MIDIノート = 開放弦のデータ+フレット番号
実際に作成したテーブルデータは、MIDIノート番号の他、周波数(82Hz〜)や、音階記号(C2〜)をフレットごとに載せたものだったので、この努力の結果はいつか役に立つ時がくるのでしょう。
■使用するマイコン
曲のイントロだけなので、せいぜい数小節。4分音符で10個程度といったところです。しかし、鍵盤では、弦と弦の間のミリセカンドの時間差を手で弾くというのはむずかしいので、マイコンでmidi信号を制御する方法を考えました。(DX7のキーボードを使わないって・・・・)
回路的にはmidi出力信号だけなので、PIC12F1840のような、小ピンのマイコンで間に合うのですが、デバッグが非常にやりにくいので、PIC16F1827を使用することに決めました。回路図は以下のようになります。嬉しいことに5V単一電源が使用できます。
曲のイントロだけなので、せいぜい数小節。4分音符で10個程度といったところです。しかし、鍵盤では、弦と弦の間のミリセカンドの時間差を手で弾くというのはむずかしいので、マイコンでmidi信号を制御する方法を考えました。(DX7のキーボードを使わないって・・・・)
回路的にはmidi出力信号だけなので、PIC12F1840のような、小ピンのマイコンで間に合うのですが、デバッグが非常にやりにくいので、PIC16F1827を使用することに決めました。回路図は以下のようになります。嬉しいことに5V単一電源が使用できます。
この回路図はKiCADで描きました。実は、使いまわしで合理化しています。マイコン名とポート名を変えただけです。
ダウンストロークの場合、6弦aから矢印の方向に時間差をもって発音することになります。リアルタイムmidiで発音する場合、通常はG時間(ゲートタイム)とS時間(ステップタイム)でノートON信号とノートOFF信号をコントロールしますが、弦と弦の間の時間差を表現するためにT時間をつけることにしました。通常、S時間(ステップタイム)はG時間(ゲートタイム)と同時にカウントを開始すると定義されているようですが、ここでは簡単のため、G時間終了後としています。
プログラムの流れとしては、
プログラムの流れとしては、
●曲コードリストから演奏するコードをとりだす。
●6弦aからT時間が経過後に、コードに相当するmidiデータを発音(ノートON)。
●G時間後に発音を止めます(ノートOFF)。
●無音の状態でS時間カウントする。
●6弦aから1弦bまでの12弦の音をそれぞれ発音する。
●6弦aからT時間が経過後に、コードに相当するmidiデータを発音(ノートON)。
●G時間後に発音を止めます(ノートOFF)。
●無音の状態でS時間カウントする。
●6弦aから1弦bまでの12弦の音をそれぞれ発音する。
流れはとてもシンプルなのですが、各弦が3つのタイマーを持つことになるので、12*3=36のタイマーを制御することになりました。
■プログラムの動作確認方法
プログラム本体の説明をする前になんなのですが、どう確認するのかが問題です。2〜3音ならば聴いてわからないこともないのですが、12弦となればさすがに難しいです。
プログラム作成時に使用しているロジック・アナライザが重宝しました。このロジアナは波形で表示することはもちろん、midiプロトコルを理解して測定した結果を時間データ付きで出力してくれるので、msの時間差を確認することが出来ました。
プログラム本体の説明をする前になんなのですが、どう確認するのかが問題です。2〜3音ならば聴いてわからないこともないのですが、12弦となればさすがに難しいです。
プログラム作成時に使用しているロジック・アナライザが重宝しました。このロジアナは波形で表示することはもちろん、midiプロトコルを理解して測定した結果を時間データ付きで出力してくれるので、msの時間差を確認することが出来ました。
■便利グッズ
演奏に使用するタイマーが36個もありますので、ソフトウェアタイマーで実現することになります。といって、用意する変数が8ビット(または16ビット)サイズで36個もあると、マイコンのメモリーが不足してしまいます。仕方がないのでビット単位で制御することにしました。(実は大好きなのですけど)。こういうビット演算をする時はヒューレットパッカードのHP-16Cが役に立ちます。HEX(16進)からBIN(2進)のほとんどの演算ができるので重宝します。例えば、64ビットサイズの16進数の1の補数を2進数で表示したいなどが簡単にできます。
演奏に使用するタイマーが36個もありますので、ソフトウェアタイマーで実現することになります。といって、用意する変数が8ビット(または16ビット)サイズで36個もあると、マイコンのメモリーが不足してしまいます。仕方がないのでビット単位で制御することにしました。(実は大好きなのですけど)。こういうビット演算をする時はヒューレットパッカードのHP-16Cが役に立ちます。HEX(16進)からBIN(2進)のほとんどの演算ができるので重宝します。例えば、64ビットサイズの16進数の1の補数を2進数で表示したいなどが簡単にできます。
残念なことに本家のHP社のHP-16Cは廃版になっています。ネット中を探していたら、アンドロイド端末のアプリで、2社から出されているのを発見しました。その他、windowsのエミュレータでも存在しています。リアルの現物ではスイスの会社(スイスフランの支払いだから)がほぼ同一のものを発表されているので、今のが破損したら購入を検討したいです。余談ですが、HP社のHP-16Cはヤフオク、ebayなどのオークションでたまに見かけます。値段も高すぎず、妥当なところです。レア物という考え方ならば、本体というよりも付属のマニュアルの方でしょう。実は・・・・。ネットで探すとマニュアルもpdfファイルで見ることができるようです。
今日の作業はここまで。ビール(もとい、新ジャンル)が10月から値上げになるそうです。だもんで、飲みだめの日々に乾杯。
※このブログで書かれた内容によって生じた損害等の一切の責任を負いかねますので、予めご了承下さい。
ギターが優しく泣いている 3 (プログラム編)
変数とテーブルの説明だけで何かわからなくなってきました。時間を置くと本人が簡単に別人に変身してしまうので、開発時に作ったドキュメント(特になにげないメモ)を保存しておくことは本当に重要だと痛感します。それでも、めげずにプログラム本体の説明をします。
//*************************************************************
// 12弦ギターのシミュレート
// MPLAB Ver.5.25
// XC8 Ver2.10
//*************************************************************
uint8_t t_no;
uint8_t g_no;
uint8_t s_no;
uint8_t arpe_no;
uint16_t tab; //1bit:6(12)弦・・・12bit:1弦
uint16_t pattern[6];
uint8_t point;
uint8_t dnib;
uint8_t unib;
// CONFIG1
#pragma config IESO = ON
#pragma config BOREN = ON
#pragma config PWRTE = OFF
#pragma config FOSC = INTOSC
#pragma config FCMEN = OFF
#pragma config MCLRE = ON
#pragma config CP = OFF
#pragma config CPD = OFF
#pragma config WDTE = OFF
#pragma config CLKOUTEN = ON
// CONFIG2
#pragma config WRT = OFF
#pragma config LVP = OFF
#pragma config STVREN = ON
#pragma config PLLEN = OFF
#pragma config BORV = LO
#define _XTAL_FREQ 32000000
#define ON 1
#define OFF 0
#define EF 0xF0 // EndFileを示す識別子
#define NP 0xF1 // NotPlayを示す識別子
#define T0COUT 206 // Timer0 100us:206
#define PRESCALE 4 // 100μs:4
//*************************************************************
//割り込みルーチン
//*************************************************************
void __interrupt () InterTimer( void ){
//結局、タイマ0割り込みは使用しませんでした
if(INTCONbits.TMR0IF == 1){
TMR0 = T0COUT; //100us
if(Task_SW >= 3) Task_SW = 0;
Task_SW++;
INTCONbits.TMR0IF = 0;
}
//タイマ1割り込み
if(PIR1bits.TMR1IF == 1){
TMR1H = 224; //1ms timer
TMR1L = 192;
//T時間のカウントを行なう
for( tcount = 0; tcount < 12; tcount++ ){
// T bits 01:ON
if((ST[tcount] & 0b11111111) == 0b00000100){
timer[tcount]++;
if(timer[tcount] > tmrT[tcount]) {
ST[tcount] &= 0b11110011;
// T bits 10 up
ST[tcount] |= 0b00001000;
timer[tcount] = 0;
}
}
}
//G時間のカウントを行なう
for( gcount = 0; gcount < 12; gcount++ ){
//G bits 01:ON
if((ST[gcount] & 0b11111111) == 0b00011101){
timer[gcount]++;
if(timer[gcount] > tmrG[gcount]) {
ST[gcount] &= 0b11001111;
// 01:up
ST[gcount] |= 0b00100000;
timer[gcount] = 0;
}
}
}
//S時間のカウントを行なう
for(scount = 0; scount < 12; scount++ ){
// S bits 01:ON
if((ST[scount] & 0b11111111) == 0b01111110){
timer[scount]++;
if(timer[scount] > tmrS[scount]) {
ST[scount] &= 0b00111111;
// 01:up
ST[scount] |= 0b10000000;
timer[scount] = 0;
}
}
}
PIR1bits.TMR1IF = 0;
}
}
//*************************************************************
// メインルーチン
//*************************************************************
void main(void){
OSCCONbits.SPLLEN = 1; // PLL X4
OSCCONbits.IRCF = 0b1110; // IRCF 8MHz_HF
OSCCONbits.SCS = 0; // INTOSC
OSCSTATbits.T1OSCR = 0; // disabled
OSCSTATbits.PLLR = 0; // 4XPLL disable
OSCSTATbits.OSTS = 0; // intosc
OSCSTATbits.HFIOFR = 0; // disabled
OSCSTATbits.HFIOFL = 0; // not0.2percent_acc
OSCSTATbits.MFIOFR = 0; // disabled
OSCSTATbits.LFIOFR = 0; // dsiabled
OSCSTATbits.HFIOFS = 0; // not0.5percent_acc
OSCTUNEbits.TUN = 0;// factory-calibrated frequency
// デジタルIOのみ
ANSELA = 0b00000000 ;
// RA1:out 他は適当
TRISA = 0b00111100 ;
PORTA = 0b00000000 ; // all L
// すべてデジタルIO
// MIDI系統のみ規定。他は適当。
ANSELB = 0b00000000 ;
TRISB = 0b00001110 ;
WPUB = 0b00000000 ;
PORTB = 0b00000000 ;
APFCON1bits.TXCKSEL = 1 ; //1:RB5 0:RB2
APFCON0bits.RXDTSEL = 1 ; //1:RB2 0:RB1
//Timer0 init(今回未使用)
prescaler = PRESCALE;
if (prescaler == 0) {
OPTION_REGbits.PSA = 1;
} else {
OPTION_REGbits.PSA = 0;
OPTION_REGbits.PS = prescaler - 1;
}
OPTION_REGbits.TMR0CS = 0; //fosc/4
OPTION_REGbits.TMR0SE = 0;
INTCONbits.TMR0IE = 1;
//INTCONbits.PEIE = 1;
//Timer1 init
T1CONbits.TMR1ON = 1; //有効にする
T1CONbits.TMR1CS = 0; //fosc/4
T1CONbits.T1CKPS = 0; //prescaler 0:2^0
TMR1H = 224; //1ms timer
TMR1L = 192;
PIE1bits.TMR1IE = 1;
INTCONbits.PEIE = 1;
//UART
TXSTAbits.CSRC = 0; // don't care
TXSTAbits.TX9 = 0; // 8 bit
TXSTAbits.TXEN = 1; // enable
TXSTAbits.SYNC = 0; // 非同期モード
TXSTAbits.SENDB = 0; // break
TXSTAbits.BRGH = 0; // Low
TXSTAbits.TRMT = 0; // TSR full
TXSTAbits.TX9D = 0;
//RCSTA
RCSTAbits.SPEN = 1; // serial port enable
RCSTAbits.RX9 = 0; // 8bit
RCSTAbits.SREN = 0; // don't care
RCSTAbits.CREN = 1; // enable
RCSTAbits.ADDEN = 0; // don't care
RCSTAbits.FERR = 0; // disable
RCSTAbits.OERR = 0; // over run error
RCSTAbits.RX9D = 0;
BAUDCON = 0; //BRG16 0
SPBRG = 15; // ボーレートを31250bpsに設定
INTCONbits.GIE = 1;
// 9n : midi channel = 0;
midi_ch = 0b10010000;
//ここから主動作のルーチンです。大まかな処理を説明すると
// ①P[ ][ ] から演奏するギターコード、M[ ]で奏法を選択
// ②Upストロークの場合は発音する順番を入れ替え
// ③発音時間は奏法により異なる。
while(1){
uint8_t i;
uint8_t j;
if( P[0][measure] != EF ){
// Level= 0:OFF 1:Down 2:Up 3-5:Arpe*
Level = M[measure] ;
bass_flg = OFF;
for(i = 0 ; i < 12 ;i++ ){
// Upストロークの時
if(Level == 2){
//発音順番を変更
j = ~i - 0b11110100;
F[i] = code[ P[0][measure] ][j] ;
N[i] = open[j];
} else { //Down,Arpe*の時
F[i] = code[ P[0][measure] ][i] ;
N[i] = open[i];
}
// 発音タイミング生成
if( F[i] != NP ){
string |= ( 1 << (uint16_t) i);
if(bass_flg == OFF){
bass_flg = ON;
//root音(bass)の決定
bass = i;
}
}
ST[i] = 0;
}
switch( Level ){
case 1: // DownとUp
case 2:
Stroke();
break;
case 3: // Arpe1
arpe_cnt = 6;
Arpe1();
break;
case 4: // Arpe2
Arpe2();
break;
case 5: // Arpe3
Arpe3();
break;
default:
break;
}
measure++;
} else{
measure = 0;
}
}
}
//*************************************************************
//各演奏パターンのルーチン
//*************************************************************
void Stroke(void){
// ①使用するテーブルはP[ ] [ ] と M[ ]です(Arpeとは異なる)。
// ②ST[ ]のフラグにより、T→G→Sの各12弦分のタイマーを作動。
// ③タイマー値を設定し、割り込みルーチンでカウントする。
// ④時間差をつけて12弦分の発音をおこなう
while( string ){ //すべての弦を演奏した?
for (t_no = 0;t_no < 12 ;t_no++){
if((ST[t_no] & 0b11110011) == 0b00000000){
//OFF?
if((ST[t_no] & 0b11111111) == 0b00000000){
//tmrT_sw = ON
ST[t_no] |= 0b00000100;
tmrT[t_no] = Tbl[t_no];
}
//Tタイマーアップ?
if((ST[t_no] & 0b11111111) == 0b00001000){
if(F[t_no] != NP){
//N[]フレットの音階:12(6)弦目から記入
note = F[t_no] + N[t_no] + capo;
while(TXIF == 0); // 送信可能?
TXREG = midi_ch;// ノートオン送信
while(TXIF == 0); // 送信可能?
TXREG = note; // 送信する
while(TXIF == 0); // 送信可能?
TXREG = V[t_no]; // 送信する
}
ST[t_no] &= 0b11111100;
//Ttimerが完了
ST[t_no] |= 0b00001100;
//tmrG_sw = ここではOFF;Gset 00→01
ST[t_no] |= 0b00000001;
}
}
}
// G
for (g_no = 0;g_no < 12 ;g_no++){
if((ST[g_no] & 0b11001111 ) == 0b00001101){
//OFF?
if((ST[g_no] & 0b11111111) == 0b00001101){
//tmrG_sw = ON;
ST[g_no] |= 0b00010000;
tmrG[g_no] = Tbl[tmr];
}
//Gタイマーアップ?
if((ST[g_no] & 0b11111111) == 0b00101101){
//g_no番目の弦の鳴動OFF(midiへOFF出力)
//Gtimerが完了
ST[g_no] |= 0b00110000;
if(F[g_no] != NP){
note = F[g_no] + N[g_no];
while(TXIF == 0); // 送信可能?
TXREG = midi_ch;// ノートオン送信
while(TXIF == 0); // 送信可能?
TXREG = note; // 送信する
while(TXIF == 0); // 送信可能?
TXREG = 0; // string.V[g_no] = 0
}
ST[g_no] &= 0b11111100;
//tmrS_sw = ここではOFF;Sset→10
ST[g_no] |= 0b00000010;
}
}
}
for (s_no = 0;s_no < 12 ;s_no++){ // S
if(( ST[s_no] & 0b00111111 ) == 0b00111110){
//OFF?
if((ST[s_no] & 0b11111111) == 0b00111110){
// tmrS_sw = ON;
ST[s_no] |= 0b01000000;
// 0ms
tmrG[g_no] = Tbl[0];
}
//Sタイマーアップ?
if((ST[s_no] & 0b11111111) == 0b10111110){
//Stimerが完了;Tset→11
ST[s_no] |= 0b11111111;
string &= ~(1 << s_no);
//13-16ビット目をマスク
string &= 0b0000111111111111;
}
}
}
}
}
//*************************************************************
void Arpe1(void){
// ①使用するテーブルはP[ ] [ ]とM[ ](Strokeとは異なる)と、CT[ ]。
// ②ST[ ]のフラグにより、T→G→Sの各12弦分のタイマーを作動。
// ③タイマー値を設定し、割り込みルーチンでカウントする。
// ④CT[ ]で示す時間差で各弦を発音する。
//すべての弦を演奏した?
while( string ){
//00:T 01:G 10:S 11:END
for (t_no = 0;t_no < 12 ;t_no++){ // T
if((ST[t_no] & 0b11110011) == 0b00000000){
//OFF?
if((ST[t_no] & 0b11111111) == 0b00000000){
//tmrT_sw = ON
ST[t_no] |= 0b00000100;
tmrT[t_no] = Tbl[ CT[t_no] ];
}
//Tタイマーアップ?
if((ST[t_no] & 0b11111111) == 0b00001000){
//t_no番目の弦を鳴動(midiへ出力)
if(F[t_no] != NP){
//N[]フレットの音階:12(6)弦目から
note = F[t_no] + N[t_no] + capo;
while(TXIF == 0); // 送信可能?
TXREG = midi_ch;// ノートオン送信
while(TXIF == 0); // 送信可能?
TXREG = note; // 送信する
while(TXIF == 0); // 送信可能?
TXREG = V[t_no]; // 送信する
}
ST[t_no] &= 0b11111100;
//Ttimerが完了
ST[t_no] |= 0b00001100;
//tmrG_sw = ここではOFF;Gset 00→01
ST[t_no] |= 0b00000001;
}
}
}
}
// G
for (g_no = 0;g_no < 12 ;g_no++){
if((ST[g_no] & 0b11001111 ) == 0b00001101){
//OFF?
if((ST[g_no] & 0b11111111) == 0b00001101){
//tmrG_sw = ON;
ST[g_no] |= 0b00010000;
//G時間は各弦共通
tmrG[g_no] = Tbl[ 14 ];
}
//Gタイマーアップ?
if((ST[g_no] & 0b11111111) == 0b00101101){
//g_no番目の弦の鳴動OFF(midiへOFF出力)
ST[g_no] |= 0b00110000; //Gtimerが完了
if(F[g_no] != NP){
note = F[g_no] + N[g_no];
while(TXIF == 0); // 送信可能?
TXREG = midi_ch; // ノートオン送信
while(TXIF == 0); // 送信可能?
TXREG = note; // 送信する
while(TXIF == 0); // 送信可能?
TXREG = 0; // string.V[g_no] = 0
}
ST[g_no] &= 0b11111100;
//tmrS_sw = ここではOFF;Sset→10
ST[g_no] |= 0b00000010;
}
}
}
// S
for (s_no = 0;s_no < 12 ;s_no++){
if(( ST[s_no] & 0b00111111 ) == 0b00111110){
//OFF?
if((ST[s_no] & 0b11111111) == 0b00111110){
//tmrS_sw = ON;
ST[s_no] |= 0b01000000;
//S時間は無しに設定
tmrS[s_no] = Tbl[0];
}
//Sタイマーアップ?
if((ST[s_no] & 0b11111111) == 0b10111110){
arpe_cnt--;
if(arpe_cnt == 0){
//if(ST[s_no] == 0b11111111)
string &= ~(1 << s_no);
//13-16ビット目をマスク
string &= 0b0000111111111111;
} else {
//Stimerが完了;Tset→11
ST[s_no] = 0b00000000;
}
}
}
}
}
//*************************************************************
void Arpe2(void){
// ①使用するテーブルはP[ ] [ ](Arpe2の)とTap[ ]。
// ②ST[ ]のフラグにより、T→G→Sの各12弦分のタイマー作動。
// ③タイマー値を設定し、割り込みルーチンでカウントする。
// ④メモリ節約のため、Tap[ ]でG時間とS時間を規定している。
// 発音タイミング生成
for( arpe_no = 0 ; arpe_no < 12 ; arpe_no++ ){
F[arpe_no] = P[arpe_no][measure];
if( F[arpe_no] != NP ){
string |= ( 1 << (uint16_t) arpe_no);
N[arpe_no] = open[arpe_no];
ST[arpe_no] = 0;
dnib = Tap[arpe_no][measure] & 0b00001111;
unib = (Tap[arpe_no][measure] >> 4) & 0b00001111;
tmrG[arpe_no] = Tbl[dnib];
tmrS[arpe_no] = Tbl[unib];
}
}
tab = string;
for (t_no = 0;t_no < 12 ;t_no++){
//演奏するフレットか?
if((tab >> t_no) & 1){
if(F[t_no] != NP){
//N[]フレットの音階:12(6)弦目から記入
note = F[t_no] + N[t_no] + capo;
while(TXIF == 0); // 送信可能?
TXREG = midi_ch; // ノートオン送信
while(TXIF == 0); // 送信可能?
TXREG = note; // 送信する
while(TXIF == 0); // 送信可能?
TXREG = V[t_no]; // 送信する
}
//Tタイマーは処理済みにする
ST[t_no] = 0b00001101;
}
}
while(string){
for (g_no = 0;g_no < 12 ;g_no++){
arpe_cnt = (uint16_t) g_no;
//演奏するフレットか?
if((tab >> arpe_cnt) & 1){
//G
if((ST[g_no] & 0b11001111) == 0b00001101){
//OFF?
if((ST[g_no] & 0b11111111) == 0b00001101){
//tmrG_sw = ON
ST[g_no] |= 0b00010000;
}
//Gタイマーアップ?
if((ST[g_no] & 0b11111111) == 0b00101101){
ST[g_no] |= 0b00110000;
if(F[g_no] != NP){
//N[]フレットの音階:12(6)弦目から記入
note = F[g_no] + N[g_no] + capo;
while(TXIF == 0); // 送信可能?
TXREG = midi_ch; // ノートオフ送信
while(TXIF == 0); // 送信可能?
TXREG = note; // 送信する
while(TXIF == 0); // 送信可能?
TXREG = 0; // 送信する
}
ST[g_no] &= 0b11111100;
//tmrS_sw = ここではOFF;Sset 01→10
ST[g_no] |= 0b00000010;
}
}
}
}
// S
for (s_no = 0;s_no < 12 ;s_no++){
arpe_cnt = (uint16_t) s_no ;
//演奏するフレットか?
if((tab >> arpe_cnt) & 1){
if(( ST[s_no] & 0b00111111 ) == 0b00111110){
//OFF?
if((ST[s_no] & 0b11111111) == 0b00111110){
//tmrS_sw = ON;
ST[s_no] |= 0b01000000;
}
//Sタイマーアップ?
if((ST[s_no] & 0b11111111) == 0b10111110){
//Stimerが完了;Tset→11
ST[s_no] |= 0b11111111;
string &= ~(1 << s_no);
//13-16ビット目をマスク
string &= 0b0000111111111111;
}
}
}
}
}
}
//*************************************************************
void Arpe3(void){
// ①使用するテーブルはP[ ] [ ]とCT[ ]とpattern[ ]です。
// ②ST[ ]のフラグにより、T→G→Sの各12弦分のタイマーを作動。
// ③タイマー値を設定し、割り込みルーチンでカウントする。
// ④pattern[ ]で1ピッキングあたりの弦を順次に発音する。
uint8_t t_no;
uint8_t g_no;
uint8_t s_no;
uint8_t arpe_no;
uint16_t tab; //1bit目:6(12)弦・・・12bit目:1弦
//アルペジオのroot音の探索と順番カウンタ
for(arpe_no = 0;arpe_no < 6; arpe_no++){
tab = pattern[arpe_no];
//4(7)弦から6(12)弦でroot音は?
if( tab & 0b0000000000111111){
tab &= 0b1111111111000000;
//12弦であること
tab |= (uint16_t) ( 0b000000011 << bass );
}
string = tab;
tmr = CT[arpe_no] ; // TmrG[tmr]
for (t_no = 0;t_no < 12 ;t_no++){
//演奏するフレットか?
if((tab >> t_no) & 1){
if(F[t_no] != NP){
//N[]フレットの音階:12(6)弦目から記入
note = F[t_no] + N[t_no] + capo;
while(TXIF == 0); // 送信可能?
TXREG = midi_ch; // ノートオン送信
while(TXIF == 0); // 送信可能?
TXREG = note; // 送信する
while(TXIF == 0); // 送信可能?
TXREG = V[t_no]; // 送信する
}
//Tタイマーは処理済みとする
ST[t_no] = 0b00001101;
}
}
while(string){
for (g_no = 0;g_no < 12 ;g_no++){
arpe_cnt = (uint16_t) g_no;
//演奏するフレットか?
if((tab >> arpe_cnt) & 1){
//G
if((ST[g_no] & 0b11001111) == 0b00001101){
//OFF?
if((ST[g_no] & 0b11111111) == 0b00001101){
//tmrG_sw = ON
ST[g_no] |= 0b00010000;
tmrG[g_no] = Tbl[tmr]; //
}
//Gタイマーアップ?
if((ST[g_no] & 0b11111111) == 0b00101101){
ST[g_no] |= 0b00110000;
if(F[g_no] != NP){
//N[]フレットの音階:12(6)弦目から記入
note = F[g_no] + N[g_no] + capo;
while(TXIF == 0); // 送信可能?
TXREG = midi_ch; // ノートオフ送信
while(TXIF == 0); // 送信可能?
TXREG = note; // 送信する
while(TXIF == 0); // 送信可能?
TXREG = 0; // 送信する
}
ST[g_no] &= 0b11111100;
//tmrS_sw = ここではOFF;Sset 01→10
ST[g_no] |= 0b00000010;
}
}
}
}
for (s_no = 0;s_no < 12 ;s_no++){ // S
arpe_cnt = (uint16_t) s_no ;
//演奏するフレットか?
if((tab >> arpe_cnt) & 1){
if(( ST[s_no] & 0b00111111 ) == 0b00111110){
//OFF?
if((ST[s_no] & 0b11111111) == 0b00111110){
//tmrS_sw = ON;
ST[s_no] |= 0b01000000;
}
//Sタイマーアップ?
if((ST[s_no] & 0b11111111) == 0b10111110){
//Stimerが完了;Tset→11
ST[s_no] |= 0b11111111;
string &= ~(1 << s_no);
//13-16ビット目をマスク
string &= 0b0000111111111111;
}
}
}
}
}
}
}
//*************************************************************■実は・・・・・
YouTubeの動画を見ていると、アップ/ダウン・ストロークの演奏の後にスライド・バーによる演奏が続いていたのでした。これもイントロに含まれますね。この部分のTAB譜を見ると、奇妙な記号が出てきます。これがスライド・ギター奏法をあらわすらしいです。今回はいろいろ試してみたのですが、同じような効果が実現できませんでした。ギターの音色も変えたほうが良さそうなので、しばらく研究してみることにします。
YouTubeの動画を見ていると、アップ/ダウン・ストロークの演奏の後にスライド・バーによる演奏が続いていたのでした。これもイントロに含まれますね。この部分のTAB譜を見ると、奇妙な記号が出てきます。これがスライド・ギター奏法をあらわすらしいです。今回はいろいろ試してみたのですが、同じような効果が実現できませんでした。ギターの音色も変えたほうが良さそうなので、しばらく研究してみることにします。
キーワードは、スライドバー 、 ボトルネック
余談ですが、スライド・バーをお酒の瓶のくびを切って自作したことがあります。あれはどこに行ったのだろう。でも、最近は専用のスライド・バーが安く買えるみたいです。
■感想
アップ・ダウンストロークの演奏ですが、DX7に接続してみたところ、弦と弦の間の時間差や12弦ギターの感じは少しはでているように思いました。ストロークの時間(G時間)を適切に調整して、S時間は0にしてやると、いい線まではいくかなと思いました。著作権の問題で、コード進行や実際の演奏音は出せませんが、各時間をリアルタイムに調整できればもっと楽しめるでしょう。
そして、アルペジオ演奏(Arpe1〜Arpe3)ですが、どれもこれも一長一短があるようです。おすすめしないのはArpe2グループで、参考のデータを見て「これは・・・」と感じたら多分正解です。普通の楽譜がコスパ最高と思えるでしょう。指使いをリアルに再現できるのはArpe3のほうです。何よりピッキングのタイミングが重要みたいです。プログラムを後から付け足して行く方法で仕上げたので、テーブルのデータがわかりづらくなってしまいました。
最後にもっとおもしろいようにするために以下のように改善を図っていこうと思います。
そして、アルペジオ演奏(Arpe1〜Arpe3)ですが、どれもこれも一長一短があるようです。おすすめしないのはArpe2グループで、参考のデータを見て「これは・・・」と感じたら多分正解です。普通の楽譜がコスパ最高と思えるでしょう。指使いをリアルに再現できるのはArpe3のほうです。何よりピッキングのタイミングが重要みたいです。プログラムを後から付け足して行く方法で仕上げたので、テーブルのデータがわかりづらくなってしまいました。
最後にもっとおもしろいようにするために以下のように改善を図っていこうと思います。
●変化を目で見えるようにする
●時間(T,G,S)・ノート信号・Volumeはロータリーエンコーダー(RE)などで操作して、連続で変化できるようにすること
●コード名やテンポ、ストロークなどはわかりやすく指定できること。
やはりピアノロールのようなグラフィカル画面でタイミングやノートを変化出来るようにしたほうが良い。
●時間(T,G,S)・ノート信号・Volumeはロータリーエンコーダー(RE)などで操作して、連続で変化できるようにすること
●コード名やテンポ、ストロークなどはわかりやすく指定できること。
やはりピアノロールのようなグラフィカル画面でタイミングやノートを変化出来るようにしたほうが良い。
今日の作業はここまで。
食材店で眺めていたらコアントローの54度というのを見つけた。アブサン化したのと思ったら製菓用のらしかった。香りが大好きなコアントロ・サワーで乾杯。
**** 回路図が非公開になっていたので修正しました。*****
※このブログで書かれた内容によって生じた損害等の一切の責任を負いかねますので、予めご了承下さい。