File size: 19,700 Bytes
a5fab31
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
"""Chorus/graft commands: DELLA voice blending (pulse/support/lead).

``voice graft`` is the single-graft primitive (pulse→donor); ``voice chorus``
chains it (pulse→support at 0.8, then →lead at 0.9, or a single 0.9 graft
when support is skipped). Both write named registry voices via the same
``_sing_graft`` core so the CLI can never drift from the tested path.
"""

import hashlib
import json
import sys
from pathlib import Path

from vlib.ui import _fail, _noninteractive, _now_iso, _ok, _say, _step
from vlib.sources import _sanitize_voice_name, _slugify_voice_name, open_source
from vlib.tensors import encode_from_f32, write_safetensors
from vlib.registry import (_assert_not_symlink, _read_meta,
                            _registry_folder, _registry_resolve, log_operation)
from vlib.delta import _choose_voice
from vlib.della import VALID_RESCALES, merge as _della_merge

HOUSE_DENSITY = 0.8
HOUSE_LEAD_DENSITY = 0.9
HOUSE_EPSILON = 0.1
HOUSE_LEAD_EPSILON = 0.05
HOUSE_LAMBDA = 1.0
HOUSE_SEED = 42
HOUSE_RESCALE = "l1"


def _graft_params(args):
    """Coerce graft knobs from argparse; fail loud on bad values."""
    try:
        density = float(getattr(args, "density", HOUSE_DENSITY))
    except (TypeError, ValueError):
        _fail(f"  βœ— --density must be a number, got {getattr(args, 'density', None)!r}.")
    try:
        epsilon = float(getattr(args, "epsilon", HOUSE_EPSILON))
    except (TypeError, ValueError):
        _fail(f"  βœ— --epsilon must be a number, got {getattr(args, 'epsilon', None)!r}.")
    try:
        lambda_ = float(getattr(args, "lambda_", HOUSE_LAMBDA))
    except (TypeError, ValueError):
        _fail(f"  βœ— --lambda must be a number, got {getattr(args, 'lambda_', None)!r}.")
    rescale = str(getattr(args, "rescale", HOUSE_RESCALE) or HOUSE_RESCALE)
    if rescale not in VALID_RESCALES:
        _fail(f"  βœ— --rescale must be {'/'.join(VALID_RESCALES)}, got {rescale!r}.")
    seed = getattr(args, "seed", HOUSE_SEED)
    if seed is not None:
        try:
            seed = int(seed)
        except (TypeError, ValueError):
            _fail(f"  βœ— --seed must be an integer, got {seed!r}.")
    if not 0 < density < 1:
        _fail(f"  βœ— --density must be in (0,1), got {density}.")
    if density + epsilon >= 1 or density - epsilon <= 0:
        _fail(f"  βœ— --density Β± --epsilon must stay in (0,1): got density {density:g} epsilon {epsilon:g}.")
    return density, epsilon, lambda_, rescale, seed


def _flag_config(args):
    """Coerce chorus flags to a full config. Shared by all three modes so
    flags mean the same thing everywhere: non-interactive uses them directly,
    --config screens prefill from them, plain applies them silently."""
    def _opt(name, house):
        v = getattr(args, name, None)
        return house if v is None else float(v)
    density = _opt("density", HOUSE_DENSITY)
    lead_density = _opt("lead_density", HOUSE_LEAD_DENSITY)
    epsilon = _opt("epsilon", HOUSE_EPSILON)
    lead_epsilon = _opt("lead_epsilon", HOUSE_LEAD_EPSILON)
    lambda_ = _opt("lambda_", HOUSE_LAMBDA)
    rescale = str(getattr(args, "rescale", HOUSE_RESCALE) or HOUSE_RESCALE)
    if rescale not in VALID_RESCALES:
        _fail(f"  βœ— --rescale must be {'/'.join(VALID_RESCALES)}.")
    seed = getattr(args, "seed", HOUSE_SEED)
    seed = HOUSE_SEED if seed is None else int(seed)
    for _d, _e, _tag in ((density, epsilon, "first graft"),
                         (lead_density, lead_epsilon, "lead graft")):
        if not 0 < _d < 1:
            _fail(f"  βœ— {_tag} density must be in (0,1), got {_d}.")
        if _d + _e >= 1 or _d - _e <= 0:
            _fail(f"  βœ— {_tag} density Β± epsilon must stay in (0,1): got {_d:g} Β± {_e:g}.")
    return density, lead_density, epsilon, lead_epsilon, lambda_, rescale, seed

def _head_ref(source):
    """Single head tensor ref of a voice source; voices hold exactly one."""
    names = [n for n in source.names() if n != "delta.voice.marker"]
    if not names:
        _fail("  βœ— No tensors found for graft.")
    if len(names) > 1:
        _fail(f"  βœ— Graft needs a single-tensor voice, found {len(names)} tensors "
              f"({', '.join(names[:4])}{'…' if len(names) > 4 else ''}). "
              "Use `voice get <model>` for a head-only voice.")
    return source.ref(names[0])


def _check_overwrite_folder(folder, args):
    if folder.exists() and ((folder / "voice.safetensors").exists()
                             or (folder / "voice.gguf").exists()):
        if _noninteractive(args):
            if not getattr(args, "yes", False):
                _fail(f"  βœ— Voice '{folder.name}' exists. Re-run with --yes to overwrite.")
        else:
            try:
                ans = input(f"  Voice '{folder.name}' exists. Overwrite? [y/N]: ").strip().lower()
            except (EOFError, KeyboardInterrupt):
                _say("  Cancelled.")
                sys.exit(1)
            if ans not in ("y", "yes"):
                _say("  Cancelled.")
                sys.exit(1)


def _resolve_graft_voice(name, role):
    """Registry name (or path) β†’ (voice_id, source). Fails loud when missing."""
    path, meta = _registry_resolve(name)
    if path is None or not path.exists():
        _fail(f"  βœ— {role} voice '{name}' not found. Use 'voice list' or pass a .safetensors path.")
    vid = (meta or {}).get("name") or Path(str(path)).stem
    return vid, open_source(str(path))


def _validate_pair(pulse_ref, donor_ref):
    """Distinct voices + identical head geometry, or fail loud with a table."""
    if tuple(pulse_ref.shape) != tuple(donor_ref.shape):
        _say(f"  {'voice':<12}{'tensor':<28}{'shape'}")
        _say(f"  {'─' * 12}{'─' * 28}{'─' * 20}")
        _say(f"  {'pulse':<12}{pulse_ref.name:<28}{list(pulse_ref.shape)}")
        _say(f"  {'donor':<12}{donor_ref.name:<28}{list(donor_ref.shape)}")
        _fail(f"  βœ— Head shapes differ: {list(pulse_ref.shape)} vs {list(donor_ref.shape)}. "
              "Chorus needs identical head geometry.")
    _say("  Lineage is not verified β€” voices with different base models still merge, results may vary.")


def _sing_graft(pulse_id, pulse_src, donor_id, donor_src, out_name,
                density, epsilon, lambda_, rescale, seed, args, chain=None):
    """One DELLA graft pulse→donor into registry voice ``out_name``.

    Shared by ``voice graft`` and ``voice chorus`` so both CLIs run the same
    tensor path. Returns the registry folder.
    """
    import numpy as np
    pulse_ref = _head_ref(pulse_src)
    donor_ref = _head_ref(donor_src)
    if pulse_id.strip().lower() == donor_id.strip().lower():
        _fail("  βœ— Pulse and donor are the same voice β€” graft needs two distinct voices.")
    from vlib.tensors import _is_ggml_quant as _is_q
    for _role, _ref in (("Pulse", pulse_ref), ("Donor", donor_ref)):
        try:
            _quant = _is_q(_ref.dtype)
        except Exception:
            _quant = False
        if _quant:
            _fail(f"  βœ— {_role} voice is quantized GGUF ({_ref.dtype}) β€” graft needs float heads. "
                  "Cast it onto a float target first, then graft the result.")
    folder = _registry_folder(out_name)
    _check_overwrite_folder(folder, args)
    _step(f"Grafting {donor_id} onto {pulse_id} (density {density:g})…")
    base_f32 = pulse_src.read_f32(pulse_ref.name)
    donor_f32 = donor_src.read_f32(donor_ref.name)
    merged = _della_merge(np.asarray(base_f32, dtype=np.float32),
                          [np.asarray(donor_f32, dtype=np.float32)],
                          density=density, epsilon=epsilon, lambda_=lambda_,
                          rescale=rescale, seed=seed)
    _assert_not_symlink(folder)
    folder.mkdir(parents=True, exist_ok=True)
    try:
        import os
        os.chmod(folder, 0o700)
    except Exception:
        pass
    _assert_not_symlink(folder)
    out_path = folder / "voice.safetensors"
    tmp = folder / "voice.safetensors.tmp"
    write_safetensors({pulse_ref.name: (encode_from_f32(merged, pulse_ref.dtype),
                                         pulse_ref.dtype,
                                         tuple(pulse_ref.shape))}, str(tmp))
    tmp.replace(out_path)
    try:
        import os
        os.chmod(out_path, 0o600)
    except Exception:
        pass
    cfg = {"density": density, "epsilon": epsilon, "lambda": lambda_,
           "rescale": rescale, "seed": seed}
    meta = {
        "name": out_name.strip().lower(),
        "type": "chorus",
        "method": "della",
        "pulse": pulse_id,
        "donor": donor_id,
        "tensor_name": pulse_ref.name,
        "shape": [int(x) for x in pulse_ref.shape],
        "dtype": pulse_ref.dtype,
        "config": cfg,
        "config_hash": hashlib.sha256(
            json.dumps(cfg, sort_keys=True).encode()).hexdigest()[:12],
    }
    if chain:
        meta["chain"] = chain
    try:
        meta["bytes"] = out_path.stat().st_size
    except Exception:
        pass
    meta["downloaded_at"] = _now_iso()
    jtmp = folder / "voice.json.tmp"
    jtmp.write_text(json.dumps(meta, indent=2) + "\n")
    try:
        import os
        os.chmod(jtmp, 0o600)
    except Exception:
        pass
    jtmp.replace(folder / "voice.json")
    log_operation({"op": "graft", "pulse": pulse_id, "donor": donor_id,
                   "density": density, "epsilon": epsilon, "lambda": lambda_,
                   "rescale": rescale, "seed": seed, "output": str(folder),
                   "status": "ok"})
    return folder


def _prompt_knobs(role, density, epsilon, lambda_, seed, out_name):
    """--config knob screen: Enter accepts each default, typing overrides."""
    if _noninteractive({"non_interactive": True}):
        return density, epsilon, lambda_, seed, out_name
    try:
        raw = input(f"  {role} density [{density:g}]: ").strip()
        if raw:
            density = float(raw)
        raw = input(f"  epsilon [{epsilon:g}]: ").strip()
        if raw:
            epsilon = float(raw)
        raw = input(f"  lambda [{lambda_:g}]: ").strip()
        if raw:
            lambda_ = float(raw)
        raw = input(f"  seed [{seed}]: ").strip()
        if raw:
            seed = int(raw)
        raw = input(f"  output name [{out_name}]: ").strip()
        if raw:
            out_name = raw
    except (EOFError, KeyboardInterrupt):
        _say("  Cancelled.")
        sys.exit(1)
    except ValueError:
        _fail("  βœ— Knob must be a number β€” re-run `voice chorus --config` to retry.")
    if _sanitize_voice_name(out_name) is None:
        _fail(f"  βœ— Invalid output name '{out_name}'.")
    if not 0 < density < 1:
        _fail(f"  βœ— density must be in (0,1), got {density}.")
    return density, epsilon, lambda_, seed, out_name


def cmd_graft(args):
    pulse_arg = getattr(args, "pulse", None)
    donor_arg = getattr(args, "donor", None)
    if not pulse_arg or not str(pulse_arg).strip():
        pulse_arg = _choose_voice(args)
    if not donor_arg or not str(donor_arg).strip():
        donor_arg = _choose_voice(args)
    pulse_id, pulse_src = _resolve_graft_voice(str(pulse_arg).strip(), "Pulse")
    donor_id, donor_src = _resolve_graft_voice(str(donor_arg).strip(), "Donor")
    density, epsilon, lambda_, rescale, seed = _graft_params(args)
    out = getattr(args, "out", None) or f"chorus-{_slugify_voice_name(pulse_id)}-{_slugify_voice_name(donor_id)}"
    out = str(out).strip()
    if _sanitize_voice_name(out) is None:
        _fail(f"  βœ— Invalid --out name '{out}'.")
    folder = _sing_graft(pulse_id, pulse_src, donor_id, donor_src, out,
                         density, epsilon, lambda_, rescale, seed, args)
    _ok(f"Done β†’ {folder}")
    _say(f"  Use it:   voice cast <your-model.gguf> {folder.name}")
    _say(f"  Hear it:  voice cast <your-model.gguf> {folder.name} --out /tmp/chorus.gguf")


def _pick_from(voices, exclude=()):
    """Numbered pick from a fixed registry list (excludes by lowercase id)."""
    shown = [(vid, src) for vid, src in voices
             if vid.strip().lower() not in {e.strip().lower() for e in exclude}]
    if not shown:
        _fail("  βœ— No more voices to choose from.")
    _say("  Available voices:")
    for i, (vid, _src) in enumerate(shown, 1):
        _say(f"  {i}. {vid}")
    try:
        choice = input(f"  Choose [1-{len(shown)}]: ").strip()
    except (EOFError, KeyboardInterrupt):
        _say("  Cancelled.")
        sys.exit(1)
    if not choice.isdigit() or not (1 <= int(choice) <= len(shown)):
        _fail("  βœ— Invalid choice.")
    return shown[int(choice) - 1]


def _registry_voices():
    """All registry voices as (id, source) pairs; fails loud when empty."""
    from vlib.registry import _iter_voices
    voices = []
    for d in _iter_voices():
        meta = _read_meta(d / "voice.json")
        if not meta:
            continue
        vid = meta.get("name", d.name)
        try:
            src = open_source(str(d / ("voice.gguf" if (d / "voice.gguf").exists()
                                        else "voice.safetensors")))
        except Exception as e:
            _fail(f"  βœ— Voice '{vid}' is unreadable ({e}).")
        voices.append((vid, src))
    if not voices:
        _fail("  βœ— No voices stored yet. Use 'voice get <model>' to save one.")
    return voices


def cmd_chorus(args):
    config = bool(getattr(args, "config", False))
    if _noninteractive(args):
        # Flags override defaults silently; positionals answer the prompts.
        # --config has no staged screens here (no TTY): flags ARE the config.
        pulse_arg = getattr(args, "pulse", None)
        donor_args = getattr(args, "donors", None) or []
        if not pulse_arg or not donor_args:
            _fail("  βœ— Non-interactive chorus needs `voice chorus <pulse> <support> <lead>` "
                  "(pass one donor for a duet).")
        if len(donor_args) > 2:
            _fail("  βœ— Chorus takes at most two donors (support + lead).")
        pulse_id, pulse_src = _resolve_graft_voice(str(pulse_arg).strip(), "Pulse")
        ids_srcs = [_resolve_graft_voice(str(d).strip(), "Donor") for d in donor_args]
        density, lead_density, epsilon, lead_epsilon, lambda_, rescale, seed = _flag_config(args)
        out = getattr(args, "out", None)
        if out is not None:
            out = str(out).strip()
            if _sanitize_voice_name(out) is None:
                _fail(f"  βœ— Invalid --out name '{out}'.")
        if len(ids_srcs) == 1:
            (lead_id, lead_src) = ids_srcs[0]
            name = str(out or f"chorus-{_slugify_voice_name(pulse_id)}-{_slugify_voice_name(lead_id)}").strip()
            folder = _sing_graft(pulse_id, pulse_src, lead_id, lead_src, name,
                                 lead_density, lead_epsilon, lambda_, rescale, seed,
                                 args)
        else:
            (support_id, support_src), (lead_id, lead_src) = ids_srcs
            duet = str(f"chorus-{_slugify_voice_name(pulse_id)}-{_slugify_voice_name(support_id)}").strip()
            folder = _sing_graft(pulse_id, pulse_src, support_id, support_src, duet,
                                 density, epsilon, lambda_, rescale, seed, args)
            _say(f"  Duet β†’ {folder.name} (audition it: voice cast <model.gguf> {folder.name})")
            duet_src = open_source(str(folder / "voice.safetensors"))
            name = str(out or f"chorus-{_slugify_voice_name(pulse_id)}-{_slugify_voice_name(lead_id)}").strip()
            folder = _sing_graft(folder.name, duet_src, lead_id, lead_src, name,
                                 lead_density, lead_epsilon, lambda_, rescale, seed + 1,
                                 args, chain=[duet, name])
        _ok(f"Done β†’ {folder}")
        _say(f"  Hear it:  voice cast <your-model.gguf> {folder.name} --out /tmp/chorus.gguf")
        return

    voices = _registry_voices()
    _say("  Choose pulse:")
    pulse_id, pulse_src = _pick_from(voices)
    _say("  Choose support (Enter to skip):")
    shown = [(vid, src) for vid, src in voices
             if vid.strip().lower() != pulse_id.strip().lower()]
    for i, (vid, _src) in enumerate(shown, 1):
        _say(f"  {i}. {vid}")
    try:
        raw = input(f"  Choose support [1-{len(shown)}] (Enter to skip): ").strip()
    except (EOFError, KeyboardInterrupt):
        _say("  Cancelled.")
        sys.exit(1)
    support = None
    if raw:
        if not raw.isdigit() or not (1 <= int(raw) <= len(shown)):
            _fail("  βœ— Invalid choice.")
        support = shown[int(raw) - 1]
    _say("  Choose lead:")
    taken = {pulse_id.strip().lower()} | ({support[0].strip().lower()} if support else set())
    lead_id, lead_src = _pick_from(voices, exclude=taken)

    _fd, _ld, _fe, _le, _fl, _fr, _fs = _flag_config(args)
    if support is None:
        # Duet: single graft at lead density. Plain applies flags silently;
        # --config screens prefill from flags, Enter keeps them.
        if config:
            out = f"chorus-{_slugify_voice_name(pulse_id)}-{_slugify_voice_name(lead_id)}"
            density, epsilon, lambda_, seed, out = _prompt_knobs(
                "Lead", _ld, _le, _fl, _fs, out)
            rescale = _fr
        else:
            density, epsilon, lambda_, seed, rescale = _ld, _le, _fl, _fs, _fr
            out = f"chorus-{_slugify_voice_name(pulse_id)}-{_slugify_voice_name(lead_id)}"
        _say(f"  {pulse_id} pulse + {lead_id} lead β†’ {out}")
        folder = _sing_graft(pulse_id, pulse_src, lead_id, lead_src, out,
                             density, epsilon, lambda_, rescale, seed, args)
        _ok(f"Done β†’ {folder}")
        _say(f"  Hear it:  voice cast <your-model.gguf> {folder.name} --out /tmp/chorus.gguf")
        return

    support_id, support_src = support
    _say(f"  {pulse_id} pulse + {support_id} support + {lead_id} lead β†’ chorus-{_slugify_voice_name(pulse_id)}-{_slugify_voice_name(lead_id)}")
    if config:
        duet = f"chorus-{_slugify_voice_name(pulse_id)}-{_slugify_voice_name(support_id)}"
        density, epsilon, lambda_, seed, duet = _prompt_knobs(
            "Support", _fd, _fe, _fl, _fs, duet)
        folder = _sing_graft(pulse_id, pulse_src, support_id, support_src, duet,
                             density, epsilon, lambda_, _fr, seed, args)
        _say(f"  Duet β†’ {folder.name} β€” cast it and listen before the lead lands: "
             f"voice cast <model.gguf> {folder.name}")
        final = f"chorus-{_slugify_voice_name(pulse_id)}-{_slugify_voice_name(lead_id)}"
        density2, epsilon2, lambda2, seed2, final = _prompt_knobs(
            "Lead", _ld, _le, lambda_, seed + 1, final)
        duet_src = open_source(str(folder / "voice.safetensors"))
        folder = _sing_graft(folder.name, duet_src, lead_id, lead_src, final,
                             density2, epsilon2, lambda2, _fr, seed2,
                             args, chain=[duet, final])
    else:
        duet = f"chorus-{_slugify_voice_name(pulse_id)}-{_slugify_voice_name(support_id)}"
        folder = _sing_graft(pulse_id, pulse_src, support_id, support_src, duet,
                             _fd, _fe, _fl, _fr, _fs, args)
        duet_src = open_source(str(folder / "voice.safetensors"))
        final = f"chorus-{_slugify_voice_name(pulse_id)}-{_slugify_voice_name(lead_id)}"
        folder = _sing_graft(folder.name, duet_src, lead_id, lead_src, final,
                             _ld, _le, _fl, _fr, _fs + 1, args,
                             chain=[duet, final])
    _ok(f"Done β†’ {folder}")
    _say(f"  Hear it:  voice cast <your-model.gguf> {folder.name} --out /tmp/chorus.gguf")