
2026/08/04 3:32
ZX スペクトル システムツアー:サウンド
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
欠落する要素 は、提示された特定の「重要ポイントリスト」に対する「完全性」に大きく影響します。サマリーは明確であり主な物語の構成を捉えていますが、重要ポイントリストにはアドレスや式、特定のサイクル数などの詳細な技術仕様が含まれており、サマリーではそれらが一般化されたり完全に省略されたりしています。「すべての主要な重要ポイントが反映されているか」という厳格なチェックポイントを踏むと、現状のサマリーはリストに含まれた詳細な技術データに対して忠実さを欠いています。
改善されたサマリー: 本文は ZX Spectrum 向けの 2 つのオーディオ方法を詳述しています:1 ビットビーパーと AY-3-8910 シンセサイザチップ(強化モデルや周辺機器として搭載されているもの)。初期のオーディオ試作ではタイミングの不一致や浮動小数点誤差により失敗し、解決策として周波数の手動管理、コードを
$9000 へ移して早期モデルでのビデオ回路への干渉を防ぐこと、一貫した 10 サイクルの分岐を実現するために絶対アドレスの JP 命令を使用することが挙げられます。ビーパーは $03B5 の BIOS を活用(例:437500/f – 30.125 のような式)するか、ポート $FE を直接使用する方法があり、タイミング調整のため NOP を使用し、ループのアンロールのような最適化を施すことでノート持続時間を 1.5 秒から 2.4 秒以上まで延長します。あるいは AY-3 チップを使用すれば特定のクロック速度で調整された式(3546900/(32f) など)を用いて複雑な音楽を実現できます。Python スクリプトを使ってこれらの技術を利用した BASIC コードを生成し、ミュージシャンがプラットフォームと方法(ビーパーは効果やジングル用、AY-3 は連続音楽用)を選択できるようにします。本文
シューティングギャラリーのサウンド機能:ZXSpectrum の初期ツアー
シューティングギャラリーの話題はこれで一区切りとなりました。次は、ZXSpectrum の初期ツアーのうちこれまで触れられていない「サウンド」の部分へと移りましょう。他プラットフォームに比べて特に新しい要素はないものの、それは同時に 核心となる解決策の原理がすでに構築済みであることを意味します。我々が必要なのは、Spectrum 特有の装飾(frosting)を施すだけだと言えるでしょう。
メインラインのサウンド機能の種類
メインラインの Spectrum システムにはサウンド機能として二つの選択肢があります:
- ソフトウェア制御による 1 ビットのブイパー(ビーパ)
- すべてのシステムに搭載されています。
- その基盤となるハードウェアはアップル II や PC スピーカーで目にしたものと大差ありません。
- General Instrument AY-3-8910 チップを搭載したシンセサイザー機能
- 128K Spectrum 以降の全モデルに搭載されています。
- 16K および 48K バリアントでは周辺機器として利用可能でした。
- Atari ST に搭載されているものとは基本的に同型ですが、クロック周波数が異なります。
- チップとの通信には組み込み BIOS コールが存在しないのが特徴です。
この記事の締めくくりには、C 調のスケールを再生する 3 つのプログラムを提供することを目指します。
1. BIOS を介したブイパーの利用
1 ビットのブイパーはアップル II のそれと非常に類似しています。
- I/O ポート $10 のビット設定:スピーカーの状態を変更できます。
- トーン生成原理:そのビットを反転させる間隔を細かくタイミング制御することによって生成されます。
BASIC の
BEEP コマンドが既にこの処理を担当しており、汎用的なルーティンへのフォワーディングを行うことで実現されています。ブイパー用ルーティンはメモリ場所 $03B5 に存在し、率直に言えば私が 6502 システムのために作成したバージョンよりも巧妙です。
- HL レジスタ:4 サイクル(no-op の長さ)単位の遅延時間を指定します。
- その高精度を実現する技術は、以前実装したプログラムメーブルなサイクル精度の 6502 遅延とはよく似たものです。
- DE レジスタ:音符の持続時間を指定しますが、不便なのは実際に持つべき波長数そのものを指定している点です。同じ長さだがピッチが異なるノートでは、それぞれ異なる duration コードが必要となります。
一方、
BEEP コマンド自体は、中間 C(middle C)から半音階オフの音符と、秒単位の持続時間を指定されることを前提としています。必要な値を得る方法を実装地点 $03F8 に存在する実装を見てみましょう。これはシステムの浮動小数点エンジンを通じて行われるため、きれいな公式をそのまま引き出すことができます:
- 周波数コード(HL):
437500/f – 30.125- $f$ は目標周波数 (Hz)
- 持続コード(DE):
f × t- $t$ は継続時間 (秒)
以下は、Python コードを用いて C 調スケールへのこれらの値を適用し、再生用プログラムを作成したものです。
org $7000 ld b,8 ; 8 音符再生 ld hl,scale ; ポインターから読み込み開始 loop: push bc ld c,(hl) ; BC に周波数をロード inc hl ld b,(hl) inc hl ld e,(hl) ; HL に持続時間をロード inc hl ld d,(hl) inc hl push hl ; ポインターを一旦保存 ld h,b ; BC をコピーして HL へ ld l,c call $03b5 ; ROM に任せて実行 pop hl pop bc djnz loop ret scale: dw $066a,$0082,$05b3,$0092,$0511,$00a4,$04c6,$00ae dw $043d,$00c3,$03c4,$00dc,$0357,$00f6,$0325,$0105
性能評価
- これはアップル II サウンド再生ルーティンと同様の「半波長」方式ですが、はるかに微細なタイミング制御により、全体のトーン精度が著しく向上しています。
- 初期記事で直面したトレードオフのほとんどがこのシステムには見られず、専用 PSG から期待される種類の制御力を得ています。
- 唯一の欠点:両方の側面で独自に分算処理を行う必要がある点が少し手間がかかる点です。
- 汎用的なルーティン(任意の周波数と持続時間を扱えるもの)を実現したい場合は、自分での分算器を用意する必要があります。
- しかし、16KB の Spectrum では他に選択肢がなく、このルーティンを頼るほかありません。
2. 直接ブイパーの利用
BIOS ルーティンは十分に優秀ですが、独自のオシレータ関数を作成するのであれば、全く異なる原理に基づいて実装したいところです。 SID チップや Ensoniq チップの内部機構に触発されて作成した、16 ビットシステム方式に基づく周波数型の原理が利用可能です。Z80 が内蔵する 16 ビット数学処理のサポートのおかげで、6502 コードよりもはるかに実装しやすくなります。
基本思想と I/O ポート
- 動作原理:周波数コードをカウンタに繰り返し加算し、カウンタがオーバーフローするたびにスピーカーを反転させます。
- 注意点:他に一切干渉しないように確保する必要があります。
I/O ポート $FE は三役を担っています:
- 下位 3 ビット:ボーダー色を設定します(
システム変数の値を保持)。BORDCR - $08 ビット:テープデッキ制御時とスピーカ制御時のオーディオ出力を切り替えます。常にオンに強制します。
- $10 ビット:実際に操作したいスピーカーオーディオの制御用です。
実装の改善(タイミング調整)
最初はすべてを直接実装し、可能な限り分岐数を減らすアプローチから始めました。しかし、各パスが完全に同じ時間になるようにコードを見直す必要があります。
- 問題点:
命令は分岐時 12 サイクル、非分岐時 7 サイクルかかるため、タイミング調整が困難です。JR - 解決策:
を使わず、絶対アドレス指定のJR
バージョンを使用します。JP- 条件付きであっても常に 10 サイクルという絶妙なタイミング特性を持ちます。
これにより大幅に簡素化され、遅延分岐は二つの NOP とメインラインへの
JP に置き換わります。
sound: ld hl,0 ; カウンタは 0 から開始 ld a,(\$5c48) ; BORDCR and $38 ; ボーダー色を抽出 rrca ; 下位 3 ビットへシフト rrca rrca or $08 ; テープオーディオ無効化 di ; IRQ による割り込みを抑制 push af .loop: pop af add hl,de ; 周波数をカウンタに追加 jp nc,2F ; オーバーフローした場合… xor $10 ; …スピーカー出力反転 out (\$fe),a ; +7 = 14 サイクル (JP 非分岐) 1 dec bc ; タイマー減算 push af ; 0 チェック ld a,b or c jr nz,.loop pop af ; クリーナップ ei ret 2 nop ; オーバーフローなし場合、18 サイクル待機 (JP 分岐 + NOP×2) nop jp 1B
性能と問題点
- 合計サイクル:反復当たり 84 サイクル。
- 周波数制御:3Hz 以上、ほぼ 100kHz までの線形制御が可能で、十分に性能は良好です。
- 持続時間の限界:約 1.5 秒までしかありません(理想的とは言えません)。
- 音質の問題:生成されるトーンは音質が極めて悪く、ピッチが大きく外れ、タイミングも不整合であり、ガタガタしたりワブルしたりします。
問題の原因と解決策
問題は初期 Spectrum では $4000–$7FFF レンジアに限定されていますが、16K Spectrum ではこれが全 RAM を占めるためこの技術は不可能となります。さらに、スタックポインタの位置も影響します(
CLEAR コマンドによってスタックポインタが押し下げられ、メモリアクセス待機状態の影響を受けます)。
以下の改善を行いました:
- RAM の上位に 1 バイトを確保してグローバル変数として直接使用し、スタックの位置を特定する必要を取り消しました。
- ループを展開し、duration コードを半分のサイズにしました(BPM 100 で全音符は 2.4 秒持続)。
以下が再生ルーティンの最終コードです:
sound: ld hl,0 ld a,(\$5c48) ; BORDCR and $38 rrca rrca rrca or $08 di .lp: add hl,de ; +11 = 11 jp nc,2F ; +10 = 21 xor $10 ; + 7 = 28 out (\$fe),a ; +11 = 39 1 nop ; + 4 = 43 nop ; + 4 = 47 nop ; + 4 = 51 nop ; + 4 = 55 nop ; + 4 = 59 nop ; + 4 = 63 nop ; + 4 = 67 nop ; + 4 = 71 nop ; + 4 = 75 nop ; + 4 = 79 jp 3F ; +10 = 89 3 add hl,de ; +11 = 11 jp nc,5F ; +10 = 21 xor $10 ; + 7 = 28 out (\$fe),a ; +11 = 39 4 dec bc ; + 6 = 45 ld (.scratch),a ; +13 = 58 ld a,b ; + 4 = 62 or c ; + 4 = 66 ld a,(.scratch) ; +13 = 79 jp nz,.lp ; +10 = 89 5 ei ret 2 nop ; + 4 = 25 nop ; + 4 = 29 jp 1B ; +10 = 39 .scratch # 1
スケールを再生するプログラムも完成しました:
org $8000 map \$9000 ; スタック位置回避用マップ ld b,8 ld hl,scale loop: ld e,(hl) inc hl ld d,(hl) inc hl push hl push bc ld bc,\$2000 ; スタックポインタの位置調整 call sound pop bc pop hl djnz loop ret scale: dw \$0367,\$03d2,\$044a,\$048b,\$051a,\$05ba,\$066e,\$06cf
3. AY-3 サウンドチップのプログラミング
AY-3-8910 は以前にも登場しましたが、Spectrum 128 と以降のモデルでは新知見があります。
- 書き込み方法の違い:
- まずポート $FFFD にレジスタ番号を書き、次にポート $BFFD に値を書きます。
- 周波数計算式の変化:
- チップを駆動するメインクロックが 4MHz から 3.5469 MHzに変更されているためです。
- 周波数 $f$ Hz のコードは:
3546900/(32f)
- エンベロープ長計算式:
- 長さ $t$ 秒のエンベロープに対するコードは:
3546900/(512t)
- 長さ $t$ 秒のエンベロープに対するコードは:
これにより、以前の記事の内容を参照しつつも、これらのパラメータ変更のみで AY チップを使用できます。
簡易関数とマクロ
以下の簡易関数
ayreg を作成しました。これはバイト D をレジスタ A に書き込む処理です。
ayreg: ld bc,\$fffd ; AY-3 インデックスポート out (c),a ; レジスタ番号書き込み ld b,\$bf ; AY-3 バリューポート ld a,d ; 値を A に out (c),a ; 値書き込み ret
さらに、データが定数かポインタから読み取るかに関わらず呼び出しを容易にするためのマクロ
AY も作成しました。
macro AY index,val:(hl) ld a,index ; レジスタ番号 ld d,val ; 値 (または HL ポインタ) call ayreg endmacro
これによりエンベロープシステムを利用しても、メインコードが最も短くなりました。
org \$7000 ;; AY-3 ボイス初期化 AY \$0b,\$10 ; エンベロープ長さ: 1 秒 AY \$0c,\$1b AY \$08,\$10 ; チャンネル A でエンベロープ使用 ld b,8 ld hl,scale 1 push bc AY \$00 ; (HL) テーブルから周波数読み込み inc hl ; ポインタを進める AY \$01 inc hl AY \$0d,\$09 ; 減衰エンベロープ開始 AY \$07,\$fe ; チャンネル A 有効化 call pause ; その後半秒待つ pop bc djnz 1B AY \$07,\$ff ; チャンネル A 無効化 ret pause: push af ; 25 フレーム待機 push hl ld hl,\$5c78 ; FRAMES リザージメント地点 ld a,(hl) add 25 1 halt cp (hl) jr nz,1B pop hl pop af ret scale: dw \$01a7,\$0179,\$0150,\$013d,\$011a,\$00fb,\$00e0,\$00d3
これを使って何ができるか
-
ブイパー(スピーカー):
- 主に、実行中に動作を一時停止しても問題ないほど短い効果音やジングルに限定されます。
- アップル II のサウンド利用とほぼ同様の使い方です。
- PC スピーカーも類似の回路を持っていましたが、専用の中断と独立したタイマーで駆動できるため、システム全体を占める必要がありませんでした。
-
AY-3 チップ:
- 主に自律的に動作し、音楽ドライバや環境効果音にも容易に対応できます。
- 私が検討したシステムの中では、これには Atari STだけでなく MSX リーンも含まれます。