[Astro] #120 SID Player — reSID + fake6502 WASM + AudioWorklet によるブラウザ完結型 Commodore 64 SIDプレイヤー
概要
1980年代に世界で最も売れたパソコン「Commodore 64(C64)」に搭載された伝説的音源チップSID(MOS 6581/8580)を、ブラウザ上で完全にエミュレートし、.sidファイルをリアルタイムに再生するWebプレイヤーを構築した。
C++製のSIDチップエミュレーター「reSID」とC製の6502 CPUエミュレーター「fake6502」をEmscriptenでWASMビルド(82KB)し、AudioWorkletスレッド内で直接instantiateしてリアルタイム音声合成を行っている。
SID Player
Commodore 64 SID音源プレイヤー。.sidファイルをドロップして再生。3Voice分離波形表示、チップモデル切替対応。
lain-lab.comスクリーンショット
動画(GIF)
.sidファイルの入手先
DeepSID — Online SID Player
HVSC(High Voltage SID Collection)ベースのオンラインSIDプレイヤー。数万曲の.sidファイルをダウンロード可能。
deepsid.chordian.net
HVSC — High Voltage SID Collection
世界最大のSID楽曲アーカイブ。50,000曲以上の.sidファイルを収録。
hvsc.c64.org今回の実装内容
| 機能・モジュール | 概要 |
|---|---|
| SIDチップエミュレーション | reSID(MOS 6581/8580)をEmscriptenでWASMビルド |
| 6502 CPUエミュレーション | fake6502(パブリックドメイン)をCブリッジで分離コンパイル |
| PSIDヘッダー解析 | .sidファイルのマジック、init/playアドレス、曲数、タイトル等をパース |
| C64メモリマップ | 64KB RAM + SIDレジスタ(D41F)フック |
| AudioWorklet再生 | WASM直接instantiate、128サンプル単位のリアルタイム合成 |
| 3Voice分離波形 | reSIDの各Voice出力を個別に取り出しリングバッファで表示 |
| チップモデル切替 | MOS 6581 / MOS 8580 をリアルタイムに切替 |
| AnalyserNode連携 | ミックス波形 + 周波数スペクトラム表示 |
| SIDレジスタモニター | 32レジスタの16進数値をリアルタイム更新 |
| 再生制御 | 再生/一時停止/停止/前曲/次曲、ボリューム調整 |
背景
C64のSIDチップ(Sound Interface Device)は、単なるゲーム用音源ではなく、3基のオシレーター(鋸歯状波、三角波、パルス波/PWM、ノイズ)、ハードウェア・リングモジュレーション、本格的なADSRエンベロープ、アナログフィルター(LP/BP/HP)を備えた「1チップ・シンセサイザー」だった。
.sidファイルはMIDIや音声データではなく、C64の6502 CPU用マシン語プログラムそのもの。これを再生するには、SIDチップだけでなく6502 CPUのエミュレーションも必要になる。
スクリーンショット
3Voice分離波形表示
波形 + スペクトラム(Bothモード)
技術実装の詳細
1. アーキテクチャ
[ Main Thread (Astro UI) ]
│ .sidファイル読み込み → AudioWorkletNode作成
▼
[ AudioWorklet Thread (sid-processor.js) ]
│ WebAssembly.instantiate(wasmBytes) → WASM直接ロード
│ 128サンプルごとにprocess() → sid_render_float_multi()
▼
[ WASM (reSID + fake6502 + PSID Parser) 82KB ]
│ 1サンプルごとに:
│ SIDチップをクロック → 50Hzでplayルーチン実行
│ → 6502がSIDレジスタに書き込み → reSIDに転送
└─► Float32Array → AudioWorklet出力 → スピーカー
2. C/C++分離コンパイル
fake6502はC言語で書かれており、C++の予約語(and, or, not, xor)と衝突する。fake6502_bridge.cを作成してCコンパイラ(emcc)で別途コンパイルし、C++のsid_wrapper.cppとリンクする。
emcc fake6502_bridge.c -O3 -c -o fake6502_bridge.o
em++ sid_wrapper.cpp resid/src/*.cc fake6502_bridge.o -O3 \
-I./resid/src --no-entry -s STANDALONE_WASM=1 \
-s EXPORTED_FUNCTIONS='[...]' -o sid_core.wasm
3. SIDレジスタフック(D41F)
6502コードがC64のSIDレジスタアドレスに書き込むと、write6502()コールバック内でreSIDに値を転送する。
void write6502(uint16_t address, uint8_t value) {
g_ram[address] = value;
if (address >= 0xD400 && address <= 0xD41F) {
g_sid.write(address & 0x1F, value);
}
}
4. 3Voice個別出力
reSIDのvoice[3]配列から各Voiceの出力を個別に取り出し、4本のfloatバッファ(mix + V1/V2/V3)に書き出す。
void sid_render_float_multi(float* out_mix, float* out_v1,
float* out_v2, float* out_v3, int num_samples) {
for (int i = 0; i < num_samples; ++i) {
g_sid.clock(cycles);
out_v1[i] = (float)g_sid.voice[0].output() / 32768.0f;
out_v2[i] = (float)g_sid.voice[1].output() / 32768.0f;
out_v3[i] = (float)g_sid.voice[2].output() / 32768.0f;
out_mix[i] = /* DCオフセット除去済みミックス */;
}
}
5. AudioWorklet内でのWASM直接instantiate
AudioWorkletスレッドではWeb Workerと異なりimportScripts()でEmscriptenグルーコードを読めない。STANDALONE_WASMビルドにより、WebAssembly.instantiate()で直接ロードする。
const { instance } = await WebAssembly.instantiate(wasmBytes);
this.exports = instance.exports;
this.exports._initialize(); // C++ staticオブジェクトのコンストラクタ実行
this.exports.sid_init(sampleRate);
6. WebAudioノードチェーン
AudioWorkletNode (SID) → GainNode (音量) → AnalyserNode (波形解析) → スピーカー
AnalyserNodeでミックス波形と周波数スペクトラムを取得し、Canvas2Dで描画。3Voice個別波形はWASMから直接取得してリングバッファに蓄積。
技術スタック
- Astro — ページ構築
- reSID — SIDチップエミュレーション(C++, MOS 6581/8580対応)
- fake6502 — 6502 CPUエミュレーション(C, パブリックドメイン)
- Emscripten — C/C++ → WASM ビルド(STANDALONE_WASM)
- AudioWorklet — リアルタイム音声処理スレッド
- WebAudio API — GainNode, AnalyserNode
- Canvas 2D — 波形/スペクトラム/レジスタ描画
更新履歴
- v0.1: reSIDのEmscriptenビルド成功、AudioWorkletでテストトーン(ノコギリ波)再生。
- v0.2: fake6502組み込み、PSIDヘッダーパーサー実装、.sidファイル再生成功。
- v0.3: Astroページ化、AnalyserNode連携、波形/スペクトラムビジュアライザー、SIDレジスタモニター。
- v0.5: 3Voice分離波形表示(WASM改造)、チップモデル切替(6581/8580)、リングバッファスクロール表示、一時停止/再開機能。
- v1.0: 公開。
関連リンク
[JavaScript] reSID + fake6502をWASM化してAudioWorkletで.sidファイルを再生する ― ブラウザ完結SIDプレイヤー構築記 // PROTOCOL.LAIN
reSID(MOS 6581/8580エミュレーター)とfake6502(6502 CPUエミュレーター)をEmscriptenでSTANDALONE_WASMビルドし、AudioWorkletスレッド内で直接WASMをinstantiateしてリアルタイム音声合成を実現。PSIDヘッダー解析、C64メモリマップ配線、50Hz playルーチン呼び出しまで。
lain-lab.com