PC-8001mk2でゲームを制作する上で、避けては通れない問題、それがBEEPを使った音楽再生です。この機種は音を出す方法が 2400Hzという固定の周波数しか出せない BEEP のみなのです。まあ、カセットのリレーを使ってカチカチと音を出せなくはないのですが、それはとりあえずここでは止めておきます。この BEEPで如何に任意の周波数を再生するかという説明と、その過程で制作されたツールと仕様書をまとめて公開していきます。
<!> 注意
本記事に掲載されたツールはフリーソフトという扱いです。このツールをどのようにお使いいただいても構いませんし、出力されたデータを皆様の作成されたゲームに組み込んでいただいても全く問題ありません。表計算データやデータ仕様に関してもご自由にお使いください。その代わりと言ってはなんですが、本ツールを使用した結果については自己責任です。内藤は何も保証いたしませんし、修正の義務もございません。その旨ご承知おきください。
<!> 注意
本記事に掲載されたツールはフリーソフトという扱いです。このツールをどのようにお使いいただいても構いませんし、出力されたデータを皆様の作成されたゲームに組み込んでいただいても全く問題ありません。表計算データやデータ仕様に関してもご自由にお使いください。その代わりと言ってはなんですが、本ツールを使用した結果については自己責任です。内藤は何も保証いたしませんし、修正の義務もございません。その旨ご承知おきください。
- 原理

周波数は、1秒間に何回波形が上下に振れたかで決まります。例えばラの音は440Hzってのは有名ですが、これは1秒間に440回、周波数が揺れたことを意味します。そのため、440Hzの音を出したければ、1秒間に440回、PCM波形を上下させれば良いのです。
ここで、では1秒間とはどういう数値なのかという疑問が起きます。これを決めているのがサンプリング周波数(Sampling Rate)です。CD のサンプリング周波数は 44.1KHz と聞いたことがあるかと思いますが、これは 1秒間に 44100回の反復が最大で行えることを意味します。だから、440Hz で音を鳴らしたければ、44100÷440≒100となりますから、凡そ 100カウント毎に周波数の上下を変更すれば良いのです。
※小数演算して100.22727...で計算を続けたほうがより正確な周波数が出力されるようです。
※小数演算して100.22727...で計算を続けたほうがより正確な周波数が出力されるようです。
さて、BEEPの話しに戻ります。BEEPはそれ自体が 2400Hzの振幅が出ていますが、音が出ているという意味では波形onの状態とみなすことが出来ます。そして、音を止めれば波形offとなります。つまり、BEEPのon/offを高速で行うことによって、擬似的に矩形波を作り出すことが出来ます。1秒間にどれだけの処理ができるかなどの計算が必要ですが、動作原理としてはこのように単純明快なものとなります。
※うちではこれを2箇所で使っていますが本当に便利で助かっています。一部、コンセント幅が広いのでACアダプタを差し込みやすいのです。- 音を出す
さて、原理の項で音の出し方は分かりました。大事なのは波形の長さをきっちりと把握することです。ここで今回大変参考になったサイトをご紹介いたします。
ここに記載のコードを実際にアセンブルしてみると、ちゃんと音階が奏でられます。マシン語の各命令は処理速度としてクロックが用いられます。CPUクロックは1秒間に処理できる総クロック数と言い換えることが出来ます。PC-8001mk2 の CPU は Z80 4MHz ですが、厳密に 4MHz とは、3993600 となります(水晶発振速度に依存)。ここから、波形の上側と下側の処理時間が同じになるように、Z80のマシン語の記述を工夫して全く同じクロックでループするようにします。
こちらが私が改変したコードです。

参考サイトでは音の長さは1バイトで処理されていました。最終的にはそれで良かったのですが、ドライバ作成時点では音の長さが255を超える可能性があったので、ループカウンタを16bitに拡張しています。また、音長と振動のループ初期値に関しては、IXから取得ではなく、事前にプログラム書き換えをしてデータを読み込むようにしています。
ループの処理部分は同様に 6+4+4+10 = 24clk となっています。ループ以外で必要とする処理時間は、前半の振動直後が 6+6+6+6+10+7+11 = 52clk、後半が 6+4+4+10+10+7+11 = 52clk と前後半で全く同じ処理時間に調整してあります。この調整がとても大事で狂っているととても気持ち悪い音になります。
プログラム的にはこんなにシンプルなのですが、大事なのが振動に必要なループ数と、音長を固定化するためのループ数です。コメントにもあるように、一回の振動に要する処理時間は、104 + (24 * n * 2) となります。PC-8001mk2 の処理速度は1秒間に 3993600回の実行となりますので、鳴らしたい周波数で割った値が1回辺りの処理時間 clk となります。この値から固定処理時間 104 を引いて、残った値を 48 で割れば、n の値が求められます。ラの音 440Hz であれば、
((3993600 / 440) - 104) / 48 = 186.92 ≒ 187
この 187 が Z80 の振動処理で Ring.patch1 と Ring.patch2 の値に入る振動ループ数になります。続いて音長です。参考にしたサイトでは「おとのながさ(てきとう)」となっていた部分です。こちらは、鳴らしたい音の長さをクロック数で考えて、1回の振動で消費されるクロック数で割ってやれば、全体のループ数が出てきます。例えば、250ms の長さでラの音を出したければ、3993600 * 250 / 1000 = 998400 クロックが全体の処理時間になりますので、この 998400 を1回のループに要する時間で割れば、全体のループ数が出てきます。
(3993600 * 250 / 1000) / (104 + (48 * 187 * 2) = 109.95 ≒ 110
先の Z80コードでは、Ring.vib に 110 をセットすれば、ラの音が 250ms の時間発音されることになります。ただ、これだと 250ms もの長時間、CPU 専有されてしまいますので、BGM と使用するために再生時間は 8ms に絞り込んでいます。ラの音なら
(3993600 * 8 / 1000) / (104 + (48 * 187 * 2) = 3.518 ≒ 4
たったの4回しかループしていないんです。だから Ring.vib は 8bit で十分となります。時間を見つけてそのうち直しますが、おそらく鳴っている結果としては変わりがないと思います。そして、こんな計算をいちいち Z80 側で行うわけにはいきませんので、事前に表計算で数値を作成して、その結果をテーブルとして取り込んでいます。以下の表計算ファイルで計算式を確認できます。
ウエイト時間計算.ods20240405-1400
なお、厳密にクロック数計算をしていますので、処理速度が変わる状態での再生は避けなければなりません。PC-8001mk2 は、GVRAM バンクを開くと、水平帰線が表に出ている間は CPU が止められてしまうようでした。そのため、ゲームの画像表示等で GVRAM バンクを開いている状態では、確実に割り込み禁止をするなどで、処理時間が変わるタイミングでの再生処理は避けるようにします。GVRAM での割り込み禁止をしないと、音が時々ビッとかブッとかと大変耳障りなノイズが聞こえるようになります。
※これを徹底してるつもりだったんですが、まだ時々ノイズが出てしまいます。原因不明です…
※ 一部でご存知iCleverのBTキーボード。私はこのメーカーの折りたたみキーボードを2種類持っていますがいずれも使いやすいです。3つのデバイスを切り替えられるのが本当に便利です。PC、スマホ、タブレットを登録して使ってます。
- MMLの仕様を策定する
さて、音楽演奏を行うためには MML の策定が必要です。これは譜面を記号化したような内容となります。MMLは40年以上昔から存在してます。ある程度は共通の仕様になっていると思いますが、一部の特殊なコマンドに関しては、各自のオリジナル命令になっています。私は MML 策定時に最も気にしているのが、コンバート後のデータ量削減です。音各データは意外なほどにメモリを圧迫します。そのため、可能な限り仕様策定の時点でデータサイズが小さくなるよう配慮スべきなのです。私の目標は 1ch で100バイト未満です。どんなに大きくなっても 150バイトには収めたいと思っています。200バイトなんてとんでもない(苦笑
そこで毎回ビット単位でデータを縮める努力をします。前回 Newシティヒーローのゲーム制作では、使用している全体の音階記号の仕様頻度を取りまとめて、最も使用頻度の高い音階から短いビットレートを割り当てるという、ハフマン圧縮的なデータ構造を採用しました。これはコンポーザーが一人だったので問題なかったのですが、今回は複数名を予定しているため、統計を取るのが困難です。そこで、コマンドデータ列を 4bit単位で表すことにしました。最も使用頻度が高いであろう音長省略形の音階を4bitに割り当てて、それ以外を4bit+4bit、または4bit+4bit+4bitストリームとしています。
あと、効果音に関しては、だいたい今までの経験上、最大速度で音を激しく変化させることばかりでしたので、音階とオクターブだけのシンプルな構造として、音長は常に高速でぶん回す仕様としています。今回は今のところ 30ms(32分音符相当)で再生させています。この仕様としているため、効果音としてコンバートすると1音に付き4bit固定となっています。かなりデータサイズは小さいです。
ゲートタイムは仕様から削除しました。どのみち、音楽再生は音長に関係なく、音の発振は 8ms しかありません。それ以外の長さは、単純に無音ウエイトですから、そもそもゲートタイムの意味がないのです。タイ記号は前後の音階が同じであれば、一つの音階記号にまとめてしまい、タイ記号そのものは消滅させています。
と、まあ、いろいろ注意事項を書きましたが、詳細は以下の仕様書を御覧ください。
20240405-1430
- 演奏データを作成する
上記MMLの仕様を理解した上で、演奏データの作成に入ります。内部カウント値は L1=48 固定です。そのため、48を正しく割り切れる16分音符(48÷16 = 3)までが使用可能範囲です。多少音長がふらついても良いのであれば、a(2)b(1)とダイレクト音長を指定することで、無理やり32分音符相当を再生することは出来ます。
MMLの記述はテキストファイルです。テキストで記述されたMMLファイルを、ConvBeepV2.exe に食わせると、エラーがなければ同じパスに拡張子が bin にリネームされたコンバート済みバイナリファイルが出力されます。エラー発生時はコンソールにエラー内容やヒントが表示されますので、それを元に修正をお願いいたします。
-L1 L1の初期カウント値を変更(デフォルトは 48 -M 変換モードを指定できます(デフォルトは BGM
L1の初期カウント値の変更は今回は行わないようにお願いします。この数値は割り込み速度とも関連していますので、変更されると整合性が取れなくなってしまいます。
変換モードは MUSIC, BGM, EFFECT が指定できます。MUSIC はオクターブが o2~o6 の範囲で使用できますが、ゲーム内では o2 以下は再生不能ですので、このモードは使用しないでください。BGM は o3~o6 が有効範囲です。EFFECT も BGM と同様に o3~o6 が有効範囲です。EFFECT を指定すると、音長やループなどの拡張コマンドが一切使えなくなります。
Logicool(ロジクール)
2011-08-12
- 実際に演奏させてみる

最初に鳴らしたいバイナリファイルをフォームにD&Dします。データに問題がなければ Successfully parsed. と表示されます。エラーがあれば Failed to load. となります。モードが違ってる場合も同様にエラー表示が出ますが、その場合は Type を正しいモードに変更すればエラーが消えるはずです。ループ再生なら Repeat play にチェックを入れて Play ボタンを押します。音楽再生中は Stop と表示が変わります。この Stop を押すと音楽再生が止まります。
Saveボタンは演奏データをwavファイルとして出力します。Int. は割り込み速度です。所謂テンポと同等ですが、これを 26 より小さくするとゲームの処理速度的に負荷が大きくなりますので、出来れば 26 のままとしてください。このデフォルトの 26 とは 26ms を意味しています。2ms 割り込み 13回に一度処理をするという意味になります。
波形出力は下記のサイトを参考にしております。
Wavファイルのデータ構造は下記のサイトを参考にしております。
Music モードは音長のデバッグ用に使ってください。なお、D&D するたびにフォームの色が変わるのは、D&D を受け付けたことが視覚的に分かるようにしたかっただけで他意はありません。
- ゲームのBGMや効果音募集中です
ゲームのジャンル的にはシューティングゲームです。但し、世界観は RPG 風味です。敵キャラも全てファンタジー系になっています。例えるなら UNDEADLINE(幻獣鬼ではない)とか Y's2 とかの世界観です。ハイドライド系と言っても良いかもです。マップは草原、森林、悪魔城、墓地、ダンジョン、洞窟、砂漠、湖畔、要塞、玉座を予定しています。今のところは悪魔城まで完成しています。

登場する敵はスライム、コボルド、スネーク、吸血コウモリ、ゾンビ、etc です。敵キャラは、ここに掲げた種類以外はまだ出来ていないので不透明です。マップも出来ているのは悪魔城までです。欲しいデータの種類は以下の通りです。
- ゲーム中のBGM(メモリに余裕があればマップ毎、メモリが厳しければ一種類
- ボスBGM(登場ジングルとBGMを個別に
- ゲーム開始ジングル
- ステージ開始ジングル
- ステージクリアジングル
- ゲームクリアジングル
- プレイヤーミス効果音(ぐはっ)
- 秘密が解けた効果音(たりらりたらららん♪…は京都方面から叱られますw)
- 敵に当たった攻撃が跳ね返った音(キンッ)
- プレイヤーエクステンド(たりらたらったったー)
- スイッチが入った音(カチッ)
- 高得点が入った音(たりたりー)
- エンディングBGM(詳細未定
- ローディングBGM(詳細未定
ツールは下記のリンクからダウンロードしてください。
こちらにはデータコンバータである ConvBeepV2.exe、後述の再生テストツールである PlayBeepV2.exe、MML仕様書とサンプルMMLテキストが格納されています。
皆様のご協力をお待ちしております!BeepMusicV2-3.zip
20240406-1325
<!>注意
20240405版でコンバートしたバイナリデータで、20240406版プレイヤーに D&D すると、不具合を起こす可能性があります。必ず20240406版の ConvBeepV2 で再度コンバートし直してから、プレイヤーに D&D するようにしてください。
20240406-1325
・コンバータのオプションに-tテンポ速度指定を追加
・プレイヤーにD&Dしたらテンポ指定をInt.に自動反映するように
20240405-2135
20240405-2135
・ループ回数が7回に制限されていたバグをFix
・ループのネスト判定がMMLの初頭と最終にあると狂っていたバグをFix
・仕様書のデフォルト音長の解説が完全に間違っていたバグをFix20240405-1520
・初版公開
AUGSHYO
コメント
コメント一覧 (2)
内藤時浩
が
しました