From ce2b99948ddfad0031e07c8eeb01a54a9ccfb72a Mon Sep 17 00:00:00 2001 From: Oskar Kapala Date: Wed, 10 Jun 2026 15:36:03 +0200 Subject: [PATCH] chore: drop Unity no-LLM PoC-1 (superseded by native OpenXR coexistence spec) --- docs/ONDEVICE.md | 117 ---------------- ondevice/README.md | 193 -------------------------- ondevice/Scripts/CommandBank.cs | 118 ---------------- ondevice/Scripts/SessionController.cs | 125 ----------------- ondevice/Scripts/TtsManager.cs | 168 ---------------------- 5 files changed, 721 deletions(-) delete mode 100644 docs/ONDEVICE.md delete mode 100644 ondevice/README.md delete mode 100644 ondevice/Scripts/CommandBank.cs delete mode 100644 ondevice/Scripts/SessionController.cs delete mode 100644 ondevice/Scripts/TtsManager.cs diff --git a/docs/ONDEVICE.md b/docs/ONDEVICE.md deleted file mode 100644 index b0e6d3e..0000000 --- a/docs/ONDEVICE.md +++ /dev/null @@ -1,117 +0,0 @@ -# ONDEVICE — PoC-1 na Meta Quest 3 - -Cel buildu dla pierwszego uruchomienia na urządzeniu. Spójny z `ROADMAP.md`. - -## 1. Cel PoC-1 -Uruchomić na Quest 3 minimalną aplikację, która w **passthrough** wyświetla i -**wypowiada** (Piper przez sherpa-onnx) polskie polecenia, oraz **zmierzyć -wydajność w realu**. **Bez żywego LLM na urządzeniu** (decyzja). **Bez STT.** - -## 2. Zakres -W zakresie: -- bank poleceń wygenerowany offline (z istniejącego `ipin_vr`), -- TTS na urządzeniu (sherpa-onnx + głos polski), -- minimalne UI w passthrough (panel z tekstem + trigger „następne"), -- instrumentacja i pomiar wydajności. - -Poza zakresem PoC-1: LLM na urządzeniu, STT, ocena wykonania, dopracowane UI/MR, -obiekty 3D. (To etap 2/3 wg `ROADMAP.md`.) - -## 3. Runtime — decyzja -**Unity** dla PoC-1. -- Uzasadnienie: najszybsza droga do działającego passthrough + TTS; narzut RAM - Unity (~1 GiB) jest nieistotny, bo bez LLM cały budżet spokojnie mieści się w - limicie 5,75 GiB. Meta XR SDK daje passthrough „z pudełka", a sherpa-onnx ma - gotowe wtyczki do Unity. -- Cel docelowego produktu pozostaje **natywny OpenXR** (lżejszy, gdy dojdzie LLM). -- To jedyna łatwo odwracalna decyzja architektoniczna tego dokumentu; reszta nie - zależy od wyboru runtime. - -## 4. Komponenty - -### 4.1 Bank poleceń (offline) -Rozszerzyć `ipin_vr` o podkomendę `export`, która zrzuca bank gotowych, -poprawnych poleceń do JSON (reużywamy zweryfikowanego generatora — gramatyka -gwarantowana u źródła). - -Wywołanie: -``` -python -m ipin_vr export --per-level 300 --out commands.json [--seed N] -``` -Format pliku: -```json -{ - "meta": {"version": "1", "per_level": 300}, - "commands": [ - {"level": 1, "text": "Połóż jabłko na stole."}, - {"level": 2, "text": "Umieść niebieskie jabłko na stole."}, - {"level": 3, "text": "Połóż zieloną książkę na półce i umieść klucz na biurku."} - ] -} -``` -Wymóg: deduplikacja w obrębie poziomu; `--per-level` to liczba unikatów na poziom -(jeśli przestrzeń jest mniejsza, tyle ile się da). Plik trafia do assetów aplikacji. - -### 4.2 TTS na urządzeniu -- Biblioteka: **sherpa-onnx** przez wtyczkę Unity (np. `Ponyu-dev/Unity-Sherpa-ONNX` - lub `EitanWong/com.eitan.sherpa-onnx-unity`). -- Głos: **`vits-piper-pl_PL-gosia-medium`** (z releasu `tts-models` sherpa-onnx; - zawiera `.onnx`, `tokens.txt`, `espeak-ng-data`). -- ABI: **arm64-v8a**. -- Haczyk do obsłużenia: modele w `StreamingAssets` są spakowane w APK — przed - użyciem skopiować do ścieżki zapisywalnej i podać tę nową ścieżkę do sherpa-onnx - (wtyczki zwykle robią to same; zweryfikować). - -### 4.3 Passthrough UI -- Passthrough przez **Meta XR SDK** (building block / komponent passthrough). -- Jeden panel (world-space lub HUD) z tekstem polecenia, czcionka z polskimi znakami. -- Trigger „następne polecenie": przycisk kontrolera albo prosty przycisk na panelu. - -## 5. Podział pracy: kod vs Unity Editor -Część generowalna kodem (Claude Code): -- podkomenda `export` w `ipin_vr` (+ test), -- skrypty C# (ładowanie `commands.json`, wybór poziomu, wyświetlanie tekstu, - wywołanie TTS, obsługa triggera), -- `ondevice/README.md` z instrukcją złożenia projektu. - -Część ręczna w Unity Editorze (nie da się w pełni wygenerować plikami): -- utworzenie projektu Unity (aktualne LTS) i konfiguracja Androida/OpenXR, -- instalacja Meta XR SDK i wtyczki sherpa-onnx, -- włączenie passthrough, podpięcie skryptów do sceny, import głosu i `commands.json`, -- build i sideload APK na Quest. - -Skrypty C# mają być samodzielne i podpinalne, nie zakładać konkretnej hierarchii sceny. - -## 6. Przepływ aplikacji -1. Start: passthrough on, wczytaj `commands.json` z assetów. -2. Ustaw poziom (domyślnie 1; prosty przełącznik 1/2/3). -3. Pokaż losowe polecenie danego poziomu na panelu. -4. Wypowiedz je (sherpa-onnx). -5. Trigger „następne" → wróć do kroku 3. - -## 7. Pomiar wydajności (właściwy cel PoC-1) -Zmierzyć i zanotować: -- PSS aplikacji względem limitu 5,75 GiB, -- stabilność klatek przy włączonym passthrough (spadki/utracone klatki), -- latencja TTS: od wywołania do pierwszego dźwięku oraz czas pełnej syntezy, -- RAM zajmowany przez model głosu, -- moment wejścia w throttling termiczny przy dłuższym użyciu, -- zużycie baterii. - -Narzędzia: OVR Metrics Tool, Perfetto, `logcat` (m.in. `lowmemorykiller`). - -## 8. Środowisko developerskie -Najpierw **sanity check bez kodu**: wgrać prebudowane sherpa-onnx TTS APK z -`pl_PL-gosia` na telefon z Androidem (lub sideload na Quest) i potwierdzić jakość -polskiego głosu. Dopiero potem budować aplikację Unity. Iteracja skryptów/logiki -najszybsza na telefonie Snapdragon; pomiary docelowe — na Queście. - -## 9. Definition of done -- Apka na Queście w passthrough pokazuje i wypowiada polskie polecenia z banku. -- Trigger „następne" działa; poziomy 1–3 dostępne. -- Zebrany komplet metryk z sekcji 7 (choćby zgrubnie) — to jest wynik PoC-1. - -## 10. Dalej (poza PoC-1) -- PoC-2: dołożyć llama.cpp na urządzeniu i zmierzyć ponownie (żywy LLM). -- Etap 3: STT + ocena wykonania, sesje, statystyki. -- Ewentualne przejście runtime na natywny OpenXR pod docelowy budżet RAM z LLM. diff --git a/ondevice/README.md b/ondevice/README.md deleted file mode 100644 index aaf0997..0000000 --- a/ondevice/README.md +++ /dev/null @@ -1,193 +0,0 @@ -# ondevice — składanie projektu Unity (PoC-1, Meta Quest 3) - -Skrypty C# w `Scripts/` są gotowe do podpięcia. Poniżej lista ręcznych kroków -w Unity Editorze (nie da się ich wygenerować plikami). Odpowiada §5 z `docs/ONDEVICE.md`. - ---- - -## 0. Wymagania wstępne - -| Narzędzie | Wersja | -|---|---| -| Unity | 2022 LTS (6000.x) lub 2023 LTS | -| Android Build Support + OpenJDK | przez Unity Hub | -| Meta XR SDK | aktualny (via Package Manager → Add by git URL) | -| sherpa-onnx Unity plugin | patrz krok 4 | -| TextMeshPro | com.unity.textmeshpro (wbudowany w Unity) | - ---- - -## 1. Nowy projekt Unity - -1. Utwórz projekt **3D (URP)** lub **3D (Built-in)**. -2. Otwórz **Edit → Project Settings → Player → Android**: - - Target Architecture: **ARM64** - - Minimum API: **29** (Quest 3) - - Target API: **32** (lub latest) -3. Otwórz **Edit → Project Settings → XR Plug-in Management → Android**: - - Zaznacz **OpenXR** - - W zakładce OpenXR dodaj **Meta Quest feature set** -4. Otwórz **Edit → Project Settings → XR Plug-in Management → OpenXR**: - - Zaznacz **Meta Quest Support** i **Hand Tracking Subsystem** (opcjonalnie). - ---- - -## 2. Meta XR SDK — passthrough - -1. W **Package Manager** dodaj Meta XR SDK: - ``` - https://npm.developer.oculus.com (scope: com.meta.xr) - ``` - Zainstaluj: **Meta XR All-in-One SDK** lub minimum **Meta XR Core SDK**. -2. Utwórz scenę z **OVRCameraRig** (lub użyj prefabu z SDK). -3. Na `OVRManager` włącz **Passthrough Support = Required**. -4. Dodaj komponent **OVRPassthroughLayer** do kamery (Type: Underlay). - ---- - -## 3. Bank poleceń — generowanie i import - -```bash -# W repo ipin-vr (Python): -python -m ipin_vr export --per-level 300 --out commands.json --seed 42 -``` - -Następnie: -1. Utwórz katalog **Assets/StreamingAssets/** (jeśli nie istnieje). -2. Skopiuj `commands.json` do **Assets/StreamingAssets/commands.json**. - ---- - -## 4. sherpa-onnx — instalacja wtyczki i modelu głosu - -### 4a. Wtyczka - -Wybierz jedną z opcji: - -**Opcja A — ręczne .aar/.dll z wydania sherpa-onnx:** -1. Pobierz z https://github.com/k2-fsa/sherpa-onnx/releases pliki dla Unity - (szukaj `unity-*` w Assets danego wydania). -2. Umieść `.dll` (Windows/Editor) i `.aar` (Android) w `Assets/Plugins/`. - -**Opcja B — pakiet UPM (community):** -``` -https://github.com/EitanWong/com.eitan.sherpa-onnx-unity.git -``` -Dodaj przez **Package Manager → Add package from git URL**. - -### 4b. Model głosu polskiego - -1. Pobierz `vits-piper-pl_PL-gosia-medium.tar.bz2` z: - https://github.com/k2-fsa/sherpa-onnx/releases/tag/tts-models -2. Rozpakuj — otrzymasz katalog `vits-piper-pl_PL-gosia-medium/` zawierający: - - `pl_PL-gosia-medium.onnx` - - `tokens.txt` - - `espeak-ng-data/` (katalog) -3. Skopiuj cały katalog do **Assets/StreamingAssets/**: - ``` - Assets/StreamingAssets/ - commands.json - vits-piper-pl_PL-gosia-medium/ - pl_PL-gosia-medium.onnx - tokens.txt - espeak-ng-data/ - ``` - -### 4c. Aktywacja silnika TTS w kodzie - -Po zainstalowaniu wtyczki odkomentuj bloki oznaczone `[SHERPA]` w `TtsManager.cs`: -- `using SherpaOnnx;` (lub odpowiednia przestrzeń nazw wtyczki) -- blok konfiguracji `OfflineTtsConfig` w `InitCoroutine` -- blok `_tts.Generate(...)` w `SpeakCoroutine` - -**Uwaga na espeak-ng-data:** Weryfikuj, czy Twoja wtyczka kopiuje `espeak-ng-data` -automatycznie ze StreamingAssets. Jeśli nie — spakuj katalog jako `.zip` i rozpakowuj -w `TtsManager.EnsureEspeakData()`. - ---- - -## 5. Scena — podpinanie skryptów - -### 5a. Obiekty w hierarchii - -``` -Scene -├── OVRCameraRig ← z Meta XR SDK -│ └── CenterEyeAnchor -├── SessionManager ← pusty GameObject -│ ├── CommandBank.cs -│ ├── TtsManager.cs -│ └── SessionController.cs -└── Canvas (World Space) ← panel UI - ├── LoadingPanel - │ └── TMP_Text "Ładowanie..." - └── SessionPanel - ├── TMP_Text "CommandText" ← główny tekst polecenia - ├── Button "Next" ← trigger "następne" - └── LevelButtons - ├── Button "L1" - ├── Button "L2" - └── Button "L3" -``` - -### 5b. Inspector — SessionController - -| Pole | Przypisz | -|---|---| -| Command Bank | GameObject SessionManager → CommandBank | -| Tts Manager | GameObject SessionManager → TtsManager | -| Command Text | TMP_Text CommandText | -| Loading Panel | GameObject LoadingPanel | -| Session Panel | GameObject SessionPanel | -| Next Button | Button Next | -| Start Level | 1 | - -### 5c. Przyciski poziomu — OnClick - -Każdy przycisk L1/L2/L3: -- Cel: SessionManager → SessionController -- Funkcja: `SetLevel(1)` / `SetLevel(2)` / `SetLevel(3)` - -### 5d. TMP_Text — czcionka z polskimi znakami - -Domyślna czcionka TMP może nie zawierać wszystkich polskich znaków. Aby to naprawić: -1. Pobierz czcionkę z polskim zestawem (np. Noto Sans Regular). -2. Utwórz **TMP Font Asset** (**Window → TextMeshPro → Font Asset Creator**), - zaznaczając znaki Latin Extended-A i Latin Supplement. -3. Przypisz nowy Font Asset do komponentu TMP_Text. - ---- - -## 6. Build i sideload na Quest 3 - -```bash -# W Unity Editor: -# File → Build Settings → Android → Switch Platform -# Player Settings → Package Name: com.twojanazwa.ipinvr -# File → Build And Run (upewnij się, że Quest 3 podłączony przez USB + tryb dev) -``` - -Lub zbuduj APK i sideloaduj ręcznie: -```bash -adb install -r ipin-vr.apk -``` - ---- - -## 7. Pomiar wydajności (cel PoC-1) - -Po uruchomieniu na Queście zbierz metryki wg §7 z `docs/ONDEVICE.md`: -- **OVR Metrics Tool** (sideload z Meta) — FPS, CPU/GPU load, RAM -- **adb logcat** — `lowmemorykiller`, błędy sherpa-onnx -- **Perfetto** — latencja TTS od wywołania `Speak()` do pierwszego dźwięku - -Metryki docelowe: PSS < 5,75 GiB, FPS stable 72/90, latencja TTS < 1 s. - ---- - -## 8. Sanity check bez Unity (zalecane najpierw) - -Zanim zaczniesz budować projekt, przetestuj głos na telefonie z Androidem: -1. Pobierz oficjalne APK TTS ze strony sherpa-onnx. -2. Sideloaduj i sprawdź jakość polskiego głosu `gosia-medium`. -3. Dopiero potem wchodź w pełny projekt Unity. diff --git a/ondevice/Scripts/CommandBank.cs b/ondevice/Scripts/CommandBank.cs deleted file mode 100644 index 7477543..0000000 --- a/ondevice/Scripts/CommandBank.cs +++ /dev/null @@ -1,118 +0,0 @@ -// CommandBank.cs — ładuje commands.json z StreamingAssets, udostępnia losowe -// polecenia per-level. Samoistny MonoBehaviour; podepnij do dowolnego GameObject. -// -// Plik commands.json generuj offline: -// python -m ipin_vr export --per-level 300 --out commands.json -// Następnie umieść go w Assets/StreamingAssets/commands.json. - -using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; -using UnityEngine; -using UnityEngine.Networking; - -namespace IpinVr -{ - [Serializable] - public class CommandEntry - { - public int level; - public string text; - } - - [Serializable] - internal class BankMeta - { - public string version; - public int per_level; - } - - [Serializable] - internal class BankData - { - public BankMeta meta; - public List commands; - } - - public class CommandBank : MonoBehaviour - { - [SerializeField] private string jsonFileName = "commands.json"; - - private readonly Dictionary> _byLevel = new(); - private bool _loaded; - - public bool IsLoaded => _loaded; - - /// Wczytaj bank asynchronicznie. onDone wywoływane po załadowaniu. - public void Load(Action onDone = null) - { - StartCoroutine(LoadCoroutine(onDone)); - } - - private IEnumerator LoadCoroutine(Action onDone) - { - string srcPath = Path.Combine(Application.streamingAssetsPath, jsonFileName); - string json; - -#if UNITY_ANDROID && !UNITY_EDITOR - // Na Androidzie StreamingAssets są wewnątrz APK — czytamy przez WebRequest. - using var req = UnityWebRequest.Get(srcPath); - yield return req.SendWebRequest(); - if (req.result != UnityWebRequest.Result.Success) - { - Debug.LogError($"[CommandBank] Błąd wczytywania {jsonFileName}: {req.error}"); - yield break; - } - json = req.downloadHandler.text; -#else - if (!File.Exists(srcPath)) - { - Debug.LogError($"[CommandBank] Plik nie istnieje: {srcPath}"); - yield break; - } - json = File.ReadAllText(srcPath); - yield return null; -#endif - - var data = JsonUtility.FromJson(json); - if (data?.commands == null) - { - Debug.LogError("[CommandBank] Nie można sparsować commands.json"); - yield break; - } - - _byLevel.Clear(); - foreach (var entry in data.commands) - { - if (!_byLevel.TryGetValue(entry.level, out var list)) - _byLevel[entry.level] = list = new List(); - list.Add(entry.text); - } - - int total = data.commands.Count; - Debug.Log($"[CommandBank] Wczytano {total} poleceń (v{data.meta?.version})"); - _loaded = true; - onDone?.Invoke(); - } - - /// Zwraca losowe polecenie dla danego poziomu, lub null jeśli brak. - public string GetRandom(int level) - { - if (!_loaded) - { - Debug.LogWarning("[CommandBank] Bank nie jest jeszcze wczytany"); - return null; - } - if (!_byLevel.TryGetValue(level, out var list) || list.Count == 0) - { - Debug.LogWarning($"[CommandBank] Brak poleceń dla poziomu {level}"); - return null; - } - return list[UnityEngine.Random.Range(0, list.Count)]; - } - - public int CountForLevel(int level) => - _byLevel.TryGetValue(level, out var l) ? l.Count : 0; - } -} diff --git a/ondevice/Scripts/SessionController.cs b/ondevice/Scripts/SessionController.cs deleted file mode 100644 index b90e0ad..0000000 --- a/ondevice/Scripts/SessionController.cs +++ /dev/null @@ -1,125 +0,0 @@ -// SessionController.cs — główna logika aplikacji (ONDEVICE §6): -// Start → wczytaj bank → ustaw poziom → pokaż losowe polecenie → TTS -// → trigger "następne" → pokaż kolejne. -// -// Samoistny MonoBehaviour; podepnij do dowolnego GameObject w scenie. -// Wymagane referencje do uzupełnienia w Inspectorze: -// • commandBank — GameObject z CommandBank.cs -// • ttsManager — GameObject z TtsManager.cs -// • commandText — komponent TextMeshProUGUI z tekstem polecenia -// • loadingPanel — panel widoczny podczas ładowania (opcjonalnie) -// • sessionPanel — panel widoczny podczas sesji -// Przyciski poziomu: w Inspectorze podepnij OnClick → SetLevel(1/2/3). -// Trigger "następne": podepnij OnClick → OnNextTrigger(). - -using System; -using TMPro; -using UnityEngine; -using UnityEngine.UI; - -namespace IpinVr -{ - public class SessionController : MonoBehaviour - { - [Header("Komponenty (wymagane)")] - [SerializeField] private CommandBank commandBank; - [SerializeField] private TtsManager ttsManager; - [SerializeField] private TMP_Text commandText; // TextMeshPro — wymaga pakietu com.unity.textmeshpro - - [Header("Panele UI (opcjonalne)")] - [SerializeField] private GameObject loadingPanel; - [SerializeField] private GameObject sessionPanel; - - [Header("Przycisk 'Następne' (opcjonalny — można też wołać OnNextTrigger() z kodu)")] - [SerializeField] private Button nextButton; - - [Header("Ustawienia")] - [SerializeField] [Range(1, 3)] private int startLevel = 1; - - private int _currentLevel; - private bool _busy; // podczas TTS nie przyjmujemy triggera - - private void Start() - { - _currentLevel = startLevel; - SetPanelsVisible(loading: true); - - if (nextButton != null) - nextButton.onClick.AddListener(OnNextTrigger); - - if (commandBank == null || ttsManager == null) - { - Debug.LogError("[Session] Brakuje referencji CommandBank lub TtsManager w Inspectorze"); - return; - } - - commandBank.Load(() => - ttsManager.Initialize(() => - { - SetPanelsVisible(loading: false); - ShowNext(); - }) - ); - } - - private void OnDestroy() - { - if (nextButton != null) - nextButton.onClick.RemoveListener(OnNextTrigger); - } - - // --- publiczne API --- - - /// Trigger "następne polecenie" — z przycisku kontrolera lub UI. - public void OnNextTrigger() - { - if (!_busy) - ShowNext(); - } - - /// Zmień poziom (1–3). Wywoływane przez przyciski poziomu w UI. - public void SetLevel(int level) - { - if (level < 1 || level > 3) - { - Debug.LogWarning($"[Session] Nieprawidłowy poziom: {level}"); - return; - } - _currentLevel = level; - Debug.Log($"[Session] Poziom → {level}"); - if (!_busy) - ShowNext(); - } - - // --- logika wewnętrzna --- - - private void ShowNext() - { - if (!commandBank.IsLoaded || !ttsManager.IsReady) - return; - - string text = commandBank.GetRandom(_currentLevel); - if (text == null) - { - commandText.text = $"(brak poleceń dla poziomu {_currentLevel})"; - return; - } - - commandText.text = text; - _busy = true; - if (nextButton != null) nextButton.interactable = false; - - ttsManager.Speak(text, onDone: () => - { - _busy = false; - if (nextButton != null) nextButton.interactable = true; - }); - } - - private void SetPanelsVisible(bool loading) - { - if (loadingPanel != null) loadingPanel.SetActive(loading); - if (sessionPanel != null) sessionPanel.SetActive(!loading); - } - } -} diff --git a/ondevice/Scripts/TtsManager.cs b/ondevice/Scripts/TtsManager.cs deleted file mode 100644 index 9462cc8..0000000 --- a/ondevice/Scripts/TtsManager.cs +++ /dev/null @@ -1,168 +0,0 @@ -// 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; - } - } -}