LLM 不使用・ブラウザ完結の擬音生成エンジン — テンション駆動マルコフ連鎖の設計

APIコールゼロ。ブラウザ内で完結する日本語擬音生成エンジンのアーキテクチャ解説

Published: 2026-04-12

architectureonomatopoeiamarkov-chainbrowser

なぜ LLM を使わないのか

hakadoru.ai は LLM を活用した創作支援ツールですが、擬音生成にはあえて LLM を使用していません。その理由は明確です。

決定論性: 同じパラメータからは同じ結果が得られる必要があります。シード値による再現性は、作家が「あのパターンに戻したい」と思ったときに不可欠です。LLM の出力は本質的に確率的で、同じプロンプトから毎回異なる結果が返ります。

速度: 擬音生成はリアルタイムプレビューが求められます。パラメータ変更から 500ms 以内に結果を返す必要がありますが、LLM API のレイテンシはこの要件を満たしません。

コスト: 擬音はパラメータを変えながら何度も試行錯誤します。一回ごとに API 呼び出しが発生するのは、ユーザー体験の観点でもコストの観点でも非合理的です。

オフライン動作: プリセットデータさえキャッシュしていれば、ネットワーク接続なしで動作します。

アーキテクチャ概要

擬音エンジンのパイプラインは以下の4段階で構成されています。

1. タグパーサー

テキスト中の <ono> タグを解析し、構造化されたパラメータ(ドメイン、テンション開始/終了、持続時間、セリフ、イベント)を抽出します。SaaS 側ではシーン本文に埋め込まれたタグを解析しますが、スタンドアロンツールでは UI のパラメータから直接 OnoTag オブジェクトを構築します。

2. テンションカーブ(SigmoidCurve)

開始テンションから終了テンションへの遷移を、シグモイド関数で補間します。

シグモイド関数を採用した理由は、自然な「ゆるやかに始まり、急速に変化し、ゆるやかに収束する」という曲線が、物語のテンション変化のパターンに一致するためです。急峻さパラメータ(k=6)は、実験的に「物語的に自然」と感じられる値を採用しています。

テンション値は生成ループの各ステップで参照され、以下に影響します。

  • どのサウンドクラスターが活性化するか(tensionAffinity)
  • ポーズ(間)の長さと頻度(PAUSE_DISTRIBUTION)
  • セリフ変形のレベル(lineTransform)
  • マルコフ遷移の重み(tensionMin/tensionMax フィルタ)

3. マルコフリゾルバー(MarkovResolver)

擬音生成の核心部分です。サウンドクラスター間の遷移をタグベースのマルコフ連鎖で制御します。

サウンドクラスター

各ドメインのプリセットは複数のサウンドクラスターで構成されています。クラスターは「意味的に関連する音のグループ」です。例えば、銃撃戦ドメインの場合:

  • shot-single: 単発の銃声(ダン、バン、パン)
  • shot-burst: 連射(ダダダ、ダダダダ)
  • ricochet: 跳弾(キィン、カィン)
  • shell-casing: 薬莢(チャリ、カラン)
  • explosion: 爆発(ドオン、ズドン)

各クラスターには tensionAffinity(テンション親和性)が設定されており、min/peak/max の3点で活性化度が決まります。peak に近いテンションほど、そのクラスターが選択されやすくなります。

タグベース遷移

従来のマルコフ連鎖がステート ID 間の遷移確率を定義するのに対し、このエンジンはタグ(属性ラベル)間の遷移規則を使用します。

fromTags: ['impact']  →  toTags: ['metallic']  weight: 0.4
fromTags: ['staccato'] →  toTags: ['sustained'] weight: 0.3, tensionMin: 0.6

タグベースの利点は、新しいクラスターを追加したときに既存の遷移規則が自動的に適用される点です。新クラスターに適切なタグを付与するだけで、自然な遷移パターンに組み込まれます。

4段階の優先度

生成ループの各ステップでは、以下の優先順位でアクションが決定されます。

  1. イベントトリガー: surprise、pain 等のイベントが発火した場合、対応クラスターを強制選択
  2. セリフ挿入: 予定されたセリフの位置に到達した場合、テンションに応じた変形を適用して挿入
  3. テンション変化トリガー: テンションの微分値が閾値を超えた場合、on_rise/on_fall タグを持つクラスターを選択
  4. 通常遷移: マルコフ遷移に従ってクラスターを選択し、テンションに応じたバリアントを出力

4. レンダラー

生成されたセグメント列を最終テキストに変換します。レンダリング設定はプリセットごとに異なります。

  • defaultScript: ひらがな / カタカナ / 混合
  • exclamationMark: 感嘆符のスタイル(全角「!」)
  • longDash: ダッシュのスタイル(「——」)
  • pauseChars: 間の表現(「…」「……」改行)

環境音系ドメイン(銃撃、足音、崩壊)はカタカナ、感情系ドメイン(泣き声、鼓動)はひらがな、環境音(ambient)は混合がデフォルトです。この区分は、日本語の慣習(擬音語→カタカナ、擬態語→ひらがな)に基づいています。

セリフ変形(Line Transform)

セリフ変形は5段階で制御されます。テンション閾値を超えるごとに変形が深くなります。

レベル テンション 効果 例(「やめて」)
intact < 0.3 そのまま 「やめて」
split 0.3-0.5 分断 「やめ…て…」
phonetic 0.5-0.7 音韻崩壊 「やめ……て……」
truncated 0.7-0.85 切断 「やめ——」
dissolved > 0.85 溶解 断片が擬音に混入

閾値はプリセットごとに調整可能で、キャラクターの「理性が保てる限界」をプロファイルレベルで設定できます。

シード値による再現性

すべての乱数生成にシード付き疑似乱数生成器(SeededRng)を使用しています。同じシード・同じパラメータからは、常に同一の出力が得られます。

スタンドアロンツールの「別パターンで再生成」ボタンは、シード値を +1 するだけで実現しています。気に入ったパターンのシード値を記録しておけば、いつでもそのパターンに戻れます。

CDN 配信とキャッシュ戦略

プリセットデータはブランドグループ(BL / 一般小説 / 二次創作)ごとに JSON バンドルとして配信されます。

  • 配信パス: /api/presets/ono/{brand_key}.json
  • キャッシュ: Cache-Control: public, max-age=86400, s-maxage=86400, stale-while-revalidate=604800
  • サイズ: 約 20-40KB(gzip 後)

Vercel Edge Network によるグローバル CDN キャッシュにより、プリセットデータは最寄りのエッジノードから配信されます。24時間のキャッシュ TTL と7日間の stale-while-revalidate により、プリセット更新時もダウンタイムなしで配信が切り替わります。

パフォーマンス特性

  • 生成時間: 通常 5-50ms(セグメント数依存)、NFR 上限 500ms
  • バンドルロード: 初回フェッチ後はブラウザキャッシュから即座にロード
  • メモリ使用: プリセットレジストリは Map 構造、全プリセットで数百 KB 以下

まとめ

擬音生成は LLM が不得意な領域です。決定論性、速度、コスト、オフライン動作のすべてが、アルゴリズミックなアプローチを指し示しています。テンション駆動マルコフ連鎖とタグベース遷移の組み合わせにより、ルールベースの硬直性と LLM の不確実性の間のスイートスポットを実現しています。

LLM 不使用・ブラウザ完結の擬音生成エンジン — テンション駆動マルコフ連鎖の設計 — hakadoru.ai