Spaces:
Paused
Paused
File size: 10,470 Bytes
9bb4f7a 7247776 7233487 9bb4f7a 7233487 87f74c7 7233487 87f74c7 7233487 87f74c7 7233487 1c4e882 7233487 1c4e882 7233487 87f74c7 7233487 1c4e882 7233487 9bb4f7a 7233487 9bb4f7a 7233487 9bb4f7a 7233487 7247776 9bb4f7a 7247776 7233487 7247776 7233487 7247776 9bb4f7a 7247776 7233487 7247776 7233487 7247776 9bb4f7a 7233487 9bb4f7a 7233487 9bb4f7a 7233487 7247776 | 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 | import os, json, sys, numpy as np, gradio as gr
import matplotlib; matplotlib.use("Agg"); import matplotlib.pyplot as plt
from huggingface_hub import snapshot_download
PRIVATE = os.environ.get("MODEL_REPO", "")
d = snapshot_download(PRIVATE, token=os.environ.get("HF_TOKEN")) if PRIVATE else os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, d)
import keep_audio_core as K
cfg = json.load(open(os.path.join(d, "config.json"))) if os.path.exists(os.path.join(d, "config.json")) else {}
R = K.Recognizer(os.path.join(d, "audio2b_KEEP_graine0.pt"), os.path.join(d, "audio2b_normalisation.pt"), gain=cfg.get("gain", "peak"))
TAU, MIN_FRAME = 0.95, 10
# Voice-first shell: pure black canvas, one narrow column, no cards, no boxes.
# The result is a stage (huge word + bars), the waveform is the visual centerpiece.
CSS = """
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&display=swap');
html, body { background: #000000 !important; }
.gradio-container {
--body-background-fill: #000000;
--background-fill-primary: #000000;
--background-fill-secondary: #000000;
--block-background-fill: transparent;
--block-border-color: transparent;
--block-label-background-fill: transparent;
--block-label-text-color: #8A8A93;
--block-title-text-color: #F5F5F7;
--body-text-color: #F5F5F7;
--body-text-color-subdued: #8A8A93;
--button-primary-background-fill: #F5F5F7;
--button-primary-background-fill-hover: #FFFFFF;
--button-primary-text-color: #000000;
--button-secondary-background-fill: transparent;
--button-secondary-background-fill-hover: #1A1A1E;
--button-secondary-text-color: #F5F5F7;
--button-secondary-border-color: rgba(255,255,255,.22);
--input-background-fill: transparent;
--input-border-color: rgba(255,255,255,.22);
--input-text-color: #F5F5F7;
--input-placeholder-color: #6E6E73;
max-width: 560px !important; margin: 0 auto !important; padding: 26px 22px 44px !important;
background: #000000 !important; color: #F5F5F7 !important;
font-family: 'Space Grotesk', system-ui, sans-serif !important;
}
.gradio-container .block, .gradio-container .form, .gradio-container .wrap,
.gradio-container .panel, .gradio-container .component, .gradio-container .audio,
.gradio-container .plot, .gradio-container .input, .gradio-container .output {
background: transparent !important; border: none !important;
box-shadow: none !important; border-radius: 0 !important;
}
.gradio-container label span {
color: #8A8A93 !important; font-size: 11px !important; font-weight: 500 !important;
letter-spacing: 2.5px !important; text-transform: uppercase !important;
}
.gradio-container .prose { color: #C7C7CC !important; }
.gradio-container .prose strong { color: #F5F5F7 !important; }
.topbar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
.brand { font-weight: 700; font-size: 19px; letter-spacing: -.5px; }
.brand i { color: #E5C15D; font-style: normal; }
.ondevice { font-size: 12px; color: #8A8A93; border: 1px solid rgba(255,255,255,.18); border-radius: 999px; padding: 5px 12px; }
.stage { text-align: center; padding: 34px 0 6px; }
.stage .eyebrow { font-size: 11px; letter-spacing: 3px; text-transform: uppercase; color: #8A8A93; margin-bottom: 16px; }
.stage .word { font-size: 78px; font-weight: 700; letter-spacing: -3px; line-height: 1; margin: 6px 0 0; }
.stage .word.dim { color: #333338; }
.stage .conf { font-size: 15px; color: #E5C15D; margin-top: 14px; }
.stage .top3 { margin-top: 20px; display: flex; flex-direction: column; gap: 8px; }
.stage .top3 .row { display: flex; align-items: center; gap: 12px; font-size: 14px; color: #C7C7CC; }
.stage .top3 .row b { width: 62px; text-align: right; font-weight: 500; color: #F5F5F7; }
.stage .top3 .bar { flex: 1; height: 5px; background: #1C1C1F; border-radius: 99px; overflow: hidden; }
.stage .top3 .bar i { display: block; height: 100%; background: linear-gradient(90deg, #C9A227, #F5D98B); border-radius: 99px; }
.stage .top3 .pct { width: 46px; text-align: left; color: #8A8A93; }
.eq { display: flex; justify-content: center; align-items: flex-end; gap: 5px; height: 46px; margin: 6px 0 10px; }
.eq i { display: block; width: 5px; border-radius: 99px; background: linear-gradient(180deg, #F5D98B, #C9A227); animation: eqpulse 1.15s ease-in-out infinite; transform-origin: bottom; }
@keyframes eqpulse { 0%, 100% { transform: scaleY(.22); } 50% { transform: scaleY(1); } }
.hairline { height: 1px; background: rgba(255,255,255,.10); margin: 30px 0; }
.chips { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; }
.chip { font-size: 14px; color: #C7C7CC; background: #141416; border-radius: 999px; padding: 9px 15px; }
.quiet { color: #8A8A93 !important; font-size: 13.5px !important; line-height: 1.65 !important; }
.quiet strong { color: #C7C7CC !important; }
.gradio-container button.primary { border-radius: 999px !important; height: 56px !important; font-size: 17px !important; font-weight: 700 !important; }
.gradio-container button:not(.primary) { border-radius: 999px !important; }
.gradio-container .audio button { border-radius: 999px !important; background: #141416 !important; border: 1px solid rgba(255,255,255,.14) !important; }
.gradio-container label[data-testid$="-radio-label"] {
border-radius: 999px !important; border: 1px solid rgba(255,255,255,.14) !important;
padding: 7px 15px !important; background: transparent !important; color: #8A8A93 !important;
}
.gradio-container label[data-testid$="-radio-label"].selected { border-color: #E5C15D !important; color: #F5F5F7 !important; }
.gradio-container label[data-testid$="-radio-label"] span { color: inherit !important; background: transparent !important; letter-spacing: 0 !important; text-transform: none !important; font-size: 14px !important; }
.footer { opacity: .45; }
"""
TOPBAR = """<div class="topbar"><div class="brand">KEEP <i>•</i> Listen</div><div class="ondevice">On-device · CPU</div></div>"""
def _eq():
heights = [22, 34, 18, 40, 28, 46, 26, 36, 20, 30, 42, 24, 32, 18, 38]
return "".join(f'<i style="height:{h}px;animation-delay:{i * 0.09:.2f}s"></i>' for i, h in enumerate(heights))
def stage_empty():
return f"""<div class="stage"><div class="eyebrow">KEEP is listening</div><div class="eq">{_eq()}</div><p class="word dim">Say a word</p></div>"""
def stage_result(word, conf, top3):
rows = "".join(
f'<div class="row"><b>{w}</b><span class="bar"><i style="width:{p * 100:.0f}%"></i></span><span class="pct">{p:.0%}</span></div>'
for w, p in top3)
return (f"""<div class="stage"><div class="eyebrow">KEEP heard</div><p class="word">{word}</p>"""
f"""<p class="conf">{conf:.0%} confident</p><div class="top3">{rows}</div></div>""")
def glow_plot(P, top, sure):
t = np.arange(P.shape[0]) * 10
fig, ax = plt.subplots(figsize=(7, 2.8), facecolor="#000000")
ax.set_facecolor("#000000")
COLORS = ["#E5C15D", "#5EC8D8", "#A78BFA"]
for i, c in zip(top, COLORS):
ax.plot(t, P[:, i], lw=7, color=c, alpha=0.12, solid_capstyle="round")
ax.plot(t, P[:, i], lw=2.2, color=c, label=K.WORDS[i], solid_capstyle="round")
ax.fill_between(t, P[:, top[0]], color="#E5C15D", alpha=0.10)
ax.axhline(TAU, color="#6E6E73", ls=(0, (2, 4)), lw=1)
if len(sure):
td = (sure[0] + MIN_FRAME) * 10
ax.axvline(td, color="#FFFFFF", ls="--", lw=1, alpha=0.7)
ax.set_ylim(0, 1.05); ax.set_yticks([])
for s in ax.spines.values():
s.set_visible(False)
ax.tick_params(axis="x", colors="#6E6E73", labelsize=9)
ax.set_xticks([0, 250, 500, 750, 1000])
ax.set_xlabel("listened time (ms)", color="#6E6E73", fontsize=9)
leg = ax.legend(loc="upper left", frameon=False, fontsize=10)
for txt in leg.get_texts():
txt.set_color("#C7C7CC")
fig.tight_layout()
return fig
def listen(audio, gain_choice):
R.gain = gain_choice
if audio is None:
return stage_empty(), gr.update(visible=False), "Record or upload a word first."
sr, wav = audio
_, P = R(wav, sr) # P : [101 steps (10 ms), 10 words]
final = P[-1]; top = final.argsort()[::-1][:3]
sure = np.where(P[MIN_FRAME:].max(1) >= TAU)[0]
fig = glow_plot(P, top, sure)
w = K.WORDS[int(final.argmax())]
top3 = [(K.WORDS[int(i)], float(final[i])) for i in top]
stage = stage_result(w, float(final.max()), top3)
if final.max() < 0.6:
msg = f"**Not sure** (best guess: “{w}”, {final.max():.0%}). Say one of the 10 words clearly."
elif len(sure):
w0 = K.WORDS[int(P[sure[0] + MIN_FRAME].argmax())]
msg = f"It was already sure of “{w0}” after **{(sure[0] + MIN_FRAME) * 10} ms** of listening."
else:
msg = "Recognized at the end of listening."
return stage, gr.update(value=fig, visible=True), msg
CHIPS = """<div class="chips"><span class="chip">yes</span><span class="chip">no</span><span class="chip">up</span><span class="chip">down</span><span class="chip">left</span><span class="chip">right</span><span class="chip">on</span><span class="chip">off</span><span class="chip">stop</span><span class="chip">go</span></div>"""
with gr.Blocks(title="KEEP Listen", css=CSS) as demo:
gr.HTML(TOPBAR)
stage = gr.HTML(stage_empty())
a = gr.Audio(sources=["microphone", "upload"], type="numpy", label="Your word — about 1 second")
btn = gr.Button("Listen", variant="primary")
plot = gr.Plot(show_label=False, visible=False)
msg = gr.Markdown(elem_classes=["quiet"])
gain_radio = gr.Radio(["peak", "none"], value=cfg.get("gain", "peak"),
label="Volume setting (peak = automatic, none = raw)")
btn.click(listen, [a, gain_radio], [stage, plot, msg])
gr.HTML('<div class="hairline"></div>')
gr.Markdown("**Known words.** Say a single word, close to the microphone. The curve shows how its confidence rises while listening.", elem_classes=["quiet"])
gr.HTML(CHIPS)
gr.HTML('<div class="hairline"></div>')
gr.Markdown("**Limits.** It only knows these 10 words: any other sound will be classified as one of them. "
"Tested at about 97% on never-heard voices (Speech Commands test set); a noisy microphone or a very "
"different accent can lower that number. It recognizes words, it does not understand speech.",
elem_classes=["quiet"])
demo.launch()
|