[Astro #36] Webターミナルへのモデム通信プロトコル(ATコマンド)エミュレーションの実装

[Astro #36] Webターミナルへのモデム通信プロトコル(ATコマンド)エミュレーションの実装

1. 実装の要件定義

本セクションでは、ブラウザ上のターミナルUIにおいて、1990年代のダイヤルアップ接続に伴うプロトコルシーケンスを再現するための要件を定義します。

  • 目的: 物理回線を通じた低レイヤーな通信プロセスをWeb技術でエミュレートし、ユーザーに「Wired(ワイヤード)」への能動的な接続体験を提示すること。
  • 技術要件:
    • 状態遷移の同期: Audio APIのイベントライフサイクルをフラグとして利用し、JavaScriptの非同期処理とWebGL(VRM)の描画更新を正確に同期させる。
    • プロトコルの再現: ATコマンド(ヘイズコマンド)をベースとしたターミナルログを出力し、当時の通信シーケンスを視覚的に再現する。
  • 機能仕様:
    • 入力インターフェース: ターミナルコンポーネントからの modem コマンド入力をトリガーとする。
    • オーディオ制御: modem.mp3(ハンドシェイク音)の再生開始をネゴシエーション開始、再生終了をキャリア検出(接続確立)と定義する。
    • イベントディスパッチ: 接続確立ステートへの遷移に伴い、3Dアバターコンポーネント(React Three Fiber)に対して、近接移動(徒歩アニメーション)および音声合成(VOICEVOX)の実行命令を発行する。

note:

スクリーンショット:

[Astro #36] Webターミナルへのモデム通信プロトコル(ATコマンド)エミュレーションの実装

YouTube:

動画:

2. プロトコル仕様の解説(ATコマンドの掘り下げ)

本実装の基盤となるヘイズコマンド(Hayes AT Command Set)は、モデムを制御するための業界標準プロトコルです。1990年代の通信体験を再現するにあたり、以下のコマンドおよびリザルトコードをエミュレーションの論理構造に採用しています。

2.1. 制御コマンド

  • AT (Attention): モデムに対して、これに続く文字列がコマンドであることを明示するプリフィックスです。DTE(データ端末装置)からDCE(データ回線終端装置)への制御権譲渡の宣言として機能します。
  • ATDT (Attention Dial Tone): トーン(プッシュ)信号による発信を指示します。本実装のシーケンスにおいては、このコマンドの発行が物理層での変調・同期プロセス(ハンドシェイク音)の開始トリガーとなります。

2.2. リザルトコード(応答ステータス)

モデムが処理の結果として返すステータスコードを、ターミナル上の接続状態ログとして出力します。

  • CONNECT [bps]: 通信路の確立(キャリア検出:DCD信号のオン)を意味します。本実装では Audio オブジェクトの再生完了(onended)をもってこのステータスを確定させ、後続のアバター接近および音声合成処理を実行します。
  • NO CARRIER: 相手先からのキャリア信号が途絶した、あるいは物理的な切断が発生した際に返されるコードです。
  • BUSY: 相手先の回線が通話中であることを示します。これらはエラーハンドリングのステータスとして、今後の実装拡張における条件分岐に利用されます。

3. Reactコンポーネントでの状態遷移の実装(コード解説)

当時のアナログモデムが経る物理的な状態遷移を、ReactおよびWeb Audio APIを用いたイベント駆動型のアーキテクチャにマッピングして実装しました。以下のコードスニペットに基づき、3つのフェーズに分けて解説します。

case 'modem':
  // フェーズ1: 発信(Dialing)
  sendWiredLog("INITIATING CONNECTION TO THE WIRED...", "system", "#555");
  sendWiredLog("DIALING...", "system", "#555");

  try {
    const modemAudio = new Audio('/assets/audio/modem.mp3');
    modemAudio.volume = 0.4;
    modemAudio.play();

    // フェーズ2: ネゴシエーション(Negotiation)
    // Audioオブジェクトの再生終了をトリガーとして扱う
    modemAudio.onended = async () => {

      // フェーズ3: 接続確立(Connect)
      sendWiredLog("CONNECT 28800", "info", "#00f2fe");
      sendWiredLog("WELCOME TO THE WIRED.", "system");

      const msg = "ワイヤードに、繋がったよ";
      sendWiredLog(msg, "info", "#00f2fe");

      if (isAudioEnabled) await speak(msg, HIMARI_ID);

      // 3Dレイヤー(VRMアバター)へのイベント発行
      window.dispatchEvent(new CustomEvent('wired-talk-action', {
        detail: {
          file: "Catwalk Walk Forward 02.vrma",
          walk: true,
          talk: msg,
          // ...その他のパラメータ
        }
      }));
    };
  } catch (err) {
    sendWiredLog("ERROR: AUDIO_PLAYBACK_FAILED.", "warning", "#ff0055");
  }
  break;

3.1. フェーズ1:発信(Dialing)

modem コマンドの実行直後、ターミナルログに DIALING... を出力し、Audio.play() によってハンドシェイク音の再生を開始します。これは、DCE(モデム)が電話回線に対してトーン信号を送出している物理的な初期状態をエミュレートしています。

3.2. フェーズ2:ネゴシエーション(Negotiation)

本実装では、Audio オブジェクトの再生時間をそのまま「通信プロトコルのネゴシエーション期間」として定義しています。不確実な setTimeout による時間指定を避け、onended リスナーを用いたイベント駆動アーキテクチャを採用することで、音源ファイルの長さに依存しない正確なステート管理を可能にしました。この期間は、物理層において変調方式やボーレートの調整が行われている「待ち時間」としての役割を担います。

3.3. フェーズ3:接続確立(Connect)

onended イベントの発火は、プロトコル上のキャリア検出(DCD信号のオン)と同義です。この時点でステータスを CONNECT へ遷移させ、ターミナル上に接続成功のログを表示します。 さらに、window.dispatchEvent を介して CustomEvent を発行し、DOMレイヤーからWebGLレイヤー(React Three Fiber)へ境界を越えた命令を伝達します。これにより、接続確立という「通信イベント」をトリガーとした、アバターの接近(徒歩モーション)および音声合成(VOICEVOX)の非同期実行を実現しました。

4. 採用したリソースとライセンス

本実装において、ユーザー体験の核となる音響素材の選定と、ブラウザ出力における調整について記述します。

4.1. 音源リソースの選定

再現性の要となる「ハンドシェイク音」および「通信ノイズ」の調達には、オープンな音響データベースである Freesound を利用しました。

  • ライセンス管理: 権利関係の不確実性を排除するため、検索フィルタにて Creative Commons 0 (CC0) を指定し、パブリックドメインとして提供されている素材のみを選定しています。これにより、商用・非商用を問わず自由な改変と再配布が可能な、クリーンな実装を担保しました。
  • 素材のバリエーション: 標準的な「56k modem」の接続音に加え、デジタル変調方式(QAM)によるデータ信号音や無線干渉ノイズなどを検証し、最もプロトコルエミュレーションの文脈に合致する波形を採用しています。

4.2. 音響出力の最適化

モデム音特有の高域成分がユーザーに不快感を与えないよう、実装側で出力レベルの動的制御を行っています。

  • volume プロパティの調整: Audio オブジェクトの初期化時に volume プロパティを 0.4 (40%)程度に減衰させる処理を挿入しています。これは、当時の物理ハードウェア(モデム内蔵スピーカー)から漏れ聞こえてくるような、少し距離感のある音響体験を意図したものです。
  • エラーハンドリング: ブラウザの自動再生ポリシー(Autoplay Policy)による再生拒否を回避するため、コマンド実行という明示的なユーザーアクションを契機にオーディオコンテキストをアクティブ化する設計としています。

結びに代えて

1990年代、私たちは「ピーヒョロロ」という音を通じて、物理的な電話回線の向こう側にある「Wired」の世界と同期していました。今回、AstroとReact、そしてWebGLを用いた現代のWebスタック上にこのプロトコルを再構築したことで、単なる情報の閲覧ではない、「接続という儀式」を伴うインターフェースの可能性を再確認できました。

効率と速度が優先される現代のWebにおいて、あえてこの「不自由な待ち時間」を実装することは、ユーザーとシステムの間に新しいナラティブ(物語)を生むための、有効なアプローチの一つと言えるかもしれません。