プログラミング
ZX Spectrumの1ビットビーパーでのデジタルオーディオ
Digital Audio on the ZX Spectrum's 1-Bit Beeper (bumbershootsoft.wordpress.com)
要約
この記事では、ZX Spectrumのシンプルな1ビットビーパーを使用して高品質なデジタルオーディオを生成する方法を探求しています。この技術は、ハードウェアとエミュレーターの両方で検証されており、実用性は限定的ですが、音質は大幅に向上します。
全文翻訳
ついにこのZX Spectrumシリーズを締めくくる時が来ました。すべてのSpectrumモデルに搭載されているシンプルな1ビットビーパーから、高品質なデジタルオーディオを発信する方法を見ていきます。結果はかなり良好です。これは先週のデモリールの最後のサウンドであり、その前のシンプルなPCM再生システムとの違いは歴然としています。また、現時点では、当時のハードウェアと最新のクローン両方でデモプログラムのハードウェア検証も行いました。インターネットの気まぐれにより、識別は通常ハンドルネームで行われますが、Mastodon経由の「gmc」氏は48K Spectrumで実行でき、Tom Harte氏は現代的なシステム再構築であるOmni 128システムで結果を得ました。エミュレーターのサポートもかなり広まっています。上記のデモをキャプチャするためにFuseを使用しましたが、少なくともEightyOne、Zesarux、Spectaculator、Clock Signalでも問題なく動作します。この技術の実用性はやや限定的です。外部アンプのないハードウェアでは音が非常に小さく、実験しているサンプル長と品質に必要なRAMは事実上「すべて」です。しかし、それは機能します。それが機能するようにすることは、しかし、旅でした。私が最初に機能するものを思いつくまでに、2回の完全な書き直しが必要でした。それでも、設定したタイミング制約にはほとんど収まりませんでした。このシステムでこれまで使用したことのないいくつかのテクニックを引き出す必要がありました。私は主にここでアセンブリ言語で作業しています。なぜなら、私が扱っているシステムでは、そこで作業する方が一般的に快適だからです。DOSのような適切なコンパイラサポートがあれば、通常Cでも十分にうまくいくでしょう。今日、私はCで十分にうまくやれなかったでしょう。オーディオエフェクトのために正確にタイミングが取られた命令シーケンスが必要だったことを除いても、タイミング制約は非常に厳しく、レジスタのプレッシャーは非常に激しかったため、機能させるには直接チップアクセスが必要でした。これは問題の最も難しい形態でさえなく、私がここで使用するテクニックを改善するためのいくつかの簡単な方法がありますが、私のテストでは必要ありません。今日は、Dmitry Milk氏のSpectrumサウンドエンジンと、Apple IIe用のMichael J. Mahon氏のRTSynthシステムを見ることで締めくくります。彼らは私が直面した問題とは少し異なる問題を解決しています。そのため、私たちのサウンド出力アプローチは似ていますが、完全に異なるCPU上にあるにもかかわらず、私の再生システムよりも互いに似ています。
過去の取り組み
私は最初に1ビットデジタルサンプルに遭遇したのは、1990年代初頭のDOSゲームを通じてでした。特に、図解されたテキストアドベンチャー「Eric the Unready」は、「RealSound」と呼ばれるテクノロジーでPCスピーカーを通じて効果音を再生でき、Star Control II: The Ur-Quan MastersはAmigaスタイルのトラッカーミュージックをサウンドトラックとして使用し、SoundBlasterと同様にPCスピーカーでも陽気に配信できました。PCの1ビットスピーカーは、SpectrumやApple IIよりもプログラムするのが著しく簡単です。約10年前に、単純なトーン生成とPWM再生の両方を試しました。私の知る限り、それらはすべてまだ機能的で正確ですが、今日私たちがやっていることには実際には必要ありません。しかし、それらのアプローチは私たちの設計と戦略を動機づけています。
ここでの基本的な物理原理は、回路には静電容量があり、スピーカーには質量があるため、1ビット信号が0から1またはその逆に実際に移動するには時間がかかり、その後、スピーカーのコーンが実際に音波を生成するためにその空間を移動するのにさらに時間がかかるということです。信号を途中で中断すると、信号とスピーカーは空間全体を移動できず、より細かく変化する音波が得られます。これはPCでは不合理なほど簡単でした。SpectrumのようにI/Oポートから直接スピーカーの設定をコマンドできるだけでなく、システムクロック割り込みとは独立して動作する高度にプログラム可能なハードウェアタイマーから駆動することもできました。これは、ビーパーをサイクルカウントされたコードで常に監視する必要がある代わりに、送信する信号を変更する必要があるときにのみタイミング情報を設定すればよいことを意味しました。通常のビープ音は周波数を変更するときだけですが、マイクロ秒単位の精度でワンショットパルス幅を設定することもできました。これは、16kHz、またはサンプルレートが何であれ、CPUタイマー割り込みを設定し、そのサンプルのPCM値に基づいて実行される時間だけサウンドタイマーを設定できることを意味しました。したがって、タイマーチップ自体が一種のデジタル-アナログコンバーターとして機能しました。
Spectrumには、サンプルレートを強制するためのタイマーも、パルス幅を設定するためのタイマーもありませんが、ソフトウェアでそれらの両方を直接行うことができるはずです。一般的なアイデアは、各サンプルと同じ長さのループを持ち、ループごとにスピーカーをオンにしてからオフにし、その両側に2つの短いが可変長の遅延を置くことです。「スピーカーを再びオフにする」の後に続く遅延です。最初の遅延はパルス幅を設定し、2番目の遅延はパルス幅がどれだけ広くても、コードブロック全体の時間が同じになるようにします。
誤った試み1: 実際の遅延ループ
私の最初の実装試みは、ナプキン計算から始まりました。私のPC再生システムは、1から127マイクロ秒までのパルス幅を受け入れていました。8kHzのサンプルレートは、125マイクロ秒ごとにパルスが必要であることを意味します。これは私が眉をひそめる最初のポイントです。私のテストプログラムは16kHzのサンプルレートを使用しており、サンプルが実際に範囲全体を使用していた場合、私は自分自身をオーバーランすることになります。おそらく私のサンプルは思ったよりもソフトでした。それでも、これは基本的に同じサンプルなので、私たちは合理的な状態にあるはずです。Spectrum CPUは約3.5MHzなので、125マイクロ秒は438サイクルです。438サイクルは、デフォルトの遅延ループの15回の反復に収まるのに十分快適です。スピーカーをオンにした後にオフにするには18サイクル必要なので、遅延範囲は13*15+2+18=215サイクルまでになります。これは16kHzマーク自体に十分に近いため、最初の実験では8kHzモードに留まることにしました。私はすでに8kHzで7ビットオーディオのクリップのバージョンを持っています。しばらく前にNESで使用しました。この8kHzウィンドウには、そのデータを直接使用し、オンザフライで4ビットPCMに変換するのに十分な時間があります。
最初のドラフトの擬似コードは次のようになりました。
サンプルバイトNを読み込み、4ビットに切り捨てる。
スピーカーをオンにする。
何もしないループをN回実行する。
スピーカーをオフにする。
同じ何もしないループを16-N回実行する。
サンプルポインタをインクリメントし、サンプル終了なら終了。
適切な時間待ってから125マイクロ秒に達したら、ステップ1に戻る。
ここでの重要な洞察は、ステップ3と5が個別に可変の時間を要する可能性がある一方で、それらを合わせると常に同じ時間を要することです。残念ながら、このビルドはまったく機能しませんでした。得られたのは、鋭い高音の笛だけでした。当時、遅延が長すぎると結論付けました。スピーカーの遷移中に中断できておらず、代わりに8kHzのトーンを生成していました。より短いビット幅に切り捨てても、実際には役立ちませんでした。
誤った試み2: 動的な遅延
私の最初の考えは、「定数因子」の問題を抱えているということでした。サンプルはかなりソフトで、主に6〜10の範囲のサンプルを意味し、おそらくそれらの数値を下げる必要があるかもしれません。しかし、何を目標にすべきかはよくわかりませんでした。そして、13サイクルのジャンプは少し過剰に思えました。より細かい粒度で再構築することにしました。ブログのずっと最初の方で、6502での微調整されたプログラム可能な遅延について調べました。これは、Atari 2600で最初に見たテクニックを借りたものです。そこには、SinclairのZ80では再現できないCPU固有のトリックがありましたが、実際には必要ありませんでした。それらのトリックは、1MHzの6502にサイクル単位の精度を達成させました。つまり、4サイクル単位の精度があれば十分です。一般的なテクニックは、遅延命令の長いストリームを配置し、可変遅延を達成するために、