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"
|
style="width: 80px"
|
||||||
/>
|
/>
|
||||||
</div>
|
</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 -->
|
<!-- 16-Band Equalizer -->
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// js/audio-context.js
|
// js/audio-context.js
|
||||||
// Shared Audio Context Manager - handles EQ and provides context for visualizer
|
// 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)
|
// 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];
|
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.outputNode = null;
|
||||||
this.isInitialized = false;
|
this.isInitialized = false;
|
||||||
this.isEQEnabled = false;
|
this.isEQEnabled = false;
|
||||||
|
this.isMonoAudioEnabled = false;
|
||||||
|
this.monoMergerNode = null;
|
||||||
this.currentGains = new Array(16).fill(0);
|
this.currentGains = new Array(16).fill(0);
|
||||||
this.audio = null;
|
this.audio = null;
|
||||||
|
|
||||||
|
|
@ -168,13 +170,16 @@ class AudioContextManager {
|
||||||
this.outputNode = this.audioContext.createGain();
|
this.outputNode = this.audioContext.createGain();
|
||||||
this.outputNode.gain.value = 1;
|
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
|
// Connect filter chain: filter[0] -> filter[1] -> ... -> filter[15] -> outputNode
|
||||||
for (let i = 0; i < this.filters.length - 1; i++) {
|
for (let i = 0; i < this.filters.length - 1; i++) {
|
||||||
this.filters[i].connect(this.filters[i + 1]);
|
this.filters[i].connect(this.filters[i + 1]);
|
||||||
}
|
}
|
||||||
this.filters[this.filters.length - 1].connect(this.outputNode);
|
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._connectGraph();
|
||||||
|
|
||||||
this.isInitialized = true;
|
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() {
|
_connectGraph() {
|
||||||
if (!this.source || !this.audioContext) return;
|
if (!this.source || !this.audioContext) return;
|
||||||
|
|
@ -194,6 +199,13 @@ class AudioContextManager {
|
||||||
// Disconnect everything first
|
// Disconnect everything first
|
||||||
this.source.disconnect();
|
this.source.disconnect();
|
||||||
this.outputNode.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)
|
// Only disconnect destination from analyser to preserve other taps (like Butterchurn)
|
||||||
try {
|
try {
|
||||||
|
|
@ -202,15 +214,34 @@ class AudioContextManager {
|
||||||
// Ignore if not connected
|
// 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) {
|
if (this.isEQEnabled && this.filters.length > 0) {
|
||||||
// EQ enabled: source -> EQ filters -> output -> analyser -> destination
|
// EQ enabled: lastNode -> EQ filters -> output -> analyser -> destination
|
||||||
this.source.connect(this.filters[0]);
|
lastNode.connect(this.filters[0]);
|
||||||
this.outputNode.connect(this.analyser);
|
this.outputNode.connect(this.analyser);
|
||||||
this.analyser.connect(this.audioContext.destination);
|
this.analyser.connect(this.audioContext.destination);
|
||||||
console.log('[AudioContext] EQ connected');
|
console.log('[AudioContext] EQ connected');
|
||||||
} else {
|
} else {
|
||||||
// EQ disabled: source -> analyser -> destination
|
// EQ disabled: lastNode -> analyser -> destination
|
||||||
this.source.connect(this.analyser);
|
lastNode.connect(this.analyser);
|
||||||
this.analyser.connect(this.audioContext.destination);
|
this.analyser.connect(this.audioContext.destination);
|
||||||
console.log('[AudioContext] EQ bypassed');
|
console.log('[AudioContext] EQ bypassed');
|
||||||
}
|
}
|
||||||
|
|
@ -303,6 +334,27 @@ class AudioContextManager {
|
||||||
return this.isInitialized && this.isEQEnabled;
|
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
|
* Set gain for a specific band
|
||||||
*/
|
*/
|
||||||
|
|
@ -372,6 +424,7 @@ class AudioContextManager {
|
||||||
_loadSettings() {
|
_loadSettings() {
|
||||||
this.isEQEnabled = equalizerSettings.isEnabled();
|
this.isEQEnabled = equalizerSettings.isEnabled();
|
||||||
this.currentGains = equalizerSettings.getGains();
|
this.currentGains = equalizerSettings.getGains();
|
||||||
|
this.isMonoAudioEnabled = monoAudioSettings.isEnabled();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import {
|
||||||
homePageSettings,
|
homePageSettings,
|
||||||
sidebarSectionSettings,
|
sidebarSectionSettings,
|
||||||
fontSettings,
|
fontSettings,
|
||||||
|
monoAudioSettings,
|
||||||
} from './storage.js';
|
} from './storage.js';
|
||||||
import { audioContextManager, EQ_PRESETS } from './audio-context.js';
|
import { audioContextManager, EQ_PRESETS } from './audio-context.js';
|
||||||
import { getButterchurnPresets } from './visualizers/butterchurn.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
|
// 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 = {
|
export const sidebarSettings = {
|
||||||
STORAGE_KEY: 'monochrome-sidebar-collapsed',
|
STORAGE_KEY: 'monochrome-sidebar-collapsed',
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue