Bin
2025-12-16 9e0b2ba2c317b1a86212f24cbae3195ad1f3dbfa
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
import { HYBRID_LINEAR_DOWNSAMPLE_SPLIT } from "../constants";
 
/**
 * Downsample FFT data for the linear scale by hybrid pooling (average for low bins, max for high bins)
 * @param fftData
 * @param bins
 */
export function downsampleLinear(fftData: Float32Array, bins: number): Float32Array {
  const binCount = fftData.length;
  if (bins >= binCount) return fftData;
  const groupSize = Math.floor(binCount / bins);
  const result = new Float32Array(bins);
  const split = Math.floor(bins * HYBRID_LINEAR_DOWNSAMPLE_SPLIT);
  for (let i = 0; i < bins; i++) {
    if (i < split) {
      // Average for low bins
      let sum = 0;
      for (let j = 0; j < groupSize; j++) {
        sum += fftData[i * groupSize + j];
      }
      result[i] = sum / groupSize;
    } else {
      // Max for high bins
      let max = Number.NEGATIVE_INFINITY;
      for (let j = 0; j < groupSize; j++) {
        max = Math.max(max, fftData[i * groupSize + j]);
      }
      result[i] = max;
    }
  }
  return result;
}
 
/**
 * Downsample FFT data for log scale by hybrid pooling (average for low bins, max for high bins)
 * @param fftData
 * @param bins
 */
export function downsampleLog(fftData: Float32Array, bins: number): Float32Array {
  const binCount = fftData.length;
  if (bins >= binCount) return fftData;
  const result = new Float32Array(bins);
  const logMin = Math.log(1);
  const logMax = Math.log(binCount + 1);
 
  for (let i = 0; i < bins; i++) {
    let startF = Math.floor(Math.exp(logMin + (logMax - logMin) * (i / bins)) - 1);
    let endF = Math.floor(Math.exp(logMin + (logMax - logMin) * ((i + 1) / bins)) - 1);
    // Clamp to valid range
    startF = Math.max(0, startF);
    endF = Math.max(startF + 1, endF); // Ensure at least one bin
    let sum = 0;
    let max = Number.NEGATIVE_INFINITY;
    let count = 0;
    for (let j = startF; j < endF && j < binCount; j++) {
      sum += fftData[j];
      max = Math.max(max, fftData[j]);
      count++;
    }
    if (count === 0) {
      // For the first bin, use fftData[0]
      result[i] = fftData[0];
    } else {
      result[i] = i < bins * HYBRID_LINEAR_DOWNSAMPLE_SPLIT ? sum / count : max;
    }
  }
  return result;
}
 
/**
 * Downsample FFT data for Mel scale by averaging bins within a kernel
 * @param fftData
 * @param bins
 */
export function downsampleMel(fftData: Float32Array, bins: number): Float32Array {
  const binCount = fftData.length;
  if (bins >= binCount) return fftData;
  const kernelSize = Math.floor(binCount / bins);
  const result = new Float32Array(bins);
  for (let i = 0; i < bins; i++) {
    let sum = 0;
    let count = 0;
    // Center the kernel on the target bin
    const center = Math.floor(((i + 0.5) * binCount) / bins);
    const start = Math.max(0, center - Math.floor(kernelSize / 2));
    const end = Math.min(binCount, center + Math.ceil(kernelSize / 2));
    for (let j = start; j < end; j++) {
      sum += fftData[j];
      count++;
    }
    result[i] = count > 0 ? sum / count : 0;
  }
  return result;
}