【ハード音源】ファミコン風音源をマイコンで作ろうとした話【欲しい】
この記事は FUN AdventCalendar 2022 の17日目の記事になります。
昨日はかげろんさんによる プログラミング教育とTAをやった気がする でした!
私も小学生~高校生にプログラミングを教える機会が時々あった身として、答えをそのまま教えないように、でも理解につながるくらいは教えつつ...と、どこまで教えるのか悩んでいました。難しいです...
TAは...一度やってみようと思った時はあったのですが、移動時間的にきついです...笑笑
自己紹介+はじめに
こんにちは、kazu8823と申します(twitter:@kazujdDR8823)
知能システムコース3年、好きな言語はC・C#、最近はプロジェクトでドアを作ってました。
こんなことや、
情報処理演習で作ったエアギターマシン pic.twitter.com/tzRPmw6LQP
— kazu (@kazujdDR8823) January 17, 2022
そんなことや、
— kazu (@kazujdDR8823) June 22, 2022
あんなことや、
ごめん、同窓会には行けません。いま、ハイラルにいます。 pic.twitter.com/IGfQZ0EtJN
— kazu (@kazujdDR8823) January 10, 2022
こんなことを、
両手に花 pic.twitter.com/BChRvwzi6z
— kazu (@kazujdDR8823) November 27, 2022
しています。
よろしくお願いします。
- 自己紹介+はじめに
- 事の発端
- 事の発端2
- 仕様
- 使うマイコン
- 回路作成
- 1. とりあえず音を出す
- 2. 和音にする
- 3. ちゃんとした音階にする
- 4. 矩形波
- 5. 三角波
- 6. ノイズ
- 7. 全部音出してみる
- 8. ファミコン風音源製作!!!
- 9. 演奏!!!!
- 10. まとめ!!
事の発端
ということで作ります
ヤマハ(当時は日本楽器製造)が1983年に開発したFM音源IC
様々なアーケードゲームの基板に使用されている
・ファンタジーゾーン
・アレックスキッド
・沙羅曼蛇
・グラディウスII -GOFERの野望-
・ストリートファイターII
・バトルガレッガ
事の発端2
もともとアドカレでは別のネタを書く予定でした。
それは「ファミコンカセットを作ろう!」というやつです。
ですが、プロジェクトが忙しくアドカレの準備に手を付けないでいたら、いざ準備を始めた時には必要な部品の発注が間に合わないことが確定しました....
しかしファミコンもあきらめきれないので、いっそファミコン音源作っちゃえとなりました。
(FM音源よりファミコン音源の方が楽そうなのもあったりなかったり...)
ということで作ります
仕様
製作に入る前に、具体的な音源の仕様を決めます。
なるべくファミコン音源の仕様に寄せつつ、時間がかかりそうな要素は省いて、簡易的なものにします。
・2チャンネル
・デューティー比は12.5%、25%、50%の三種類 (デューティー比を変えるとちょっと音が変わります。)
・音量は16段階
・1チャンネル
・綺麗な三角波ではなく、16段階にカクカクしている
・音量変更不可
ノイズ
・1チャンネル
・長周期ノイズと短周期ノイズの二種類
・音量は16段階
・周波数は16段階
の計4チャンネルです。
DPCMはあきらめました...
矩形波・三角波に関しては、ファミコンと違い周波数指定ではなく、音階で指定する形にしました。
それと時間がなかったのでエンベローブは実装してないです...
↓参考にさせていただきました
使うマイコン
今回使用するマイコンはこちらです!!!
私の愛するdspic33fj64gp802ちゃんです!!!!
秋月で1個870円で販売中です!!
みんな買おうね!!!!超オススメ!!!!
picマイコンの中ではちょっと値段が高めなのですが、
・最大クロック80MHz
・プログラムメモリ:64kB
・データメモリ:16kB
・GPIO:21ピン
・ADC:10チャンネル
・I2C:1チャンネル
・SPI:2チャンネル
・タイマ:5チャンネル
・クロック:数字がでかいほど処理が速い
・プログラムメモリ:プログラムが格納されるメモリ。でかいほどめっちゃプログラム書ける
・データメモリ:変数の値とかが格納されるメモリ。でかいほどめっちゃ変数宣言できる
・GPIO:General Purpose Input Output 入出力に使えるピンのこと
・ADC:Analog to Digital Converter アナログ値を読み取る機能
・I2C, SPI:マイコンやICなどが通信する規格
・タイマ:時間測ったり、定期的な処理をするのに使ったりするやつ
そして!!!
・DAC:4チャンネル!!!!!
(差動出力なので、実質2系統)
PWMじゃなく、ガチのアナログ出力がついているんです!!!!
しかもオーディオ向けにいろいろとついていて、何とハードウェア側で256倍のオーバーサンプリングをしてくれるので、エイリアシングノイズを意識せずとも減らせます!!!
神だね
ほら皆さん欲しくなってきたでしょう~
ここから買えますよ~
ただし、電源電圧は3.0~3.6Vです。この点だけご注意ください...
そして、このマイコンは冒頭の自己紹介の時に紹介した、エアギターマシンの内部にも使用しています。
情報処理演習で作ったエアギターマシン pic.twitter.com/tzRPmw6LQP
— kazu (@kazujdDR8823) January 17, 2022
入力系は全部arduinoに任せて、音の生成処理はすべてdspic33fj64gp802で行っています。
というか今回の記事は書く時間無かったんで、これのリメイクみたいなもんです...
それでは作っていきましょう!!!
開発環境は
・MPLAB X IDE v5.45
・XC16 v1.70
です
回路作成
回路作成(というかプログラムも)するのにあたってめちゃくちゃ参考にさせていただいたサイトをご紹介します。
はい、picマイコンユーザーおなじみのサイトです。本当にありがとうございます!!
いつも参考にさせていただいています。
ということで、ブレッドボード上に回路を製作しました。上記のサイトにある回路図から必要な部分(電源回路・オーディオ出力・水晶発振子)だけ取り出し、入力としてスイッチ6つとLED3つ(後でもう一つ増えます)を加えています。
↓とりあえずのLチカ
電子工作初心者です!!
— kazu (@kazujdDR8823) December 13, 2022
Lチカできました!!!嬉しい!! pic.twitter.com/TJ20RJly1f
こんな感じです
それでは実際に音を鳴らしてみましょう
1. とりあえず音を出す
まずは正弦波を出してみましょう
今回は音の波形の作り方としてウェーブテーブル方式を使います。
具体的にはこんな感じに、波形一周期分を任意の数で分割して配列に入れておきます。
今回は256個に分割しています。
// 正弦波 サンプル数:256 const signed int sinWave[] = { 0, 804, 1607, 2410, 3211, 4011, 4807, 5601, 6392, 7179, 7961, 8739, 9511, 10278, 11038, 11792, 12539, 13278, 14009, 14732, 15446, 16150, 16845, 17530, 18204, 18867, 19519, 20159, 20787, 21402, 22004, 22594, 23169, 23731, 24278, 24811, 25329, 25831, 26318, 26789, 27244, 27683, 28105, 28510, 28897, 29268, 29621, 29955, 30272, 30571, 30851, 31113, 31356, 31580, 31785, 31970, 32137, 32284, 32412, 32520, 32609, 32678, 32727, 32757, 32766, 32757, 32727, 32678, 32609, 32520, 32412, 32284, 32137, 31970, 31785, 31580, 31356, 31113, 30851, 30571, 30272, 29955, 29621, 29268, 28897, 28510, 28105, 27683, 27244, 26789, 26318, 25831, 25329, 24811, 24278, 23731, 23169, 22594, 22004, 21402, 20787, 20159, 19519, 18867, 18204, 17530, 16845, 16150, 15446, 14732, 14009, 13278, 12539, 11792, 11038, 10278, 9511, 8739, 7961, 7179, 6392, 5601, 4807, 4011, 3211, 2410, 1607, 804, 0, -804, -1607, -2410, -3211, -4011, -4807, -5601, -6392, -7179, -7961, -8739, -9511, -10278, -11038, -11792, -12539, -13278, -14009, -14732, -15446, -16150, -16845, -17530, -18204, -18867, -19519, -20159, -20787, -21402, -22004, -22594, -23169, -23731, -24278, -24811, -25329, -25831, -26318, -26789, -27244, -27683, -28105, -28510, -28897, -29268, -29621, -29955, -30272, -30571, -30851, -31113, -31356, -31580, -31785, -31970, -32137, -32284, -32412, -32520, -32609, -32678, -32727, -32757, -32767, -32757, -32727, -32678, -32609, -32520, -32412, -32284, -32137, -31970, -31785, -31580, -31356, -31113, -30851, -30571, -30272, -29955, -29621, -29268, -28897, -28510, -28105, -27683, -27244, -26789, -26318, -25831, -25329, -24811, -24278, -23731, -23169, -22594, -22004, -21402, -20787, -20159, -19519, -18867, -18204, -17530, -16845, -16150, -15446, -14732, -14009, -13278, -12539, -11792, -11038, -10278, -9511, -8739, -7961, -7179, -6392, -5601, -4807, -4011, -3211, -2410, -1607, -804 };
そして音を鳴らすときには、これを順番に参照して鳴らします。
例えば、1秒間に256階音量の更新が行われるとしたら、
・更新のたびに参照する場所を(要素1, 要素2, 要素3, ...)と読むと -> 1Hzに
・更新のたびに参照する場所を(要素1, 要素3, 要素5, ...)と読むと -> 2Hzに
・更新のたびに参照する場所を(要素1, 要素5, 要素9, ...)と読むと -> 4Hzに
といった感じに、何個飛ばして読むかによって、任意の周波数の音にすることができます。
ウェーブテーブル方式を採用した理由として、とにかく処理負荷が軽いことがあります。マイコンのような計算資源が限られている環境では、三角関数は相当重い処理になってしまうため、ただ配列から値を引っ張ってくるだけで波形を作れるこの方式を採用しました。
それでは実装していきます。
....と、プログラムを乗せようと思ったのですが、あまりにも長いので要点だけのせます...
// DAC割り込み処理関数 void __attribute__((interrupt, no_auto_psv)) _DAC1LInterrupt(void){ IFS4bits.DAC1LIF = 0; // 割り込みフラグリセット signed int out = 0; if(flag == 1){ out = sinWave[step++]; //out = sawWave[step++]; LED1 = 1; }else{ LED1 = 0; } // 出力 DAC1LDAT = out; }
音を鳴らしている部分の関数です。48000Hzの周期で呼ばれます。
・sinWaveは少し上に載せたコードで初期化しています
(・sawWaveには同様の方法でのこぎり波を入れています)
・flag は スイッチ1でON/OFFしています。
・DAC1LDAT に格納した値がそのままアナログ出力されます。
設定はいろいろとありますが、音を鳴らす部分の処理はたったこれだけで完成します!
そして、実際に音を鳴らしている様子を直撮りしようとしたのですが、聞こえにくすぎたため、ライン撮りで録音しています。
正弦波ライン撮り
ちなみにライン撮りした波形はこんな感じで、結構綺麗に出力できています。
ついでにのこぎり波も作ってみました
(スピーカーしょぼい&無理やり音量上げてるので、音めちゃくちゃ悪いです...スイマセン...)
のこぎり波ライン撮り(ちょっと音量注意)
2. 和音にする
単音だけではさみしいので和音にしてみましょう
和音にする方法は単純で、ただ足し算するだけです。
プログラム全文はこちら
// DAC割り込み処理関数 void __attribute__((interrupt, no_auto_psv)) _DAC1LInterrupt(void){ IFS4bits.DAC1LIF = 0; // 割り込みフラグリセット // 出力 signed int out = 0; // 1音目 if(flag1 == 1){ out += sinWave[step1] >> 2; step1 += 1; LED1 = 1; }else{ LED1 = 0; } // 2音目 if(flag2 == 1){ out += sinWave[step2] >> 2; step2 += 2; LED2 = 1; }else{ LED2 = 0; } // 3音目 if(flag3 == 1){ out += sinWave[step3] >> 2; step3 += 3; LED3 = 1; }else{ LED3 = 0; } // 出力 DAC1LDAT = out; }
一点だけ注意点として、DAC1LDATは符号あり16bitのため、-32768~32767の範囲に収める必要があります。
sinWaveは振幅32768としてデータを作っていたため、そのままでは簡単にオーバーフローしてしまいます。そのためそれぞれの音で1/4にしています。
オーバーフローしたときの音もそれはそれで面白い音が出ます...
音小さめです
音量が小さいのでわかりにくいのですが、しっかりと和音が出せています。
ライン撮り撮るの忘れた...
3. ちゃんとした音階にする
さっきの和音は音程もクソもないものだったので、次はちゃんと音階にしましょう。
音階ごとの周波数を元に、1周期ごとに加算する値をしっかり計算します。
具体的には
(ウェーブテーブルの数 / (サンプリング周波数 / 音の周波数)) * 256
となります。今回の場合
(256 / (48000 / 音の周波数)) * 256
です。
ちなみに一番後ろの「* 256」はなぜあるのかというと、処理負荷を軽減するためにあります。
何個飛ばしで値を読むかをただ計算するだけだと、少数がたくさん出てしまい、かといって切り捨てたりしてしまうと音痴な音になってしまいます。
じゃあ少数で計算すればいいじゃんという話なのですが、基本的に整数演算より少数演算のほうが処理が重いです。なので、できる限り整数演算のみで終わらしたいところです。
そのために、256倍(8ビットシフトで処理できる)した状態で全部の計算をしています。
逆に配列を参照するときは sinWave[step1 >> 8] のように256で割ってあげることにより、処理負荷が高くならないようにしつつもある程度の精度を保ちながら計算できるようになります。
プログラム全文はこちら
// DAC割り込み処理関数 void __attribute__((interrupt, no_auto_psv)) _DAC1LInterrupt(void){ IFS4bits.DAC1LIF = 0; // 割り込みフラグリセット // 出力 signed int out = 0; // stepに加算する値の計算式 // (ウェーブテーブルの数 / (サンプリング周波数 / 音の周波数)) * 256 // (256 / (48000 / 音の周波数)) * 256 // 1音目 if(flag1 == 1){ out += sinWave[step1 >> 8] >> 2; step1 += 714; // ド 523.251Hz LED1 = 1; }else{ LED1 = 0; } // 2音目 if(flag2 == 1){ out += sinWave[step2 >> 8] >> 2; step2 += 900; // ミ 659.255Hz LED2 = 1; }else{ LED2 = 0; } // 3音目 if(flag3 == 1){ out += sinWave[step3 >> 8] >> 2; step3 += 1070; // ソ 783.991Hz LED3 = 1; }else{ LED3 = 0; } // 出力 DAC1LDAT = out; }
一つ前との違いは、stepに加算する値を変更しただけです。
無理やり音量上げているのでノイズ酷いです....
ドミソの和音が作れました
今回は音階に対応する値を直接書いていましたが、実際に使用する際は下の写真のように、音程ごとに配列に入れて格納すると使いやすいです。
4. 矩形波
ここまではとりあえず音を鳴らしてみよう的なものでしたが、ここからが本番みたいなもんです。ファミコン風の音を鳴らします。
まずは矩形波を鳴らしてみましょう。
まずは先ほどと同様にウェーブテーブルを作ります。
相当長いので画像で...
・square12_5Wave -> デューティー比12.5%
・square25Wave -> デューティー比25%
・square50Wave -> デューティー比50%
となっています
プログラム全文はこちら
// DAC割り込み処理関数 void __attribute__((interrupt, no_auto_psv)) _DAC1LInterrupt(void){ IFS4bits.DAC1LIF = 0; // 割り込みフラグリセット // 出力 signed int out = 0; // stepに加算する値の計算式 // (ウェーブテーブルの数 / (サンプリング周波数 / 音の周波数)) * 256 // (256 / (48000 / 音の周波数)) * 256 // 1音目 if(flag1 == 1){ out += square12_5Wave[step1 >> 8] >> 2; step1 += 714; // ド 523.251Hz LED1 = 1; }else{ LED1 = 0; } // 2音目 if(flag2 == 1){ out += square25Wave[step2 >> 8] >> 2; step2 += 714; //step2 += 900; // ミ 659.255Hz LED2 = 1; }else{ LED2 = 0; } // 3音目 if(flag3 == 1){ out += square50Wave[step3 >> 8] >> 2; step3 += 714; //step3 += 1070; // ソ 783.991Hz LED3 = 1; }else{ LED3 = 0; } // 出力 DAC1LDAT = out; }
ひとつ前との違いは、使うテーブルを矩形波の物に変更しただけです
矩形波ライン撮り(音量注意です)
duty比 12.5->25->50->同時 の順番です
矩形波が作れました。それっぽいものができてきていますね!
5. 三角波
続いて三角波を鳴らしてみましょう
仕様の話の時にも少し触れましたが、ファミコンの三角波は純粋な三角波ではありません。
出力が16段階になっているため、カクカクとした三角波になっています。
このカクカクとした部分がファミコンの三角波特有の音を作っています。
まずはウェーブテーブルを作ります
プログラム全文はこちら
// DAC割り込み処理関数 void __attribute__((interrupt, no_auto_psv)) _DAC1LInterrupt(void){ IFS4bits.DAC1LIF = 0; // 割り込みフラグリセット // 出力 signed int out = 0; // stepに加算する値の計算式 // (ウェーブテーブルの数 / (サンプリング周波数 / 音の周波数)) * 256 // (256 / (48000 / 音の周波数)) * 256 // 1音目 if(flag1 == 1){ out += tri16Wave[step1 >> 8] >> 2; step1 += 714; // ド 523.251Hz LED1 = 1; }else{ LED1 = 0; } // 2音目 if(flag2 == 1){ out += tri16Wave[step2 >> 8] >> 2; step2 += 900; // ミ 659.255Hz LED2 = 1; }else{ LED2 = 0; } // 3音目 if(flag3 == 1){ out += tri16Wave[step3 >> 8] >> 2; step3 += 1070; // ソ 783.991Hz LED3 = 1; }else{ LED3 = 0; } // 出力 DAC1LDAT = out; }
ひとつ前との違いは、使うテーブルを三角波の物に変更しただけです
三角波ライン撮り
あれっ? ファミコンっぽさ薄いぞ....
ちょっと波形を見てみましょう
うん、16段階が完全に消えてますねこれ。なんかただざらざらしてる感じの波形になってますね...
なぜだ....
あっ!!!!!!
オーバーサンプリングされちゃってんじゃん.....
これはマジでどうしようもないので諦めます...
ファミコン風だからね!!!!!仕方ないね!!!
ということで普通の三角波ができました....
6. ノイズ
最後にノイズを鳴らしてみましょう
こちらを参考にしています
ファミコンのノイズをおおざっぱに説明すると(間違ってたらスイマセン...)
・HIGH,LOWのどちらかが乱数によってランダムに出力される
・乱数の更新周期は周波数(16段階)によって変わる
・長周期ノイズでは、32767回で1ループ
・短周期ノイズでは、93回で1ループ
らしいです。よくわからん
最初は乱数の更新をタイマにやらせるとか考えたんですが、テーブルにしても32kbit(=4KB)なので、ウェーブテーブル(?)にしちゃえ、ってなりました。
ってことでウェーブテーブル(?)にしました。さすがに載せれる長さではないですね...
短周期ノイズはこんな感じになります。
要素1の7bit目->要素1の6bit目~~~~要素1の0bit目->要素2の7bit目->~~~~~
という順番に拾っていくように作りました。
// 短周期ノイズ サンプル数:96 const unsigned char noiseS[] = { 0x00, 0x02, 0x02, 0x0a, 0x02, 0x2a, 0x22, 0x8a, 0x00, 0x28, 0x28, 0x88 };
プログラム全文はこちら
先ほどと同じ部分は省略しています
// DAC割り込み処理関数 void __attribute__((interrupt, no_auto_psv)) _DAC1LInterrupt(void){ static unsigned long noiseLFreq = 0; static unsigned long noiseSFreq = 0; IFS4bits.DAC1LIF = 0; // 割り込みフラグリセット // 出力 signed int out = 0; ~~~~~ ちょっと省略 ~~~~~ // ノイズ if(flag4 == 1){ unsigned int tempStep = step4 >> 8; // 該当のビットが0か1かで出力切り替え if((noiseL[tempStep >> 3] & (0x80 >> (tempStep & 0x0007))) == 0){ out += -8192; }else{ out += 8192; } step4 = (step4 + noiseFreq[noiseLFreq >> 15]) & 0x7fffff; noiseLFreq = (noiseLFreq + 1) & 0x07ffff; } if(flag5 == 1){ unsigned int tempStep = step5 >> 8; // 該当のビットが0か1かで出力切り替え if((noiseS[tempStep >> 3] & (0x80 >> (tempStep & 0x0007))) == 0){ out += -8192; }else{ out += 8192; } step5 += noiseFreq[noiseSFreq >> 15]; if(step5 >= 24064){ // 24064 = 94 * 256 step5 -= 24064; } noiseSFreq = (noiseSFreq + 1) & 0x07ffff; } // 出力 DAC1LDAT = out; }
動画も撮ったのですが、上手く音が取れていなかったためライン撮りのみ載せました。
ノイズライン撮り(長周期ノイズ->短周期ノイズ の順番)
周波数16段階を少しづつ変えています。
長周期ノイズそれっぽい!!!!!!!
そして明らかに短周期ノイズうまくいってないけどとりあえずよし!!!!!!
(原因特定するまでの時間がない...)
7. 全部音出してみる
矩形波・三角波・ノイズの3種類が出せるようになったので、全部いっぺんに出してみたいと思います!
正直この工程は必要ありませんが、「あ~俺これを作ったんだな~」感を味わうためにやってます。
プログラム全文はこちら
// DAC割り込み処理関数 void __attribute__((interrupt, no_auto_psv)) _DAC1LInterrupt(void){ IFS4bits.DAC1LIF = 0; // 割り込みフラグリセット // 出力 signed int out = 0; // stepに加算する値の計算式 // (ウェーブテーブルの数 / (サンプリング周波数 / 音の周波数)) * 256 // (256 / (48000 / 音の周波数)) * 256 // 1音目 if(flag1 == 1){ out += tri16Wave[step1 >> 8] >> 2; step1 += 714; // ド 523.251Hz LED1 = 1; }else{ LED1 = 0; } // 2音目 if(flag2 == 1){ out += square12_5Wave[step2 >> 8] >> 2; step2 += 900; // ミ 659.255Hz LED2 = 1; }else{ LED2 = 0; } // 3音目 if(flag3 == 1){ out += square50Wave[step3 >> 8] >> 2; step3 += 1070; // ソ 783.991Hz LED3 = 1; }else{ LED3 = 0; } // ノイズ if(flag4 == 1){ unsigned int tempStep = step4 >> 8; // 該当のビットが0か1かで出力切り替え if((noiseL[tempStep >> 3] & (0x80 >> (tempStep & 0x0007))) == 0){ out += -8192; }else{ out += 8192; } step4 = (step4 + noiseFreq[5]) & 0x7fffff; } // ノイズ if(flag5 == 1){ unsigned int tempStep = step5 >> 8; // 該当のビットが0か1かで出力切り替え if((noiseL[tempStep >> 3] & (0x80 >> (tempStep & 0x0007))) == 0){ out += -8192; }else{ out += 8192; } step5 = (step5 + noiseFreq[13]) & 0x7fffff; } // ノイズ if(flag6 == 1){ unsigned int tempStep = step6 >> 8; // 該当のビットが0か1かで出力切り替え if((noiseS[tempStep >> 3] & (0x80 >> (tempStep & 0x0007))) == 0){ out += -8192; }else{ out += 8192; } step6 += noiseFreq[15]; if(step6 >= 24064){ // 24064 = 94 * 256 step6 -= 24064; } } // 出力 DAC1LDAT = out; }
出している音は
・三角波(ド)
・矩形波12.5%(ミ)
・矩形波50%(ソ)
・長周期ノイズ(高い音)
・長周期ノイズ(低い音)
・短周期ノイズ
です
動画撮るのミスっていたので、ライン撮りのみです...(音量注意)
これで全部の音が作れたので....
8. ファミコン風音源製作!!!
今まではボタンで音を鳴らしていましたが、自動で演奏するようにします!
シーケンサの機能も持たせることを考えましたが、外部から通信できたほうが応用しやすいと思い、SPI通信で音のデータを送ることにしました。
ちなみにデータを送るのに使用しているマイコンはpic18f26k22です。
このマイコンもなかなかに面白くてオススメです!!!!
静電容量を計測するためのモジュールがあったりとめちゃくちゃ面白いです!!!
ぜひ!!!!!
ということで完成!!!!!!
(右下のちっちゃいブレッドボードが演奏データを送っているマイコンです)
プログラムはこちら!!
9. 演奏!!!!
実際に演奏させてみます。
スピーカーの箱がティッシュ箱なのは、良い感じの箱がこれしかなかったからです...
↓ライン撮りver
↓こちらを参考にさせていただきました。
うん...
めちゃくちゃよくてビビってる
正直ここまでうまくいくと思ってなかったです。もっとボロボロな感じになるかなと思ってました....
10. まとめ!!
久しぶりにマイコンをつかった音ネタができたので楽しかったです!!
ただ、記事としては説明省いている部分もたくさんあるので、もし何か質問等あれば私(twitter:@kazujdDR8823)までお願いします!!!
みんなもPICマイコン始めよう!!!!楽しいよ!!!!
明日は Sumeshi さんによる データベーススペシャリスト大変でした です!
お楽しみに!!!
(私も受験したことありますが、マジで夢にE-R図出てきました。しかも毎日。地獄かよ)
ちなみにpart2の20日には二郎系ラーメンの話をする記事を
part3の22日にはシューティングはいいぞおじさんをする記事を書く予定です。
ぜひそちらも見ていただけると幸いです!
ここまでご覧いただきありがとうございました!!!!