From 058cb36640e880b7818c9b556f1415655e7b6fd4 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Tue, 20 May 2025 20:28:52 +0100 Subject: [PATCH 01/10] structure --- packages/superdough/worklets.mjs | 52 ++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index 03d3a966f..66148ecbc 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -761,3 +761,55 @@ class PulseOscillatorProcessor extends AudioWorkletProcessor { } registerProcessor('pulse-oscillator', PulseOscillatorProcessor); + + + +class ByteBeatProcessor extends AudioWorkletProcessor { + constructor() { + super(); + this.pi = _PI; + } + + static get parameterDescriptors() { + return [ + { + name: 'begin', + defaultValue: 0, + max: Number.POSITIVE_INFINITY, + min: 0, + }, + + { + name: 'end', + defaultValue: 0, + max: Number.POSITIVE_INFINITY, + min: 0, + }, + ]; + } + + process(inputs, outputs, params) { + if (this.disconnected) { + return false; + } + if (currentTime <= params.begin[0]) { + return true; + } + if (currentTime >= params.end[0]) { + return false; + } + const output = outputs[0]; + + for (let i = 0; i < (output[0].length ?? 0); i++) { + + for (let o = 0; o < output.length; o++) { + // Combination of both oscillators with envelope applied + output[o][i] = 0.15 * (out0 - out1) * this.envf; + } + } + + return true; // keep the audio processing going + } +} + +registerProcessor('byte-beat-processor', BeatBeatProcessor); From b8d2d024667e525d09c388fe8402437c23952d9f Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Tue, 20 May 2025 21:41:40 +0100 Subject: [PATCH 02/10] bb --- packages/core/controls.mjs | 14 ++++++++ packages/superdough/superdough.mjs | 2 ++ packages/superdough/synth.mjs | 57 ++++++++++++++++++++++++++++++ packages/superdough/worklets.mjs | 16 +++++++-- 4 files changed, 86 insertions(+), 3 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 383c2ffa7..94f034480 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -445,6 +445,20 @@ export const { coarse } = registerControl('coarse'); */ export const { drive } = registerControl('drive'); +/** + * Allows you to set the output channels on the interface + * + * @name byteBeatExpression + * @synonyms bbexpr + * + * @param {number | Pattern} byteBeatExpression pattern the output channels + * @example + * note("e a d b g").channels("3:4") + * + */ +export const { byteBeatExpression, bbexpr } = registerControl('byteBeatExpression', 'bbexpr'); + + /** * Allows you to set the output channels on the interface * diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index 55b72d165..0afbb3d67 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -137,6 +137,7 @@ const defaultDefaultValues = { shapevol: 1, distortvol: 1, delay: 0, + byteBeatExpression: '0', delayfeedback: 0.5, delaytime: 0.25, orbit: 1, @@ -475,6 +476,7 @@ export const superdough = async (value, t, hapDuration) => { let { s = getDefaultValue('s'), bank, + byteBeatExpression, source, gain = getDefaultValue('gain'), postgain = getDefaultValue('postgain'), diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index 9a1e4d150..63c5a0b37 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -144,6 +144,63 @@ export function registerSynthSounds() { { prebake: true, type: 'synth' }, ); + + registerSound( + 'bytebeat', + (begin, value, onended) => { + const ac = getAudioContext(); + let { byteBeatExpression } = value; + + let { duration} = value; + const [attack, decay, sustain, release] = getADSRValues( + [value.attack, value.decay, value.sustain, value.release], + 'linear', + [0.001, 0.05, 0.6, 0.01], + ); + const holdend = begin + duration; + const end = holdend + release + 0.01; + let o = getWorklet( + ac, + 'byte-beat-processor', + { + + begin, + end, + }, + { + outputChannelCount: [2], + }, + ); + + + o.port.postMessage(byteBeatExpression); + + let envGain = gainNode(1); + envGain = o.connect(envGain); + + getParamADSR(envGain.gain, attack, decay, sustain, release, 0, 1, begin, holdend, 'linear'); + + let timeoutNode = webAudioTimeout( + ac, + () => { + destroyAudioWorkletNode(o); + envGain.disconnect(); + onended(); + }, + begin, + end, + ); + + return { + node: envGain, + stop: (time) => { + timeoutNode.stop(time); + }, + }; + }, + { prebake: true, type: 'synth' }, + ); + registerSound( 'pulse', (begin, value, onended) => { diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index 66148ecbc..bd52e3233 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -767,7 +767,10 @@ registerProcessor('pulse-oscillator', PulseOscillatorProcessor); class ByteBeatProcessor extends AudioWorkletProcessor { constructor() { super(); - this.pi = _PI; + this.bb = '0' + this.port.onmessage = (event) => { + this.bb = event.data; + }; } static get parameterDescriptors() { @@ -798,13 +801,20 @@ class ByteBeatProcessor extends AudioWorkletProcessor { if (currentTime >= params.end[0]) { return false; } + const bb = this.bb + + const f = Function('t', 'return ' + bb) + const output = outputs[0]; for (let i = 0; i < (output[0].length ?? 0); i++) { + let t = currentTime + t *= sampleRate + const signal = ((f(t)&255)/127.5 - 1)/4 for (let o = 0; o < output.length; o++) { // Combination of both oscillators with envelope applied - output[o][i] = 0.15 * (out0 - out1) * this.envf; + output[o][i] = signal } } @@ -812,4 +822,4 @@ class ByteBeatProcessor extends AudioWorkletProcessor { } } -registerProcessor('byte-beat-processor', BeatBeatProcessor); +registerProcessor('byte-beat-processor', ByteBeatProcessor); From 65f1760fad44fdd8a452448a327dc6b9065780ca Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Tue, 20 May 2025 21:45:14 +0100 Subject: [PATCH 03/10] format --- packages/core/controls.mjs | 5 ++--- packages/superdough/synth.mjs | 7 ++----- packages/superdough/worklets.mjs | 17 +++++++---------- 3 files changed, 11 insertions(+), 18 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 94f034480..5e0c450d6 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -446,19 +446,18 @@ export const { coarse } = registerControl('coarse'); export const { drive } = registerControl('drive'); /** - * Allows you to set the output channels on the interface + * Create byte beats with custom expressions * * @name byteBeatExpression * @synonyms bbexpr * * @param {number | Pattern} byteBeatExpression pattern the output channels * @example - * note("e a d b g").channels("3:4") + * s("bytebeat").bbexpr('t*(t>>15^t>>66)') * */ export const { byteBeatExpression, bbexpr } = registerControl('byteBeatExpression', 'bbexpr'); - /** * Allows you to set the output channels on the interface * diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index 63c5a0b37..dd2c92451 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -144,14 +144,13 @@ export function registerSynthSounds() { { prebake: true, type: 'synth' }, ); - registerSound( 'bytebeat', (begin, value, onended) => { const ac = getAudioContext(); let { byteBeatExpression } = value; - let { duration} = value; + let { duration } = value; const [attack, decay, sustain, release] = getADSRValues( [value.attack, value.decay, value.sustain, value.release], 'linear', @@ -163,7 +162,6 @@ export function registerSynthSounds() { ac, 'byte-beat-processor', { - begin, end, }, @@ -172,14 +170,13 @@ export function registerSynthSounds() { }, ); - o.port.postMessage(byteBeatExpression); let envGain = gainNode(1); envGain = o.connect(envGain); getParamADSR(envGain.gain, attack, decay, sustain, release, 0, 1, begin, holdend, 'linear'); - + let timeoutNode = webAudioTimeout( ac, () => { diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index bd52e3233..8d14ac445 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -762,12 +762,10 @@ class PulseOscillatorProcessor extends AudioWorkletProcessor { registerProcessor('pulse-oscillator', PulseOscillatorProcessor); - - class ByteBeatProcessor extends AudioWorkletProcessor { constructor() { super(); - this.bb = '0' + this.bb = '0'; this.port.onmessage = (event) => { this.bb = event.data; }; @@ -801,20 +799,19 @@ class ByteBeatProcessor extends AudioWorkletProcessor { if (currentTime >= params.end[0]) { return false; } - const bb = this.bb + const bb = this.bb; - const f = Function('t', 'return ' + bb) + const f = Function('t', 'return ' + bb); const output = outputs[0]; for (let i = 0; i < (output[0].length ?? 0); i++) { - - let t = currentTime - t *= sampleRate - const signal = ((f(t)&255)/127.5 - 1)/4 + let t = currentTime; + t *= sampleRate; + const signal = ((f(t) & 255) / 127.5 - 1) / 4; for (let o = 0; o < output.length; o++) { // Combination of both oscillators with envelope applied - output[o][i] = signal + output[o][i] = signal; } } From 45bccf13ffce223243879a847ec1916957964246 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Tue, 20 May 2025 21:47:43 +0100 Subject: [PATCH 04/10] add test --- test/__snapshots__/examples.test.mjs.snap | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index e7d6dbb18..b9cdb444b 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -1516,6 +1516,15 @@ exports[`runs examples > example "brandBy" example index 0 1`] = ` ] `; +exports[`runs examples > example "byteBeatExpression" example index 0 1`] = ` +[ + "[ 0/1 → 1/1 | s:bytebeat byteBeatExpression:t*(t>>15^t>>66) ]", + "[ 1/1 → 2/1 | s:bytebeat byteBeatExpression:t*(t>>15^t>>66) ]", + "[ 2/1 → 3/1 | s:bytebeat byteBeatExpression:t*(t>>15^t>>66) ]", + "[ 3/1 → 4/1 | s:bytebeat byteBeatExpression:t*(t>>15^t>>66) ]", +] +`; + exports[`runs examples > example "cat" example index 0 1`] = ` [ "[ 0/1 → 1/1 | note:e5 ]", From 5e4eb7fc53f995cbdad132ad71d6828d8b7c2e5c Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Tue, 20 May 2025 21:53:10 +0100 Subject: [PATCH 05/10] gain --- packages/superdough/worklets.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index 8d14ac445..b4923d5aa 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -811,7 +811,7 @@ class ByteBeatProcessor extends AudioWorkletProcessor { const signal = ((f(t) & 255) / 127.5 - 1) / 4; for (let o = 0; o < output.length; o++) { // Combination of both oscillators with envelope applied - output[o][i] = signal; + output[o][i] = signal * .4; } } From 608dfd515e4b305ae9152de7a114d313a7742563 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Tue, 27 May 2025 14:37:55 +0200 Subject: [PATCH 06/10] WIP oscillations --- .vscode/launch.json | 16 + packages/superdough/remove.html | 295 ++++++++++++++++++ packages/superdough/superdough.mjs | 4 +- packages/superdough/synth.mjs | 3 + packages/superdough/worklets.mjs | 460 ++++++++++++++++++++++++++++- 5 files changed, 763 insertions(+), 15 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 packages/superdough/remove.html diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..5bd1b92fe --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + + { + "type": "chrome", + "request": "launch", + "name": "Launch Chrome against localhost", + "url": "http://localhost:4321", + "webRoot": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/packages/superdough/remove.html b/packages/superdough/remove.html new file mode 100644 index 000000000..59476043c --- /dev/null +++ b/packages/superdough/remove.html @@ -0,0 +1,295 @@ +🌱 audio worklets +In the last post, we've been filling fixed length audio buffers. While this is very simple to implement, it's a bit annoying when the audio is always stopping. To calculate samples infinitely, we can use an AudioWorklet. Now we've arrived at a typical bytebeat/floatbeat editor: + +💡 hit ctrl+enter to update the code & ctrl+. to stop +((( // by stimmer (2011-10-03) +t*(4|t>>13&3)>>(~t>>11&1)&128|t*(t>>11&t>>13)*(~t>>9&3)&127 +) & 255) / 127.5 - 1)/4 + play | stop +The AudioWorklet is the lowest abstaction we get with the Web Audio API. It essentially gives us a function that fills a buffer of 128 samples over and over again, also running isolated from the rest of the page. At a sample rate of 44100/s, this means we fill a buffer every ~3ms (44100/128). + +Here are some examples to try: + +hello sine +slow sine (inaudible) +just intonation triad +hello saw +hello pulse +wandering sine +bytebeat 1 +bytebeat 2 +gladyouask - chirps +aks - TR-808-like hihat +show source (271 loc) + + + + 🌱 audio worklets + + + +

🌱 audio worklets

+

+ In the last post, we've been filling + fixed length audio buffers. While this is very simple to implement, it's a + bit annoying when the audio is always stopping. To calculate samples + infinitely, we can use an AudioWorklet. Now we've arrived at a typical + bytebeat/floatbeat editor: +

+
+ 💡 hit ctrl+enter to update the code & ctrl+. to stop +
+ + play | stop + +

+ The AudioWorklet is the lowest abstaction we get with the Web Audio API. + It essentially gives us a function that fills a buffer of 128 samples over + and over again, also running isolated from the rest of the page. At a + sample rate of 44100/s, this means we fill a buffer every ~3ms + (44100/128). +

+

Here are some examples to try:

+ + +
+ show page source +

+    
+

+ back to garten.salat +

+ + +back to garten.salat \ No newline at end of file diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index 0afbb3d67..07ab08054 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -446,7 +446,7 @@ function mapChannelNumbers(channels) { return (Array.isArray(channels) ? channels : [channels]).map((ch) => ch - 1); } -export const superdough = async (value, t, hapDuration) => { +export const superdough = async (value, t, hapDuration, cps) => { const ac = getAudioContext(); t = typeof t === 'string' && t.startsWith('=') ? Number(t.slice(1)) : ac.currentTime + t; let { stretch } = value; @@ -580,7 +580,7 @@ export const superdough = async (value, t, hapDuration) => { // get source AudioNode let sourceNode; if (source) { - sourceNode = source(t, value, hapDuration); + sourceNode = source(t, value, hapDuration,cps); } else if (getSound(s)) { const { onTrigger } = getSound(s); const onEnded = () => { diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index dd2c92451..554bb1e9a 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -158,10 +158,13 @@ export function registerSynthSounds() { ); const holdend = begin + duration; const end = holdend + release + 0.01; + const frequency = getFrequencyFromValue(value); + let o = getWorklet( ac, 'byte-beat-processor', { + frequency, begin, end, }, diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index b4923d5aa..3d1bbccbe 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -295,7 +295,7 @@ class LadderProcessor extends AudioWorkletProcessor { cutoff = (cutoff * 2 * _PI) / sampleRate; cutoff = cutoff > 1 ? 1 : cutoff; - const k = Math.min(8, resonance * 0.4); + const k = Math.min(8, resonance * 0.13); // drive makeup * resonance volume loss makeup let makeupgain = (1 / drive) * Math.min(1.75, 1 + k); @@ -762,13 +762,29 @@ class PulseOscillatorProcessor extends AudioWorkletProcessor { registerProcessor('pulse-oscillator', PulseOscillatorProcessor); + +function frequencyToDigit(freq, baseFreq) { + const d = Math.log2(freq / baseFreq); + return Math.max(d, 0) + Math.pow(2, Math.min(d, 0)); +} + class ByteBeatProcessor extends AudioWorkletProcessor { constructor() { super(); - this.bb = '0'; + this.codeText = '0'; this.port.onmessage = (event) => { - this.bb = event.data; + + this.codeText = event.data.trim().replace( + /^eval\(unescape\(escape(?:`|\('|\("|\(`)(.*?)(?:`|'\)|"\)|`\)).replace\(\/u\(\.\.\)\/g,["'`]\$1%["'`]\)\)\)$/, + (match, m1) => unescape(escape(m1).replace(/u(..)/g, '$1%'))); + }; + this.virtualRate = 32000; // target sample rate + // this.nativeRate = sampleRate; // actual context rate, e.g., 48000 + // this.ratio = this.nativeRate / this.virtualRate; + this.t = null; + this.framebuffer = new Float32Array(Math.floor(sampleRate/60)); + this.func = null; } static get parameterDescriptors() { @@ -779,7 +795,17 @@ class ByteBeatProcessor extends AudioWorkletProcessor { max: Number.POSITIVE_INFINITY, min: 0, }, - + { + name: 'frequency', + defaultValue: 440, + min: Number.EPSILON, + }, + { + name: 'detune', + defaultValue: 0, + min: Number.NEGATIVE_INFINITY, + max: Number.POSITIVE_INFINITY, + }, { name: 'end', defaultValue: 0, @@ -799,24 +825,432 @@ class ByteBeatProcessor extends AudioWorkletProcessor { if (currentTime >= params.end[0]) { return false; } - const bb = this.bb; + if (this.t == null) { + this.t = params.begin[0] * this.virtualRate + } + + + let codeText = this.codeText; + + // const f = Function('t', 'return ' + codeText); + + + // const chyx = { + // /*bit*/ "bitC": function (x, y, z) { return x & y ? z : 0 }, + // /*bit reverse*/"br": function (x, size = 8) { + // if(size > 32) { throw new Error("br() Size cannot be greater than 32") } else { + // let result = 0; + // for (let idx = 0; idx < (size - 0); idx++) { + // result += chyx.bitC(x, 2 ** idx, 2 ** (size - (idx + 1))) + // } + // return result + // } + // }, + // /*sin that loops every 128 "steps", instead of every pi steps*/"sinf": function (x) { return Math.sin(x / (128 / Math.PI)) }, + // /*cos that loops every 128 "steps", instead of every pi steps*/"cosf": function (x) { return Math.cos(x / (128 / Math.PI)) }, + // /*tan that loops every 128 "steps", instead of every pi steps*/"tanf": function (x) { return Math.tan(x / (128 / Math.PI)) }, + // /*converts t into a string composed of it's bits, regex's that*/"regG": function (t, X) { return X.test(t.toString(2)) } + // /*corrupt sound"crpt": function(x,y=8) {return chyx.br(chyx.br(x,y)+t,y)^chyx.br(t,y)}, + // decorrupt sound"decrpt": function(x,y=8) {return chyx.br(chyx.br(x^chyx.br(t,y),y)-t,y)},*/ + // } + // // Create shortened Math functions + // const mathParams = Object.getOwnPropertyNames(Math); + // const values = mathParams.map(k => Math[k]); + // const chyxNames = Object.getOwnPropertyNames(chyx); + // const chyxFuncs = chyxNames.map(k => chyx[k]); + // mathParams.push('int', 'window', ...chyxNames); + // values.push(Math.floor, globalThis, ...chyxFuncs); + + + + + // Optimize code like eval(unescape(escape`XXXX`.replace(/u(..)/g,"$1%"))) + // this.func = new Function(...mathParams, 't', `return 0,\n${ codeText || 0 };`).bind(globalThis, ...values); + this.func = Function('t', 'return ' + codeText); - const f = Function('t', 'return ' + bb); const output = outputs[0]; + const ct = params.begin[0] + const tIncrement = this.virtualRate / sampleRate + - for (let i = 0; i < (output[0].length ?? 0); i++) { - let t = currentTime; - t *= sampleRate; - const signal = ((f(t) & 255) / 127.5 - 1) / 4; - for (let o = 0; o < output.length; o++) { - // Combination of both oscillators with envelope applied - output[o][i] = signal * .4; + // console.info(ct, currentTime) + for (let i = 0; i < output[0].length; i++) { + let t = Math.floor(this.t) + const detune = getParamValue(i, params.detune); + const freq = applySemitoneDetuneToFrequency(getParamValue(i, params.frequency), detune / 100); + // t = t * (Math.log(freq / 440)) / Math.LN2; + + t = t * frequencyToDigit(freq, 440) + // console.info(t) + // let t = (currentTime * sampleRate) + i + // let t = this.t + + // const index = Math.floor(this.t) + const funcValue = this.func(t) + let signal = (funcValue & 255) / 127.5 - 1 + const out = signal; + for (let c = 0; c < output.length; c++) { + output[c][i] = out; } + this.t = this.t + tIncrement + // if (i % 8 === 0){ + // this.t = this.t + tIncrement + + // } + // this.framebuffer[this.t%this.framebuffer.length] = out; + // if(this.t % this.framebuffer.length === 0 && this.t > 0) { + // this.port.postMessage(this.framebuffer) + // } } + + + // for (let i = 0; i < (output[0].length ?? 0); i++) { + // let t = currentTime; + // t *= sampleRate; + // const signal = this.func(t) + // // const signal = ((this.func(t) + 128) & 255) / 127.5 - 1 + // // const signal = ((this.func(t) & 255) / 127.5 - 1) / 4; + // for (let o = 0; o < output.length; o++) { + // // Combination of both oscillators with envelope applied + // output[o][i] = signal * .4; + // } + // } return true; // keep the audio processing going } } registerProcessor('byte-beat-processor', ByteBeatProcessor); + + + +// class ByteBeatProcessor extends AudioWorkletProcessor { +// constructor(...args) { +// super(...args); +// this.audioSample = 0; +// this.byteSample = 0; +// // this.drawMode = 'Points'; +// // this.errorDisplayed = true; +// this.func = null; +// this.getValues = null; +// this.isFuncbeat = false; +// this.isPlaying = true; +// this.playbackSpeed = 1; +// this.divisorStorage = 0; +// this.lastTime = -1; +// this.lastFuncValue = [null, null]; +// this.lastByteValue = [0, 0]; +// this.outValue = [0, 0]; +// // this.sampleRate = 8000; +// // this.sampleRatio = 1; +// this.sampleDivisor/*PRO*/ = 1; +// this.soundMode = 'Bytebeat'; +// // Object.seal(this); +// // ByteBeatProcessor.deleteGlobals(); +// // ByteBeatProcessor.freezeGlobals(); +// this.port.addEventListener('message', e => this.receiveData(e.data)); +// this.port.start(); +// } + +// static get parameterDescriptors() { +// return [ +// { +// name: 'begin', +// defaultValue: 0, +// max: Number.POSITIVE_INFINITY, +// min: 0, +// }, + +// { +// name: 'end', +// defaultValue: 0, +// max: Number.POSITIVE_INFINITY, +// min: 0, +// }, +// ]; +// } +// // static deleteGlobals() { +// // // Delete single letter variables to prevent persistent variable errors (covers a good enough range) +// // for(let i = 0; i < 26; ++i) { +// // delete globalThis[String.fromCharCode(65 + i)]; +// // delete globalThis[String.fromCharCode(97 + i)]; +// // } +// // // Delete global variables +// // for(const name in globalThis) { +// // if(Object.prototype.hasOwnProperty.call(globalThis, name)) { +// // delete globalThis[name]; +// // } +// // } +// // } +// // static freezeGlobals() { +// // Object.getOwnPropertyNames(globalThis).forEach(name => { +// // const prop = globalThis[name]; +// // const type = typeof prop; +// // if((type === 'object' || type === 'function') && name !== 'globalThis') { +// // Object.freeze(prop); +// // } +// // if(type === 'function' && Object.prototype.hasOwnProperty.call(prop, 'prototype')) { +// // Object.freeze(prop.prototype); +// // } +// // Object.defineProperty(globalThis, name, { writable: false, configurable: false }); +// // }); +// // } + +// process(inputs, [outputData], params) { +// if (currentTime <= params.begin[0]) { +// return true; +// } +// if (currentTime >= params.end[0]) { +// return false; +// } + + + +// const chDataLen = outputData[0].length; +// if(!chDataLen || !this.isPlaying) { +// return true; +// } +// let time = currentTime; +// let { byteSample } = this; +// const drawBuffer = []; +// const isDiagram = this.drawMode === 'Combined' || this.drawMode === 'Diagram' || this.drawMode === 'Spectrogram'; + +// for(let i = 0; i < chDataLen; ++i) { +// time += this.sampleRatio; +// const CT = Math.floor(time); +// if(this.lastTime !== CT) { +// let funcValue; +// const currentSample = Math.floor(byteSample); + +// // long cascade of null handlers +// const inputs0 = inputs[0] ?? [ ]; +// const inputs00 = inputs0[0] ?? [ ]; +// const inputs01 = inputs0[1] ?? inputs00; +// const inputs00i = inputs00[i] ?? 0; +// const inputs01i = inputs01[i] ?? 0; +// const micSample = [inputs00i, inputs01i, inputs00i / 2 + inputs01i / 2]; + +// if(this.isFuncbeat) { +// funcValue = this.func(currentSample / this.sampleRate, this.sampleRate, +// currentSample, micSample); +// } else { +// funcValue = this.func(currentSample, micSample); +// } + +// funcValue = Array.isArray(funcValue) ? [funcValue[0], funcValue[1]] : [funcValue, funcValue]; +// let hasValue = false; +// let ch = 2; +// while(ch--) { +// try { +// funcValue[ch] = +funcValue[ch]; +// } catch(err) { +// funcValue[ch] = NaN; +// } +// if(isDiagram) { +// if(!isNaN(funcValue[ch])) { +// this.outValue[ch] = this.getValues(funcValue[ch], ch); +// } else { +// this.lastByteValue[ch] = NaN; +// } +// hasValue = true; +// continue; +// } +// if(funcValue[ch] === this.lastFuncValue[ch]) { +// continue; +// } else if(!isNaN(funcValue[ch])) { +// this.outValue[ch] = this.getValues(funcValue[ch], ch); +// hasValue = true; +// } else if(!isNaN(this.lastFuncValue[ch])) { +// this.lastByteValue[ch] = NaN; +// hasValue = true; +// } +// } +// if(hasValue) { +// drawBuffer.push({ t: currentSample, value: [...this.lastByteValue] }); +// } +// byteSample += CT - this.lastTime; +// this.lastFuncValue = funcValue; +// this.lastTime = CT; +// } +// outputData[0][i] = this.outValue[0]; +// outputData[1][i] = this.outValue[1]; +// } +// if(Math.abs(byteSample) > Number.MAX_SAFE_INTEGER) { +// this.resetTime(); +// return true; +// } +// this.audioSample += chDataLen; +// let isSend = false; +// const data = {}; +// if(byteSample !== this.byteSample) { +// isSend = true; +// data.byteSample = this.byteSample = byteSample; +// } +// if(drawBuffer.length) { +// isSend = true; +// data.drawBuffer = drawBuffer; +// } +// if(isSend) { +// this.sendData(data); +// } +// return true; +// } +// receiveData(data) { +// if(data.byteSample !== undefined) { +// this.byteSample = +data.byteSample || 0; +// this.resetValues(); +// } + + +// if(data.playbackSpeed !== undefined) { +// const sampleRatio = this.sampleRatio / this.playbackSpeed; +// this.playbackSpeed = data.playbackSpeed; +// this.setSampleRatio(sampleRatio); +// } +// if(data.mode !== undefined) { +// this.isFuncbeat = data.mode === 'Funcbeat'; +// switch (data.mode) { +// case 'Bytebeat': +// this.getValues = (funcValue, ch) => (this.lastByteValue[ch] = funcValue & 255) / 127.5 - 1; +// break; +// case 'Signed Bytebeat': +// this.getValues = (funcValue, ch) => +// (this.lastByteValue[ch] = (funcValue + 128) & 255) / 127.5 - 1; +// break; +// case 'Floatbeat': +// case 'Funcbeat': +// this.getValues = (funcValue, ch) => { +// const limited = Math.max(Math.min(funcValue, 1), -1); +// this.lastByteValue[ch] = limited * 127.5 + 127.5 | 0 +// return limited; +// }; +// break; +// case 'Bitbeat': +// this.getValues = (funcValue, ch) => { +// this.lastByteValue[ch] = funcValue & 1 ? 255 : 0; +// return (funcValue & 1) - 0.5; +// }; +// break; +// case '2048': +// this.getValues = (funcValue, ch) => { +// this.lastByteValue[ch] = funcValue / 8 & 255; +// return (funcValue & 2047) / 1023.5 - 1 +// }; +// break; +// case 'logmode': +// this.getValues = (funcValue, ch) => (this.lastByteValue[ch] = (Math.log2(funcValue) * 32) & 255) / 127.5 - 1; +// break; +// case 'logHack': +// this.getValues = (funcValue, ch) => { +// const neg = (funcValue < 0) ? -32 : 32; +// return (this.lastByteValue[ch] = (Math.log2(Math.abs(funcValue)) * neg) & 255) / 127.5 - 1; +// }; +// break; +// case 'logHack2': +// this.getValues = (funcValue, ch) => { +// const neg = funcValue < 0 +// return funcValue == 0 ? 0 : ((this.lastByteValue[ch] = ((Math.log2(Math.abs(funcValue)) * (neg ? -16 : 16)) + (neg ? -127 : 128)) & 255) / 127.5 - 1); +// }; +// break; + +// default: this.getValues = (_funcValue) => NaN; +// } +// } +// if(data.setFunction !== undefined) { +// this.setFunction(data.setFunction); +// } +// if(data.resetTime === true) { +// this.resetTime(); +// } +// if(data.sampleRate !== undefined) { +// this.sampleRate = data.sampleRate; +// } +// if(data.sampleRatio !== undefined) { +// this.setSampleRatio(data.sampleRatio); +// } +// if(data.divisor !== undefined) { +// this.sampleDivisor = data.divisor; +// } +// if(data.DMode !== undefined) { +// this.soundMode = data.DMode; +// } +// if(data.drawMode !== undefined) { +// this.drawMode = data.drawMode; +// } +// } +// sendData(data) { +// this.port.postMessage(data); +// } +// resetTime() { +// this.byteSample = 0; +// this.resetValues(); +// this.sendData({ byteSample: 0 }); +// } +// resetValues() { +// this.audioSample = 0; +// this.lastTime = -1; +// this.outValue = [0, 0]; +// this.lastFuncValue = [null,null]; +// } +// setFunction(codeText) { +// const chyx = { +// /*bit*/ "bitC": function (x, y, z) { return x & y ? z : 0 }, +// /*bit reverse*/"br": function (x, size = 8) { +// if(size > 32) { throw new Error("br() Size cannot be greater than 32") } else { +// let result = 0; +// for (let idx = 0; idx < (size - 0); idx++) { +// result += chyx.bitC(x, 2 ** idx, 2 ** (size - (idx + 1))) +// } +// return result +// } +// }, +// /*sin that loops every 128 "steps", instead of every pi steps*/"sinf": function (x) { return Math.sin(x / (128 / Math.PI)) }, +// /*cos that loops every 128 "steps", instead of every pi steps*/"cosf": function (x) { return Math.cos(x / (128 / Math.PI)) }, +// /*tan that loops every 128 "steps", instead of every pi steps*/"tanf": function (x) { return Math.tan(x / (128 / Math.PI)) }, +// /*converts t into a string composed of it's bits, regex's that*/"regG": function (t, X) { return X.test(t.toString(2)) } +// /*corrupt sound"crpt": function(x,y=8) {return chyx.br(chyx.br(x,y)+t,y)^chyx.br(t,y)}, +// decorrupt sound"decrpt": function(x,y=8) {return chyx.br(chyx.br(x^chyx.br(t,y),y)-t,y)},*/ +// } +// // Create shortened Math functions +// const params = Object.getOwnPropertyNames(Math); +// const values = params.map(k => Math[k]); +// const chyxNames = Object.getOwnPropertyNames(chyx); +// const chyxFuncs = chyxNames.map(k => chyx[k]); +// params.push('int', 'window', ...chyxNames); +// values.push(Math.floor, globalThis, ...chyxFuncs); +// // ByteBeatProcessor.deleteGlobals(); +// // Code testing +// let isCompiled = false; +// const oldFunc = this.func; + + +// if(this.isFuncbeat) { +// this.func = new Function(...params, codeText).bind(globalThis, ...values); +// } else { +// // Optimize code like eval(unescape(escape`XXXX`.replace(/u(..)/g,"$1%"))) +// codeText = codeText.trim().replace( +// /^eval\(unescape\(escape(?:`|\('|\("|\(`)(.*?)(?:`|'\)|"\)|`\)).replace\(\/u\(\.\.\)\/g,["'`]\$1%["'`]\)\)\)$/, +// (match, m1) => unescape(escape(m1).replace(/u(..)/g, '$1%'))); +// this.func = new Function(...params, 't', '_micSample', `return 0,\n${ codeText || 0 };`) +// .bind(globalThis, ...values); +// } +// isCompiled = true; +// if(this.isFuncbeat) { +// this.func = this.func(); +// this.func(0, this.sampleRate, 0, [0, 0, 0]); +// } else { +// this.func(0, [0, 0, 0]); +// } + + +// this.sendData({ error: { message: '', isCompiled }, updateUrl: true }); +// } +// setSampleRatio(sampleRatio) { +// const timeOffset = Math.floor(this.sampleRatio * this.audioSample) - this.lastTime; +// this.sampleRatio = sampleRatio * this.playbackSpeed; +// this.lastTime = Math.floor(this.sampleRatio * this.audioSample) - timeOffset; +// } +// } + +// registerProcessor('byte-beat-processor', ByteBeatProcessor); \ No newline at end of file From 366637026b14d3f26fe058b6bf13c5654e57ab8b Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Tue, 27 May 2025 17:34:53 +0200 Subject: [PATCH 07/10] bbs --- packages/superdough/remove.html | 295 --------------------- packages/superdough/synth.mjs | 8 +- packages/superdough/worklets.mjs | 422 +------------------------------ 3 files changed, 17 insertions(+), 708 deletions(-) delete mode 100644 packages/superdough/remove.html diff --git a/packages/superdough/remove.html b/packages/superdough/remove.html deleted file mode 100644 index 59476043c..000000000 --- a/packages/superdough/remove.html +++ /dev/null @@ -1,295 +0,0 @@ -🌱 audio worklets -In the last post, we've been filling fixed length audio buffers. While this is very simple to implement, it's a bit annoying when the audio is always stopping. To calculate samples infinitely, we can use an AudioWorklet. Now we've arrived at a typical bytebeat/floatbeat editor: - -💡 hit ctrl+enter to update the code & ctrl+. to stop -((( // by stimmer (2011-10-03) -t*(4|t>>13&3)>>(~t>>11&1)&128|t*(t>>11&t>>13)*(~t>>9&3)&127 -) & 255) / 127.5 - 1)/4 - play | stop -The AudioWorklet is the lowest abstaction we get with the Web Audio API. It essentially gives us a function that fills a buffer of 128 samples over and over again, also running isolated from the rest of the page. At a sample rate of 44100/s, this means we fill a buffer every ~3ms (44100/128). - -Here are some examples to try: - -hello sine -slow sine (inaudible) -just intonation triad -hello saw -hello pulse -wandering sine -bytebeat 1 -bytebeat 2 -gladyouask - chirps -aks - TR-808-like hihat -show source (271 loc) - - - - 🌱 audio worklets - - - -

🌱 audio worklets

-

- In the last post, we've been filling - fixed length audio buffers. While this is very simple to implement, it's a - bit annoying when the audio is always stopping. To calculate samples - infinitely, we can use an AudioWorklet. Now we've arrived at a typical - bytebeat/floatbeat editor: -

-
- 💡 hit ctrl+enter to update the code & ctrl+. to stop -
- - play | stop - -

- The AudioWorklet is the lowest abstaction we get with the Web Audio API. - It essentially gives us a function that fills a buffer of 128 samples over - and over again, also running isolated from the rest of the page. At a - sample rate of 44100/s, this means we fill a buffer every ~3ms - (44100/128). -

-

Here are some examples to try:

- - -
- show page source -

-    
-

- back to garten.salat -

- - -back to garten.salat \ No newline at end of file diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index 554bb1e9a..abadec311 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -147,8 +147,14 @@ export function registerSynthSounds() { registerSound( 'bytebeat', (begin, value, onended) => { + const defaultBeats = [ + '(t%255 >= t/255%255)*255', + '(t*(t*8%60 <= 300)|(-t)*(t*4%512 < 256))+t/400', + 't', + ] + const { n } = value + const { byteBeatExpression = defaultBeats[n % defaultBeats.length] } = value const ac = getAudioContext(); - let { byteBeatExpression } = value; let { duration } = value; const [attack, decay, sustain, release] = getADSRValues( diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index 3d1bbccbe..282c9a717 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -762,28 +762,22 @@ class PulseOscillatorProcessor extends AudioWorkletProcessor { registerProcessor('pulse-oscillator', PulseOscillatorProcessor); - -function frequencyToDigit(freq, baseFreq) { - const d = Math.log2(freq / baseFreq); - return Math.max(d, 0) + Math.pow(2, Math.min(d, 0)); -} - class ByteBeatProcessor extends AudioWorkletProcessor { constructor() { super(); this.codeText = '0'; this.port.onmessage = (event) => { - this.codeText = event.data.trim().replace( + this.codeText = event.data.trim().replace( /^eval\(unescape\(escape(?:`|\('|\("|\(`)(.*?)(?:`|'\)|"\)|`\)).replace\(\/u\(\.\.\)\/g,["'`]\$1%["'`]\)\)\)$/, (match, m1) => unescape(escape(m1).replace(/u(..)/g, '$1%'))); - + }; - this.virtualRate = 32000; // target sample rate + this.virtualRate = 112600; // target sample rate // this.nativeRate = sampleRate; // actual context rate, e.g., 48000 // this.ratio = this.nativeRate / this.virtualRate; this.t = null; - this.framebuffer = new Float32Array(Math.floor(sampleRate/60)); + this.framebuffer = new Float32Array(Math.floor(sampleRate / 60)); this.func = null; } @@ -829,64 +823,19 @@ class ByteBeatProcessor extends AudioWorkletProcessor { this.t = params.begin[0] * this.virtualRate } - let codeText = this.codeText; - - // const f = Function('t', 'return ' + codeText); - - - // const chyx = { - // /*bit*/ "bitC": function (x, y, z) { return x & y ? z : 0 }, - // /*bit reverse*/"br": function (x, size = 8) { - // if(size > 32) { throw new Error("br() Size cannot be greater than 32") } else { - // let result = 0; - // for (let idx = 0; idx < (size - 0); idx++) { - // result += chyx.bitC(x, 2 ** idx, 2 ** (size - (idx + 1))) - // } - // return result - // } - // }, - // /*sin that loops every 128 "steps", instead of every pi steps*/"sinf": function (x) { return Math.sin(x / (128 / Math.PI)) }, - // /*cos that loops every 128 "steps", instead of every pi steps*/"cosf": function (x) { return Math.cos(x / (128 / Math.PI)) }, - // /*tan that loops every 128 "steps", instead of every pi steps*/"tanf": function (x) { return Math.tan(x / (128 / Math.PI)) }, - // /*converts t into a string composed of it's bits, regex's that*/"regG": function (t, X) { return X.test(t.toString(2)) } - // /*corrupt sound"crpt": function(x,y=8) {return chyx.br(chyx.br(x,y)+t,y)^chyx.br(t,y)}, - // decorrupt sound"decrpt": function(x,y=8) {return chyx.br(chyx.br(x^chyx.br(t,y),y)-t,y)},*/ - // } - // // Create shortened Math functions - // const mathParams = Object.getOwnPropertyNames(Math); - // const values = mathParams.map(k => Math[k]); - // const chyxNames = Object.getOwnPropertyNames(chyx); - // const chyxFuncs = chyxNames.map(k => chyx[k]); - // mathParams.push('int', 'window', ...chyxNames); - // values.push(Math.floor, globalThis, ...chyxFuncs); - - - - - // Optimize code like eval(unescape(escape`XXXX`.replace(/u(..)/g,"$1%"))) - // this.func = new Function(...mathParams, 't', `return 0,\n${ codeText || 0 };`).bind(globalThis, ...values); - this.func = Function('t', 'return ' + codeText); - - + this.func = Function('t', 'return ' + codeText); const output = outputs[0]; - const ct = params.begin[0] const tIncrement = this.virtualRate / sampleRate - - // console.info(ct, currentTime) + for (let i = 0; i < output[0].length; i++) { let t = Math.floor(this.t) const detune = getParamValue(i, params.detune); const freq = applySemitoneDetuneToFrequency(getParamValue(i, params.frequency), detune / 100); - // t = t * (Math.log(freq / 440)) / Math.LN2; - t = t * frequencyToDigit(freq, 440) - // console.info(t) - // let t = (currentTime * sampleRate) + i - // let t = this.t - - // const index = Math.floor(this.t) + t = t / (this.virtualRate / 256) * freq + const funcValue = this.func(t) let signal = (funcValue & 255) / 127.5 - 1 const out = signal; @@ -894,363 +843,12 @@ class ByteBeatProcessor extends AudioWorkletProcessor { output[c][i] = out; } this.t = this.t + tIncrement - // if (i % 8 === 0){ - // this.t = this.t + tIncrement - - // } - // this.framebuffer[this.t%this.framebuffer.length] = out; - // if(this.t % this.framebuffer.length === 0 && this.t > 0) { - // this.port.postMessage(this.framebuffer) - // } - } - - // for (let i = 0; i < (output[0].length ?? 0); i++) { - // let t = currentTime; - // t *= sampleRate; - // const signal = this.func(t) - // // const signal = ((this.func(t) + 128) & 255) / 127.5 - 1 - // // const signal = ((this.func(t) & 255) / 127.5 - 1) / 4; - // for (let o = 0; o < output.length; o++) { - // // Combination of both oscillators with envelope applied - // output[o][i] = signal * .4; - // } - // } + } + return true; // keep the audio processing going } } registerProcessor('byte-beat-processor', ByteBeatProcessor); - - - -// class ByteBeatProcessor extends AudioWorkletProcessor { -// constructor(...args) { -// super(...args); -// this.audioSample = 0; -// this.byteSample = 0; -// // this.drawMode = 'Points'; -// // this.errorDisplayed = true; -// this.func = null; -// this.getValues = null; -// this.isFuncbeat = false; -// this.isPlaying = true; -// this.playbackSpeed = 1; -// this.divisorStorage = 0; -// this.lastTime = -1; -// this.lastFuncValue = [null, null]; -// this.lastByteValue = [0, 0]; -// this.outValue = [0, 0]; -// // this.sampleRate = 8000; -// // this.sampleRatio = 1; -// this.sampleDivisor/*PRO*/ = 1; -// this.soundMode = 'Bytebeat'; -// // Object.seal(this); -// // ByteBeatProcessor.deleteGlobals(); -// // ByteBeatProcessor.freezeGlobals(); -// this.port.addEventListener('message', e => this.receiveData(e.data)); -// this.port.start(); -// } - -// static get parameterDescriptors() { -// return [ -// { -// name: 'begin', -// defaultValue: 0, -// max: Number.POSITIVE_INFINITY, -// min: 0, -// }, - -// { -// name: 'end', -// defaultValue: 0, -// max: Number.POSITIVE_INFINITY, -// min: 0, -// }, -// ]; -// } -// // static deleteGlobals() { -// // // Delete single letter variables to prevent persistent variable errors (covers a good enough range) -// // for(let i = 0; i < 26; ++i) { -// // delete globalThis[String.fromCharCode(65 + i)]; -// // delete globalThis[String.fromCharCode(97 + i)]; -// // } -// // // Delete global variables -// // for(const name in globalThis) { -// // if(Object.prototype.hasOwnProperty.call(globalThis, name)) { -// // delete globalThis[name]; -// // } -// // } -// // } -// // static freezeGlobals() { -// // Object.getOwnPropertyNames(globalThis).forEach(name => { -// // const prop = globalThis[name]; -// // const type = typeof prop; -// // if((type === 'object' || type === 'function') && name !== 'globalThis') { -// // Object.freeze(prop); -// // } -// // if(type === 'function' && Object.prototype.hasOwnProperty.call(prop, 'prototype')) { -// // Object.freeze(prop.prototype); -// // } -// // Object.defineProperty(globalThis, name, { writable: false, configurable: false }); -// // }); -// // } - -// process(inputs, [outputData], params) { -// if (currentTime <= params.begin[0]) { -// return true; -// } -// if (currentTime >= params.end[0]) { -// return false; -// } - - - -// const chDataLen = outputData[0].length; -// if(!chDataLen || !this.isPlaying) { -// return true; -// } -// let time = currentTime; -// let { byteSample } = this; -// const drawBuffer = []; -// const isDiagram = this.drawMode === 'Combined' || this.drawMode === 'Diagram' || this.drawMode === 'Spectrogram'; - -// for(let i = 0; i < chDataLen; ++i) { -// time += this.sampleRatio; -// const CT = Math.floor(time); -// if(this.lastTime !== CT) { -// let funcValue; -// const currentSample = Math.floor(byteSample); - -// // long cascade of null handlers -// const inputs0 = inputs[0] ?? [ ]; -// const inputs00 = inputs0[0] ?? [ ]; -// const inputs01 = inputs0[1] ?? inputs00; -// const inputs00i = inputs00[i] ?? 0; -// const inputs01i = inputs01[i] ?? 0; -// const micSample = [inputs00i, inputs01i, inputs00i / 2 + inputs01i / 2]; - -// if(this.isFuncbeat) { -// funcValue = this.func(currentSample / this.sampleRate, this.sampleRate, -// currentSample, micSample); -// } else { -// funcValue = this.func(currentSample, micSample); -// } - -// funcValue = Array.isArray(funcValue) ? [funcValue[0], funcValue[1]] : [funcValue, funcValue]; -// let hasValue = false; -// let ch = 2; -// while(ch--) { -// try { -// funcValue[ch] = +funcValue[ch]; -// } catch(err) { -// funcValue[ch] = NaN; -// } -// if(isDiagram) { -// if(!isNaN(funcValue[ch])) { -// this.outValue[ch] = this.getValues(funcValue[ch], ch); -// } else { -// this.lastByteValue[ch] = NaN; -// } -// hasValue = true; -// continue; -// } -// if(funcValue[ch] === this.lastFuncValue[ch]) { -// continue; -// } else if(!isNaN(funcValue[ch])) { -// this.outValue[ch] = this.getValues(funcValue[ch], ch); -// hasValue = true; -// } else if(!isNaN(this.lastFuncValue[ch])) { -// this.lastByteValue[ch] = NaN; -// hasValue = true; -// } -// } -// if(hasValue) { -// drawBuffer.push({ t: currentSample, value: [...this.lastByteValue] }); -// } -// byteSample += CT - this.lastTime; -// this.lastFuncValue = funcValue; -// this.lastTime = CT; -// } -// outputData[0][i] = this.outValue[0]; -// outputData[1][i] = this.outValue[1]; -// } -// if(Math.abs(byteSample) > Number.MAX_SAFE_INTEGER) { -// this.resetTime(); -// return true; -// } -// this.audioSample += chDataLen; -// let isSend = false; -// const data = {}; -// if(byteSample !== this.byteSample) { -// isSend = true; -// data.byteSample = this.byteSample = byteSample; -// } -// if(drawBuffer.length) { -// isSend = true; -// data.drawBuffer = drawBuffer; -// } -// if(isSend) { -// this.sendData(data); -// } -// return true; -// } -// receiveData(data) { -// if(data.byteSample !== undefined) { -// this.byteSample = +data.byteSample || 0; -// this.resetValues(); -// } - - -// if(data.playbackSpeed !== undefined) { -// const sampleRatio = this.sampleRatio / this.playbackSpeed; -// this.playbackSpeed = data.playbackSpeed; -// this.setSampleRatio(sampleRatio); -// } -// if(data.mode !== undefined) { -// this.isFuncbeat = data.mode === 'Funcbeat'; -// switch (data.mode) { -// case 'Bytebeat': -// this.getValues = (funcValue, ch) => (this.lastByteValue[ch] = funcValue & 255) / 127.5 - 1; -// break; -// case 'Signed Bytebeat': -// this.getValues = (funcValue, ch) => -// (this.lastByteValue[ch] = (funcValue + 128) & 255) / 127.5 - 1; -// break; -// case 'Floatbeat': -// case 'Funcbeat': -// this.getValues = (funcValue, ch) => { -// const limited = Math.max(Math.min(funcValue, 1), -1); -// this.lastByteValue[ch] = limited * 127.5 + 127.5 | 0 -// return limited; -// }; -// break; -// case 'Bitbeat': -// this.getValues = (funcValue, ch) => { -// this.lastByteValue[ch] = funcValue & 1 ? 255 : 0; -// return (funcValue & 1) - 0.5; -// }; -// break; -// case '2048': -// this.getValues = (funcValue, ch) => { -// this.lastByteValue[ch] = funcValue / 8 & 255; -// return (funcValue & 2047) / 1023.5 - 1 -// }; -// break; -// case 'logmode': -// this.getValues = (funcValue, ch) => (this.lastByteValue[ch] = (Math.log2(funcValue) * 32) & 255) / 127.5 - 1; -// break; -// case 'logHack': -// this.getValues = (funcValue, ch) => { -// const neg = (funcValue < 0) ? -32 : 32; -// return (this.lastByteValue[ch] = (Math.log2(Math.abs(funcValue)) * neg) & 255) / 127.5 - 1; -// }; -// break; -// case 'logHack2': -// this.getValues = (funcValue, ch) => { -// const neg = funcValue < 0 -// return funcValue == 0 ? 0 : ((this.lastByteValue[ch] = ((Math.log2(Math.abs(funcValue)) * (neg ? -16 : 16)) + (neg ? -127 : 128)) & 255) / 127.5 - 1); -// }; -// break; - -// default: this.getValues = (_funcValue) => NaN; -// } -// } -// if(data.setFunction !== undefined) { -// this.setFunction(data.setFunction); -// } -// if(data.resetTime === true) { -// this.resetTime(); -// } -// if(data.sampleRate !== undefined) { -// this.sampleRate = data.sampleRate; -// } -// if(data.sampleRatio !== undefined) { -// this.setSampleRatio(data.sampleRatio); -// } -// if(data.divisor !== undefined) { -// this.sampleDivisor = data.divisor; -// } -// if(data.DMode !== undefined) { -// this.soundMode = data.DMode; -// } -// if(data.drawMode !== undefined) { -// this.drawMode = data.drawMode; -// } -// } -// sendData(data) { -// this.port.postMessage(data); -// } -// resetTime() { -// this.byteSample = 0; -// this.resetValues(); -// this.sendData({ byteSample: 0 }); -// } -// resetValues() { -// this.audioSample = 0; -// this.lastTime = -1; -// this.outValue = [0, 0]; -// this.lastFuncValue = [null,null]; -// } -// setFunction(codeText) { -// const chyx = { -// /*bit*/ "bitC": function (x, y, z) { return x & y ? z : 0 }, -// /*bit reverse*/"br": function (x, size = 8) { -// if(size > 32) { throw new Error("br() Size cannot be greater than 32") } else { -// let result = 0; -// for (let idx = 0; idx < (size - 0); idx++) { -// result += chyx.bitC(x, 2 ** idx, 2 ** (size - (idx + 1))) -// } -// return result -// } -// }, -// /*sin that loops every 128 "steps", instead of every pi steps*/"sinf": function (x) { return Math.sin(x / (128 / Math.PI)) }, -// /*cos that loops every 128 "steps", instead of every pi steps*/"cosf": function (x) { return Math.cos(x / (128 / Math.PI)) }, -// /*tan that loops every 128 "steps", instead of every pi steps*/"tanf": function (x) { return Math.tan(x / (128 / Math.PI)) }, -// /*converts t into a string composed of it's bits, regex's that*/"regG": function (t, X) { return X.test(t.toString(2)) } -// /*corrupt sound"crpt": function(x,y=8) {return chyx.br(chyx.br(x,y)+t,y)^chyx.br(t,y)}, -// decorrupt sound"decrpt": function(x,y=8) {return chyx.br(chyx.br(x^chyx.br(t,y),y)-t,y)},*/ -// } -// // Create shortened Math functions -// const params = Object.getOwnPropertyNames(Math); -// const values = params.map(k => Math[k]); -// const chyxNames = Object.getOwnPropertyNames(chyx); -// const chyxFuncs = chyxNames.map(k => chyx[k]); -// params.push('int', 'window', ...chyxNames); -// values.push(Math.floor, globalThis, ...chyxFuncs); -// // ByteBeatProcessor.deleteGlobals(); -// // Code testing -// let isCompiled = false; -// const oldFunc = this.func; - - -// if(this.isFuncbeat) { -// this.func = new Function(...params, codeText).bind(globalThis, ...values); -// } else { -// // Optimize code like eval(unescape(escape`XXXX`.replace(/u(..)/g,"$1%"))) -// codeText = codeText.trim().replace( -// /^eval\(unescape\(escape(?:`|\('|\("|\(`)(.*?)(?:`|'\)|"\)|`\)).replace\(\/u\(\.\.\)\/g,["'`]\$1%["'`]\)\)\)$/, -// (match, m1) => unescape(escape(m1).replace(/u(..)/g, '$1%'))); -// this.func = new Function(...params, 't', '_micSample', `return 0,\n${ codeText || 0 };`) -// .bind(globalThis, ...values); -// } -// isCompiled = true; -// if(this.isFuncbeat) { -// this.func = this.func(); -// this.func(0, this.sampleRate, 0, [0, 0, 0]); -// } else { -// this.func(0, [0, 0, 0]); -// } - - -// this.sendData({ error: { message: '', isCompiled }, updateUrl: true }); -// } -// setSampleRatio(sampleRatio) { -// const timeOffset = Math.floor(this.sampleRatio * this.audioSample) - this.lastTime; -// this.sampleRatio = sampleRatio * this.playbackSpeed; -// this.lastTime = Math.floor(this.sampleRatio * this.audioSample) - timeOffset; -// } -// } - -// registerProcessor('byte-beat-processor', ByteBeatProcessor); \ No newline at end of file From 63f4f202f16730b57a6ec4e3ed735fb067ba114a Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Tue, 27 May 2025 17:35:35 +0200 Subject: [PATCH 08/10] working --- packages/superdough/superdough.mjs | 2 +- packages/superdough/synth.mjs | 10 +++------- packages/superdough/worklets.mjs | 28 +++++++++++++--------------- 3 files changed, 17 insertions(+), 23 deletions(-) diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index 07ab08054..79c9093af 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -580,7 +580,7 @@ export const superdough = async (value, t, hapDuration, cps) => { // get source AudioNode let sourceNode; if (source) { - sourceNode = source(t, value, hapDuration,cps); + sourceNode = source(t, value, hapDuration, cps); } else if (getSound(s)) { const { onTrigger } = getSound(s); const onEnded = () => { diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index abadec311..1bd2ff8fc 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -147,13 +147,9 @@ export function registerSynthSounds() { registerSound( 'bytebeat', (begin, value, onended) => { - const defaultBeats = [ - '(t%255 >= t/255%255)*255', - '(t*(t*8%60 <= 300)|(-t)*(t*4%512 < 256))+t/400', - 't', - ] - const { n } = value - const { byteBeatExpression = defaultBeats[n % defaultBeats.length] } = value + const defaultBeats = ['(t%255 >= t/255%255)*255', '(t*(t*8%60 <= 300)|(-t)*(t*4%512 < 256))+t/400', 't']; + const { n } = value; + const { byteBeatExpression = defaultBeats[n % defaultBeats.length] } = value; const ac = getAudioContext(); let { duration } = value; diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index 282c9a717..9c77857e2 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -767,11 +767,12 @@ class ByteBeatProcessor extends AudioWorkletProcessor { super(); this.codeText = '0'; this.port.onmessage = (event) => { - - this.codeText = event.data.trim().replace( - /^eval\(unescape\(escape(?:`|\('|\("|\(`)(.*?)(?:`|'\)|"\)|`\)).replace\(\/u\(\.\.\)\/g,["'`]\$1%["'`]\)\)\)$/, - (match, m1) => unescape(escape(m1).replace(/u(..)/g, '$1%'))); - + this.codeText = event.data + .trim() + .replace( + /^eval\(unescape\(escape(?:`|\('|\("|\(`)(.*?)(?:`|'\)|"\)|`\)).replace\(\/u\(\.\.\)\/g,["'`]\$1%["'`]\)\)\)$/, + (match, m1) => unescape(escape(m1).replace(/u(..)/g, '$1%')), + ); }; this.virtualRate = 112600; // target sample rate // this.nativeRate = sampleRate; // actual context rate, e.g., 48000 @@ -820,33 +821,30 @@ class ByteBeatProcessor extends AudioWorkletProcessor { return false; } if (this.t == null) { - this.t = params.begin[0] * this.virtualRate + this.t = params.begin[0] * this.virtualRate; } let codeText = this.codeText; this.func = Function('t', 'return ' + codeText); const output = outputs[0]; - const tIncrement = this.virtualRate / sampleRate - + const tIncrement = this.virtualRate / sampleRate; for (let i = 0; i < output[0].length; i++) { - let t = Math.floor(this.t) + let t = Math.floor(this.t); const detune = getParamValue(i, params.detune); const freq = applySemitoneDetuneToFrequency(getParamValue(i, params.frequency), detune / 100); - t = t / (this.virtualRate / 256) * freq + t = (t / (this.virtualRate / 256)) * freq; - const funcValue = this.func(t) - let signal = (funcValue & 255) / 127.5 - 1 + const funcValue = this.func(t); + let signal = (funcValue & 255) / 127.5 - 1; const out = signal; for (let c = 0; c < output.length; c++) { output[c][i] = out; } - this.t = this.t + tIncrement - + this.t = this.t + tIncrement; } - return true; // keep the audio processing going } } From 382dce65dff05128b5e847e95104c90ccd9d3da6 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Tue, 27 May 2025 17:39:55 +0200 Subject: [PATCH 09/10] working --- packages/superdough/worklets.mjs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index 9c77857e2..b1cd1cb23 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -775,8 +775,6 @@ class ByteBeatProcessor extends AudioWorkletProcessor { ); }; this.virtualRate = 112600; // target sample rate - // this.nativeRate = sampleRate; // actual context rate, e.g., 48000 - // this.ratio = this.nativeRate / this.virtualRate; this.t = null; this.framebuffer = new Float32Array(Math.floor(sampleRate / 60)); this.func = null; @@ -838,7 +836,7 @@ class ByteBeatProcessor extends AudioWorkletProcessor { const funcValue = this.func(t); let signal = (funcValue & 255) / 127.5 - 1; - const out = signal; + const out = signal * 0.2; for (let c = 0; c < output.length; c++) { output[c][i] = out; } From dde2b9c6f8bbb5059abb86bec75ead6cfbd8ee45 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Tue, 27 May 2025 17:41:32 +0200 Subject: [PATCH 10/10] working --- .vscode/launch.json | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 5bd1b92fe..000000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - - { - "type": "chrome", - "request": "launch", - "name": "Launch Chrome against localhost", - "url": "http://localhost:4321", - "webRoot": "${workspaceFolder}" - } - ] -} \ No newline at end of file