169 lines
6.4 KiB
C#
169 lines
6.4 KiB
C#
|
|
// 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<AudioSource>();
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>Kopiuje pliki z StreamingAssets i inicjuje silnik TTS.</summary>
|
||
|
|
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();
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>Syntezuje text i odtwarza. Wywołuje onDone po zakończeniu odtwarzania.</summary>
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|