import { InputNode, OutputNode, ToneAudioNode, ToneAudioNodeOptions } from "../../core/context/ToneAudioNode"; import { NormalRange, PowerOfTwo } from "../../core/type/Units"; import { optionsFromArguments } from "../../core/util/Defaults"; import { Split } from "../channel/Split"; import { Gain } from "../../core/context/Gain"; import { assert, assertRange } from "../../core/util/Debug"; export type AnalyserType = "fft" | "waveform"; export interface AnalyserOptions extends ToneAudioNodeOptions { size: PowerOfTwo; type: AnalyserType; smoothing: NormalRange; channels: number; } /** * Wrapper around the native Web Audio's [AnalyserNode](http://webaudio.github.io/web-audio-api/#idl-def-AnalyserNode). * Extracts FFT or Waveform data from the incoming signal. * @category Component */ export class Analyser extends ToneAudioNode { readonly name: string = "Analyser"; readonly input: InputNode; readonly output: OutputNode; /** * The analyser node. */ private _analysers: AnalyserNode[] = []; /** * Input and output are a gain node */ private _gain: Gain; /** * The channel splitter node */ private _split: Split; /** * The analysis type */ private _type!: AnalyserType; /** * The buffer that the FFT data is written to */ private _buffers: Float32Array[] = []; /** * @param type The return type of the analysis, either "fft", or "waveform". * @param size The size of the FFT. This must be a power of two in the range 16 to 16384. */ constructor(type?: AnalyserType, size?: number); constructor(options?: Partial); constructor() { super(optionsFromArguments(Analyser.getDefaults(), arguments, ["type", "size"])); const options = optionsFromArguments(Analyser.getDefaults(), arguments, ["type", "size"]); this.input = this.output = this._gain = new Gain({ context: this.context }); this._split = new Split({ context: this.context, channels: options.channels, }); this.input.connect(this._split); assertRange(options.channels, 1); // create the analysers for (let channel = 0; channel < options.channels; channel++) { this._analysers[channel] = this.context.createAnalyser(); this._split.connect(this._analysers[channel], channel, 0); } // set the values initially this.size = options.size; this.type = options.type; this.smoothing = options.smoothing; } static getDefaults(): AnalyserOptions { return Object.assign(ToneAudioNode.getDefaults(), { size: 1024, smoothing: 0.8, type: "fft" as AnalyserType, channels: 1, }); } /** * Run the analysis given the current settings. If [[channels]] = 1, * it will return a Float32Array. If [[channels]] > 1, it will * return an array of Float32Arrays where each index in the array * represents the analysis done on a channel. */ getValue(): Float32Array | Float32Array[] { this._analysers.forEach((analyser, index) => { const buffer = this._buffers[index]; if (this._type === "fft") { analyser.getFloatFrequencyData(buffer); } else if (this._type === "waveform") { analyser.getFloatTimeDomainData(buffer); } }); if (this.channels === 1) { return this._buffers[0]; } else { return this._buffers; } } /** * The size of analysis. This must be a power of two in the range 16 to 16384. */ get size(): PowerOfTwo { return this._analysers[0].frequencyBinCount; } set size(size: PowerOfTwo) { this._analysers.forEach((analyser, index) => { analyser.fftSize = size * 2; this._buffers[index] = new Float32Array(size); }); } /** * The number of channels the analyser does the analysis on. Channel * separation is done using [[Split]] */ get channels(): number { return this._analysers.length; } /** * The analysis function returned by analyser.getValue(), either "fft" or "waveform". */ get type(): AnalyserType { return this._type; } set type(type: AnalyserType) { assert(type === "waveform" || type === "fft", `Analyser: invalid type: ${type}`); this._type = type; } /** * 0 represents no time averaging with the last analysis frame. */ get smoothing(): NormalRange { return this._analysers[0].smoothingTimeConstant; } set smoothing(val: NormalRange) { this._analysers.forEach(a => a.smoothingTimeConstant = val); } /** * Clean up. */ dispose(): this { super.dispose(); this._analysers.forEach(a => a.disconnect()); this._split.dispose(); this._gain.dispose(); return this; } }