Guida allo sviluppo di app che integrano l’API del motore VOICEVOX

App per canzoni
Questa pagina documenta senza omissioni i contenuti della guida originale.
1. Registra prima il motore tramite URL

Impostazioni di connessione al motore
engine_manifest.json. In un’app client esterna, invece, è più semplice adottare un approccio in cui si registra “l’URL del server HTTP API già avviato”.
Gli URL rappresentativi sono i seguenti.
Il motore ufficiale dispone di una funzione di regolazione automatica della porta: se 50021 è in uso, si avvia sulla prima porta libera a partire da 50022.
Anche la versione Laravel, se avviata senza specificare
--port=50513 sulla porta predefinita 8000, si avvia sulla 8001 in caso di conflitto, ma se specifichi la porta la funzione di regolazione è disabilitata.
Al momento della registrazione, invece di salvare solo la stringa URL, chiama come conferma di connessione almeno le seguenti API e salva l’URL insieme alle meta-informazioni ottenute.
L’aspetto importante è che nei progetti e nelle tracce non si salva l’URL, ma
engineId e styleId. L’URL cambia in base all’ambiente dell’utente, mentre engineId corrisponde al campo uuid di /engine_manifest e si allinea facilmente anche a singer.engineId di .vvproj.
Note per Xcode: nell’app per canzoni, RegisteredEngine contiene baseURL, engineId, name, frameRate, defaultSamplingRate, version, singers e viene salvato in UserDefaults. Se normalizzi l’URL rimuovendo slash finale, query e fragment prima dell’uso, puoi trattare http://127.0.0.1:50021/ e http://127.0.0.1:50021 come identici. Se usi connessioni HTTP locali in un’app macOS, verifica anche le impostazioni di sandbox e App Transport Security.
2. Non gestire nell’app “l’avvio del motore” come l’app ufficiale
Se in un’app esterna cerchi di implementare fin dall’inizio la stessa gestione dell’avvio del motore dell’editor ufficiale, dovrai occuparti di avvio dei processi per ogni OS, binari inclusi, conflitti di porta, aggiornamenti e gestione dei log. In particolare, quando si utilizzano implementazioni del motore diverse da quella ufficiale, l’approccio di specificare la posizione dell’eseguibile del motore per avviarlo è di difficile integrazione. Inizialmente è sicuro attenersi al seguente flusso operativo.- L’utente avvia il motore per primo.
- Nell’app si inserisce l’URL.
- L’app ottiene le meta-informazioni e le registra.
- Al momento del rendering, l’
engineIdregistrato viene risolto in URL.
TextField per l’URL, i pulsanti Registra / Riconnetti, i pulsanti preset per la versione ufficiale/Laravel e l’elenco dei motori registrati. Durante la connessione asincrona mostra un ProgressView e per gli errori di connessione è meglio anteporre una spiegazione rivolta all’utente al posto del semplice testo di LocalizedError.
3. Fai scegliere il cantante e lo stile dopo la registrazione del motore
Nelle API di VOICEVOX, la voce utilizzata infine per generare l’audio è determinata dallo style ID passato al parametro queryspeaker. Nelle canzoni, ogni traccia ha uno stile di cantante; nella UI, espandi singers dai motori registrati e proponi nome del cantante, nome dello stile e style ID come opzioni selezionabili.
I valori da salvare sono sufficienti nei due seguenti.
.vvproj e supporti bene il multi-motore. Quando apri un progetto, se il relativo engineId non è registrato, mostralo come “motore non registrato” e invita a registrare l’URL.
Note per Xcode: in Swift, mantieni sulla traccia Singer(engineId: String, styleId: Int) e appiattisci per la visualizzazione i dati da RegisteredEngine.singers in un array di ViewModel come SingerStyleOption. Quando cambi l’assegnazione del cantante, invalida l’audio già renderizzato.
4. Se pensi alla compatibilità con .vvproj, usa un modello di canzone basato su tick
I dati canzone dell’editor VOICEVOX hanno note e tempi basati sui tick. L’API del motore richiede infine una lunghezza in frame, ma è preferibile mantenere il modello di editing interno all’app basato sui tick, per una migliore integrazione con piano roll, cambio di tempo, tempo in chiave, Undo/Redo e lettura/scrittura di .vvproj.
La struttura minima è la seguente.
Solo al momento del rendering, usa la tempo map per convertire i tick in secondi e calcola
frame_length in base al frame_rate del motore.
Note per Xcode: quando leggi/scrivi JSON in stile .vvproj con Codable, separa tracks come Dictionary con ID come chiavi e trackOrder come array dell’ordine di visualizzazione. Prima del salvataggio, verifica duplicati di trackOrder, ID inesistenti e tracce non ordinate per prevenire disallineamenti UI al caricamento.
5. Implementa la sintesi delle canzoni come API in 4 fasi
Il rendering delle canzoni ha più passaggi rispetto al/audio_query → /synthesis del talk. La pipeline base è composta da 4 fasi.
In /sing_frame_audio_query, /sing_frame_f0 e /sing_frame_volume usa lo style ID 6000 del singing teacher, mentre solo nell’ultima chiamata a /frame_synthesis usa lo styleId del cantante effettivamente selezionato. Questo valore può essere gestito con le stesse premesse sia sul motore ufficiale che sulla versione Laravel.
Nelle richieste di ciascuna API è importante non rompere la corrispondenza tra Score e FrameAudioQuery. Score.notes[].frame_length si calcola dal frame_rate del motore, e il silenzio si rappresenta con key: null, lyric: "".
Note per Xcode: in Swift crea un protocol come VoicevoxEngineSongAPI e implementa il client con URLSession per facilitare i test. Negli endpoint che restituiscono JSON, imposta Content-Type / Accept a application/json; la risposta di /frame_synthesis va trattata come Data binario WAV. Trasforma le risposte HTTP diverse da 2xx in errori che includono status code e corpo della risposta.
6. Suddividi in frasi e usa la cache
Se sintetizzi ogni volta l’intera canzone in un’unica passata, anche una piccola modifica comporta la rigenerazione di tutte le tracce, peggiorando la reattività della UI. Come nell’editor ufficiale, è pratico un design che suddivide gruppi di note consecutive in frasi separate dalle pause. Metti in cache le seguenti fasi per ogni frase.
Nell’implementazione iniziale, in caso di modifica puoi invalidare in blocco l’audio renderizzato per la traccia interessata. Per poter aggiungere in seguito l’invalidazione incrementale a livello di frase, costruisci le chiavi di cache come hash degli input.
Note per Xcode: mantieni la cache di rendering in un
actor per gestire lo stato con Swift Concurrency. Incrementa il numero di generazione all’avvio di un nuovo rendering e ignora le risposte API tardive. Combinando Task.checkCancellation() e la verifica di generazione puoi gestire sia le cancellazioni che i re-render.
7. Determina frame rate e pause dalle meta-informazioni del motore
ScoreNote.frame_length si calcola dai secondi e dal frame_rate. Non fissare frame_rate come valore hard-coded: ottienilo da /engine_manifest in fase di registrazione e salvalo.
Nella sintesi delle canzoni, inserisci pause all’inizio e alla fine della frase. Se la pausa iniziale è troppo breve, la generazione dei fonemi può diventare instabile: nell’implementazione trova un equilibrio tra “la pausa effettiva”, “una nota da un quarto” e “un tick calcolato a ritroso da un minimo di secondi”. Aggiungi anche un breve silenzio alla fine e, se necessario, applica un fade out all’ultima sezione di silenzio.
Inoltre, correggi la lunghezza in frame in modo che non risulti ≤ 0 a causa degli arrotondamenti. Nelle note brevi o subito dopo un cambio di tempo, gli errori di arrotondamento producono facilmente 0 frame.
Note per Xcode: se rendi tickToSecond / secondToTick funzioni pure e le testi, puoi utilizzare la stessa conversione per piano roll, playhead, rendering ed esportazione video. Assicurati che frameLength = Int(round(seconds * frameRate)) garantisca almeno 1 frame, spostando eventuali differenze sulle note adiacenti.
8. Distingui dove agiscono le modifiche dei parametri: “prima della generazione” o “prima della sintesi”
Nelle canzoni, le modifiche di tessitura, volume, pitch, volume ed epigrafia dei fonemi influiscono ciascuna su fasi diverse.
Se decidi presto quali modifiche invalidano quali cache, il risultato del rendering rimane coerente anche aggiungendo in seguito funzioni di editing dalla UI.
Note per Xcode: non disperdere l’applicazione delle modifiche dei parametri nel ViewModel; concentrala in una logica pura come
SongParameterEditApplicator per facilitare i test. Corregge esplicitamente o trasforma in errore gli accessi fuori range agli array e i volumi negativi.
9. Riproduzione, mix ed esportazione usano lo stesso giudizio sulle tracce
Dopo il rendering, posiziona sulla timeline i WAV per ogni frase e riproducili. Nel supporto multi-traccia, se la valutazione di solo/mute/gain/pan differisce tra riproduzione normale, esportazione WAV completa ed esportazione per stem, il comportamento diventa imprevedibile per l’utente. Come regola comune, se esiste almeno una traccia solo, riproduci solo le tracce solo; se non ci sono tracce solo, riproduci le tracce non mute. Nell’esportazione per stem, esporta la traccia bersaglio in isolamento, separandola dallo stato solo/mute normale. Note per Xcode: su macOS puoi costruire il grafo di riproduzione conAVAudioEngine, AVAudioPlayerNode e AVAudioMixerNode. Predisponi un percorso di rendering offline separato dalla riproduzione normale per l’esportazione, e centralizza la codifica WAV. Se durante la riproduzione avviene una modifica e l’audio renderizzato viene invalidato, ferma la riproduzione e comunica chiaramente lo stato.
10. La UI distingue “non registrato”, “non assegnato” e “non renderizzato”
Nelle app che integrano l’API del motore VOICEVOX, i motivi di fallimento si dividono in più casi.
Se raggruppi tutto in “non è possibile riprodurre”, non è possibile identificare la causa. Distingui gli stati e mostra nella UI l’azione successiva.
Note per Xcode: nel ViewModel di SwiftUI, definisci
renderingState come un enum con valori tipo idle, rendering, rendered, stale, failed per allineare il controllo di disabled dei pulsanti alla visualizzazione dello stato. Consenti di mostrare i messaggi di errore non solo nella status bar ma anche in un alert.
11. Nel multi-motore separa i cicli di vita di “URL” e “engineId”
Nel supporto multi-motore, ciò che genera confusione è la differenza di ruolo tra URL eengineId.
Nel file di progetto non incorporare l’URL; salva
engineId e styleId. Nelle impostazioni dell’app mantieni la corrispondenza engineId -> URL. In questo modo, anche un progetto ricevuto da qualcun altro può essere renderizzato registrando nel tuo ambiente lo stesso motore tramite URL.
Note per Xcode: prima del rendering, risolvi da engineId a RegisteredEngine con qualcosa come Dictionary(uniqueKeysWithValues: registeredEngines.map { ($0.engineID, $0) }). Se non trovi corrispondenza, non inviare la richiesta HTTP e fallisci in anticipo con “il motore del cantante non è registrato”.
12. Suddividi i test tra HTTP client, conversione del modello e piano di rendering
Poiché è oneroso mantenere sempre acceso il motore VOICEVOX per i test, nei test automatici standard è preferibile mockare l’HTTP e verificare la generazione delle richieste e le transizioni di stato lato app. Ecco gli elementi da testare in via prioritaria.
Note per Xcode: utilizzando
URLSessionConfiguration.ephemeral con URLProtocol sostituito, puoi testare il client URLSession senza un server HTTP reale. Inietta l’API nel renderer tramite protocol e per lo stato della cache in un actor fornisci metodi che restituiscano snapshot per facilitare la verifica.
Ordine consigliato di implementazione
Se parti da una configurazione minima, questo è un buon ordine.1
Registrazione dell'URL del motore e salvataggio delle meta-informazioni
Registra l’URL e salva insieme le meta-informazioni ottenute da
/engine_manifest e /version.2
Elenco degli stili di cantante e assegnazione
Mostra l’elenco degli stili di cantante e assegna
engineId + styleId alle tracce.3
Modello basato su tick
Consolida per primo il modello Song/Track/Note basato sui tick.
4
Client dell'API in 4 fasi
Implementa la generazione dello
Score e il client dell’API in 4 fasi per le canzoni.5
Rendering per frasi
Introduci il rendering a livello di frase e la cache.
6
Funzione di riproduzione
Riproduci sulla timeline i WAV già renderizzati.
7
Giudizio multi-traccia
Unifica il giudizio di solo/mute/gain/pan per il multi-traccia.
8
Esportazione
Aggiungi esportazione WAV ed esportazione per stem.
9
Funzioni di editing
Aggiungi le modifiche di pitch, volume e tempi dei fonemi.
10
Funzionalità avanzate
Prosegui verso funzionalità avanzate come lettura/scrittura compatibile con
.vvproj ed esportazione video.