Spaces:
Running
Running
Download static/scanner-worklet.js from MattSymetry/musicSlip: direct link, hf CLI and curl.
- Browser
- Download file 2.69 kB
-
https://huggingface.co/spaces/MattSymetry/musicSlip/resolve/main/static/scanner-worklet.js
- Command line
-
hf download hf://spaces/MattSymetry/musicSlip/static/scanner-worklet.js
-
curl -L -o scanner-worklet.js https://huggingface.co/spaces/MattSymetry/musicSlip/resolve/main/static/scanner-worklet.js
2.69 kB
| // PCM sample player for the waveform decoders. The main thread reads a row of | |
| // the slip image back as raw audio samples and posts them here; this just | |
| // buffers them in a ring and plays them at the audio rate. No STFT, no phase | |
| // reconstruction -- the waveform is the signal, so there's nothing to invent. | |
| // Pitch is correct when samples are fed at the AudioContext's sample rate. | |
| // Hann low-pass FIR, used to reconstruct the thermal (1-bit dither) stream. | |
| // Applied over the CONTINUOUS sample ring (no per-row seams), so there is no | |
| // row-boundary buzz. No-op for the gray / digital modes. | |
| const LP_L = 21, LP_HALF = LP_L >> 1; | |
| const LP_W = (() => { | |
| const w = new Float32Array(LP_L); let s = 0; | |
| for (let i = 0; i < LP_L; i++) { w[i] = 0.5 - 0.5 * Math.cos((2 * Math.PI * i) / (LP_L - 1)); s += w[i]; } | |
| for (let i = 0; i < LP_L; i++) w[i] /= s; | |
| return w; | |
| })(); | |
| class PCMPlayer extends AudioWorkletProcessor { | |
| constructor() { | |
| super(); | |
| this.ring = new Float32Array(1 << 19); // ~0.5 M samples of slack | |
| this.mask = this.ring.length - 1; | |
| this.w = 0; this.r = 0; this.gain = 1.0; | |
| this.lp = false; // thermal reconstruction low-pass | |
| this.port.onmessage = (e) => { | |
| const d = e.data; | |
| if (d.type === "samples") { | |
| const s = d.data, ring = this.ring, mask = this.mask; | |
| for (let i = 0; i < s.length; i++) { ring[this.w & mask] = s[i]; this.w++; } | |
| } else if (d.type === "gain") { | |
| this.gain = d.v; | |
| } else if (d.type === "lowpass") { | |
| this.lp = !!d.on; | |
| } else if (d.type === "reset") { | |
| this.r = this.w; // drop anything buffered | |
| } | |
| }; | |
| } | |
| sampleAt(r) { | |
| const ring = this.ring, mask = this.mask; | |
| if (!this.lp) return ring[r & mask]; | |
| let acc = 0; // Hann low-pass with look-ahead | |
| for (let t = -LP_HALF; t <= LP_HALF; t++) { | |
| const idx = r + t; | |
| acc += LP_W[t + LP_HALF] * (idx >= 0 && idx < this.w ? ring[idx & mask] : 0); | |
| } | |
| return acc; | |
| } | |
| process(inputs, outputs) { | |
| const ch = outputs[0], out = ch[0], n = out.length, g = this.gain; | |
| // Keep LP_HALF samples of look-ahead available for the low-pass. | |
| const avail = () => this.w - this.r > (this.lp ? LP_HALF : 0); | |
| for (let k = 0; k < n; k++) { | |
| out[k] = avail() ? Math.tanh(this.sampleAt(this.r) * g) : 0; | |
| if (this.r < this.w) this.r++; | |
| } | |
| const cap = (sampleRate * 0.5) | 0; // bound write-ahead latency | |
| if (this.w - this.r > cap) this.r = this.w - cap; | |
| for (let c = 1; c < ch.length; c++) ch[c].set(out); | |
| return true; | |
| } | |
| } | |
| registerProcessor("pcm", PCMPlayer); | |