概要
プログラミング言語を書くと演奏できるシーケンサを作成中です。
とりあえず音が鳴る部分まで作っている状態だが、仕様が固まってきたのでこういうところに記載を残すもの。
動いてるところ
詳細
発音はminiaudioを利用。
公式
これで何をするか
オーディオ再生を行いたい。
自分の場合はシンセサイザーを作成したい。
ついでにMIDI出力を組み合わせたい。
実装リファレンス
https://miniaud.io/docs/manual/index.html
1.1のLow Level APIを使う。
もちろんHighLevelAPI使うのでもいいんだけど、エンジンの都合により再生システムを変更することが往々にして存在するので、低レベル部分に手が出せるようにする。
実際、LowLevelAPIで作ろうとした時点で不都合が起きたので、LowLevelAPIを参考に改造して同様の機能を持つAPIを作成した上で動作させた。
IFを利用して定型的な処理としてオーディオを流すようにしているが、最初から用意されているものについてはLowLevelAPI上に1音程度鳴る想定のものしかない。
(おそらくHighLevelAPIで複数の音を鳴らす想定なのかと)
あまりAPIに依存してしまうと波形アクセスが困難なケースに遭遇してしまう可能性が高くなるため、LowLevelAPIとして複数波形にアクセス可能とした。
よって自分が作ったAPIだと、LowLevelAPIだけどシンセサイザー+サンプリングの音が複数発音可能になっている。
ついでに後述するとMIDI再生も同期するようにしている。
なお、WASAPIのみ想定。
(レイテンシー減少はそれほど求めない)
MIDI再生
PortMidiを利用。
https://portmidi.github.io/portmidi_docs/index.html
シーケンスは別途登録して流すが、miniaudioのコールバック関数上でMIDI再生(NoteOn/NoteOFF)を起動できるようにした。
元々別にスレッドを立ててシーケンスを再生しようとしたが、計算タイミングの違いにより同期が困難だったため、コールバック関数上でMIDI関連のメッセージ送信を行うこととした。
PortMidiの関数で「現在時刻+秒数」で発音指定ができる命令があるのでそれを使う。
デバイス切り替えの時間やそれに伴う処理待ち等が入るかと思ったがそうでもなかった。
例:Printfで出力先を指定する場合、コールバックを阻害する可能性あり。
結果としてドライバがコールバックを阻害することなく、かつデータ量も抑えて送信することができるようになった。
副次的な効果として、オーディオとMIDI再生がほぼ同期するようになった。
シーケンス
MiniScriptのリスト構造を読んで、現在再生中の秒数が全体のうちの何割かを求めて、その位置に該当するリストの値を検知、これを再生する譜面としてオーディオ・MIDI出力・サンプリング再生のコールバック関数に渡すこととした。
コールバックのバッファ処理バイト数から進んだシーケンスの時間を導出して反映し、そこからシーケンスの時間を求めることにした。
MiniScript
https://miniscript.org/
最初はdouble変数で計算精度や速度に問題でないかなー?と思っていたが、常用に問題ないことを確認したため、実装では全体的にdouble/longサイズの変数を利用している。
終わりに
11月くらいには公開予定なので興味のある方は使ってみてください。