File size: 3,440 Bytes
27630e2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
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();
  }
}