feat(playground): EOC NISPS Shared mode — combined MLP for synth + effects (meml-dqt)
- Add totalOutputCount() helper: engine params + EOC params when nispsMode === 'shared' - setActiveEngine() calls resizeMLP(totalOutputCount()) and rebuilds heatmap with combined paramMeta - eoc:change listener handles nispsMode-changed and module-* events: resizes MLP, rebuilds heatmap, toggles .shared-mode CSS class - routeOutputs() slices outputs: indices < engineParamCount go to engine, remainder to eocChain.setParam() in Shared mode - CSS: .heatmap-strip.shared-mode adds green bottom border and '+ FX' label to signal combined output space
This commit is contained in:
parent
0a5f2544ca
commit
4f44cd016d
2 changed files with 82 additions and 7 deletions
|
|
@ -1731,6 +1731,24 @@ html, body {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---- Heatmap shared-mode indicator (EOC NISPS Shared mode active) ---- */
|
||||||
|
/* .heatmap-strip gets this class; ::after appends a small '+ FX' label */
|
||||||
|
.heatmap-strip.shared-mode {
|
||||||
|
border-bottom: 1px solid rgba(120, 220, 180, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.heatmap-strip.shared-mode::after {
|
||||||
|
content: '+ FX';
|
||||||
|
font-size: 9px;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
color: rgba(120, 220, 180, 0.8);
|
||||||
|
padding: 0 4px;
|
||||||
|
white-space: nowrap;
|
||||||
|
align-self: center;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
/* ---- Help modal ---- */
|
/* ---- Help modal ---- */
|
||||||
.help-overlay {
|
.help-overlay {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|
|
||||||
|
|
@ -748,6 +748,19 @@ function outputCountForMode(mode) {
|
||||||
return N_SYNTH_OUTPUTS;
|
return N_SYNTH_OUTPUTS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Total MLP output count: engine params + EOC params when in Shared mode.
|
||||||
|
* In all other NISPS modes (bypass/linked/independent) or when no EOC chain
|
||||||
|
* exists, only the engine's own param count is included.
|
||||||
|
*
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
function totalOutputCount() {
|
||||||
|
const engineParams = activeEngine?.paramCount ?? N_SYNTH_OUTPUTS;
|
||||||
|
const eocParams = (eocChain?.nispsMode === 'shared') ? (eocChain?.paramCount ?? 0) : 0;
|
||||||
|
return engineParams + eocParams;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recreate IML instances with a new output count.
|
* Recreate IML instances with a new output count.
|
||||||
* Joystick IML uses warm-start weight transfer to preserve learned mappings.
|
* Joystick IML uses warm-start weight transfer to preserve learned mappings.
|
||||||
|
|
@ -799,13 +812,20 @@ async function setActiveEngine(engine) {
|
||||||
if (engineDockBtn) engineDockBtn.title = `Engine: ${engine.displayName}`;
|
if (engineDockBtn) engineDockBtn.title = `Engine: ${engine.displayName}`;
|
||||||
EngineSwitcher.setActive(engine.id);
|
EngineSwitcher.setActive(engine.id);
|
||||||
EngineSwitcher.setLoading(engine.id, false);
|
EngineSwitcher.setLoading(engine.id, false);
|
||||||
await resizeMLP(engine.paramCount);
|
await resizeMLP(totalOutputCount());
|
||||||
|
|
||||||
// Reload MIDI CC map for the new engine (scoped storage key)
|
// Reload MIDI CC map for the new engine (scoped storage key)
|
||||||
reloadMidiCCMap();
|
reloadMidiCCMap();
|
||||||
|
|
||||||
// Rebuild heatmap cells from the new engine's paramMeta
|
// Rebuild heatmap cells — include EOC params when in shared mode
|
||||||
|
if (eocChain?.nispsMode === 'shared') {
|
||||||
|
const combinedMeta = [...(engine.paramMeta ?? []), ...eocChain.paramMeta];
|
||||||
|
rebuildHeatmap(combinedMeta);
|
||||||
|
} else {
|
||||||
rebuildHeatmap(engine.paramMeta);
|
rebuildHeatmap(engine.paramMeta);
|
||||||
|
}
|
||||||
|
document.getElementById('heatmap-cells')?.parentElement
|
||||||
|
?.classList.toggle('shared-mode', eocChain?.nispsMode === 'shared');
|
||||||
|
|
||||||
// Rewire EOC chain to the new engine's output node
|
// Rewire EOC chain to the new engine's output node
|
||||||
if (eocChain && _eocInited) {
|
if (eocChain && _eocInited) {
|
||||||
|
|
@ -1050,9 +1070,30 @@ async function init() {
|
||||||
EOCChainUI.init(eocChain, eocDrawerBody);
|
EOCChainUI.init(eocChain, eocDrawerBody);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log EOC structural changes; future NISPS modes will act on this event
|
// Handle EOC structural changes — resize MLP and rebuild heatmap when in Shared mode
|
||||||
window.addEventListener('eoc:change', () => {
|
window.addEventListener('eoc:change', async (e) => {
|
||||||
console.log('[EOC] chain changed, paramCount:', eocChain.paramCount);
|
const reason = e.detail?.reason;
|
||||||
|
console.log('[EOC] chain changed, reason:', reason, 'paramCount:', eocChain.paramCount, 'nispsMode:', eocChain.nispsMode);
|
||||||
|
|
||||||
|
// Only act on nispsMode switches or module add/remove/move — ignore other events
|
||||||
|
if (reason === 'nispsMode-changed' || reason?.startsWith('module')) {
|
||||||
|
const newTotal = totalOutputCount();
|
||||||
|
if (newTotal !== N_OUTPUTS) {
|
||||||
|
await resizeMLP(newTotal);
|
||||||
|
}
|
||||||
|
// Rebuild heatmap to include EOC params when in shared mode, or revert when leaving
|
||||||
|
if (eocChain.nispsMode === 'shared') {
|
||||||
|
const combinedMeta = [
|
||||||
|
...(activeEngine?.paramMeta ?? []),
|
||||||
|
...eocChain.paramMeta,
|
||||||
|
];
|
||||||
|
rebuildHeatmap(combinedMeta);
|
||||||
|
} else {
|
||||||
|
rebuildHeatmap(activeEngine?.paramMeta ?? []);
|
||||||
|
}
|
||||||
|
document.getElementById('heatmap-cells')?.parentElement
|
||||||
|
?.classList.toggle('shared-mode', eocChain.nispsMode === 'shared');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Debug probe — exposed on window when ?debug=1 is in the URL.
|
// Debug probe — exposed on window when ?debug=1 is in the URL.
|
||||||
|
|
@ -1800,15 +1841,31 @@ function routeOutputs(outputs) {
|
||||||
const now = performance.now();
|
const now = performance.now();
|
||||||
if (now - _lastParamSendTime >= PARAM_SEND_INTERVAL) {
|
if (now - _lastParamSendTime >= PARAM_SEND_INTERVAL) {
|
||||||
_lastParamSendTime = now;
|
_lastParamSendTime = now;
|
||||||
|
const engineParamCount = activeEngine.paramCount;
|
||||||
for (let i = 0; i < overridden.length && i < N_OUTPUTS; i++) {
|
for (let i = 0; i < overridden.length && i < N_OUTPUTS; i++) {
|
||||||
const v = overridden[i];
|
const v = overridden[i];
|
||||||
if (Math.abs(v - _lastSentParams[i]) > PARAM_DEAD_ZONE) {
|
if (Math.abs(v - _lastSentParams[i]) > PARAM_DEAD_ZONE) {
|
||||||
|
// Only send engine params (indices before engineParamCount) to the engine
|
||||||
|
if (i < engineParamCount) {
|
||||||
activeEngine.setParam(i, v);
|
activeEngine.setParam(i, v);
|
||||||
|
}
|
||||||
_lastSentParams[i] = v;
|
_lastSentParams[i] = v;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// In Shared mode, route outputs beyond engine params to the EOC chain
|
||||||
|
if (eocChain?.nispsMode === 'shared') {
|
||||||
|
const engineParamCount = activeEngine?.paramCount ?? 0;
|
||||||
|
const eocParamCount = eocChain.paramCount;
|
||||||
|
for (let i = 0; i < eocParamCount; i++) {
|
||||||
|
const outputIndex = engineParamCount + i;
|
||||||
|
if (outputIndex < outputs.length) {
|
||||||
|
eocChain.setParam(i, outputs[outputIndex]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if (outputMode === 'midi-cc') {
|
} else if (outputMode === 'midi-cc') {
|
||||||
// MIDI CC output — route through overrides then send CC messages
|
// MIDI CC output — route through overrides then send CC messages
|
||||||
if (midiOutput && midiOutput.enabled && midiOutput.activeOutput) {
|
if (midiOutput && midiOutput.enabled && midiOutput.activeOutput) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue