Dynamic changes to LFO

This commit is contained in:
2026-03-26 15:47:04 +00:00
parent ecb61dad41
commit b8d3dc48ab
2 changed files with 92 additions and 4 deletions

54
www/bootstrap.js vendored
View File

@@ -360,6 +360,20 @@ class PatchRouter {
}
}
// ── Waveform sample (mirrors Rust waveform_sample) ───────────────────────────
function waveformSample(waveIdx, phase) {
const TAU = 2 * Math.PI;
switch (waveIdx) {
case 0: return Math.sin(phase * TAU); // Sine
case 1: return 2 * phase - 1; // Saw
case 2: return phase < 0.5 ? 1 : -1; // Square
case 3: return 4 * Math.abs(phase - Math.floor(phase + 0.5)) - 1; // Triangle
case 4: return phase < 0.25 ? 1 : -1; // Pulse
default: return 0;
}
}
// ── Computer keyboard map (standard DAW layout) ───────────────────────────────
const KEY_NOTE = {
@@ -401,6 +415,15 @@ async function bootstrap() {
const ro = new ResizeObserver(() => allCanvases.forEach(fitCanvas));
allCanvases.forEach(c => ro.observe(c));
// ── Component registry: param min/max for CV display scaling ─────────
// { kind → { paramId → { min, max } } }
const paramRanges = new Map(
JSON.parse(patchbay.available_components()).map(c => [
c.kind,
new Map(c.params.map(p => [p.id, { min: p.min, max: p.max }]))
])
);
// ── Default patch bay layout ──────────────────────────────────────────
const cw = pbCanvas.width || pbCanvas.clientWidth || 800;
patchbay.add_module("vco", cw * 0.08, 80);
@@ -500,6 +523,37 @@ async function bootstrap() {
const outLabel = router.isOutputPatched() ? "patched" : "unpatched";
status.textContent = router.isOutputPatched() ? "Running · output patched" : "Running · output unpatched";
// ── Push live CV modulation offsets to the patchbay for display ──
// This makes param bars and waveform previews reflect CV-modulated
// values even when the knob hasn't moved.
{
const patch = JSON.parse(patchbay.get_patch_json());
patchbay.clear_cv_mods();
for (const cable of patch.cables) {
if (!cable.dst_jack.startsWith("cv_")) continue;
const srcMod = patch.modules.find(m => m.id === cable.src);
if (!srcMod || srcMod.kind !== "lfo") continue;
const paramId = cable.dst_jack.slice(3); // "cv_rate_hz" → "rate_hz"
const dstMod = patch.modules.find(m => m.id === cable.dst);
if (!dstMod) continue;
const range = paramRanges.get(dstMod.kind)?.get(paramId);
if (!range) continue;
// LFO normalised output: -depth..+depth
const rate = srcMod.params.rate_hz ?? 2;
const depth = srcMod.params.depth ?? 0.5;
const waveIdx = Math.round(srcMod.params.waveform ?? 0);
const phase = ((now / 1000) * rate) % 1.0;
const lfoNorm = waveformSample(waveIdx, phase) * depth;
// Scale to ±½ of the param's full range for display
const offset = lfoNorm * (range.max - range.min) * 0.5;
patchbay.set_cv_mod(dstMod.id, paramId, offset);
}
}
oscilloscope.draw();
spectrum.draw();
patchbay.draw(now);