Source: utils/VisuComponentMono.js

import BaseComponent from "./BaseComponent.js";


class VisuComponentMono extends BaseComponent {


  /** @summary VisuComponentMono is an abstraction for mono visualisation component. It must be inherited.
   * @author Arthur Beaulieu
   * @since 2020
   * @augments BaseComponent
   * @description <blockquote>Mono components inherit this class to benefit its node routing and canvas
   * configuration. It is meant to use a single canvas for mono or merged L/R audio channels. This class extends
   * BaseComponent to benefits all shared properties between visualisations.</blockquote>
   * @param {object} options - The visualizer root 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 **/
  constructor(options) {
    super();
    /** @private
     * @member {object} - Audio nodes from web audio API to manipulate data with */
    this._nodes = {
      source: null, // HTML audio element
      analyser: null // Analysis node
    };
    /** @private
     * @member {object} - The canvas to rendered mono data to */
    this._canvas = null;
    /** @private
     * @member {object} - The canvas associated context */
    this._ctx = null;
    // Construction sequence
    this._fillAttributes(options);
    this._buildUI();
    this._setAudioNodes();
    this._addEvents();
  }


  /** @method
   * @name _fillAttributes
   * @private
   * @override
   * @memberof VisuComponentMono
   * @author Arthur Beaulieu
   * @since 2020
   * @description <blockquote>Internal method to fill internal properties from options object sent to constructor.</blockquote>
   * @param {object} options - The visualizer root 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) {
    this._type = options.type;
    this._player = options.player;
    this._renderTo = options.renderTo;
    this._fftSize = options.fftSize;
    this._audioCtx = options.audioContext;
    this._inputNode = options.inputNode;
  }


  /** @method
   * @name _buildUI
   * @private
   * @override
   * @memberof VisuComponentMono
   * @author Arthur Beaulieu
   * @since 2020
   * @description <blockquote>Create and configure canvas then append it to given DOM element.</blockquote> **/
  _buildUI() {
    this._dom.container = document.createElement('DIV');
    this._dom.container.classList.add(`audio-${this._type}`);
    this._canvas = document.createElement('CANVAS');
    this._canvas.style.cssText = 'background-color:black;border:solid 1px #2c2c30;display:block;box-sizing:border-box;';
    this._ctx = this._canvas.getContext('2d');
    this._ctx.translate(0.5, 0.5);
    this._canvas.width = this._renderTo.offsetWidth - 2;
    this._canvas.height = this._renderTo.offsetHeight - 2;
    this._dom.container.appendChild(this._canvas);
    this._renderTo.appendChild(this._dom.container);
  }


  /** @method
   * @name _setAudioNodes
   * @private
   * @override
   * @memberof VisuComponentMono
   * @author Arthur Beaulieu
   * @since 2020
   * @description <blockquote>Build audio chain with source -> analyzer -> destination.</blockquote> **/
  _setAudioNodes() {
    let audioCtxSent = false;
    if (!this._audioCtx) {
      this._audioCtx = new AudioContext();
      this._nodes.source = this._audioCtx.createMediaElementSource(this._player);
    } else {
      audioCtxSent = true;
      this._nodes.source = this._inputNode;
    }

    this._nodes.analyser = this._audioCtx.createAnalyser();
    this._nodes.analyser.fftSize = this._fftSize;

    this._nodes.source.connect(this._nodes.analyser);

    if (!audioCtxSent) {
      this._nodes.analyser.connect(this._audioCtx.destination);
    }
  }


  /** @method
   * @name _onResize
   * @private
   * @override
   * @memberof VisuComponentMono
   * @author Arthur Beaulieu
   * @since 2020
   * @description <blockquote>On resize event callback.</blockquote> **/
  _onResize() {
    this._canvas.width = this._renderTo.offsetWidth - 2;
    this._canvas.height = this._renderTo.offsetHeight - 2;
  }


  /** @method
   * @name _clearCanvas
   * @private
   * @override
   * @memberof VisuComponentMono
   * @author Arthur Beaulieu
   * @since 2020
   * @description <blockquote>Clear component canvas context from its content.</blockquote> **/
  _clearCanvas() {
    this._canvas.getContext('2d').clearRect(0, 0, this._canvas.width, this._canvas.height);
  }


}


export default VisuComponentMono;