きっかけ
国際モールス符号の文字割り当てが二分木で表現できることを知り、それをインタラクティブに操作できる学習アプリをNuxt 3で作ることにした。元はReactで書かれた実装があり、それをVueに移植する形で開発を進めた。
二分木とモールス符号の関係
モールス符号の全文字は、一本の二分木に収まる。STARTノードから左に進むとダッシュ(-)、右に進むとドット(・)を追加していく。各文字の符号がそのまま木の中の位置を決める。
START
/ \
-(dash) ・(dot)
/ \
T E
/ \ / \
M N A I
英語の設計思想: 頻度最適化
サミュエル・モールスは、英語で最もよく使われる文字に短い符号を割り当てた。E(・)とT(-)は1符号で済む。この2文字だけで英文の約22%を占める。シャノンの情報理論(1948年)より100年以上前の直感的な最適化だった。
和文モールスは別の設計思想
日本語の和文モールスでは「む」(-)と「へ」(・)が最短の1符号。「む」と「へ」が日本語で最頻出というわけではなく、英語版とは異なる設計思想で作られている。
実務では和文モールスはほぼ使われず、日本語もローマ字(欧文モールス)で打つのが主流。
数字の位置
数字(0-9)は全て5符号で統一されている。二分木でいうと最も深い階層に位置する。アルファベットのツリーに数字を統合するため、ツリーのmaxDepthを5に拡張した。
実装した機能
二分木ビジュアライザー(SVG描画)
アプリの中心となる機能。SVGで二分木を描画し、英語(A-Z + 0-9)と日本語(ひらがな)の2つのツリーを切り替えて表示できる。
描画の設計:
- ノード: 円の中に文字ラベルを配置
- エッジの色分け: ダッシュ=オレンジ、ドット=青
- STARTノード: ツリーの根。ここからの探索パスを視覚的に追跡する
- nodeSpacing: ノード間隔の調整でツリー全体のレイアウトを制御
練習モード
文字を選択すると、音声が再生されると同時にツリー上のパスが順番にハイライトされる。
動作の流れ:
- 文字選択ボタンをクリック
- Web Audio APIでモールス音声を生成・再生
- 各符号の再生タイミングに合わせて
activeTraversalを更新 - STARTノードから終着点まで、エッジとノードが順次光る
- 非アクティブなエッジ/ノードはopacity 0.3でフェード
- 終着点ノードは通常より大きい円(r=26)で描画し、黒文字で強調
CSS transitionを使ったハイライトアニメーションで、符号の進行を目で追える。
フレーズ再生機能
単一文字だけでなく、定型フレーズをまとめて再生できる。
プリセットフレーズ:
- SOS — 遭難信号。
・・・ --- ・・・ - CQ CQ — 不特定多数への呼びかけ。アマチュア無線の交信開始時に使う
- 73 — 「よろしく」(Best regards)。無線通信の定番挨拶
父がアマチュア無線家で、コールサインはJA6OLU。子供の頃、無線機の前で「CQ CQ」と繰り返し呼びかけている姿をよく見ていた。このアプリでCQ CQのフレーズを再生すると、あの頃の光景を思い出す。
再生の仕組み:
- フレーズを文字に分解し、文字ごとにツリー探索を実行
- 文字間・単語間に適切な間隔を挿入
- ツリー上でリアルタイムにパスが追跡される
コクピットレイアウト(PC向け)
デスクトップ表示では情報を効率よく配置した。
- 左側: 練習モード(文字選択ボタン群 + コントロール)
- 右側: 二分木ツリー(SVG描画領域)
- 上部: コントロール群を横に並べる(光/音出力モード、速度調整、カテゴリ切替)
操作パネルとビジュアライゼーションを横に並べることで、ボタンを押してすぐにツリーの変化を確認できる。
OG画像の設定
/morseページにusePageOgSignatureを使ったOG画像URL生成を追加した。Worker側のgeneralハンドラーで動的生成されるため、Worker側の変更は不要だった。
技術的に工夫した点
React から Vue への移植
元のReact実装をVueに書き直す際、状態管理の違いに注意が必要だった。ReactのuseState + useEffectパターンを、Vueのref + watch/computedに置き換えていく作業が中心になる。
Web Audio API によるモールス音声生成
ブラウザのWeb Audio APIを使い、ダッシュとドットの音声をリアルタイムに生成している。外部の音声ファイルは不要で、OscillatorNodeで正弦波を鳴らす。ダッシュはドットの3倍の長さ、符号間の無音はドット1つ分、文字間はドット3つ分というモールスの国際規格に従った。
SVG描画のチューニング
ノード数が多い(アルファベット26文字 + 数字10文字 + 中間ノード)ため、nodeSpacingとノードサイズの調整に試行錯誤があった。深い階層ほどノードが密集するので、水平方向の間隔を階層ごとに計算している。
アニメーションのタイミング制御
符号の再生タイミングとツリー上のハイライトを同期させる部分が、実装上で最も手間がかかった。setTimeoutのチェーンで各符号の開始・終了タイミングを管理し、activeTraversal配列を順次更新していく。