自作MIDI音源「CureSynth」製作記事の一覧はこちら
回路図、ソースコードはこちら
前回は、自作MIDI音源「CureSynth」の音源部の構成について紹介しました。
今回から、組み込みソフトウェアの紹介に移ります。
はじめに
本稿では、コアとなる処理を考え、「メインループ内で実施するか否か」を決め、アバウトなソフト構造を示します。
「CureSynth」のコアとなる処理は、次の5つです。
- MIDIメッセージの受信
- UART(USART)割り込みで実施
- MIDIメッセージの解析
- メインループ内で実施
- 音データの生成
- タイマ割り込みで、サンプリング周波数ごとに実施
- DACの制御
- DMAによる転送で実施
- タイマ割り込みで、サンプリング周波数ごとに実施(バッファの書き換えのみ)
- ディスプレイの制御
- DMAによる転送で実施
- メインループ内で実施(バッファの書き換えのみ)
1.MIDIメッセージの受信
MIDIインタフェースは非同期シリアル通信なので、シリアル通信に関する一般的な手法で受信ができます。受信方法としては「ポーリング」と「UART(USART)割り込み」がありますが(※)、負荷を少しでも下げるために割り込みによる受信をします。MIDIメッセージはバイト単位なので、1バイト(8bit)受信するごとに割り込みが発生するようにします。
また、割り込み処理内でリングバッファ(FIFO)に蓄積し、処理の安定化を図ります。
※STM32F7ではDMAによる受信にも対応していますが、MIDIでは受信データ規模が小さく、割り込みと比較してメリットが少ないと思われるので、今回は不採用です。良い使い方があるのかもしれませんが…
2.MIDIメッセージの解析
受信したMIDIメッセージの解析は、割り込み処理内ではなくメインループ内で行います。理由は、MIDIメッセージは複数のバイト列で意味をなすため、1バイト受信するごとに処理する必要がないからです。
3.音データの生成
MIDIメッセージを解析した情報をもとに、音データを生成します。
ここで、MIDIメッセージには時間の情報が含まれないことに注意します。MIDIでは「C音の発音を開始」「C音の発音を終了」といった情報は伝送できますが、発音開始時刻・終了時刻を伝送することはできません。そのため、MIDI音源側で予め音データを演算しておくことはできず、音データはその都度生成する必要があります。
従って、タイマ割り込みで、音データを都度生成するのが妥当でしょう。このとき、タイマ割り込みの周期がサンプリング周波数になります。
4.DACの制御
DACには、部品箱にストックのある「PCM5102A」を使用しました。STM32F7にはDACが内蔵されていますが、量子化bit数が12bitと小さいことや、ノイズフロアが大きくS/N比が悪いことから、オーディオ用途に使うには厳しく、外部DACが必要と判断しました。
PCM5102Aは、音声データを「I2S信号」として入力すれば発音してくれるので、制御が簡単です。MCUのペリフェラルにはI2S出力があり、HALの関数を呼ぶだけで操作できます。
ただし、I2S信号を安定的に供給しなければなりません。I2S信号が途切れると、PCM5102Aでは出力がMUTE状態となるため、プチノイズが発生します。
そこで、音声データの転送にDMAを用いることで、安定化を図ります。具体的には、リングバッファ(FIFO)領域を用意し、I2Sペリフェラルに対して繰り返し転送するような設定をしておきます。またタイマ割り込みを用い、バッファの書き換えをサンプリング周波数ごとに行います。
5.ディスプレイの制御
ディスプレイモジュールには、AliExpressで購入したOLEDを用いました。
リンクはこちら→1PCS 1.3″ OLED module blue color IIC I2C …
こちらには「SH1106」というICが使われており、I2C経由で表示内容の制御ができます。
負荷の軽減のため、ディスプレイの制御にもDMAを用いることにしました。DACと同様に、リングバッファ領域を用意しています。DACの制御と異なるのは、周期にこだわる必要が無い点です(というより、DACの制御にリソースを割きたい)。そこでバッファ領域の書き換えはメインループ内で行っています。
6.ソフト構造の決定
1.~4.の内容をもとに、上図のとおりソフト構造を決めます。めちゃいい加減な図ですが…。
MIDIメッセージの解析部(cureMidi)、音源部(cureSynth)、ディスプレイコントロール(cureDisplay)は、メインプログラム(main.c)から呼び出すことにします。
また、MIDIメッセージを解析し、音源部に伝える必要があるため、MIDIメッセージの解析部から音源部を呼び出します。
さらにMIDI音源部では、受信したMIDIメッセージをバッファに入れる/読み込む操作が必要ですので、バッファ制御部(cureBuffer)を呼び出しています。
ところで本稿では省きましたが、音源部からバッファ制御部を呼び出している理由は、ディレイ・エフェクトを実装するためです。
適当な説明になってしまいましたが、設計思想が伝われば幸いです。伝わるかな…?
今回はここまで。
次回はMIDIメッセージの受信とリングバッファについて紹介します。
コメント