dsh-her 0.1.0 → 0.2.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/lib/client.js +862 -506
- package/lib/core/character.js +1 -1
- package/lib/core/index.js +3 -1
- package/lib/core/sounds.js +31 -0
- package/lib/core/spoken-numbers.js +90 -0
- package/lib/core/voices.js +31 -1
- package/lib/dsh/api.js +53 -0
- package/lib/dsh/plugin.js +129 -14
- package/lib/types/core/character.d.ts +1 -1
- package/lib/types/core/index.d.ts +3 -1
- package/lib/types/core/sounds.d.ts +21 -0
- package/lib/types/core/spoken-numbers.d.ts +11 -0
- package/lib/types/core/voices.d.ts +18 -2
- package/lib/types/dsh/api.d.ts +37 -1
- package/lib/types/dsh/plugin.d.ts +2 -0
- package/lib/types/voice/ming-model.d.ts +62 -0
- package/lib/types/voice/ming-voices.d.ts +75 -0
- package/lib/types/voice/ming.d.ts +103 -0
- package/lib/types/voice/speech-queue.d.ts +4 -2
- package/lib/types/voice/uv.d.ts +12 -0
- package/lib/types/voice/voice.d.ts +14 -6
- package/lib/voice/edge.js +4 -48
- package/lib/voice/ming-model.js +30 -0
- package/lib/voice/ming-voices.js +181 -0
- package/lib/voice/ming.js +335 -0
- package/lib/voice/speech-queue.js +5 -3
- package/lib/voice/uv.js +60 -0
- package/lib/voice/voice.js +46 -10
- package/package.json +2 -1
- package/python/ming-requirements.in +1 -0
- package/python/ming-requirements.txt +887 -0
- 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}"})
|