view src/app/spectrogram/Spectrogram.ts @ 129:161af71c80d4

Module for spectrogram stuff. Consume the WavesSpectrogramLayer in Waveform... of course.
author Lucas Thompson <dev@lucas.im>
date Thu, 16 Mar 2017 10:35:53 +0000
parents
children 8aa1ff061503
line wrap: on
line source
/**
 * Created by lucast on 16/03/2017.
 */
import {RealFft, KissRealFft} from "piper/fft/RealFft";
import {hann} from "piper/FftUtilities";
import {Framing} from "piper";
import Waves from 'waves-ui';

class SpectrogramEntity extends Waves.utils.MatrixEntity {

  private samples: Float32Array;
  private framing: Framing;
  private fft: RealFft;
  private real: Float32Array;
  private nCols: number;
  private window: Float32Array;

  constructor(samples: Float32Array, options: Framing & Object) {
    super();
    this.samples = samples;
    this.framing = options;
    this.real = new Float32Array(this.framing.blockSize);
    this.nCols = Math.floor(this.samples.length / this.framing.stepSize); //!!! not correct
    this.fft = new KissRealFft(this.framing.blockSize);
    this.window = hann(this.framing.blockSize);
  }

  getColumnCount(): number {
    return this.nCols;
  }

  getColumnHeight(): number {
    return Math.floor(this.framing.blockSize * 0.5) + 1;
  }

  getColumn(n: number): Float32Array {

    const startSample = n * this.framing.stepSize;
    const sz = this.framing.blockSize;

    this.real.fill(0);

    let available = sz;
    if (startSample + sz >= this.samples.length) {
      available = this.samples.length - startSample;
    }

    for (let i = 0; i < available; ++i) {
      this.real[i] = this.samples[startSample + i] * this.window[i];
    }

    const complex = this.fft.forward(this.real);

    const h = this.getColumnHeight();
    const col = new Float32Array(h);

    for (let i = 0; i < h; ++i) {
      const real: number = complex[i * 2];
      const imaginary: number = complex[i * 2 + 1];
      col[i] = real * real + imaginary * imaginary;
    }

    return col;
  }
}

export class WavesSpectrogramLayer extends Waves.core.Layer {
  constructor(buffer: AudioBuffer,
              options: Framing & Object) {

    const defaults = {
      normalise: 'hybrid',
      gain: 40.0,
      channel: 0,
      stepSize: 512,
      blockSize: 1024
    };

    const mergedOptions: Framing & Object & {channel: number} =
      Object.assign({}, defaults, options);

    super('entity',
      new SpectrogramEntity(
        buffer.getChannelData(mergedOptions.channel),
        mergedOptions
      ),
      mergedOptions
    );

    this.configureShape(Waves.shapes.Matrix, {}, mergedOptions);
  }
}