# 機能設計書 43-再生位置同期

## 概要

本ドキュメントは、K-ShootMania のプレイ画面における音楽タイマーとゲームタイマーの同期補正機能の設計を記述する。BASS Audio Library の再生位置とHGIMG3エンジンの描画タイマーを連携させ、音楽と譜面表示のずれを最小限に抑える機能である。

### 本機能の処理概要

**業務上の目的・背景**：音楽ゲームでは音楽の再生位置と譜面の描画位置が正確に同期していることが最も重要な要件である。オーディオバッファリングやシステム負荷による微小なずれを補正し、プレイヤーの判定体験を損なわないようにするために本機能が必要である。

**機能の利用シーン**：プレイ画面の毎フレーム更新時に呼び出され、音楽再生位置（mtp）とゲーム内時間（t）を同期させる。特に、BPM変化、一時停止（STOP）、手動シフト操作時に正確な同期を維持する。

**主要な処理内容**：
1. BASS_ChannelGetPosition_msで現在の音楽再生位置（mtp）を取得する
2. 音楽再生位置が大幅に巻き戻った場合（stat1<mtp-500）のリピート検出とミュート処理
3. 手動シフト（mshift）が設定されている場合、BASS_ChannelSetPosition_msで全トラックの再生位置を変更
4. hggettime/hgsettimeによるHGIMG3タイマーとの同期補正（timingdif変数による滑らかな補正）
5. スクリーンショット出力モード（output=1）時は独自のタイマー計算を使用
6. 再生終了判定（mendt）と再生開始タイミング判定（playSongIfTimeReachesOffset）

**関連システム・外部連携**：BASS Audio Library（bass.dll）、HGIMG3（hgimg3）

**権限による制御**：特になし。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 5 | ゲームプレイ画面 | 主画面 | processPlayTimingによる音楽タイマーとゲームタイマーの同期補正 |

## 機能種別

リアルタイムタイミング制御処理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| mid | 整数配列 | Yes | BASSチャンネルハンドル配列 | - |
| t_mo | 整数 | Yes | 音楽オフセット時間（ms） | - |
| mshift | 整数 | No | 手動シフト値（ms） | 0以外の場合にシフト実行 |
| mstop | 整数 | No | 一時停止フラグ（1=停止中） | 0または1 |
| output | 整数 | No | スクリーンショット出力モード（1=出力中） | 0または1 |
| sync | 浮動小数点 | No | 判定タイミング補正値（ms） | - |
| disableadjtimer | 整数 | No | タイマー調整無効化フラグ | 0または1 |

### 入力データソース

- BASSオーディオエンジン: BASS_ChannelGetPosition_msからの再生位置
- HGIMG3エンジン: hggettimeからのゲームタイマー
- ゲームロジック: sync変数（判定補正値）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| t | 整数 | 現在のゲーム時間（ms） |
| rt | 整数 | 再生用の実時間（ms） |
| at | 整数 | 描画用の調整済み時間（ms） |
| mtp | 整数 | 音楽再生位置（ms） |
| cpos | 整数 | 再生カーソル位置（0-200） |
| t_tp | 整数 | 前フレームからの時間差（ms） |

### 出力先

- ゲームロジック各モジュールへの時間値供給

## 処理フロー

### 処理シーケンス

```
1. 音楽再生位置取得
   └─ BASS_ChannelGetPosition_ms(mid.0) で現在再生位置を取得
2. リピート検出
   └─ stat1 < mtp-500 の場合、全トラックをミュート（mrepeat=1）
3. 手動シフト処理
   └─ mshift != 0 の場合、全トラックの再生位置を変更
4. ゲーム時間計算
   └─ t = t_mo + stat1（基本時間）
5. タイマー同期補正
   └─ timingdif = (timingdif*7 + (t-at)) / 8 で滑らかに補正
6. STOP処理
   └─ mstop=1 の場合、t = t_mo + stoppos で固定
7. 再生終了判定
   └─ endtype/adjmodeに基づく終了条件判定
8. カーソル位置計算
   └─ cpos = 200 * t / mlength
9. エフェクト用タイマー差分計算
   └─ tfx_diff = d3timer()（エフェクト処理遅延補正用）
10. 判定補正適用
    └─ t += sync（判定タイミング補正値を加算）
```

### フローチャート

```mermaid
flowchart TD
    A[BASS_ChannelGetPosition_ms] --> B{stat1 < mtp-500?}
    B -->|Yes| C[mrepeat=1, 全トラックミュート]
    B -->|No| D[mtp=stat1]
    C --> D
    D --> E{mshift != 0?}
    E -->|Yes| F[全トラック再生位置変更]
    E -->|No| G[t = t_mo + stat1]
    F --> G
    G --> H{output=1?}
    H -->|Yes| I[スクリーンショット用タイマー]
    H -->|No| J[hggettime -> at]
    I --> K[t = at]
    J --> L{disableadjtimer=1?}
    L -->|Yes| M[hgsettime t]
    L -->|No| N[timingdif補正]
    K --> O[再生終了判定]
    M --> O
    N --> O
    O --> P[cpos = 200*t/mlength]
    P --> Q[t += sync]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-43-01 | リピート検出 | 再生位置が500ms以上巻き戻った場合、リピートとして全トラックミュート | stat1 < mtp - 500 かつ mstop=0 |
| BR-43-02 | タイミング差分補正 | timingdif = (timingdif*7 + diff) / 8 の加重平均で滑らかに補正 | disableadjtimer=0 かつ mstop=0 |
| BR-43-03 | STOP中時間固定 | STOP命令中はt = t_mo + stopposで時間を固定 | mstop=1 |
| BR-43-04 | トラック間同期検証 | 複数トラック間の再生位置差が100ms以上の場合、再調整フラグを立てる | fixPlaybackDelay関数 |

### 計算ロジック

- 基本時間: `t = t_mo + stat1`（t_mo: 音楽オフセット, stat1: BASS再生位置）
- タイミング差分補正: `timingdif = (timingdif * 7 + double(t - at)) / 8`
- 補正後時間: `t = at + int(round(timingdif))`
- カーソル位置: `cpos = 200 * t / mlength`

## データベース操作仕様

本機能ではデータベース操作は発生しない。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | リピート検出 | 再生位置が大幅に巻き戻った場合 | 全トラックミュート後、再生位置を再設定 |
| - | トラック間ズレ | 複数トラックの再生位置差が100ms超 | tadj=2フラグで再調整を通知 |

### リトライ仕様

該当なし。毎フレームの処理で自動的に補正が行われる。

## トランザクション仕様

該当なし。

## パフォーマンス要件

- 毎フレーム（約16ms@60fps）以内に処理完了すること
- timingdif補正により、急激な時間変化を避け滑らかな同期を実現すること

## セキュリティ考慮事項

特になし。

## 備考

- timingdifの初期値は-50000fに設定されており、最初のフレームで大きな差分を検出した場合にリセットされる（timingdif < -40000 の条件）
- d3timer()はHGIMG3の高精度タイマーを使用する
- mlength=0の場合、ゼロ除算防止のためmlength=1に補正される（70行目）

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | play_audio.hsp | `src/scene/play/play_audio.hsp` | processPlayTiming関数全体。t, rt, at, mtp, timingdif等のタイミング変数の関係を理解する |

**読解のコツ**: HSPの`hgsettime`/`hggettime`はHGIMG3エンジンのタイマー操作命令。`INT_MAX`は最大整数値で、タイマーのラップアラウンド防止に使用される。

#### Step 2: エントリーポイントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | play_audio.hsp | `src/scene/play/play_audio.hsp` | processPlayTiming関数（1-94行目） |

**主要処理フロー**:
1. **3行目**: `BASS_ChannelGetPosition_ms(mid.0)` で音楽再生位置を取得
2. **4-11行目**: リピート検出（stat1<mtp-500）でミュート処理
3. **14-28行目**: mshift!=0の場合の手動シフト処理
4. **30-31行目**: `t = t_mo + stat1`, `t2 = t` で基本時間を設定
5. **46-50行目**: timingdif補正ロジック
6. **51行目**: mstop=1の場合の時間固定処理

#### Step 3: 再生開始処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | play_audio.hsp | `src/scene/play/play_audio.hsp` | playSongIfTimeReachesOffset関数（96-138行目） |

**主要処理フロー**:
- **97行目**: `t_moex<=rt` で再生開始タイミング判定
- **100-113行目**: 再生開始位置の設定（開始遅延がある場合のオフセット計算）
- **120-128行目**: BASS_ChannelPlayによる実際の再生開始

#### Step 4: トラック間同期を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | play_audio.hsp | `src/scene/play/play_audio.hsp` | fixPlaybackDelay関数（140-148行目） |

**主要処理フロー**:
- **141行目**: mid.0の再生位置取得
- **142-147行目**: mid.1以降との再生位置差が100ms以上の場合、tadj=2を設定

### プログラム呼び出し階層図

```
on_update_play (毎フレーム)
    |
    +-- processPlayTiming
    |       +-- BASS_ChannelGetPosition_ms(mid.0) -> mtp
    |       +-- hggettime -> at
    |       +-- timingdif補正 -> t
    |
    +-- playSongIfTimeReachesOffset
    |       +-- rt >= t_moex で再生開始
    |       +-- BASS_ChannelPlay(mid.0)
    |
    +-- fixPlaybackDelay
            +-- トラック間再生位置差チェック
```

### データフロー図

```
[入力]                      [処理]                           [出力]

BASS再生位置                processPlayTiming                ゲーム時間変数
  mid.0 position -------->  t = t_mo + position              t (ゲーム時間)
                            timingdif補正                     rt (再生用時間)
HGIMG3タイマー              hggettime -> at                   at (描画用時間)
  d3timer() ------------->  timingdif平滑化                   cpos (カーソル位置)
                            t = at + round(timingdif)
sync変数 ----------------> t += sync                         t_tp (フレーム差分)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| play_audio.hsp | `src/scene/play/play_audio.hsp` | ソース | processPlayTiming, playSongIfTimeReachesOffset, fixPlaybackDelay関数 |
| play_init.hsp | `src/scene/play/play_init.hsp` | ソース | t_moex初期化、タイミング変数の初期設定 |
| play_game_logic.hsp | `src/scene/play/play_game_logic.hsp` | ソース | sync変数の計算、mstop/mshift制御 |
| play_key_input.hsp | `src/scene/play/play_key_input.hsp` | ソース | 一時停止時の再生位置制御 |
| bass.as | `src/bass.as` | ソース | BASS API バインディング |
