532 lines
21 KiB
Markdown
532 lines
21 KiB
Markdown
# 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
|
|
|
|
```text
|
|
[ 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 the `Section Sub-Mix Bus Node`.
|
|
* **Routing to MAIN SESSION:** The output of the `Section Sub-Mix Bus Node` does not connect directly to `audioCtx.destination`. Instead, it routes into the Input Node of Track 2 on the MAIN SESSION (the track hosting the `SECTION_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).
|
|
|
|
```text
|
|
+------------------------------------------------------------------------------------------------+
|
|
| 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.0$ to $+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 $\rightarrow$ Yellow $\rightarrow$ Red clipping indicator above $0\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`)
|
|
|
|
```json
|
|
{
|
|
"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`)
|
|
|
|
```json
|
|
{
|
|
"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`):
|
|
|
|
```javascript
|
|
// 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:
|
|
|
|
```jsx
|
|
// 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 $\rightarrow$ Track 1 volume increases/decreases as expected.
|
|
* Mute Track 1 $\rightarrow$ Track 1 output is silenced.
|
|
|
|
|
|
* [ ] **SECTION-TAB Mixer & Routing Operations:**
|
|
* Open a SECTION-TAB into a sub-tab $\rightarrow$ Mixer Console dynamically switches header to `MIXER: SECTION TAB` displaying internal section tracks.
|
|
* Adjust Section Track 1 Fader $\rightarrow$ Guitar 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) $\rightarrow$ Entire SECTION-TAB volume adjusts synchronously.
|
|
* Mute Track 2 $\rightarrow$ Entire SECTION-TAB output is silenced completely. |