import BaseDisplay from './BaseDisplay';
import Fft from '../../common/operator/Fft';
import { getColors } from '../utils/display-utils';
const definitions = {
scale: {
type: 'float',
default: 1,
metas: { kind: 'dynamic' },
},
color: {
type: 'string',
default: getColors('spectrum'),
nullable: true,
metas: { kind: 'dynamic' },
},
min: {
type: 'float',
default: -80,
metas: { kind: 'dynamic' },
},
max: {
type: 'float',
default: 6,
metas: { kind: 'dynamic' },
}
};
/**
* Display the spectrum of the incomming `signal` input.
*
* @memberof module:client.sink
*
* @param {Object} options - Override default parameters.
* @param {Number} [options.scale=1] - Scale display of the spectrogram.
* @param {String} [options.color=null] - Color of the spectrogram.
* @param {Number} [options.min=-80] - Minimum displayed value (in dB).
* @param {Number} [options.max=6] - Maximum displayed value (in dB).
* @param {Number} [options.width=300] - Width of the canvas.
* _dynamic parameter_
* @param {Number} [options.height=150] - Height of the canvas.
* _dynamic parameter_
* @param {Element|CSSSelector} [options.container=null] - Container element
* in which to insert the canvas. _constant parameter_
* @param {Element|CSSSelector} [options.canvas=null] - Canvas element
* in which to draw. _constant parameter_
*
* @todo - expose more `fft` config options
*
* @example
* import * as lfo from 'waves-lfo/client';
*
* const audioContext = new AudioContext();
*
* navigator.mediaDevices
* .getUserMedia({ audio: true })
* .then(init)
* .catch((err) => console.error(err.stack));
*
* function init(stream) {
* const source = audioContext.createMediaStreamSource(stream);
*
* const audioInNode = new lfo.source.AudioInNode({
* audioContext: audioContext,
* sourceNode: source,
* });
*
* const spectrum = new lfo.sink.SpectrumDisplay({
* canvas: '#spectrum',
* });
*
* audioInNode.connect(spectrum);
* audioInNode.start();
* }
*/
class SpectrumDisplay extends BaseDisplay {
constructor(options = {}) {
super(definitions, options, false);
}
/** @private */
processStreamParams(prevStreamParams) {
this.prepareStreamParams(prevStreamParams);
this.fft = new Fft({
size: this.streamParams.frameSize,
window: 'hann',
norm: 'linear',
});
this.fft.initStream(this.streamParams);
this.propagateStreamParams();
}
/** @private */
processSignal(frame) {
const bins = this.fft.inputSignal(frame.data);
const nbrBins = bins.length;
const width = this.canvasWidth;
const height = this.canvasHeight;
const scale = this.params.get('scale');
const binWidth = width / nbrBins;
const ctx = this.ctx;
ctx.fillStyle = this.params.get('color');
// error handling needs review...
let error = 0;
for (let i = 0; i < nbrBins; i++) {
const x1Float = i * binWidth + error;
const x1Int = Math.round(x1Float);
const x2Float = x1Float + (binWidth - error);
const x2Int = Math.round(x2Float);
error = x2Int - x2Float;
if (x1Int !== x2Int) {
const width = x2Int - x1Int;
const db = 20 * Math.log10(bins[i]);
const y = this.getYPosition(db * scale);
ctx.fillRect(x1Int, y, width, height - y);
} else {
error -= binWidth;
}
}
}
}
export default SpectrumDisplay;