SSブログ
前の10件 | -

スローなブギにしてくれ

 DX7を弾きこなすのがこのブログの壮大なテーマなのに、いっこうに演奏技術が上達していないではないか。練習もしないでプロのミュージシャンの動画みたいに演奏したいというのがそもそも無理なのだ。大いに反省して、それならばコンピュータ(マイコンだけど)を駆使して上達してやろうと思い立った。
やっぱりキーボードの練習というと、楽器メーカーから出ている鍵盤が教えてくれるものが頭に浮かびます。最近のは鍵盤そのものが光ったり、音声で指示してくれるものがあったりして、短期間で上達できそうです。また、youtubeをチェックすると、ウォーターフォール タイプでのレッスン動画というのがあって、画面上で、鍵盤の上に光の点が滝のように下に降りてくるので、その部分(鍵盤)を押さえると演奏出来るというものをよく見かけます。このようにいろいろな方法がありますが、DX7でも出来る方法ということで、キーボードを簡単にマスターできる装置を考えました。以下、キーボードの演奏理論や音楽技法などは全くといって知らないのでおかしな点があることをお許し願いたいです。

■キーボードマーカーの仕様
small_システム.png大まかな説明をすると、MIDIデータを取り込んで、ノート音データに応じた鍵盤のLEDを点灯させ、同時に指にも振動でお知らせする装置です(左のイメージ図ですが、あらためて見ると怪しい健康器具のような感じです。本当に効果あるの?と思ってしまう)

■回路図
OLED_Keyboard_marker.png

この回路図に書ききれていませんが、ダイナミック駆動回路は2系統のモーター回路と4系統のLEDドライブ回路および2系統のスイッチ入力回路を1ブロックとして4個のブロックから成り立っています。(実際に作るよりも回路図を書くほうがちょっと苦労しました。)

■部品リスト

 
                              部品 数量     特記事項
小計(円)
PIC16F1827 1    150
GM009605(SSD1306)
1
   $1.94
WaterProof Vibration motor 7×24mm   10  10個セット  $9.90
Si2301           pch-MOS FET 4  50pcs  $0.38
IRLML2502   nch-MOS FET 10  50pcs  $1.02
 面実装LED 1206(EIAJ 3216)タイプ 32  5色各100本  $2.10
1N4007 アキシャルリード  8  20本/パック  100
1N4148 アキシャルリード 32  50本/パック  100
両面ノンスルーホール基板 100×150mm  1  
 660
XH3P ベースポスト TOP型  25  @10  250
抵抗  80    $0.86
電解コンデンサ  2    20
積層セラミックコンデンサ  2    40
ジャンパーピン+ピンヘッダー  8 100+25  125
ICソケット18P
 1
   40
スイッチング電源アダプター 3.3V 2.2A  1    580
Φ2.1標準DCジャック 1    30

表の価格は参考です。また価格には送料込みと無しの場合があります。

■使用する部品の説明

●マイコン
microchip社のPIC16F1827を使用します。マイコンにはポート数が16本もありますが、今回は1オクターブ12音(ドから次のドまで)をLEDへ出力するポートと、指の指示用振動子が10本で出力は22本が必要になります。また、音階切替用に入力ポートも必要なので、4×8=32本のダイナミック駆動としました。正確には出力が4×6=24本、入力が4×2=8本となります。
●両面基板
ダイナミック入出力回路の基板はマトリクス(縦横が交差する)で配線するのが効率がよいです。片面基板だと、ランド側はすずめっき線で一直線に配線が出来ますが、反対面の方はすずめっき線の交差する箇所を何らかの方法で固定する必要があります。これがなかなか面倒で、それではと両面基板を使用するのですが、スルーホールが生成され表裏のランドに導通がある基板で配線するとかなり大変で悲劇が生じます。テスターで誤配線を探し出すのが配線にかかった時間よりも長くなるという状態になりかねません。マトリクス配線には何と言ってもノンスルーホール基板(NPTH基板とかNTH基板と呼ぶ)が良好です。
残念なことにNTH基板は取り扱いが少ないようで、通販サイトでもあまり見かけません。秋月電子でもサイズが小さな基板(95×72mm)しかありません。海外の通販サイトではNPTH基板で検索してもかかりませんでした。別名の検索ワードがあるのかもしれません。折角なので国内のネットで探しまわると値段が手頃でサイズの大きなNTH基板を見つけました。
キーワードは、e-toolz  ノンスルーホール ユニバーサル基板
今回は150mm×100mmの基板を使用しました。
●MOS FET
ダイナミック駆動のビットScan0〜3側のドライブにはPch-MOS FETを使用します。このFETのことをハイサイドスイッチと呼ぶのだそうです。振動子(モーター)の駆動電流はDC1.5Vの場合0.29Aという定格なのでPNP-Trでも問題ではないのですが、ちょっと使ってみたかったのです。Pch品は種類が多くないようです。Vishay社のSi2301という品番を選びました。出力ポートのbit0〜5のドライブにはNch-MOS FETを使用しています。VGS(th)が1.2VというIRF社のIRLM2502を選びました。どちらのFETもSOT23というパッケージのチップ品で米粒くらいの大きさなのですが、個人的にははんだ付けしやすいと思っています。(このことは後ほど説明します)
●振動子(モーター)
S_PIC_20210623_211309.JPG
スマホに内蔵されているブルブルする部品です。振動子と呼んでいますが中身はモーターです。形状は指にフィットするように棒状のものを探しました。時分割で駆動して問題はないのかと思いましたが、影響は無いようです。動作させるうちにマッサージする装置と勘違いしそうになりました。

●OLED
S_PIC_20210623_211551.JPG
128×64ドットの表示器が2ドルほどで売られているのを見つけました。OLED(有機EL)なのでバックライトが不要となります。GM009605と本体に表示がありますが調べてみるとSSD1306という型番で探すとマニュアルを見つけることが出来ました。I2C通信タイプなので配線本数が少なくて済みます。キャラクタジェネレータが内蔵されていないので自力で用意する必要がありますが、先人の皆様のおかげで簡単に実装することが出来ました。5×8ドットの文字は小さくて見にくいのでちょっと工夫が必要でした。
キーワードは、128×64ドット 0.96インチ OLED SSD1306 I2C Arduino
表示色は白、青、黄色などがありますが、白色が見やすいです。今回の回路には特に必要という訳ではないですが、デバッグ時にあると非常に便利と個人的には思っています。
●チップLED
S_PIC_20210623_211954.JPG
鍵盤の上部につけるガイド用のLEDです。白鍵盤は白色LED、黒鍵盤は黄色LEDを使用しています(黒色LEDってあるのかな)。サイズは3216(EIAJ)なのではんだ付けは比較的楽です。左の画像はLEDの裏側です。三角のマークがカソードを示していると初めて知りました。
今日はここまでです。さぁ、ビールを飲みましょう。出張の帰りに、ビール缶とおかき、いかの燻製、ピーナッツなどが入ったおつまみセットや貝のひもなんかを買いこんで新幹線で飲んだ「せんべろ出張」がなつかしいです。
文中で使用した鍵盤のイラストは「来夢来人さんのピアノの鍵盤のフリー素材」より引用しました。
https://ecsozai.civillink.net/largeillust/suisougaku3832.html
手のイラストは「季節行事の無料イラスト集」より引用しました。
https://shinbunsozai.info/hand/
※このブログで書かれた内容によって生じた損害等の一切の責任を負いかねますので、予めご了承下さい。

nice!(0)  コメント(0) 
共通テーマ:趣味・カルチャー

ギターが優しく泣いている

イントロの素晴らしい曲というと、ジョージ・ハリスンの「マイ・スウィート・ロード」が思い浮かびます。このイントロをシンセサイザーで演奏してみても、どうもしっくりいきません。それはギターには弦と弦の間に時間差があるからではないかと思い当たりました。ではと、DX7でギターをシミュレートしてやろうと考えたのです。デジタルシンセサイザーのDX7は同時発音数が16音もあります。だったら、6弦ギターではなく、12弦ギターも可能なはずです。わりと簡単な動機ですが、ギターのストローク奏法を実現しようとしたのが発端です。

■12弦ギターのチューニング
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〜)をフレットごとに載せたものだったので、この努力の結果はいつか役に立つ時がくるのでしょう。

■使用するマイコン
曲のイントロだけなので、せいぜい数小節。4分音符で10個程度といったところです。しかし、鍵盤では、弦と弦の間のミリセカンドの時間差を手で弾くというのはむずかしいので、マイコンでmidi信号を制御する方法を考えました。(DX7のキーボードを使わないって・・・・)
回路的にはmidi出力信号だけなので、PIC12F1840のような、小ピンのマイコンで間に合うのですが、デバッグが非常にやりにくいので、PIC16F1827を使用することに決めました。回路図は以下のようになります。嬉しいことに5V単一電源が使用できます。


Guitar.png

この回路図はKiCADで描きました。実は、使いまわしで合理化しています。マイコン名とポート名を変えただけです。

■動作の説明
下図はギターのフレットと指で押さえる点をあらわしています。
GateTiME.png

ダウンストロークの場合、6弦aから矢印の方向に時間差をもって発音することになります。リアルタイムmidiで発音する場合、通常はG時間(ゲートタイム)とS時間(ステップタイム)でノートON信号とノートOFF信号をコントロールしますが、弦と弦の間の時間差を表現するためにT時間をつけることにしました。通常、S時間(ステップタイム)はG時間(ゲートタイム)と同時にカウントを開始すると定義されているようですが、ここでは簡単のため、G時間終了後としています。
プログラムの流れとしては、

●曲コードリストから演奏するコードをとりだす。
●6弦aからT時間が経過後に、コードに相当するmidiデータを発音(ノートON)。
●G時間後に発音を止めます(ノートOFF)。
●無音の状態でS時間カウントする。
●6弦aから1弦bまでの12弦の音をそれぞれ発音する。

流れはとてもシンプルなのですが、各弦が3つのタイマーを持つことになるので、12*3=36のタイマーを制御することになりました。

■プログラムの動作確認方法
プログラム本体の説明をする前になんなのですが、どう確認するのかが問題です。2〜3音ならば聴いてわからないこともないのですが、12弦となればさすがに難しいです。
プログラム作成時に使用しているロジック・アナライザが重宝しました。このロジアナは波形で表示することはもちろん、midiプロトコルを理解して測定した結果を時間データ付きで出力してくれるので、msの時間差を確認することが出来ました。

MIDI_Guitar出力.png

 













■便利グッズ
演奏に使用するタイマーが36個もありますので、ソフトウェアタイマーで実現することになります。といって、用意する変数が8ビット(または16ビット)サイズで36個もあると、マイコンのメモリーが不足してしまいます。仕方がないのでビット単位で制御することにしました。(実は大好きなのですけど)。こういうビット演算をする時はヒューレットパッカードのHP-16Cが役に立ちます。HEX(16進)からBIN(2進)のほとんどの演算ができるので重宝します。例えば、64ビットサイズの16進数の1の補数を2進数で表示したいなどが簡単にできます。

Soft_HC16C_120157s.JPG


HP16C_120432s.JPG









残念なことに本家のHP社のHP-16Cは廃版になっています。ネット中を探していたら、アンドロイド端末のアプリで、2社から出されているのを発見しました。その他、windowsのエミュレータでも存在しています。リアルの現物ではスイスの会社(スイスフランの支払いだから)がほぼ同一のものを発表されているので、今のが破損したら購入を検討したいです。余談ですが、HP社のHP-16Cはヤフオク、ebayなどのオークションでたまに見かけます。値段も高すぎず、妥当なところです。レア物という考え方ならば、本体というよりも付属のマニュアルの方でしょう。実は・・・・。ネットで探すとマニュアルもpdfファイルで見ることができるようです。


今日の作業はここまで。ビール(もとい、新ジャンル)が10月から値上げになるそうです。だもんで、飲みだめの日々に乾杯。

※このブログで書かれた内容によって生じた損害等の一切の責任を負いかねますので、予めご了承下さい。

nice!(0)  コメント(0) 
共通テーマ:趣味・カルチャー

ギターが優しく泣いている 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譜を見ると、奇妙な記号が出てきます。これがスライド・ギター奏法をあらわすらしいです。今回はいろいろ試してみたのですが、同じような効果が実現できませんでした。ギターの音色も変えたほうが良さそうなので、しばらく研究してみることにします。

キーワードは、スライドバー 、 ボトルネック

余談ですが、スライド・バーをお酒の瓶のくびを切って自作したことがあります。あれはどこに行ったのだろう。でも、最近は専用のスライド・バーが安く買えるみたいです。
■感想
アップ・ダウンストロークの演奏ですが、DX7に接続してみたところ、弦と弦の間の時間差や12弦ギターの感じは少しはでているように思いました。ストロークの時間(G時間)を適切に調整して、S時間は0にしてやると、いい線まではいくかなと思いました。著作権の問題で、コード進行や実際の演奏音は出せませんが、各時間をリアルタイムに調整できればもっと楽しめるでしょう。
そして、アルペジオ演奏(Arpe1〜Arpe3)ですが、どれもこれも一長一短があるようです。おすすめしないのはArpe2グループで、参考のデータを見て「これは・・・」と感じたら多分正解です。普通の楽譜がコスパ最高と思えるでしょう。指使いをリアルに再現できるのはArpe3のほうです。何よりピッキングのタイミングが重要みたいです。プログラムを後から付け足して行く方法で仕上げたので、テーブルのデータがわかりづらくなってしまいました。
最後にもっとおもしろいようにするために以下のように改善を図っていこうと思います。
●変化を目で見えるようにする
●時間(T,G,S)・ノート信号・Volumeはロータリーエンコーダー(RE)などで操作して、連続で変化できるようにすること
●コード名やテンポ、ストロークなどはわかりやすく指定できること。
やはりピアノロールのようなグラフィカル画面でタイミングやノートを変化出来るようにしたほうが良い。

 
今日の作業はここまで。
食材店で眺めていたらコアントローの54度というのを見つけた。アブサン化したのと思ったら製菓用のらしかった。香りが大好きなコアントロ・サワーで乾杯。

**** 回路図が非公開になっていたので修正しました。*****

※このブログで書かれた内容によって生じた損害等の一切の責任を負いかねますので、予めご了承下さい。



nice!(0)  コメント(0) 
共通テーマ:趣味・カルチャー

ギターが優しく泣いている2 (ソフト設計編)

引き続き、12弦ギターをシミュレートするプログラムを説明します。

■プログラムの設計
●ダウン・ストロークとアップ・ストローク
当たり前なのですが、ギターの演奏にはダウン・ストローク(上から下へ弾く)だけでなく、アップ・ストローク(下から上へ弾く)も必要です。そのアップ・ストロークは単純に1弦から6弦の方向に発音すればいいと考えていました。プログラムの構築がほぼ出来た段階でこの事実に気付いたので、実際にmidi出力してみると、おかしな音になってしまいました。各弦がT時間のズレを持っているので、後から発音するはずの音が先に発音するという不思議なものになったのでした。
●ギターのコード表
ギターを演奏するときに必ず目にするのがコード表です。これをリストのテーブルに落としこむ時に、コード表はTAB譜にも見えるなぁと思いました。TAB譜については詳しい人に教えてもらうとして、

キーワードは、ギター TAB譜

今回作成したプログラムも、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
             };

今日の作業はここまで。暑かった夏もちょっとマシになって、よく眠れます。寝る前はもちろん赤ワインのオンザロックで乾杯。

※このブログで書かれた内容によって生じた損害等の一切の責任を負いかねますので、予めご了承下さい。


nice!(0)  コメント(0) 
共通テーマ:趣味・カルチャー

真夜中の訪問者2(ソフト編)

■タイミング設計について
前回、Audacityで玄関インターホンの発生周波数を測定したと書きましたが、時間変化と発生周波数の関係が読み取れません。そこでパソコンに 「Spek」という名前のソフトをインストールしました。これで時系列にスペクトラムを表示してくれます。
キーワードは、ubuntu Spek 音響スペクトラムアナライザ
s玄関エントランス・スペクトラム.png
グラフでは玄関インターホンの鳴動音。縦軸が周波数、横軸が時間軸を表しています。
このグラフ(わかりにくいです)と実際のオーディオ波形から、判定時間を決めました。
100usのサンプリング周期で8個(メモリ限界でした)なので、10KHzですが、ナイキスト周波数は5KHzとなります。1区間は5/4=1.25KHzの範囲です。玄関インターホンが鳴動すると、3区間目のレベルが設定値を越えるのをとらえて判定しています。(これが正解かどうかはわかりません)

■プログラムの流れ
●コンデンサマイクからの入力を100usの周期でAD変換
●AD変換値からゼロ点を求める
●FFT処理
●結果から2乗和の平方根を算出
●計算値が判定レベル以上ならLEDを点滅

■動作
      ●起動確認のため、通電するとLEDが2回点滅します。
      ●電池残量確認のため、30秒に1回点滅します。
      ●検知の判定は2段階で、レベルとタイミングは固定値です。
     ①マイク入力にある周波数範囲で入力があった場合、
     ②それから一定時間後に、そのマイク入力が無音を検出し、
     ③その後、一定時間後にレベル以上を検出すると、玄関の呼び出しと 判定します
     ●呼び出しがあればLEDの点滅を20秒間続けます。

■開発環境について
●対象マイコンはPIC12F1840 クロック:内部 32MHz駆動
●グラフィックLCD(ST7735液晶モジュール)はきむ茶工房さんのskST7735ライブラリをまるごと使用しています。ただし、メモリサイズの関係からフォントは使用しませんでした。(#include "font.h"をコメントアウトしています)。
●MPLAB X IDEはv5.25@linux mint 18.3
●使用コンパイラはXC8 (v.1.45) 。コンパイル後のデータメモリは206バイト(80%)プログラムメモリは3583バイト(87%)でした。
●ライターはPICKIT3を使用しています。デバッグ用ビルドではコンパイルエラーが発生し、いろいろなメッセージをみかけました。このメッセージ自体がコピーできないので原因追求がしにくかったです。
●LCD用SPI通信とデバッガとの相性が悪いようです。FFT演算結果の確認をする時はLCD表示をOFFにすることで対応しました。もっと良い方法があるのかもしれません。
●LCDが誤表示をする時があります。ライブラリの問題ではないことは確認できましたが、よくわからないのでLCDを表示する直前に割り込みを禁止しています。di()とei()はマクロで、定義はヘッダーで定義済みなので気楽に使用しています。
●configのLVP(Low voltage programming enable)はONですよ。
■プログラムリスト
/*   動作クロックは 32MHz タイマー1は10msに設定。
 *                                                                   PIC              ST7735      
 *                                                                   SCK     ←→  SCL
 *  1 VDD              8 Vss                               SDO     ←→  SDA
 *  2 LED               7 SDO                             VDD     ←→  RES
 *  3 AN3in            6 SCK                        GLCD_RS ←→  DC
 *  4 MCLR            5 GLCD_RS                VDD     ←→  BL  
 *                                              
 *                 PICKIT3のピン番号
 *                               ⑥未使用
 * 7 RA0(ICSPDAT)  ④
 * 6 RA1(ICSPCLK)  ⑤
 *                               ③GND
 *       ____                ②VDD
 * 4 RA3(MCLR)       ①    
 * 
 * LCD_TFTのスペックは ST7735の緑タブ品(基板は1.8inch 128*120 RGB と記載あり)
 *                端子を上にして左が①             接続するマイコンのpin番号
 *                 ①GND                                  ⑧ :GND
 *                 ②VCC                                   ① :VCC
 *                 ③SCL                                   ⑥ :SCL
 *                 ④SDA                                   ⑦ :SDO 
 *                 ⑤RES                                   ⑧ :VCC
 *                 ⑥DC                                     ② :LCD_RS
 *                 ⑦CS                                     ⑤ :LCD_CS
 *                 ⑧BL                                      ① :VCC
 * 
 */

#pragma config FOSC     = INTOSC //内蔵osc使用
#pragma config WDTE     = OFF //watch dog timer
#pragma config PWRTE    = OFF //Power up timer enable
#pragma config MCLRE    = ON  //Mclr/Vpp select bit
#pragma config CP       = OFF  //Code Protection bit
#pragma config CPD      = OFF   //Data Protection bit
#pragma config BOREN    = OFF  //Brown-out Reset enable bit 
#pragma config CLKOUTEN = OFF  //Clock out enable bit
#pragma config IESO     = OFF  //internal External switchover
#pragma config FCMEN    = OFF //Fall safe Clock monitor
 
#pragma config WRT    = OFF  //Flash Memory protection 
#pragma config PLLEN  = ON   //PLL enable bit 4*PLL
#pragma config STVREN = ON  //stack overflow/underflow reset
#pragma config BORV   = HI     //Brown-out reset Voltage
#pragma config LVP    = ON     //Low voltage programming enable
#include <xc.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <pic12f1840.h>

#define   xdebug 0
●LCD表示のライブラリ
LCD表示のライブラリは、きむ茶工房ガレージハウスさんの「TFT LCD(ST7735)モジュールに表示を行なう(skSDlib編)」を引用しました。

#include  "skSPIlib.h"
#include  "skST7735x.h"
//#include  "font.h"  

#define _XTAL_FREQ  32000000

#define PRESCALE  6
#define T0COUT  131

#define ON   1
#define OFF  0

#define samples 8

#define  upper_max  250
#define  lower_min    0

#define buffsize 8

#define Level1 120
#define Level2  50
#define Level3 120

int16_t   n;               // データ点数
int16_t   theta;           // m*2/n
int16_t  ar[samples];      //int32_t  入力データの実部(n点)
int16_t  ai[samples];      //int32_t  入力データ虚部(n点)
        
int16_t  spc[samples];

uint8_t incnt;
uint8_t AD_flag;
     
uint8_t spi_mode;
uint8_t acnt;
uint8_t dat;
uint8_t data;
uint8_t dat1;
uint8_t disp_mul;
uint8_t ad_mul;
uint8_t ii;
int16_t ad_buff1[buffsize];
uint8_t addata;
int16_t  mysqrt(uint16_t );
uint8_t prescaler;

//TMR1割り込み
uint16_t sec_tmr;
uint8_t  tmr_sw1;
uint8_t  tmr_sw2;
uint8_t  tmr_sw3;
uint8_t  tmr_sw4;
uint8_t  tmr_sw5;
uint8_t  tmr_sw6;
uint8_t  tmr_flg;

uint16_t sectimer1;
uint16_t sectimer2;
uint16_t sectimer3;
uint16_t sectimer4;
uint16_t sectimer5;
uint16_t sectimer6;

uint16_t xxx;
uint8_t sincnt;
uint8_t loop;
static const int8_t sin[] ={ //8*16  正弦テーブル(m点。sin(0..π))
                     0x00,0x03,0x06,0x09,0x0C,0x0F,0x12,0x15,
                     0x18,0x1C,0x1F,0x22,0x25,0x28,0x2B,0x2E,
                     0x30,0x33,0x36,0x39,0x3C,0x3F,0x41,0x44,
                     0x47,0x49,0x4C,0x4E,0x51,0x53,0x55,0x58,
                     0x5A,0x5C,0x5E,0x60,0x62,0x64,0x66,0x68,
                     0x6A,0x6C,0x6D,0x6F,0x70,0x72,0x73,0x75,
                     0x76,0x77,0x78,0x79,0x7A,0x7B,0x7C,0x7C,
                     0x7D,0x7E,0x7E,0x7F,0x7F,0x7F,0x7F,0x7F,
                     0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7E,0x7E,
                     0x7D,0x7C,0x7C,0x7B,0x7A,0x79,0x78,0x77,
                     0x76,0x75,0x73,0x72,0x70,0x6F,0x6D,0x6C,
                     0x6A,0x68,0x66,0x64,0x62,0x60,0x5E,0x5C,
                     0x5A,0x58,0x55,0x53,0x51,0x4E,0x4C,0x49,
                     0x47,0x44,0x41,0x3F,0x3C,0x39,0x36,0x33,
                     0x30,0x2E,0x2B,0x28,0x25,0x22,0x1F,0x1C,
                     0x18,0x15,0x12,0x0F,0x0C,0x09,0x06,0x03,
                    };

static const int8_t cos[]={ //8*16 余弦テーブル(m点。cos(0..π))
                     0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7E,0x7E,
                     0x7D,0x7C,0x7C,0x7B,0x7A,0x79,0x78,0x77,
                     0x76,0x75,0x73,0x72,0x70,0x6F,0x6D,0x6C,
                     0x6A,0x68,0x66,0x64,0x62,0x60,0x5E,0x5C,
                     0x5A,0x58,0x55,0x53,0x51,0x4E,0x4C,0x49,
                     0x47,0x44,0x41,0x3F,0x3C,0x39,0x36,0x33,
                     0x30,0x2E,0x2B,0x28,0x25,0x22,0x1F,0x1C,
                     0x18,0x15,0x12,0x0F,0x0C,0x09,0x06,0x03,
                     0x00,-0x03,-0x06,-0x09,-0x0C,-0x0F,-0x12,-0x15,
                    -0x18,-0x1C,-0x1F,-0x22,-0x25,-0x28,-0x2B,-0x2E,
                    -0x30,-0x33,-0x36,-0x39,-0x3C,-0x3F,-0x41,-0x44,
                    -0x47,-0x49,-0x4C,-0x4E,-0x51,-0x53,-0x55,-0x58,
                    -0x5A,-0x5C,-0x5E,-0x60,-0x62,-0x64,-0x66,-0x68,
                    -0x6A,-0x6C,-0x6D,-0x6F,-0x70,-0x72,-0x73,-0x75,
                    -0x76,-0x77,-0x78,-0x79,-0x7A,-0x7B,-0x7C,-0x7C,
                    -0x7D,-0x7E,-0x7E,-0x7F,-0x7F,-0x7F,-0x7F,-0x7F,
                    };

static const int8_t sintbl[] ={       // 8個
                     0x00,0x5A,0x00,-0x5A,0x00,0x5A,0x00,-0x5A,  
                     };

●割り込みルーチン

void __interrupt () InterTimer( void ){
    if(INTCONbits.TMR0IF && INTCONbits.TMR0IE){
        INTCONbits.TMR0IF = 0;   //基本タイマーは100uS
        TMR0 = T0COUT;
    }

    if(PIR1bits.TMR1IF && PIE1bits.TMR1IE){
        TMR1H =  99;             //10ms timer
        TMR1L = 192;

        sec_tmr ++ ;              //電池の生存確認用に30秒に一度点灯
        if(sec_tmr > 3000){       //10ms*100 = 1sec
            sec_tmr = 0;      
            PORTAbits.RA5 = 1;     //LED ON
            tmr_sw1 = ON;
        }
        if(tmr_sw1 == ON ){        //ON時間タイマー
            sectimer1++;
            if(sectimer1 > 25){      //LED 250ms ON
                sectimer1 = 0;
                PORTAbits.RA5 = 0; //LED OFF
                tmr_sw1 = OFF;
            }
        }

        //報知のシーケンスが検出レベルに達した時(tmr_sw2がONした時) 点滅動作へ
        if(tmr_sw2 == ON){         //検出後の点滅タイマー
            sectimer2++;
            if(sectimer2 > 25){    //点滅のON時間250ms
                tmr_sw2 = OFF;
                sectimer2 = 0;
                PORTAbits.RA5 = OFF;     //LED OFF
                tmr_sw3 = ON;
            }
        }

        if(tmr_sw3 == ON){         //検出後の点滅タイマー OFF時間
            sectimer3++;
            if(sectimer3 > 40){    //点滅のOFF時間400ms
                tmr_sw3 = OFF;
                tmr_sw2 = ON;
                tmr_sw4 = ON;
                sectimer3 = 0;
                PORTAbits.RA5 = ON;     //LED 
            }
        }

        if(tmr_sw4 == ON ){  //sw2がON検出後の点滅している時間
            sectimer4++;
            if(sectimer4 > 2000){      //点滅を20秒で終了
                tmr_sw2 = OFF;
                tmr_sw3 = OFF;
                tmr_sw4 = OFF;
                sectimer4 = 0;                
                PORTAbits.RA5 = OFF;     //LED 
            }
        }

        if(tmr_sw5 == ON ){
            sectimer5++;
            if((sectimer5 >= 175) && (sectimer5 <= 300) ) {           //2375
                if( data < Level2){       //規定時間経過した時無音か
                    tmr_sw5 = OFF;
                    tmr_sw6 = ON;
                    sectimer6 = 0;
                }
            }
        }

        if(tmr_sw6 == ON) {
            sectimer6++;
            if((sectimer6 >= 150) && (sectimer6 <= 325) ) {           //3875
                if( data > Level3){   //規定時間経過した時レベルあるか
                    tmr_sw6 = OFF;
                    tmr_sw2 = ON;       //同時にLEDの点滅タイマーを起動
                    sectimer6 = 0;
                }
            }
        }
        
        if((sectimer5 > 3000)||(sectimer6 > 3000)) { //カウンターのリセット
            tmr_sw5 = OFF;
            tmr_sw6 = OFF;
            sectimer5 = 0;
            sectimer6 = 0;
        }
        
        PIR1bits.TMR1IF = 0;     
    }
}

●平方根ルーチン
以下のコードはプログラムモグモグさんの「平方根なんてビット演算ですればいいじゃない」より引用しました。重複防止のため、変数名を変えています。

        int16_t mysqrt( uint16_t xxx ){
        int16_t  aaa = 0;
        int16_t  ccc = 0;
        int16_t  yyy = 0;
        int16_t  iii = 0;
  
        int16_t  ttt = (int16_t )xxx;
  
        while(ttt >>= 1){
            ++iii;
        }
        for(iii += iii & 1; iii >= 0; iii -= 2){
            ccc = (yyy << 1 | 1) <= xxx >> iii;
            aaa = aaa << 1 | ccc;
            yyy = yyy << 1 | ccc;
            xxx -= ccc * yyy << iii;
            yyy += ccc;
        }
        return aaa;
     } 


void main(void){
        OSCCONbits.SPLLEN = 1;              
        OSCCONbits.IRCF = 0b1110;  // 内蔵発振周波数は8MHz
        OSCCONbits.SCS = 0b00;	  // クロックソースは内蔵発振or外部クロック                
        
        //IO_init
        APFCONbits.SDOSEL = 0;       // SDOに割り付け
        TRISA = 0b00011000;          //RA5:O, RA4:I, RA3:I,  RA2:O, RA1:O, RA0:O
        PORTAbits.RA5 = 0;

        ANSELAbits.ANSA4 = 1;         // AN3(RA4) only           
        TRISAbits.TRISA4 = 1;          
        
        ADCON0 = 0b00001101;           // AN3(RA4) only
        ADCON1 = 0b00010000;
        OPTION_REGbits.nWPUEN = 0;    // 弱プルアップ 0がenable  
        WPUA   = 0b00101100;

       
        //電源投入時 2回LED点滅
        PORTAbits.RA5 = 1;
        __delay_ms(200);
        PORTAbits.RA5 = 0;
        __delay_ms(400);
        PORTAbits.RA5 = 1;
        __delay_ms(300);
        PORTAbits.RA5 = 0;

if(xdebug){
        SSP1CON1bits.WCOL = 0;
        SSP1CON1bits.SSPOV = 0;
        
        SSP1CON1bits.SSPEN = 1;     //SPI disenable
        SSP1CON1bits.SSPM  = 2;      //SPI_MASTER 2:Fosc/64
        SSP1STATbits.SMP   = 0;        //sampling data:middle
        SSP1CON1bits.CKP = 0;         //アイドル時 Lで、立ち上がりで送信
        SSP1STATbits.CKE = 1;
        SSP1CON1bits.SSPEN = 1;    //SPI enable
}

 //
 // Graphic LCD (SPI) ST7735_TFT-LCD
 //
    if(xdebug){    
        GLCD_DisplayInit();
        GLCD_begin(CHIP_ST7735X | DISPLAY_TYPE_1,BLACK_SCREEN) ;
        __delay_ms(100);
        GLCD_ColorSet(0,0,63) ;        // 青色
        GLCD_Box(15,25,150,120);
        GLCD_CLS(0);                    //画面クリア 0:黒 1:白
    }
        prescaler = PRESCALE;
        OPTION_REGbits.TMR0CS = 0;
        OPTION_REGbits.TMR0SE = 0;
        OPTION_REGbits.PSA = 0;
        OPTION_REGbits.PS = prescaler - 1;
        TMR0 = T0COUT;
        INTCONbits.TMR0IE = 1;      // Timer 0 Enable
        INTCONbits.TMR0IF = 0;      // interrupt Flag clear

        //Timer1 init
        T1CONbits.TMR1ON = 1;
        T1CONbits.TMR1CS = 0;       //fosc/4
        T1CONbits.T1CKPS = 1;       //prescaler 1:2^1
        TMR1H = 99;                          //10ms timer
        TMR1L = 192;
        PIE1bits.TMR1IE = 1;
        INTCONbits.PEIE = 1;
        
        //AD secdtion
        n = samples;
        incnt = 0;
        disp_mul = 3;
        ad_mul = 3; 
        INTCONbits.GIE = 1;


    while(1){
        for(incnt = 0; incnt < samples ; incnt++){              
            ADCON0bits.GO_nDONE = 1;
            while(ADCON0bits.GO_nDONE);

            addata = ADRESH;
            if( addata > upper_max ) addata = upper_max ;
            ad_buff1[incnt] = (int16_t)addata;         
            __delay_us(100);
        }
●FFT演算処理
以下のFFTに関わるコードはBlackfin空挺団::Blog「FFTを固定小数点化するblackfin(6)」より引用しました。16ビット→8ビットのため、変数のサイズ、castの明確化などを変更しています。

            int8_t m;
            int8_t mh;
            int8_t i;
            int8_t j;
            int8_t k;

            int16_t wr;
            int16_t wi;
            int16_t xr;
            int16_t xi;
        
            int16_t rr_b;
            int16_t ii_b;
            int16_t ri_b;
            int16_t ir_b;
            
            int16_t AD_sum;
            int16_t Average;
            uint8_t ll;
            uint8_t mm;
            
            AD_sum = 0;
            theta = 64 * 2/n;
            for( acnt = 0; acnt < samples ; acnt ++){
                ar[acnt] = (int16_t) ad_buff1[acnt];

                AD_sum += ar[acnt] ;
                ai[acnt] = 0;
                
            }

            Average = AD_sum / samples ;           

            for(mm = 0; mm < samples; mm++){
                ar[mm] = (ar[mm] - Average) << ad_mul;
            }
/* FFT演算の検証のためのテスト用データをAD変換バッファに書き込む
================================================================
            //ar[]← SinWave
            for(sincnt = 0; sincnt < samples ; sincnt++){
                ar[sincnt] += ((int16_t) sintbl[sincnt]) ;
            }
=================================================================
*/

            for (m = n; m > 1; m /= 2) {
                mh = m/2;
                for (i = 0; i < mh; i++) {
                    wr = (int16_t) cos[theta * i];
                    wi = (int16_t) sin[theta * i];
                    for (j = i; j < n; j += m) {
                        k = j + mh;
                        xr = (ar[j] - ar[k]) >> 1 ;
                        xi = (ai[j] - ai[k]) >> 1 ;
                        ar[j] = (ar[j] + ar[k]) >> 1 ;
                        ai[j] = (ai[j] + ai[k]) >> 1 ;
                        rr_b =  wr * xr;
                        ii_b =  wi * xi;
                        ri_b =  wr * xi;
                        ir_b =  wi * xr;
                        ar[k] = (rr_b - ii_b) >> 8; // >>15   
                        ai[k] = (ri_b + ir_b) >> 8; // >>15
                    }
                } 
                theta *= 2;
            }
    // unscrambler 
            i = 0;
            for (j = 1; j < n - 1; j++) {
                for (k = n >> 1; k > (i ^= k); k >>= 1);
                if (j < i) {
                    xr = ar[j];
                    xi = ai[j];
                    ar[j] = ar[i];
                    ai[j] = ai[i];
                    ar[i] = xr;
                    ai[i] = xi;
                }
            }

        if(xdebug){    
                di();
                GLCD_ColorSet( 63, 63, 63) ;       // 白色63all→0all
                GLCD_Line(2,2,2,132) ;             // index yline
                GLCD_Line(2,2,162,2) ;             // index xline
                GLCD_Line(160,2,160,132) ;         // index yline
                GLCD_ColorSet( 63, 63 , 0) ;       // 黄色
                GLCD_Line( 2, 2    , 160  ,2    ); // 0
                GLCD_Line( 2, 2+127, 160  ,2+127); // 横線が 128(0x7F) のレベル
                for(ii = 0;ii < samples ;ii++ ){
                    if(ii <  4) GLCD_ColorSet( 63, 63, 0) ;              // 黄色
                    if(ii == 4) GLCD_ColorSet( 63,  0, 0) ;              // 赤色
                    if(ii >  4) GLCD_ColorSet(  0,  0,63) ;              // 青色
                    GLCD_Line( 15+ii*10, 0, 15+ii*10,3);   //目盛り
                }
                ei();

        }        
                for( ii = 0 ; ii < samples ; ii++ ){
                    spc[ii] =   ((ar[ii] * ar[ii]) + (ai[ii] * ai[ii])) << 4;  

                    if( spc[ii] < 0 )  spc[ii] = 32767 ;
                    dat = (uint8_t) mysqrt( spc[ii] );
                    if(ii == 2) data = dat;
                    dat1 = dat >> 1;               //LCD表示用データ
                    if(dat1 > 128 )  dat1 = 128;   //Yのはみ出し防止
                    
                }
       if(xdebug){
           __delay_ms(2000) ;                  //適当な数値
              di();
              GLCD_CLS(0);                    //画面クリア 0:黒 1:白
              ei();
       }
            if(data >= Level1) { 
                if((tmr_sw5 == OFF) && (tmr_sw6 == OFF)){
                    tmr_sw5 = ON;
                    sectimer5 = 0;
                } 
            }
    }
}

■もっと、どうにかしたい時は
もっと精密に設計するためには、サンプリング数をもっと増やせば可能です。メモリーサイズが多ければ周波数を細分化できます。ネットで調べていたらTI製のLMC567というCMOSトーンデコーダを見つけました。オリジナルメーカーは不明ですが、JRCのNJM567のCMOS版だそうです。形状がDIP8P。アリババで今でも手に入ります。検知周波数はCRで決めることができますが、メカニカルスイッチで切替えるのは面白くないので、デジタルポテンショメータICのマイクロチップ社MCP41010の抵抗と組み合わせるともっと精度がいいものができそうです。アナログ+デジタルのハイブリッド構成で計測器レベルで作れそうです。
作ってから気がついたのですが、電池がすぐになくなってしまいます。特性をみると、CR1220は35mAhでした。220mAhのCR2032の方がよかったかもしれません。基板スペースも問題ないですね。でも、多分、プロならスリープ状態にしておいてマイク入力レベルで目を覚ます方式にするのでしょう。
 ■謝辞
前回にも言いましたがFFT(高速フーリエ変換)についての知識はほとんどなく、そのプログラムについても全くの素人です。この課題を実現するため、下記のブログにある、プログラムリストを引用しました。特に固定小数点によるFFTについては、酔漢さんのBlackfin空挺団::blogの「FFTを固定小数点化する(1)〜(6)」では基礎的な知識からマイコン実現での問題点について丁寧に教えていただきました。でも、決して、各著作者様へのお問い合わせはしないようにおねがいします。
//********************************************************
(1)Blackfin空挺団::Blog「FFTを固定小数点化する(6)」
(2)プログラムモグモグ「平方根なんてビット演算ですればいいじゃない」
https://itchyny.hatenablog.com/entry/20101222/1293028538
(3)きむ茶工房ガレージハウス「TFT LCD(ST7735)モジュールに表示を行なう(skSDlib編)」
http://zattouka.net/GarageHouse/micon/MPLAB/18F25K22/GLCD/ST7735/ST7735x_2.htm

炭酸100%のハイボールがめっちゃうまいそうです。炭酸メーカーでバーボンのハイボールを作れるのですが、こずかいが・・・・・。今年の夏の目標にしましょう。
※このブログで書かれた内容によって生じた損害等の一切の責任を
負いかねますので、予めご了承下さい。

nice!(0)  コメント(0) 
共通テーマ:趣味・カルチャー

真夜中の訪問者

キーボードを鳴らすのは夜が多い。朝からとか昼間とかにガンガンと音楽ではなくてキュイーンとかブィーンとかの音を鳴らす(弾くとは言っていない)にはちょっと気がひける。夜もヘッドフォンをつけてすることになる。しかし、これがなかなか落ち着かないのだ。没頭している間に電話が鳴ったら、何かの急用で来客があったらどうしようかと。真夜中に人が来ることなんて無いと思っていたら、何の連絡もなしに、実際に人が来ることがあるのだ・・・・。
それはある日のこと、夜中の3時に突然ガス警報器が作動し、警報を発した。対処の方法がわからず警報機を取り外して様子をみていたら、それから1時間ほどして玄関に来訪者があった。警備会社の社員らしく、様子を見に来たという。経緯を簡単に話してお引取りをいただいたが、真夜中に寝ぼけ姿ではなく、普通の服装で出てきたのは相当不審に思えたであろう(都会ではなく地方では特に)。

■玄関インターホンの検知
あまりに不安が募ると おちおちとしていられないので、来客や電話の検知をしてやろうと思い立ちました。ヘッドフォンに周囲の音を拾ってシンセ音とミキシングするというのではおもしろくありません。それではと、マイクに設定値以上の入力レベルがあればお知らせサインを出力するプログラムを作ってみましたが、敏感にしすぎたのか、やたらと警報が発生し、閉口しました。そこで周波数で判別してみようと思い立ちました。こういう時はそう、(みんな大好き)フーリエ変換なのだ。と言いつつ、そんな知識はまったく持ち合わせていません。FFTとかバタフライ演算などというキーワードは自慢ではないが聞いたことがある程度です。ネットの情報を探ってみた。そこで得た情報を元に、実現の条件をまとめてみる。

●なるべくコンパクトにしたい
●計算速度を考えるとやっぱりFFT(高速フーリエ変換)でしょう
●計測器を作るわけではないので、精度はそこそこにし、周波数範囲も可聴範囲で。
●浮動小数点演算ではなく、固定小数点演算が簡単そう。
●回転因子(これなに?)の演算は難しそうなので、ここは三角関数をメインにします。

■固定小数点とは何か
上にも書きましたがFFTを説明できるほどの能力はまったくありません。さらに固定小数点とは????なのでキーワードで検索してください。
キーワードは、FFT 固定小数点演算


■ターゲットの音源
当面のターゲットは玄関インターフォンの音色。この周波数はどうして測ればいいのか。パソコンでは音楽関連にはAudacityというプログラムがあります。音楽・音声に関しては思いつくことはほとんど出来るので重宝しています。このソフトの解析タブにスペクトラム表示というのが用意されているのでこれを利用することにしました。

キーワードは、Audacity スペクトラム表示 前後を反転

余談ですが、Audacityに関して感動したことがあります。さる有名な曲は、放送禁止になった元の曲を逆回転で再生して着想を得たという話がありました。実際に確認してみようと、テープで、モノーラルで録音した音をステレオで再生したらその逆回転の音を得ることが出来ました。この作業は準備に時間がかかり、結果は数分というものだったのです。これがAudacityではコマンド一発でできたことで感動しました。ローマ字で watasi wa bakadesu と書き、逆にしてusedaka bawisataw と録音し、逆再生するとちゃんと言葉になって聞こえるというのも音の真髄に触れるようで感動ものです。

■PICマイコンの謎
ネットでFFT関連のプログラムを検索すると、PICについてはほぼすべてが dspicに関するもので、8ビットマイコンシリーズではほとんど紹介がありません。理由はプログラムメモリーが少ない、メモリーも不十分だからです。だったらどこまで出来るのかを確認してみようと思いました。

■回路図
FFTcaller.png

■使用する部品のリスト
部品リスト 数量 単価
PIC12F1840-I/P 1 110
NJU7043D 1 70
ECマイクロホン C9767 1 100(4個で)
半固定VR(50KΩ)
1 40
LED 赤Φ5(Φ3の方が良好) 1 20
バッテリケース タカチ HU1220
1 176
電池 CR1220 (3V) 1 510(5個で)
片面ユニバーサル基板 60×28 
1 -
清涼菓子ケース 1 -

●PIC12F1840のマニュアルは英文ですが、同じファミリーの12F1822のが和文です。
●デジタルとアナログ回路の分離についてはトランジスタ技術2020年3月号p.64の回路(下記)を引用しました。マイク増幅回路のノイズがほぼ解消しました。
●OPアンプはレールトゥレール入出力(フルスイング)である必要があります。
●液晶モジュールはST7735R(緑色タブ)が使用可能ですが、クリップで接続し使用する形になります。接続した状態でのデバッガ操作に問題がありました。
●PICKIT3との接続もクリップで行います。
■組み立て
sPIC_164849.JPGsPIC_164825.JPG









●基板は清涼菓子のケースに収容しますが、フリスク品がなく、よく似たものにしました。
●マイコンやOPアンプに8P DIPソケットを使用すると、約1.5mmはみ出します。
●LEDはΦ5のものを使用しましたが、約2mm飛び出します。
●電池ケースはタカチ製がピッタリです。最適な形状のLR44は1.5V(残念)なので注意
●チップ抵抗やコンデンサはパターン面に配置しましたが、固定は接着剤のB-7000を使用しました。スマホの修理では必須部品です。熱が加わると固定が外れます。

sPIC_170340.JPGsPIC_170359.JPG







Curtis Fullerの「BLUES-ette」を聴きながらバーボンを飲むと、一気に1960年ニューヨークのファイブスポットカフェにジャンプできますぜ。

********************************************************
以下の記事から回路を引用しました。
(1)「ライセンスフリー!MP3ソフトウェアプレーヤの製作」
    白阪一郎様  トラジスタ技術 2020年3月号 p.64 CQ出版社
※このブログで書かれた内容によって生じた損害等の一切の責任を負いかねますので、予めご了承下さい。

nice!(0)  コメント(0) 
共通テーマ:趣味・カルチャー

メンテナンスしよう

 ずっと前からの課題だった、DX7の液晶バックライト化とコイン電池の交換をしようと思い立ちました。ネットで情報を探ると、本当に親切丁寧に教えてくれるページが多いです。
ネットの情報、特にyoutubeの動画を見ながら本体を分解し、LCDモジュールに部品を取り付けると見事にバックライト化は完了しました。(おしまい、おしまい)・・・・・これが理想なのですが、そうはいかなかったので備忘録に書いておくことにしました。

■液晶のバックライト化
数年前からNOZ CELICAさんの「NOZ's Stylish Sound♪」を拝見していました。そのYAMAHA「DX7」の液晶ディスプレイ交換♪ のページの内容を参考にしました。
●交換に使用するバックライト付き液晶
NOZさんのページで、KD-162-626LPSG-LGという型番のバックライト液晶が紹介されています。このページがきっかけで電子部品通販サイトの鈴商で数年前に購入しました。残念なことに現在は取り扱いがないようです。しかし、親切なことに、この部品の取説を置いてくれています。httpsで暗号化されていないとブラウザに注意されます。
キーワードは、鈴商、KD-162-626LPSG-LG pdf

SPIC_20200203_160121.JPGこの製造元のCrystalfontz社をネットで調べると、キャラクターLCDのページにCFAH1602シリーズという仕様の近いものがありました。(同等かは不明です)



DX7/9 SERVICE MANUALによると、元のLCD(バックライト無し)は日立製LM016という品番だそうです。これにバックライトが付いたLCDが日立(ルネサス?)から出ているのか出ていたのかはわかりません。
キーワードは、YAMAHA、DX7/9、Service Manual 
ネットをざっと調べたところでは、HD44780コントローラを使用して、16×2行のLCD部品は見つかりますが、外形が異なるとか、ピン配列が違うものばかりでした
●基板接続用のコネクタ
液晶モジュール基板には16個の穴が空いていますが、DX7の元からのコネクタは14極なので、受け側のベース付ポストは14極の日圧製 S14B-XH-A を使用しました。形状はトップ型ではなく、サイド型でないと取り付け時に泣くに泣けないことになります。電子部品店に今でもあるのかとちょっと心配でしたが、ちゃんと置いてありました。
SPIC_20200203_165914.JPG元のLCDモジュールと上に付いている化粧パネルを分離するのが一番苦労するところです。無理に引き剥がすことはせず、ホットエアーを使います。ドライヤーでももちろんOKですが、ノズルの形状によりスポット状に熱風が当たりますので作業性が抜群です。
余談ですが、スマートフォンの部品固定には接着剤が多用されていますので、スマホの修理では必須アイテムだと思います。価格的には、工業用がウン十万していましたが、数千円で買える物もあります。それでも購入には相当悩んだのですが、パネルを3回ほど交換して、元は取れたと思いました。
●改造作業
バックライト用の電源を5Vラインから取るのですが、モジュールのピン配列に注意です。XH14Pの1番ピンの隣が16ピンで、その隣が15ピンです。1ピンはVssですから、16ピンのLED(-)とは隣同士。2ピンはVddなので15ピンのLED(+)とはアキシャルのリードが配線に便利です。

SPIC_20200203_204506.JPGSPIC_20200203_165408.JPGCrystalfontzの説明書ではLCDへの供給電圧がTyp 5Vのところ、バックライトLED側はTyp4.2Vです。0.8Vならダイオードの電圧降下分でいけると考え、高速スイッチングダイオードにしました。
1N4148のアキシャルリードタイプを取り付けてみましたが、5Vが4.6Vくらいしか落ちません。では、という訳で2本直列にしてみると4.2Vになりました。


輝度の調整は何もしませんでしたが、結局高くついたので抵抗にしておけばよかった。
カタログを見ていたらLCDモジュールのバックライトLEDに制限抵抗が入っていないのに気付きました。なので、ダイオードで電圧降下するのはやめて抵抗(22Ω以上)に変更します。画像もそのうち変更します。(どうもすいませんでした)

■バッテリー交換
バッテリーはCR2032なので、タカチ製コイン電池ホルダー CH7410、電池ホルダーを包み込む用の熱収縮チューブ、基板から電池端子を外すための100Wのはんだごてなどを用意しました。ところが、テスターで両端の電圧を測ってみると何と3.02Vもあります。基板を外した上に、さらに電池の端子を外すのはちょっと面倒なので、今回は保留にしました。


炭酸メーカーで作った炭酸を飲むとほんとに美味しい。でもこれがいつの間にか強炭酸になり、そのうちに強強炭酸にエスカレートしていく予感が・・・・。


※このブログで書かれた内容によって生じた損害等の一切の責任を負いかねますので、予めご了承下さい。

nice!(0)  コメント(0) 
共通テーマ:趣味・カルチャー

音を創るということ6(ソフト作成編)

プログラムの作成2回目です。おさらいのためにプログラムを見返していますが、もっと簡単にできないのかとか、出来るプログラマーだったら、〇〇指向とか〇〇プログラミングで見通しのよい作り方をするんだろうかとか、本当に余計なことを考えてしまいます。


■voiceパラメータのMAX値テーブル

//**************************************************************
//Table voice_max[165]
//**************************************************************

const uint8_t voice_max[165]={
 99,     // voice[0]=   ;  OP6_EG_RATE1
 99,     // voice[1]=   ;  OP6_EG_RATE2
 99,     // voice[2]=   ;  OP6_EG_RATE3
 99,     // voice[3]=   ;  OP6_EG_RATE4
 99,     // voice[4]=   ;  OP6_EG_LEVEL1
 99,     // voice[5]=   ;  OP6_EG_LEVEL2
 99,     // voice[6]=   ;  OP6_EG_LEVEL3
 99,     // voice[7]=   ;  OP6_EG_LEVEL4
 99,     // voice[8]=   ;  OP6_KEY_BOARD_LEVEL_SCALE_BREAK_POINT
 99,     // voice[9]=   ;  OP6_KEY_BOARD_LEVEL_SCALE_LEFT_DEPTH
 99,     // voice[10]=   ; OP6_KEY_BOARD_LEVEL_SCALE_RIGHT_DEPTH
  3,     // voice[11]=   ; OP6_KEY_BOARD_LEVEL_SCALE_LEFT_CURVE
  3,     // voice[12]=   ; OP6_KEY_BOARD_LEVEL_SCALE_RIGHT_CURVE
  7,     // voice[13]=   ; OP6_KEY_BOARD_RATE_SCALLING
  3,     // voice[14]=   ; OP6_MOD SENSITIVITY_AMPLITUDE
  7,     // voice[15]=   ; OP6_OPERATOR_KEY_VELOCITY_SENSITIVITY
 99,     // voice[16]=   ; OP6_OPERATOR_OUTPUT_LEVEL
  1,     // voice[17]=   ; OP6_OSCILLATOR_MODE
 31,     // voice[18]=   ; OP6_OSCILLATOR_FREQUENCY_COARSE
 99,     // voice[19]=   ; OP6_OSCILLATOR_FREQUENCY_FINE
 14,     // voice[20]=   ; OP6_OSCILLATOR_DETUNE
 99,     // voice[21]=   ;  OP5_EG_RATE1
 99,     // voice[22]=   ;  OP5_EG_RATE2
 99,     // voice[23]=   ;  OP5_EG_RATE3
 99,     // voice[24]=   ;  OP5_EG_RATE4
 99,     // voice[25]=   ;  OP5_EG_LEVEL1
 99,     // voice[26]=   ;  OP5_EG_LEVEL2
 99,     // voice[27]=   ;  OP5_EG_LEVEL3
 99,     // voice[28]=   ;  OP5_EG_LEVEL4
 99,     // voice[29]=   ;  OP5_KEY_BOARD_LEVEL_SCALE_BREAK_POINT
 99,     // voice[30]=   ;  OP5_KEY_BOARD_LEVEL_SCALE_LEFT_DEPTH
 99,     // voice[31]=   ;  OP5_KEY_BOARD_LEVEL_SCALE_RIGHT_DEPTH
  3,     // voice[32]=   ;  OP5_KEY_BOARD_LEVEL_SCALE_LEFT_CURVE
  3,     // voice[33]=   ;  OP5_KEY_BOARD_LEVEL_SCALE_RIGHT_CURVE
  7,     // voice[34]=   ;  OP5_KEY_BOARD_RATE_SCALLING
  3,     // voice[35]=   ;  OP5_MOD SENSITIVITY_AMPLITUDE
  7,     // voice[36]=   ;  OP5_OPERATOR_KEY_VELOCITY_SENSITIVITY
 99,     // voice[37]=   ;  OP5_OPERATOR_OUTPUT_LEVEL
  1,     // voice[38]=   ;  OP5_OSCILLATOR_MODE
 31,     // voice[39]=   ;  OP5_OSCILLATOR_FREQUENCY_COARSE
 99,     // voice[40]=   ;  OP5_OSCILLATOR_FREQUENCY_FINE
 14,     // voice[41]=   ;  OP5_OSCILLATOR_DETUNE
 99,     // voice[42]=   ;  OP4_EG_RATE1
 99,     // voice[43]=   ;  OP4_EG_RATE2
 99,     // voice[44]=   ;  OP4_EG_RATE3
 99,     // voice[45]=   ;  OP4_EG_RATE4
 99,     // voice[46]=   ;  OP4_EG_LEVEL1
 99,     // voice[47]=   ;  OP4_EG_LEVEL2
 99,     // voice[48]=   ;  OP4_EG_LEVEL3
 99,     // voice[49]=   ;  OP4_EG_LEVEL4
 99,     // voice[50]=   ;  OP4_KEY_BOARD_LEVEL_SCALE_BREAK_POINT
 99,     // voice[51]=   ;  OP4_KEY_BOARD_LEVEL_SCALE_LEFT_DEPTH
 99,     // voice[52]=   ;  OP4_KEY_BOARD_LEVEL_SCALE_RIGHT_DEPTH
  3,     // voice[53]=   ;  OP4_KEY_BOARD_LEVEL_SCALE_LEFT_CURVE
  3,     // voice[54]=   ;  OP4_KEY_BOARD_LEVEL_SCALE_RIGHT_CURVE
  7,     // voice[55]=   ;  OP4_KEY_BOARD_RATE_SCALLING
  3,     // voice[56]=   ;  OP4_MOD SENSITIVITY_AMPLITUDE
  7,     // voice[57]=   ;  OP4_OPERATOR_KEY_VELOCITY_SENSITIVITY
 99,     // voice[58]=   ;  OP4_OPERATOR_OUTPUT_LEVEL
  1,     // voice[59]=   ;  OP4_OSCILLATOR_MODE
 31,     // voice[60]=   ;  OP4_OSCILLATOR_FREQUENCY_COARSE
 99,     // voice[61]=   ;  OP4_OSCILLATOR_FREQUENCY_FINE
 14,     // voice[62]=   ;  OP4_OSCILLATOR_DETUNE
 99,     // voice[63]=   ;  OP3_EG_RATE1
 99,     // voice[64]=   ;  OP3_EG_RATE2
 99,     // voice[65]=   ;  OP3_EG_RATE3
 99,     // voice[66]=   ;  OP3_EG_RATE4
 99,     // voice[67]=   ;  OP3_EG_LEVEL1
 99,     // voice[68]=   ;  OP3_EG_LEVEL2
 99,     // voice[69]=   ;  OP3_EG_LEVEL3
 99,     // voice[70]=   ;  OP3_EG_LEVEL4
 99,     // voice[71]=   ;  OP3_KEY_BOARD_LEVEL_SCALE_BREAK_POINT
 99,     // voice[72]=   ;  OP3_KEY_BOARD_LEVEL_SCALE_LEFT_DEPTH
 99,     // voice[73]=   ; OP3_KEY_BOARD_LEVEL_SCALE_RIGHT_DEPTH
  3,     // voice[74]=   ; OP3_KEY_BOARD_LEVEL_SCALE_LEFT_CURVE
  3,     // voice[75]=   ; OP3_KEY_BOARD_LEVEL_SCALE_RIGHT_CURVE
  7,     // voice[76]=   ; OP3_KEY_BOARD_RATE_SCALLING
  3,     // voice[77]=   ; OP3_MOD SENSITIVITY_AMPLITUDE
  7,     // voice[78]=   ; OP3_OPERATOR_KEY_VELOCITY_SENSITIVITY
 99,     // voice[79]=   ; OP3_OPERATOR_OUTPUT_LEVEL
  1,     // voice[80]=   ; OP3_OSCILLATOR_MODE
 31,     // voice[81]=   ; OP3_OSCILLATOR_FREQUENCY_COARSE
 99,     // voice[82]=   ; OP3_OSCILLATOR_FREQUENCY_FINE
 14,     // voice[83]=   ; OP3_OSCILLATOR_DETUNE
 99,     // voice[84]=   ;  OP2_EG_RATE1
 99,     // voice[85]=   ;  OP2_EG_RATE2
 99,     // voice[86]=   ;  OP2_EG_RATE3
 99,     // voice[87]=   ;  OP2_EG_RATE4
 99,     // voice[88]=   ;  OP2_EG_LEVEL1
 99,     // voice[89]=   ;  OP2_EG_LEVEL2
 99,     // voice[90]=   ;  OP2_EG_LEVEL3
 99,     // voice[91]=   ;  OP2_EG_LEVEL4
 99,     // voice[92]=   ;  OP2_KEY_BOARD_LEVEL_SCALE_BREAK_POINT
 99,     // voice[93]=   ;  OP2_KEY_BOARD_LEVEL_SCALE_LEFT_DEPTH
 99,     // voice[94]=   ; OP2_KEY_BOARD_LEVEL_SCALE_RIGHT_DEPTH
  3,     // voice[95]=   ; OP2_KEY_BOARD_LEVEL_SCALE_LEFT_CURVE
  3,     // voice[96]=   ; OP2_KEY_BOARD_LEVEL_SCALE_RIGHT_CURVE
  7,     // voice[97]=   ; OP2_KEY_BOARD_RATE_SCALLING
  3,     // voice[98]=   ; OP2_MOD SENSITIVITY_AMPLITUDE
  7,     // voice[99]=   ; OP2_OPERATOR_KEY_VELOCITY_SENSITIVITY
 99,     // voice[100]=   ; OP2_OPERATOR_OUTPUT_LEVEL
  1,     // voice[101]=   ; OP2_OSCILLATOR_MODE
 31,     // voice[102]=   ; OP2_OSCILLATOR_FREQUENCY_COARSE
 99,     // voice[103]=   ; OP2_OSCILLATOR_FREQUENCY_FINE
 14,     // voice[104]=   ; OP2_OSCILLATOR_DETUNE
 99,     // voice[105]=   ;  OP1_EG_RATE1
 99,     // voice[106]=   ;  OP1_EG_RATE2
 99,     // voice[107]=   ;  OP1_EG_RATE3
 99,     // voice[108]=   ;  OP1_EG_RATE4
 99,     // voice[109]=   ;  OP1_EG_LEVEL1
 99,     // voice[110]=   ;  OP1_EG_LEVEL2
 99,     // voice[111]=   ;  OP1_EG_LEVEL3
 99,     // voice[112]=   ;  OP1_EG_LEVEL4
 99,     // voice[113]=   ;  OP1_KEY_BOARD_LEVEL_SCALE_BREAK_POINT
 99,     // voice[114]=   ;  OP1_KEY_BOARD_LEVEL_SCALE_LEFT_DEPTH
 99,     // voice[115]=   ; OP1_KEY_BOARD_LEVEL_SCALE_RIGHT_DEPTH
  3,     // voice[116]=   ; OP1_KEY_BOARD_LEVEL_SCALE_LEFT_CURVE
  3,     // voice[117]=   ; OP1_KEY_BOARD_LEVEL_SCALE_RIGHT_CURVE
  7,     // voice[118]=   ; OP1_KEY_BOARD_RATE_SCALLING
  3,     // voice[119]=   ; OP1_MOD SENSITIVITY_AMPLITUDE
  7,     // voice[120]=   ; OP1_OPERATOR_KEY_VELOCITY_SENSITIVITY
 99,     // voice[121]=   ; OP1_OPERATOR_OUTPUT_LEVEL
  1,     // voice[122]=   ; OP1_OSCILLATOR_MODE
 31,     // voice[123]=   ; OP1_OSCILLATOR_FREQUENCY_COARSE
 99,     // voice[124]=   ; OP1_OSCILLATOR_FREQUENCY_FINE
 14,     // voice[125]=   ; OP1_OSCILLATOR_DETUNE
 99,     // voice[126]=   ; PITCH_EG_RATE1
 99,     // voice[127]=   ; PITCH_EG_RATE2
 99,     // voice[128]=   ; PITCH_EG_RATE3
 99,     // voice[129]=   ; PITCH_EG_RATE4
 99,     // voice[130]=   ; PITCH_EG_LEVEL1
 99,     // voice[131]=   ; PITCH_EG_LEVEL2
 99,     // voice[132]=   ; PITCH_EG_LEVEL3
 99,     // voice[133]=   ; PITCH_EG_LEVEL4
 31,     // voice[134]=   ; ALGORITHM_SELECT
  7,     // voice[135]=   ; FEED_BACK
  1,     // voice[136]=   ; OSCILLATOR_SYNC
 99,     // voice[137]=   ; LFO_SPEED
 99,     // voice[138]=   ; LFO_DELAY
 99,     // voice[139]=   ; LFO_PMD
 99,     // voice[140]=   ; LFO_AMD
  1,     // voice[141]=   ; LFO_SYNC
  4,     // voice[142]=   ; LFO_WAVE
  7,     // voice[143]=   ; MOD_SENSITIVITY_PITCH
 48,     // voice[144]=   ; TRANSPOSE
0x7f,     // voice[145]=   ; VOICE_NAME1
0x7f,     // voice[146]=   ; VOICE_NAME2
0x7f,     // voice[147]=   ; VOICE_NAME3
0x7f,     // voice[148]=   ; VOICE_NAME4
0x7f,     // voice[149]=   ; VOICE_NAME5
0x7f,     // voice[150]=   ; VOICE_NAME6
0x7f,     // voice[151]=   ; VOICE_NAME7
0x7f,     // voice[152]=   ; VOICE_NAME8
0x7f,     // voice[153]=   ; VOICE_NAME9
0x7f,     // voice[154]=   ; VOICE_NAME10
0x3F,     // voice[155]=   ; OPERATOR_ON/OFF 
// D6:0 D5:OP1 D4:OP2 D3:OP3 D2:OP4 D1:OP5 D0:OP6
//以降はこの機器用のパラメータ
 5,       // voice[156]    ;selectされたOPナンバー op1:5   op6:0
 1,       // voice[157]    ;OP6
 1,       // voice[158]    ;OP5
 1,       // voice[159]    ;OP4
 1,       // voice[160]    ;OP3
 1,       // voice[161]    ;OP2
 1,       // voice[162]    ;OP1
 99,      // voice[163]    ;None
 99,      // voice[164]    ;None
};

//**************************************************************
●DX7の取扱説明書の31ページに載っている各パラメータのMAX値をテーブルにしています。右回しでMAXで止めるようにしています。左回し一杯はもちろん0です。
●パラメータの156番以降はこの装置内だけで使用するために追加しました。パラメータ163と164は、レイアウト図の上から4個目、左から2個目RE用です。今回は使用していませんが、メニュー切り替え用に利用しようとしています。パラメータ157〜162もOPの操作用に確保したのですが、今のところ予定なしです。
●コメントが途切れていますが、取扱説明書を丸写ししただけです。

■voice初期データのテーブル

//*************************************************************
// Table voice_init[165 ]  voice初期データ
//*************************************************************

const uint8_t voice_init[165]={
0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x63,0x63,0x63,
0x63,0x63,0x63,0x63,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x01,0x00,0x00,0x63,0x63,0x63,0x63,0x63,0x63,
0x63,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,
0x00,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x63,0x63,
0x63,0x63,0x63,0x63,0x63,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x63,0x63,0x63,0x63,0x63,
0x63,0x63,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x63,
0x00,0x01,0x00,0x00,0x63,0x63,0x63,0x63,0x32,0x32,0x32,0x32,
0x00,0x00,0x01,0x23,0x00,0x00,0x00,0x01,0x00,0x03,0x18,0x20,
0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x3F, 0, 1, 1, 1,
 1, 1, 1, 0, 0,
};

//*************************************************************
●電源投入時の最初のデータです。これをベースに設定値を書き換えていくのですが、電源投入時にDX7からのデータを受け付ければもっと音作りが楽になりそう。
●voice_maxとかvoice_initとかパラメータのデータが多いです。構造体を使えばもっとすっきりした形になるのだろうと思いつつ、最初の設計が試行錯誤で進んでいるので仕方ないね。開発初期段階の設計は本当に重要なのですよ。
●実はこのデータはDX7の初期時のデータを参考にしています。こういうデータは著作権に関係するのかなとファイル名だけは変えましたけど。

■リングバッファ

//*************************************************************
// write_buff( ) と Read_buff( )
//*************************************************************

#define MAX 256
uint8_t data3;
uint8_t data4;

void write_buff(uint8_t data3){ 
    if(!(rd_index == (wr_index+1) % MAX)){ 
        buff[wr_index++] = data3;
        if(wr_index == MAX) wr_index = 0;
    }
 } 

uint8_t Read_buff(void){ 
    if(!(rd_index == wr_index)){
        data4 = buff[rd_index];
        ++ rd_index;
    } 
   if(rd_index == MAX) rd_index = 0;
   return data4;
 }

 //************************************************************* 
●間違いを発見しました。write_buff は小文字なのに Read_buff は大文字でした。そのまま行きます。
●入力は割り込みで行っているので、メインルーチンの処理ではバッファが必要です。MAXが256では短いかなと思ってます。
●リングバッファをもうひとつ作っていました。詳細はこの下を見てください。

■ main()

//*************************************************************
void main(void){
    //OSC_init();
	    OSCCONbits.IRCF = 7; // 8MHz
	    OSCTUNEbits.PLLEN = 1; // PLL(x6)
	    OSCCONbits.SCS = 0; 

    //IO_init();
	// 0が出力、1が入力
	    TRISA =  0b00010000; 
	    TRISB =  0b10001111; 
            UCONbits.USBEN = 0; 
            UCFGbits.UTRDIS = 1;
	    TRISC =  0b11111000;
     
	// 0がアナログ、1がデジタル
	    ANCON0 = 0b11111111;
	    ANCON1 = 0b00011111;
 	    INTCON2bits.RBPU = 0; 
            RPINR16 = 10;
            RPOR9   =  6;      

       // PRESCALE(0-8):6
           T0CONbits.TMR0ON = 1;
           T0CONbits.T08BIT = 1; // 8-bit timer
           T0CONbits.T0CS = 0;   // use internal-OSC
          if (PRESCALE == 0) {
           T0CONbits.PSA = 1; // Not use prescaler
        } else {
	        T0CONbits.PSA = 0; // use prescaler
	        T0CONbits.T0PS = PRESCALE - 1;
        }
        TMR0 = T0COUT; //T0COUT=181
        INTCONbits.TMR0IE = 1; 
        INTCONbits.TMR0IF = 0; 
        INTCON2bits.TMR0IP = 0;
        INTCONbits.PEIE = 1;
        rmidi_index = 0;
        tmidi_index = 0;
  
    LATA = 0b11101111;
    LATC = 0b00000111;
    LATB = 0b00110000;

     //UART_Initialize();
        TXSTA2bits.SYNC = 0;
        TXSTA2bits.BRGH = 0;
        TXSTA2bits.TXEN = 1; 
        TXSTA2bits.TX9  = 0;   
        RCSTA2bits.SPEN = 1;
        RCSTA2bits.RX9  = 0;  
        RCSTA2bits.CREN = 1;  
        BAUDCON2bits.BRG16 = 0; 
        BAUDCON2bits.RXDTP = 0; 
        BAUDCON2bits.TXCKP = 0; 
        SPBRG2   = 23 ;  // ボーレートを31250bpsに設定 

        INTCONbits.PEIE = 1;
        INTCONbits.GIE = 1;
    
    //Init_buff();    //初期データを編集バッファへ転送
        for(bufno = 0;bufno < 165; bufno++){
            edit_voice[bufno] = voice_init[bufno];
        }
        SelectNo = voice_init[156];  //voiceNoにOP1をセット
    
    while(1){
        In_oprate();
        DX7_task();        
    }

}

//************************************************************** 
●メインの前半は初期設定です。特別なことは何もしていません。
●編集で使うvoice_edit[ ]に初期データ voice_init[ ]を転送します。これがないと操作時に微妙に苦労するのです。
●後半は無限ループで回していますが、スイッチ(SW)とロータリーエンコーダ(RE)の処理を行なうIn_oprate() と、 実際の動作を行なうDX7_task()で構成しています。

■DX7_task( )

//**************************************************************
// DX7_task( )
//**************************************************************
 void DX7_task(void ){
            taskloop = paramcount;
            for(i = 0;i < taskloop; i++){
                midichar[i] = Read_midibuff();
                while(TX2IF == 0);
                TXREG2 = midichar[i];
            }
         }

//**************************************************************
uint8_t midi_modify[7]={0xf0,0x43,0b00010000,00,00,00,0xf7}; 

void StoreMidi(void){
                            uint8_t add1;
                            uint8_t add2;
                            uint8_t i;
                            add2 = param & 0b01111111;
                            add1 = (param >> 7) & 0b00000011; 
                            midi_modify[3] = add1;
                            midi_modify[4] = add2;
                            midi_modify[5] = data;                        
                            for(i = 0;i < 7; i++){             
                                Write_midibuff(midi_modify[i]);
                            }
       }
//**************************************************************
        void Write_midibuff(uint8_t data1){ 
                if(!(rmidi_index == (tmidi_index + 1) % MAX)){
                    midibuff[tmidi_index++] = data1;
                    if(tmidi_index == MAX) tmidi_index = 0;
            
                }
        }

        uint8_t Read_midibuff(void){
            if(!(rmidi_index == tmidi_index)){
               data2 = midibuff[rmidi_index];
               ++ rmidi_index;
           }
           if(rmidi_index == MAX) rmidi_index = 0;
            return data2;
            
        } 

//**************************************************************
●さり気なく、Write_midibuff とか Read_midibuff とかの関数を書いていますが、リングバッファはパラメータ入力用とこのmidi用の2種類あったのでした。

■SW・REの入力処理

//**************************************************************
// In_oprate
//**************************************************************
    void In_oprate(void){
        uint8_t tmp;
        uint8_t p;
        uint8_t row;     //行
        uint8_t column;  //列
        uint8_t count;
        uint8_t swcnt;
        uint8_t swand;
        uint8_t swnum;
        uint8_t padr;
        
        for(swcnt=0;swcnt < 2;swcnt++ ){
            op_flag_old = op_flag;
            swand = And[swcnt];
            for(swnum = 0 ;swnum < 8 ;swnum = swnum + 2){
                if(swcnt == 0 ){
                    //ONの処理
                    if((swand >> swnum) & 0b00000001){ 
                        op_flag |= (0b00001000 >> (swnum >> 1));
   
                    } else { 
                        //OFFの処理 
                    op_flag &= ~(0b00001000 >> (swnum >> 1));
                    }
                }
                if(swcnt == 1 && swnum > 3 ){
                    if((swand >> swnum) & 0b00000001 ){ //ONの処理
                        if(swnum == 4){ 
                            op_flag |= 0b00100000; //ch_no= 1 bit= 4,6
         
                        }
                        if(swnum == 6){
                            op_flag |= 0b00010000;

                        }
                        } else { //OFFの処理 
                            if(swnum == 4){ 
                                op_flag &= ~(0b00100000); 
                            }
                            if(swnum == 6){
                                op_flag &= ~(0b00010000); 
                            }
                        }
                }
            }
            if(op_flag ^ op_flag_old){
                        padr = 155;
                        edit_voice[155] = op_flag;                                                        
  
                        param = padr;
                        data = edit_voice[padr];
                        StoreMidi();
            }

        }
        
        INTCONbits.TMR0IE = 0; //TMR0 disable 
        ch_no = Read_buff();
        Rot_R = Read_buff();
        Exor =  Read_buff(); 
        INTCONbits.TMR0IE = 1; //TMR0 enable
       
        for(count = 0 ;count < 8;count = count + 2){ 
            if(ch_no == 0 ||( ch_no == 1 && count > 3)) continue;
            if((Exor >> count) & 0b00000001){                     
                if((Rot_R >> count) & 0b00000001){ 
                    boundry[ch_no] |= (0b00000001 << count);
                    if(((Rot_R >> count) >> 1) & 0b00000001 ){
                           cw_ccw[ch_no] |= (0b00000010 << count); 
                        }   else {
                           cw_ccw[ch_no] |= (0b00000001 << count); 
                            } 
                } else { //立ち下がり 
                    boundry[ch_no] |= (0b00000001 << (count + 1));
                    if(((Rot_R >> count) >> 1) & 0b00000001 ){   
                            cw_ccw[ch_no] |= (0b00000001 << count);
                        } else {
                            cw_ccw[ch_no] |= (0b00000010 << count);
                        } 
                }  
                    
            }           
        }
        for(column = 0;column < 8 ;column =column + 2) { 
            //いずれかの入力に変化があったら
            if(boundry[ch_no]){                    
                p=0;
                if(ch_no > 5){ //row=6,7,8,9,10,11
                    SelectNo = edit_voice[156]; //OP1-OP6を選択
                    p = SelectNo * 21;
                    if(ch_no == 6) {
                        if( column > 4) p = 0;             
                    }   
                    if((ch_no == 8) && (column > 6)) p = 0;
                }  

                padr = no[ch_no][column] + p;
                Rotcnt[column] = edit_voice[padr];

         if(((boundry[ch_no] >> column) & 0b00000011) == 1){
                    //CW
         if(((cw_ccw[ch_no] >> column) & 0b00000011) == 1){
                     Rotcnt[column]++;
                     if(Rotcnt[column] > voice_max[padr]) {
                          Rotcnt[column] = voice_max[padr];
                     } 
                    }
                    //CCW                        
         if(((cw_ccw[ch_no] >> column) & 0b00000011) == 2){
                        Rotcnt[column]--;
                        if(Rotcnt[column] > voice_max[padr] ) {
                            Rotcnt[column] = 0;
                        }
                    }
                    if(edit_voice[padr] ^ Rotcnt[column]){
                        edit_voice[padr] = Rotcnt[column];                                       
                        timerstart = 1;      //次の入力があるとタイマーを
                        periodtimer = 0;   //リセットし、再カウント
                        
                        param = padr;     //columnの必要な部分のみ送信
                        data = edit_voice[padr];
                        StoreMidi();
                    }
                    boundry[ch_no] &= ~(0b00000011 << column );
                    cw_ccw[ch_no]  &= ~(0b00000011 << column );
                }
                    
            }
        }
        boundry[ch_no] = 0;
        cw_ccw[ch_no] = 0;
        ppp = 0;
    }

//************************************************************** 
●前回のno[12][8]のテーブルを参照して、まず1行目と2行目の半分(bit4からbit7)にあるスイッチ(以下SW)について、ビットシフト演算により偶数ビットのSWの状態をみます。そのON/OFF状態をオペレータフラグ voice[155]に反映します。
●1行の中にSWとREが混在している上に、3行目以降はRE処理も書いているのでややこしいです。
●2行目の半分(bit0からbit3)以降はREのvoiceパラメータ確定と回転方向の判別・数値増減を行います。1行に4個のREがあるので、for文も0、2、4、6をキーにチェックします。
●余談ですが、C で for文は for (  ;  ;  )のスタイルですが、perlだと for ( 0, 2, 4, 6)という書き方もできます。変数に$をつけるとかの約束がありますが、Cに似ているので結構抵抗が少ないです。その分、つまづくことは何時もなんですが。しかし、最近、人気無いですね。
●for文の変数ですが、最初はswnum、その後 countとなり、すぐcolumnって統一性がないのまるわかり。これが試行錯誤プログラミングなのだ。
●セレクトされているOP番号が決まれば、簡単な計算でそのOP番号に関連するパラメータアドレスを算出しています。ややこしいことをせずにベタ書きの方がひょっとして良かったのではと思う今日このごろ。
●最後のほうでタイマをカウントしている部分がありますが、これはいわゆるウォッチドッグタイマで、REの数値を増減後の一定時間経過後にMIDIデータ(音楽データ)を送信することを考えています。そう、キーボード弾かなくてもいいのです。でも今回のテーマには沿わないので実装はしていません。
●ソースリストはこんな形よりもファイルをダウンロード出来るようにしておくのがいいですよね。このSSブログには簡単にできるような説明がありませんでした。Githubで登録するのを考えましたが時間がとれない。これからの課題にします。何しろ明日はセンター試験の2日目なんで・・・(別に関係ないけど)。

飛ばして進めたので忘れていることが多いようで、まとまりがないです。一応、MIDIコントローラの話はここまで。家の中を探索すると梅酒とかりん酒がありました。寒くなってきたのでちょっとだけ飲んでみよう。

※このブログで書かれた内容によって生じた損害等の一切の責任を負いかねますので、予めご了承下さい。

nice!(0)  コメント(0) 
共通テーマ:趣味・カルチャー

音を創るということ5(ソフト設計&作成編)

今回からプログラムの作成にはいります。
■開発環境について

●対象マイコンはPIC18F27J53 クロック:内部 48MHz駆動
●利用した内蔵モジュールはタイマー(TIMER0)割り込みとEUSART(MIDI出力用)という一般的なものなので、他のマイコンでも動作するはず(多分)です。
●MPLAB X IDEはv5.25@linux mint 18.3
●使用コンパイラはXC8 (v.1.45) v.2.10では不明のエラーが出ましたが、解決策はあるようです。
●ライターはPICKIT3を使用しています。PICKIT4も出ていますが、お安いということでこれにしました。アリババでも扱っています(別にまわし者では・・・・)、専用の書き込みアダプターなんてのが付属していますね。付録とか粗品とかの言葉に弱い。純正品かどうかは不明です。
keyword is microchip  pickit3  programmer 
●C文法およびMPLAB操作方法などはちゃんとした別のところを参照して下さい。何しろ、フローチャートなんて書かないで、思いついたらメモに書きなぐりぱなし、ネットのを丸写しなもんで(車輪は何度も発明するな・・・ですね)。
●開発時の反省点としては、コメントとドキュメントは絶対に必要だと思いました。コードを書いて、デバッガで実行し、結果をみては修正するという、いきあたりばったりの試行錯誤プログラミング ではちょっと前の記憶が飛びやすいです。

■処理の流れ
簡単に言うと、タイマー割り込み毎に入力を読み込み、メインルーチンでデータの範囲を適正化したうえでmidiデータに加工し、UARTを通じてMIDIフォーマット通りに送信するプログラムです。

以下のプログラムリストの説明は、忘れないように覚え書きのために書いています。なので、多少わかりにくいところがあるかもしれません。

■リスト内パラメータの説明


//**************************************************************
//parameter
//**************************************************************
 ●使用しているパラメータの説明
              7654 3210
  ・Rot_RB =   XXXX XXXX
              |||| ||||_RB0 
              |||| |||__RB1 
              |||| ||___RB2       
              |||| |____RB3
              ||||______RC4 
              |||_______RC5 
              ||________RC6             
              |_________RC7 
 
 ・Rot_RB_OLD  一回前のRot_RBデータ
 ・Rot_RB_buff[ch_no]一回前のRot_RBデータch_noごとに保存
 ・boundry[ch_no]:A相の波形変化
   7 6  5 4  3 2  1 0        00  変化なし         
    |    |    |    |         01  立ち上がり
   [3]  [2]  [1]  [0]        10  立ち下がり
                             11  SW用
 
 ・cw_ccw[ch_no] :回転方向
   7 6  5 4  3 2  1 0        00  変化なし         
    |    |    |    |         01  cw 時計回り
   [3]  [2]  [1]  [0]        10  ccw 反時計回り
                             11  未使用
            7654 3210
・op_flag  = 00XX XXXX
              || ||||_OP6 
              || |||__OP5 
              || ||___OP4       
              || |____OP3
              ||______OP2 
              |_______OP1

 ・voice_max[] 各ボイスパラメータの規定されている上限値
           0〜155+a
   param MIDI出力用ボイスパラメータのNo
   data  MIDI出力用ボイスパラメータのデータ

//**************************************************************
 

●パラメータの名称ですが、その時々の気分で変わっています。前々から関数やフラグ類の名称の付け方にはこだわりがあって、例えば
InputRotaryEncoderHogeDoit();
なんていうのがかたまりであると読み飛ばし、無視、挙句にわからなくなるというクセがあるので、その時の気分で名称をつけて済ますというスタイルなのです。でも、やはり、可読性を重視すべきでコメントとドキュメントは絶対に必要と今頃反省しています。

■割り込み処理


//**************************************************************
// Interrupt Routine
//**************************************************************

void interrupt InterTimer( void ){ 
    if(INTCONbits.TMR0IF && INTCONbits.TMR0IE){
        INTCONbits.TMR0IF = 0;   //基本タイマーは400uS
        TMR0 = T0COUT; 
        
        ReadPort();

        prsc1ms++;    //以下はタイマーで使用
        prscdelay++;
        if( prsc1ms > 5){  
            prsc1ms = 0;
            prscdelay = 0;
        }
    }
}
//**************************************************************

●割り込み処理ルーチンで一番心配なのが設定通りにタイマーが動作しているかです。ここでこけると、皆こけたがこわい。特にpicマイコンは設定がわかりにくいので、ロジアナで最初に確認しました。(こう書くと何となくリッチマンのようですが、あくまでプアーマンです)。タイマーの設定については設定値を算出してくれる親切なサイトもあるようです。
キーワードは、PIC18 TIMER0 設定値 計算
●タイマー割り込み時間は400usとしました。 ReadPort()という関数でスイッチの入力を読み取ります。先だっての実験から、ロータリーエンコーダーA相の最速ONタイミングは約5.4msでした。その時、B相が約2.6ms間同じ状態を維持しているので、ノイズ対策として3回一致のタイミングだと合計して半分の1.3ms以下であれば確実に入力確定すると考えました。400usなら合計800usなので、十分余裕があります(多分)。でも、その後、ノイズ対策はこれでは意味がないと思い至りました。

■スイッチ・RE入力処理    ReadPort()

//*************************************************************
 void ReadPort(void){
        for(Ich_no = 0;Ich_no < 12;Ich_no++){
            Scan_out(Ich_no);
            Rot_RB_OLD = Rot_RB_buff[Ich_no];

            Rot_RB = (~PORTB) & 0b00001111; //RBの下位4ビット
            Rot_RC = (~PORTC) & 0b11110000; //RCの上位4ビット
            Rot_RB = Rot_RB | Rot_RC;
            Rot_RB_buff[Ich_no] = Rot_RB;
            
            if(Ich_no == 0){
                And[Ich_no] = Rot_RB & Rot_RB_OLD; //0SWの判定用
                continue;            
            }
            if(Ich_no == 1){
                And[Ich_no] = Rot_RB & Rot_RB_OLD; //1SWの判定用                            
            }
            IExor = (Rot_RB ^ Rot_RB_OLD);
            if( IExor ){
                write_buff(Ich_no);   //これら4個のパラメータを
                write_buff(Rot_RB);  //リングバッファにストア
                write_buff(IExor);                   

            }
        }
        Scan_out(66);  //0〜11以外の数字  
    }

//************************************************************** 
 
//**************************************************************
// Scan_out()
//**************************************************************
void Scan_out( uint8_t sn){
            LATA = 0b11101111;
            LATB = 0b00110000;
            LATC = 0b00000111;
            switch(sn){
                case 0: LATA0=0; break;     
                case 1: LATA1=0; break;
                case 2: LATA2=0; break;
                case 3: LATA3=0; break;
                case 4: LATA5=0; break;
                case 5: LATA6=0; break;
                case 6: LATA7=0; break;
                case 7: LATC0=0; break;
                case 8: LATC1=0; break;
                case 9: LATC2=0; break;
                case 10:LATB4=0; break;
                case 11:LATB5=0; break;
                default:
                        LATA = 0b11101111;
                        LATC = 0b00000111;
                        LATB = 0b00110000;
            }
        }

//**************************************************************
●スイッチ・RE入力は12行を一気に読み込んでいます。その後、スイッチの前処理をしています。スイッチとロータリーエンコーダ(RE)の入力加工はメインの方で行います。割り込みルーチンの処理時間が心配でしたが、タイマー割り込み時間の400usより少なかったです。
●相変わらず()でくくるのが多いです。Cの優先順位が覚えられません。わからない時は()でくくるのが鉄則。以降、一向に反省する気配はないでしょう。

■テーブルの説明

//**************************************************************
// no[ ][ ]   table
//**************************************************************
  const uint8_t no[12][8]= { {159,159,160,160,161,161,162,162},
                             {137,137,142,142,157,157,158,158},
                             {141,141,140,140,139,139,138,138},
                             {129,129,128,128,127,127,126,126},
                             {163,163,144,144,135,135,134,134},
                             {133,133,132,132,131,131,130,130},
                             {13,13,14,14,143,143,156,156},  
                             {17,17,8,8,15,15,16,16},     
                             {20,20,19,19,18,18,136,136},   
                             {3,3,2,2,1,1,0,0},        
                             {10,10,9,9,12,12,11,11},     
                             {7,7,6,6,5,5,4,4} };
  
//*************************************************************
●switch文がお気に入りです。if文で書くと、何となくスマートではないのですね。整理、整頓、清掃、清潔が身についているからでしょうか? ・・・いいえ、単にコスパ最高だからなんですわ。
● パネル(ケース)のスイッチ・RE位置とプログラム上のスイッチ・REの関係をここで決めています。テーブルの数値は、DX7の取扱説明書のp.30のDX共通 voice Parameterの番号です。RE1個あたりに2バイト使っています。レイアウトを変更する時に使用します。
●このリストの説明は次回にします。なんせ、メインルーチンが複雑な上に、これにともなうテーブルがまたでかい。op1からop6までの処理をきれいにまとめることができなかったからなのです。

今日の作業はここまで。ホッピーを焼酎で割ったのを飲みたいけど、飲み屋に行くとすぐ忘れてしまう。カルピスサワーで乾杯。

※このブログで書かれた内容によって生じた損害等の一切の責任を負いかねますので、予めご了承下さい。

nice!(0)  コメント(0) 
共通テーマ:趣味・カルチャー

音を創るということ4(製作編)

■部品リスト
$1は110円で換算しています。
部品名 数量 単価 小計(円)
PIC18F27J53 1 270 270
PC900V 1 $0.266/10個 3
78N05 1 30 30
NJM2845           入手したのはリード付きだった
1
50 50
1N4148W 97 $0.75/100個 80
ロータリーエンコーダ  EC12E2420801 42
80
3360
ユニバーサル基板 片面115×150 1 814 814
スイッチ 6 80 480
抵抗入りTr DTD114EA  デジトラ10K-10K 4 10 40
XH3P ベースポスト 52 $2.09/100個 120
XH3P ハウジング 52 $0.61/50個 70
XH3P ピン 156 $0.92/100個 158
ICソケット 6P 1 15 15
ICソケット 28P 300mil 1 70 70
ピンヘッダー2P&ジャンパー 1 15 15
ピンヘッダー6P 1 20 20
電解コンデンサ 6.3V 22uF 1 10 10
電解コンデンサ 16V 100uF 1 10 10
電解コンデンサ 50V 10uF 1 10 10
積層セラミック 50V 0.1uF 4 15 60
SMD抵抗 220   EIAJで3216サイズ 5 $3.45/1250個 1.5
SMD抵抗 280  E96 系列なのでE12の270で可 1 $3.45/1250個 0.3
SMD抵抗 10K 11 $3.45/1250個 3.4
スズメッキ線  実測すると3m未満でした 1 250 250
ケース タカチ YM-200 1 1400 1400
DINソケット(メス)5P 3 60 180
電源プラグΦ2.1 1 60 60
つまみ(RE用) 42 $0.54/10個 250
DC9V 電源アダプター 1j
580
580
電線 AWG28 3色で各7m程度 3 630 1890
RE用六角ナット M9×0.75×2.5mm 42 571/50個 480
RE用ワッシャー 9×16×1.5mm 42 497/50個 418
    合計 11185.7



































●手持ちの部品を多数使用したので、リストの値段は秋月電子通商などの現在での価格を調査しました。
●SMD抵抗(コンデンサも)は詰め合わせパックで買うのがお得です。80種類×25個パックなどがあります。
keyword is SMD 1206 resistor kit assorted set Capacitor
●リストを見ると、アリババ(中国)から買うのがベストみたいですが、発注してから部品が到着するまで50〜60日かかる場合があると表示されています。つまり、時間を買うことに価値があるのかを見極めることです。
●RE用六角ナットとワッシャーはREの位置決めタブをケースに出さないためのものです。
●念の為にですが、つまみを紛失した時等のためにメンテナンス用部品も確保しています。

■基板
DSC_00019.JPGDSC_00020.JPG












片面ユニバーサル基板にXH3Pのベースポストを配置していきます。レイアウトは行で12個、列で4個となります。XH3Pからの電線が集中するので、各ベースポスト間は余裕を持って配置しました。でも、マイコン他の部品のスペースがなくなるのがこわいです。
マイコン、PC900Vは後々のメンテナンスを考えてICソケットを使用しました。
表面の画像に逆流防止のダイオードが見えません。実は裏側(蛇の目パターン面)にハンダ付けしています。ダイオードの電極間のピッチはカタログ値で3.7mmあります。一方、蛇の目パターンはランドも含めると4mm弱となりますので、蛇の目パターンにダイオードを載せるとちょうどよい寸法となります(下のイメージの感じ)。
面実装・手はんだイメージ1.png
ハンダ付けの際は、フラックスをはんだ面に塗り、爪楊枝で軽く押さえながらはんだ付けをします。

キーワードは、はけ付き フラックス
とはいえ、完成後にテスターで導通チェックをしたところ、5個もはんだ付け不良が発生していました。5/96の不良です。52083ppmの不良では製品としてはちょっとですね。
抵抗、コンデンサなら3216とか3225(もちろんmmサイズ)のチップサイズがちょうどよい大きさです
余談となりますが、チップ部品を扱うとき(特に1608サイズ以下)にピンセットでつまむことがあります。ところが、普通のピンセットではつまむ時にはじいてしまう事故が意外に多いのです。そんな時は、通常は閉じているピンセットを使いましょう。100均でも扱っているので見つけたら即ゲットです。
DSC_00022.JPGキーワードは、逆作用 ピンセット







■ケース加工
PIC_122711.JPGPIC_122817.JPG








何度もしつこいほど言いますが、REとスイッチの穴数は48個、それにMIDI端子が3個、電源アダプター穴が1個で、計52個もあります。これをハンドドリルで穴を開け、その後リーマで穴を拡げるという作業をするのは趣味の範囲を超えています。
そんな時にカインズホームの通販ページを見ていると、振動ドリルがお買い得品として1480円で売り出しているのを見つけました。恥ずかしながら、それまで、振動ドリルに電気ドリルの機能があるとは知らなかったのです。(決してカインズホームのまわし者ではありません。ただの電子工作員です。)

同じ頃、パーツ店で1520円のミニホールソーを見つけました。これらでREの9mmの穴はあっという間に開けることができました。
キーワードは、振動ドリル、ミニホールソー

MIDI端子(DIN5P)の穴径は14.8mm以上とあります。まず9mmの穴を開け、その後リーマーで穴を拡げました。これくらいなら体力を消耗するなんてことはないです。

ケースに部品を取り付けている時に大きな失敗に気づきました。なんと、スイッチとDIN 5Pが当たってしまうのです。あと5mmスイッチを下にずらしておけばよかった。

■電装
何度もしつこいほど言いますが、XH3Pが52個で電線は146本あります。カシメの工具は株式会社エンジニアのPA-21を使用します。1本につき2回かしめると、きれいな状態になるようです。これを続けて作業に慣れると、なんだか段々ハイな気分になっていくのです。これってcaulking highっていうのかな。

■つまみ
部品を集めるのに、REのつまみを探すのが一番苦労しました。リアルのパーツ屋さんや通販部品のサイトなど、「パネルを埋め尽くすような数のつまみ」に合うようなつまみがないのです(日本語むずかしい、探しきれていないだけ)。アリババで見つけようと決めました。つまみはGoogleの英訳でknobと出ています。これを出発点としてさまよった結果、なんとか見つけることができました。
keyword is 360 Degrees Rotary Encoder D Half Shaft Hole Caps Knob
物の名前がわからないとなかなか進めないです。〇〇レンズのアプリに期待。

正月は暖かだったです。気分は、「春なのにコスモスみたい」です(・・・・ちょっと違うな)。さぁ、冷たいビールで乾杯。


※このブログで書かれた内容によって生じた損害等の一切の責任を負いかねますので、予めご了承下さい。

nice!(0)  コメント(0) 
共通テーマ:趣味・カルチャー
前の10件 | -

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。