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

# Ontwikkelgids voor apps met de engine-API - VOICEVOX for Laravel

> Een implementatiegericht overzicht voor het ontwikkelen van clientapps die de VOICEVOX-engine-API aanroepen: van URL-registratie en de song-synthesepipeline tot caching en multi-engine-ontwerp.

# Ontwikkelgids voor apps met de VOICEVOX-engine-API

<Frame caption="Song-app">
  <img src="https://mintcdn.com/invokable/BODEyCSwaMBfQXfp/images/laravel-voicevox/song-app-1.png?fit=max&auto=format&n=BODEyCSwaMBfQXfp&q=85&s=91a6f894c9a5c128517f125e1afaa5ce" alt="VOICEVOX Song" width="3106" height="1646" data-path="images/laravel-voicevox/song-app-1.png" />
</Frame>

Implementatienotities voor het bouwen van een clientapp die de VOICEVOX-engine-API aanroept.\
Op basis van de implementatie-ervaring met een song-app voor macOS zetten we op een rij wat er nodig is om als externe app stabiel met de engine-API te werken — in plaats van de interne architectuur van de officiële VOICEVOX-editor één op één over te nemen.

De algemene richtlijnen staan eerst, zodat ze ook bruikbaar zijn buiten Xcode/Swift; aan het eind van elk onderdeel staat een aanvulling voor Xcode.

<Info>
  Deze pagina is gebaseerd op de [oorspronkelijke gids](https://github.com/invokable/laravel-voicevox/blob/main/docs/develop/voicevox-engine-api-app-guide.md) en documenteert de inhoud zonder iets weg te laten.
</Info>

## 1. Registreer de engine eerst via een URL

<Frame caption="Engineverbindingsinstellingen">
  <img src="https://mintcdn.com/invokable/-Erq4G4RzGqd8RU2/images/laravel-voicevox/song-app-2.png?fit=max&auto=format&n=-Erq4G4RzGqd8RU2&q=85&s=f8676c5ab7b58f87d87c2585da87ff58" alt="VOICEVOX Song" width="2024" height="1248" data-path="images/laravel-voicevox/song-app-2.png" />
</Frame>

De officiële VOICEVOX-editor start het meegeleverde of opgegeven engine-uitvoerbestand vanuit de app en beheert het op basis van engine-instellingen en `engine_manifest.json`. Voor een externe clientapp is het daarentegen handiger om eerst "de URL van een reeds draaiende HTTP API-server" te registreren.

Typische URL's zijn:

| Engine                       | Voorbeeld                |
| ---------------------------- | ------------------------ |
| Officiële VOICEVOX-engine    | `http://127.0.0.1:50021` |
| Laravel-versie van de engine | `http://127.0.0.1:50513` |

De officiële engine heeft een automatische poortaanpassing: als 50021 in gebruik is, start hij op een vrije poort vanaf 50022.\
De Laravel-versie start, als je `--port=50513` niet opgeeft, op de standaardpoort 8000 en wijkt zo nodig uit naar 8001 en hoger; als je wel een poort opgeeft, is de aanpassing uitgeschakeld.

Sla bij registratie niet alleen de URL-string op, maar roep als verbindingscontrole minimaal de volgende API's aan en bewaar de opgehaalde metadata erbij.

| API                                                     | Doel                                                                          |
| ------------------------------------------------------- | ----------------------------------------------------------------------------- |
| `GET /engine_manifest`                                  | Ophalen van `uuid`, enginenaam, `frame_rate`, standaard samplerate, enzovoort |
| `GET /version`                                          | Versie ophalen voor weergave en compatibiliteitscontrole                      |
| `GET /singers`                                          | Lijst met zangstijlen ophalen                                                 |
| `GET /singer_info?speaker_uuid=...&resource_format=...` | Iconen en aanvullende informatie ophalen                                      |

Belangrijk is dat je in projecten en tracks niet de URL maar het `engineId` en `styleId` opslaat. De URL verschilt per gebruikersomgeving, maar het `engineId` komt overeen met de `uuid` uit `/engine_manifest` en sluit ook goed aan bij `singer.engineId` in `.vvproj`.

**Aanvulling voor Xcode:** in de song-app bevat `RegisteredEngine` de velden `baseURL`, `engineId`, `name`, `frameRate`, `defaultSamplingRate`, `version` en `singers`, opgeslagen in `UserDefaults`. Normaliseer URL's op afsluitende slash, query en fragment voordat je ze gebruikt, zodat `http://127.0.0.1:50021/` en `http://127.0.0.1:50021` als identiek worden behandeld. Controleer bij lokale HTTP-verbindingen in een macOS-app ook de instellingen voor de sandbox en App Transport Security.

## 2. Neem niet net als de officiële app het "starten van de engine" op je

Als je in een externe app vanaf het begin hetzelfde enginestartbeheer wilt implementeren als de officiële editor, moet je omgaan met OS-specifieke processtart, meegeleverde binaries, poortconflicten, updates en logbeheer. Vooral als je een andere implementatie dan de officiële engine gebruikt, past het model "start op basis van de locatie van het engine-uitvoerbestand" slecht.

Het is veilig om eerst dit werkmodel vast te leggen.

1. De gebruiker start de engine zelf.
2. De gebruiker voert de URL in de app in.
3. De app haalt metadata op en registreert de engine.
4. Bij het renderen wordt de URL opgelost vanuit het geregistreerde `engineId`.

Met deze aanpak kun je de officiële engine, de Laravel-versie, een engine in Docker en een engine op een andere host met dezelfde abstractie behandelen. Maak bij verbindingsproblemen de volgende stap voor de gebruiker expliciet, bijvoorbeeld "start de engine en maak opnieuw verbinding".

**Aanvulling voor Xcode:** in SwiftUI werkt het prettig om een apart scherm "Engineverbinding" te maken met een `TextField` voor de URL, een knop `Registreren / Opnieuw verbinden`, presetknoppen voor de officiële/Laravel-versie en een lijst met geregistreerde engines. Toon tijdens een asynchrone verbinding een `ProgressView`, en zet bij verbindingsfouten liever een uitleg voor de gebruiker vooraan dan de tekst van een `LocalizedError` letterlijk te tonen.

## 3. Laat zangers en stijlen pas kiezen na engineregistratie

In de VOICEVOX-API wordt de stem die uiteindelijk voor spraakgeneratie wordt gebruikt bepaald door het style-ID dat je meegeeft in de queryparameter `speaker`. Omdat een song per track een zangstijl heeft, ontvouw je in de UI de `singers` van de geregistreerde engine en maak je van zangernaam, stijlnaam en style-ID de keuzeopties.

De volgende twee waarden zijn voldoende om op te slaan.

```jsonc theme={null}
{
  "singer": {
    "engineId": "engine-uuid",
    "styleId": 6000
  }
}
```

Met deze structuur sluit je goed aan bij songdata die compatibel is met `.vvproj` en ondersteun je gemakkelijk meerdere engines. Als bij het openen van een project het betreffende `engineId` niet is geregistreerd, toon het dan als "niet-geregistreerde engine" en vraag om URL-registratie.

**Aanvulling voor Xcode:** geef in Swift elke track een `Singer(engineId: String, styleId: Int)` en maak voor weergave een afgeplatte array voor de ViewModel, zoals `SingerStyleOption`, op basis van `RegisteredEngine.singers`. Maak gerenderde audio ongeldig zodra de zangertoewijzing verandert.

## 4. Kies een songmodel op tick-basis als je `.vvproj`-compatibiliteit wilt

De songdata van de VOICEVOX-editor houdt noten en tempo bij op tick-basis. De engine-API zelf heeft uiteindelijk framelengtes nodig, maar een bewerkingsmodel op tick-basis in de app is beter geschikt voor de pianorol, tempowijzigingen, maatsoorten, undo/redo en het lezen en schrijven van `.vvproj`.

De minimale structuur is als volgt.

| Element       | Belangrijkste velden                                       |
| ------------- | ---------------------------------------------------------- |
| Song          | `tpqn`, `tempos`, `timeSignatures`, `tracks`, `trackOrder` |
| Track         | `name`, `singer`, `notes`, `gain`, `pan`, `solo`, `mute`   |
| Note          | `id`, `position`, `duration`, `noteNumber`, `lyric`        |
| Tempo         | `position`, `bpm`                                          |
| TimeSignature | `measureNumber`, `beats`, `beatType`                       |

Converteer alleen bij het renderen ticks naar seconden met de tempomap en bereken `frame_length` op basis van de `frame_rate` van de engine.

**Aanvulling voor Xcode:** als je `.vvproj`-achtige JSON leest en schrijft met `Codable`, splits `tracks` dan als Dictionary met ID's als sleutel en `trackOrder` als array met de weergavevolgorde. Valideer vóór het opslaan op duplicaten in `trackOrder`, niet-bestaande ID's en niet-gesorteerde tracks; zo voorkom je UI-problemen na het laden.

## 5. Implementeer songsynthese als een API met vier stappen

Het renderen van een song heeft meer stappen dan het `/audio_query` → `/synthesis` van talk. De basispipeline bestaat uit deze vier stappen.

```mermaid theme={null}
sequenceDiagram
  participant App as Client-<br>app
  participant Engine as VOICEVOX-<br>engine

  App->>Engine: POST /sing_frame_audio_query?speaker=6000
  Engine-->>App: FrameAudioQuery
  App->>Engine: POST /sing_frame_f0?speaker=6000
  Engine-->>App: f0[]
  App->>Engine: POST /sing_frame_volume?speaker=6000
  Engine-->>App: volume[]
  App->>Engine: POST /frame_synthesis?speaker=track.styleId
  Engine-->>App: WAV
```

Bij `/sing_frame_audio_query`, `/sing_frame_f0` en `/sing_frame_volume` gebruik je `6000`, het style-ID van de singing teacher; alleen bij het laatste `/frame_synthesis` gebruik je het `styleId` van de daadwerkelijk gekozen zanger. Deze waarde kun je bij zowel de officiële engine als de Laravel-versie op dezelfde manier gebruiken.

Bij elke API-request is het belangrijk dat de overeenkomst tussen `Score` en `FrameAudioQuery` intact blijft. Bereken `Score.notes[].frame_length` op basis van de `frame_rate` van de engine en representeer stilte als `key: null`, `lyric: ""`.

**Aanvulling voor Xcode:** maak in Swift een protocol zoals `VoicevoxEngineSongAPI` en implementeer het als `URLSession`-client; dat maakt testen makkelijker. Zet bij endpoints die JSON teruggeven `Content-Type` / `Accept` op `application/json`, en behandel de response van `/frame_synthesis` als binaire WAV-`Data`. Maak van alles wat geen HTTP 2xx is een fout die de statuscode en de responsebody bevat.

## 6. Splits in frasen en cache per frase

Als je de hele song telkens in één keer synthetiseert, wordt bij elke kleine bewerking de hele track opnieuw gegenereerd en wordt de UI traag. Net als in de officiële editor is het handig om aaneengesloten notengroepen als frasen te behandelen en te splitsen op rusten.

Cache per frase de volgende stappen.

| Cache      | Wat in de invoer hoort                                     |
| ---------- | ---------------------------------------------------------- |
| AudioQuery | Engine-ID, framerate, tempo, Score, toonbereikaanpassing   |
| F0         | AudioQuery, pitchbewerkingen, frasepositie                 |
| Volume     | AudioQuery, F0, waarden die de volumegeneratie beïnvloeden |
| Voice/WAV  | Synthesequery, definitief style-ID                         |

In een eerste implementatie mag je bij een bewerking de gerenderde audio van de betreffende track in zijn geheel ongeldig maken. Maak cachekeys op basis van een hash van de invoer, zodat je later differentiële invalidatie per frase kunt toevoegen.

**Aanvulling voor Xcode:** door de rendercache in een `actor` te leggen, is het toestandbeheer met Swift Concurrency eenvoudiger. Verhoog een generatienummer wanneer een nieuwe rendering start en accepteer verouderde API-responses niet meer. De combinatie van `Task.checkCancellation()` en een generatiecheck dekt zowel annuleren als opnieuw renderen af.

## 7. Bepaal framerate en rusten op basis van de engine-metadata

`ScoreNote.frame_length` bereken je uit de duur in seconden en de `frame_rate`. Sla de `frame_rate` niet als vaste waarde op in je code, maar haal die bij registratie op uit `/engine_manifest` en bewaar die.

Bij songsynthese voeg je rusten toe aan het begin en einde van elke frase. Als de beginrust te kort is, kan de foneemgeneratie instabiel worden; balanceer in de implementatie daarom tussen "de daadwerkelijke rust", "een kwartnoot" en "een aantal ticks terugberekend uit een minimale duur". Voeg ook aan het einde een korte stilte toe en fade zo nodig het laatste stille segment uit.

Corrigeer verder framelengtes zodat ze door afronding nooit op 0 of lager uitkomen. Bij korte noten of direct na een tempowijziging ontstaan door afrondingsfouten gemakkelijk frames van lengte 0.

**Aanvulling voor Xcode:** maak van `tickToSecond` / `secondToTick` pure functies en test ze, zodat de pianorol, de afspeelkop, het renderen en de video-export dezelfde conversie gebruiken. Garandeer dat het resultaat van `frameLength = Int(round(seconds * frameRate))` minimaal 1 frame is, bijvoorbeeld door het verschil naar de aangrenzende noot te verplaatsen.

## 8. Onderscheid waar parameterbewerkingen effect hebben: "vóór generatie" of "vóór synthese"

Bij songs beïnvloeden bewerkingen van toonbereik, stemvolume, pitch, volume en foneemtiming elk een andere fase.

| Bewerking               | Moment van toepassing                                  |
| ----------------------- | ------------------------------------------------------ |
| `keyRangeAdjustment`    | De key bij Score-generatie, pitchshift na F0-generatie |
| `volumeRangeAdjustment` | De volume-array vóór `/frame_synthesis`                |
| Pitchbewerking          | De F0-array na `/sing_frame_f0`                        |
| Volumebewerking         | De volume-array na `/sing_frame_volume`                |
| Foneemtimingbewerking   | `FrameAudioQuery.phonemes`                             |

Als je vroeg vastlegt welke bewerking welke cache ongeldig maakt, blijft het renderresultaat consistent, ook wanneer je later UI-bewerkingsfuncties toevoegt.

**Aanvulling voor Xcode:** verspreid het toepassen van parameterbewerkingen niet over ViewModels, maar concentreer het in pure logica zoals een `SongParameterEditApplicator`; dat test makkelijker. Corrigeer of signaleer expliciet toegang buiten arraygrenzen en negatieve volumes.

## 9. Gebruik dezelfde trackbepaling voor afspelen, mixen en exporteren

Na het renderen plaats je de WAV's per frase op de tijdlijn en speel je ze af. Als bij multitrack-ondersteuning de solo/mute/gain/pan-bepaling verschilt tussen normaal afspelen, volledige WAV-export en stem-export, krijgt de gebruiker onvoorspelbaar gedrag.

Als gemeenschappelijke regel: als er minstens één solotrack is, speel je alleen de solotracks af; zijn er geen solotracks, dan speel je de tracks die niet gemute zijn. Bij stem-export geef je de doeltrack afzonderlijk uit en koppel je dat los van de normale solo/mute-status.

**Aanvulling voor Xcode:** op macOS kun je met `AVAudioEngine`, `AVAudioPlayerNode` en `AVAudioMixerNode` een afspeelgraaf bouwen. Zorg voor de export voor een apart offline-renderingpad naast het normale afspelen en deel de WAV-encoding. Als tijdens het afspelen een bewerking de gerenderde audio ongeldig maakt, stop dan het afspelen en maak de status expliciet.

## 10. Laat de UI onderscheid maken tussen "niet geregistreerd, niet toegewezen, niet gerenderd"

In een app die met de VOICEVOX-engine-API werkt, zijn er meerdere soorten faalredenen.

| Toestand                                      | Te tonen instructie                               |
| --------------------------------------------- | ------------------------------------------------- |
| Engine niet geregistreerd                     | Registreer een URL in het engineverbindingsscherm |
| Engine niet gestart                           | Start de engine en verbind opnieuw                |
| `engineId` van het project niet geregistreerd | Registreer de bijbehorende engine-URL             |
| Geen zanger toegewezen aan de track           | Kies een geregistreerde zangstijl                 |
| Audio verouderd na bewerking                  | Opnieuw renderen nodig                            |
| Bezig met renderen                            | Maak annuleren mogelijk                           |

Als je dit allemaal samenvoegt tot "kan niet afspelen", is de oorzaak onduidelijk. Splits de toestanden en toon de volgende actie in de UI.

**Aanvulling voor Xcode:** met een enum voor `renderingState` in de SwiftUI-ViewModel, zoals `idle`, `rendering`, `rendered`, `stale`, `failed`, houd je de disabled-status van knoppen en de statusweergave gemakkelijk consistent. Zorg dat foutmeldingen niet alleen in de statusbalk maar ook als alert getoond kunnen worden.

## 11. Scheid bij multi-engine de levensduur van "URL" en "engineId"

Wat bij multi-engine-ondersteuning gemakkelijk tot verwarring leidt, is dat de URL en het `engineId` een verschillende rol hebben.

| Waarde     | Levensduur                                       | Gebruik                           |
| ---------- | ------------------------------------------------ | --------------------------------- |
| URL        | Verschilt per gebruikersomgeving                 | Als bestemming voor API-aanroepen |
| `engineId` | Identifier van de engine-implementatie/het model | Als referentie binnen het project |
| `styleId`  | Stijlidentifier binnen de engine                 | Als `speaker`-parameter           |

Sluit URL's niet in het projectbestand in, maar sla `engineId` en `styleId` op. Houd in de app-instellingen de mapping `engineId -> URL` bij. Zo kan iemand een project van een ander ontvangen en het renderen zodra dezelfde engine in de eigen omgeving via URL is geregistreerd.

**Aanvulling voor Xcode:** los vóór het renderen het `engineId` op naar een `RegisteredEngine`, bijvoorbeeld met `Dictionary(uniqueKeysWithValues: registeredEngines.map { ($0.engineID, $0) })`. Verstuur bij een ontbrekende engine geen HTTP-request, maar laat het vooraf falen met "de engine van de zanger is niet geregistreerd".

## 12. Test HTTP-client, modelconversie en renderplanning afzonderlijk

Testen met een permanent draaiende VOICEVOX-engine is zwaar; mock daarom in de reguliere geautomatiseerde tests de HTTP-laag en verifieer de requestopbouw en toestandstransities aan de app-kant.

Onderdelen die je met prioriteit wilt testen:

| Onderwerp              | Wat je controleert                                                                                                         |
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| URL-normalisatie       | Afsluitende slashes en URL's met pad worden behandeld zoals verwacht                                                       |
| Engineregistratie      | `/engine_manifest`, `/version`, `/singers`, `/singer_info` worden aangeroepen en er kan een opslagmodel worden gebouwd     |
| `.vvproj` I/O          | Consistentie tussen trackOrder en tracks, omgang met oude/toekomstige versies                                              |
| tick/seconde-conversie | Conversies over tempowijzigingen heen zijn omkeerbaar                                                                      |
| Score-generatie        | Rusten, frame\_length, standaardteksten, toonbereikaanpassing                                                              |
| Renderplanning         | Niet-geregistreerde engines, ontbrekende zangers en niet-overeenkomende engineId's worden vóór de API-aanroep gedetecteerd |
| Cache                  | Hergebruik bij identieke invoer en invalidatie na een bewerking                                                            |

**Aanvulling voor Xcode:** met een `URLSessionConfiguration.ephemeral` waarin je het `URLProtocol` vervangt, kun je de `URLSession`-client testen zonder echte HTTP-server. Injecteer de API's in de renderer via een protocol en geef de cachestatus van de `actor` terug via een methode die snapshots levert; dat maakt verificatie eenvoudiger.

## Aanbevolen implementatievolgorde

Als je met een minimale opzet begint, is deze volgorde het beste.

<Steps>
  <Step title="Engine-URL-registratie en metadata-opslag">
    Registreer de URL en sla de metadata uit `/engine_manifest` en `/version` erbij op.
  </Step>

  <Step title="Zangstijloverzicht en toewijzing">
    Toon de lijst met zangstijlen en wijs `engineId + styleId` toe aan tracks.
  </Step>

  <Step title="Model op tick-basis">
    Leg eerst het Song/Track/Note-model op tick-basis vast.
  </Step>

  <Step title="API-client met vier stappen">
    Implementeer de `Score`-generatie en de song-API-client met vier stappen.
  </Step>

  <Step title="Renderen per frase">
    Introduceer rendering en caching per frase.
  </Step>

  <Step title="Afspeelfunctie">
    Maak het mogelijk gerenderde WAV's op de tijdlijn af te spelen.
  </Step>

  <Step title="Multitrack-bepaling">
    Maak de solo/mute/gain/pan-bepaling voor multitrack uniform.
  </Step>

  <Step title="Exporteren">
    Voeg WAV-export en stem-export toe.
  </Step>

  <Step title="Bewerkingsfuncties">
    Voeg bewerkingen toe voor pitch, volume en foneemtiming.
  </Step>

  <Step title="Geavanceerde functies">
    Ga verder met geavanceerde functies zoals `.vvproj`-compatibel lezen/schrijven en video-export.
  </Step>
</Steps>

In plaats van vanaf het begin alle functies van de officiële editor na te bouwen, kun je beter eerst URL-registratie, zangertoewijzing, de API met vier stappen en de re-renderstatus vastleggen; dan sluit je gemakkelijk aan op zowel de officiële engine als de Laravel-versie.

## Gerelateerde links

* [VOICEVOX Core for PHP-pakketpagina](/nl/packages/voicevox-core-php)
* [Engine-API-modus: talk](/nl/packages/laravel-voicevox/engine-talk)
* [Engine-API-modus: song](/nl/packages/laravel-voicevox/engine-song)
* [Score en Note in detail](/nl/packages/laravel-voicevox/song-score-note)
* [.vvproj-bestandsspecificatie](/nl/packages/laravel-voicevox/vvproj)


## Related topics

- [Engine-API-modus: song (zangsynthese) - VOICEVOX for Laravel](/nl/packages/laravel-voicevox/engine-song.md)
- [VOICEVOX for Laravel](/nl/packages/laravel-voicevox/index.md)
- [Aan de slag - VOICEVOX for Laravel](/nl/packages/laravel-voicevox/getting-started.md)
- [Engine-API-modus: talk (tekst-naar-spraak) - VOICEVOX for Laravel](/nl/packages/laravel-voicevox/engine-talk.md)
- [Clientmodus - gebruikerswoordenboek - VOICEVOX for Laravel](/nl/packages/laravel-voicevox/client-user-dict.md)
