Spaces:
Running
Running
export class Visualizer { | |
constructor(audioContext, canvasId) { | |
this.canvas = document.getElementById(canvasId); | |
this.ctx = this.canvas.getContext('2d'); | |
this.analyser = audioContext.createAnalyser(); | |
// Ensure fftSize is a valid power of two (16384 is the max standard value) | |
this.analyser.fftSize = 16384; | |
this.bufferLength = this.analyser.frequencyBinCount; | |
this.dataArray = new Uint8Array(this.bufferLength); | |
this.sampleRate = audioContext.sampleRate; | |
this.binWidth = this.sampleRate / this.analyser.fftSize; | |
// Adjusted frequency range | |
this.startBin = Math.floor(20 / this.binWidth); | |
this.endBin = Math.floor(200 / this.binWidth); | |
this.usableBins = this.endBin - this.startBin; | |
// Slightly reduced smoothing for faster response | |
this.analyser.smoothingTimeConstant = 0.45; // Reduced from 0.5 | |
// Peak tracking with slightly more dynamic range | |
this.peakLevels = new Array(this.usableBins).fill(0); | |
this.peakDecayRate = 0.85; // Slightly faster decay | |
this.dynamicPeakMultiplier = 1.25; // Increased from 1.2 | |
this.resize(); | |
window.addEventListener('resize', () => this.resize()); | |
} | |
resize() { | |
const dpr = window.devicePixelRatio || 1; | |
this.canvas.width = this.canvas.offsetWidth * dpr; | |
this.canvas.height = this.canvas.offsetHeight * dpr; | |
this.ctx.scale(dpr, dpr); | |
this.canvas.style.width = this.canvas.offsetWidth + 'px'; | |
this.canvas.style.height = this.canvas.offsetHeight + 'px'; | |
} | |
getAverageAmplitude() { | |
let sum = 0; | |
for (let i = this.startBin; i < this.endBin; i++) { | |
sum += this.dataArray[i]; | |
} | |
// Slightly increased sensitivity | |
return Math.min((sum / this.usableBins / 255) * 0.75, 1); | |
} | |
draw() { | |
this.analyser.getByteFrequencyData(this.dataArray); | |
const width = this.canvas.offsetWidth; | |
const height = this.canvas.offsetHeight; | |
// Slightly more transparent fade effect with a color closer to the background | |
this.ctx.fillStyle = 'rgba(31, 3, 107, 0.3)'; | |
this.ctx.fillRect(0, 0, width, height); | |
const barWidth = (width / this.usableBins) * 0.9; | |
const spacing = barWidth * 0.1; | |
let x = (width - (this.usableBins * (barWidth + spacing))) / 2; | |
for (let i = this.startBin; i < this.endBin; i++) { | |
const index = i - this.startBin; | |
const currentValue = this.dataArray[i]; | |
// More controlled peak tracking | |
this.peakLevels[index] = Math.max( | |
currentValue, | |
Math.min( | |
this.peakLevels[index] * this.peakDecayRate, | |
currentValue * this.dynamicPeakMultiplier | |
) | |
); | |
// Slightly reduced peak height scaling | |
const barHeight = Math.min( | |
(this.peakLevels[index] * 0.9) * (height / 255), | |
height | |
); | |
const gradient = this.ctx.createLinearGradient(0, height, 0, height - barHeight); | |
gradient.addColorStop(0, 'rgba(106, 74, 194, 0.8)'); // Matching the end color of background | |
gradient.addColorStop(1, 'rgba(31, 3, 107, 0.4)'); // Matching the start color of background | |
this.ctx.fillStyle = gradient; | |
this.ctx.beginPath(); | |
this.ctx.roundRect( | |
x, | |
height - barHeight, | |
barWidth, | |
barHeight, | |
3 | |
); | |
this.ctx.fill(); | |
x += barWidth + spacing; | |
} | |
return this.getAverageAmplitude(); | |
} | |
} |