File size: 2,107 Bytes
10e7a50
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""Audio preparation helpers for DreamVoice reference clips."""

from __future__ import annotations

import os
import tempfile

import librosa
import numpy as np
import soundfile as sf

REFERENCE_SAMPLE_RATE = 16_000
MIN_REFERENCE_SECONDS = 5.0
MAX_REFERENCE_SECONDS = 60.0
MIN_RMS = 0.005


def prepare_reference(path: str) -> str:
    """Clean and validate a voice reference clip for VoxCPM2.

    The returned file is a temporary mono 16 kHz WAV. The caller owns cleanup of
    that returned path after synthesis finishes.
    """
    if not path or not os.path.exists(path):
        raise ValueError("Please record or upload a voice clip first.")

    try:
        audio, _ = librosa.load(path, sr=REFERENCE_SAMPLE_RATE, mono=True)
    except Exception as exc:  # noqa: BLE001 - present a friendly UI-safe error.
        raise ValueError("I couldn't read that audio clip. Please try a WAV or MP3 recording.") from exc

    if audio.size == 0 or not np.isfinite(audio).all():
        raise ValueError("That voice clip looks empty. Please record 5-60 seconds of clear speech.")

    audio = np.asarray(audio, dtype=np.float32)
    audio, _ = librosa.effects.trim(audio, top_db=35)

    if audio.size == 0:
        raise ValueError("That voice clip is too quiet. Please record closer to the microphone.")

    peak = float(np.max(np.abs(audio)))
    rms = float(np.sqrt(np.mean(np.square(audio))))
    if peak <= 0.0 or rms < MIN_RMS:
        raise ValueError("That voice clip is too quiet. Please record in a quiet room, closer to the microphone.")

    duration = audio.size / REFERENCE_SAMPLE_RATE
    if duration < MIN_REFERENCE_SECONDS:
        raise ValueError("Please record at least 5 seconds of clear speech.")
    if duration > MAX_REFERENCE_SECONDS:
        max_samples = int(MAX_REFERENCE_SECONDS * REFERENCE_SAMPLE_RATE)
        audio = audio[:max_samples]

    audio = np.clip(audio, -1.0, 1.0)
    fd, cleaned_path = tempfile.mkstemp(prefix="dreamvoice_ref_", suffix=".wav")
    os.close(fd)
    sf.write(cleaned_path, audio, REFERENCE_SAMPLE_RATE, subtype="PCM_16")
    return cleaned_path