21 KiB
TECHNICAL SPECIFICATION & SYSTEM ARCHITECTURE FOR MAIN SESSION AND SECTION-TAB MIXER
This document details the implementation of a docked Mixer Console (Mixer Control Panel - MCP) positioned at the bottom of the screen (similar to REAPER), alongside the hierarchical audio signal routing architecture when SECTION-TABs are nested within MAIN SESSION tracks.
1. HIERARCHICAL AUDIO SIGNAL ROUTING ARCHITECTURE
To allow a SECTION-TAB to maintain an independent mix while assigning final master control of its output to a single track in the MAIN SESSION, the system employs a Sub-Mix Bus Piping model via the Web Audio API.
Audio Signal Graph Tree
[ SECTION-TAB (Sub-Session) ]
├── Track S1 (Audio/MIDI) ──► Gain ──► Pan ──┐
├── Track S2 (Audio/MIDI) ──► Gain ──► Pan ──┼──► [ SECTION SUB-MIX BUS ]
└── Track S3 (Audio/MIDI) ──► Gain ──► Pan ──┘ │ (Section Master Out)
│
▼ (Piped to host track)
[ MAIN SESSION (Root Session) ] │
├── Track 1 (Normal Audio) ──► Gain ──► Pan ─────────────┼──┐
├── Track 2 (SECTION ITEM Track) ◄───────────────────────┘ │
│ └─ Gain Node ──► FX Chain ──► Pan Node ───────────────┼──► [ MAIN MASTER BUS ]
└── Track 3 (Normal MIDI) ──► Gain ──► Pan ─────────────┘ │
▼
[ AudioContext Destination ]
(Speakers / Soundcard)
Audio Routing Principles:
- Inside SECTION-TAB: Internal tracks (
S1, S2, S3) are balanced via volume, pan, and Mute/Solo controls on the SECTION-TAB's dedicated mixer. Output signals from these tracks are summed at theSection Sub-Mix Bus Node. - Routing to MAIN SESSION: The output of the
Section Sub-Mix Bus Nodedoes not connect directly toaudioCtx.destination. Instead, it routes into the Input Node of Track 2 on the MAIN SESSION (the track hosting theSECTION_ITEM). - MAIN SESSION Management:
- The Volume Fader on Track 2 of the MAIN SESSION controls the overall volume of the entire SECTION-TAB.
- Mute, Solo, and FX Chain controls on Track 2 of the MAIN SESSION directly process all audio originating from that SECTION-TAB before it reaches the Main Master Bus.
2. DOCKED MIXER CONSOLE (MCP) UI STRUCTURE
The Mixer Console is designed as a docked panel underneath the Timeline, split into two main sections: Master Channel Strip (Left) and Track Channel Strips (Right).
+------------------------------------------------------------------------------------------------+
| DOCKED MIXER CONSOLE |
+-------------------+----------------------------------------------------------------------------+
| MASTER STRIP | TRACK CHANNEL STRIPS (Track 1, Track 2 [SECTION], Track 3...) |
| | |
| (Knob) Pan | (Knob) Pan (Knob) Pan (Knob) Pan (Knob) Pan |
| [MONO] | -inf dB -inf dB -inf dB -inf dB |
| [M] [S] | [M] [S] [M] [S] [M] [S] [M] [S] |
| [ROUTE] | [|||] [|||] Peak [|||] [|||] Peak [|||] [|||] Peak [|||] [|||] Peak|
| [FX] [Power] | [===] Fader [===] Fader [===] Fader [===] Fader |
| [TRIM] | [FX] [Power] [FX] [Power] [FX] [Power] [FX] [Power] |
| [FADER] (VU) | [🔴] [ROUTE] [🔴] [ROUTE] [🔴] [ROUTE] [🔴] [ROUTE] |
| | ------------------ ------------------ ------------------ ----------------|
| RMS/Peak Meter | Track 1 (Kick) Track 2 (SECTION) Track 3 (Guitars) Track 4 (Lead) |
| MASTER | [🎵 SF2 Synth] [📦 Sec: Chorus] [🎸 Pedalboard] [🎹 Vital] |
+-------------------+----------------------------------------------------------------------------+
Channel Strip Components:
-
Top Pan Knob: Rotatable control for Left/Right stereo balance (Pan Left/Right,
-1.0to+1.0). -
Numeric dB Readout: Displays current volume level in numeric dB (e.g.,
0.0\text{ dB},-6.5\text{ dB},-inf). -
Mute [M] & Solo [S] Buttons:
-
Orange [M] button: Mutes track output.
-
Yellow [S] button: Solos track output (mutes all non-soloed tracks).
-
Vertical Level Meter (VU Peak Meter): Real-time LED signal amplitude meter (Green
\rightarrowYellow\rightarrowRed clipping indicator above0\text{ dBFS}). -
Volume Fader Slider: Vertical linear slider for gain control.
-
FX & Power Toggle:
-
[FX] button: Opens the Effects Plugin management window (Reverb, Delay, EQ, Compressor).
-
[Power] button: Toggles bypass state for the track's entire FX chain.
-
Record Arm [🔴] & Routing/Trim Buttons:
-
Red circle [🔴]: Arms track for recording.
-
[ROUTE] button: Opens the Input/Output Matrix configuration panel.
-
Track Identifier Footer: Displays track index (
1, 2, 3...) and track name (e.g., Track 2 - Chorus Section).
3. STATE STORE SCHEMAS
3.1 Main Session State (main_session_mixer_state)
{
"session_id": "main_session_root",
"master_bus": {
"volume_db": 0.0,
"pan": 0.0,
"mute": false,
"is_mono": false,
"fx_chain": []
},
"tracks": [
{
"id": "track_01",
"name": "Track 1 - Drums",
"type": "AUDIO",
"volume_db": -2.5,
"pan": 0.0,
"mute": false,
"solo": false,
"is_armed": false
},
{
"id": "track_02_section_host",
"name": "Track 2 - Chorus Section",
"type": "SECTION",
"volume_db": 0.0,
"pan": 0.0,
"mute": false,
"solo": false,
"referenced_section_id": "section_chorus_001"
}
]
}
3.2 Section-Tab State (section_store)
{
"section_store": {
"section_chorus_001": {
"id": "section_chorus_001",
"name": "Chorus Section Tab",
"is_root": false,
"parent_track_id": "track_02_section_host",
"sub_mix_bus": {
"volume_db": 0.0,
"pan": 0.0,
"mute": false,
"fx_chain": []
},
"tracks": [
{
"id": "sec_track_1",
"name": "Sec Guitar 1",
"volume_db": -1.0,
"pan": -0.5,
"mute": false,
"solo": false
},
{
"id": "sec_track_2",
"name": "Sec Vocal Lead",
"volume_db": +1.5,
"pan": 0.0,
"mute": false,
"solo": false
}
]
}
}
}
4. AUDIO GRAPH NODE ENGINE (WEB AUDIO API)
To execute audio summing according to the architecture above, the application uses a manager class to maintain Web Audio nodes (AudioNode Graph):
// app/static/js/services/audioMixerGraphManager.js
export class AudioMixerGraphManager {
constructor(audioCtx) {
this.audioCtx = audioCtx;
// Main Session Master Nodes
this.mainMasterGain = this.audioCtx.createGain();
this.mainMasterPan = this.audioCtx.createStereoPanner();
this.mainMasterAnalyser = this.audioCtx.createAnalyser();
// Connect Master Graph -> Speakers
this.mainMasterGain
.connect(this.mainMasterPan)
.connect(this.mainMasterAnalyser)
.connect(this.audioCtx.destination);
// Map storing track AudioNodes
this.trackNodesMap = new Map(); // trackId -> { inputGain, faderGain, panNode, analyserNode }
this.sectionBusesMap = new Map(); // sectionId -> { subMixGain, subMixPan, outputNode }
}
/**
* Initializes Audio Nodes for a track on the Main Session
*/
createMainTrackNodes(track) {
const inputGain = this.audioCtx.createGain();
const faderGain = this.audioCtx.createGain();
const panNode = this.audioCtx.createStereoPanner();
const analyserNode = this.audioCtx.createAnalyser();
analyserNode.fftSize = 64;
// Track internal signal chain connection:
// InputGain -> FaderGain -> PanNode -> Analyser -> Main Master Gain
inputGain
.connect(faderGain)
.connect(panNode)
.connect(analyserNode)
.connect(this.mainMasterGain);
const nodeBundle = { inputGain, faderGain, panNode, analyserNode, track };
this.trackNodesMap.set(track.id, nodeBundle);
// Update initial Gain/Pan values
this.updateTrackVolumePan(track.id, track.volume_db, track.pan, track.mute);
return nodeBundle;
}
/**
* Initializes a Sub-Mix Bus for a SECTION-TAB and PIPES it to a Main Session track
* @param {string} sectionId - Section ID
* @param {string} hostTrackId - Main Session Track ID hosting this section
*/
createSectionSubMixBus(sectionId, hostTrackId) {
const subMixGain = this.audioCtx.createGain();
const subMixPan = this.audioCtx.createStereoPanner();
// Section Sub-Mix Bus internal connection
subMixGain.connect(subMixPan);
// CRITICAL STEP: Locate host track on Main Session to pipe signal
const hostTrackNodes = this.trackNodesMap.get(hostTrackId);
if (hostTrackNodes) {
// Pipe Output of Section Bus directly into InputGain of Main Track
subMixPan.connect(hostTrackNodes.inputGain);
console.log(`[MixerRouter] Sub-Mix Bus of Section "${sectionId}" successfully PIPED into Main Track "${hostTrackId}"`);
} else {
console.warn(`[MixerRouter] Host Track "${hostTrackId}" not found. Section bus falling back to Master.`);
subMixPan.connect(this.mainMasterGain);
}
const sectionBundle = { subMixGain, subMixPan, hostTrackId };
this.sectionBusesMap.set(sectionId, sectionBundle);
return sectionBundle;
}
/**
* Initializes an INTERNAL track inside a SECTION-TAB
*/
createSectionTrackNodes(sectionId, secTrack) {
const sectionBundle = this.sectionBusesMap.get(sectionId);
if (!sectionBundle) {
throw new Error(`Section Sub-Mix Bus for "${sectionId}" has not been initialized!`);
}
const inputGain = this.audioCtx.createGain();
const faderGain = this.audioCtx.createGain();
const panNode = this.audioCtx.createStereoPanner();
const analyserNode = this.audioCtx.createAnalyser();
// Connect Track in Section to the Section's Sub-Mix Bus
inputGain
.connect(faderGain)
.connect(panNode)
.connect(analyserNode)
.connect(sectionBundle.subMixGain); // Direct to Section Sub-Mix Bus!
const nodeBundle = { inputGain, faderGain, panNode, analyserNode, secTrack };
this.trackNodesMap.set(secTrack.id, nodeBundle);
this.updateTrackVolumePan(secTrack.id, secTrack.volume_db, secTrack.pan, secTrack.mute);
return nodeBundle;
}
/**
* Updates Volume (dB) & Pan using standard audio formulas
*/
updateTrackVolumePan(trackId, volumeDb, panValue, isMuted) {
const bundle = this.trackNodesMap.get(trackId);
if (!bundle) return;
// Convert dB to Linear Gain Factor: Gain = 10 ^ (dB / 20)
const linearGain = isMuted ? 0.0 : Math.pow(10, volumeDb / 20.0);
bundle.faderGain.gain.setTargetAtTime(linearGain, this.audioCtx.currentTime, 0.01);
bundle.panNode.pan.setTargetAtTime(panValue, this.audioCtx.currentTime, 0.01);
}
}
5. NESTED MUTE/SOLO LOGIC MATRIX
When muting or soloing a track on the Main Session or within a Section Tab, the system applies hierarchical rules:
| User Action | Main Session Impact | Internal Section Tracks Impact |
|---|---|---|
| Mute Track 2 (Section Host) on Main Session | Track 2 is muted. Other tracks play normally. | Entire Section is muted (signal is blocked at Track 2's Fader Gain). |
| Mute Section Track 1 inside Section Tab | No impact on other Main Tracks. | Only Section Track 1 is silenced. Other Section Tracks (S2, S3) continue routing to Track 2. |
| Solo Section Track 1 inside Section Tab | No impact on Main Session. | Mutes S2, S3 within the Section. Only S1 outputs to Track 2 of the Main Session. |
| Solo Track 2 on Main Session | Mutes Track 1 and Track 3 on Main Session. | The Section audio stream plays normally to the Main Master Bus. |
6. STATE-DRIVEN REACT/CANVAS COMPONENT
Below is the React implementation for the Mixer Console supporting dynamic switching between MAIN SESSION and SECTION-TAB mixers:
// app/static/js/components/MixerConsole.jsx
import React, { useState, useEffect } from 'react';
export function MixerConsole({ sessionState, activeTabContext, mixerGraphMgr }) {
// Determine if Mixer is rendering for MAIN SESSION or SECTION-TAB
const isSectionTab = activeTabContext?.type === 'SECTION_TAB';
// Retrieve target tracks list for Mixer Console display
const currentTracks = isSectionTab
? sessionState.section_store[activeTabContext.referenced_section_id]?.tracks || []
: sessionState.main_session.tracks;
const currentTitle = isSectionTab
? `MIXER: SECTION TAB (${activeTabContext.title})`
: "MIXER: MAIN SESSION";
return (
<div className="flex flex-col h-64 bg-slate-900 border-t border-slate-700 text-slate-200 select-none">
{/* Mixer Header / Tab Indicator */}
<div className="flex items-center justify-between px-3 py-1 bg-slate-950 border-b border-slate-800 text-xs font-semibold">
<div className="flex items-center gap-2">
<span className={`w-2.5 h-2.5 rounded-full ${isSectionTab ? "bg-amber-400 animate-pulse" : "bg-indigo-500"}`} />
<span className="text-white tracking-wide">{currentTitle}</span>
</div>
{isSectionTab && (
<span className="text-[10px] text-amber-300/80 bg-amber-950/60 px-2 py-0.5 rounded border border-amber-800/50">
Routed to Main Track: {activeTabContext.parent_track_name || "Track Host"}
</span>
)}
</div>
{/* Mixer Strips Container */}
<div className="flex-1 flex overflow-x-auto p-2 gap-1.5 custom-scrollbar bg-slate-900/90">
{/* 1. MASTER STRIP (Far Left) */}
<MasterChannelStrip
isSectionBus={isSectionTab}
mixerGraphMgr={mixerGraphMgr}
/>
<div className="w-[1px] bg-slate-700/60 mx-1 my-1" />
{/* 2. TRACK STRIPS (Audio Channels) */}
{currentTracks.map((track, idx) => (
<TrackChannelStrip
key={track.id}
track={track}
index={idx + 1}
mixerGraphMgr={mixerGraphMgr}
/>
))}
</div>
</div>
);
}
/**
* Channel Strip Component representing a single Track
*/
function TrackChannelStrip({ track, index, mixerGraphMgr }) {
const [volumeDb, setVolumeDb] = useState(track.volume_db || 0);
const [pan, setPan] = useState(track.pan || 0);
const [isMuted, setIsMuted] = useState(Boolean(track.mute));
const [isSoloed, setIsSoloed] = useState(Boolean(track.solo));
const handleVolumeChange = (e) => {
const newDb = parseFloat(e.target.value);
setVolumeDb(newDb);
track.volume_db = newDb;
mixerGraphMgr?.updateTrackVolumePan(track.id, newDb, pan, isMuted);
};
const handleToggleMute = () => {
const newMute = !isMuted;
setIsMuted(newMute);
track.mute = newMute;
mixerGraphMgr?.updateTrackVolumePan(track.id, volumeDb, pan, newMute);
};
return (
<div className="w-24 bg-slate-950 border border-slate-800 rounded flex flex-col p-1.5 items-center justify-between shadow-lg">
{/* 1. Pan Knob */}
<div className="flex flex-col items-center w-full">
<span className="text-[10px] text-slate-400 font-mono">
{pan === 0 ? "center" : pan < 0 ? `L${Math.abs(Math.round(pan * 100))}` : `R${Math.round(pan * 100)}`}
</span>
<input
type="range"
min="-1"
max="1"
step="0.05"
value={pan}
onChange={(e) => {
const p = parseFloat(e.target.value);
setPan(p);
track.pan = p;
mixerGraphMgr?.updateTrackVolumePan(track.id, volumeDb, p, isMuted);
}}
className="w-12 h-1 accent-indigo-500 cursor-pointer"
/>
</div>
{/* 2. dB Readout */}
<div className="text-[11px] font-mono text-slate-300 font-semibold my-0.5">
{volumeDb <= -60 ? "-inf" : `${volumeDb > 0 ? "+" : ""}${volumeDb.toFixed(1)}dB`}
</div>
{/* 3. Mute / Solo Buttons */}
<div className="flex gap-1 w-full my-1">
<button
onClick={handleToggleMute}
className={`flex-1 text-[10px] font-bold py-0.5 rounded border transition-colors ${
isMuted ? "bg-amber-600 text-white border-amber-500" : "bg-slate-800 text-slate-400 border-slate-700 hover:bg-slate-700"
}`}
>
M
</button>
<button
onClick={() => {
const s = !isSoloed;
setIsSoloed(s);
track.solo = s;
}}
className={`flex-1 text-[10px] font-bold py-0.5 rounded border transition-colors ${
isSoloed ? "bg-yellow-500 text-black border-yellow-400" : "bg-slate-800 text-slate-400 border-slate-700 hover:bg-slate-700"
}`}
>
S
</button>
</div>
{/* 4. Fader & Meter Section */}
<div className="flex-1 flex items-center justify-center gap-1.5 w-full my-1 relative">
{/* Fader Slider */}
<input
type="range"
min="-60"
max="12"
step="0.5"
value={volumeDb}
onChange={handleVolumeChange}
className="h-28 w-2 appearance-none bg-slate-800 rounded outline-none cursor-pointer [writing-mode:vertical-lr] [direction:rtl]"
/>
{/* VU Peak Meter Bar */}
<div className="w-2.5 h-28 bg-slate-900 border border-slate-800 rounded overflow-hidden flex flex-col justify-end p-0.5">
<div className="w-full bg-gradient-to-t from-emerald-500 via-yellow-400 to-red-500 h-[40%]" />
</div>
</div>
{/* 5. FX & Power Buttons */}
<div className="flex gap-1 w-full my-1">
<button className="flex-1 bg-slate-800 hover:bg-slate-700 text-slate-300 text-[9px] py-0.5 rounded border border-slate-700 font-semibold">
FX
</button>
<button className="px-1.5 bg-slate-800 hover:bg-slate-700 text-slate-400 text-[9px] py-0.5 rounded border border-slate-700">
⑈
</button>
</div>
{/* 6. Footer Name */}
<div className="w-full text-center bg-slate-900 py-1 rounded border border-slate-800/80 mt-1">
<div className="text-[10px] font-bold text-white truncate px-1">{index}. {track.name}</div>
<div className="text-[8px] text-slate-500 uppercase tracking-tighter truncate">{track.type}</div>
</div>
</div>
);
}
/**
* Master Channel Strip Component
*/
function MasterChannelStrip({ isSectionBus, mixerGraphMgr }) {
return (
<div className="w-28 bg-slate-950 border-2 border-indigo-900/60 rounded flex flex-col p-1.5 items-center justify-between shadow-2xl">
<div className="text-[10px] font-bold text-indigo-400 uppercase tracking-wider">
{isSectionBus ? "SEC BUS" : "MASTER"}
</div>
{/* Peak / RMS Canvas Meter */}
<div className="w-full h-32 bg-black border border-indigo-950 rounded relative my-2 overflow-hidden flex items-center justify-center">
<span className="text-[9px] font-mono text-emerald-400/80 font-semibold">-inf dB</span>
</div>
<div className="w-full text-center bg-indigo-950/80 border border-indigo-800/60 py-1 rounded mt-auto">
<div className="text-[10px] font-bold text-indigo-200 uppercase">
{isSectionBus ? "SUB-MIX" : "MAIN OUT"}
</div>
</div>
</div>
);
}
7. OPERATIONAL VERIFICATION CHECKLIST
-
Main Session Mixer Operations:
-
Adjust Track 1 Fader
\rightarrowTrack 1 volume increases/decreases as expected. -
Mute Track 1
\rightarrowTrack 1 output is silenced. -
SECTION-TAB Mixer & Routing Operations:
-
Open a SECTION-TAB into a sub-tab
\rightarrowMixer Console dynamically switches header toMIXER: SECTION TABdisplaying internal section tracks. -
Adjust Section Track 1 Fader
\rightarrowGuitar volume within the Section increases/decreases without affecting other Main Tracks. -
Bus Routing Point Verification:
-
Switch back to MAIN SESSION tab.
-
Adjust Track 2 Fader (Track hosting Section Item)
\rightarrowEntire SECTION-TAB volume adjusts synchronously. -
Mute Track 2
\rightarrowEntire SECTION-TAB output is silenced completely.