4CM split routing: stereo pipeline + breakpoint + Web UI
Pipeline: - process() dispatches to _process_mono() or _process_4cm() based on routing_mode - _process_4cm() splits chain at routing_breakpoint: pre blocks on ch0, post on ch1 - _process_single_block() extracted for reuse in both mono and 4cm paths - routing_mode/routing_breakpoint load from preset via load_preset() - set_routing() for runtime configuration - Properties: routing_mode (mono|4cm), routing_breakpoint with validation Web server: - GET /api/routing — current routing mode and breakpoint - POST /api/routing — set routing mode/breakpoint, persist to current preset, WS broadcast - _gather_state() includes routing_mode and routing_breakpoint Web UI: - Settings page: 4CM toggle + breakpoint slider with routing description - Dashboard: routing badge (4CM/Mono) with breakpoint info - app.js: WebSocket handler, updateRoutingUI(), toggle4cm(), set4cmBreakpoint() - style.css: .badge, .badge-4cm, .badge-mono, .routing-status, .routing-desc Tests: mock pipeline fixture updated with routing_mode/routing_breakpoint
This commit is contained in:
@@ -80,6 +80,74 @@ pedalWS.on('tuner_changed', (msg) => {
|
||||
}
|
||||
});
|
||||
|
||||
pedalWS.on('routing_changed', (msg) => {
|
||||
updateRoutingUI(msg.routing_mode, msg.routing_breakpoint);
|
||||
});
|
||||
|
||||
/* ── 4CM Routing UI ────────────────────────────────────────────── */
|
||||
|
||||
function updateRoutingUI(mode, breakpoint) {
|
||||
// Dashboard badge
|
||||
const indicator = document.getElementById('routing-mode-indicator');
|
||||
const info = document.getElementById('routing-info');
|
||||
if (indicator) {
|
||||
indicator.setAttribute('data-mode', mode);
|
||||
if (mode === '4cm') {
|
||||
indicator.innerHTML = '<span class="badge badge-4cm">4CM</span>';
|
||||
} else {
|
||||
indicator.innerHTML = '<span class="badge badge-mono">Mono</span>';
|
||||
}
|
||||
}
|
||||
if (info) {
|
||||
info.textContent = mode === '4cm' ? `Breakpoint: ${breakpoint}` : 'Full chain';
|
||||
}
|
||||
|
||||
// Settings page controls
|
||||
const toggleBtn = document.getElementById('4cm-toggle-btn');
|
||||
if (toggleBtn) {
|
||||
toggleBtn.textContent = mode === '4cm' ? 'ON' : 'OFF';
|
||||
toggleBtn.classList.toggle('active', mode === '4cm');
|
||||
}
|
||||
const bpRow = document.getElementById('breakpoint-row');
|
||||
if (bpRow) {
|
||||
bpRow.style.opacity = mode === '4cm' ? '1' : '0.4';
|
||||
bpRow.style.pointerEvents = mode === '4cm' ? 'auto' : 'none';
|
||||
}
|
||||
const bpSlider = document.getElementById('4cm-breakpoint');
|
||||
const bpText = document.getElementById('4cm-breakpoint-text');
|
||||
if (bpSlider) bpSlider.value = breakpoint;
|
||||
if (bpText) bpText.textContent = breakpoint;
|
||||
|
||||
const desc = document.getElementById('4cm-routing-desc');
|
||||
if (desc) {
|
||||
if (mode === '4cm') {
|
||||
desc.innerHTML = `Input 1 (Guitar) → Pre blocks [0..${breakpoint}) → Send | Input 2 (Return) → Post blocks [${breakpoint}..] → Output`;
|
||||
} else {
|
||||
desc.textContent = 'Mono: Guitar → Full chain → Output';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function toggle4cm() {
|
||||
const btn = document.getElementById('4cm-toggle-btn');
|
||||
const currentMode = btn && btn.classList.contains('active') ? '4cm' : 'mono';
|
||||
const newMode = currentMode === '4cm' ? 'mono' : '4cm';
|
||||
try {
|
||||
await apiPost('/routing', { routing_mode: newMode });
|
||||
} catch (e) {
|
||||
console.warn('Failed to toggle 4CM:', e);
|
||||
}
|
||||
}
|
||||
|
||||
async function set4cmBreakpoint(val) {
|
||||
const bp = parseInt(val);
|
||||
try {
|
||||
await apiPost('/routing', { routing_breakpoint: bp });
|
||||
} catch (e) {
|
||||
console.warn('Failed to set breakpoint:', e);
|
||||
}
|
||||
}
|
||||
|
||||
function updateDashboardState(state) {
|
||||
if (!state || !state.connected) return;
|
||||
|
||||
@@ -128,6 +196,11 @@ function updateDashboardState(state) {
|
||||
// IR
|
||||
const irNameEl = document.getElementById('ir-name');
|
||||
if (irNameEl) irNameEl.textContent = state.ir_name || 'None loaded';
|
||||
|
||||
// 4CM Routing
|
||||
if (state.routing_mode) {
|
||||
updateRoutingUI(state.routing_mode, state.routing_breakpoint);
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Dashboard action handlers ─────────────────────────────────── */
|
||||
|
||||
@@ -651,4 +651,47 @@ body {
|
||||
.mb-8 { margin-bottom: 8px; }
|
||||
.flex { display: flex; }
|
||||
.flex-1 { flex: 1; }
|
||||
.gap-8 { gap: 8px; }
|
||||
.gap-8 { gap: 8px; }
|
||||
|
||||
/* ── Routing / 4CM ────────────────────────────────────────────────── */
|
||||
|
||||
.routing-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.routing-indicator {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 3px 10px;
|
||||
border-radius: 10px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.5px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.badge-4cm {
|
||||
background: var(--warning);
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.badge-mono {
|
||||
background: var(--text-muted);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.routing-info {
|
||||
font-size: 0.82rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.routing-desc {
|
||||
font-size: 0.78rem;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.4;
|
||||
}
|
||||
Reference in New Issue
Block a user