@weasel-js/audio 1.10.1 → 1.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -0
- package/dist/index.d.ts +180 -5
- package/dist/index.js +1736 -600
- package/dist/index.js.map +1 -1
- package/dist/voiceProcessor.gen-KWFGBRBP.js +6 -0
- package/dist/voiceProcessor.gen-KWFGBRBP.js.map +1 -0
- package/package.json +4 -1
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// src/worklet/voiceProcessor.gen.ts
|
|
2
|
+
var voiceProcessorSource = '"use strict";\n(() => {\n // worklet/protocol.ts\n var VOICE_PROCESSOR = "weasel-voices";\n\n // biquad.ts\n function biquadCoefficients(type, frequency, sampleRate2, Q = 1, gain = 0, into = { b0: 0, b1: 0, b2: 0, a1: 0, a2: 0 }) {\n const f = Math.min(Math.max(frequency, 1e-3), sampleRate2 * 0.4999);\n const w0 = 2 * Math.PI * f / sampleRate2;\n const cos = Math.cos(w0);\n const sin = Math.sin(w0);\n const A = 10 ** (gain / 40);\n const alphaQ = sin / (2 * Math.max(Q, 1e-4));\n const alphaDb = sin / (2 * 10 ** (Q / 20));\n const shelf = sin * Math.SQRT2 * Math.sqrt(A);\n let b0 = (1 - cos) / 2;\n let b1 = 1 - cos;\n let b2 = b0;\n let a0 = 1 + alphaDb;\n let a1 = -2 * cos;\n let a2 = 1 - alphaDb;\n if (type === "highpass") {\n b0 = b2 = (1 + cos) / 2;\n b1 = -(1 + cos);\n } else if (type === "peaking") {\n b0 = 1 + alphaQ * A;\n b1 = -2 * cos;\n b2 = 1 - alphaQ * A;\n a0 = 1 + alphaQ / A;\n a2 = 1 - alphaQ / A;\n } else if (type === "lowshelf") {\n b0 = A * (A + 1 - (A - 1) * cos + shelf);\n b1 = 2 * A * (A - 1 - (A + 1) * cos);\n b2 = A * (A + 1 - (A - 1) * cos - shelf);\n a0 = A + 1 + (A - 1) * cos + shelf;\n a1 = -2 * (A - 1 + (A + 1) * cos);\n a2 = A + 1 + (A - 1) * cos - shelf;\n } else if (type === "highshelf") {\n b0 = A * (A + 1 + (A - 1) * cos + shelf);\n b1 = -2 * A * (A - 1 + (A + 1) * cos);\n b2 = A * (A + 1 + (A - 1) * cos - shelf);\n a0 = A + 1 - (A - 1) * cos + shelf;\n a1 = 2 * (A - 1 - (A + 1) * cos);\n a2 = A + 1 - (A - 1) * cos - shelf;\n } else if (type !== "lowpass") {\n a0 = 1 + alphaQ;\n a2 = 1 - alphaQ;\n b0 = type === "bandpass" ? alphaQ : type === "notch" ? 1 : 1 - alphaQ;\n b1 = type === "bandpass" ? 0 : -2 * cos;\n b2 = type === "bandpass" ? -alphaQ : type === "notch" ? 1 : 1 + alphaQ;\n }\n into.b0 = b0 / a0;\n into.b1 = b1 / a0;\n into.b2 = b2 / a0;\n into.a1 = a1 / a0;\n into.a2 = a2 / a0;\n return into;\n }\n\n // envelope.ts\n function resolveEnvelope(env = {}) {\n return {\n attack: Math.max(0, env.attack ?? 5),\n decay: Math.max(0, env.decay ?? 0),\n sustain: Math.min(1, Math.max(0, env.sustain ?? 1)),\n release: Math.max(0, env.release ?? 30)\n };\n }\n var heldLevel = (e, t) => {\n if (t < e.attack) return t / e.attack;\n if (t < e.attack + e.decay) return 1 - (1 - e.sustain) * (t - e.attack) / e.decay;\n return e.sustain;\n };\n function envelopeLevel(e, t, gate) {\n if (gate === void 0 || t <= gate) return heldLevel(e, t);\n if (e.release === 0) return 0;\n return Math.max(0, heldLevel(e, gate) * (1 - (t - gate) / e.release));\n }\n\n // oscillator.ts\n var TAU = 2 * Math.PI;\n var PEAK_POINTS = 4096;\n var TRIANGLE_SUMMED_FROM = 1 / 128;\n function polyBlep(t, dt) {\n const d = Math.min(dt, 0.5);\n if (t < d) {\n const x = t / d;\n return x + x - x * x - 1;\n }\n if (t > 1 - d) {\n const x = (t - 1) / d;\n return x * x + x + x + 1;\n }\n return 0;\n }\n var sineShape = (phase) => Math.sin(TAU * phase);\n var STEPPED_LEVEL = 1 / 1.17898;\n var BUILT_IN = {\n sine: sineShape,\n square: (phase, dt) => STEPPED_LEVEL * ((phase < 0.5 ? 1 : -1) + polyBlep(phase, dt) - polyBlep((phase + 0.5) % 1, dt)),\n sawtooth: (phase, dt) => {\n const t = (phase + 0.5) % 1;\n return STEPPED_LEVEL * (2 * t - 1 - polyBlep(t, dt));\n },\n triangle: (phase, dt) => {\n if (dt < TRIANGLE_SUMMED_FROM) return phase < 0.25 ? 4 * phase : phase < 0.75 ? 2 - 4 * phase : 4 * phase - 4;\n let v = 0;\n let sign = 1;\n for (let k = 1; k * dt < 0.5; k += 2) {\n v += sign * Math.sin(TAU * k * phase) / (k * k);\n sign = -sign;\n }\n return v * 8 / (Math.PI * Math.PI);\n }\n };\n function harmonicShape(levels) {\n const sum = (phase, count) => {\n let v = 0;\n for (let k = 1; k <= count; k += 1) v += levels[k - 1] * Math.sin(TAU * k * phase);\n return v;\n };\n let peak = 0;\n for (let i = 0; i < PEAK_POINTS; i += 1) peak = Math.max(peak, Math.abs(sum(i / PEAK_POINTS, levels.length)));\n const scale = peak > 0 ? 1 / peak : 0;\n return (phase, dt) => scale * sum(phase, Math.min(levels.length, Math.ceil(0.5 / dt) - 1));\n }\n function waveShape(wave) {\n return typeof wave === "string" ? BUILT_IN[wave] : harmonicShape(wave);\n }\n\n // pitch.ts\n function midiToFrequency(midi) {\n return 440 * 2 ** ((midi - 69) / 12);\n }\n var LETTERS = { c: 0, d: 2, e: 4, f: 5, g: 7, a: 9, b: 11 };\n function noteToMidi(name) {\n const m = /^([A-Ga-g])([#b]*)(-?\\d+)$/.exec(name.trim());\n if (!m) throw new Error(`@weasel-js/audio: cannot read "${name}" as a note name`);\n let semitone = LETTERS[m[1].toLowerCase()];\n for (const acc of m[2]) semitone += acc === "#" ? 1 : -1;\n return (Number(m[3]) + 1) * 12 + semitone;\n }\n function toFrequency(pitch) {\n const hz = typeof pitch === "number" ? pitch : typeof pitch === "string" ? midiToFrequency(noteToMidi(pitch)) : midiToFrequency(pitch.midi);\n if (!(hz > 0) || !Number.isFinite(hz)) {\n throw new RangeError(`@weasel-js/audio: a pitch must be a positive frequency, got ${hz}`);\n }\n return hz;\n }\n\n // synthVoice.ts\n var NOISE_SECONDS = 2;\n var NOISE_RMS = 0.25;\n function noiseSamples(color, length) {\n const out = new Float32Array(length);\n let s = 2654435769;\n const white = () => {\n s = Math.imul(s, 1664525) + 1013904223 >>> 0;\n return s / 4294967296 * 2 - 1;\n };\n if (color === "white") {\n for (let i = 0; i < length; i += 1) out[i] = white();\n } else if (color === "pink") {\n let b0 = 0, b1 = 0, b2 = 0, b3 = 0, b4 = 0, b5 = 0, b6 = 0;\n for (let i = 0; i < length; i += 1) {\n const w = white();\n b0 = 0.99886 * b0 + w * 0.0555179;\n b1 = 0.99332 * b1 + w * 0.0750759;\n b2 = 0.969 * b2 + w * 0.153852;\n b3 = 0.8665 * b3 + w * 0.3104856;\n b4 = 0.55 * b4 + w * 0.5329522;\n b5 = -0.7616 * b5 - w * 0.016898;\n out[i] = b0 + b1 + b2 + b3 + b4 + b5 + b6 + w * 0.5362;\n b6 = w * 0.115926;\n }\n } else {\n let y = 0;\n for (let i = 0; i < length; i += 1) {\n y = (y + 0.02 * white()) / 1.02;\n out[i] = y;\n }\n }\n const step = (out[length - 1] - out[0]) / Math.max(1, length - 1);\n let mean = 0;\n for (let i = 0; i < length; i += 1) {\n out[i] -= step * i;\n mean += out[i];\n }\n mean /= length;\n let sq = 0;\n for (let i = 0; i < length; i += 1) {\n out[i] -= mean;\n sq += out[i] * out[i];\n }\n const scale = sq > 0 ? NOISE_RMS / Math.sqrt(sq / length) : 0;\n for (let i = 0; i < length; i += 1) out[i] *= scale;\n return out;\n }\n var isInharmonic = (wave) => typeof wave === "object" && !Array.isArray(wave);\n function resolvePartials(wave) {\n if (wave.partials.length === 0) {\n throw new RangeError("@weasel-js/audio: an inharmonic wave needs at least one partial");\n }\n let total = 0;\n for (const p of wave.partials) {\n if (!(p.ratio > 0) || !Number.isFinite(p.ratio)) {\n throw new RangeError(`@weasel-js/audio: partial ratio must be a positive number, got ${p.ratio}`);\n }\n if (p.decay !== void 0 && !(p.decay > 0)) {\n throw new RangeError(`@weasel-js/audio: partial decay must be a positive number of ms, got ${p.decay}`);\n }\n total += Math.abs(p.gain ?? 1);\n }\n return wave.partials.map((p) => ({\n ratio: p.ratio,\n gain: total > 0 ? (p.gain ?? 1) / total : 0,\n decay: p.decay\n }));\n }\n function resolveNote(note) {\n const wave = note.wave ?? "sine";\n return {\n from: toFrequency(note.pitch),\n glideTo: note.glide ? toFrequency(note.glide.to) : void 0,\n partials: isInharmonic(wave) ? resolvePartials(wave) : null\n };\n }\n\n // voiceKernel.ts\n var tuning = (rate, detune) => rate * 2 ** (detune / 1200);\n function filterStage(spec, sampleRate2) {\n const type = spec.type ?? "lowpass";\n const amount = spec.amount ?? 0;\n const env = spec.envelope && amount !== 0 ? resolveEnvelope(spec.envelope) : null;\n const coefficients = biquadCoefficients(type, spec.frequency, sampleRate2, spec.Q, spec.gain);\n let cutoff = spec.frequency;\n let z1 = 0;\n let z2 = 0;\n return (x, t, gate) => {\n if (env) {\n const hz = spec.frequency + amount * envelopeLevel(env, t, gate);\n if (hz !== cutoff) {\n cutoff = hz;\n biquadCoefficients(type, hz, sampleRate2, spec.Q, spec.gain, coefficients);\n }\n }\n const y = coefficients.b0 * x + z1;\n z1 = coefficients.b1 * x - coefficients.a1 * y + z2;\n z2 = coefficients.b2 * x - coefficients.a2 * y;\n return y;\n };\n }\n function createKernel(patch, sampleRate2, source) {\n const env = resolveEnvelope(patch.envelope);\n const gain = patch.gain ?? 1;\n const filter = patch.filter ? filterStage(patch.filter, sampleRate2) : null;\n const msPerSample = 1e3 / sampleRate2;\n let gate = patch.duration;\n let tune = tuning(patch.rate ?? 1, patch.detune ?? 0);\n let n = 0;\n let ended = false;\n return {\n render(out, from = 0, to = out.length) {\n if (ended) return false;\n for (let i = from; i < to; i += 1) {\n const t = n * msPerSample;\n if (gate !== void 0 && t >= gate + env.release) {\n ended = true;\n return false;\n }\n const raw = source(t, tune);\n out[i] += gain * envelopeLevel(env, t, gate) * (filter ? filter(raw, t, gate) : raw);\n n += 1;\n }\n return true;\n },\n release() {\n const t = n * msPerSample;\n if (gate === void 0 || t < gate) gate = t;\n },\n retune(rate, detune) {\n tune = tuning(rate, detune);\n }\n };\n }\n function createNoteKernel(note, sampleRate2) {\n const { from, glideTo, partials } = resolveNote(note);\n const glideMs = note.glide?.ms ?? note.duration ?? 100;\n const linear = note.glide?.curve === "linear";\n const wave = note.wave ?? "sine";\n const shape = isInharmonic(wave) ? sineShape : waveShape(wave);\n const tones = partials ? partials.map((p) => ({ ratio: p.ratio, gain: p.gain, decay: p.decay, phase: 0 })) : [{ ratio: 1, gain: 1, phase: 0 }];\n return createKernel(note, sampleRate2, (t, tune) => {\n let hz = from;\n if (glideTo !== void 0) {\n const k = glideMs > 0 ? Math.min(1, t / glideMs) : 1;\n hz = linear ? from + (glideTo - from) * k : from * (glideTo / from) ** k;\n }\n const cycles = hz * tune / sampleRate2;\n let sum = 0;\n for (const tone of tones) {\n const dt = cycles * tone.ratio;\n if (dt >= 0.5) continue;\n const level = tone.decay === void 0 ? tone.gain : tone.gain * Math.exp(-t / tone.decay);\n sum += level * shape(tone.phase, dt);\n tone.phase = (tone.phase + dt) % 1;\n }\n return sum;\n });\n }\n var loops = /* @__PURE__ */ new Map();\n function noiseLoop(color, sampleRate2) {\n const key = `${color}:${sampleRate2}`;\n let loop = loops.get(key);\n if (!loop) {\n loop = noiseSamples(color, Math.round(sampleRate2 * NOISE_SECONDS));\n loops.set(key, loop);\n }\n return loop;\n }\n function createNoiseKernel(noise, sampleRate2, random = Math.random) {\n const loop = noiseLoop(noise.noise, sampleRate2);\n let at = Math.floor(random() * loop.length);\n return createKernel(noise, sampleRate2, (_t, tune) => {\n const i = Math.floor(at);\n const a = loop[i];\n const sample = a + (loop[(i + 1) % loop.length] - a) * (at - i);\n at = (at + tune) % loop.length;\n return sample;\n });\n }\n\n // worklet/voiceMixer.ts\n var levelAt = (v, frame) => {\n if (frame >= v.rampEnd) return v.to;\n if (frame <= v.rampStart) return v.from;\n return v.from + (v.to - v.from) * (frame - v.rampStart) / (v.rampEnd - v.rampStart);\n };\n function createVoiceMixer(sampleRate2) {\n const voices = /* @__PURE__ */ new Map();\n let scratch = new Float32Array(128);\n const frameOf = (seconds) => Math.round(seconds * sampleRate2);\n const add = (id, kernel, at, level, pan) => {\n voices.set(id, {\n kernel,\n start: frameOf(at),\n stop: Infinity,\n pan,\n from: level,\n to: level,\n rampStart: 0,\n rampEnd: 0\n });\n };\n return {\n receive(event, frame) {\n if (event.kind === "note" || event.kind === "noise") {\n const kernel = event.kind === "note" ? createNoteKernel(event.patch, sampleRate2) : createNoiseKernel(event.patch, sampleRate2, () => event.seed);\n kernel.retune(event.rate, event.detune);\n add(event.id, kernel, event.at, event.level, event.pan);\n return;\n }\n if (event.kind === "dispose") {\n voices.clear();\n return;\n }\n const v = voices.get(event.id);\n if (!v) return;\n if (event.kind === "level") {\n v.from = levelAt(v, frame);\n v.to = event.value;\n v.rampStart = frame;\n v.rampEnd = Math.max(frame, event.end > 0 ? frameOf(event.end) : frame);\n } else if (event.kind === "pan") {\n v.pan = event.value;\n } else if (event.kind === "retune") {\n v.kernel.retune(event.rate, event.detune);\n } else if (event.kind === "release") {\n v.kernel.release();\n } else {\n v.stop = Math.min(v.stop, Math.max(frame, frameOf(event.at)));\n }\n },\n render(left, right, frame) {\n const n = left.length;\n if (scratch.length < n) scratch = new Float32Array(n);\n const ended = [];\n for (const [id, v] of voices) {\n const from = Math.max(0, v.start - frame);\n const to = Math.min(n, v.stop - frame);\n let alive = true;\n if (from < to) {\n scratch.fill(0, from, to);\n alive = v.kernel.render(scratch, from, to);\n const x = (Math.min(1, Math.max(-1, v.pan)) + 1) * Math.PI / 4;\n const gl = Math.cos(x);\n const gr = Math.sin(x);\n for (let i = from; i < to; i += 1) {\n const s = scratch[i] * levelAt(v, frame + i);\n left[i] += s * gl;\n right[i] += s * gr;\n }\n }\n if (!alive || v.stop <= frame + n) {\n voices.delete(id);\n ended.push(id);\n }\n }\n return ended;\n },\n size: () => voices.size\n };\n }\n\n // worklet/voiceProcessor.ts\n var VoiceProcessor = class extends AudioWorkletProcessor {\n mixer = createVoiceMixer(sampleRate);\n disposed = false;\n constructor() {\n super();\n this.port.onmessage = (e) => {\n if (e.data.kind === "dispose") this.disposed = true;\n this.mixer.receive(e.data, currentFrame);\n };\n }\n process(_inputs, outputs) {\n if (this.disposed) return false;\n const [left, right] = outputs[0];\n for (const id of this.mixer.render(left, right ?? new Float32Array(left.length), currentFrame)) {\n this.port.postMessage({ kind: "ended", id });\n }\n return true;\n }\n };\n try {\n registerProcessor(VOICE_PROCESSOR, VoiceProcessor);\n } catch {\n }\n})();\n';
|
|
3
|
+
|
|
4
|
+
export { voiceProcessorSource };
|
|
5
|
+
//# sourceMappingURL=voiceProcessor.gen-KWFGBRBP.js.map
|
|
6
|
+
//# sourceMappingURL=voiceProcessor.gen-KWFGBRBP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/worklet/voiceProcessor.gen.ts"],"names":[],"mappings":";AAIO,IAAM,oBAAA,GAA+B","file":"voiceProcessor.gen-KWFGBRBP.js","sourcesContent":["// Generated by packages/audio/scripts/gen-worklet.mjs from voiceProcessor.ts and what it\n// imports. Do not edit: run `npm run gen:audio-worklet`; `npm run check:audio-worklet` fails while it is stale.\n\n/** The voice processor as one script, for `audioWorklet.addModule`. */\nexport const voiceProcessorSource: string = \"\\\"use strict\\\";\\n(() => {\\n // worklet/protocol.ts\\n var VOICE_PROCESSOR = \\\"weasel-voices\\\";\\n\\n // biquad.ts\\n function biquadCoefficients(type, frequency, sampleRate2, Q = 1, gain = 0, into = { b0: 0, b1: 0, b2: 0, a1: 0, a2: 0 }) {\\n const f = Math.min(Math.max(frequency, 1e-3), sampleRate2 * 0.4999);\\n const w0 = 2 * Math.PI * f / sampleRate2;\\n const cos = Math.cos(w0);\\n const sin = Math.sin(w0);\\n const A = 10 ** (gain / 40);\\n const alphaQ = sin / (2 * Math.max(Q, 1e-4));\\n const alphaDb = sin / (2 * 10 ** (Q / 20));\\n const shelf = sin * Math.SQRT2 * Math.sqrt(A);\\n let b0 = (1 - cos) / 2;\\n let b1 = 1 - cos;\\n let b2 = b0;\\n let a0 = 1 + alphaDb;\\n let a1 = -2 * cos;\\n let a2 = 1 - alphaDb;\\n if (type === \\\"highpass\\\") {\\n b0 = b2 = (1 + cos) / 2;\\n b1 = -(1 + cos);\\n } else if (type === \\\"peaking\\\") {\\n b0 = 1 + alphaQ * A;\\n b1 = -2 * cos;\\n b2 = 1 - alphaQ * A;\\n a0 = 1 + alphaQ / A;\\n a2 = 1 - alphaQ / A;\\n } else if (type === \\\"lowshelf\\\") {\\n b0 = A * (A + 1 - (A - 1) * cos + shelf);\\n b1 = 2 * A * (A - 1 - (A + 1) * cos);\\n b2 = A * (A + 1 - (A - 1) * cos - shelf);\\n a0 = A + 1 + (A - 1) * cos + shelf;\\n a1 = -2 * (A - 1 + (A + 1) * cos);\\n a2 = A + 1 + (A - 1) * cos - shelf;\\n } else if (type === \\\"highshelf\\\") {\\n b0 = A * (A + 1 + (A - 1) * cos + shelf);\\n b1 = -2 * A * (A - 1 + (A + 1) * cos);\\n b2 = A * (A + 1 + (A - 1) * cos - shelf);\\n a0 = A + 1 - (A - 1) * cos + shelf;\\n a1 = 2 * (A - 1 - (A + 1) * cos);\\n a2 = A + 1 - (A - 1) * cos - shelf;\\n } else if (type !== \\\"lowpass\\\") {\\n a0 = 1 + alphaQ;\\n a2 = 1 - alphaQ;\\n b0 = type === \\\"bandpass\\\" ? alphaQ : type === \\\"notch\\\" ? 1 : 1 - alphaQ;\\n b1 = type === \\\"bandpass\\\" ? 0 : -2 * cos;\\n b2 = type === \\\"bandpass\\\" ? -alphaQ : type === \\\"notch\\\" ? 1 : 1 + alphaQ;\\n }\\n into.b0 = b0 / a0;\\n into.b1 = b1 / a0;\\n into.b2 = b2 / a0;\\n into.a1 = a1 / a0;\\n into.a2 = a2 / a0;\\n return into;\\n }\\n\\n // envelope.ts\\n function resolveEnvelope(env = {}) {\\n return {\\n attack: Math.max(0, env.attack ?? 5),\\n decay: Math.max(0, env.decay ?? 0),\\n sustain: Math.min(1, Math.max(0, env.sustain ?? 1)),\\n release: Math.max(0, env.release ?? 30)\\n };\\n }\\n var heldLevel = (e, t) => {\\n if (t < e.attack) return t / e.attack;\\n if (t < e.attack + e.decay) return 1 - (1 - e.sustain) * (t - e.attack) / e.decay;\\n return e.sustain;\\n };\\n function envelopeLevel(e, t, gate) {\\n if (gate === void 0 || t <= gate) return heldLevel(e, t);\\n if (e.release === 0) return 0;\\n return Math.max(0, heldLevel(e, gate) * (1 - (t - gate) / e.release));\\n }\\n\\n // oscillator.ts\\n var TAU = 2 * Math.PI;\\n var PEAK_POINTS = 4096;\\n var TRIANGLE_SUMMED_FROM = 1 / 128;\\n function polyBlep(t, dt) {\\n const d = Math.min(dt, 0.5);\\n if (t < d) {\\n const x = t / d;\\n return x + x - x * x - 1;\\n }\\n if (t > 1 - d) {\\n const x = (t - 1) / d;\\n return x * x + x + x + 1;\\n }\\n return 0;\\n }\\n var sineShape = (phase) => Math.sin(TAU * phase);\\n var STEPPED_LEVEL = 1 / 1.17898;\\n var BUILT_IN = {\\n sine: sineShape,\\n square: (phase, dt) => STEPPED_LEVEL * ((phase < 0.5 ? 1 : -1) + polyBlep(phase, dt) - polyBlep((phase + 0.5) % 1, dt)),\\n sawtooth: (phase, dt) => {\\n const t = (phase + 0.5) % 1;\\n return STEPPED_LEVEL * (2 * t - 1 - polyBlep(t, dt));\\n },\\n triangle: (phase, dt) => {\\n if (dt < TRIANGLE_SUMMED_FROM) return phase < 0.25 ? 4 * phase : phase < 0.75 ? 2 - 4 * phase : 4 * phase - 4;\\n let v = 0;\\n let sign = 1;\\n for (let k = 1; k * dt < 0.5; k += 2) {\\n v += sign * Math.sin(TAU * k * phase) / (k * k);\\n sign = -sign;\\n }\\n return v * 8 / (Math.PI * Math.PI);\\n }\\n };\\n function harmonicShape(levels) {\\n const sum = (phase, count) => {\\n let v = 0;\\n for (let k = 1; k <= count; k += 1) v += levels[k - 1] * Math.sin(TAU * k * phase);\\n return v;\\n };\\n let peak = 0;\\n for (let i = 0; i < PEAK_POINTS; i += 1) peak = Math.max(peak, Math.abs(sum(i / PEAK_POINTS, levels.length)));\\n const scale = peak > 0 ? 1 / peak : 0;\\n return (phase, dt) => scale * sum(phase, Math.min(levels.length, Math.ceil(0.5 / dt) - 1));\\n }\\n function waveShape(wave) {\\n return typeof wave === \\\"string\\\" ? BUILT_IN[wave] : harmonicShape(wave);\\n }\\n\\n // pitch.ts\\n function midiToFrequency(midi) {\\n return 440 * 2 ** ((midi - 69) / 12);\\n }\\n var LETTERS = { c: 0, d: 2, e: 4, f: 5, g: 7, a: 9, b: 11 };\\n function noteToMidi(name) {\\n const m = /^([A-Ga-g])([#b]*)(-?\\\\d+)$/.exec(name.trim());\\n if (!m) throw new Error(`@weasel-js/audio: cannot read \\\"${name}\\\" as a note name`);\\n let semitone = LETTERS[m[1].toLowerCase()];\\n for (const acc of m[2]) semitone += acc === \\\"#\\\" ? 1 : -1;\\n return (Number(m[3]) + 1) * 12 + semitone;\\n }\\n function toFrequency(pitch) {\\n const hz = typeof pitch === \\\"number\\\" ? pitch : typeof pitch === \\\"string\\\" ? midiToFrequency(noteToMidi(pitch)) : midiToFrequency(pitch.midi);\\n if (!(hz > 0) || !Number.isFinite(hz)) {\\n throw new RangeError(`@weasel-js/audio: a pitch must be a positive frequency, got ${hz}`);\\n }\\n return hz;\\n }\\n\\n // synthVoice.ts\\n var NOISE_SECONDS = 2;\\n var NOISE_RMS = 0.25;\\n function noiseSamples(color, length) {\\n const out = new Float32Array(length);\\n let s = 2654435769;\\n const white = () => {\\n s = Math.imul(s, 1664525) + 1013904223 >>> 0;\\n return s / 4294967296 * 2 - 1;\\n };\\n if (color === \\\"white\\\") {\\n for (let i = 0; i < length; i += 1) out[i] = white();\\n } else if (color === \\\"pink\\\") {\\n let b0 = 0, b1 = 0, b2 = 0, b3 = 0, b4 = 0, b5 = 0, b6 = 0;\\n for (let i = 0; i < length; i += 1) {\\n const w = white();\\n b0 = 0.99886 * b0 + w * 0.0555179;\\n b1 = 0.99332 * b1 + w * 0.0750759;\\n b2 = 0.969 * b2 + w * 0.153852;\\n b3 = 0.8665 * b3 + w * 0.3104856;\\n b4 = 0.55 * b4 + w * 0.5329522;\\n b5 = -0.7616 * b5 - w * 0.016898;\\n out[i] = b0 + b1 + b2 + b3 + b4 + b5 + b6 + w * 0.5362;\\n b6 = w * 0.115926;\\n }\\n } else {\\n let y = 0;\\n for (let i = 0; i < length; i += 1) {\\n y = (y + 0.02 * white()) / 1.02;\\n out[i] = y;\\n }\\n }\\n const step = (out[length - 1] - out[0]) / Math.max(1, length - 1);\\n let mean = 0;\\n for (let i = 0; i < length; i += 1) {\\n out[i] -= step * i;\\n mean += out[i];\\n }\\n mean /= length;\\n let sq = 0;\\n for (let i = 0; i < length; i += 1) {\\n out[i] -= mean;\\n sq += out[i] * out[i];\\n }\\n const scale = sq > 0 ? NOISE_RMS / Math.sqrt(sq / length) : 0;\\n for (let i = 0; i < length; i += 1) out[i] *= scale;\\n return out;\\n }\\n var isInharmonic = (wave) => typeof wave === \\\"object\\\" && !Array.isArray(wave);\\n function resolvePartials(wave) {\\n if (wave.partials.length === 0) {\\n throw new RangeError(\\\"@weasel-js/audio: an inharmonic wave needs at least one partial\\\");\\n }\\n let total = 0;\\n for (const p of wave.partials) {\\n if (!(p.ratio > 0) || !Number.isFinite(p.ratio)) {\\n throw new RangeError(`@weasel-js/audio: partial ratio must be a positive number, got ${p.ratio}`);\\n }\\n if (p.decay !== void 0 && !(p.decay > 0)) {\\n throw new RangeError(`@weasel-js/audio: partial decay must be a positive number of ms, got ${p.decay}`);\\n }\\n total += Math.abs(p.gain ?? 1);\\n }\\n return wave.partials.map((p) => ({\\n ratio: p.ratio,\\n gain: total > 0 ? (p.gain ?? 1) / total : 0,\\n decay: p.decay\\n }));\\n }\\n function resolveNote(note) {\\n const wave = note.wave ?? \\\"sine\\\";\\n return {\\n from: toFrequency(note.pitch),\\n glideTo: note.glide ? toFrequency(note.glide.to) : void 0,\\n partials: isInharmonic(wave) ? resolvePartials(wave) : null\\n };\\n }\\n\\n // voiceKernel.ts\\n var tuning = (rate, detune) => rate * 2 ** (detune / 1200);\\n function filterStage(spec, sampleRate2) {\\n const type = spec.type ?? \\\"lowpass\\\";\\n const amount = spec.amount ?? 0;\\n const env = spec.envelope && amount !== 0 ? resolveEnvelope(spec.envelope) : null;\\n const coefficients = biquadCoefficients(type, spec.frequency, sampleRate2, spec.Q, spec.gain);\\n let cutoff = spec.frequency;\\n let z1 = 0;\\n let z2 = 0;\\n return (x, t, gate) => {\\n if (env) {\\n const hz = spec.frequency + amount * envelopeLevel(env, t, gate);\\n if (hz !== cutoff) {\\n cutoff = hz;\\n biquadCoefficients(type, hz, sampleRate2, spec.Q, spec.gain, coefficients);\\n }\\n }\\n const y = coefficients.b0 * x + z1;\\n z1 = coefficients.b1 * x - coefficients.a1 * y + z2;\\n z2 = coefficients.b2 * x - coefficients.a2 * y;\\n return y;\\n };\\n }\\n function createKernel(patch, sampleRate2, source) {\\n const env = resolveEnvelope(patch.envelope);\\n const gain = patch.gain ?? 1;\\n const filter = patch.filter ? filterStage(patch.filter, sampleRate2) : null;\\n const msPerSample = 1e3 / sampleRate2;\\n let gate = patch.duration;\\n let tune = tuning(patch.rate ?? 1, patch.detune ?? 0);\\n let n = 0;\\n let ended = false;\\n return {\\n render(out, from = 0, to = out.length) {\\n if (ended) return false;\\n for (let i = from; i < to; i += 1) {\\n const t = n * msPerSample;\\n if (gate !== void 0 && t >= gate + env.release) {\\n ended = true;\\n return false;\\n }\\n const raw = source(t, tune);\\n out[i] += gain * envelopeLevel(env, t, gate) * (filter ? filter(raw, t, gate) : raw);\\n n += 1;\\n }\\n return true;\\n },\\n release() {\\n const t = n * msPerSample;\\n if (gate === void 0 || t < gate) gate = t;\\n },\\n retune(rate, detune) {\\n tune = tuning(rate, detune);\\n }\\n };\\n }\\n function createNoteKernel(note, sampleRate2) {\\n const { from, glideTo, partials } = resolveNote(note);\\n const glideMs = note.glide?.ms ?? note.duration ?? 100;\\n const linear = note.glide?.curve === \\\"linear\\\";\\n const wave = note.wave ?? \\\"sine\\\";\\n const shape = isInharmonic(wave) ? sineShape : waveShape(wave);\\n const tones = partials ? partials.map((p) => ({ ratio: p.ratio, gain: p.gain, decay: p.decay, phase: 0 })) : [{ ratio: 1, gain: 1, phase: 0 }];\\n return createKernel(note, sampleRate2, (t, tune) => {\\n let hz = from;\\n if (glideTo !== void 0) {\\n const k = glideMs > 0 ? Math.min(1, t / glideMs) : 1;\\n hz = linear ? from + (glideTo - from) * k : from * (glideTo / from) ** k;\\n }\\n const cycles = hz * tune / sampleRate2;\\n let sum = 0;\\n for (const tone of tones) {\\n const dt = cycles * tone.ratio;\\n if (dt >= 0.5) continue;\\n const level = tone.decay === void 0 ? tone.gain : tone.gain * Math.exp(-t / tone.decay);\\n sum += level * shape(tone.phase, dt);\\n tone.phase = (tone.phase + dt) % 1;\\n }\\n return sum;\\n });\\n }\\n var loops = /* @__PURE__ */ new Map();\\n function noiseLoop(color, sampleRate2) {\\n const key = `${color}:${sampleRate2}`;\\n let loop = loops.get(key);\\n if (!loop) {\\n loop = noiseSamples(color, Math.round(sampleRate2 * NOISE_SECONDS));\\n loops.set(key, loop);\\n }\\n return loop;\\n }\\n function createNoiseKernel(noise, sampleRate2, random = Math.random) {\\n const loop = noiseLoop(noise.noise, sampleRate2);\\n let at = Math.floor(random() * loop.length);\\n return createKernel(noise, sampleRate2, (_t, tune) => {\\n const i = Math.floor(at);\\n const a = loop[i];\\n const sample = a + (loop[(i + 1) % loop.length] - a) * (at - i);\\n at = (at + tune) % loop.length;\\n return sample;\\n });\\n }\\n\\n // worklet/voiceMixer.ts\\n var levelAt = (v, frame) => {\\n if (frame >= v.rampEnd) return v.to;\\n if (frame <= v.rampStart) return v.from;\\n return v.from + (v.to - v.from) * (frame - v.rampStart) / (v.rampEnd - v.rampStart);\\n };\\n function createVoiceMixer(sampleRate2) {\\n const voices = /* @__PURE__ */ new Map();\\n let scratch = new Float32Array(128);\\n const frameOf = (seconds) => Math.round(seconds * sampleRate2);\\n const add = (id, kernel, at, level, pan) => {\\n voices.set(id, {\\n kernel,\\n start: frameOf(at),\\n stop: Infinity,\\n pan,\\n from: level,\\n to: level,\\n rampStart: 0,\\n rampEnd: 0\\n });\\n };\\n return {\\n receive(event, frame) {\\n if (event.kind === \\\"note\\\" || event.kind === \\\"noise\\\") {\\n const kernel = event.kind === \\\"note\\\" ? createNoteKernel(event.patch, sampleRate2) : createNoiseKernel(event.patch, sampleRate2, () => event.seed);\\n kernel.retune(event.rate, event.detune);\\n add(event.id, kernel, event.at, event.level, event.pan);\\n return;\\n }\\n if (event.kind === \\\"dispose\\\") {\\n voices.clear();\\n return;\\n }\\n const v = voices.get(event.id);\\n if (!v) return;\\n if (event.kind === \\\"level\\\") {\\n v.from = levelAt(v, frame);\\n v.to = event.value;\\n v.rampStart = frame;\\n v.rampEnd = Math.max(frame, event.end > 0 ? frameOf(event.end) : frame);\\n } else if (event.kind === \\\"pan\\\") {\\n v.pan = event.value;\\n } else if (event.kind === \\\"retune\\\") {\\n v.kernel.retune(event.rate, event.detune);\\n } else if (event.kind === \\\"release\\\") {\\n v.kernel.release();\\n } else {\\n v.stop = Math.min(v.stop, Math.max(frame, frameOf(event.at)));\\n }\\n },\\n render(left, right, frame) {\\n const n = left.length;\\n if (scratch.length < n) scratch = new Float32Array(n);\\n const ended = [];\\n for (const [id, v] of voices) {\\n const from = Math.max(0, v.start - frame);\\n const to = Math.min(n, v.stop - frame);\\n let alive = true;\\n if (from < to) {\\n scratch.fill(0, from, to);\\n alive = v.kernel.render(scratch, from, to);\\n const x = (Math.min(1, Math.max(-1, v.pan)) + 1) * Math.PI / 4;\\n const gl = Math.cos(x);\\n const gr = Math.sin(x);\\n for (let i = from; i < to; i += 1) {\\n const s = scratch[i] * levelAt(v, frame + i);\\n left[i] += s * gl;\\n right[i] += s * gr;\\n }\\n }\\n if (!alive || v.stop <= frame + n) {\\n voices.delete(id);\\n ended.push(id);\\n }\\n }\\n return ended;\\n },\\n size: () => voices.size\\n };\\n }\\n\\n // worklet/voiceProcessor.ts\\n var VoiceProcessor = class extends AudioWorkletProcessor {\\n mixer = createVoiceMixer(sampleRate);\\n disposed = false;\\n constructor() {\\n super();\\n this.port.onmessage = (e) => {\\n if (e.data.kind === \\\"dispose\\\") this.disposed = true;\\n this.mixer.receive(e.data, currentFrame);\\n };\\n }\\n process(_inputs, outputs) {\\n if (this.disposed) return false;\\n const [left, right] = outputs[0];\\n for (const id of this.mixer.render(left, right ?? new Float32Array(left.length), currentFrame)) {\\n this.port.postMessage({ kind: \\\"ended\\\", id });\\n }\\n return true;\\n }\\n };\\n try {\\n registerProcessor(VOICE_PROCESSOR, VoiceProcessor);\\n } catch {\\n }\\n})();\\n\";\n"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weasel-js/audio",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.1",
|
|
4
4
|
"description": "Web Audio engine for 2D scenes: voices, buses, lookahead scheduling, spatialization and analysis. No weasel dependencies.",
|
|
5
|
+
"weasel": {
|
|
6
|
+
"needs": ["webaudio"]
|
|
7
|
+
},
|
|
5
8
|
"license": "MIT",
|
|
6
9
|
"type": "module",
|
|
7
10
|
"sideEffects": false,
|