WIP oscillations

This commit is contained in:
Jade (Rose) Rowland
2025-05-27 14:37:55 +02:00
parent 5e4eb7fc53
commit 608dfd515e
5 changed files with 763 additions and 15 deletions
+16
View File
@@ -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}"
}
]
}
+295
View File
@@ -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)
<html><!-- license: AGPL-3.0 --><head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🌱 audio worklets</title>
<style>
body {
background-color: #222;
max-width: 500px;
margin: auto;
font-family: serif;
font-size: 1.2em;
color: #edd;
text-align: left;
padding: 20px 8px;
}
@font-face {
font-family: "FontWithASyntaxHighlighter";
src: url("/fonts/FontWithASyntaxHighlighter-Regular.woff2")
format("woff2");
}
a {
color: cyan;
font-size: 1em;
cursor: pointer;
}
textarea,
pre {
overflow: auto;
font-family: "FontWithASyntaxHighlighter", monospace;
padding: 8px;
font-size: 12px;
border: 0;
outline: none;
background-color: #44444490;
border: 0;
color: white;
width: 100%;
margin-top: 8px;
box-sizing: border-box;
}
ul,
ol {
padding-left: 20px;
}
canvas {
max-width: 100%;
background-color: #44444490;
}
</style>
</head>
<body>
<h2>🌱 audio worklets</h2>
<p>
In the <a href="/audio-dsp/buffers.html">last post</a>, 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:
</p>
<div>
<small>💡 hit ctrl+enter to update the code &amp; ctrl+. to stop</small>
</div>
<textarea id="code" spellcheck="false" rows="8"></textarea>
<a id="play">play</a> | <a id="stop">stop</a>
<canvas id="canvas" width="800" height="200"></canvas>
<p>
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).
</p>
<p>Here are some examples to try:</p>
<ul>
<li>
<a href="#TWF0aC5zaW4odC80NDEwMCoyKk1hdGguUEkqMTIxKQ==">hello sine</a>
</li>
<li>
<a href="#TWF0aC5zaW4odC80NDEwMCoyKk1hdGguUEkqMik=">slow sine (inaudible)</a>
</li>
<li>
<a href="#KApNYXRoLnNpbih0LzQ0MTAwKjIqTWF0aC5QSSoyMDApCitNYXRoLnNpbih0LzQ0MTAwKjIqTWF0aC5QSSozMDApCitNYXRoLnNpbih0LzQ0MTAwKjIqTWF0aC5QSSo0MDApCikvNA==">
just intonation triad
</a>
</li>
<li>
<a href="#KHQvNDQxMDApKjYxJTEtLjU=">hello saw</a>
</li>
<li>
<a href="#KCh0LzQ0MTAwKjYxKSUxPi41Py0xOjEpLzI=">hello pulse</a>
</li>
<li>
<a href="#KHQ9dC80NDEwMCwKTWF0aC5zaW4odCoyKk1hdGguUEkqMTIwKS8zCitNYXRoLnNpbih0KjIqTWF0aC5QSSozMDEpLzMKKQ==">wandering sine</a>
</li>
<li>
<a href="#KCgoIC8vIGJ5IHN0aW1tZXIgKDIwMTEtMTAtMDMpCnQqKDR8dD4+MTMmMyk+Pih+dD4+MTEmMSkmMTI4fHQqKHQ+PjExJnQ+PjEzKSoofnQ+PjkmMykmMTI3CikgJiAyNTUpIC8gMTI3LjUgLSAxKS80">bytebeat 1</a>
</li>
<li>
<a href="#KCgoIC8vIGxocGhyIC0gRnJhY3RhbGl6ZWQgUGFzdAoodD4+MTBedD4+MTEpJTUqKCh0Pj4xNCYzXnQ+PjE1JjEpKzEpKnQlOTkrKCgzKyh0Pj4xNCYzKS0odD4+MTYmMSkpLzMqdCU5OSY2NCkKKSAmIDI1NSkgLyAxMjcuNSAtIDEpLzQ=">bytebeat 2</a>
</li>
<li>
<a href="#ICh0PXQvNDQxMDAsCk1hdGguc2luKHQqMSpNYXRoLlBJKjM1MCkvNAorTWF0aC5zaW4odCoxKk1hdGguUEkqNTIwKS84IAorTWF0aC5zaW4odCoyKk1hdGguUEkqKCgodCo5KSUxMj4uNT8tLjg6MTcwMCkpKS8xMAorTWF0aC5zaW4odCoyKk1hdGguUEkqKCgodCo0KSUyPi4yPy0uODoyMjAwKSkpLzEwCitNYXRoLnNpbih0KjIqTWF0aC5QSSooKCh0KjMpJTE+LjI/LS44OjM2MDApKSkvMTAKKygodCoyKSUxPi4yPy0uODouOCkvNAorKCh0KSUxPi4zPy0uODouOCkvMgop">gladyouask - chirps</a>
</li>
<li>
<a href="#KHQ9dC80NDEwMCwKLy8gc291cmNlOiBodHRwczovL3d3dy5yZWRkaXQuY29tL3IvZWRtcHJvZHVjdGlvbi9jb21tZW50cy8xOHVtNXgzL3RpcHNfYW5kX3RyaWNrc19mb3JfYXV0aGVudGljX3RyODA4X3NvdW5kc19raWNrLwpbMjA0LDI5OCwzNjYsNTE1LDU0MCw4MDBdLm1hcChoej0+eyAvLyBoaWhhdCB0dW5pbmcKICBsZXQgYT0wOwogIGNvbnN0IEkwPTE4OyAvLyBmaXJzdCBoYXJtb255IChoaXBhc3MgZmlsdGVyKQogIGNvbnN0IE49MzA7IC8vIGhhcm1vbnkgY291bnQKICAvLyBzcXVhcmUgd2F2ZSBmb3VyaWVyIHNlcmllczoKICBmb3IgKGxldCBpID0gSTA7IGkgPCBJMCtOOyBpKz0yKSB7CiAgICBpZiAoaHoqaSA+PSAyMjAwMCkgYnJlYWs7IC8vIHN0b3AgYXQgbnlxdWlzdCBsaW1pdAogICAgYSArPSBNYXRoLnNpbih0KjIqTWF0aC5QSSpoeippKS9OOwogIH07CiAgcmV0dXJuIGE7Cn0pLnJlZHVjZSgoYSxiKT0+YStiKQoqCjAuMSpbMTAsMSwyLDUsOSwxLDQsNywxMCwwLDIsNSwxLDcsMSw3XVsoKHQqMTApfDApJTE2XSAvLyBwYXR0ZXJuCioKKCgxLSgodCUwLjEpKjEwKSkqKjEuNykpIC8qIGVudmVsb3BlICov">aks - TR-808-like hihat</a>
</li>
</ul>
<details>
<summary id="loc">show page source</summary>
<pre id="pre"></pre>
</details>
<p>
<a href="/">back to garten.salat</a>
</p>
<script>
// init canvas
const canvas = document.querySelector("#canvas");
const drawContext = canvas.getContext("2d");
canvas.width = 800;
canvas.height = 200;
drawSilence(drawContext);
// init audio context
const ctx = new AudioContext();
document.addEventListener("click", function firstClick() {
ctx.resume(); // autoply policy
document.removeEventListener("click", firstClick);
});
// init code input
const input = document.querySelector("#code");
document.querySelector("#play").onclick = () => play();
document.querySelector("#stop").onclick = () => stop();
let worklet;
async function play() {
await ctx.resume();
stop();
worklet = await runWorklet(ctx, input.value);
worklet.port.onmessage = (e) => drawBuffer(e.data);
}
let stop = () => {
worklet?.disconnect();
worklet?.port.postMessage("stop");
};
// read base64 code from url
let urlCode = window.location.hash.slice(1);
if (urlCode) {
urlCode = atob(urlCode);
console.log("loaded code from url!");
}
input.value = urlCode || `Math.sin(t/44100*2*Math.PI*220)/2`;
// live coding / sharing logic
window.addEventListener("hashchange", function () {
const urlCode = atob(window.location.hash.slice(1));
input.value = urlCode;
play();
});
input.addEventListener("keydown", async (e) => {
if ((e.ctrlKey || e.altKey) && e.key === "Enter") {
const hash = btoa(input.value);
let urlCode = window.location.hash.slice(1);
if (hash !== urlCode) {
window.location.hash = "#" + hash;
} else {
play();
}
}
if ((e.ctrlKey || e.altKey) && e.code === "Period") {
e.preventDefault();
stop();
}
});
async function runWorklet(ac, expression) {
const name = `worklet-${Date.now()}`;
const workletCode = `
let dsp = (t) => ${expression};
class MyProcessor extends AudioWorkletProcessor {
constructor() {
super();
this.t = 0;
this.active = true;
this.framebuffer = new Float32Array(Math.floor(sampleRate/60));
this.port.onmessage = (e) => e.data === "stop" && (this.active = false)
}
process(inputs, outputs, parameters) {
const output = outputs[0];
for (let i = 0; i < output[0].length; i++) {
const out = dsp(this.t);
for (let c = 0; c < output.length; c++)
output[c][i] = out;
this.t++;
this.framebuffer[this.t%this.framebuffer.length] = out;
if(this.t % this.framebuffer.length === 0 && this.t > 0) {
this.port.postMessage(this.framebuffer)
}
}
return this.active;
}
}
registerProcessor('${name}', MyProcessor);`;
await ac.resume();
const dataURL = `data:text/javascript;base64,${btoa(workletCode)}`;
await ac.audioWorklet.addModule(dataURL);
const node = new AudioWorkletNode(ac, name);
node.connect(ac.destination);
return node;
}
function drawBuffer(samples) {
drawContext.clearRect(0, 0, canvas.width, canvas.height);
plot(
(x) => samples[Math.floor(x)], // this is bad..
drawContext,
[1, samples.length],
[-1, 1],
"white",
2
);
}
function drawSilence(ctx, color = "white", lineWidth = 2) {
ctx.strokeStyle = color;
ctx.lineWidth = lineWidth;
ctx.beginPath();
ctx.moveTo(0, canvas.height / 2);
ctx.lineTo(canvas.width, canvas.height / 2);
ctx.stroke();
}
// plot function, see /plot.html
function plot(
fn,
ctx,
xrange = [-1, 1],
yrange = [-1, 1],
color = "black",
lineWidth = 4
) {
// these 3 functions are very good to know..
const lerp = (v, min, max) => v * (max - min) + min;
const invLerp = (v, min, max) => (v - min) / (max - min);
const remap = (v, vmin, vmax, omin, omax) =>
lerp(invLerp(v, vmin, vmax), omin, omax);
// prepare draw context
ctx.lineWidth = lineWidth;
ctx.strokeStyle = color;
// function ranges
const [x0, x1] = xrange;
const [y0, y1] = yrange;
// draw ranges
const [px0, px1] = [0, ctx.canvas.width];
const [py0, py1] = [ctx.canvas.height - ctx.lineWidth, ctx.lineWidth];
// actual draw logic
ctx.beginPath();
for (let px = 0; px < ctx.canvas.width; px++) {
const x = remap(px, px0, px1, x0, x1);
const y = fn(x);
const py = remap(y, y0, y1, py0, py1);
px === 0 ? ctx.moveTo(px, py) : ctx.lineTo(px, py);
}
ctx.stroke();
}
const html = document.querySelector("html").outerHTML;
const loc = html.split("\n").length;
document.querySelector("#pre").textContent = html;
document.querySelector("#loc").textContent = `show source (${loc} loc)`;
</script></body></html>
back to garten.salat
+2 -2
View File
@@ -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 = () => {
+3
View File
@@ -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,
},
+447 -13
View File
@@ -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);