termux-tts 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [1.4.0] - 2026-09-07
6
+
7
+ ### Added
8
+ - Direct integration with `TtsAdapter` from `ameva_runtime.adapters` SSOT.
9
+ - Dual-tier VITS Vulkan neural engine routing with strict Fail-Fast error semantics.
10
+ - English localization for all diagnostics, logs, and exception messages.
11
+
12
+ ---
13
+
5
14
  ## [1.1.5] - 2026-09-05
6
15
 
7
16
  ### Changed
@@ -12,6 +12,8 @@ class TTSEngine {
12
12
  this.preset = options.preset || 'balanced';
13
13
  this.sampleRate = options.sampleRate || 22050;
14
14
  this.engine = options.engine || 'auto';
15
+ this.device = options.device || (options.gpu ? 'gpu' : 'auto');
16
+ this.tier = options.tier || null;
15
17
  this.model = options.model || null;
16
18
  }
17
19
 
@@ -24,6 +26,8 @@ class TTSEngine {
24
26
  const speed = typeof options.speed === 'number' ? options.speed : 1.0;
25
27
  const preset = options.preset || this.preset;
26
28
  const engineType = options.engine || this.engine;
29
+ const deviceType = options.device || (options.gpu ? 'gpu' : this.device);
30
+ const tierType = options.tier || this.tier;
27
31
  const modelPath = options.model || this.model;
28
32
 
29
33
  const payload = JSON.stringify({
@@ -34,6 +38,8 @@ class TTSEngine {
34
38
  speed: speed,
35
39
  preset: preset,
36
40
  engine: engineType,
41
+ device: deviceType,
42
+ tier: tierType,
37
43
  model: modelPath
38
44
  });
39
45
 
@@ -49,7 +55,9 @@ try:
49
55
  language=data.get('language', 'ko'),
50
56
  preset=data.get('preset', 'balanced'),
51
57
  sample_rate=data.get('sample_rate', 22050),
52
- engine=data.get('engine', 'auto')
58
+ engine=data.get('engine', 'auto'),
59
+ device=data.get('device', 'auto'),
60
+ tier=data.get('tier', None)
53
61
  )
54
62
  res = engine.synthesize(data['text'], output=data['output'], speed=float(data.get('speed', 1.0)))
55
63
  result = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "termux-tts",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "On-device Text-to-Speech framework utilizing device resources (DSP Formant Vocoder, ONNX Neural Runtime & Android Native Voice)",
5
5
  "main": "index.js",
6
6
  "bin": {
package/pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "termux-tts"
7
- version = "1.3.0"
7
+ version = "1.4.0"
8
8
  description = "On-device 4-Tier Text-to-Speech framework utilizing device resources (DSP Formant Vocoder, C++ Sherpa-ONNX Neural, Android Native & Expressive)"
9
9
  readme = "README.pypi.md"
10
10
  requires-python = ">=3.10"
@@ -29,7 +29,7 @@ from .exceptions import (
29
29
  ONNXNeuralEngine = SherpaNeuralEngine
30
30
  ONNXResult = SherpaResult
31
31
 
32
- __version__ = "1.3.0"
32
+ __version__ = "1.4.0"
33
33
  __all__ = [
34
34
  "TTSEngine",
35
35
  "load",
package/termux_tts/cli.py CHANGED
@@ -29,7 +29,11 @@ def main():
29
29
  )
30
30
  synth_parser.add_argument("-m", "--model", default=None, help="Path to model file or directory")
31
31
  synth_parser.add_argument("-p", "--preset", default="balanced", choices=["fast", "balanced", "expressive", "ultra"])
32
- synth_parser.add_argument("-d", "--device", default="auto", choices=["auto", "gpu", "vulkan", "cpu"])
32
+ synth_parser.add_argument("-d", "--device", default="auto", choices=["auto", "gpu", "vulkan", "cpu"], help="Compute target device")
33
+ synth_parser.add_argument("-b", "--backend", dest="device", choices=["auto", "gpu", "vulkan", "cpu"], help="Alias for --device")
34
+ synth_parser.add_argument("--gpu", dest="device", action="store_const", const="gpu", help="Enable hardware GPU acceleration")
35
+ synth_parser.add_argument("--cpu", dest="device", action="store_const", const="cpu", help="Force CPU compute mode")
36
+ synth_parser.add_argument("--tier", default=None, choices=["high", "medium", "balanced", "fast", "ultra"], help="Target model tier (high=Studio FP16, medium=Balanced)")
33
37
  synth_parser.add_argument("-s", "--speed", type=float, default=1.0, help="Speech speed multiplier (0.5 to 2.0)")
34
38
  synth_parser.add_argument("--threads", type=int, default=4, help="Compute worker threads (ARM NEON)")
35
39
  synth_parser.add_argument("--volume", type=int, default=None, help="Set Android media volume (1 to 15)")
@@ -78,6 +82,7 @@ def main():
78
82
  device=args.device,
79
83
  threads=args.threads,
80
84
  engine=args.engine,
85
+ tier=getattr(args, "tier", None),
81
86
  ) as engine:
82
87
  res = engine.synthesize(args.text, output=args.output, speed=args.speed)
83
88
  backend_name = getattr(res, "backend", "UNKNOWN")
@@ -224,9 +224,10 @@ class TTSControl(ComponentControl):
224
224
  """기존 TTSEngine doctor() 호출 — 12단계 Vulkan Doctor 포함."""
225
225
  lite = self.doctor_lite()
226
226
  try:
227
- from termux_tts.vulkan_probe import VulkanDoctor
228
- vd = VulkanDoctor()
229
- lite["vulkan"] = vd.probe() if hasattr(vd, "probe") else {"note": "probe() not available"}
227
+ from termux_tts.engine import doctor
228
+ doc_rep = doctor()
229
+ lite["doctor"] = doc_rep
230
+ lite["vulkan"] = doc_rep.get("doctor_report") or {"status": doc_rep.get("status")}
230
231
  except Exception as e:
231
232
  lite["vulkan_error"] = str(e)
232
233
  lite["doctor_level"] = "full"
@@ -22,7 +22,12 @@ from .engine_dsp import ParametricDSPEngine, DSPResult, QUALITY_PRESETS
22
22
  from .engine_sherpa import SherpaNeuralEngine, SherpaResult
23
23
  from .engine_vulkan import VulkanNeuralEngine, VulkanResult
24
24
  from .engine_expressive import ExpressiveEngine, ExpressiveResult
25
- from .vulkan_probe import VulkanDoctor
25
+ from .hardware import (
26
+ resolve_device_backend,
27
+ bind_tts_hardware,
28
+ _resolve_ameva_runtime,
29
+ ERROR_AMEVA_TTS_E001,
30
+ )
26
31
 
27
32
  logger = logging.getLogger("termux_tts.engine")
28
33
 
@@ -39,6 +44,7 @@ class TTSEngine:
39
44
  threads: int = 4,
40
45
  sample_rate: Optional[int] = None,
41
46
  engine_type: str = "auto",
47
+ model_tier: Optional[str] = None,
42
48
  ):
43
49
  self.language = language.lower()
44
50
  self.preset = preset.lower()
@@ -47,32 +53,26 @@ class TTSEngine:
47
53
  self.model_path = model_path
48
54
  self.threads = threads
49
55
  self.sample_rate = sample_rate
50
- self.device = self.requested_device
51
- self.backend = "auto"
56
+ self.model_tier = model_tier
52
57
  self._is_closed = False
53
58
 
54
- if self.requested_device in ("vulkan", "gpu"):
55
- doc = VulkanDoctor()
56
- if not doc.is_vulkan_available:
57
- raise VulkanInitializationError(
58
- f"[FAIL-FAST] Explicit GPU backend requested ('{self.requested_device}'), "
59
- "but Vulkan hardware runtime is unavailable. Use '--device cpu' or '--device auto'."
60
- )
59
+ # 1. Resolve effective device and engine_type via safe hardware gateway
60
+ self.device, effective_engine = resolve_device_backend(
61
+ self.requested_device, self.requested_engine_type
62
+ )
63
+ if self.requested_engine_type == "auto" and effective_engine != "auto":
64
+ self.requested_engine_type = effective_engine
65
+
66
+ self.backend = "auto"
61
67
 
62
- # Attempt ameva-runtime hardware binding if available
68
+ # 2. Attempt ameva-runtime hardware binding if available
63
69
  self._binding_plan = self._bind_hardware()
64
70
 
65
71
  self.native_engine = NativeAndroidEngine(language=language)
66
72
  self.synth_engine = self._resolve_synth_engine()
67
73
 
68
74
  def _bind_hardware(self):
69
- try:
70
- from ameva_runtime.adapters.tts import TtsAdapter
71
- binding = TtsAdapter.bind(engine=self, requested_backend=self.requested_device)
72
- return binding
73
- except Exception as e:
74
- logger.debug("Hardware adapter binding skipped: %s", e)
75
- return None
75
+ return bind_tts_hardware(self, self.requested_device)
76
76
 
77
77
  def _resolve_synth_engine(self):
78
78
  t = self.requested_engine_type
@@ -86,6 +86,7 @@ class TTSEngine:
86
86
  device=self.requested_device,
87
87
  threads=self.threads,
88
88
  sample_rate=self.sample_rate or 22050,
89
+ model_tier=self.model_tier,
89
90
  )
90
91
  except (VulkanInitializationError, TTSModelLoadError) as err:
91
92
  if t in ("vulkan", "gpu", "ncnn") or self.requested_device in ("vulkan", "gpu"):
@@ -214,6 +215,7 @@ def load(
214
215
  threads: int = 4,
215
216
  sample_rate: Optional[int] = None,
216
217
  engine: str = "auto",
218
+ tier: Optional[str] = None,
217
219
  ) -> TTSEngine:
218
220
  return TTSEngine(
219
221
  model_path=model,
@@ -223,28 +225,31 @@ def load(
223
225
  threads=threads,
224
226
  sample_rate=sample_rate,
225
227
  engine_type=engine,
228
+ model_tier=tier,
226
229
  )
227
230
 
228
231
 
229
232
  def doctor() -> Dict[str, Any]:
230
- try:
231
- from ameva_runtime import vulkan as avr
232
- from ameva_runtime.vulkan.adapters import TtsAdapter
233
- doc = avr.Doctor()
234
- rep = doc.run_self_test(verbose=False)
235
- return {
236
- "doctor_report": rep,
237
- "overall_success": getattr(rep, "overall_success", False),
238
- "passed_stages": getattr(rep, "passed_stages", 0),
239
- "recommended_backend": getattr(rep, "recommended_backend", "cpu"),
240
- "status": "DIAGNOSED_VIA_AMEVA",
241
- }
242
- except Exception as e:
243
- return {
244
- "doctor_report": None,
245
- "overall_success": False,
246
- "passed_stages": 0,
247
- "recommended_backend": "cpu_neon",
248
- "error": str(e),
249
- "status": "FALLBACK_CPU",
250
- }
233
+ ameva_mod = _resolve_ameva_runtime()
234
+ if ameva_mod is not None:
235
+ try:
236
+ from ameva_runtime.adapters import TtsAdapter
237
+ adapter = TtsAdapter()
238
+ rep = adapter.resolve_diagnostic_report()
239
+ return {
240
+ "doctor_report": rep,
241
+ "overall_success": getattr(rep, "overall_success", False),
242
+ "passed_stages": getattr(rep, "passed_stages", 0),
243
+ "recommended_backend": getattr(rep, "recommended_backend", "cpu_neon"),
244
+ "status": "DIAGNOSED_VIA_AMEVA",
245
+ }
246
+ except Exception as e:
247
+ logger.debug("AMEVA Doctor invocation exception: %s", e)
248
+
249
+ return {
250
+ "doctor_report": None,
251
+ "overall_success": False,
252
+ "passed_stages": 0,
253
+ "recommended_backend": "cpu_neon",
254
+ "status": "NO_AMEVA_RUNTIME",
255
+ }
@@ -14,7 +14,6 @@ from typing import Optional
14
14
  from .exceptions import TTSInferenceError, VulkanInitializationError
15
15
  from .tokenizer import PhoneticTokenizer
16
16
  from .audio import AudioBuffer
17
- from .vulkan_probe import VulkanDoctor
18
17
 
19
18
  def _detect_cpu_backend() -> str:
20
19
  """Detect current host CPU architecture dynamically."""
@@ -171,21 +170,30 @@ class ParametricDSPEngine:
171
170
  self.tokenizer = PhoneticTokenizer(language=language)
172
171
  self._is_closed = False
173
172
 
174
- self.doctor = VulkanDoctor()
175
- self.diag_info = self.doctor.probe_all() if self.requested_device != "cpu" else {}
173
+ self.is_vulkan_available = False
174
+ self.diag_info = {}
175
+ if self.requested_device != "cpu":
176
+ try:
177
+ from ameva_runtime.adapters import TtsAdapter
178
+ rep = TtsAdapter.resolve_diagnostic_report()
179
+ self.is_vulkan_available = bool(getattr(rep, "overall_success", False) or getattr(rep, "recommended_backend", "") == "vulkan")
180
+ self.diag_info = {"recommended_backend": rep.recommended_backend, "passed_stages": rep.passed_stages}
181
+ except Exception as e:
182
+ import logging
183
+ logging.getLogger("termux_tts.engine_dsp").debug("TtsAdapter diagnostic check exception: %s", e)
176
184
  self.backend = self._resolve_backend()
177
185
 
178
186
  def _resolve_backend(self) -> str:
179
187
  cpu_b = _detect_cpu_backend()
180
188
  if self.requested_device in ("vulkan", "gpu"):
181
- if self.doctor.is_vulkan_available:
189
+ if self.is_vulkan_available:
182
190
  return "VULKAN_GPU"
183
191
  raise VulkanInitializationError(
184
192
  f"[FAIL-FAST] Explicit GPU backend requested ('{self.requested_device}'), "
185
193
  "but Vulkan hardware runtime is unavailable. Use '--device cpu' or '--device auto'."
186
194
  )
187
195
  elif self.requested_device == "auto":
188
- return "VULKAN_GPU" if self.doctor.is_vulkan_available else cpu_b
196
+ return "VULKAN_GPU" if self.is_vulkan_available else cpu_b
189
197
  return cpu_b
190
198
 
191
199
  def synthesize(self, text: str, output: Optional[str] = None, speed: float = 1.0, preset: Optional[str] = None) -> DSPResult:
@@ -4,6 +4,5 @@ Python onnxruntime is permanently deprecated in favor of C++ isolated SherpaNeur
4
4
  """
5
5
  from .engine_sherpa import SherpaNeuralEngine as ONNXNeuralEngine
6
6
  from .engine_sherpa import SherpaResult as ONNXResult
7
- from .vulkan_probe import VulkanDoctor
8
7
 
9
- __all__ = ["ONNXNeuralEngine", "ONNXResult", "VulkanDoctor"]
8
+ __all__ = ["ONNXNeuralEngine", "ONNXResult"]
@@ -69,17 +69,19 @@ class VulkanNeuralEngine:
69
69
  device: str = "vulkan",
70
70
  threads: int = 1,
71
71
  sample_rate: int = 22050,
72
+ model_tier: Optional[str] = None,
72
73
  ):
73
74
  self.language = language.lower()
74
75
  self.requested_device = device.lower()
75
76
  self.threads = threads
76
77
  self.sample_rate = sample_rate
78
+ self.model_tier = (model_tier or "high").lower()
77
79
  self._is_closed = False
78
80
 
79
81
  # 1. Locate Vulkan binary
80
82
  self.binary = self._find_binary()
81
83
 
82
- # 2. Locate VITS NCNN model directory
84
+ # 2. Locate VITS NCNN model directory with tier awareness
83
85
  self.model_dir = self._resolve_model_dir(model_path)
84
86
  self.model_name = Path(self.model_dir).name
85
87
  self.backend = "VULKAN_GPU_NCNN_VITS"
@@ -88,15 +90,19 @@ class VulkanNeuralEngine:
88
90
  """Locate sherpa-ncnn-offline-tts executable or fail-fast."""
89
91
  for candidate in self.CANDIDATE_BINARIES:
90
92
  found = shutil.which(candidate) if not os.path.isabs(candidate) else candidate
91
- if found and os.path.isfile(found) and os.access(found, os.X_OK):
93
+ if found and os.path.isfile(found) and (os.access(found, os.X_OK) or os.name == "nt"):
92
94
  return str(found)
93
95
  raise VulkanInitializationError(
94
- "[FAIL-FAST] 'sherpa-ncnn-offline-tts' Vulkan binary not found. "
95
- "Please run 'termux-tts install' to automatically download and provision the Vulkan engine."
96
+ "[ERROR: AMEVA-TTS-E001] 'sherpa-ncnn-offline-tts' Vulkan binary not found.\n"
97
+ "Cause: Hardware acceleration runtime or native Vulkan engine is not installed.\n"
98
+ "Action Required: Run one-click provisioning via:\n"
99
+ " $ termux-tts install --tier high\n"
100
+ " (Or run without GPU: termux-tts synth -e dsp -t \"...\")\n"
101
+ "Documentation: https://uno-km.vercel.app/lib/tts/"
96
102
  )
97
103
 
98
104
  def _resolve_model_dir(self, model_path: Optional[str]) -> str:
99
- """Locate directory containing config.json and *.ncnn.bin files."""
105
+ """Locate directory containing config.json and *.ncnn.bin files with tier awareness."""
100
106
  search_dirs: List[Path] = []
101
107
  if model_path:
102
108
  p = Path(model_path).expanduser().resolve()
@@ -104,20 +110,32 @@ class VulkanNeuralEngine:
104
110
  raise TTSModelLoadError(f"[FAIL-FAST] Explicit model path not found: '{model_path}'")
105
111
  search_dirs = [p]
106
112
  else:
113
+ preferred_dir_keyword = "amy-medium" if self.model_tier == "medium" else "lessac-high"
114
+
115
+ # Prioritize tier-specific matching directories first
107
116
  for s in self.STANDARD_MODEL_DIRS:
108
117
  if s.exists():
109
- search_dirs.append(s)
118
+ if preferred_dir_keyword in s.name.lower():
119
+ search_dirs.insert(0, s)
120
+ else:
121
+ search_dirs.append(s)
110
122
  for child in s.glob("ncnn-vits*"):
111
123
  if child.is_dir():
112
- search_dirs.append(child)
124
+ if preferred_dir_keyword in child.name.lower():
125
+ search_dirs.insert(0, child)
126
+ else:
127
+ search_dirs.append(child)
113
128
 
114
129
  for d in search_dirs:
115
130
  if (d / "config.json").exists() and (d / "decoder.ncnn.bin").exists():
116
131
  return str(d)
117
132
 
118
133
  raise TTSModelLoadError(
119
- "[FAIL-FAST] No valid VITS NCNN model directory found. "
120
- "Please run 'termux-tts install --tier high' to automatically download model assets."
134
+ f"[ERROR: AMEVA-TTS-E001] No valid VITS NCNN model directory found (tier: '{self.model_tier}').\n"
135
+ f"Cause: Model weights not found in standard directories.\n"
136
+ f"Action Required: Run one-click provisioning via:\n"
137
+ f" $ termux-tts install --tier {self.model_tier}\n"
138
+ f"Documentation: https://uno-km.vercel.app/lib/tts/"
121
139
  )
122
140
 
123
141
  def synthesize(
@@ -140,18 +158,30 @@ class VulkanNeuralEngine:
140
158
  temp_wav = tmp_file.name
141
159
 
142
160
  try:
143
- cmd = [
144
- self.binary,
145
- f"--vits-model-dir={self.model_dir}",
146
- "--use-vulkan-compute=1",
147
- f"--num-threads={self.threads}",
148
- f"--output-filename={temp_wav}",
149
- clean_text
150
- ]
151
-
152
- env = os.environ.copy()
153
- env["LD_LIBRARY_PATH"] = f"/system/lib64:{env.get('LD_LIBRARY_PATH', '')}"
154
- env["AMEVA_VK_DSP_ACCEL"] = "1"
161
+ try:
162
+ from ameva_runtime.adapters.tts import TtsAdapter
163
+ cmd = TtsAdapter.build_cli_args(
164
+ executable=self.binary,
165
+ model_dir=self.model_dir,
166
+ text=clean_text,
167
+ output_filename=temp_wav,
168
+ threads=self.threads,
169
+ use_vulkan=True,
170
+ )
171
+ is_mali = "mali" in str(getattr(self, "gpu_device", "")).lower()
172
+ env = TtsAdapter.get_execution_env(is_mali=is_mali)
173
+ except Exception:
174
+ cmd = [
175
+ self.binary,
176
+ f"--vits-model-dir={self.model_dir}",
177
+ "--use-vulkan-compute=1",
178
+ f"--num-threads={self.threads}",
179
+ f"--output-filename={temp_wav}",
180
+ clean_text
181
+ ]
182
+ env = os.environ.copy()
183
+ env["LD_LIBRARY_PATH"] = f"/system/lib64:{env.get('LD_LIBRARY_PATH', '')}"
184
+ env["AMEVA_VK_DSP_ACCEL"] = "1"
155
185
 
156
186
  proc = subprocess.run(
157
187
  cmd,
@@ -0,0 +1,121 @@
1
+ """
2
+ Hardware abstraction gateway and dynamic ameva-runtime soft-dependency resolver for termux-tts.
3
+ Provides strict Zero-Silent-Fallback [AMEVA-TTS-E001] compliance, hardware-agnostic routing,
4
+ and seamless fallback to Tier 1 DSP Formant / Tier 2 Native voice when running standalone.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import importlib.util
9
+ import logging
10
+ import sys
11
+ from typing import Optional, Tuple, Any
12
+
13
+ from .exceptions import VulkanInitializationError
14
+
15
+ logger = logging.getLogger("termux_tts.hardware")
16
+
17
+ ERROR_AMEVA_TTS_E001 = (
18
+ "[FAIL-FAST] [ERROR: AMEVA-TTS-E001] GPU acceleration requires 'ameva-runtime' and provisioned Vulkan assets.\n"
19
+ "Cause: Hardware acceleration runtime or native Vulkan engine is not installed.\n"
20
+ "Action Required: Run one-click provisioning via:\n"
21
+ " $ termux-tts install --tier high\n"
22
+ " (Or run without GPU: termux-tts synth -e dsp -t \"...\")\n"
23
+ "Documentation: https://uno-km.vercel.app/lib/tts/"
24
+ )
25
+
26
+
27
+ def _resolve_ameva_runtime() -> Optional[Any]:
28
+ """Check for ameva_runtime availability without top-level static dependency.
29
+
30
+ Returns the ameva_runtime module if installed, otherwise None.
31
+ """
32
+ try:
33
+ spec = importlib.util.find_spec("ameva_runtime")
34
+ if spec is not None:
35
+ import ameva_runtime
36
+ return ameva_runtime
37
+ except (ImportError, AttributeError):
38
+ pass
39
+ return None
40
+
41
+
42
+ def resolve_device_backend(
43
+ requested_device: str,
44
+ requested_engine: str = "auto",
45
+ ) -> Tuple[str, str]:
46
+ """Resolve user requested device/engine into (device, engine_type) with fail-fast compliance.
47
+
48
+ Returns:
49
+ Tuple[str, str]: (device, engine_type) e.g. ('vulkan', 'vulkan') or ('cpu', 'dsp')
50
+ """
51
+ req_dev = (requested_device or "auto").lower().strip()
52
+ req_eng = (requested_engine or "auto").lower().strip()
53
+ ameva_mod = _resolve_ameva_runtime()
54
+
55
+ # Explicit GPU/Vulkan requested
56
+ if req_dev in ("vulkan", "gpu") or req_eng in ("vulkan", "gpu", "ncnn"):
57
+ if ameva_mod is None:
58
+ raise VulkanInitializationError(ERROR_AMEVA_TTS_E001)
59
+
60
+ # Check Vulkan doctor from ameva_runtime TtsAdapter
61
+ try:
62
+ from ameva_runtime.adapters import TtsAdapter
63
+ adapter = TtsAdapter()
64
+ report = adapter.resolve_diagnostic_report()
65
+ is_vk = getattr(report, "overall_success", False) or getattr(report, "recommended_backend", "") == "vulkan"
66
+ if not is_vk:
67
+ raise VulkanInitializationError(
68
+ f"[FAIL-FAST] [ERROR: AMEVA-TTS-E002] Vulkan hardware acceleration is not supported on this device.\n"
69
+ f"Cause: No usable Vulkan physical device or ICD driver library found.\n"
70
+ f"Action Required: Use CPU or DSP synthesis via '--device cpu' or '--engine dsp'."
71
+ )
72
+ except VulkanInitializationError:
73
+ raise
74
+ except Exception as e:
75
+ logger.debug("TtsAdapter diagnostic exception: %s", e)
76
+ raise VulkanInitializationError(
77
+ f"[FAIL-FAST] [ERROR: AMEVA-TTS-E002] Vulkan hardware acceleration check failed: {e}\n"
78
+ f"Action Required: Use CPU or DSP synthesis via '--device cpu' or '--engine dsp'."
79
+ ) from e
80
+
81
+ return "vulkan", "vulkan"
82
+
83
+ # Auto mode: probe if ameva-runtime is available
84
+ if req_dev == "auto":
85
+ if ameva_mod is None:
86
+ sys.stdout.write(
87
+ "[INFO] ameva-runtime GPU engine is not provisioned. Operating in Tier 1 (Parametric DSP Formant) mode.\n"
88
+ )
89
+ sys.stdout.flush()
90
+ return "cpu", req_eng
91
+
92
+ try:
93
+ from ameva_runtime.adapters import TtsAdapter
94
+ adapter = TtsAdapter()
95
+ report = adapter.resolve_diagnostic_report()
96
+ is_vk = getattr(report, "overall_success", False) or getattr(report, "recommended_backend", "") == "vulkan"
97
+ bin_path = adapter.resolve_binary_path()
98
+ if is_vk and bin_path and req_eng in ("auto", "neural", "vits"):
99
+ return "vulkan", "vulkan"
100
+ except Exception as e:
101
+ logger.debug("TtsAdapter auto-routing probe exception: %s", e)
102
+
103
+ return "cpu", req_eng
104
+
105
+ # Explicit CPU
106
+ return "cpu", req_eng
107
+
108
+
109
+ def bind_tts_hardware(engine: Any, requested_device: str) -> Optional[Any]:
110
+ """Safely invoke AMEVA-Runtime TtsAdapter if present to configure engine instance."""
111
+ ameva_mod = _resolve_ameva_runtime()
112
+ if ameva_mod is None:
113
+ return None
114
+
115
+ try:
116
+ from ameva_runtime.adapters.tts import TtsAdapter
117
+ binding = TtsAdapter.bind(engine=engine, requested_backend=requested_device)
118
+ return binding
119
+ except Exception as e:
120
+ logger.debug("Hardware adapter binding skipped: %s", e)
121
+ return None
@@ -1,49 +0,0 @@
1
- """
2
- [DEPRECATED] This file is deprecated. 12-Stage Vulkan diagnostics are 100% delegated to ameva-runtime.
3
- """
4
- from typing import Dict, Any
5
-
6
- class VulkanDoctor:
7
- """Legacy compatibility bridge delegating directly to ameva-runtime."""
8
- def __init__(self):
9
- self.is_vulkan_available = False
10
- self.ameva_runtime_bound = True
11
- self.report = None
12
- self._check_availability()
13
-
14
- def _check_availability(self) -> None:
15
- try:
16
- from ameva_runtime import vulkan as avr
17
- self.is_vulkan_available = bool(avr.is_available())
18
- except Exception:
19
- self.is_vulkan_available = False
20
-
21
- def probe_all(self) -> Dict[str, Any]:
22
- try:
23
- from ameva_runtime import vulkan as avr
24
- doc = avr.Doctor()
25
- rep = doc.run_self_test(verbose=False)
26
- self.report = rep
27
- rec = getattr(rep, "recommended_backend", "")
28
- passed = getattr(rep, "passed_stages", 0)
29
- self.is_vulkan_available = bool(
30
- getattr(rep, "overall_success", False)
31
- or rec in ("vulkan", "vulkan_driver_only")
32
- or passed >= 7
33
- or avr.is_available()
34
- )
35
- device_name = getattr(rep, "device_name", None) or doc.quick_probe_device() or "Mali-G68"
36
- return {
37
- "overall_success": self.is_vulkan_available,
38
- "passed_stages": passed,
39
- "recommended_backend": rec or "vulkan",
40
- "status": "BOUND_AMEVA_VULKAN",
41
- "DeviceName": device_name,
42
- "DeviceModel": device_name,
43
- }
44
- except Exception as e:
45
- self.is_vulkan_available = False
46
- return {"overall_success": False, "passed_stages": 0, "status": "FALLBACK_CPU", "error": str(e)}
47
-
48
- def probe(self) -> Dict[str, Any]:
49
- return self.probe_all()