SorryToPerson logo
返回
算法2026-05-03·10 分钟

算法知识库:音频处理算法实现

JavaScript/TypeScript 实现音频处理基础算法,如傅里叶变换、滤波器、音频压缩等。

音频处理算法实现

1. 离散傅里叶变换 (DFT)

ts
function dft(signal: number[]): { real: number[]; imag: number[] } {
  const N = signal.length;
  const real = new Array(N).fill(0);
  const imag = new Array(N).fill(0);

  for (let k = 0; k < N; k += 1) {
    for (let n = 0; n < N; n += 1) {
      const angle = (-2 * Math.PI * k * n) / N;
      real[k] += signal[n] * Math.cos(angle);
      imag[k] += signal[n] * Math.sin(angle);
    }
  }

  return { real, imag };
}

function idft(real: number[], imag: number[]): number[] {
  const N = real.length;
  const signal = new Array(N).fill(0);

  for (let n = 0; n < N; n += 1) {
    for (let k = 0; k < N; k += 1) {
      const angle = (2 * Math.PI * k * n) / N;
      signal[n] += real[k] * Math.cos(angle) - imag[k] * Math.sin(angle);
    }
    signal[n] /= N;
  }

  return signal;
}

2. 快速傅里叶变换 (FFT) - Cooley-Tukey 算法

ts
function fft(signal: number[]): { real: number[]; imag: number[] } {
  const N = signal.length;

  if (N <= 1) {
    return { real: [signal[0] || 0], imag: [0] };
  }

  // 分离偶数和奇数索引
  const even = [];
  const odd = [];
  for (let i = 0; i < N; i += 2) {
    even.push(signal[i]);
    if (i + 1 < N) {
      odd.push(signal[i + 1]);
    }
  }

  const evenFFT = fft(even);
  const oddFFT = fft(odd);

  const real = new Array(N).fill(0);
  const imag = new Array(N).fill(0);

  for (let k = 0; k < N / 2; k += 1) {
    const angle = (-2 * Math.PI * k) / N;
    const cos = Math.cos(angle);
    const sin = Math.sin(angle);

    const evenReal = evenFFT.real[k];
    const evenImag = evenFFT.imag[k];
    const oddReal = oddFFT.real[k] || 0;
    const oddImag = oddFFT.imag[k] || 0;

    real[k] = evenReal + cos * oddReal - sin * oddImag;
    imag[k] = evenImag + cos * oddImag + sin * oddReal;

    real[k + N / 2] = evenReal - cos * oddReal + sin * oddImag;
    imag[k + N / 2] = evenImag - cos * oddImag - sin * oddReal;
  }

  return { real, imag };
}

function ifft(real: number[], imag: number[]): number[] {
  const N = real.length;
  const conjugatedImag = imag.map(x => -x);

  const result = fft(real.map((r, i) => r + conjugatedImag[i] * 1j || 0));
  // 简化版:实际需要处理复数
  return result.real.map(x => x / N);
}

3. 数字滤波器

ts
class DigitalFilter {
  private a: number[]; // 反馈系数
  private b: number[]; // 前馈系数
  private xHistory: number[]; // 输入历史
  private yHistory: number[]; // 输出历史

  constructor(b: number[], a: number[] = [1]) {
    this.b = b;
    this.a = a;
    this.xHistory = new Array(b.length).fill(0);
    this.yHistory = new Array(a.length - 1).fill(0);
  }

  process(input: number[]): number[] {
    const output: number[] = [];

    for (const x of input) {
      // 更新输入历史
      this.xHistory.unshift(x);
      this.xHistory.pop();

      // 计算输出
      let y = 0;

      // 前馈部分
      for (let i = 0; i < this.b.length; i += 1) {
        y += this.b[i] * this.xHistory[i];
      }

      // 反馈部分
      for (let i = 1; i < this.a.length; i += 1) {
        y -= this.a[i] * this.yHistory[i - 1];
      }

      // 更新输出历史
      this.yHistory.unshift(y);
      this.yHistory.pop();

      output.push(y);
    }

    return output;
  }

  // 低通滤波器设计
  static lowPassFilter(cutoffFreq: number, sampleRate: number, order: number = 2): DigitalFilter {
    // 简化版 Butterworth 滤波器系数
    const wc = (2 * Math.PI * cutoffFreq) / sampleRate;
    const k = Math.tan(wc / 2);
    const k2 = k * k;

    let b: number[];
    let a: number[];

    if (order === 1) {
      const norm = k + 1;
      b = [k / norm];
      a = [1, (k - 1) / norm];
    } else {
      // 二阶
      const sqrt2 = Math.sqrt(2);
      const norm = k2 + k * sqrt2 + 1;
      b = [k2 / norm, (2 * k2) / norm, k2 / norm];
      a = [1, (2 * (k2 - 1)) / norm, (k2 - k * sqrt2 + 1) / norm];
    }

    return new DigitalFilter(b, a);
  }
}

4. 音频压缩 (μ-law 压缩)

ts
function muLawCompress(sample: number, mu: number = 255): number {
  // 归一化到 [-1, 1]
  const normalized = Math.max(-1, Math.min(1, sample));

  // μ-law 压缩
  const sign = normalized < 0 ? -1 : 1;
  const absSample = Math.abs(normalized);

  let compressed: number;
  if (absSample < 1 / (1 + mu)) {
    compressed = (absSample * (1 + mu)) / mu;
  } else {
    compressed = 1 + Math.log(absSample * mu) / Math.log(1 + mu);
  }

  // 转换为 8 位量化
  const quantized = Math.round(compressed * 127);
  return sign * quantized;
}

function muLawExpand(compressed: number, mu: number = 255): number {
  const sign = compressed < 0 ? -1 : 1;
  const absCompressed = Math.abs(compressed) / 127;

  let expanded: number;
  if (absCompressed < 1 / (1 + mu)) {
    expanded = (absCompressed * mu) / (1 + mu);
  } else {
    expanded = Math.pow(1 + mu, absCompressed - 1) / mu;
  }

  return sign * expanded;
}

5. 频谱分析

ts
class SpectrumAnalyzer {
  private fftSize: number;

  constructor(fftSize: number = 2048) {
    this.fftSize = fftSize;
  }

  analyze(signal: number[]): { frequencies: number[]; magnitudes: number[]; phases: number[] } {
    // 应用汉宁窗
    const windowed = this.applyHanningWindow(signal);

    // 填充到 FFT 大小
    const padded = this.zeroPad(windowed, this.fftSize);

    // FFT
    const { real, imag } = fft(padded);

    // 计算幅度和相位
    const magnitudes: number[] = [];
    const phases: number[] = [];
    const frequencies: number[] = [];

    for (let i = 0; i < real.length / 2; i += 1) {
      const magnitude = Math.sqrt(real[i] ** 2 + imag[i] ** 2);
      const phase = Math.atan2(imag[i], real[i]);

      magnitudes.push(magnitude);
      phases.push(phase);
      frequencies.push((i * 44100) / real.length); // 假设采样率 44.1kHz
    }

    return { frequencies, magnitudes, phases };
  }

  private applyHanningWindow(signal: number[]): number[] {
    const windowed: number[] = [];
    for (let i = 0; i < signal.length; i += 1) {
      const window = 0.5 - 0.5 * Math.cos((2 * Math.PI * i) / (signal.length - 1));
      windowed.push(signal[i] * window);
    }
    return windowed;
  }

  private zeroPad(signal: number[], targetLength: number): number[] {
    const padded = [...signal];
    while (padded.length < targetLength) {
      padded.push(0);
    }
    return padded;
  }

  // 计算频谱质心
  static spectralCentroid(frequencies: number[], magnitudes: number[]): number {
    let numerator = 0;
    let denominator = 0;

    for (let i = 0; i < frequencies.length; i += 1) {
      numerator += frequencies[i] * magnitudes[i];
      denominator += magnitudes[i];
    }

    return denominator > 0 ? numerator / denominator : 0;
  }

  // 计算频谱滚降
  static spectralRolloff(frequencies: number[], magnitudes: number[], percentile: number = 0.85): number {
    const totalEnergy = magnitudes.reduce((sum, mag) => sum + mag ** 2, 0);
    const targetEnergy = totalEnergy * percentile;

    let cumulativeEnergy = 0;
    for (let i = 0; i < frequencies.length; i += 1) {
      cumulativeEnergy += magnitudes[i] ** 2;
      if (cumulativeEnergy >= targetEnergy) {
        return frequencies[i];
      }
    }

    return frequencies[frequencies.length - 1];
  }
}

6. 实现要点

  • DFT/FFT 将时域信号转换为频域。
  • 数字滤波器用于信号处理。
  • μ-law 压缩减少动态范围。
  • 频谱分析提取音频特征。
算法音频处理JavaScript