Spaces:
Paused
Paused
Download web/src/audio.js from secutorpro/dispatch-console: direct link, hf CLI and curl.
- Browser
- Download file 2.83 kB
-
https://huggingface.co/spaces/secutorpro/dispatch-console/resolve/main/web/src/audio.js
- Command line
-
hf download hf://spaces/secutorpro/dispatch-console/web/src/audio.js
-
curl -L -o audio.js https://huggingface.co/spaces/secutorpro/dispatch-console/resolve/main/web/src/audio.js
2.83 kB
| const WORKLET_SRC = ` | |
| class PCMCapture extends AudioWorkletProcessor { | |
| process(inputs) { | |
| const input = inputs[0]; | |
| if (input && input[0]) { | |
| const f32 = input[0]; | |
| const i16 = new Int16Array(f32.length); | |
| for (let i = 0; i < f32.length; i++) { | |
| const s = Math.max(-1, Math.min(1, f32[i])); | |
| i16[i] = s < 0 ? s * 0x8000 : s * 0x7fff; | |
| } | |
| this.port.postMessage(i16.buffer, [i16.buffer]); | |
| } | |
| return true; | |
| } | |
| } | |
| registerProcessor('pcm-capture', PCMCapture); | |
| `; | |
| function makeCtx() { | |
| try { return new AudioContext({ sampleRate: 16000 }); } | |
| catch { return new AudioContext(); } | |
| } | |
| export async function startCapture(onChunk) { | |
| const stream = await navigator.mediaDevices.getUserMedia({ | |
| audio: { echoCancellation: true, noiseSuppression: true }, | |
| }); | |
| const ctx = makeCtx(); | |
| const url = URL.createObjectURL(new Blob([WORKLET_SRC], { type: 'application/javascript' })); | |
| await ctx.audioWorklet.addModule(url); | |
| URL.revokeObjectURL(url); | |
| const source = ctx.createMediaStreamSource(stream); | |
| const node = new AudioWorkletNode(ctx, 'pcm-capture'); | |
| node.port.onmessage = (e) => onChunk(e.data); | |
| source.connect(node); // not connected to destination -> no local feedback | |
| return { | |
| stop() { | |
| node.port.onmessage = null; | |
| try { source.disconnect(); } catch { /* already closed */ } | |
| stream.getTracks().forEach((t) => t.stop()); | |
| ctx.close().catch(() => {}); | |
| }, | |
| }; | |
| } | |
| export class PttPlayer { | |
| constructor() { | |
| this.ctx = makeCtx(); | |
| this.nextStart = 0; | |
| } | |
| play(pcmBuffer) { | |
| const i16 = new Int16Array(pcmBuffer); | |
| if (!i16.length) return; | |
| const f32 = new Float32Array(i16.length); | |
| for (let i = 0; i < i16.length; i++) f32[i] = i16[i] / 0x8000; | |
| const buffer = this.ctx.createBuffer(1, f32.length, 16000); | |
| buffer.copyToChannel(f32, 0); | |
| const src = this.ctx.createBufferSource(); | |
| src.buffer = buffer; | |
| src.connect(this.ctx.destination); | |
| const now = this.ctx.currentTime; | |
| if (this.nextStart < now) this.nextStart = now + 0.03; | |
| src.start(this.nextStart); | |
| this.nextStart += buffer.duration; | |
| } | |
| reset() { this.nextStart = 0; } | |
| } | |
| export function sosBeep(times = 3) { | |
| let ctx; | |
| try { ctx = new AudioContext(); } catch { return; } | |
| const t0 = ctx.currentTime; | |
| for (let i = 0; i < times; i++) { | |
| const osc = ctx.createOscillator(); | |
| const gain = ctx.createGain(); | |
| osc.type = 'square'; | |
| osc.frequency.value = 880; | |
| const s = t0 + i * 0.35; | |
| gain.gain.setValueAtTime(0.0001, s); | |
| gain.gain.exponentialRampToValueAtTime(0.2, s + 0.02); | |
| gain.gain.exponentialRampToValueAtTime(0.0001, s + 0.3); | |
| osc.connect(gain).connect(ctx.destination); | |
| osc.start(s); | |
| osc.stop(s + 0.32); | |
| } | |
| setTimeout(() => ctx.close().catch(() => {}), times * 350 + 500); | |
| } |