FEAT: cài đặt tính năng cho volume main out
This commit is contained in:
+18
-16
@@ -53,11 +53,11 @@ function initMasterBus(ctx) {
|
|||||||
masterBus.compressor.attack.value = 0.003;
|
masterBus.compressor.attack.value = 0.003;
|
||||||
masterBus.compressor.release.value = 0.25;
|
masterBus.compressor.release.value = 0.25;
|
||||||
masterBus.output.gain.value = 1;
|
masterBus.output.gain.value = 1;
|
||||||
// Default routing: input → compressor → analyser → output → destination
|
// MAIN OUT routing per spec: input → compressor → output (MAIN OUT GAIN) → analyser → destination
|
||||||
masterBus.input.connect(masterBus.compressor);
|
masterBus.input.connect(masterBus.compressor);
|
||||||
masterBus.compressor.connect(masterBus.analyser);
|
masterBus.compressor.connect(masterBus.output);
|
||||||
masterBus.analyser.connect(masterBus.output);
|
masterBus.output.connect(masterBus.analyser);
|
||||||
masterBus.output.connect(ctx.destination);
|
masterBus.analyser.connect(ctx.destination);
|
||||||
return masterBus;
|
return masterBus;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,8 +81,8 @@ function toggleMasteringOnMaster(activate, nodes) {
|
|||||||
nodes.eqMid2Filter.connect(nodes.eqHighFilter);
|
nodes.eqMid2Filter.connect(nodes.eqHighFilter);
|
||||||
nodes.eqHighFilter.connect(nodes.maximizerBoostGain);
|
nodes.eqHighFilter.connect(nodes.maximizerBoostGain);
|
||||||
nodes.maximizerBoostGain.connect(nodes.maximizerCompressor);
|
nodes.maximizerBoostGain.connect(nodes.maximizerCompressor);
|
||||||
nodes.maximizerCompressor.connect(masterBus.analyser);
|
nodes.maximizerCompressor.connect(masterBus.output);
|
||||||
masterBus.analyser.connect(masterBus.output);
|
masterBus.output.connect(masterBus.analyser);
|
||||||
masterBus.masteringActive = true;
|
masterBus.masteringActive = true;
|
||||||
masterBus.eqNodes = nodes;
|
masterBus.eqNodes = nodes;
|
||||||
} else if (!activate && masterBus.masteringActive) {
|
} else if (!activate && masterBus.masteringActive) {
|
||||||
@@ -97,10 +97,10 @@ function toggleMasteringOnMaster(activate, nodes) {
|
|||||||
try { masterBus.eqNodes.maximizerBoostGain.disconnect(); } catch(e) {}
|
try { masterBus.eqNodes.maximizerBoostGain.disconnect(); } catch(e) {}
|
||||||
try { masterBus.eqNodes.maximizerCompressor.disconnect(); } catch(e) {}
|
try { masterBus.eqNodes.maximizerCompressor.disconnect(); } catch(e) {}
|
||||||
}
|
}
|
||||||
// Restore default routing
|
// Restore default routing: input → compressor → output → analyser → destination
|
||||||
masterBus.input.connect(masterBus.compressor);
|
masterBus.input.connect(masterBus.compressor);
|
||||||
masterBus.compressor.connect(masterBus.analyser);
|
masterBus.compressor.connect(masterBus.output);
|
||||||
masterBus.analyser.connect(masterBus.output);
|
masterBus.output.connect(masterBus.analyser);
|
||||||
masterBus.masteringActive = false;
|
masterBus.masteringActive = false;
|
||||||
masterBus.eqNodes = null;
|
masterBus.eqNodes = null;
|
||||||
}
|
}
|
||||||
@@ -18950,18 +18950,18 @@ const App = () => {
|
|||||||
})))), /*#__PURE__*/React.createElement("div", {
|
})))), /*#__PURE__*/React.createElement("div", {
|
||||||
className: "flex-1 flex overflow-x-auto p-1.5 gap-1.5 items-stretch"
|
className: "flex-1 flex overflow-x-auto p-1.5 gap-1.5 items-stretch"
|
||||||
}, /*#__PURE__*/React.createElement("div", {
|
}, /*#__PURE__*/React.createElement("div", {
|
||||||
className: "flex flex-col items-stretch w-16 shrink-0 bg-[#2b2b2b] border border-black/70 overflow-hidden rounded-sm"
|
className: "flex flex-col items-stretch w-[72px] shrink-0 bg-[#2b2b2b] border border-black/70 overflow-hidden rounded-sm"
|
||||||
}, /*#__PURE__*/React.createElement("div", {
|
}, /*#__PURE__*/React.createElement("div", {
|
||||||
className: "text-[9px] font-bold text-zinc-300 uppercase tracking-wider w-full text-center py-0.5 bg-[#222] border-b border-black/60 shrink-0"
|
className: "text-[9px] font-bold text-zinc-300 uppercase tracking-wider w-full text-center py-0.5 bg-[#222] border-b border-black/60 shrink-0"
|
||||||
}, "MASTER"), /*#__PURE__*/React.createElement("div", {
|
}, "MASTER"), /*#__PURE__*/React.createElement("div", {
|
||||||
className: "flex-1 flex items-stretch justify-center gap-1 px-1 py-1 min-h-0"
|
className: "flex-1 flex items-stretch justify-center gap-1 px-1 py-1 min-h-0"
|
||||||
}, /*#__PURE__*/React.createElement("div", {
|
}, /*#__PURE__*/React.createElement("div", {
|
||||||
className: "w-5 rounded-sm relative overflow-hidden bg-[#0d0d0d] border border-black/60 flex flex-col items-center"
|
className: "w-6 rounded-sm relative overflow-hidden bg-[#0d0d0d] border border-black/60 flex flex-col items-center"
|
||||||
}, /*#__PURE__*/React.createElement("input", {
|
}, /*#__PURE__*/React.createElement("input", {
|
||||||
type: "range",
|
type: "range",
|
||||||
min: "-60", max: "12", step: "0.5",
|
min: "-60", max: "12", step: "0.5",
|
||||||
value: masterVolume,
|
value: masterVolume,
|
||||||
onChange: e => { const v = parseFloat(e.target.value); setMasterVolume(v); if (masterBus) setMasterVolume(v); const linear = v <= -50 ? 0 : Math.pow(10, v / 20); if (masterBus) masterBus.output.gain.setValueAtTime(linear, getAudioContext().currentTime); },
|
onChange: e => { const v = parseFloat(e.target.value); setMasterVolume(v); const linear = v <= -50 ? 0 : Math.pow(10, v / 20); if (masterBus && masterBus.output) masterBus.output.gain.setValueAtTime(linear, getAudioContext().currentTime); },
|
||||||
className: "absolute inset-0 w-full h-full opacity-0 cursor-pointer z-10",
|
className: "absolute inset-0 w-full h-full opacity-0 cursor-pointer z-10",
|
||||||
style: { writingMode: 'vertical-lr', direction: 'rtl' }
|
style: { writingMode: 'vertical-lr', direction: 'rtl' }
|
||||||
}), /*#__PURE__*/React.createElement("div", {
|
}), /*#__PURE__*/React.createElement("div", {
|
||||||
@@ -18971,17 +18971,19 @@ const App = () => {
|
|||||||
className: "w-full bg-[#0d0d0d] transition-all duration-75",
|
className: "w-full bg-[#0d0d0d] transition-all duration-75",
|
||||||
style: { height: `${(1 - Math.min(1, Math.max(0, (masterVolume + 60) / 72))) * 100}%` }
|
style: { height: `${(1 - Math.min(1, Math.max(0, (masterVolume + 60) / 72))) * 100}%` }
|
||||||
}))), /*#__PURE__*/React.createElement("div", {
|
}))), /*#__PURE__*/React.createElement("div", {
|
||||||
className: "w-2.5 rounded-sm relative overflow-hidden bg-[#0d0d0d] border border-black/60"
|
className: "w-3 rounded-sm relative overflow-hidden bg-[#0d0d0d] border border-black/60"
|
||||||
}, /*#__PURE__*/React.createElement("div", {
|
}, /*#__PURE__*/React.createElement("div", {
|
||||||
className: "absolute bottom-0 w-full transition-all duration-75",
|
className: "absolute bottom-0 w-full transition-all duration-75",
|
||||||
style: { height: `${Math.min(100, masterVU * 100)}%`, background: masterVU > 0.85 ? '#ef4444' : masterVU > 0.7 ? '#f59e0b' : '#38bdf8' }
|
style: { height: `${Math.min(100, masterVU * 100)}%`, background: masterVU > 0.85 ? '#ef4444' : masterVU > 0.7 ? '#f59e0b' : '#38bdf8' }
|
||||||
}))), /*#__PURE__*/React.createElement("div", {
|
}))), /*#__PURE__*/React.createElement("div", {
|
||||||
className: "flex items-center gap-1 justify-center py-0.5 bg-[#222] border-t border-black/60 shrink-0"
|
className: "text-[8px] font-mono font-bold text-center py-0.5 bg-[#222] border-t border-black/60 shrink-0 flex items-center justify-center gap-1"
|
||||||
}, /*#__PURE__*/React.createElement("button", {
|
}, /*#__PURE__*/React.createElement("span", {
|
||||||
|
className: "text-zinc-400"
|
||||||
|
}, masterVolume > 0 ? '+' : '', masterVolume.toFixed(1), " dB"), /*#__PURE__*/React.createElement("button", {
|
||||||
onClick: () => setShowMasteringModal(true),
|
onClick: () => setShowMasteringModal(true),
|
||||||
className: "px-1 py-0.5 text-[7px] font-bold uppercase bg-cyan-900 hover:bg-cyan-800 text-cyan-300 rounded transition-colors",
|
className: "px-1 py-0.5 text-[7px] font-bold uppercase bg-cyan-900 hover:bg-cyan-800 text-cyan-300 rounded transition-colors",
|
||||||
title: "Open Mastering Suite"
|
title: "Open Mastering Suite"
|
||||||
}, "Mastering")), /*#__PURE__*/React.createElement("div", {
|
}, "M")), /*#__PURE__*/React.createElement("div", {
|
||||||
className: "text-[8px] font-mono font-bold text-zinc-500 w-full text-center py-0.5 bg-[#222] border-t border-black/60 shrink-0"
|
className: "text-[8px] font-mono font-bold text-zinc-500 w-full text-center py-0.5 bg-[#222] border-t border-black/60 shrink-0"
|
||||||
}, "MAIN OUT")), activeTracks.length > 0 && /*#__PURE__*/React.createElement("div", {
|
}, "MAIN OUT")), activeTracks.length > 0 && /*#__PURE__*/React.createElement("div", {
|
||||||
className: "w-px bg-zinc-700 shrink-0 self-stretch mx-0.5"
|
className: "w-px bg-zinc-700 shrink-0 self-stretch mx-0.5"
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,72 @@
|
|||||||
|
# IMPACT OF MAIN OUT VOLUME SLIDER IN THE AUDIO PROCESSING PIPELINE
|
||||||
|
|
||||||
|
In Web DAW and Mastering Suite audio architectures (powered by the Web Audio API), the MAIN OUT Volume Slider serves as the final amplification control node (**Post-Processing Master Gain Node**).
|
||||||
|
|
||||||
|
Below is a detailed breakdown of how the MAIN OUT slider impacts the audio signal processing chain:
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Signal Graph Position
|
||||||
|
|
||||||
|
The MAIN OUT Volume Slider controls a `GainNode` located at the very end of the processing chain (**Post-FX Stage**), directly preceding hardware output delivery (`AudioContext.destination` / Speakers / Headphones):
|
||||||
|
|
||||||
|
```text
|
||||||
|
[ Input Analyser ]
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
[ Processing Chain ] (Dynamic EQ ➔ Stereo Imager ➔ Maximizer / Limiter)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
[ MAIN OUT Volume Slider (Gain Node) ] ◄── (dB Adjustment Slider)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
[ Output Analyser / Peak Meter ]
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
[ AudioContext.destination ] (Hardware Speakers / DAC)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Specific Impacts on the Audio Stream
|
||||||
|
|
||||||
|
### 1. Dynamics & Character Unchanged
|
||||||
|
|
||||||
|
Because it is placed after the processing modules (Compressor, Dynamic EQ, Maximizer/Limiter), increasing or decreasing the MAIN OUT level **does NOT** alter compression behavior, harmonic saturation, or the ceiling/threshold point of the Brickwall Limiter.
|
||||||
|
|
||||||
|
The audio retains $100\%$ of its tonal color, frequency balance, and dynamic range ratio processed by the Master FX chain.
|
||||||
|
|
||||||
|
### 2. Linear Amplitude Scaling
|
||||||
|
|
||||||
|
The slider converts decibel values ($\text{dB}$) into a linear gain factor $G$ using the standard formula:
|
||||||
|
|
||||||
|
$$G = 10^{\frac{\text{dB}}{20}}$$
|
||||||
|
|
||||||
|
All PCM Float32 audio samples ($[-1.0, 1.0]$) passing through the node are multiplied directly by this factor $G$:
|
||||||
|
|
||||||
|
$$S_{\text{out}}(t) = S_{\text{processed}}(t) \times G$$
|
||||||
|
|
||||||
|
### 3. Impact on Hardware Output & Clipping
|
||||||
|
|
||||||
|
* **Decreasing the Slider ($< 0\text{ dB}$):** Reduces the waveform amplitude, allowing users to lower the monitoring volume without affecting the underlying Master processing.
|
||||||
|
* **Increasing the Slider ($> 0\text{ dB}$):** Increases signal amplitude. If the scaled amplitude exceeds $1.0$ ($0\text{ dBFS}$), the digital-to-analog converter (DAC) will suffer from hardware clipping and distortion—even if the preceding Maximizer successfully capped true peaks.
|
||||||
|
|
||||||
|
### 4. Impact on Output Metering (Post-Fader Metering)
|
||||||
|
|
||||||
|
When set to Post-Fader mode, output Peak/RMS/LUFS meters accurately reflect signal levels after multiplication by the MAIN OUT Gain factor. This ensures operators monitor the actual signal level going to speakers or export files.
|
||||||
|
|
||||||
|
### 5. Impact on WAV Bounce / Export
|
||||||
|
|
||||||
|
During final mix export (**Master WAV Export**), the MAIN OUT Slider value scales the entire final PCM Audio Buffer. Consequently, this slider dictates the absolute volume level of the rendered output WAV file.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Comparison Matrix: MAXIMIZER GAIN vs. MAIN OUT SLIDER
|
||||||
|
|
||||||
|
| Parameter / Criteria | Maximizer Boost Gain | MAIN OUT Volume Slider |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| **Signal Chain Location** | Inside FX Chain (Pre-Limiter) | After FX Chain (Post-Limiter) |
|
||||||
|
| **Dynamics Impact** | Alters LUFS, drives signal into Ceiling, increases compression density. | Does NOT alter dynamics; purely scales master output volume. |
|
||||||
|
| **Clipping Risk** | **No** (Controlled/limited by the Brickwall Ceiling). | **Yes** (Boosting $> 0\text{ dB}$ can exceed $0\text{ dBFS}$ and clip the DAC). |
|
||||||
|
| **Primary Purpose** | Increases perceived loudness (LUFS). | Controls monitoring volume for speakers/headphones or final export scaling. |
|
||||||
Reference in New Issue
Block a user