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