merge: matrix mixer + per-voice volume fix
CI build oscope-of / build-check (push) Has been cancelled

Bring the per-instrument mixer (web) and the matrix \amp respecting
per-voice volume (matrix.scd) from origin/main 6a14b3b. Disjoint from
local commits, clean auto-merge.
This commit is contained in:
clement
2026-06-28 20:16:51 +02:00
4 changed files with 79 additions and 3 deletions
+3 -3
View File
@@ -4,8 +4,8 @@
// A playhead Routine advances bar-by-bar on the launchpad clock, applying cells.
// Colors 1-6 map to Pchain overlays (\stretch / \octave / \amp). 0 = Pdef.stop.
// Loaded after sections.scd (boot step 6i). Idempotent reload, nil-guarded.
// NOTE: the \amp variation override bypasses ~lpVol per-voice volume; web vol sliders
// have no effect while a matrix variation plays — deferred to v2.
// NOTE: the \amp variation is multiplied by the per-voice fader (~lp[\vol]) so the
// web mixer (/launch/vol) controls matrix output in real time.
(
// -- Env init (nil-guarded; idempotent reload) --
@@ -74,7 +74,7 @@ File.exists(~matDir).not.if({ ("mkdir -p " ++ ~matDir.quote).systemCmd });
},
\stretch, spec[\stretch],
\octave, spec[\octave],
\amp, spec[\amp]
\amp, Pfunc({ (spec[\amp] ? 1.0) * ((~lp[\vol].notNil).if({ ~lp[\vol][name.asSymbol] ? 0.8 }, { 0.8 })) })
),
base
)
+11
View File
@@ -142,3 +142,14 @@ button.queued { animation: queued-blink 600ms ease-in-out infinite alternate; }
.tl-bar.in-loop { background: #1a2a1a; border-color: #2a5a2a; color: #4d9; }
.tl-bar.tl-head { background: #fa0; border-color: #fc0; color: #000; }
.tl-bar.tl-head.in-loop { background: #fa0; border-color: #fc0; color: #000; }
/* --- Matrix mixer: 16-strip volume + mute --- */
.matrix-mixer { display: flex; gap: 4px; margin: 12px 0; overflow-x: auto;
-webkit-overflow-scrolling: touch; padding-bottom: 4px; }
.mix-strip { display: flex; flex-direction: column; align-items: center; gap: 4px;
min-width: 40px; flex-shrink: 0; }
.mix-fader { writing-mode: vertical-lr; direction: rtl; width: 28px; height: 110px;
accent-color: #4c8; cursor: pointer; -webkit-appearance: slider-vertical; appearance: slider-vertical; }
.mix-mute { width: auto; padding: 4px 6px; font-size: 11px; }
.mix-mute.on { background: #622; border-color: #c44; color: #f88; font-weight: 700; }
.mix-label { font-size: 10px; color: #9af; text-align: center; word-break: break-all; }
+64
View File
@@ -407,6 +407,69 @@ function initTimelinePointer() {
});
}
// --- Matrix mixer: 16-voice per-strip volume + mute ---
const mixerLevel = {};
const mixerMuted = {};
MATRIX_VOICES.forEach(v => { mixerLevel[v] = 0.8; mixerMuted[v] = false; });
function renderMixer() {
const container = document.getElementById("matrix-mixer");
if (!container) return;
container.innerHTML = "";
MATRIX_VOICES.forEach(voice => {
const strip = document.createElement("div");
strip.className = "mix-strip";
strip.dataset.voice = voice;
const fader = document.createElement("input");
fader.type = "range";
fader.min = "0";
fader.max = "1.5";
fader.step = "0.05";
fader.value = String(mixerLevel[voice]);
fader.className = "mix-fader";
fader.dataset.voice = voice;
const muteBtn = document.createElement("button");
muteBtn.className = "mix-mute";
muteBtn.textContent = "M";
const label = document.createElement("span");
label.className = "mix-label";
label.textContent = voice;
fader.addEventListener("input", () => {
const v = +fader.value;
mixerLevel[voice] = v;
// Moving the fader unmutes + sends immediately
if (mixerMuted[voice]) {
mixerMuted[voice] = false;
strip.classList.remove("muted");
muteBtn.classList.remove("on");
}
send("/launch/vol", voice, v);
});
muteBtn.addEventListener("click", () => {
mixerMuted[voice] = !mixerMuted[voice];
if (mixerMuted[voice]) {
muteBtn.classList.add("on");
strip.classList.add("muted");
send("/launch/vol", voice, 0);
} else {
muteBtn.classList.remove("on");
strip.classList.remove("muted");
send("/launch/vol", voice, mixerLevel[voice]);
}
});
strip.appendChild(fader);
strip.appendChild(muteBtn);
strip.appendChild(label);
container.appendChild(strip);
});
}
// --- Matrix audio-reactive glow (trigger envelope; math in matrix_glow.js) ---
const voiceLevel = new Array(MATRIX_VOICES.length).fill(0);
let glowRaf = null;
@@ -634,6 +697,7 @@ document.addEventListener("DOMContentLoaded", () => {
renderMatrix();
renderTimeline();
initTimelinePointer();
renderMixer();
// Name input: rename selected preset and update button labels
const melName = document.getElementById("mel-name");
+1
View File
@@ -170,6 +170,7 @@
<div id="matrix-timeline" class="matrix-timeline"></div>
<div id="matrix-grid"></div>
</div>
<div id="matrix-mixer" class="matrix-mixer"></div>
</section>
</div>
<script type="module">import * as G from "./matrix_glow.js"; window.MatrixGlow = G;</script>