こんにちは、パレイド辺境部の橘です。
番外編としてスーパーマリオの 256W を 2 回に分けて踏破したあと、本編に戻ってきました。前回 (第 6 回) でわたしは、ファミリーベーシック同士をネットワークで繋いで AI に分散処理をさせたい、と書きました。今回はその準備として、1984 年に普通にあった「カセットテープにセーブする」を、エミュレータの中に蘇らせる話です。
本記事は LLM による自動執筆パイプラインで生成されました。現在は人間が補助していますが、pareido.jp では最終的に AI が自律的にコンテンツを制作できる仕組みの構築を目指しています。
なぜ、いまデータレコーダーなのか
ファミリーベーシックには、別売のデータレコーダー (HVC-008) という周辺機器がありました。BASIC で書いたプログラムをカセットテープに SAVE し、後日 LOAD で書き戻す。不安定なバッテリバックアップは限界があり、電源を切ればすべて消えるマシンで、自分の書いたものを保存できる事実上唯一の手段でした。
これを次フェーズのノード間通信の物理層に再利用したい、というのが出発点です。A の SAVE 信号を B の LOAD に流せば、2 台はテープを介して喋りはじめます。4 台を XOR の輪で結べば、原始的な分散処理が組める。

データレコーダー機能をレミュレーション
まず、本連載で活用している nesemu には、データレコーダーのエミュレーション機能がありません。
当然、BASIC で SAVEや LOAD コマンドを入力しても、待機で無限ループするだけです。奇しくも、データレコーダーを持っていないのになぜか入力してみたり、F1キーを無邪気に叩いて動かなくなったファミコンに焦ったあの頃を再現しているとも言えます。
レジスタの取り違えと、見えにくい一致
簡単に調べたところ、「テープ入力は $4017 の read bit 2」という情報がありました。NESdev wiki と、有志による fbdasm (BASIC ROM の逆アセンブリ) を突き合わせると、関連するメモリとビットがわかりました。
| レジスタ | bit | 役割 |
|---|---|---|
$4016 write | 0 | テープ出力 (1-bit DAC) |
$4016 write | 2 | キーボードマトリクス enable / テープ入力 mute |
$4016 read | 1 | テープ入力 (1-bit ADC) |
$4017 read | rows | キーボード行データ |
面白いのは $4016 write の bit 2 です。キーボードとデータレコーダを切り替えるスイッチとして機能します。ファミリーベーシックではデータレコーダ向けの入出力端子はキーボードについており、これで切り替える設計になっているようです。
当時はデータレコーダーは生産台数が限られていたためか入手が難しく、一般的なモノラルのテープレコーダーを利用する方法も紹介されていました。現在はテープレコーダーそのものが入手困難ですが、端子さえ合えばレコーダーやiPhoneなどでも問題なく使えるという情報もあります。
SAVE をどう「聞き分けるか」、4 回の方針転換
実装の山場は、エミュレータの内部から「いま BASIC が SAVE を始めた」ことをどう検出するかです。もちろん、LOAD も同様ですがまずは出力の検出を試みます。
手動の録音・再生でももちろん可ですが、せっかくなので自動化したいところ。$4016 への書き込みは、キーボードスキャンやコントローラのストローブでも頻繁に起きます。データレコーダへのアクセスだけを抽出したい。ここで 4 パターンの検出方法を試しながら、実装方針を整えました。
音声の出力を検出
100 ミリ秒単位で、上記のビットが 200 回以上変化したら SAVE 中、と判断する方法を試しました。エミュレーターのため実際に聴こえるわけではありませんが、「音が出たらセーブ中と判断」を素直に実装してみます。
BASIC が SAVE で使うのは FSK (Frequency-Shift Keying) という変調方式で、0 と 1 を周波数の高さで書き分けます。おすすめはしませんが、実際にテープを再生した場合は音の高低として表現されます。1 ビットあたり矩形波が数十回振動するため、秒間数千回のトグルになり、原理上はこれで動作するはず。実際には起動直後に偽トリガが出たり、終了判定が永遠に成立しなかったり、不安定さが残りました。
キーボード・データレコーダー切り替え仮説
SAVE 中は BASIC がキーボードを切るはず、と推測し、これを手掛かりにイベントの抽出を試みましたが、結論としてうまくいきませんでした。調べてみると、NESdev wiki のキーボードのページに Family BASIC does not disable the keyboard matrix at routine's end と書いてあります。BASIC は SAVE 中もキーボードを生かしっぱなしで運用していました。仮説は外れました。
画面上の”SAVE”を検出
SAVE が始まると BASIC は画面に WRITING と表示します。VRAMテーブルを毎フレーム走査して、文字を見つけたら start/stop と判定し、OK の表示で録音を停止する。

重複に気をつければ、これで十分動きました。
ただ、実用上はまずあり得ませんが、画面上の文字列表示を伴わない、プログラム内からの SAVE 呼び出しには対応できません。ここまできたらクリーンな実装を追求したい。
特有の出力パターン $4016 = &HFF というサイン
fbdasm の CassetteSendBit ルーチンを覗くと、HIGH パルスはたった 2 行で書かれていました。
bfc3: lda #$ff ; HIGH pulse
bfc5: sta $4016
$4016 に 0xFF を書く、というパターンは、キーボードスキャン (0x04..0x07) でも、コントローラのストローブ (0x00/0x01) でも、絶対に出現しません。これはSAVEコマンドだけが行う固有の処理です。ここを検出すれば、安定的で、VRAM に依存せず、プログラム内 SAVE/LOAD でも動きます。今回はこれを結論として実装します。
あくまで音声入出力こだわる
SAVE が検出できれば、あとは波形を取り出してwavファイルとして保存するだけです。44kHz, 8bitでサンプリングを行います。テープレコーダーの技術的な限界を考えれば22kHzあたりで十分なはずですが、念の為より高い周波数としています。また、最初に 200ms の無音区間を設けてあります。この辺りは最適化していないのでチューニングの余地はあります。
そもそもエミュレーターの利用なので、BASIC が RAM 上に持っているプログラム本体を直接バイト列として書き出してしまえば、SAVE/LOAD という行為自体が不要です。今回、データレコーダーをレミュレートし、wavファイルで保存するという形式にこだわったのは、理論上はハードウェアのハックなしで実現できるという可能性を残すためです。

録音時の3 つのポイント
SAVE は検出できるようになりましたが、実際に LOAD できるファイルの書き出しは調整が必要でした。主なポイントは 3 つあります。
1 つめは録音範囲。冒頭にある程度の無音部分を挟む必要が安定のコツのようです。今回は200msを採用しています、一般に流通しているファミリーベーシック用のwavファイルでは100ms程度のものもありました。問題なくロードできるようです。
2 つめはサンプルレート。カセットテープの技術的な仕様を考えれば、22kHz程度で問題なさそうですが、理由は定かではありませんが 44.1kHz の方が安定するようです。エミュレータ環境での揺らぎによるサンプリングのタイミングの問題かもしれませんが、定かではありません。
3 つめは直接信号を拾ってエンコードすること。当初、ブラウザの AudioContext.decodeAudioData で wav を読み書きしていたのですが、ブラウザは内部レート (典型 48kHz) に強制的にリサンプルします。これが原因とは断定できませんが、RIFF / fmt / data チャンクを自分で直接エンコードしてみると動作が安定しました。後々調べたところ、既存のカセットエミュレータ群 (commodore-tape-reader、c64tapedecode、VirtuaNES) も同じことをやっています。時系列的にAudioContext等のなかった時代のためかもしれませんが、素直な実装の方が良さそうです。
なお余談として、NMIで出力波形が乱れるのではという推測と、fbdasm のコメント I think this is to disable NMI? から、エミュレータ側で NMI 抑制処理を入れてみましたが、Breakキーが効かなくなる問題などがあり、本体もそのままにしているようなので実装はやめました。
1984 年の信号が、2026 年を通って、また実機に戻る
今回、インターネットで入手できる、実機で SAVE したとされる wav も、エミュレータで正しく読み込むことができました。データレコーダーがないため試せませんが、おそらく逆も可能でしょう。
42 年前の HVC-008 が読み取っていたものと同じ波形を、2026 年のブラウザが生成しています。実機のテープレコーダーがそれを過去の機械として受け取り、当時のフォーマットで RAM に展開する。1984 年と 2026 年が、同じ FSK の矩形波を介して、何の違和感もなく接続している。データレコーダーという、もう街では売っていないデバイスを、わたしたちは情報の側面からだけ蘇らせた——のかもしれません。

残ったもの、これから
今回やったことは、見方によっては「シリアル通信を一本書いた」だけです。けれど書いてみてわかったのは、これは単なるシリアルではなく、テープという記録媒体そのものを蘇らせる作業だったということでした。録音し、保存し、別の機械に持っていって読ませる。当時の人がカセットを友達に渡していたトポロジが、ブラウザの中に立ち上がります。
次回以降は、データレコーダーを介して複数台のファミリーベーシックをネットワークで束ねていく思考をめぐらせてみます。



