feat: mono audio
This commit is contained in:
parent
19baee21aa
commit
3974ec7551
4 changed files with 98 additions and 7 deletions
10
index.html
10
index.html
|
|
@ -2896,6 +2896,16 @@
|
|||
style="width: 80px"
|
||||
/>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="info">
|
||||
<span class="label">Mono Audio</span>
|
||||
<span class="description">Combine left and right channels into mono</span>
|
||||
</div>
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox" id="mono-audio-toggle" />
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- 16-Band Equalizer -->
|
||||
<div class="setting-item">
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// js/audio-context.js
|
||||
// Shared Audio Context Manager - handles EQ and provides context for visualizer
|
||||
|
||||
import { equalizerSettings } from './storage.js';
|
||||
import { equalizerSettings, monoAudioSettings } from './storage.js';
|
||||
|
||||
// Standard 16-band ISO center frequencies (Hz)
|
||||
const EQ_FREQUENCIES = [25, 40, 63, 100, 160, 250, 400, 630, 1000, 1600, 2500, 4000, 6300, 10000, 16000, 20000];
|
||||
|
|
@ -83,6 +83,8 @@ class AudioContextManager {
|
|||
this.outputNode = null;
|
||||
this.isInitialized = false;
|
||||
this.isEQEnabled = false;
|
||||
this.isMonoAudioEnabled = false;
|
||||
this.monoMergerNode = null;
|
||||
this.currentGains = new Array(16).fill(0);
|
||||
this.audio = null;
|
||||
|
||||
|
|
@ -168,13 +170,16 @@ class AudioContextManager {
|
|||
this.outputNode = this.audioContext.createGain();
|
||||
this.outputNode.gain.value = 1;
|
||||
|
||||
// Create mono audio merger node
|
||||
this.monoMergerNode = this.audioContext.createChannelMerger(2);
|
||||
|
||||
// Connect filter chain: filter[0] -> filter[1] -> ... -> filter[15] -> outputNode
|
||||
for (let i = 0; i < this.filters.length - 1; i++) {
|
||||
this.filters[i].connect(this.filters[i + 1]);
|
||||
}
|
||||
this.filters[this.filters.length - 1].connect(this.outputNode);
|
||||
|
||||
// Connect the audio graph based on EQ state
|
||||
// Connect the audio graph based on EQ and mono state
|
||||
this._connectGraph();
|
||||
|
||||
this.isInitialized = true;
|
||||
|
|
@ -185,7 +190,7 @@ class AudioContextManager {
|
|||
}
|
||||
|
||||
/**
|
||||
* Connect the audio graph based on EQ enabled state
|
||||
* Connect the audio graph based on EQ and mono audio state
|
||||
*/
|
||||
_connectGraph() {
|
||||
if (!this.source || !this.audioContext) return;
|
||||
|
|
@ -194,6 +199,13 @@ class AudioContextManager {
|
|||
// Disconnect everything first
|
||||
this.source.disconnect();
|
||||
this.outputNode.disconnect();
|
||||
if (this.monoMergerNode) {
|
||||
try {
|
||||
this.monoMergerNode.disconnect();
|
||||
} catch (e) {
|
||||
// Ignore if not connected
|
||||
}
|
||||
}
|
||||
|
||||
// Only disconnect destination from analyser to preserve other taps (like Butterchurn)
|
||||
try {
|
||||
|
|
@ -202,15 +214,34 @@ class AudioContextManager {
|
|||
// Ignore if not connected
|
||||
}
|
||||
|
||||
let lastNode = this.source;
|
||||
|
||||
// Apply mono audio if enabled
|
||||
if (this.isMonoAudioEnabled && this.monoMergerNode) {
|
||||
// Create a gain node to mix channels before the merger
|
||||
const monoGain = this.audioContext.createGain();
|
||||
monoGain.gain.value = 0.5; // Reduce volume to prevent clipping when mixing
|
||||
|
||||
// Connect source to mono gain
|
||||
this.source.connect(monoGain);
|
||||
|
||||
// Connect mono gain to both inputs of the merger
|
||||
monoGain.connect(this.monoMergerNode, 0, 0);
|
||||
monoGain.connect(this.monoMergerNode, 0, 1);
|
||||
|
||||
lastNode = this.monoMergerNode;
|
||||
console.log('[AudioContext] Mono audio enabled');
|
||||
}
|
||||
|
||||
if (this.isEQEnabled && this.filters.length > 0) {
|
||||
// EQ enabled: source -> EQ filters -> output -> analyser -> destination
|
||||
this.source.connect(this.filters[0]);
|
||||
// EQ enabled: lastNode -> EQ filters -> output -> analyser -> destination
|
||||
lastNode.connect(this.filters[0]);
|
||||
this.outputNode.connect(this.analyser);
|
||||
this.analyser.connect(this.audioContext.destination);
|
||||
console.log('[AudioContext] EQ connected');
|
||||
} else {
|
||||
// EQ disabled: source -> analyser -> destination
|
||||
this.source.connect(this.analyser);
|
||||
// EQ disabled: lastNode -> analyser -> destination
|
||||
lastNode.connect(this.analyser);
|
||||
this.analyser.connect(this.audioContext.destination);
|
||||
console.log('[AudioContext] EQ bypassed');
|
||||
}
|
||||
|
|
@ -303,6 +334,27 @@ class AudioContextManager {
|
|||
return this.isInitialized && this.isEQEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle mono audio on/off
|
||||
*/
|
||||
toggleMonoAudio(enabled) {
|
||||
this.isMonoAudioEnabled = enabled;
|
||||
monoAudioSettings.setEnabled(enabled);
|
||||
|
||||
if (this.isInitialized) {
|
||||
this._connectGraph();
|
||||
}
|
||||
|
||||
return this.isMonoAudioEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if mono audio is active
|
||||
*/
|
||||
isMonoAudioActive() {
|
||||
return this.isInitialized && this.isMonoAudioEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set gain for a specific band
|
||||
*/
|
||||
|
|
@ -372,6 +424,7 @@ class AudioContextManager {
|
|||
_loadSettings() {
|
||||
this.isEQEnabled = equalizerSettings.isEnabled();
|
||||
this.currentGains = equalizerSettings.getGains();
|
||||
this.isMonoAudioEnabled = monoAudioSettings.isEnabled();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import {
|
|||
homePageSettings,
|
||||
sidebarSectionSettings,
|
||||
fontSettings,
|
||||
monoAudioSettings,
|
||||
} from './storage.js';
|
||||
import { audioContextManager, EQ_PRESETS } from './audio-context.js';
|
||||
import { getButterchurnPresets } from './visualizers/butterchurn.js';
|
||||
|
|
@ -690,6 +691,17 @@ export function initializeSettings(scrobbler, player, api, ui) {
|
|||
});
|
||||
}
|
||||
|
||||
// Mono Audio Toggle
|
||||
const monoAudioToggle = document.getElementById('mono-audio-toggle');
|
||||
if (monoAudioToggle) {
|
||||
monoAudioToggle.checked = monoAudioSettings.isEnabled();
|
||||
monoAudioToggle.addEventListener('change', (e) => {
|
||||
const enabled = e.target.checked;
|
||||
monoAudioSettings.setEnabled(enabled);
|
||||
audioContextManager.toggleMonoAudio(enabled);
|
||||
});
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 16-Band Equalizer Settings
|
||||
// ========================================
|
||||
|
|
|
|||
|
|
@ -844,6 +844,22 @@ export const equalizerSettings = {
|
|||
},
|
||||
};
|
||||
|
||||
export const monoAudioSettings = {
|
||||
STORAGE_KEY: 'mono-audio-enabled',
|
||||
|
||||
isEnabled() {
|
||||
try {
|
||||
return localStorage.getItem(this.STORAGE_KEY) === 'true';
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
setEnabled(enabled) {
|
||||
localStorage.setItem(this.STORAGE_KEY, enabled ? 'true' : 'false');
|
||||
},
|
||||
};
|
||||
|
||||
export const sidebarSettings = {
|
||||
STORAGE_KEY: 'monochrome-sidebar-collapsed',
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue