音源チップからビープ音まで
メガドライブのYM2612を、JavaScriptでレジスターごとに操作します。
このページは、音源チップの基礎とC++のビープ音チュートリアルに対応するブラウザー版です。1つのチャンネルと、音を出す1つのオペレーターを使います。以下のコード全体をPlaygroundで編集・実行できます。
1. 音源チップは何をする?
YM2612は、プログラムが書き込んだ設定から音を作ります。6つのFMチャンネルがあり、それぞれ4つのオペレーターを持ちます。オペレーターは正弦波をもとにした信号を作り、エンベロープで音量を時間変化させます。音声出力になったり、別のオペレーターを変調したりします。
最初のビープ音には、音を出すオペレーター1つで十分です。変調とフィードバックを使わず、一定の音を鳴らします。
実機ではCPUがチップのデータバスへバイトを送り、クロックがチップを動かします。ここではPlaygroundがエミュレーターを実行し、生成したサンプルをブラウザーの音声出力へ送ります。コードでは設定とタイミングを指定します。
2. レジスターは小さな設定の入れ物
レジスターにはアドレスがあり、1バイトの値を持ちます。アドレスで変更する項目を選び、値で設定を指定します。JavaScriptの0xという接頭辞は16進数を表します。0x07は7、0x7fは127です。
write(0xb0, 0x07); // CH1:アルゴリズム7、フィードバック0
このwrite(address, value)はPlaygroundのヘルパーです。C++のチュートリアルで使う「レジスターを選ぶ」「値を送る」という2回のバス書き込みをまとめています。
// C++ / チップのバス操作:
chip.write(0, 0xb0); // bank 0のレジスターを選びます。
chip.write(1, 0x07); // 値を送ります。
// JavaScript / Playground:
write(0xb0, 0x07);
多くのチャンネル用レジスターは、CH1〜CH3のbank 0と、CH4〜CH6のbank 1に分かれます。Playgroundでは、このbankの引数をportと呼びます。明示するとwrite(0, 0xb0, 0x07)です。引数2つの形式ではbank 0を使います。
C++側のバスオフセットは別です。0と1はbank 0の選択・書き込み、2と3はbank 1の選択・書き込みに使います。このビープ音ではすべてbank 0へ書き込みます。
3. 単純な音色を選ぶ
アルゴリズム7は、4つのオペレーターを並列に音声出力へつなぎます。OP1〜OP3をほぼ無音にして、OP4だけを鳴らします。
OP1〜OP3はほぼ無音 · OP4を鳴らす · FBなし
4つのハードウェアスロットのオフセットは0, 4, 8, 12です。レジスター群の基準アドレスにオフセットを足すと、スロットを選べます。最後の12(0x0c)がOP4です。たとえば、そのTLは0x4cです。
これはハードウェアのアドレスオフセットで、PlaygroundのOP1–OP4定数とは異なります。中央の2スロットは論理的な順序が異なりますが、この例では同じ設定を書き込みます。
| CH1のレジスター群 | 設定 | このビープ音での設定 |
|---|---|---|
0x30 + offset | DT / MULTI:デチューンと周波数倍率 | 0x01:デチューンなし、1倍 |
0x40 + offset | TL:減衰量 | 0x7fでほぼ無音。0x00はOP4用です。大きい値ほど小さな音になります。 |
0x50 + offset | AR:立ち上がりの速さ | 0x1f:最速の立ち上がり、レートスケーリングなし |
0x80 + offset | SL / RR:減衰段階の切り替えレベルと離鍵後の減衰速度 | 0x0f:SL 0、最速のリリース |
0xb0 | アルゴリズム / フィードバック | 0x07:アルゴリズム7、フィードバックなし |
0xb4 | 左右の出力 / LFO感度 | 0xc0:左右出力を有効にし、感度は0 |
最初にfm.reset()を呼びます。D1RとD2Rは0になるので、キーオフまで音が一定に続きます。その後、リリースで素早く減衰します。
4. 音程を設定してからキーオンする
チップは音程を、音域を表すBLOCKと11ビットの数値FNUMで指定します。C++の例ではBLOCK 4、FNUM 0x269を使います。2つのレジスターに分けて書きます。
write(0xa4, 0x22); // 上位:(BLOCK 4 << 3) | FNUMの上位ビット2
write(0xa0, 0x69); // 下位:FNUMの残り8ビット
上位を先に書き、次に下位を書きます。下位の書き込みで音程設定が確定します。この値はチュートリアルの音程を再現するもので、Hzの数値ではありません。
最後に、レジスター0x28でキーオンとキーオフを操作します。0xf0では、上位4ビットが4つのオペレーターをオンにし、下位ビットがCH1を選びます。キーオンはエンベロープを開始する操作で、音色や音程の指定ではありません。
write(0x28, 0xf0); // CH1:キーオン
await sleep(3); // 3秒間鳴らします。
write(0x28, 0x00); // CH1:キーオフ
5. ビープ音を鳴らす
PlaygroundでRunを押してください。ex03_beep.cppのレジスター設定をJavaScriptで書き、時間を置いてキーオフする完全な例です。Playgroundにはfm、write、sleepが用意されています。
1つ変えて聴き比べる
- 条件式のOP4のTLを
0x00から0x20に変えると、音が小さくなります。 - 音程の上位設定を
0x22から0x2aに変えると、BLOCKが4から5になり、1オクターブ上がります。 - 設定を
sleep(3)からsleep(0.3)に変えると、短いビープ音になります。 - 左右出力を
0xc0から0x80または0x40に変えると、左だけ、または右だけから鳴ります。
6. レジスターのバイトから演奏用の操作へ
レジスターでの設定が分かったら、Playgroundのメソッドを使って、同じチャンネル設定をより直接的に書けます。
fm.setAlgo(CH1, 7, 0); // write(0xb0, 0x07)
fm.setPan(CH1, true, true); // write(0xb4, 0xc0)
fm.setFrequency(CH1, 4, 0x269); // 0xa4と0xa0へ書き込みます
fm.keyOn(CH1); // write(0x28, 0xf0)
await sleep(3);
fm.keyOff(CH1); // write(0x28, 0x00)
このコードはチャンネル設定と再生の部分を置き換えます。上のオペレーター設定も必要です。「音色設定 → 音程設定 → キーオン → 待つ → キーオフ」という流れは同じです。
次は、オペレーターとFMの音作り、または1つの音から調律と音程を調べるページへ進めます。