From 0287be547a1a4d798b1f9c2d9fa2af6fe6ef7771 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Thu, 29 May 2025 02:27:55 +0200 Subject: [PATCH] bbstart --- packages/core/controls.mjs | 15 +++- packages/superdough/superdough.mjs | 1 - packages/superdough/synth.mjs | 7 +- packages/superdough/worklets.mjs | 107 +++++++++++++++-------------- 4 files changed, 75 insertions(+), 55 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 5e0c450d6..3fa4b0507 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -451,13 +451,26 @@ export const { drive } = registerControl('drive'); * @name byteBeatExpression * @synonyms bbexpr * - * @param {number | Pattern} byteBeatExpression pattern the output channels + * @param {number | Pattern} byteBeatExpression bitwise expression for creating bytebeat * @example * s("bytebeat").bbexpr('t*(t>>15^t>>66)') * */ export const { byteBeatExpression, bbexpr } = registerControl('byteBeatExpression', 'bbexpr'); +/** + * Create byte beats with custom expressions + * + * @name byteBeatStartTime + * @synonyms bbst + * + * @param {number | Pattern} byteBeatStartTime in seconds + * @example + * note("{c g a b c d}%16").s("bytebeat").bbexpr('t&t>>8').bbst("<0 300>") + * + */ +export const { byteBeatStartTime, bbst } = registerControl('byteBeatStartTime', 'bbst'); + /** * Allows you to set the output channels on the interface * diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index 79c9093af..1069d4e84 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -476,7 +476,6 @@ export const superdough = async (value, t, hapDuration, cps) => { 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 92163ab16..670ef44bb 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -165,7 +165,9 @@ export function registerSynthSounds() { '((t^t/2+t+64)%7 * 24)', ]; const { n = 0 } = value; - const { byteBeatExpression = defaultBeats[n % defaultBeats.length] } = value; + const frequency = getFrequencyFromValue(value); + const { byteBeatExpression = defaultBeats[n % defaultBeats.length], byteBeatStartTime } = value; + const ac = getAudioContext(); let { duration } = value; @@ -176,7 +178,6 @@ export function registerSynthSounds() { ); const holdend = begin + duration; const end = holdend + release + 0.01; - const frequency = getFrequencyFromValue(value); let o = getWorklet( ac, @@ -191,7 +192,7 @@ export function registerSynthSounds() { }, ); - o.port.postMessage(byteBeatExpression); + o.port.postMessage({ codeText: byteBeatExpression, startTimeSeconds: byteBeatStartTime, frequency }); let envGain = gainNode(1); envGain = o.connect(envGain); diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index f163476ee..ec1f41e7b 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -762,59 +762,72 @@ class PulseOscillatorProcessor extends AudioWorkletProcessor { registerProcessor('pulse-oscillator', PulseOscillatorProcessor); +/** BYTE BEATS */ +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)); + }, +}; + +// Create shortened Math functions +let mathParams, byteBeatHelperFuncs; +function getByteBeatFunc(codetext) { + if ((mathParams || byteBeatHelperFuncs) == null) { + mathParams = Object.getOwnPropertyNames(Math); + byteBeatHelperFuncs = mathParams.map((k) => Math[k]); + const chyxNames = Object.getOwnPropertyNames(chyx); + const chyxFuncs = chyxNames.map((k) => chyx[k]); + mathParams.push('int', 'window', ...chyxNames); + byteBeatHelperFuncs.push(Math.floor, globalThis, ...chyxFuncs); + } + return new Function(...mathParams, 't', `return 0,\n${codetext || 0};`).bind(globalThis, ...byteBeatHelperFuncs); +} + class ByteBeatProcessor extends AudioWorkletProcessor { constructor() { super(); - this.codeText = '0'; this.port.onmessage = (event) => { - 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); + let { codeText } = event.data; + const { startTimeSeconds, frequency } = event.data; + if (startTimeSeconds != null) { + const t = startTimeSeconds * sampleRate; + this.t = t; + this.t = (t / (sampleRate / 256)) * frequency; + } - //Optimization pulled from dollchan.net, it seemed important + //Optimization pulled from dollchan.net: https://github.com/Chasyxx/EnBeat_NEW, it seemed important //Optimize code like eval(unescape(escape`XXXX`.replace(/u(..)/g,"$1%"))) - this.codeText = event.data + codeText = codeText .trim() .replace( /^eval\(unescape\(escape(?:`|\('|\("|\(`)(.*?)(?:`|'\)|"\)|`\)).replace\(\/u\(\.\.\)\/g,["'`]\$1%["'`]\)\)\)$/, (match, m1) => unescape(escape(m1).replace(/u(..)/g, '$1%')), ); - this.func = new Function(...mathParams, 't', `return 0,\n${this.codeText || 0};`).bind(globalThis, ...values); + this.func = getByteBeatFunc(codeText); }; this.t = null; this.func = null; @@ -861,23 +874,17 @@ class ByteBeatProcessor extends AudioWorkletProcessor { if (this.t == null) { this.t = params.begin[0] * sampleRate; } - - let codeText = this.codeText; - // this.func = Function('t', 'return ' + codeText); const output = outputs[0]; - 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 / (sampleRate / 256)) * freq; - + let t = (this.t / (sampleRate / 256)) * freq; const funcValue = this.func(t); let signal = (funcValue & 255) / 127.5 - 1; const out = signal * 0.2; + for (let c = 0; c < output.length; c++) { - output[c][i] = out; + output[c][i] = clamp(out, -0.2, 0.2); } this.t = this.t + 1; }