Tone.js/Tone/component/analysis/Analyser.ts

183 lines
4.4 KiB
TypeScript
Raw Normal View History

import {
InputNode,
OutputNode,
ToneAudioNode,
ToneAudioNodeOptions,
} from "../../core/context/ToneAudioNode.js";
import { NormalRange, PowerOfTwo } from "../../core/type/Units.js";
import { optionsFromArguments } from "../../core/util/Defaults.js";
import { Split } from "../channel/Split.js";
import { Gain } from "../../core/context/Gain.js";
import { assert, assertRange } from "../../core/util/Debug.js";
2019-05-23 18:00:49 +00:00
2019-09-12 19:53:30 +00:00
export type AnalyserType = "fft" | "waveform";
2019-05-23 18:00:49 +00:00
2019-09-12 19:53:30 +00:00
export interface AnalyserOptions extends ToneAudioNodeOptions {
2019-05-23 18:00:49 +00:00
size: PowerOfTwo;
type: AnalyserType;
smoothing: NormalRange;
channels: number;
2019-05-23 18:00:49 +00:00
}
/**
2019-08-21 20:00:44 +00:00
* 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
2019-05-23 18:00:49 +00:00
*/
export class Analyser extends ToneAudioNode<AnalyserOptions> {
2019-09-04 23:18:44 +00:00
readonly name: string = "Analyser";
2019-05-23 18:00:49 +00:00
readonly input: InputNode;
readonly output: OutputNode;
2019-05-23 18:00:49 +00:00
/**
2019-09-14 20:39:18 +00:00
* The analyser node.
2019-05-23 18:00:49 +00:00
*/
private _analysers: AnalyserNode[] = [];
/**
* Input and output are a gain node
*/
private _gain: Gain;
/**
* The channel splitter node
*/
private _split: Split;
2019-05-23 18:00:49 +00:00
/**
2019-09-14 20:39:18 +00:00
* The analysis type
2019-05-23 18:00:49 +00:00
*/
private _type!: AnalyserType;
/**
2019-09-14 20:39:18 +00:00
* The buffer that the FFT data is written to
2019-05-23 18:00:49 +00:00
*/
private _buffers: Float32Array[] = [];
2019-05-23 18:00:49 +00:00
2019-08-21 20:00:44 +00:00
/**
* @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.
*/
2019-05-23 18:00:49 +00:00
constructor(type?: AnalyserType, size?: number);
constructor(options?: Partial<AnalyserOptions>);
constructor() {
const options = optionsFromArguments(
Analyser.getDefaults(),
arguments,
["type", "size"]
);
super(options);
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);
}
2019-05-23 18:00:49 +00:00
// set the values initially
this.size = options.size;
this.type = options.type;
this.smoothing = options.smoothing;
2019-05-23 18:00:49 +00:00
}
static getDefaults(): AnalyserOptions {
return Object.assign(ToneAudioNode.getDefaults(), {
size: 1024,
smoothing: 0.8,
type: "fft" as AnalyserType,
channels: 1,
2019-05-23 18:00:49 +00:00
});
}
/**
2024-04-29 14:48:37 +00:00
* Run the analysis given the current settings. If {@link channels} = 1,
* it will return a Float32Array. If {@link channels} > 1, it will
* return an array of Float32Arrays where each index in the array
* represents the analysis done on a channel.
2019-05-23 18:00:49 +00:00
*/
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;
2019-05-23 18:00:49 +00:00
}
}
/**
2019-09-14 20:39:18 +00:00
* The size of analysis. This must be a power of two in the range 16 to 16384.
2019-05-23 18:00:49 +00:00
*/
get size(): PowerOfTwo {
return this._analysers[0].frequencyBinCount;
2019-05-23 18:00:49 +00:00
}
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
2024-04-29 14:48:37 +00:00
* separation is done using {@link Split}
*/
get channels(): number {
return this._analysers.length;
2019-05-23 18:00:49 +00:00
}
/**
2019-09-14 20:39:18 +00:00
* The analysis function returned by analyser.getValue(), either "fft" or "waveform".
2019-05-23 18:00:49 +00:00
*/
get type(): AnalyserType {
return this._type;
}
set type(type: AnalyserType) {
assert(
type === "waveform" || type === "fft",
`Analyser: invalid type: ${type}`
);
2019-05-23 18:00:49 +00:00
this._type = type;
}
/**
2019-09-14 20:39:18 +00:00
* 0 represents no time averaging with the last analysis frame.
2019-05-23 18:00:49 +00:00
*/
get smoothing(): NormalRange {
return this._analysers[0].smoothingTimeConstant;
2019-05-23 18:00:49 +00:00
}
set smoothing(val: NormalRange) {
this._analysers.forEach((a) => (a.smoothingTimeConstant = val));
2019-05-23 18:00:49 +00:00
}
/**
2019-09-14 20:39:18 +00:00
* Clean up.
2019-05-23 18:00:49 +00:00
*/
dispose(): this {
super.dispose();
this._analysers.forEach((a) => a.disconnect());
this._split.dispose();
this._gain.dispose();
2019-05-23 18:00:49 +00:00
return this;
}
}