Ontwikkelgids voor apps met de VOICEVOX-engine-API
Song-app
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.
Deze pagina is gebaseerd op de oorspronkelijke gids en documenteert de inhoud zonder iets weg te laten.
1. Registreer de engine eerst via een URL
Engineverbindingsinstellingen
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:
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.
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.- De gebruiker start de engine zelf.
- De gebruiker voert de URL in de app in.
- De app haalt metadata op en registreert de engine.
- Bij het renderen wordt de URL opgelost vanuit het geregistreerde
engineId.
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 queryparameterspeaker. 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.
.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.
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.
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.
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.
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 metAVAudioEngine, 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.
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 hetengineId een verschillende rol hebben.
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:
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.1
Engine-URL-registratie en metadata-opslag
Registreer de URL en sla de metadata uit
/engine_manifest en /version erbij op.2
Zangstijloverzicht en toewijzing
Toon de lijst met zangstijlen en wijs
engineId + styleId toe aan tracks.3
Model op tick-basis
Leg eerst het Song/Track/Note-model op tick-basis vast.
4
API-client met vier stappen
Implementeer de
Score-generatie en de song-API-client met vier stappen.5
Renderen per frase
Introduceer rendering en caching per frase.
6
Afspeelfunctie
Maak het mogelijk gerenderde WAV’s op de tijdlijn af te spelen.
7
Multitrack-bepaling
Maak de solo/mute/gain/pan-bepaling voor multitrack uniform.
8
Exporteren
Voeg WAV-export en stem-export toe.
9
Bewerkingsfuncties
Voeg bewerkingen toe voor pitch, volume en foneemtiming.
10
Geavanceerde functies
Ga verder met geavanceerde functies zoals
.vvproj-compatibel lezen/schrijven en video-export.