> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# .vvproj 檔案規格 - VOICEVOX for Laravel

> 整理讀取 VOICEVOX 編輯器 .vvproj JSON，並在 Laravel 中生成 Talk／Song 的實務規格。

## 目的

直接運用 VOICEVOX 編輯器的 `.vvproj`，在 Laravel 中重新生成 Talk 與 Song。

## 頂層結構

`.vvproj` 為 UTF-8 JSON。Talk 與 Song 儲存在同一個檔案中。

```json theme={null}
{
  "appVersion": "0.25.2",
  "talk": {
    "audioKeys": [],
    "audioItems": {}
  },
  "song": {
    "tpqn": 480,
    "tempos": [],
    "timeSignatures": [],
    "tracks": {},
    "trackOrder": []
  }
}
```

| 鍵            | 內容                                    |
| ------------ | ------------------------------------- |
| `appVersion` | 儲存時的編輯器版本                             |
| `talk`       | Talk 用資料 (`audioKeys` + `audioItems`) |
| `song`       | Song 用資料 (節奏、拍號、軌道群)                  |

## `talk` 區段

`talk.audioKeys` 是順序陣列，`talk.audioItems` 是以 ID 為鍵的 Record。

```json theme={null}
{
  "audioKeys": ["audio-item-uuid"],
  "audioItems": {
    "audio-item-uuid": {
      "text": "ずんだもんなのだ",
      "voice": {
        "engineId": "engine-uuid",
        "speakerId": "speaker-uuid",
        "styleId": 3
      },
      "query": {
        "accentPhrases": [],
        "speedScale": 1,
        "pitchScale": 0
      },
      "presetKey": "preset-uuid"
    }
  }
}
```

### TalkAudioItem

| 鍵                 | 內容                                                                                |
| ----------------- | --------------------------------------------------------------------------------- |
| `text`            | 輸入文字                                                                              |
| `voice.engineId`  | 引擎 ID（對應 `/engine_manifest`）                                                      |
| `voice.speakerId` | 話者 UUID                                                                           |
| `voice.styleId`   | 樣式 ID。直接傳給 `/synthesis?speaker={styleId}`                                         |
| `query`           | 相當於 `AudioQuery`                                                                  |
| `presetKey`       | 編輯器的預設集 ID。Laravel 側的預設集請參閱[預設集](/zh-TW/packages/laravel-voicevox/native-presets) |

如果 `.vvproj` 中已經儲存 `query`，就無需再執行 `/audio_query`，可直接進入合成階段。

### `accentPhrases`

| 鍵                 | 內容                              |
| ----------------- | ------------------------------- |
| `moras`           | 音節（mora）陣列（`consonant` 系有時會被省略） |
| `accent`          | 重音位置（從 1 開始）                    |
| `pauseMora`       | 在標點等處插入無聲時可能會附加                 |
| `isInterrogative` | 疑問句旗標                           |

## `song` 區段

```json theme={null}
{
  "tpqn": 480,
  "tempos": [{ "position": 0, "bpm": 120 }],
  "timeSignatures": [{ "measureNumber": 1, "beats": 4, "beatType": 4 }],
  "tracks": {
    "track-uuid": {
      "name": "無名トラック",
      "singer": { "engineId": "engine-uuid", "styleId": 3003 },
      "notes": []
    }
  },
  "trackOrder": ["track-uuid"]
}
```

| 鍵                | 內容                             |
| ---------------- | ------------------------------ |
| `tpqn`           | Ticks Per Quarter Note。標準為 480 |
| `tempos`         | 節奏對照表（`position` 為 tick）       |
| `timeSignatures` | 拍號對照表（`measureNumber` 從 1 開始）  |
| `tracks`         | 以 Track ID 為鍵的 Record          |
| `trackOrder`     | 顯示與播放順序。需與 `tracks` 的鍵集合一致     |

### Track

| 鍵                                  | 內容                                             |
| ---------------------------------- | ---------------------------------------------- |
| `singer.styleId`                   | 最終傳給 `/frame_synthesis?speaker={styleId}` 的 ID |
| `notes`                            | 音符陣列                                           |
| `keyRangeAdjustment`               | 半音單位的音高調整                                      |
| `volumeRangeAdjustment`            | 音量調整                                           |
| `pitchEditData` / `volumeEditData` | 影格單位的編輯資料                                      |
| `phonemeTimingEditData`            | 依 Note ID 的音素時間點編輯                             |
| `solo` / `mute`                    | 用來判定播放對象軌道                                     |

### Note

| 鍵            | 內容        |
| ------------ | --------- |
| `id`         | 音符 ID（唯一） |
| `position`   | 音符起始 tick |
| `duration`   | 音符長度 tick |
| `noteNumber` | MIDI 音符編號 |
| `lyric`      | 歌詞        |

## tick、秒、影格轉換

單一節奏時的轉換如下：

```text theme={null}
seconds = ticks / tpqn * 60 / bpm
frames = round(seconds * frameRate)
```

若有節奏變化，則依 `tempos` 以 `position` 遞增順序逐區間累加。

```php theme={null}
function ticksToSeconds(int $targetTick, int $tpqn, array $tempos): float
{
    $seconds = 0.0;
    $currentTick = 0;

    foreach ($tempos as $index => $tempo) {
        $nextTick = $tempos[$index + 1]['position'] ?? $targetTick;
        $segmentEnd = min($targetTick, $nextTick);

        if ($segmentEnd <= $currentTick) {
            break;
        }

        $bpm = $tempo['bpm'];
        $seconds += (($segmentEnd - $currentTick) / $tpqn) * (60 / $bpm);
        $currentTick = $segmentEnd;
    }

    return $seconds;
}
```

利用 `Note::len()` 輔助函式從 tick 轉換為影格長度的實作，請參閱 [Score 與 Note 詳細](/zh-TW/packages/laravel-voicevox/song-score-note)。

## Song 音訊生成流程

```mermaid theme={null}
flowchart TD
  A["1. 依 trackOrder 順序取出 tracks"] --> B["2. 以 solo/mute 決定生成對象"]
  B --> C["3. 將 notes 依 position 遞增排序"]
  C --> D["4. 在首尾加入休止符並轉為 Score"]
  D --> E["5. 以 tick→秒→frame 建立 frame_length"]
  E --> F["6. sing_frame_audio_query→sing_frame_f0→sing_frame_volume"]
  F --> G["7. 以 frame_synthesis 生成 WAV"]
```

## 直接編輯時的注意事項

* `tracks` 的鍵集合必須與 `trackOrder` 一致
* `talk.audioKeys` 與 `talk.audioItems` 亦需同步
* 維持 `position >= 0`、`duration >= 1`、`noteNumber` 在 `0..127` 之間
* `tempos[0].position` 通常為 `0`，`timeSignatures[0].measureNumber` 通常為 `1`
* 未知的鍵盡量保留再儲存，以免破壞與未來版本的相容性

## Laravel 程式碼範例

讀取 `.vvproj` 並生成 Talk 與 Song 的最小範例。

```php theme={null}
use Illuminate\Support\Facades\Storage;
use Revolution\Voicevox\Client\TalkAudioQuery;
use Revolution\Voicevox\Song\Note;
use Revolution\Voicevox\Song\Score;
use Revolution\Voicevox\Voicevox;

$project = json_decode(
    Storage::disk('local')->get('voicevox/sample.vvproj'),
    true,
    flags: JSON_THROW_ON_ERROR,
);

// Talk：直接使用已儲存的 query 合成
foreach ($project['talk']['audioKeys'] as $audioKey) {
    $item = $project['talk']['audioItems'][$audioKey];

    Voicevox::talk($item['text'], id: $item['voice']['styleId'])
        ->tap(fn (TalkAudioQuery $talk) => $talk->audioQuery = array_replace($talk->audioQuery, $item['query']))
        ->generate(id: $item['voice']['styleId'])
        ->storeAs('vvproj/talk', "{$audioKey}.wav");
}

// Song：此處將第一個軌道的 duration 轉換為 frame_length 進行生成
$trackId = $project['song']['trackOrder'][0];
$track = $project['song']['tracks'][$trackId];
$bpm = $project['song']['tempos'][0]['bpm'] ?? 120;

$score = Score::make([
    Note::make(length: 15, lyric: '', key: null),
    ...collect($track['notes'])->map(
        fn (array $note) => Note::make(
            length: Note::len(ticks: $note['duration'], bpm: $bpm),
            lyric: $note['lyric'] ?? 'ら',
            key: $note['noteNumber'],
            id: $note['id'] ?? null,
        ),
    )->all(),
    Note::make(length: 2, lyric: '', key: null),
]);

Voicevox::song($score, teacher: 6000)
    ->generate(id: $track['singer']['styleId'])
    ->storeAs('vvproj/song', "{$trackId}.wav");
```


## Related topics

- [引擎 API 整合應用開發指南 - VOICEVOX for Laravel](/zh-TW/packages/laravel-voicevox/app-guide.md)
- [Score 與 Note 詳細解說 - VOICEVOX for Laravel](/zh-TW/packages/laravel-voicevox/song-score-note.md)
- [Laravel MCP](/zh-TW/mcp.md)
- [API 參考 - VOICEVOX Core for PHP](/zh-TW/packages/voicevox-core-php/api.md)
- [安裝與設定 - VOICEVOX for Laravel](/zh-TW/packages/laravel-voicevox/installation.md)
