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

# Guida allo sviluppo di app con l'API del motore - VOICEVOX for Laravel

> Per sviluppare un'app client che chiama l'API del motore VOICEVOX, organizziamo dal punto di vista implementativo la registrazione degli URL, la pipeline di sintesi delle canzoni, la cache e il design multi-motore.

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

<Frame caption="App per canzoni">
  <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>

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.

<Info>
  Questa pagina documenta senza omissioni i contenuti della [guida originale](https://github.com/invokable/laravel-voicevox/blob/main/docs/develop/voicevox-engine-api-app-guide.md).
</Info>

## 1. Registra prima il motore tramite URL

<Frame caption="Impostazioni di connessione al motore">
  <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>

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.

| Motore                      | Esempio                  |
| --------------------------- | ------------------------ |
| Motore VOICEVOX ufficiale   | `http://127.0.0.1:50021` |
| Versione Laravel del motore | `http://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.

| API                                                     | Scopo                                                                          |
| ------------------------------------------------------- | ------------------------------------------------------------------------------ |
| `GET /engine_manifest`                                  | Ottiene `uuid`, nome del motore, `frame_rate`, sampling rate predefinita, ecc. |
| `GET /version`                                          | Ottiene la versione per visualizzazione e controllo compatibilità              |
| `GET /singers`                                          | Ottiene 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.

```jsonc theme={null}
{
  "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.

| Elemento      | Campi principali                                           |
| ------------- | ---------------------------------------------------------- |
| 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`                       |

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.

```mermaid theme={null}
sequenceDiagram
  participant App as App client
  participant Engine as Motore<br>VOICEVOX

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

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.

| Cache      | Elementi in input                                                |
| ---------- | ---------------------------------------------------------------- |
| AudioQuery | engineId, frame rate, tempo, Score, regolazione della tessitura  |
| F0         | AudioQuery, modifiche di pitch, posizione della frase            |
| Volume     | AudioQuery, F0, valori che influenzano la generazione del volume |
| Voice/WAV  | Query 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.

| Modifica                      | Fase di applicazione                                                       |
| ----------------------------- | -------------------------------------------------------------------------- |
| `keyRangeAdjustment`          | Chiave alla generazione dello Score, pitch shift dopo la generazione di F0 |
| `volumeRangeAdjustment`       | Array di volume prima di `/frame_synthesis`                                |
| Modifica di pitch             | Array F0 dopo `/sing_frame_f0`                                             |
| Modifica di volume            | Array volume dopo `/sing_frame_volume`                                     |
| Modifica dei tempi dei fonemi | `FrameAudioQuery.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.

| Stato                                  | Messaggio da mostrare                                        |
| -------------------------------------- | ------------------------------------------------------------ |
| Motore non registrato                  | Registra l'URL nella schermata di connessione                |
| Motore non avviato                     | Avvia il motore e riconnetti                                 |
| `engineId` del progetto non registrato | Aggiungi la registrazione dell'URL del motore corrispondente |
| Cantante non assegnato alla traccia    | Seleziona uno stile di cantante registrato                   |
| Audio obsoleto dopo la modifica        | È necessario il re-render                                    |
| Rendering in corso                     | Rendi 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`.

| Valore     | Ciclo di vita                                          | Utilizzo                             |
| ---------- | ------------------------------------------------------ | ------------------------------------ |
| URL        | Cambia in base all'ambiente dell'utente                | Destinazione delle chiamate API      |
| `engineId` | Identificatore lato implementazione del motore/modelli | Riferimento all'interno del progetto |
| `styleId`  | Identificatore di stile all'interno del motore         | Parametro `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.

| Obiettivo                | Cosa verificare                                                                                                        |
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------- |
| Normalizzazione URL      | Slash finale e URL con path sono gestiti come atteso                                                                   |
| Registrazione del motore | Chiami `/engine_manifest`, `/version`, `/singers`, `/singer_info` e costruisci il modello da salvare                   |
| I/O di `.vvproj`         | Coerenza tra trackOrder e tracks, gestione di versioni vecchie/future                                                  |
| Conversione tick/secondi | Le conversioni che attraversano cambi di tempo sono roundtrip                                                          |
| Generazione dello Score  | Pause, `frame_length`, testo predefinito, regolazione della tessitura                                                  |
| Piano di rendering       | Motore non registrato, cantante non impostato, `engineId` non corrispondente vengono rilevati prima delle chiamate API |
| Cache                    | Riuso 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.

<Steps>
  <Step title="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`.
  </Step>

  <Step title="Elenco degli stili di cantante e assegnazione">
    Mostra l'elenco degli stili di cantante e assegna `engineId + styleId` alle tracce.
  </Step>

  <Step title="Modello basato su tick">
    Consolida per primo il modello Song/Track/Note basato sui tick.
  </Step>

  <Step title="Client dell'API in 4 fasi">
    Implementa la generazione dello `Score` e il client dell'API in 4 fasi per le canzoni.
  </Step>

  <Step title="Rendering per frasi">
    Introduci il rendering a livello di frase e la cache.
  </Step>

  <Step title="Funzione di riproduzione">
    Riproduci sulla timeline i WAV già renderizzati.
  </Step>

  <Step title="Giudizio multi-traccia">
    Unifica il giudizio di solo/mute/gain/pan per il multi-traccia.
  </Step>

  <Step title="Esportazione">
    Aggiungi esportazione WAV ed esportazione per stem.
  </Step>

  <Step title="Funzioni di editing">
    Aggiungi le modifiche di pitch, volume e tempi dei fonemi.
  </Step>

  <Step title="Funzionalità avanzate">
    Prosegui verso funzionalità avanzate come lettura/scrittura compatibile con `.vvproj` ed esportazione video.
  </Step>
</Steps>

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.

## Link correlati

* [Pagina del pacchetto VOICEVOX Core for PHP](/it/packages/voicevox-core-php)
* [Modalità Engine API: Talk](/it/packages/laravel-voicevox/engine-talk)
* [Modalità Engine API: Song](/it/packages/laravel-voicevox/engine-song)
* [Spiegazione dettagliata di Score e Note](/it/packages/laravel-voicevox/song-score-note)
* [Specifica del file .vvproj](/it/packages/laravel-voicevox/vvproj)


## Related topics

- [Laravel e lo sviluppo con AI](/it/ai.md)
- [Modalità client - Preset - VOICEVOX for Laravel](/it/packages/laravel-voicevox/client-presets.md)
- [Modalità Engine API: Song (sintesi vocale cantata) - VOICEVOX for Laravel](/it/packages/laravel-voicevox/engine-song.md)
- [Laravel Sail](/it/sail.md)
- [Guida di migrazione dalla vecchia alla nuova struttura](/it/advanced/app-structure-migration.md)
