CinePlex/js/equalizer.js

130 lines
4.2 KiB
JavaScript

export class Equalizer {
constructor(audioElement, canvasElement) {
this.audioElement = audioElement;
this.canvas = canvasElement;
this.canvasCtx = this.canvas.getContext('2d');
this.FREQUENCIES = [60, 170, 310, 600, 1000, 3000, 6000, 12000, 14000, 16000];
this.PRESETS = {
flat: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
rock: [5, 4, -5, -8, -4, 4, 8, 9, 9, 9],
pop: [-2, -1, 0, 2, 4, 4, 2, 0, -1, -2],
jazz: [4, 3, 1, 2, -2, -2, 0, 1, 3, 4],
classical: [5, 4, 3, -2, -3, -5, -2, 2, 4, 5],
bass_boost:[9, 7, 4, 1, -2, -4, -4, -3, -2, -2]
};
this.audioCtx = null;
this.source = null;
this.preamp = null;
this.filters = [];
this.analyser = null;
this.animationFrameId = null;
}
init() {
try {
this.audioCtx = new (window.AudioContext || window.webkitAudioContext)();
this.source = this.audioCtx.createMediaElementSource(this.audioElement);
this.preamp = this.audioCtx.createGain();
this.filters = this.FREQUENCIES.map(freq => {
const filter = this.audioCtx.createBiquadFilter();
filter.type = 'peaking';
filter.frequency.value = freq;
filter.Q.value = 1.41;
filter.gain.value = 0;
return filter;
});
this.analyser = this.audioCtx.createAnalyser();
this.analyser.fftSize = 256;
// Connect nodes: source -> preamp -> filters -> analyser -> destination
this.source.connect(this.preamp);
let lastNode = this.preamp;
this.filters.forEach(filter => {
lastNode.connect(filter);
lastNode = filter;
});
lastNode.connect(this.analyser);
this.analyser.connect(this.audioCtx.destination);
this.drawVisualizer();
return true;
} catch (e) {
console.error("Error initializing Web Audio API:", e);
return false;
}
}
changeGain(bandIndex, value) {
if (this.filters[bandIndex]) {
this.filters[bandIndex].gain.value = value;
}
}
changePreamp(value) {
if (this.preamp) {
this.preamp.gain.value = Math.pow(10, value / 20);
}
}
applyPreset(name) {
const values = this.PRESETS[name];
if (!values) return;
const bandSliders = document.querySelectorAll('.band-slider');
values.forEach((val, i) => {
this.changeGain(i, val);
const slider = bandSliders[i];
if (slider) {
slider.value = val;
slider.nextElementSibling.textContent = `${val} dB`;
// GSAP animation for slider thumb
gsap.to(slider, {
duration: 0.4,
value: val,
ease: "power2.out"
});
}
});
}
drawVisualizer() {
this.animationFrameId = requestAnimationFrame(() => this.drawVisualizer());
if (!this.analyser) return;
const bufferLength = this.analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);
this.analyser.getByteFrequencyData(dataArray);
this.canvasCtx.clearRect(0, 0, this.canvas.width, this.canvas.height);
const barWidth = (this.canvas.width / bufferLength) * 2.5;
let x = 0;
for (let i = 0; i < bufferLength; i++) {
const barHeight = dataArray[i] / 2;
const r = barHeight + 25 * (i/bufferLength);
const g = 250 * (i/bufferLength);
const b = 50;
this.canvasCtx.fillStyle = `rgb(${r},${g},${b})`;
this.canvasCtx.fillRect(x, this.canvas.height - barHeight, barWidth, barHeight);
x += barWidth + 1;
}
}
destroy() {
if (this.animationFrameId) {
cancelAnimationFrame(this.animationFrameId);
}
if (this.audioCtx) {
this.audioCtx.close();
}
}
}