57 lines
2.5 KiB
JavaScript
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);
|