Mira
外付けEEPROMにデータを書き込み、tone関数で音楽を鳴らす電子メロディの作製🍑
<第5回>
「2つ目のトラブル🦇
tone関数ではすべての音符を鳴らせない🐸」
【目次】
🍩 Tiny蓄音機レコード仕様を作ろう 🍩
🍩1回目「 構想とEEPROMについてと曲紹介」
🍩2回目「EEPROMに楽譜を記録♬ "レコード盤作り"」
🍩3回目「EEPROMから楽譜を読み取って演奏するプログラム」
🍩4回目「トラブル1🐉 ArduinoのRAMが足りない..p_q..💦」
🍩5回目「トラブル2🦇 tone関数ではすべての音符を鳴らせない」(この記事)
🍩6回目「"ちびやっこ"意気消沈。お姉ちゃんの"みつは"は超オンチに🦆」
🍩7回目「Tiny蓄音機レコード仕様、実装します。& 動画🎬」
🍩8回目「Tiny蓄音機レコード仕様🎶 作り方まとめっ♬」
🌷 ATmega328p(蓄音機)と24LC64(レコード盤)の配線
EEPROMと、ArduinoのマイコンATmega328pを使って電子オルゴールを自作する電子工作をしています。
前回は、EEPROMに一曲分の楽譜データを書き込もうとしたところ、書き込む側のArduinoのRAMが足りなかったの。容量に対してデータが多かったのね。そこで曲をA~Eパートに分割してEEPROMに書き込むことにしました。
今回はその続きです。
<書込み用プログラムの分割>
📝 Arduinoから外付けEEPROMへの書き込み
大仕事が終わった。『Onenight』一曲分のすべての音符の音階と長さのデータをプログラムに書きました。次はその楽譜データをEEPROMに書き込むことで、『OneNight』レコード盤が完成します。
前回はArduinoのRAMが足りなかったけれど、今回は曲を5つのパートに分けて、3回にわたって(AとB、DとEはいっしょにしました)書き込みます。
さあ、うまくいくかな?
上の図のように配線をして、Arduinoの書き込み用プログラム(→ プログラム例はこちら)に分割したデータを入れて、EEPROMに書き込みを行います。このときに書き込み用プログラムの中の音符数や書き込み番地も、パートに合わせて書き換えが必要です。
📓 例:) Cパートの書き込み用プログラム
/*EEPROMへ「OneNight」の「ABパート」を書き込んだ後に 「Cパート」を書込むためのツールスケッチ*/ #include<Wire.h> #include "tone_def.h" int melody[] = { /*C*/o,ru,ru,0,s,s,s,l,s, s,o,m,o,o,l,f,r,l,o, ru,ru,ru,ru,ruh,s,l,s, //27 o,o,l,l,s,o,f,o,f,m,r, m,0,m,s,f,m,f,o, o,l,s,0,s,s,s, //26 du,s,s,s,du,s,s,s,du,l,s, //11 /*C①*/ru,fuh,su, ou,fuh,ou,su,su,fuh,mu,fuh,ru,s,ru, //14 mu,ru,mu,ou,ru,0,du,s, du,du,du,du,mu,ou,fuh,ou,fuh,ru,ru, s,l,l,s,ru,s,0,fuh, //27 ou,fuh,ou,su,su,fuh,mu,fuh,ru,ru, mu,fuh,ou,lu,su,0,mu,ou, 0,mu,fuh,0,ru,mu,mu, ou,su,muu,0, //29 ou,fu,mu,ru,s,s,ru, mu,ru,mu,o,ru,du,s, du,o,o,r,l,o,l,s,s, fh,s,mu,ou, //27 su,lu,ou,fuh,lu,ou,fuh,ru, ou,lu,su,mu,ou, mu,fuh,ru,mu,mu, ou,su,muu,0, //22 /*C*/o,ru,ru,0,s,s,s,l,s, s,o,m,o,o,l,f,r,l,o, ru,ru,ru,ru,ruh,s,l,s, //27 o,o,l,l,s,o,f,o,f,m,r, m,0,m,s,f,m,f,o, o,l,s,0,s,s,s, //26 du,s,s,s,du,s,s,s,du,l,s, //11 /*C②*/s,s, du,du,fu,ou, //6 }; int durations[] = { /*C*/8,16,3.2,5.33,16,16,16,16,16, 8,16,5.33,16,16,8,16,8,8,3.2, 16,16,16,16,8,8,8,8, //27 8,16,8,16,16,16,4,16,16,16,16, 4,16,16,16,3.2,16,8,2.66, 16,16,3.2,16,16,16,16, //26 8,16,4,16,16,16,16,8,16,16,2.28, //11 /*C①*/32,32,16, 8,16,8,8,16,8,16,8,16,16,16, //14 8,16,5.33,16,3.2,8,16,16, 8,16,8,16,16,16,8,16,8,8,16, 8,16,16,8,16,3.2,5.33,16, //27 8,16,8,8,16,8,16,8,8,16, 8,16,5.33,16,3.2,8,16,3.2, 8,16,3.2,8,16,2,4, 32,32,16,8, //29 8,16,8,8,3.2,8,8, 8,16,8,8,3.2,8,8, 8,16,3.2,8,8,8,16,16, 2,8,8,8,8, //27 8,16,8,5.33,8,16,8,5.33, 4,4,2.66,16,2.28, 16,2.28,16,2,4, 32,32,16,16, //22 /*C*/8,16,3.2,5.33,16,16,16,16,16, 8,16,5.33,16,16,8,16,8,8,3.2, 16,16,16,16,8,8,8,8, //27 8,16,8,16,16,16,4,16,16,16,16, 4,16,16,16,3.2,16,8,2.66, 16,16,3.2,16,16,16,16, //26 8,16,4,16,16,16,16,8,16,16,4, //11 /*C②*/4,2, 2,4,8,8, //6 }; void eep_write(int device_add, unsigned int eep_add, int data){ Wire.beginTransmission(device_add);//対象デバイスへ移動 Wire.write((int)(eep_add >> 8)); Wire.write((int)(eep_add & 0xff)); Wire.write(data); Wire.endTransmission(); } void setup() { Serial.begin(9600); Wire.begin(); for(int n=210; n < 253+210; n++){ //音符の数 210番地から書き込み 210~462番地まで書き込む //[210:ABパートの格納済み番地が208。209にはCパートの音符数格納。] eep_write(0x50,n,melody[n-210]/4); //濃縮[÷4:OneNight] nは210から210+253まで //eepに高音域(256以上)を格納できないので、÷4や-100の濃縮還元 delay(5);//書き込み待ち eep_write(0x50,n+719-3,durations[n-210]); //719+207(AB音符数)番地から253個書き込む delay(5);//書込み待ち } eep_write(0x50,209,253); //209番地にCの音符数格納 delay(5); } void loop() { }
ちょっとごちゃごちゃしていますが......。
プログラム下のほうの「void setup」のところを見てください。このプログラムはCパートの書込み用なので、EEPROMの書き込み先番地が210とか462とか大きな数になっています。これはABパートのデータがEEPROMの208番地まで書き込まれているから、その続きに書き込むため。音符数はCパートのみの音符数になっています。
プログラムの書き換えというのは、それら音符数や番地の数字のことです。
プログラム上のほうのmuやらruやらfuhは、音階のミやラやファ#のことです。ライブラリのようなものを作ったので、それら自作の名前が音階(周波数)を表しているんです。→ ライブラリを作る。
<外付けEEPROM内の番地図>
EEPROM[24LC64]には0番地から8191番地まで、データを格納できる番地があります。『Onenight』の音符の数(私のプログラムでの音符数だけどね)は715音符。1つの音符には「音階データ」と「長さデータ」の2種類が必要。さらに各パートごとの音符数も記憶しなきゃいけない。こんがらがっちゃいそうだから、番地図を作りました。
<番地名> <格納データ>
0 ABパートの音符数
1 曲全体のテンポ
2~208 ABパート"音階" 207個
209 Cパートの音符数
210~462 Cパート"音階" 253個
463 DEパートの音符数
464~718 DEパート"音階" 255個
719~925 ABパートの"長さ" 207個
926~1178 Cパートの"長さ" 253個
1179~1433 DEパートの"長さ" 255個
EEPROMの中はこんなふうにデータが収められていて、1433番地まで使っています。まだまだ空き地がいっぱいあるので、この5倍以上の長さの曲を入れたり、同じくらいの長さの曲を5曲入れるなんていうのもいいわね。
(この時点(トラブル前)での音符数の格納は、「0番地」に一曲の音符総数を格納するのみでした。上記のように各パートごとに音符数を格納しているのは、トラブル後の処理によるものです。)
さあ、これで加賀谷玲さんの『Onenight』が1曲まるまるっと入ったEEPROMレコード盤の完成です!(∂, ∂🎀y
レコード盤『Onenight』"EEPROM"オリジナルエディション。あの「銀河鉄道の夜」の感動を再び。加賀谷玲のオリジナル曲を電子工作娘ミラヒェンが電圧スピーカ用にオリジナルアレンジ。
~現代(いま)だからこそ味わいたいレトロロマンがある~大好評発売中!
(嘘です。非売品です🐈)
🍰 Tea Time ☕
Mira & Luna's nursery lab
<トラブル発生⚡
tone関数で制御できる音符数には制限があった🦇>
ついにレコード盤としての曲を収めたEEPROMが完成したので、さっそく再生用プログラムをArduinoに書き込んで再生してみましょう🍅
Arduino IDE の「マイコンボードに書き込む」をクリック。
「書き込みが完了しました」の文字。
そしてブザー(圧電スピーカ)から「Onenight」のメロディが......
♪ Dancing ♬ やったね🎶
・
・
・
・・・ってなる予定だったのに~
流れてきたのは変なバグ音。
い゛や゛~~🐛
Mira Bitter 🦇
ほんのちょっとだけお菓子をやけ食いしてからいろいろ試して調べたところ、tone関数でメロディを流すとき音符数が257以上だと変なバグ音が出ることがわかったの。よく見つけた私、えらい。で、256だと無音で、255以下だと正常に動作する。ミラアレンジの『Onenight』の音符数は715。さあ、どうしましょ。
もうちょびっとだけお菓子を食べて考えました。
<Tiny蓄音機の再生用プログラムを変える>
tone関数で鳴らせる音楽は音符数255個まで。これは変えられそうにない。だから音符255個以内の演奏を3回行うことにしたらどうかしら。書き込みを行ったときのように曲を3分割して、それを連続して流す。つまりtone関数のプログラムを3つ続けて書くのよ。
🍎 こんなふうに
// Tiny蓄音機実装用 [OneNight]専用 // 周波数を÷4で濃縮還元用 #include<Wire.h> int eep_read(int device_add, unsigned int eep_add){ Wire.beginTransmission(device_add);//対象デバイスへ移動 Wire.write((int)(eep_add >> 8)); Wire.write((int)(eep_add & 0xff)); Wire.endTransmission(); Wire.requestFrom(device_add,1); if(Wire.available()){} return Wire.read(); } void setup() { Wire.begin(); } void loop() { int onpu_ab = eep_read(0x50,0); //0番地から音符数を読む ABパート分 int sokudo = eep_read(0x50,1);//1番地から速度を読む(100分の1) for(int note = 0; note < onpu_ab; note++){ //ABパート再生 int melody =eep_read(0x50,note+2); int duration = eep_read(0x50,note+719); //719番地からduration格納してある int noteDuration = sokudo*100 / duration; //速度を復元(x100) tone(3, melody*4, noteDuration); int note_hazama = noteDuration * 1.10; delay(note_hazama); } int onpu_c = eep_read(0x50,209); //209番地にあるCの音符数を読む for(int note = 0; note < onpu_c; note++){ //Cパート再生 int melody =eep_read(0x50,note+onpu_ab+3); int duration = eep_read(0x50,note+onpu_ab+719); int noteDuration = sokudo*100 / duration; //速度を復元(x100) tone(3, melody*4, noteDuration); int note_hazama = noteDuration * 1.10; delay(note_hazama); } int onpu_de = eep_read(0x50,463); //463番地にあるDEの音符数を読む for(int note = 0; note < onpu_de; note++){ //DEパート再生 int melody =eep_read(0x50,note+onpu_ab+onpu_c+4); int duration = eep_read(0x50,note+onpu_ab+onpu_c+719); int noteDuration = sokudo*100 / duration; //速度を復元(x100) tone(3, melody*4, noteDuration); int note_hazama = noteDuration * 1.10; delay(note_hazama); } delay(5000); //余韻 }
「ABパート再生」「Cパート再生」「DEパート再生」という部分が3つの再生プログラムね。
こうすればEEPROM内の楽譜データすべてを演奏することができる。
でもそのためには音符数の情報を各パートごとに読み取れるようにしなきゃいけない。3つのプログラムそれぞれで音符数の情報が必要になるからね。
そんなわけで上述の「番地図」のように「AB」「C」「DE」それぞれの音符数も格納できるようにEEPROM内のデータを書き直しました。
そして再生すると、うまくいきました。(∂. <🎀y
でもね、このやり方だと1つ諦めなくちゃいけないことがあるの。それは再生用プログラムに「OneNight」用の数値を書き込んでしまったことによって、『Onenight』専用の蓄音機になってしまったこと。
これでレコード盤EEPROMを交換すれば音楽が流れる「Tiny蓄音機レコード仕様」にはならない。プログラムのリライトが必要ね。
でもまずはこのプログラムで「Tiny蓄音機レコード仕様」の作製を進めていきます。書き換えは後からでもできるもんね🐪
兎に角🐇これで「Tiny蓄音機レコード仕様」で『OneNight』を鳴らすことができそうです。やれやれ🍵
【目次】
🍩 Tiny蓄音機レコード仕様を作ろう 🍩
🍩1回目「 構想とEEPROMについてと曲紹介」
🍩2回目「EEPROMに楽譜を記録♬ "レコード盤作り"」
🍩3回目「EEPROMから楽譜を読み取って演奏するプログラム」
🍩4回目「トラブル1🐉 ArduinoのRAMが足りない..p_q..💦」
🍩5回目「トラブル2🦇 tone関数ではすべての音符を鳴らせない」(この記事)
🍩<次回>6回目「"ちびやっこ"意気消沈。お姉ちゃんの"みつは"は超オンチに🦆」
🍩7回目「Tiny蓄音機レコード仕様、実装します。& 動画🎬」
🍩8回目「Tiny蓄音機レコード仕様🎶 作り方まとめっ♬」
<お買い物コーナー>
🌟「銀河鉄道の夜」楽譜 🌟 CD 🌟 DVD
加賀谷玲さんの音楽も、KAGAYAさんの映像も、どちらも素敵です🌠