第27章. ピアノロールの仕組み
この章の目的
この章を読み終える頃には、ピアノロール例の仕組み — Keleusmaのloopプログラムがどのようにして音楽になるのか — が分かるようになります。この章を読み終える頃には、ピアノロール例の仕組み — Keleusmaのloopプログラムがどのようにして音楽になるのか — が分かるようになります。
本物の loop プログラム
第4部でloop関数を紹介し、決して終わらずにサイクルごとにホストに値を渡すプログラムだと述べて、本物のものは第8部で動かすと言いました。ピアノロールがその本物のプログラムです。1曲の楽曲がloop mainで、ピアノロールがそのホストです。第4部でloop関数を紹介し、決して終わらずにサイクルごとにホストに値を渡すプログラムだと述べて、本物のものは第8部で動かすと言いました。ピアノロールがその本物のプログラムです。1曲の楽曲がloop mainで、ピアノロールがそのホストです。
3つの部分
ピアノロールは3つの部分で構成されていて、3つの違う速度で動きます。
- 楽曲はKeleusmaの
loop main(input: Word) -> Wordです。ループの1サイクルが1つの16分音符ティックです。サイクルごとに楽曲はどの音を出すか、止めるかを決めます。楽曲はKeleusmaのloop main(input: Word) -> Wordです。ループの1サイクルが1つの16分音符ティックです。サイクルごとに楽曲はどの音を出すか、止めるかを決めます。 - ホストのメインスレッドが楽曲を動かします。ティックごとに楽曲を再開させます。120 BPMでは1ティックは125ミリ秒です。ホストのメインスレッドが楽曲を動かします。ティックごとに楽曲を再開させます。120 BPMでは1ティックは125ミリ秒です。
- ホストのオーディオスレッドが音を作ります。これははるかに速く、1秒あたり48000サンプルで走り、決してKeleusma仮想マシンに入りません。ホストのオーディオスレッドが音を作ります。これははるかに速く、1秒あたり48000サンプルで走り、決してKeleusma仮想マシンに入りません。
楽曲は段取り、ホストは音を作る
これが要点です。楽曲は音を出しません。楽曲はイベントを「段取り(スケジュール)」します。楽曲がチャンネル0にMIDIナンバー60を鳴らさせると決めたとき、ホストのネイティブを呼びます。これが要点です。楽曲は音を出しません。楽曲はイベントを「段取り(スケジュール)」します。楽曲がチャンネル0にMIDIナンバー60を鳴らさせると決めたとき、ホストのネイティブを呼びます。
host::play(0, 60)
その呼び出しがホストの音声状態に書き込みます。オーディオスレッドがそれを読んで音にします。楽曲の仕事はタイミングと音の選び方で、ホストの仕事は音の合成です。第16章で紹介した「ホストと話す」というアイデアの、ネイティブ関数呼び出しがその架け橋です。その呼び出しがホストの音声状態に書き込みます。オーディオスレッドがそれを読んで音にします。楽曲の仕事はタイミングと音の選び方で、ホストの仕事は音の合成です。第16章で紹介した「ホストと話す」というアイデアの、ネイティブ関数呼び出しがその架け橋です。
楽曲の記憶
楽曲は自分がどこにいるかを、第18章のデータセグメントを使って覚えています。バンドルされているすべての楽曲は同じ形のdata stateブロックを宣言しています — 1回切りの初期化フラグ、ループカウンター、セクションポインター、いくつかの汎用スロット、そして8つのカウンターの配列が2つ(各チャンネルがパターンのどこにいるかを記録するものと、各チャンネルの次の音まで残っているティック数を記録するもの)です。楽曲は自分がどこにいるかを、第18章のデータセグメントを使って覚えています。バンドルされているすべての楽曲は同じ形のdata stateブロックを宣言しています — 1回切りの初期化フラグ、ループカウンター、セクションポインター、いくつかの汎用スロット、そして8つのカウンターの配列が2つ(各チャンネルがパターンのどこにいるかを記録するものと、各チャンネルの次の音まで残っているティック数を記録するもの)です。
init ブロック
楽曲のloop main本体が最初のサイクルでまず行うのは、state.initフラグで守られた1回切りの初期化ブロックです。楽曲のloop main本体が最初のサイクルでまず行うのは、state.initフラグで守られた1回切りの初期化ブロックです。
if state.init == 0 {
host::song_name("C major progression, four-bar loop");
host::set_waveform(0, 0);
host::set_adsr(0, 5, 80, 700, 150);
host::set_enable(0, 1);
// ... 他のチャンネルも構成 ...
state.init = 1;
};
``````if state.init == 0 {
host::song_name("C major progression, four-bar loop");
host::set_waveform(0, 0);
host::set_adsr(0, 5, 80, 700, 150);
host::set_enable(0, 1);
// ... 他のチャンネルも構成 ...
state.init = 1;
};
データセグメントはゼロで始まるので、最初のサイクルではstate.initは0、ブロックが走り、最後にstate.initを1にします。それ以降のサイクルではブロックは飛ばされます。ここが各チャンネルの楽器を選ぶ場所です。データセグメントはゼロで始まるので、最初のサイクルではstate.initは0、ブロックが走り、最後にstate.initを1にします。それ以降のサイクルではブロックは飛ばされます。ここが各チャンネルの楽器を選ぶ場所です。
ティックごとの本体
initブロックのあと、楽曲は各チャンネルを同じやり方で処理します。あるチャンネルについて、残りティック数がゼロになったら、楽曲はそのチャンネルの次の音を調べ、host::playかhost::silenceを呼び、カウンターをその新しい音の長さに設定し、チャンネルの位置を進めます。そうでなければ、ただカウンターを1だけ減らします。それから楽曲はyieldして、サイクルが終わります。initブロックのあと、楽曲は各チャンネルを同じやり方で処理します。あるチャンネルについて、残りティック数がゼロになったら、楽曲はそのチャンネルの次の音を調べ、host::playかhost::silenceを呼び、カウンターをその新しい音の長さに設定し、チャンネルの位置を進めます。そうでなければ、ただカウンターを1だけ減らします。それから楽曲はyieldして、サイクルが終わります。
各ティックは決まった、小さな仕事量を行います。第20章の「有界ステップ」保証がここでも成り立っています — 楽曲はティックをはみ出すことができません。各ティックは決まった、小さな仕事量を行います。第20章の「有界ステップ」保証がここでも成り立っています — 楽曲はティックをはみ出すことができません。
楽曲の差し替え
ピアノロールは10曲の楽曲を持っています。キーを押すと、走っている楽曲が次の楽曲に差し替わります。その差し替えが、第26章のホットコードスワップ、RESETの境界で起こるもの、を耳で聴ける形にしたものです。ピアノロールは10曲の楽曲を持っています。キーを押すと、走っている楽曲が次の楽曲に差し替わります。その差し替えが、第26章のホットコードスワップ、RESETの境界で起こるもの、を耳で聴ける形にしたものです。
この章のまとめ
- 楽曲は
loop mainで、ピアノロールがそのホストです。 - 楽曲はネイティブ呼び出しで音のイベントを段取りし、ホストのオーディオスレッドが音を合成します。楽曲はネイティブ呼び出しで音のイベントを段取りし、ホストのオーディオスレッドが音を合成します。
- 楽曲は自分の位置をデータセグメントに保持します。
- 1回切りのinitブロックが最初のサイクルで楽器を構成します。
- 各ティックは有界な仕事量を行います。
次の章では、ピアノロールを自分の機械で動かします。