// TtsManager.cs — synteza mowy przez sherpa-onnx (głos vits-piper-pl_PL-gosia-medium). // // Instalacja wtyczki (wybierz jedną): // A) Oficjalna: skopiuj .dll / .aar z wydania sherpa-onnx do Assets/Plugins/ // https://github.com/k2-fsa/sherpa-onnx/releases (szukaj unity-*) // B) UPM: https://github.com/EitanWong/com.eitan.sherpa-onnx-unity // // Po zainstalowaniu: odkomentuj bloki oznaczone [SHERPA] poniżej. // // Pliki modelu (StreamingAssets/vits-piper-pl_PL-gosia-medium/): // pl_PL-gosia-medium.onnx // tokens.txt // espeak-ng-data/ (katalog) // Pobierz ze: https://github.com/k2-fsa/sherpa-onnx/releases/tag/tts-models // → vits-piper-pl_PL-gosia-medium.tar.bz2 using System; using System.Collections; using System.IO; using UnityEngine; using UnityEngine.Networking; // [SHERPA] using SherpaOnnx; // dokładna przestrzeń nazw zależy od wtyczki namespace IpinVr { public class TtsManager : MonoBehaviour { [Header("Ścieżki modelu (względem StreamingAssets)")] [SerializeField] private string modelOnnx = "vits-piper-pl_PL-gosia-medium/pl_PL-gosia-medium.onnx"; [SerializeField] private string tokensFile = "vits-piper-pl_PL-gosia-medium/tokens.txt"; [SerializeField] private string espeakDataDir = "vits-piper-pl_PL-gosia-medium/espeak-ng-data"; [Header("Odtwarzanie")] [SerializeField] private AudioSource audioSource; [Header("Parametry syntezy")] [SerializeField] [Range(1, 4)] private int numThreads = 2; [SerializeField] [Range(0.5f, 2.0f)] private float speed = 1.0f; public bool IsReady { get; private set; } // [SHERPA] private OfflineTts _tts; private void Awake() { if (audioSource == null) audioSource = gameObject.AddComponent(); } /// Kopiuje pliki z StreamingAssets i inicjuje silnik TTS. public void Initialize(Action onReady = null) { StartCoroutine(InitCoroutine(onReady)); } private IEnumerator InitCoroutine(Action onReady) { string[] files = { modelOnnx, tokensFile }; foreach (var rel in files) yield return StartCoroutine(EnsureFile(rel)); // espeak-ng-data: sherpa-onnx Unity plugins zwykle obsługują to wewnętrznie. // Jeśli Twoja wtyczka tego nie robi, spakuj katalog jako .zip i rozpakowuj tu. yield return StartCoroutine(EnsureEspeakData()); // [SHERPA] Inicjalizacja silnika: // // string base = Application.persistentDataPath; // var config = new OfflineTtsConfig // { // model = new OfflineTtsModelConfig // { // vits = new OfflineTtsVitsModelConfig // { // model = Path.Combine(base, modelOnnx), // tokens = Path.Combine(base, tokensFile), // dataDir = Path.Combine(base, espeakDataDir), // }, // numThreads = numThreads, // provider = "cpu", // debug = false, // }, // }; // _tts = new OfflineTts(config); IsReady = true; Debug.Log("[TtsManager] Gotowy (sherpa-onnx stub — odkomentuj bloki [SHERPA])"); onReady?.Invoke(); } /// Syntezuje text i odtwarza. Wywołuje onDone po zakończeniu odtwarzania. public void Speak(string text, Action onDone = null) { if (!IsReady) { Debug.LogWarning("[TtsManager] Nie zainicjowany — pomijam TTS"); onDone?.Invoke(); return; } if (audioSource.isPlaying) audioSource.Stop(); StartCoroutine(SpeakCoroutine(text, onDone)); } public void Stop() { StopAllCoroutines(); audioSource.Stop(); } private IEnumerator SpeakCoroutine(string text, Action onDone) { // [SHERPA] Zastąp ten blok wywołaniem sherpa-onnx: // // var audio = _tts.Generate(text, speakerId: 0, speed: speed); // var clip = AudioClip.Create("tts", audio.samples.Length, 1, // audio.sampleRate, false); // clip.SetData(audio.samples, 0); // audioSource.clip = clip; // audioSource.Play(); // yield return new WaitUntil(() => !audioSource.isPlaying); Debug.Log($"[TtsManager] (stub) Speak: \"{text}\""); yield return new WaitForSeconds(0.1f); // zastępcze opóźnienie onDone?.Invoke(); } // --- kopiowanie plików ze StreamingAssets --- private IEnumerator EnsureFile(string relative) { string dest = Path.Combine(Application.persistentDataPath, relative); if (File.Exists(dest)) yield break; Directory.CreateDirectory(Path.GetDirectoryName(dest)!); string src = Path.Combine(Application.streamingAssetsPath, relative); #if UNITY_ANDROID && !UNITY_EDITOR using var req = UnityWebRequest.Get(src); yield return req.SendWebRequest(); if (req.result == UnityWebRequest.Result.Success) File.WriteAllBytes(dest, req.downloadHandler.data); else Debug.LogError($"[TtsManager] Błąd kopiowania {relative}: {req.error}"); #else if (File.Exists(src)) File.Copy(src, dest, overwrite: false); yield return null; #endif } private IEnumerator EnsureEspeakData() { // Opcja A: Twoja wtyczka sherpa-onnx ustawia dataDir wewnętrznie // → nic nie rób tutaj. // Opcja B: Spakuj espeak-ng-data/ jako espeak-ng-data.zip w StreamingAssets // i tu go rozpakuj do Application.persistentDataPath. // Opcja C: Użyj ścieżki Application.streamingAssetsPath bezpośrednio, // jeśli Twoja wtyczka obsługuje adb-accessible paths. Debug.Log("[TtsManager] espeak-ng-data: zweryfikuj obsługę w wybranej wtyczce — patrz README"); yield return null; } } }