LLM 不使用・ブラウザ完結の擬音生成エンジン — テンション駆動マルコフ連鎖の設計
APIコールゼロ。ブラウザ内で完結する日本語擬音生成エンジンのアーキテクチャ解説
Published: 2026-04-12
なぜ 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段階の優先度
生成ループの各ステップでは、以下の優先順位でアクションが決定されます。
- イベントトリガー: surprise、pain 等のイベントが発火した場合、対応クラスターを強制選択
- セリフ挿入: 予定されたセリフの位置に到達した場合、テンションに応じた変形を適用して挿入
- テンション変化トリガー: テンションの微分値が閾値を超えた場合、on_rise/on_fall タグを持つクラスターを選択
- 通常遷移: マルコフ遷移に従ってクラスターを選択し、テンションに応じたバリアントを出力
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 の不確実性の間のスイートスポットを実現しています。