WIP oscillations
This commit is contained in:
parent
5e4eb7fc53
commit
608dfd515e
5 changed files with 763 additions and 15 deletions
|
|
@ -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);
|
||||
Loading…
Add table
Add a link
Reference in a new issue