import VisuComponentStereo from '../utils/VisuComponentStereo.js';
import CanvasUtils from '../utils/CanvasUtils.js';
import ColorUtils from '../utils/ColorUtils.js';
class Oscilloscope extends VisuComponentStereo {
/** @summary Oscilloscope displays a merged or L/R oscilloscope in real time.
* @author Arthur Beaulieu
* @since 2020
* @augments VisuComponentStereo
* @description <blockquote>This will display a single/dual canvas with frequency displayed with.</blockquote>
* @param {object} options - The oscilloscope options
* @param {string} options.type - The component type as string
* @param {object} options.player - The player to take as processing input (if inputNode is given, player source will be ignored)
* @param {object} options.renderTo - The DOM element to render canvas in
* @param {number} options.fftSize - The FFT size for analysis. Must be a power of 2. High values may lead to heavy CPU cost
* @param {object} [options.audioContext=null] - The audio context to base analysis from
* @param {object} [options.inputNode=null] - The audio node to take source instead of player's one
* @param {boolean} [options.merged=false] - Merge left and right channel into one output
* @param {string} [options.colors] - The oscilloscope background and signal color
* @param {string} [options.colors.signal=ColorUtils.defaultPrimaryColor] - The signal color
* @param {string} [options.colors.background=ColorUtils.defaultPrimaryColor] - The background color **/
constructor(options) {
super(options);
// Define default oscillo color
this._colors = {
signal: ColorUtils.defaultPrimaryColor
}
// Save color sent by caller if any
if (options.colors && options.colors.signal) {
this._colors.signal = options.colors.signal;
}
// Update canvas CSS background color
const bgColor = (options.colors ? options.colors.background || ColorUtils.defaultBackgroundColor : ColorUtils.defaultBackgroundColor);
if (this._merged === true) {
this._canvas.style.backgroundColor = bgColor;
} else {
this._canvasL.style.backgroundColor = bgColor;
this._canvasR.style.backgroundColor = bgColor;
}
// Init oscilloscope dimensions
this._updateDimensions();
}
/* --------------------------------------------------------------------------------------------------------------- */
/* ------------------------------------- VISUCOMPONENTSTEREO OVERRIDES ---------------------------------------- */
/* --------------------------------------------------------------------------------------------------------------- */
/** @method
* @name _fillAttributes
* @private
* @override
* @memberof Oscilloscope
* @author Arthur Beaulieu
* @since 2020
* @description <blockquote>Internal method to fill internal properties from options object sent to constructor.</blockquote>
* @param {object} options - The oscilloscope options
* @param {string} options.type - The component type as string
* @param {object} options.player - The player to take as processing input (if inputNode is given, player source will be ignored)
* @param {object} options.renderTo - The DOM element to render canvas in
* @param {number} options.fftSize - The FFT size for analysis. Must be a power of 2. High values may lead to heavy CPU cost
* @param {object} [options.audioContext=null] - The audio context to base analysis from
* @param {object} [options.inputNode=null] - The audio node to take source instead of player's one **/
_fillAttributes(options) {
super._fillAttributes(options)
// Dimensions will be computed when canvas have been created
this._dimension = {
height: null,
canvasHeight: null,
width: null
};
}
/** @method
* @name _buildUI
* @private
* @override
* @memberof Oscilloscope
* @author Arthur Beaulieu
* @since 2020
* @description <blockquote>Create and configure canvas then append it to given DOM element.</blockquote> **/
_buildUI() {
super._buildUI();
if (this._merged === true) {
this._dom.container.removeChild(this._canvasR);
}
}
/** @method
* @name _onResize
* @private
* @override
* @memberof Oscilloscope
* @author Arthur Beaulieu
* @since 2020
* @description <blockquote>On resize event callback.</blockquote> **/
_onResize() {
super._onResize();
this._updateDimensions();
}
/** @method
* @name _processAudioBin
* @private
* @override
* @memberof Oscilloscope
* @author Arthur Beaulieu
* @since 2020
* @description <blockquote>Real time method called by WebAudioAPI to process PCM data. Here we make a 8 bit time
* analysis.</blockquote> **/
_processAudioBin() {
if (this._isPlaying === true) {
this._clearCanvas();
if (this._merged === true) {
this._mergedStereoAnalysis();
} else {
this._stereoAnalysis();
}
// Draw next frame
requestAnimationFrame(this._processAudioBin);
}
}
/* ---------- Oscilloscope internal methods ---------- */
/** @method
* @name _mergedStereoAnalysis
* @private
* @memberof Oscilloscope
* @author Arthur Beaulieu
* @since 2020
* @description <blockquote>Perform a merged Left and Right analysis with 8 bit time domain data.</blockquote> **/
_mergedStereoAnalysis() {
// Create TimeDomain array with frequency bin length
let timeDomain = new Uint8Array(this._nodes.analyser.frequencyBinCount);
// Left/Right channel
this._nodes.analyser.getByteTimeDomainData(timeDomain);
CanvasUtils.drawOscilloscope(this._canvasL, {
samples: this._nodes.analyser.frequencyBinCount,
timeDomain: timeDomain,
colors: this._colors.signal
});
}
/** @method
* @name _stereoAnalysis
* @private
* @memberof Oscilloscope
* @author Arthur Beaulieu
* @since 2020
* @description <blockquote>Perform a separated Left and Right analysis with 8 bit time domain data.</blockquote> **/
_stereoAnalysis() {
// Create TimeDomain array with freqency bin length
let timeDomain = new Uint8Array(this._nodes.analyserL.frequencyBinCount);
// Left channel
this._nodes.analyserL.getByteTimeDomainData(timeDomain);
CanvasUtils.drawOscilloscope(this._canvasL, {
samples: this._nodes.analyserL.frequencyBinCount,
timeDomain: timeDomain,
colors: this._colors.signal
});
// Right channel
this._nodes.analyserR.getByteTimeDomainData(timeDomain);
CanvasUtils.drawOscilloscope(this._canvasR, {
samples: this._nodes.analyserR.frequencyBinCount,
timeDomain: timeDomain,
colors: this._colors.signal
});
}
/** @method
* @name _updateDimensions
* @private
* @memberof Oscilloscope
* @author Arthur Beaulieu
* @since 2020
* @description <blockquote>Usually called on resize event, update canvas dimension to fit render to DOM object.</blockquote> **/
_updateDimensions() {
this._dimension.height = this._renderTo.offsetHeight - 4; // 2px borders times two channels
this._dimension.width = this._renderTo.offsetWidth - 2; // 2px borders
this._dimension.canvasHeight = this._dimension.height / 2;
if (this._merged === true) {
this._canvasL.width = this._dimension.width;
this._canvasL.height = this._dimension.canvasHeight * 2;
} else {
this._canvasL.width = this._dimension.width;
this._canvasL.height = this._dimension.canvasHeight;
this._canvasR.width = this._dimension.width;
this._canvasR.height = this._dimension.canvasHeight;
}
}
}
export default Oscilloscope;