# model.py 設計書

## 概要

`model.py` は VRCT アプリケーションのビジネスロジックファサードとして機能し、音声認識、翻訳、オーバーレイ表示、OSC通信、WebSocket通信など、すべてのサブシステムへの統一されたインターフェースを提供する。シングルトンパターンで実装され、重い初期化処理を遅延実行することで、アプリケーションの起動時間を短縮している。

## アーキテクチャ上の位置づけ

```
┌─────────────┐
│controller.py│ (Business Logic Control Layer)
└──────┬──────┘
       │ Facade Pattern
┌──────▼──────┐
│  model.py   │ ◄── このファイル
└──────┬──────┘
       │ Aggregation & Delegation
┌──────▼────────────────────────────────┐
│ Subsystems                            │
│ - Translator                          │
│ - AudioTranscriber                    │
│ - Overlay / OverlayImage             │
│ - OSCHandler                          │
│ - WebSocketServer                     │
│ - Transliterator                      │
│ - Watchdog                            │
│ - DeviceManager (via device_manager)  │
└───────────────────────────────────────┘
```

## 主要コンポーネント

### 1. threadFnc クラス

**責務:** 関数を繰り返し実行するスレッドラッパー

**特徴:**
- デーモンスレッドとして動作
- ループ制御（停止・一時停止・再開）機能を提供
- 終了時のクリーンアップ関数をサポート

**メソッド:**

#### `__init__(fnc, end_fnc=None, daemon=True, *args, **kwargs)`

**パラメータ:**
- `fnc`: 繰り返し実行する関数
- `end_fnc`: スレッド終了時に実行する関数（オプション）
- `daemon`: デーモンフラグ（デフォルト: True）
- `*args, **kwargs`: `fnc` に渡す引数

#### `stop() -> None`
ループを停止し、スレッドを終了させる。

#### `pause() -> None`
ループを一時停止する（関数の実行を停止）。

#### `resume() -> None`
一時停止したループを再開する。

#### `run() -> None`
スレッドのメインループ。`self.loop` が True の間、`self.fnc()` を繰り返し呼び出す。

**使用例:**
```python
def print_message():
    print("Hello")
    sleep(1)

def cleanup():
    print("Thread ended")

th = threadFnc(print_message, end_fnc=cleanup)
th.start()
# ... しばらく実行 ...
th.stop()
th.join()
```

---

### 2. Model クラス

**責務:** アプリケーションのすべてのサブシステムへのファサードインターフェース

**パターン:** シングルトン（`__new__` で制御）

**初期化戦略:** 遅延初期化（Lazy Initialization）
- `__new__`: インスタンスの生成のみ（軽量）
- `init()`: 重い初期化処理（明示的な呼び出しが必要）
- `ensure_initialized()`: 初期化が必要なメソッドで自動的に呼び出される

---

### 3. 初期化メソッド

#### `__new__(cls) -> Model`

**責務:** シングルトンインスタンスの生成

**処理:**
1. `cls._instance` が None の場合のみ新規インスタンスを生成
2. `_inited` フラグを False に設定（実際の初期化は未実施）
3. 既存のインスタンスがあればそれを返却

**重要:** このメソッドでは重い初期化を行わない（import 時のパフォーマンス向上）

#### `init() -> None`

**責務:** すべてのサブシステムの初期化

**処理:**
1. **初期化済みチェック:** `_inited` フラグが True なら何もしない
2. **属性の初期化:**
   ```python
   self.logger = None
   self.mic_audio_queue = None
   self.mic_mute_status = None
   self.previous_send_message = ""
   self.previous_receive_message = ""
   ```
3. **サブシステムの初期化:**
   - `Translator()`: 翻訳エンジン
   - `KeywordProcessor()`: 禁止ワードフィルター
   - `Overlay()`: オーバーレイシステム
   - `OverlayImage()`: オーバーレイ画像生成
   - `Transliterator()`: 音訳（ひらがな・ローマ字変換）
   - `Watchdog()`: プロセス監視
   - `OSCHandler()`: OSC通信
   - `WebSocketServer()`: WebSocket通信
4. **コールバック関数の初期化:**
   ```python
   self.check_mic_energy_fnc: Callable[[float], None] = lambda v: None
   self.check_speaker_energy_fnc: Callable[[float], None] = lambda v: None
   ```
5. **初期化完了フラグ:** `_inited = True`

#### `ensure_initialized() -> None`

**責務:** 初期化が未実施の場合に `init()` を呼び出す

**使用箇所:** 初期化が必要なすべての public メソッド

**エラーハンドリング:**
```python
try:
    self.init()
except Exception:
    errorLogging()
```

---

### 4. 翻訳機能

#### モデルウェイト管理

##### `checkTranslatorCTranslate2ModelWeight(weight_type: str) -> bool`
指定されたモデルウェイトが存在するかチェック。

**パラメータ:**
- `weight_type`: "tiny", "small", "medium", "large" 等

**戻り値:** モデルが存在する場合 True

##### `downloadCTranslate2ModelWeight(weight_type, callback=None, end_callback=None) -> bool`

**責務:** CTranslate2 モデルウェイトのダウンロード

**パラメータ:**
- `weight_type`: モデルタイプ
- `callback`: 進捗通知用コールバック（`progress: float` を受け取る）
- `end_callback`: 完了時のコールバック

**実装:** `downloadCTranslate2Weight()` ユーティリティ関数に委譲

##### `downloadCTranslate2ModelTokenizer(weight_type) -> bool`
トークナイザーファイルのダウンロード。

#### 翻訳モデル制御

##### `changeTranslatorCTranslate2Model() -> None`

**責務:** 翻訳モデルの変更・再ロード

**処理:**
```python
self.translator.changeCTranslate2Model(
    path=config.PATH_LOCAL,
    model_type=config.CTRANSLATE2_WEIGHT_TYPE,
    device=config.SELECTED_TRANSLATION_COMPUTE_DEVICE["device"],
    device_index=config.SELECTED_TRANSLATION_COMPUTE_DEVICE["device_index"],
    compute_type=config.SELECTED_TRANSLATION_COMPUTE_TYPE
)
```

**VRAMエラー:** `ValueError("VRAM_OUT_OF_MEMORY")` を送出する可能性がある

##### `isLoadedCTranslate2Model() -> bool`
CTranslate2 モデルがロード済みかチェック。

##### `isChangedTranslatorParameters() -> bool`
翻訳パラメータが変更されたかチェック。

##### `setChangedTranslatorParameters(is_changed: bool) -> None`
翻訳パラメータ変更フラグを設定。

#### DeepL 認証

##### `authenticationTranslatorDeepLAuthKey(auth_key: str) -> bool`

**責務:** DeepL API キーの検証

**処理:** `translator.authenticationDeepLAuthKey()` に委譲

**戻り値:** 認証成功時 True

#### 翻訳実行

##### `getTranslate(translator_name, source_language, target_language, target_country, message) -> Tuple[str, bool]`

**責務:** メッセージの翻訳

**パラメータ:**
- `translator_name`: "CTranslate2", "DeepL", "DeepL_API" 等
- `source_language`: 元言語（"ja", "en" 等）
- `target_language`: 翻訳先言語
- `target_country`: 翻訳先国（方言対応用）
- `message`: 翻訳するテキスト

**戻り値:**
- `translation`: 翻訳結果（文字列）
- `success_flag`: 成功時 True

**エラーハンドリング:**
```python
translation = self.translator.translate(...)
if isinstance(translation, str):
    success_flag = True
else:
    # 翻訳失敗時のリトライロジック
    while True:
        # フェールセーフ処理
```

##### `getInputTranslate(message, source_language=None) -> Tuple[list, list]`

**責務:** 送信メッセージの翻訳（複数言語対応）

**処理:**
1. `config.SELECTED_TRANSLATION_ENGINES[config.SELECTED_TAB_NO]` で翻訳エンジンを取得
2. `config.SELECTED_TARGET_LANGUAGES` で翻訳先言語リストを取得
3. 有効な各言語について `getTranslate()` を呼び出し

**戻り値:**
- `translations`: 翻訳結果のリスト
- `success_flags`: 各翻訳の成功フラグのリスト

##### `getOutputTranslate(message, source_language=None) -> Tuple[list, list]`

**責務:** 受信メッセージの翻訳（単一言語）

**処理:** `getInputTranslate()` と同様だが、翻訳先が自分の言語（1つ）のみ

---

### 5. 音声認識機能

#### Whisper モデル管理

##### `checkTranscriptionWhisperModelWeight(weight_type: str) -> bool`
Whisper モデルウェイトの存在確認。

##### `downloadWhisperModelWeight(weight_type, callback=None, end_callback=None) -> bool`
Whisper モデルウェイトのダウンロード。

#### マイク音声認識

##### `startMicTranscript(fnc: Callable[[dict], None]) -> None`

**責務:** マイク音声認識の開始

**パラメータ:**
- `fnc`: 認識結果を受け取るコールバック関数

**処理フロー:**
1. **デバイス取得:**
   ```python
   mic_host_name = config.SELECTED_MIC_HOST
   mic_device_name = config.SELECTED_MIC_DEVICE
   mic_device_list = device_manager.getMicDevices().get(mic_host_name, [...])
   selected_mic_device = [device for device in mic_device_list if device["name"] == mic_device_name]
   ```
2. **デバイス検証:**
   - デバイスがない場合、`fnc({"text": False, "language": None})` を呼び出して終了
3. **音声キューの作成:**
   ```python
   self.mic_audio_queue = Queue()
   ```
4. **レコーダーの初期化:**
   ```python
   self.mic_audio_recorder = SelectedMicEnergyAndAudioRecorder(
       device=mic_device,
       energy_threshold=config.MIC_THRESHOLD,
       dynamic_energy_threshold=config.MIC_AUTOMATIC_THRESHOLD,
       phrase_time_limit=config.MIC_RECORD_TIMEOUT,
   )
   self.mic_audio_recorder.recordIntoQueue(self.mic_audio_queue, None)
   ```
5. **文字起こし器の初期化:**
   ```python
   self.mic_transcriber = AudioTranscriber(
       speaker=False,
       source=self.mic_audio_recorder.source,
       phrase_timeout=config.MIC_PHRASE_TIMEOUT,
       max_phrases=config.MIC_MAX_PHRASES,
       transcription_engine=config.SELECTED_TRANSCRIPTION_ENGINE,
       root=config.PATH_LOCAL,
       whisper_weight_type=config.WHISPER_WEIGHT_TYPE,
       device=config.SELECTED_TRANSCRIPTION_COMPUTE_DEVICE["device"],
       device_index=config.SELECTED_TRANSCRIPTION_COMPUTE_DEVICE["device_index"],
       compute_type=config.SELECTED_TRANSCRIPTION_COMPUTE_TYPE,
   )
   ```
6. **文字起こしスレッドの起動:**
   ```python
   def sendMicTranscript():
       # キューから音声データを取得
       # AudioTranscriber で文字起こし
       # fnc() で結果を送信
   
   def endMicTranscript():
       # クリーンアップ処理
   
   self.mic_print_transcript = threadFnc(sendMicTranscript, end_fnc=endMicTranscript)
   self.mic_print_transcript.start()
   ```
7. **ミュート状態の同期:**
   ```python
   self.changeMicTranscriptStatus()
   ```

##### `resumeMicTranscript() -> None`

**責務:** 一時停止したマイク音声認識の再開

**処理:**
1. 音声キューをクリア
2. レコーダーを再開: `self.mic_audio_recorder.resume()`

##### `pauseMicTranscript() -> None`

**責務:** マイク音声認識の一時停止

**処理:** `self.mic_audio_recorder.pause()`

##### `changeMicTranscriptStatus() -> None`

**責務:** VRChat のマイクミュート状態に応じて音声認識を制御

**処理:**
```python
if config.VRC_MIC_MUTE_SYNC is True:
    match self.mic_mute_status:
        case True:
            self.pauseMicTranscript()
        case False:
            self.resumeMicTranscript()
        case None:
            self.resumeMicTranscript()  # 不明な場合は一時停止しない
else:
    self.resumeMicTranscript()
```

##### `stopMicTranscript() -> None`

**責務:** マイク音声認識の停止とリソース解放

**処理:**
1. 文字起こしスレッドの停止
2. レコーダーの再開（一時停止中の場合）と停止
3. インスタンスの破棄

**VRAMエラー検出:**

##### `detectVRAMError(error: Exception) -> Tuple[bool, Optional[str]]`

**責務:** VRAM不足エラーの検出

**処理:**
```python
error_str = str(error)
if isinstance(error, ValueError) and len(error.args) > 0 and error.args[0] == "VRAM_OUT_OF_MEMORY":
    return True, error_str
if "CUDA out of memory" in error_str or "CUBLAS_STATUS_ALLOC_FAILED" in error_str:
    return True, error_str
return False, None
```

**使用箇所:**
- 翻訳実行時
- 音声認識開始時

#### スピーカー音声認識

以下のメソッドはマイク音声認識と同様の構造:
- `startSpeakerTranscript(fnc)`
- `stopSpeakerTranscript()`

**相違点:**
- `speaker=True` で AudioTranscriber を初期化
- `SelectedSpeakerEnergyAndAudioRecorder` を使用

#### エネルギーレベル監視

##### `startCheckMicEnergy(fnc: Optional[Callable[[float], None]] = None) -> None`

**責務:** マイクの音量レベル監視の開始

**処理:**
1. コールバック関数を設定: `self.check_mic_energy_fnc = fnc`
2. マイクデバイスを取得
3. エネルギーレコーダーを初期化:
   ```python
   mic_energy_queue = Queue()
   self.mic_energy_recorder = SelectedMicEnergyRecorder(mic_device)
   self.mic_energy_recorder.recordIntoQueue(mic_energy_queue)
   ```
4. エネルギー送信スレッドを起動:
   ```python
   def sendMicEnergy():
       if not mic_energy_queue.empty():
           energy = mic_energy_queue.get()
           self.check_mic_energy_fnc(energy)
       sleep(0.01)
   
   self.mic_energy_plot_progressbar = threadFnc(sendMicEnergy)
   self.mic_energy_plot_progressbar.start()
   ```

##### `stopCheckMicEnergy() -> None`
エネルギー監視の停止とリソース解放。

**対応するスピーカー用メソッド:**
- `startCheckSpeakerEnergy(fnc)`
- `stopCheckSpeakerEnergy()`

---

### 6. オーバーレイ機能

#### 画像生成

##### `createOverlayImageSmallLog(message, your_language, translation, target_language) -> object`

**責務:** 小さなログウィンドウ用の画像生成

**パラメータ:**
- `message`: 元のメッセージ（オプション）
- `your_language`: 元の言語（オプション）
- `translation`: 翻訳結果のリスト
- `target_language`: 翻訳先言語の辞書（オプション）

**処理:**
```python
target_language_list = []
if isinstance(target_language, dict):
    target_language_list = list(target_language.values())
return self.overlay_image.createOverlayImageSmallLog(
    message, your_language, translation, target_language_list
)
```

##### `createOverlayImageSmallMessage(message: str) -> object`

**責務:** 小さなメッセージウィンドウ用の画像生成（単一言語）

**処理:**
```python
ui_language = config.UI_LANGUAGE
convert_languages = {
    "en": "Default",
    "jp": "Japanese",
    "ko": "Korean",
    "zh-Hans": "Chinese Simplified",
    "zh-Hant": "Chinese Traditional",
}
language = convert_languages.get(ui_language, "Default")
return self.overlay_image.createOverlayImageSmallLog(message, language)
```

##### `createOverlayImageLargeLog(message_type, message, your_language, translation, target_language=None) -> object`

**責務:** 大きなログウィンドウ用の画像生成

**パラメータ:**
- `message_type`: "send" または "received"

**処理:** `createOverlayImageSmallLog()` と同様

##### `createOverlayImageLargeMessage(message: str) -> object`

**責務:** 大きなメッセージウィンドウ用の画像生成

**特殊処理:**
```python
overlay_image = OverlayImage(config.PATH_LOCAL)
for _ in range(2):
    # 2回繰り返して画像を生成（理由は不明、バグ修正のため？）
    overlay_image.createOverlayImageLargeLog("send", message, language)
return overlay_image.createOverlayImageLargeLog("send", message, language)
```

#### 表示制御

##### `clearOverlayImageSmallLog() -> None`
小さなログウィンドウをクリア。

##### `updateOverlaySmallLog(img: object) -> None`
小さなログウィンドウの画像を更新。

##### `updateOverlaySmallLogSettings() -> None`

**責務:** 小さなログウィンドウの設定更新

**処理:** 設定の変更を検出し、オーバーレイに反映:
```python
size = "small"
if (self.overlay.settings[size]["x_pos"] != config.OVERLAY_SMALL_LOG_SETTINGS["x_pos"] or
    # ... 他の設定項目 ...):
    self.overlay.updateSettings(config.OVERLAY_SMALL_LOG_SETTINGS, size)
```

**設定項目:**
- 位置（x_pos, y_pos, z_pos）
- 回転（x_rotation, y_rotation, z_rotation）
- トラッカー（tracker）
- 表示時間（display_duration）
- フェードアウト時間（fadeout_duration）
- 透明度（opacity）
- UIスケーリング（ui_scaling）

##### `clearOverlayImageLargeLog() -> None`
大きなログウィンドウをクリア。

##### `updateOverlayLargeLog(img: object) -> None`
大きなログウィンドウの画像を更新。

##### `updateOverlayLargeLogSettings() -> None`
大きなログウィンドウの設定更新（`updateOverlaySmallLogSettings()` と同様）。

#### オーバーレイシステム制御

##### `startOverlay() -> None`
オーバーレイシステムを起動（OpenVR の初期化）。

##### `shutdownOverlay() -> None`
オーバーレイシステムを終了（リソース解放）。

---

### 7. OSC 通信機能

#### 設定

##### `setOscIpAddress(ip_address: str) -> None`
VRChat への送信先 IP アドレスを設定。

##### `setOscPort(port: int) -> None`
OSC ポート番号を設定。

#### メッセージ送信

##### `oscStartSendTyping() -> None`
タイピング中の通知を送信（VRChat のチャットボックスにインジケーターが表示される）。

##### `oscStopSendTyping() -> None`
タイピング終了の通知を送信。

##### `oscSendMessage(message: str) -> None`

**責務:** VRChat へメッセージを送信

**パラメータ:**
- `message`: 送信するテキスト

**処理:**
```python
self.osc_handler.sendMessage(
    message=message,
    notification=config.NOTIFICATION_VRC_SFX
)
```

#### OSC 受信

##### `setMuteSelfStatus() -> None`
VRChat の現在のマイクミュート状態を取得。

##### `startReceiveOSC() -> None`

**責務:** OSC パラメータの受信開始

**処理:**
```python
def changeHandlerMute(address, osc_arguments):
    if config.ENABLE_TRANSCRIPTION_SEND is True:
        self.mic_mute_status = osc_arguments[0]
        self.changeMicTranscriptStatus()

dict_filter_and_target = {
    self.osc_handler.osc_parameter_muteself: changeHandlerMute,
}
self.osc_handler.setDictFilterAndTarget(dict_filter_and_target)
self.osc_handler.receiveOscParameters()
```

**監視パラメータ:**
- `/avatar/parameters/MuteSelf`: マイクミュート状態

##### `stopReceiveOSC() -> None`
OSC 受信を停止。

##### `getIsOscQueryEnabled() -> bool`
OSC Query 機能が有効かチェック。

---

### 8. 音訳機能

#### 音訳システム制御

##### `startTransliteration() -> None`
音訳システムを起動（`Transliterator` インスタンスを生成）。

##### `stopTransliteration() -> None`
音訳システムを停止（インスタンスを破棄）。

#### 音訳実行

##### `convertMessageToTransliteration(message, hiragana=True, romaji=True) -> list`

**責務:** メッセージをひらがな・ローマ字に変換

**パラメータ:**
- `message`: 変換するテキスト
- `hiragana`: ひらがなを含める
- `romaji`: ローマ字を含める

**処理:**
```python
if hiragana is False and romaji is False:
    return []

keys_to_keep = {"orig"}
if hiragana:
    keys_to_keep.add("hira")
if romaji:
    keys_to_keep.add("hepburn")

if self.transliterator is None:
    self.startTransliteration()

data_list = self.transliterator.analyze(message, use_macron=False)
filtered_list = [
    {key: value for key, value in item.items() if key in keys_to_keep}
    for item in data_list
]
return filtered_list
```

**戻り値の例:**
```python
[
    {"orig": "こんにちは", "hira": "こんにちは", "hepburn": "konnichiwa"},
    {"orig": "世界", "hira": "せかい", "hepburn": "sekai"}
]
```

---

### 9. キーワードフィルター

#### フィルター管理

##### `resetKeywordProcessor() -> None`
キーワードプロセッサをリセット（すべてのキーワードを削除）。

##### `addKeywords() -> None`
禁止ワードをキーワードプロセッサに追加。

**処理:**
```python
for f in config.MIC_WORD_FILTER:
    self.keyword_processor.add_keyword(f)
```

#### フィルタリング

##### `checkKeywords(message: str) -> bool`
メッセージに禁止ワードが含まれているかチェック。

**戻り値:** 禁止ワードが含まれている場合 True

**実装:**
```python
return len(self.keyword_processor.extract_keywords(message)) != 0
```

---

### 10. 重複検出

##### `detectRepeatSendMessage(message: str) -> bool`

**責務:** 送信メッセージの重複検出

**処理:**
```python
repeat_flag = False
if self.previous_send_message == message:
    repeat_flag = True
self.previous_send_message = message
return repeat_flag
```

##### `detectRepeatReceiveMessage(message: str) -> bool`
受信メッセージの重複検出（`detectRepeatSendMessage()` と同様）。

---

### 11. デバイス管理

#### マイクデバイス

##### `getListMicHost() -> list`

**責務:** マイクホストのリスト取得

**戻り値:** ["MME", "WASAPI", ...] 等

**処理:**
```python
try:
    dm = device_manager.getMicDevices()
    result = [host for host in dm.keys()]
except Exception:
    errorLogging()
    result = []
return result
```

##### `getMicDefaultDevice() -> str`
選択されたホストのデフォルトマイクデバイス名を取得。

##### `getListMicDevice() -> list`
選択されたホストのマイクデバイス一覧を取得。

#### スピーカーデバイス

##### `getListSpeakerDevice() -> list`
スピーカーデバイス一覧を取得。

**処理:**
```python
try:
    sd = device_manager.getSpeakerDevices()
    result = [device["name"] for device in sd]
except Exception:
    errorLogging()
    result = ["NoDevice"]
return result
```

---

### 12. 言語管理

##### `getListLanguageAndCountry() -> list`

**責務:** 音声認識と翻訳の両方をサポートする言語・国のリスト取得

**処理:**
1. `transcription_lang` から音声認識サポート言語を取得
2. `translation_lang` から翻訳サポート言語を取得
3. 両方でサポートされている言語を抽出
4. 各言語の国バリエーションを列挙

**戻り値の例:**
```python
[
    {"language": "en", "country": "US"},
    {"language": "en", "country": "UK"},
    {"language": "ja", "country": "JP"},
    # ...
]
```

##### `findTranslationEngines(source_lang, target_lang, engines_status) -> list`

**責務:** 指定された言語ペアをサポートする翻訳エンジンの検索

**パラメータ:**
- `source_lang`: 元言語の辞書（複数の言語が有効化されている可能性）
- `target_lang`: 翻訳先言語の辞書
- `engines_status`: 各エンジンの有効/無効状態

**処理:**
```python
selectable_engines = [key for key, value in engines_status.items() if value is True]
compatible_engines = []
for engine in list(translation_lang.keys()):
    languages = translation_lang.get(engine, {}).get("source", {})
    source_langs = [e["language"] for e in list(source_lang.values()) if e["enable"] is True]
    target_langs = [e["language"] for e in list(target_lang.values()) if e["enable"] is True]
    language_list = list(languages.keys())

    if all(e in language_list for e in source_langs) and all(e in language_list for e in target_langs):
        if engine in selectable_engines:
            compatible_engines.append(engine)

return compatible_engines
```

---

### 13. ロギング

##### `startLogger() -> None`

**責務:** ファイルロギングの開始

**処理:**
```python
os_makedirs(config.PATH_LOGS, exist_ok=True)
file_name = os_path.join(config.PATH_LOGS, f"{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.log")
self.logger = setupLogger("log", file_name)
self.logger.disabled = False
```

**ログファイル名の例:** `2023-10-13_15-30-45.log`

##### `stopLogger() -> None`
ファイルロギングの停止。

---

### 14. ソフトウェアアップデート

##### `checkSoftwareUpdated() -> dict`

**責務:** 最新バージョンの確認

**処理:**
```python
update_flag = False
version = ""
try:
    # GitHub API 等から最新バージョン情報を取得
    # packaging.version.parse でバージョン比較
except Exception:
    errorLogging()
return {
    "is_update_available": update_flag,
    "new_version": version,
}
```

##### `updateSoftware() -> None`

**責務:** 通常版のアップデート実行

**処理:**
1. アップデーターをダウンロード（最大5回リトライ）
2. `Popen()` でアップデーターを起動
3. 現在のプロセスを終了

##### `updateCudaSoftware() -> None`
CUDA版のアップデート実行（`--cuda` オプション付きでアップデーターを起動）。

---

### 15. Watchdog 機能

##### `startWatchdog() -> None`

**責務:** Watchdog 監視スレッドの起動

**処理:**
```python
self.th_watchdog = threadFnc(self.watchdog.start)
self.th_watchdog.daemon = True
self.th_watchdog.start()
```

##### `feedWatchdog() -> None`
Watchdog にハートビート信号を送信（タイムアウトをリセット）。

##### `setWatchdogCallback(callback: Callable) -> None`
Watchdog タイムアウト時のコールバック関数を設定。

##### `stopWatchdog() -> None`
Watchdog を停止し、スレッドの終了を待機。

---

### 16. WebSocket サーバー

#### サーバー制御

##### `startWebSocketServer(host: str, port: int) -> None`

**責務:** WebSocket サーバーの起動

**処理:**
1. 既に起動中なら何もしない
2. `websocket_server_loop = True` に設定
3. 別スレッドで asyncio イベントループを実行:
   ```python
   async def WebSocketServerMain():
       self.websocket_server = WebSocketServer(host, port)
       self.websocket_server_alive = True
       await self.websocket_server.start()
       # ループ終了まで待機
       self.websocket_server_alive = False
   
   self.th_websocket_server = Thread(target=lambda: asyncio.run(WebSocketServerMain()))
   self.th_websocket_server.daemon = True
   self.th_websocket_server.start()
   ```

##### `stopWebSocketServer() -> None`

**責務:** WebSocket サーバーの停止

**処理:**
1. `websocket_server_loop = False` に設定
2. サーバーの停止を要求
3. スレッドの終了を待機（タイムアウト付き）

**エラーハンドリング:**
```python
try:
    # サーバー停止処理
except Exception:
    errorLogging()
finally:
    self.th_websocket_server = None
    self.websocket_server = None
    self.websocket_server_alive = False
```

##### `checkWebSocketServerAlive() -> bool`
WebSocket サーバーの稼働状態を確認。

#### メッセージ送信

##### `websocketSendMessage(message_dict: dict) -> bool`

**責務:** すべての接続クライアントにメッセージをブロードキャスト

**パラメータ:**
- `message_dict`: 送信する辞書（JSON にシリアライズされる）

**処理:**
```python
if not self.websocket_server_alive or not self.websocket_server:
    return False
try:
    self.websocket_server.broadcast(message_dict)
    return True
except Exception:
    errorLogging()
    return False
```

---

## 依存関係

### 外部ライブラリ

```python
from subprocess import Popen
from os import makedirs as os_makedirs
from os import path as os_path
from datetime import datetime
from time import sleep
from queue import Queue
from threading import Thread
from requests import get as requests_get
from typing import Callable, Optional, cast
from packaging.version import parse
from flashtext import KeywordProcessor
```

### 内部モジュール

```python
from device_manager import device_manager
from config import config
from models.translation.translation_translator import Translator
from models.osc.osc import OSCHandler
from models.transcription.transcription_recorder import SelectedMicEnergyAndAudioRecorder, SelectedSpeakerEnergyAndAudioRecorder
from models.transcription.transcription_recorder import SelectedMicEnergyRecorder, SelectedSpeakerEnergyRecorder
from models.transcription.transcription_transcriber import AudioTranscriber
from models.translation.translation_languages import translation_lang
from models.transcription.transcription_languages import transcription_lang
from models.translation.translation_utils import checkCTranslate2Weight, downloadCTranslate2Weight, downloadCTranslate2Tokenizer
from models.transcription.transcription_whisper import checkWhisperWeight, downloadWhisperWeight
from models.transliteration.transliteration_transliterator import Transliterator
from models.overlay.overlay import Overlay
from models.overlay.overlay_image import OverlayImage
from models.watchdog.watchdog import Watchdog
from models.websocket.websocket_server import WebSocketServer
from utils import errorLogging, setupLogger
```

---

## スレッド構成

### メインスレッド
- アプリケーションのメインループ（`mainloop.py` が管理）

### Model 管理のスレッド

#### 音声認識スレッド
- `mic_print_transcript`: マイク音声認識結果の処理
- `speaker_print_transcript`: スピーカー音声認識結果の処理

#### エネルギー監視スレッド
- `mic_energy_plot_progressbar`: マイクの音量レベル監視
- `speaker_energy_plot_progressbar`: スピーカーの音量レベル監視

#### その他のスレッド
- `th_watchdog`: Watchdog 監視
- `th_websocket_server`: WebSocket サーバー（asyncio イベントループ）

### サブシステム管理のスレッド
- `device_manager.th_monitoring`: デバイス変更監視
- `mic_audio_recorder.th_record`: マイク音声録音
- `speaker_audio_recorder.th_record`: スピーカー音声録音
- `osc_handler.th_receive`: OSC パラメータ受信

---

## エラーハンドリング

### VRAM不足エラー

**検出:**
```python
is_vram_error, error_message = self.detectVRAMError(e)
```

**対応:**
1. エラーを `ValueError("VRAM_OUT_OF_MEMORY")` として送出
2. Controller 側でキャッチして機能を無効化
3. ユーザーに通知

### デバイスアクセスエラー

**検出:**
- デバイスが見つからない場合: `NoDevice`
- アクセス失敗時: コールバックに `False` を渡す

**対応:**
1. エラーをログに記録
2. Controller に通知
3. 処理を継続（他の機能に影響なし）

### ネットワークエラー

**検出:**
- 翻訳API呼び出し失敗
- モデルウェイトダウンロード失敗

**対応:**
1. リトライロジック（翻訳の場合）
2. フォールバック（CTranslate2 への切り替え）
3. エラー通知

---

## パフォーマンス最適化

### 1. 遅延初期化

重い初期化処理を `init()` に分離し、必要になるまで実行しない。

**利点:**
- アプリケーションの起動時間を短縮
- 未使用の機能のリソースを消費しない

### 2. シングルトンパターン

Model クラスはアプリケーション全体で1つのインスタンスのみ存在。

**利点:**
- メモリ使用量の削減
- 状態の一貫性

### 3. スレッドによる並列処理

音声認識、エネルギー監視、WebSocket サーバーなど、ブロッキング処理を別スレッドで実行。

**利点:**
- UI のレスポンス性向上
- 複数機能の同時実行

---

## テストシナリオ

### 1. 初期化テスト

**ケース:**
- 初回初期化
- 既に初期化済みの場合
- 初期化失敗時

**確認項目:**
- `_inited` フラグが正しく設定されているか
- すべてのサブシステムが初期化されているか
- エラーが適切にログされているか

### 2. 音声認識テスト

**ケース:**
- デバイスがない場合
- 音声認識開始・停止・一時停止・再開
- VRAMエラーの発生

**確認項目:**
- コールバックが正しく呼び出されているか
- スレッドが適切に管理されているか
- エラーが検出されているか

### 3. 翻訳テスト

**ケース:**
- 単一言語翻訳
- 複数言語翻訳
- 翻訳エンジンの切り替え
- API エラー

**確認項目:**
- 翻訳結果が正しいか
- エラー時のフォールバックが動作するか

### 4. オーバーレイテスト

**ケース:**
- 画像生成
- 設定更新
- オーバーレイの起動・停止

**確認項目:**
- 画像が正しく生成されるか
- 設定変更が反映されるか

---

## 制限事項

### 1. シングルトンの制約

**問題:** テストやマルチインスタンスが困難

**影響:**
- ユニットテストでモックが難しい
- 複数の VRChat インスタンスへの対応が不可能

### 2. グローバル状態依存

**問題:** `config` モジュールへの強い依存

**影響:**
- テスタビリティの低下
- 設定変更の追跡が困難

### 3. エラーハンドリングの不完全性

**問題:** 一部のエラーは握りつぶされる

**影響:**
- デバッグが困難
- ユーザーへの適切なエラー通知が不足

### 4. スレッドの管理複雑性

**問題:** 多数のスレッドとその状態管理

**影響:**
- デッドロックのリスク
- リソースリークの可能性

---

## 今後の改善案

### 1. 依存性注入（DI）の導入

```python
class Model:
    def __init__(self, config, device_manager, translator, ...):
        self.config = config
        self.device_manager = device_manager
        self.translator = translator
        # ...
```

**利点:**
- テスタビリティの向上
- モジュール間の疎結合

### 2. 非同期化（asyncio）

```python
async def startMicTranscript(self, callback):
    async for result in self.mic_transcriber.transcribe():
        await callback(result)
```

**利点:**
- スレッド管理の簡素化
- パフォーマンスの向上

### 3. イベント駆動アーキテクチャ

```python
class Model:
    def __init__(self):
        self.event_bus = EventBus()
    
    def on_transcription_result(self, result):
        self.event_bus.emit("transcription_result", result)
```

**利点:**
- モジュール間の疎結合
- 拡張性の向上

### 4. エラーハンドリングの統一

```python
class ModelError(Exception):
    pass

class VRAMError(ModelError):
    pass

class DeviceError(ModelError):
    pass
```

**利点:**
- エラーの分類と処理の統一
- エラー情報の追跡

---

## 関連ファイル

- **controller.py** - ビジネスロジック制御レイヤー
- **config.py** - 設定管理
- **device_manager.py** - デバイス監視・自動選択
- **mainloop.py** - 通信レイヤー
- **utils.py** - ログとユーティリティ関数
- **models/** - サブシステムの実装

---

## まとめ

`model.py` は VRCT のすべてのサブシステムへの統一されたファサードインターフェースを提供し、音声認識、翻訳、オーバーレイ、OSC通信、WebSocket通信など、複雑な機能を簡潔なAPIで公開する。シングルトンパターンと遅延初期化により、リソースの効率的な利用を実現している。スレッドを活用した並列処理により、複数の機能を同時に実行しながらUIのレスポンス性を維持している。VRAMエラーやデバイスエラーに対する適切なハンドリングにより、ユーザーエクスペリエンスを向上させている。
