> ## 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-Dateispezifikation - VOICEVOX für Laravel

> Ordnet eine praxisnahe Spezifikation zum Einlesen von .vvproj-JSON des VOICEVOX-Editors und zum Erzeugen von Talk und Song in Laravel.

## Ziel

Ermöglicht es, `.vvproj`-Dateien des VOICEVOX-Editors unverändert zu nutzen und Talk und Song in Laravel neu zu erzeugen.

## Top-Level-Struktur

`.vvproj` ist UTF-8-JSON. Talk und Song werden in derselben Datei gespeichert.

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

| Key          | Inhalt                                      |
| ------------ | ------------------------------------------- |
| `appVersion` | Editor-Version beim Speichern               |
| `talk`       | Daten für Talk (`audioKeys` + `audioItems`) |
| `song`       | Daten für Song (Tempo, Taktart, Tracks)     |

## `talk`-Abschnitt

`talk.audioKeys` ist ein geordnetes Array; `talk.audioItems` ist ein Record mit IDs als Keys.

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

| Key               | Inhalt                                                                                                          |
| ----------------- | --------------------------------------------------------------------------------------------------------------- |
| `text`            | Eingabetext                                                                                                     |
| `voice.engineId`  | Engine-ID (entspricht `/engine_manifest`)                                                                       |
| `voice.speakerId` | Sprecher-UUID                                                                                                   |
| `voice.styleId`   | Style-ID. Wird direkt an `/synthesis?speaker={styleId}` übergeben.                                              |
| `query`           | Entspricht `AudioQuery`                                                                                         |
| `presetKey`       | Preset-ID des Editors. Presets auf Laravel-Seite siehe [Presets](/de/packages/laravel-voicevox/native-presets). |

Wenn `query` in `.vvproj` bereits gespeichert ist, können Sie direkt zur Synthese übergehen, ohne `/audio_query` erneut auszuführen.

### `accentPhrases`

| Key               | Inhalt                                                                      |
| ----------------- | --------------------------------------------------------------------------- |
| `moras`           | Mora-Array (`consonant` u. ä. können weggelassen werden)                    |
| `accent`          | Akzentposition (1-basiert)                                                  |
| `pauseMora`       | Kann angehängt werden, wenn bei Satzzeichen etc. eine Stille eingefügt wird |
| `isInterrogative` | Flag für Fragesätze                                                         |

## `song`-Abschnitt

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

| Key              | Inhalt                                                                                  |
| ---------------- | --------------------------------------------------------------------------------------- |
| `tpqn`           | Ticks Per Quarter Note. Standard ist 480                                                |
| `tempos`         | Tempo-Map (`position` ist ein Tick)                                                     |
| `timeSignatures` | Taktart-Map (`measureNumber` ist 1-basiert)                                             |
| `tracks`         | Record mit Track-ID als Key                                                             |
| `trackOrder`     | Anzeige-/Wiedergabereihenfolge. Muss mit der Schlüsselmenge von `tracks` übereinstimmen |

### Track

| Key                                | Inhalt                                                          |
| ---------------------------------- | --------------------------------------------------------------- |
| `singer.styleId`                   | Letztlich an `/frame_synthesis?speaker={styleId}` übergebene ID |
| `notes`                            | Noten-Array                                                     |
| `keyRangeAdjustment`               | Tonhöhenanpassung in Halbtonschritten                           |
| `volumeRangeAdjustment`            | Lautstärkeanpassung                                             |
| `pitchEditData` / `volumeEditData` | Bearbeitungsdaten pro Frame                                     |
| `phonemeTimingEditData`            | Phonem-Timing-Bearbeitung pro Note-ID                           |
| `solo` / `mute`                    | Wird zur Bestimmung der Wiedergabe-Tracks verwendet             |

### Note

| Key          | Inhalt                      |
| ------------ | --------------------------- |
| `id`         | Note-ID (eindeutig)         |
| `position`   | Startzeit der Note in Ticks |
| `duration`   | Notenlänge in Ticks         |
| `noteNumber` | MIDI-Notennummer            |
| `lyric`      | Liedtext                    |

## Umrechnung Tick / Sekunde / Frame

Bei einem einzigen Tempo gilt:

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

Bei Tempoänderungen summieren Sie `tempos` in aufsteigender Reihenfolge von `position` abschnittsweise.

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

Eine Implementierung, die mit dem `Note::len()`-Helper von Ticks in Frame-Längen umrechnet, finden Sie unter [Score- und Note-Details](/de/packages/laravel-voicevox/song-score-note).

## Ablauf der Song-Audio-Erzeugung

```mermaid theme={null}
flowchart TD
  A["1. Tracks in der Reihenfolge von trackOrder herausnehmen"] --> B["2. Erzeugungsziele über solo/mute festlegen"]
  B --> C["3. notes in aufsteigender position-Reihenfolge sortieren"]
  C --> D["4. Am Anfang und Ende Pausen einfügen und in Score umwandeln"]
  D --> E["5. tick→Sekunden→frame, um frame_length zu erzeugen"]
  E --> F["6. sing_frame_audio_query→sing_frame_f0→sing_frame_volume"]
  F --> G["7. WAV mit frame_synthesis erzeugen"]
```

## Hinweise bei direkter Bearbeitung

* Die Schlüsselmenge von `tracks` und `trackOrder` müssen unbedingt übereinstimmen.
* Auch `talk.audioKeys` und `talk.audioItems` müssen synchron gehalten werden.
* Halten Sie `position >= 0`, `duration >= 1` und `noteNumber` innerhalb von `0..127` ein.
* `tempos[0].position` ist üblicherweise `0`, `timeSignatures[0].measureNumber` üblicherweise `1`.
* Unbekannte Keys nach Möglichkeit erhalten und wieder speichern, um die Kompatibilität mit zukünftigen Versionen nicht zu brechen.

## Laravel-Code-Beispiel

Ein Minimalbeispiel, das `.vvproj` einliest und Talk und Song erzeugt.

```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: Vorhandene Query direkt synthetisieren
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 die duration des ersten Tracks in frame_length umwandeln und erzeugen
$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

- [Entwicklungsleitfaden für Engine-API-Integrationsanwendungen - VOICEVOX für Laravel](/de/packages/laravel-voicevox/app-guide.md)
- [VOICEVOX für Laravel](/de/packages/laravel-voicevox/index.md)
- [VoicevoxResponse - VOICEVOX für Laravel](/de/packages/laravel-voicevox/response.md)
- [Konfigurationsreferenz - VOICEVOX für Laravel](/de/packages/laravel-voicevox/configuration.md)
- [Erste Schritte - VOICEVOX für Laravel](/de/packages/laravel-voicevox/getting-started.md)
