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

# Spécification du fichier .vvproj - VOICEVOX for Laravel

> Spécification pratique pour charger un JSON .vvproj de l'éditeur VOICEVOX et générer les voix Talk/Song depuis Laravel.

## Objectif

Utiliser tel quel le `.vvproj` de l'éditeur VOICEVOX pour regénérer Talk et Song depuis Laravel.

## Structure de haut niveau

`.vvproj` est un JSON UTF-8. Il conserve Talk et Song dans un même fichier.

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

| Clé          | Contenu                                          |
| ------------ | ------------------------------------------------ |
| `appVersion` | Version de l'éditeur au moment de la sauvegarde  |
| `talk`       | Données Talk (`audioKeys` + `audioItems`)        |
| `song`       | Données Song (tempo, mesure, ensemble de pistes) |

## Section `talk`

`talk.audioKeys` est un tableau ordonné ; `talk.audioItems` est un Record indexé par ID.

```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

| Clé               | Contenu                                                                                                                       |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `text`            | Texte saisi                                                                                                                   |
| `voice.engineId`  | ID du moteur (correspond à `/engine_manifest`)                                                                                |
| `voice.speakerId` | UUID du locuteur                                                                                                              |
| `voice.styleId`   | ID de style. Passé tel quel à `/synthesis?speaker={styleId}`                                                                  |
| `query`           | Équivalent à un `AudioQuery`                                                                                                  |
| `presetKey`       | ID de preset dans l'éditeur. Pour les presets côté Laravel, consultez [Presets](/fr/packages/laravel-voicevox/native-presets) |

Si `query` est déjà stocké dans le `.vvproj`, on peut passer directement à la synthèse sans réexécuter `/audio_query`.

### `accentPhrases`

| Clé               | Contenu                                                             |
| ----------------- | ------------------------------------------------------------------- |
| `moras`           | Tableau de mores (les éléments comme `consonant` peuvent être omis) |
| `accent`          | Position de l'accent (indexée à partir de 1)                        |
| `pauseMora`       | Peut apparaître pour insérer un silence après une ponctuation       |
| `isInterrogative` | Drapeau d'interrogation                                             |

## Section `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"]
}
```

| Clé              | Contenu                                                                              |
| ---------------- | ------------------------------------------------------------------------------------ |
| `tpqn`           | Ticks Per Quarter Note. Standard : 480                                               |
| `tempos`         | Table des tempos (`position` en ticks)                                               |
| `timeSignatures` | Table des signatures rythmiques (`measureNumber` commence à 1)                       |
| `tracks`         | Record indexé par ID de piste                                                        |
| `trackOrder`     | Ordre d'affichage et de lecture. Doit correspondre à l'ensemble des clés de `tracks` |

### Track

| Clé                                | Contenu                                                    |
| ---------------------------------- | ---------------------------------------------------------- |
| `singer.styleId`                   | ID finalement passé à `/frame_synthesis?speaker={styleId}` |
| `notes`                            | Tableau de notes                                           |
| `keyRangeAdjustment`               | Ajustement de tessiture en demi-tons                       |
| `volumeRangeAdjustment`            | Ajustement de volume                                       |
| `pitchEditData` / `volumeEditData` | Données d'édition par frame                                |
| `phonemeTimingEditData`            | Édition de la temporisation des phonèmes par ID de note    |
| `solo` / `mute`                    | Utilisés pour déterminer les pistes à lire                 |

### Note

| Clé          | Contenu                |
| ------------ | ---------------------- |
| `id`         | ID de la note (unique) |
| `position`   | Tick de début          |
| `duration`   | Longueur en ticks      |
| `noteNumber` | Numéro de note MIDI    |
| `lyric`      | Paroles                |

## Conversion tick / seconde / frame

Avec un tempo unique, la conversion est la suivante.

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

Avec plusieurs tempos, cumulez les segments en parcourant `tempos` par `position` croissant.

```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;
}
```

Pour l'implémentation de la conversion de ticks vers une longueur en frames via le helper `Note::len()`, consultez [Détails Score et Note](/fr/packages/laravel-voicevox/song-score-note).

## Flux de génération de la voix Song

```mermaid theme={null}
flowchart TD
  A["1. Extraire les tracks selon trackOrder"] --> B["2. Déterminer les cibles avec solo/mute"]
  B --> C["3. Trier les notes par position croissante"]
  C --> D["4. Ajouter un silence en tête et en fin, puis passer en Score"]
  D --> E["5. Calculer frame_length via tick → secondes → frame"]
  E --> F["6. sing_frame_audio_query → sing_frame_f0 → sing_frame_volume"]
  F --> G["7. Générer le WAV avec frame_synthesis"]
```

## Précautions pour l'édition directe

* Faites toujours correspondre l'ensemble des clés de `tracks` et `trackOrder`
* Synchronisez de la même manière `talk.audioKeys` et `talk.audioItems`
* Respectez `position >= 0`, `duration >= 1` et `noteNumber` dans `0..127`
* Habituellement `tempos[0].position` vaut `0` et `timeSignatures[0].measureNumber` vaut `1`
* Conservez si possible les clés inconnues à la réécriture pour ne pas casser la compatibilité avec les versions futures

## Exemple de code Laravel

Exemple minimal pour charger un `.vvproj` et générer Talk et 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 : synthèse directe à partir de la query stockée
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 : ici on convertit la `duration` de la première piste en frame_length et on génère
$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

- [Guide de développement d'applications intégrées à l'API du moteur - VOICEVOX for Laravel](/fr/packages/laravel-voicevox/app-guide.md)
- [Mode client - Presets - VOICEVOX for Laravel](/fr/packages/laravel-voicevox/client-presets.md)
- [Mode client - Dictionnaire utilisateur - VOICEVOX for Laravel](/fr/packages/laravel-voicevox/client-user-dict.md)
- [VOICEVOX for Laravel](/fr/packages/laravel-voicevox/index.md)
- [Installation et configuration - VOICEVOX for Laravel](/fr/packages/laravel-voicevox/installation.md)
