widgets/eqband.js

/*
 * This file is part of AUX.
 *
 * AUX is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * AUX is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General
 * Public License along with this program; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA  02110-1301  USA
 */

import { Filter } from '../modules/filter.js';
import { ChartHandle } from './charthandle.js';
import { addClass, toggleClass } from '../utils/dom.js';
import { warn } from '../utils/log.js';
import { defineRender } from '../renderer.js';

const type_to_mode = {
  parametric: 'circular',
  notch: 'line-vertical',
  lowpass1: 'block-right',
  lowpass2: 'block-right',
  lowpass3: 'block-right',
  lowpass4: 'block-right',
  highpass1: 'block-left',
  highpass2: 'block-left',
  highpass3: 'block-left',
  highpass4: 'block-left',
  'low-shelf': 'line-vertical',
  'high-shelf': 'line-vertical',
  null: 'circular',
};

const type_to_pref = {
  parametric: ['left', 'top', 'right', 'bottom'],
  notch: ['left', 'right', 'top', 'bottom'],
  lowpass1: [
    'left',
    'top-left',
    'bottom-left',
    'right',
    'top-right',
    'bottom-right',
    'top',
    'bottom',
  ],
  lowpass2: [
    'left',
    'top-left',
    'bottom-left',
    'right',
    'top-right',
    'bottom-right',
    'top',
    'bottom',
  ],
  lowpass3: [
    'left',
    'top-left',
    'bottom-left',
    'right',
    'top-right',
    'bottom-right',
    'top',
    'bottom',
  ],
  lowpass4: [
    'left',
    'top-left',
    'bottom-left',
    'right',
    'top-right',
    'bottom-right',
    'top',
    'bottom',
  ],
  highpass1: [
    'right',
    'top-right',
    'bottom-right',
    'left',
    'top-left',
    'bottom-left',
    'top',
    'bottom',
  ],
  highpass2: [
    'right',
    'top-right',
    'bottom-right',
    'left',
    'top-left',
    'bottom-left',
    'top',
    'bottom',
  ],
  highpass3: [
    'right',
    'top-right',
    'bottom-right',
    'left',
    'top-left',
    'bottom-left',
    'top',
    'bottom',
  ],
  highpass4: [
    'right',
    'top-right',
    'bottom-right',
    'left',
    'top-left',
    'bottom-left',
    'top',
    'bottom',
  ],
  'low-shelf': ['left', 'right', 'top', 'bottom'],
  'high-shelf': ['left', 'right', 'top', 'bottom'],
  null: ['left'],
};

/**
 * An EqBand extends a {@link ChartHandle} and holds a
 * dependent {@link Filter}. It is used as a fully functional representation
 * of a single equalizer band in {@link Equalizer} EqBand needs a {@link Chart}
 * or any other derivate to be drawn in.
 *
 * @class EqBand
 *
 * @param {Object} [options={ }] - An object containing initial options.
 *
 * @property {String|Function} [options.type="parametric"] - The type of the filter.
 *   Possible values are <code>parametric</code>, <code>notch</code>,
 *   <code>low-shelf</code>, <code>high-shelf</code>, <code>lowpass[n]</code> or
 *   <code>highpass[n]</code>.
 * @property {Number} options.freq - Frequency setting. This is an alias for the option <code>x</code>
 *   defined by {@link ChartHandle}.
 * @property {Number} options.gain - Gain setting. This is an alias for the option <code>y</code>
 *   defined by {@link ChartHandle}.
 * @property {Number} options.q - Quality setting. This is an alias for the option <code>z</code>
 *   defined by {@link ChartHandle}.
 * @property {Boolean} [options.active=true] - Set to `false` to not take this filter into account when drawing the response graph.
 *
 * @extends ChartHandle
 */
export class EqBand extends ChartHandle {
  static get _options() {
    return {
      type: 'string|function',
      gain: 'number',
      freq: 'number',
      x: 'number',
      y: 'number',
      z: 'number',
      q: 'number',
      active: 'boolean',
    };
  }

  static get options() {
    return {
      type: 'parametric',
      active: true,
    };
  }

  static get static_events() {
    return {
      set_freq: function (v) {
        this.update('x', v);
      },
      set_gain: function (v) {
        this.update('y', v);
      },
      set_q: function (v) {
        this.update('z', v);
      },
      userset: function (k, v) {
        switch (k) {
          case 'x':
            this.userset('freq', v);
            return false;
          case 'y':
            this.userset('gain', v);
            return false;
          case 'z':
            this.userset('q', v);
            return false;
        }
      },
    };
  }

  static get renderer() {
    return [
      defineRender('active', function (active) {
        toggleClass(this.element, 'aux-inactive', !active);
      }),
    ];
  }

  initialize(options) {
    /**
     * @member {Filter} EqBand#filter - The filter providing the graphical calculations.
     */
    super.initialize(options);

    this.filter = new Filter({
      type: this.get('type'),
    });

    const mode = this.get('mode');
    this.set('type', this.get('type'));
    if (mode) this.set('mode', this.get('mode'));

    /**
     * @member {HTMLDivElement} EqBand#element - The main SVG group.
     *   Has class <code>.aux-eqband</code>.
     */
    ['freq', 'gain', 'q'].forEach((name) => {
      if (name in options) {
        this.set(name, options[name]);
      }
    });
    this.filter.reset();
  }

  draw(O, element) {
    addClass(element, 'aux-eqband');

    super.draw(O, element);
  }

  /**
   * Calculate the gain for a given frequency in Hz.
   *
   * @method EqBand#frequencyToGain
   *
   * @param {number} freq - The frequency.
   *
   * @returns {number} The gain at the given frequency.
   */
  frequencyToGain(freq) {
    return this.filter.getFrequencyToGain()(freq);
  }

  // GETTER & SETTER
  set(key, value) {
    switch (key) {
      case 'type':
        if (typeof value === 'string') {
          const mode = type_to_mode[value];
          const pref = type_to_pref[value];
          if (!mode) {
            warn('Unsupported type:', value);
            return;
          }
          this.set('mode', mode);
          this.set('preferences', pref);
          this.set('show_axis', mode === 'line-vertical');
        }
        this.filter.set('type', value);
        break;
      case 'freq':
      case 'gain':
      case 'q':
        value = this.filter.set(key, value);
        break;
    }
    return super.set(key, value);
  }
}