130 lines
4.2 KiB
JavaScript
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();
|
|
}
|
|
}
|
|
} |