Have all linear input be controllable by an LFO
This commit is contained in:
53
www/bootstrap.js
vendored
53
www/bootstrap.js
vendored
@@ -231,9 +231,9 @@ class PatchRouter {
|
||||
return dst?.kind === "out" && c.dst_jack === "audio_in";
|
||||
});
|
||||
|
||||
// Mark VCAs that have an ADSR patched into their cv_in
|
||||
// Mark VCAs that have an ADSR patched into any of their CV param inputs
|
||||
for (const c of patch.cables) {
|
||||
if (c.dst_jack !== "cv_in") continue;
|
||||
if (!c.dst_jack.startsWith("cv_")) continue;
|
||||
const srcMod = patch.modules.find(m => m.id === c.src);
|
||||
const dstInfo = this.nodes.get(c.dst);
|
||||
if (srcMod?.kind === "adsr" && dstInfo?.kind === "vca") {
|
||||
@@ -312,28 +312,49 @@ class PatchRouter {
|
||||
const dst = this.nodes.get(cable.dst);
|
||||
if (!src?.node || !dst?.node) return; // ADSR has node:null — skip
|
||||
|
||||
// ── Audio signal cables ────────────────────────────────────────────
|
||||
// ── Audio signal ───────────────────────────────────────────────────
|
||||
if (cable.dst_jack === "audio_in") {
|
||||
try { src.node.connect(dst.node); } catch(_) {}
|
||||
return;
|
||||
}
|
||||
|
||||
// ── CV modulation cables ───────────────────────────────────────────
|
||||
if (cable.dst_jack === "cv_in") {
|
||||
if (src.kind === "lfo") {
|
||||
if (dst.kind === "svf") {
|
||||
// LFO modulates filter cutoff frequency
|
||||
try { src.node.connect(dst.node.frequency); } catch(_) {}
|
||||
} else if (dst.kind === "vco") {
|
||||
// LFO modulates VCO pitch (vibrato)
|
||||
try { src.node.connect(dst.node.frequency); } catch(_) {}
|
||||
}
|
||||
// LFO → VCA: not wired (VCA cv_in is for ADSR envelope only)
|
||||
}
|
||||
// ADSR → VCA: handled by parameter automation in noteOn/Off, not a node connection
|
||||
// ── Per-parameter CV modulation: cv_{param_id} ────────────────────
|
||||
if (cable.dst_jack.startsWith("cv_")) {
|
||||
// ADSR → VCA: handled by parameter automation in noteOn/Off (not a node connection)
|
||||
if (src.kind === "adsr") return;
|
||||
const ap = this._getAudioParam(dst, cable.dst_jack.slice(3));
|
||||
if (ap) try { src.node.connect(ap); } catch(_) {}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the Web Audio AudioParam that corresponds to a param id on a node,
|
||||
// or null if no mapping exists.
|
||||
_getAudioParam(nodeInfo, paramId) {
|
||||
switch (nodeInfo.kind) {
|
||||
case "vco":
|
||||
if (paramId === "freq_hz") return nodeInfo.node.frequency;
|
||||
break;
|
||||
case "svf":
|
||||
if (paramId === "cutoff_hz") return nodeInfo.node.frequency;
|
||||
if (paramId === "resonance") return nodeInfo.node.Q;
|
||||
break;
|
||||
case "vca":
|
||||
if (paramId === "gain") return nodeInfo.node.gain;
|
||||
break;
|
||||
case "lfo":
|
||||
if (paramId === "rate_hz") return nodeInfo.oscNode.frequency;
|
||||
if (paramId === "depth") return nodeInfo.node.gain;
|
||||
break;
|
||||
case "out":
|
||||
if (paramId === "level") return nodeInfo.node.gain;
|
||||
break;
|
||||
case "adsr":
|
||||
// ADSR has no Web Audio node; envelope is driven via parameter automation
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
_midiHz(note) {
|
||||
return 440 * Math.pow(2, (note - 69) / 12);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user