File size: 2,591 Bytes
c86f0bd
eb47b93
c86f0bd
 
dd52739
 
 
c86f0bd
eb47b93
c86f0bd
 
 
 
 
 
 
dd52739
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
eb47b93
dd52739
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""Smart Turn Detection — Hugging Face Space demo (ONNX, CPU).
Runs the exported 8s model: raw 16k waveform -> P(turn complete).
Inference is CPU-only, so predictions don't touch the GPU queue. The one decorated
`_gpu_probe` below exists only so the ZeroGPU runtime accepts the Space; it's never called."""
import numpy as np
import onnxruntime as ort
import gradio as gr

try:
    import spaces

    @spaces.GPU
    def _gpu_probe():  # satisfies ZeroGPU's "must expose a GPU function" check; unused
        return None
except Exception:
    pass

SR, WIN = 16000, 8
N = SR * WIN  # 128000
SESS = ort.InferenceSession("smart_turn_8s.onnx", providers=["CPUExecutionProvider"])


def prob_complete(wav, sr):
    wav = np.asarray(wav, dtype=np.float32)
    if wav.ndim > 1:
        wav = wav.mean(axis=1)
    if sr != SR:
        import librosa
        wav = librosa.resample(wav, orig_sr=sr, target_sr=SR)
    mx = np.abs(wav).max()
    if mx > 0:
        wav = wav / mx
    x = wav[-N:]                                  # last 8s
    buf = np.zeros((1, N), np.float32)
    buf[0, N - len(x):] = x                       # right-align (left-pad)
    return float(SESS.run(None, {"waveform": buf})[0][0])


def predict(audio, threshold):
    if audio is None:
        return "Record or upload some speech.", None
    sr, wav = audio
    if wav.dtype.kind in "iu":
        wav = wav.astype(np.float32) / np.iinfo(wav.dtype).max
    p = prob_complete(wav, sr)
    verdict = "✅ Turn COMPLETE — safe to respond" if p > threshold else "⏳ Still speaking — wait"
    return f"{verdict}\n\nP(complete) = {p:.3f}   (threshold {threshold:.2f})", {"complete": p, "still speaking": 1 - p}


with gr.Blocks(title="Smart Turn Detection") as demo:
    gr.Markdown(
        "# 🎙️ Smart Turn Detection (whisper-tiny, 8s)\n"
        "Has the speaker **finished their turn**, or are they just pausing? Audio only, "
        "no transcription. Tuned for English + Hindi/Hinglish. ~7.8M params, ~38 ms/clip on CPU.\n\n"
        "_Try a finished sentence vs. trailing off with 'umm…'. Raise the threshold to interrupt less._")
    inp = gr.Audio(sources=["microphone", "upload"], type="numpy", label="Speech")
    thr = gr.Slider(0.05, 0.95, value=0.5, step=0.05, label="Decision threshold")
    btn = gr.Button("Detect turn", variant="primary")
    out_txt = gr.Textbox(label="Verdict")
    out_lbl = gr.Label(label="Probabilities")
    btn.click(predict, [inp, thr], [out_txt, out_lbl])
    inp.stop_recording(predict, [inp, thr], [out_txt, out_lbl])

if __name__ == "__main__":
    demo.launch()