Skip to content

Commit

Permalink
ULTIMATE PROGRAMMABLE SYNTH (#19)
Browse files Browse the repository at this point in the history
  • Loading branch information
HyperLan-git authored Jun 23, 2024
1 parent 47c0c66 commit 22492ed
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 7 deletions.
50 changes: 46 additions & 4 deletions scripts/FX.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ const PARAMS = {
"audiobuffersource": ["detune", "playbackRate", "buffer", "loop", "loopStart", "loopEnd"],
"streamsource": [],
"streamdestination": [],
"constant": ["offset", "type", "data"]
"constant": ["offset", "type", "data"],
"worklet": ["code", "processorOptions"]
};

const MODULATIONS = {
Expand All @@ -48,7 +49,8 @@ const MODULATIONS = {
"oscillator": ["frequency", "detune"],
"audiobuffersource": ["detune", "playbackRate"],
"streamsource": [],
"constant": ["offset"]
"constant": ["offset"],
"worklet": []
};

const CONST_NODE_TYPE = [
Expand Down Expand Up @@ -391,7 +393,20 @@ class FX extends EventTarget {
node = new ChannelMergerNode(this.node.context, {numberOfInputs: this.node.numberOfInputs});
else if(this.fxtype == "channelsplitter")
node = new ChannelSplitterNode(this.node.context, {numberOfOutputs: this.node.numberOfOutputs});
else
else if(this.fxtype == "worklet") {
node = new AudioWorkletNode(
AC,
"programmable-processor",
{
numberOfInputs: this.node.numberOfInputs,
numberOfOutputs: this.node.numberOfOutputs,
parameterData: undefined,
processorOptions: {
fct: this.node.code
}
}
);
} else
node = new FX_TYPES[this.fxtype](this.node.context);

fx = new FX(node);
Expand Down Expand Up @@ -711,4 +726,31 @@ class FXGraph {
}
return nodes;
}
};
};

function createWorklet() {
if(AC == null) return;
const text = get("workletcode").innerText;
const opt = {
fct: text
};
const newfx = new AudioWorkletNode(
AC,
"programmable-processor",
{
numberOfInputs: Number(get("workletinputs").value),
numberOfOutputs: Number(get("workletoutputs").value),
parameterData: undefined,
processorOptions: opt
}
);
newfx.port.postMessage({type: "stop"});
newfx.port.onmessage = (e) => {
if(e.data.type == 'error')
get("workleterror").innerHTML = "Error caused by your code : " + e.data.data;
};
newfx.code = text;
newfx.processorOptions = opt;
fx.addNode(newfx);
updateModUI(fx.getAllNodes());
}
10 changes: 9 additions & 1 deletion scripts/FXUI.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ function distortionFunction(name, func, n) {
let arr = new Float32Array(n);
for(let i = 0; i < n; i++) {
let x = i * 2 / n - 1;
//TODO use Function() instead of eval
arr[i] = eval(func);
if(arr[i] > 1) arr[i] = 1;
if(arr[i] < -1) arr[i] = -1;
Expand Down Expand Up @@ -777,6 +778,12 @@ function updateBufferSource(name) {
get("value_bufend_" + name).innerHTML = end;
}

function drawWorklet(name) {
return {
html: ""
};
}

const FX_DRAW = {
"gain": drawGain,
"delay": drawDelay,
Expand All @@ -788,5 +795,6 @@ const FX_DRAW = {
"convolver": drawConvolver,
"oscillator": drawOscillator,
"constant": drawConstant,
"audiobuffersource": drawBufferSource
"audiobuffersource": drawBufferSource,
"worklet": drawWorklet
};
14 changes: 12 additions & 2 deletions scripts/synth.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// TODO understand wtf is an audio worklet
// FIXME turn everything into modules!!!

let AC = new AudioContext();

let masterFader = new GainNode(AC),
Expand Down Expand Up @@ -107,10 +107,12 @@ function initAudio() {

adsr.connect(fx.getOutput());
fx.connectGraphNode(adsr, fx.getOutput());
fx.getOutput().node.connect(masterFader);
//fx.getOutput().node.connect(masterFader);
updateModUI(fx.getAllNodes());

drawSynth();

AC.audioWorklet.addModule("scripts/worklet.js");
}

function initMIDI() {
Expand Down Expand Up @@ -438,6 +440,8 @@ function stopNote(note = 69) {
}
setTimeout(() => {
for(let k in nodes) {
if(nodes[k].fxtype == "worklet")
nodes[k].node.port.postMessage({type: "stop"});
nodes[k].disconnect();
}
}, maxEnv);
Expand Down Expand Up @@ -540,6 +544,12 @@ let fx = null;
let drawflow = null;

window.onload = () => {
hljs.highlightAll();
/*get("workletcode").addEventListener("input", () => {
delete get("workletcodedisplay").dataset.highlighted;
get("workletcodedisplay").innerText = get("workletcode").value;
hljs.highlightAll();
});*/
AC = null;

let id = document.getElementById("drawflow");
Expand Down
29 changes: 29 additions & 0 deletions scripts/worklet.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
class ProgrammableProcessor extends AudioWorkletProcessor {
fct;
processorOptions;
shouldStop;

constructor(options) {
super();

this.processorOptions = options;
this.fct = new Function("inputs", "outputs", "parameters", "\"use strict\";" + options.processorOptions.fct);
this.shouldStop = false;
this.port.onmessage = (e) => {
if(e.data.type == 'stop')
this.shouldStop = true;
};
}

// I love how the docs won't even attempt to warn against the huge memory leak the example causes
process(inputs, outputs, parameters) {
try {
this.fct(inputs, outputs, parameters);
} catch(e) {
this.port.postMessage({type: "error", data: e});
this.shouldStop = true;
}
return !this.shouldStop;
}
}
registerProcessor("programmable-processor", ProgrammableProcessor);
4 changes: 4 additions & 0 deletions style.css
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ ul {
font-size: 24px;
}

code {
display: flex;
}

.sidebar {
position: fixed;
height: 100%;
Expand Down
25 changes: 25 additions & 0 deletions synth.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
<!-- XXX for local testing <link rel="stylesheet" href="./node_modules/drawflow/dist/drawflow.min.css">
<script src="./node_modules/drawflow/dist/drawflow.min.js"></script>-->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/jerosoler/Drawflow/dist/drawflow.min.css">
Expand Down Expand Up @@ -78,6 +80,29 @@
<th>Delete</th>
<tbody id="modList"></tbody>
</table>
<div>
<p>
Programmable node:<br>
Number of inputs: <input type="number" min="0" max="10" value="0" id="workletinputs"></input><br>
Number of outputs: <input type="number" min="1" max="10" value="1" id="workletoutputs"></input><br>
Params (TODO)<br>
<button onclick="createWorklet();">ADD</button><br>
<!-- TODO fix syntax coloring -->
Code: <pre><code class="language-js" id="workletcode" style="width:98.5%; height: 20em; resize: none;" contenteditable="true">// Your arguments here are "inputs", "outputs" and "parameters"
const output = outputs[0];
// For every channel
for(let ch = 0; ch < output.length; ch++) {
let channel = output[ch];
// Fill the audio buffer
for (let sample = 0; sample < channel.length; sample++) {
// With random data (white noise)
channel[sample] = Math.random() * 2 - 1;
}
}
</code></pre>
<p style="color:black; background-color: red; padding:0%" id="workleterror"></p>
</p>
</div>
<div>
<p>
<button id="midi" onclick="initMIDI(); this.disabled = true;">ENABLE MIDI INPUT</button><br>
Expand Down

0 comments on commit 22492ed

Please sign in to comment.