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

# Specificatie van het .vvproj-bestand - VOICEVOX for Laravel

> Praktische specificatie voor het inlezen van de .vvproj-JSON van de VOICEVOX-editor en het genereren van talk en song in Laravel.

## Doel

De `.vvproj` van de VOICEVOX-editor direct benutten, zodat je talk en song in Laravel opnieuw kunt genereren.

## Structuur op het hoogste niveau

Een `.vvproj` is UTF-8 JSON. Talk en song worden in hetzelfde bestand opgeslagen.

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

| Sleutel      | Inhoud                                     |
| ------------ | ------------------------------------------ |
| `appVersion` | Editorversie bij het opslaan               |
| `talk`       | Talk-gegevens (`audioKeys` + `audioItems`) |
| `song`       | Song-gegevens (tempo, maatsoort, tracks)   |

## De `talk`-sectie

`talk.audioKeys` is een geordende array, `talk.audioItems` is een record met ID's als sleutel.

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

| Sleutel           | Inhoud                                                                                                                 |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `text`            | Ingevoerde tekst                                                                                                       |
| `voice.engineId`  | Engine-ID (komt overeen met `/engine_manifest`)                                                                        |
| `voice.speakerId` | Spreker-UUID                                                                                                           |
| `voice.styleId`   | Stijl-ID. Wordt direct doorgegeven aan `/synthesis?speaker={styleId}`                                                  |
| `query`           | Equivalent van `AudioQuery`                                                                                            |
| `presetKey`       | Preset-ID van de editor. Voor presets aan de Laravel-kant, zie [Presets](/nl/packages/laravel-voicevox/native-presets) |

Als er al een `query` in de `.vvproj` is opgeslagen, kun je direct doorgaan naar de synthese zonder `/audio_query` opnieuw uit te voeren.

### `accentPhrases`

| Sleutel           | Inhoud                                                              |
| ----------------- | ------------------------------------------------------------------- |
| `moras`           | Array van mora's (velden als `consonant` kunnen ontbreken)          |
| `accent`          | Accentpositie (begint bij 1)                                        |
| `pauseMora`       | Kan aanwezig zijn wanneer bij leestekens een stilte wordt ingevoegd |
| `isInterrogative` | Vlag voor vraagzinnen                                               |

## De `song`-sectie

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

| Sleutel          | Inhoud                                                                       |
| ---------------- | ---------------------------------------------------------------------------- |
| `tpqn`           | Ticks per kwartnoot. Standaard is 480                                        |
| `tempos`         | Tempomap (`position` is in ticks)                                            |
| `timeSignatures` | Maatsoortmap (`measureNumber` begint bij 1)                                  |
| `tracks`         | Record met track-ID's als sleutel                                            |
| `trackOrder`     | Weergave- en afspeelvolgorde. Moet overeenkomen met de sleutels van `tracks` |

### Track

| Sleutel                            | Inhoud                                                             |
| ---------------------------------- | ------------------------------------------------------------------ |
| `singer.styleId`                   | ID dat uiteindelijk naar `/frame_synthesis?speaker={styleId}` gaat |
| `notes`                            | Array van noten                                                    |
| `keyRangeAdjustment`               | Toonhoogteaanpassing in halve tonen                                |
| `volumeRangeAdjustment`            | Volumeaanpassing                                                   |
| `pitchEditData` / `volumeEditData` | Bewerkingsgegevens per frame                                       |
| `phonemeTimingEditData`            | Bewerking van foneemtiming per Note-ID                             |
| `solo` / `mute`                    | Gebruikt om te bepalen welke tracks worden afgespeeld              |

### Note

| Sleutel      | Inhoud                      |
| ------------ | --------------------------- |
| `id`         | Note-ID (uniek)             |
| `position`   | Start-tick van de noot      |
| `duration`   | Lengte van de noot in ticks |
| `noteNumber` | MIDI-nootnummer             |
| `lyric`      | Songtekst                   |

## Conversie tussen ticks, seconden en frames

Bij een enkel tempo converteer je zo:

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

Bij tempowisselingen sommeer je `tempos` per segment, gesorteerd oplopend op `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;
}
```

Voor de implementatie die met de `Note::len()`-helper ticks naar framelengtes converteert, zie [Score en Note in detail](/nl/packages/laravel-voicevox/song-score-note).

## Flow van song-audiogeneratie

```mermaid theme={null}
flowchart TD
  A["1. Haal tracks op in trackOrder-volgorde"] --> B["2. Bepaal met solo/mute wat gegenereerd wordt"]
  B --> C["3. Sorteer notes oplopend op position"]
  C --> D["4. Voeg rusten toe aan begin en einde en maak een Score"]
  D --> E["5. Maak frame_length via tick→seconden→frame"]
  E --> F["6. sing_frame_audio_query→sing_frame_f0→sing_frame_volume"]
  F --> G["7. Genereer WAV met frame_synthesis"]
```

## Aandachtspunten bij direct bewerken

* Houd de sleutels van `tracks` en `trackOrder` altijd in overeenstemming
* Synchroniseer op dezelfde manier `talk.audioKeys` en `talk.audioItems`
* Houd `position >= 0`, `duration >= 1` en `noteNumber` binnen `0..127`
* `tempos[0].position` is normaal `0`, `timeSignatures[0].measureNumber` is normaal `1`
* Behoud onbekende sleutels waar mogelijk bij het opnieuw opslaan, zodat de compatibiliteit met toekomstige versies niet breekt

## Laravel-codevoorbeeld

Een minimaal voorbeeld dat een `.vvproj` inleest en talk en song genereert.

```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: synthetiseer direct met de opgeslagen 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: hier converteren we de duration van de eerste track naar frame_length en genereren we
$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

- [Ontwikkelgids voor apps met de engine-API - VOICEVOX for Laravel](/nl/packages/laravel-voicevox/app-guide.md)
- [Native modus - gebruikerswoordenboek - VOICEVOX for Laravel](/nl/packages/laravel-voicevox/native-user-dict.md)
- [Laravel-updates van maart 2026](/nl/blog/changelog/202603.md)
- [Native modus - presets - VOICEVOX for Laravel](/nl/packages/laravel-voicevox/native-presets.md)
- [Laravel AI SDK](/nl/ai-sdk.md)
