Vai al contenuto principale

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

VOICEVOX Song
Note di implementazione per lo sviluppo di un’app client che chiama l’API del motore VOICEVOX. Sulla base di un registro di implementazione di un’app per canzoni per macOS, invece di portare direttamente la struttura interna dell’editor ufficiale VOICEVOX, organizziamo gli elementi necessari per utilizzare stabilmente l’API del motore come app esterna. Per essere utilizzabile anche in ambienti diversi da Xcode/Swift, presentiamo prima le linee guida generali e poi, alla fine di ogni sezione, aggiungiamo note specifiche per Xcode.
Questa pagina documenta senza omissioni i contenuti della guida originale.

1. Registra prima il motore tramite URL

VOICEVOX Song
L’editor ufficiale VOICEVOX avvia dal lato app l’eseguibile del motore incluso o specificato e lo gestisce basandosi sulle impostazioni del motore e su 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.
MotoreEsempio
Motore VOICEVOX ufficialehttp://127.0.0.1:50021
Versione Laravel del motorehttp://127.0.0.1:50513
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.
APIScopo
GET /engine_manifestOttiene uuid, nome del motore, frame_rate, sampling rate predefinita, ecc.
GET /versionOttiene la versione per visualizzazione e controllo compatibilità
GET /singersOttiene l’elenco degli stili di canto
GET /singer_info?speaker_uuid=...&resource_format=...Ottiene icone e informazioni aggiuntive
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.
  1. L’utente avvia il motore per primo.
  2. Nell’app si inserisce l’URL.
  3. L’app ottiene le meta-informazioni e le registra.
  4. Al momento del rendering, l’engineId registrato viene risolto in URL.
Con questo approccio, puoi trattare il motore ufficiale, la versione Laravel, il motore su Docker e il motore su un altro host tramite la stessa astrazione. In caso di impossibilità a connettersi, indica chiaramente l’operazione successiva da compiere, come “Avvia il motore e riconnetti”. Note per Xcode: in SwiftUI, è comodo separare la schermata di “connessione al motore” con 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 query speaker. 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.
{
  "singer": {
    "engineId": "engine-uuid",
    "styleId": 6000
  }
}
Con questa struttura ti allinei facilmente ai dati canzone compatibili con .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.
ElementoCampi principali
Songtpqn, tempos, timeSignatures, tracks, trackOrder
Trackname, singer, notes, gain, pan, solo, mute
Noteid, position, duration, noteNumber, lyric
Tempoposition, bpm
TimeSignaturemeasureNumber, beats, beatType
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.
CacheElementi in input
AudioQueryengineId, frame rate, tempo, Score, regolazione della tessitura
F0AudioQuery, modifiche di pitch, posizione della frase
VolumeAudioQuery, F0, valori che influenzano la generazione del volume
Voice/WAVQuery per la sintesi, style ID finale
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.
ModificaFase di applicazione
keyRangeAdjustmentChiave alla generazione dello Score, pitch shift dopo la generazione di F0
volumeRangeAdjustmentArray di volume prima di /frame_synthesis
Modifica di pitchArray F0 dopo /sing_frame_f0
Modifica di volumeArray volume dopo /sing_frame_volume
Modifica dei tempi dei fonemiFrameAudioQuery.phonemes
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 con AVAudioEngine, 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.
StatoMessaggio da mostrare
Motore non registratoRegistra l’URL nella schermata di connessione
Motore non avviatoAvvia il motore e riconnetti
engineId del progetto non registratoAggiungi la registrazione dell’URL del motore corrispondente
Cantante non assegnato alla tracciaSeleziona uno stile di cantante registrato
Audio obsoleto dopo la modificaÈ necessario il re-render
Rendering in corsoRendi possibile annullare
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 e engineId.
ValoreCiclo di vitaUtilizzo
URLCambia in base all’ambiente dell’utenteDestinazione delle chiamate API
engineIdIdentificatore lato implementazione del motore/modelliRiferimento all’interno del progetto
styleIdIdentificatore di stile all’interno del motoreParametro speaker
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.
ObiettivoCosa verificare
Normalizzazione URLSlash finale e URL con path sono gestiti come atteso
Registrazione del motoreChiami /engine_manifest, /version, /singers, /singer_info e costruisci il modello da salvare
I/O di .vvprojCoerenza tra trackOrder e tracks, gestione di versioni vecchie/future
Conversione tick/secondiLe conversioni che attraversano cambi di tempo sono roundtrip
Generazione dello ScorePause, frame_length, testo predefinito, regolazione della tessitura
Piano di renderingMotore non registrato, cantante non impostato, engineId non corrispondente vengono rilevati prima delle chiamate API
CacheRiuso con lo stesso input e invalidazione dopo la modifica
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.
Invece di replicare fin dall’inizio tutte le funzionalità dell’editor ufficiale, consolida prima registrazione dell’URL, assegnazione del cantante, API in 4 fasi e stato di re-render: ti sarà più facile allinearti sia al motore ufficiale che alla versione Laravel.
Ultima modifica il 13 luglio 2026