Files
ytplayer/frontend/vendor/jamesdsp/controller.js
2026-10-07 19:23:35 +08:00

57 lines
2.5 KiB
JavaScript

import { JamesDSPNode } from './jamesdsp-node.js';
const presets = {
Flat: {},
'Bass boost': { 'bass.enabled': 1, 'bass.boost': 6, 'eq.enabled': 1, 'eq.band0': 3, 'eq.band1': 2, 'gain': -3 },
Vocal: { 'eq.enabled': 1, 'eq.band3': -2, 'eq.band5': 2, 'eq.band7': 3, 'eq.band9': 2, 'eq.band11': -1 },
'Headphone crossfeed': { 'crossfeed.enabled': 1, 'crossfeed.mode': 1 },
Loudness: { 'limiter.threshold': -1, 'gain': 2 },
};
export class JamesDSPController {
constructor() { this.context = null; this.node = null; this.source = null; this.element = null; this.sources = new WeakMap(); }
async enable(element, settings) {
const AC = window.AudioContext || window.webkitAudioContext;
if (!AC) throw new Error('Web Audio is not supported in this browser');
if (!this.context) this.context = new AC();
if (![44100, 48000].includes(this.context.sampleRate)) throw new Error(`JamesDSP supports 44.1 or 48 kHz; this device uses ${this.context.sampleRate} Hz`);
if (!this.node) {
this.node = await JamesDSPNode.create(this.context);
this.node.addEventListener('error', (event) => this.fault(event.detail));
}
if (this.element !== element) {
if (this.source) { this.source.disconnect(this.node); this.source.connect(this.context.destination); }
this.source = this.sources.get(element) || this.context.createMediaElementSource(element);
this.sources.set(element, this.source);
this.node.connect(this.source);
this.element = element;
}
await this.node.setBypass(false);
await this.apply(settings);
if (this.context.state === 'suspended') await this.context.resume();
}
async apply(settings) {
if (!this.node) return;
const preset = presets[settings.preset] || presets.Flat;
const params = { ...preset, ...(settings.params || {}) };
await this.node.setBypass(true);
await this.node.applyPreset(params);
await this.node.setBypass(false);
}
async update(name, value, settings) {
settings.params ||= {};
settings.params[name] = value;
await this.apply(settings);
}
async disable() {
if (this.node) await this.node.setBypass(true).catch(() => {});
}
fault(error) {
console.warn('[JamesDSP] Processing failed; routing media dry:', error);
try { this.source?.disconnect(this.node); this.source?.connect(this.context.destination); } catch {}
this.node?.destroy(); this.node = null; this.source = null; this.element = null;
this.onError?.(error || new Error('Audio processor failed'));
}
}
export const JAMESDSP_PRESETS = Object.keys(presets);