dsh-her 0.1.0 → 0.3.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.
Files changed (39) hide show
  1. package/README.md +1 -1
  2. package/lib/client.js +862 -506
  3. package/lib/core/character.js +1 -1
  4. package/lib/core/index.js +3 -1
  5. package/lib/core/sounds.js +31 -0
  6. package/lib/core/spoken-numbers.js +90 -0
  7. package/lib/core/voices.js +31 -1
  8. package/lib/dsh/api.js +53 -0
  9. package/lib/dsh/body-install.js +25 -8
  10. package/lib/dsh/body-launcher.js +1 -1
  11. package/lib/dsh/plugin.js +129 -14
  12. package/lib/platform.js +12 -0
  13. package/lib/types/core/character.d.ts +1 -1
  14. package/lib/types/core/index.d.ts +3 -1
  15. package/lib/types/core/sounds.d.ts +21 -0
  16. package/lib/types/core/spoken-numbers.d.ts +11 -0
  17. package/lib/types/core/voices.d.ts +18 -2
  18. package/lib/types/dsh/api.d.ts +37 -1
  19. package/lib/types/dsh/body-install.d.ts +5 -1
  20. package/lib/types/dsh/plugin.d.ts +2 -0
  21. package/lib/types/platform.d.ts +9 -0
  22. package/lib/types/voice/ming-model.d.ts +62 -0
  23. package/lib/types/voice/ming-voices.d.ts +75 -0
  24. package/lib/types/voice/ming.d.ts +103 -0
  25. package/lib/types/voice/speech-queue.d.ts +4 -2
  26. package/lib/types/voice/uv.d.ts +12 -0
  27. package/lib/types/voice/voice.d.ts +14 -6
  28. package/lib/voice/edge.js +4 -48
  29. package/lib/voice/ming-model.js +30 -0
  30. package/lib/voice/ming-voices.js +181 -0
  31. package/lib/voice/ming.js +335 -0
  32. package/lib/voice/models.js +2 -1
  33. package/lib/voice/speech-queue.js +5 -3
  34. package/lib/voice/uv.js +61 -0
  35. package/lib/voice/voice.js +46 -10
  36. package/package.json +2 -1
  37. package/python/ming-requirements.in +1 -0
  38. package/python/ming-requirements.txt +887 -0
  39. package/python/ming_worker.py +77 -0
@@ -0,0 +1,77 @@
1
+ """dsh-her's Ming voice (src/voice/ming.ts starts it with its own Python).
2
+
3
+ One JSON request per line on stdin, one JSON reply per line on stdout; the
4
+ model is loaded once and stays loaded. Everything the libraries print goes
5
+ to stderr, so stdout carries only replies.
6
+
7
+ {"op": "load", "id": n, "model": dir} -> {"id": n, "ok": true, "loadMs": n}
8
+ {"op": "speak", "id": n, "text": ..., -> {"id": n, "ok": true, "pcm16": base64,
9
+ "voice": built-in name "sampleRate": 44100, "ms": n}
10
+ | "ref": {"wav": path, "text": ...} (or {"id": n, "ok": false, "error": ...})
11
+ | "design": a description of the voice,
12
+ "emotion": "高兴"?, "speed": "慢速"?, "steps": 10, "seed": n?}
13
+ """
14
+
15
+ import base64
16
+ import json
17
+ import sys
18
+ import time
19
+
20
+ replies = sys.stdout
21
+ sys.stdout = sys.stderr
22
+
23
+ import mlx.core as mx # noqa: E402
24
+ import numpy as np # noqa: E402
25
+ from mlx_audio.tts.models.bailingmm.bailingmm import ming3_instruction # noqa: E402
26
+ from mlx_audio.tts.utils import load_model # noqa: E402
27
+
28
+ model = None
29
+
30
+
31
+ def reply(message):
32
+ replies.write(json.dumps(message, ensure_ascii=False) + "\n")
33
+ replies.flush()
34
+
35
+
36
+ def speak(request):
37
+ args = {"text": request["text"], "ddpm_steps": int(request.get("steps", 10))}
38
+ emotion = request.get("emotion")
39
+ speed = request.get("speed")
40
+ if request.get("seed") is not None:
41
+ mx.random.seed(int(request["seed"]))
42
+ if request.get("design"):
43
+ # A voice from its description: no speaker, the description is the whole instruction.
44
+ args.update(instruct=request["design"], use_zero_spk_emb=True)
45
+ elif request.get("ref"):
46
+ args.update(ref_audio=request["ref"]["wav"], ref_text=request["ref"]["text"])
47
+ if emotion or speed:
48
+ args["instruct"] = ming3_instruction(emotion=emotion, speed=speed)
49
+ elif request.get("voice"):
50
+ if emotion or speed:
51
+ args.update(instruct=ming3_instruction(emotion=emotion, speed=speed, ip=request["voice"]), use_zero_spk_emb=True)
52
+ else:
53
+ args["voice"] = request["voice"]
54
+ audio = np.concatenate([np.array(part.audio) for part in model.generate(**args)])
55
+ pcm = (np.clip(audio, -1.0, 1.0) * 32767).astype("<i2")
56
+ return base64.b64encode(pcm.tobytes()).decode("ascii")
57
+
58
+
59
+ for line in sys.stdin:
60
+ if not line.strip():
61
+ continue
62
+ request = {}
63
+ try:
64
+ request = json.loads(line)
65
+ started = time.perf_counter()
66
+ if request["op"] == "load":
67
+ model = load_model(request["model"])
68
+ # The first sentence after loading is slow (about 5 s); pay for it now.
69
+ speak({"text": "嗯。", "voice": "灵小柔", "steps": 5})
70
+ reply({"id": request.get("id"), "ok": True, "loadMs": round((time.perf_counter() - started) * 1000)})
71
+ elif request["op"] == "speak":
72
+ pcm16 = speak(request)
73
+ reply({"id": request["id"], "ok": True, "pcm16": pcm16, "sampleRate": model.sample_rate, "ms": round((time.perf_counter() - started) * 1000)})
74
+ else:
75
+ raise ValueError("unknown op " + str(request.get("op")))
76
+ except Exception as error: # noqa: BLE001 - every failure goes back to the plugin
77
+ reply({"id": request.get("id"), "ok": False, "error": f"{type(error).__name__}: {error}"})