text: /* *
   *
   *  (c) 2009-2019 Øystein Moseng
   *
   *  Code for sonifying single points.
   *
   *  License: www.highcharts.com/license
   *
   * */
  
  
  /**
   * Define the parameter mapping for an instrument.
   *
   * @requires module:modules/sonification
   *
   * @interface Highcharts.PointInstrumentMappingObject
   *//**
   * Define the volume of the instrument. This can be a string with a data
   * property name, e.g. `'y'`, in which case this data property is used to define
   * the volume relative to the `y`-values of the other points. A higher `y` value
   * would then result in a higher volume. This option can also be a fixed number
   * or a function. If it is a function, this function is called in regular
   * intervals while the note is playing. It receives three arguments: The point,
   * the dataExtremes, and the current relative time - where 0 is the beginning of
   * the note and 1 is the end. The function should return the volume of the note
   * as a number between 0 and 1.
   * @name Highcharts.PointInstrumentMappingObject#volume
   * @type {string|number|Function}
   *//**
   * Define the duration of the notes for this instrument. This can be a string
   * with a data property name, e.g. `'y'`, in which case this data property is
   * used to define the duration relative to the `y`-values of the other points. A
   * higher `y` value would then result in a longer duration. This option can also
   * be a fixed number or a function. If it is a function, this function is called
   * once before the note starts playing, and should return the duration in
   * milliseconds. It receives two arguments: The point, and the dataExtremes.
   * @name Highcharts.PointInstrumentMappingObject#duration
   * @type {string|number|Function}
   *//**
   * Define the panning of the instrument. This can be a string with a data
   * property name, e.g. `'x'`, in which case this data property is used to define
   * the panning relative to the `x`-values of the other points. A higher `x`
   * value would then result in a higher panning value (panned further to the
   * right). This option can also be a fixed number or a function. If it is a
   * function, this function is called in regular intervals while the note is
   * playing. It receives three arguments: The point, the dataExtremes, and the
   * current relative time - where 0 is the beginning of the note and 1 is the
   * end. The function should return the panning of the note as a number between
   * -1 and 1.
   * @name Highcharts.PointInstrumentMappingObject#pan
   * @type {string|number|Function|undefined}
   *//**
   * Define the frequency of the instrument. This can be a string with a data
   * property name, e.g. `'y'`, in which case this data property is used to define
   * the frequency relative to the `y`-values of the other points. A higher `y`
   * value would then result in a higher frequency. This option can also be a
   * fixed number or a function. If it is a function, this function is called in
   * regular intervals while the note is playing. It receives three arguments:
   * The point, the dataExtremes, and the current relative time - where 0 is the
   * beginning of the note and 1 is the end. The function should return the
   * frequency of the note as a number (in Hz).
   * @name Highcharts.PointInstrumentMappingObject#frequency
   * @type {string|number|Function}
   */
  
  
  /**
   * @requires module:modules/sonification
   *
   * @interface Highcharts.PointInstrumentOptionsObject
   *//**
   * The minimum duration for a note when using a data property for duration. Can
   * be overridden by using either a fixed number or a function for
   * instrumentMapping.duration. Defaults to 20.
   * @name Highcharts.PointInstrumentOptionsObject#minDuration
   * @type {number|undefined}
   *//**
   * The maximum duration for a note when using a data property for duration. Can
   * be overridden by using either a fixed number or a function for
   * instrumentMapping.duration. Defaults to 2000.
   * @name Highcharts.PointInstrumentOptionsObject#maxDuration
   * @type {number|undefined}
   *//**
   * The minimum pan value for a note when using a data property for panning. Can
   * be overridden by using either a fixed number or a function for
   * instrumentMapping.pan. Defaults to -1 (fully left).
   * @name Highcharts.PointInstrumentOptionsObject#minPan
   * @type {number|undefined}
   *//**
   * The maximum pan value for a note when using a data property for panning. Can
   * be overridden by using either a fixed number or a function for
   * instrumentMapping.pan. Defaults to 1 (fully right).
   * @name Highcharts.PointInstrumentOptionsObject#maxPan
   * @type {number|undefined}
   *//**
   * The minimum volume for a note when using a data property for volume. Can be
   * overridden by using either a fixed number or a function for
   * instrumentMapping.volume. Defaults to 0.1.
   * @name Highcharts.PointInstrumentOptionsObject#minVolume
   * @type {number|undefined}
   *//**
   * The maximum volume for a note when using a data property for volume. Can be
   * overridden by using either a fixed number or a function for
   * instrumentMapping.volume. Defaults to 1.
   * @name Highcharts.PointInstrumentOptionsObject#maxVolume
   * @type {number|undefined}
   *//**
   * The minimum frequency for a note when using a data property for frequency.
   * Can be overridden by using either a fixed number or a function for
   * instrumentMapping.frequency. Defaults to 220.
   * @name Highcharts.PointInstrumentOptionsObject#minFrequency
   * @type {number|undefined}
   *//**
   * The maximum frequency for a note when using a data property for frequency.
   * Can be overridden by using either a fixed number or a function for
   * instrumentMapping.frequency. Defaults to 2200.
   * @name Highcharts.PointInstrumentOptionsObject#maxFrequency
   * @type {number|undefined}
   */
  
  
  /**
   * An instrument definition for a point, specifying the instrument to play and
   * how to play it.
   *
   * @interface Highcharts.PointInstrumentObject
   *//**
   * An Instrument instance or the name of the instrument in the
   * Highcharts.sonification.instruments map.
   * @name Highcharts.PointInstrumentObject#instrument
   * @type {Highcharts.Instrument|string}
   *//**
   * Mapping of instrument parameters for this instrument.
   * @name Highcharts.PointInstrumentObject#instrumentMapping
   * @type {Highcharts.PointInstrumentMappingObject}
   *//**
   * Options for this instrument.
   * @name Highcharts.PointInstrumentObject#instrumentOptions
   * @type {Highcharts.PointInstrumentOptionsObject|undefined}
   *//**
   * Callback to call when the instrument has stopped playing.
   * @name Highcharts.PointInstrumentObject#onEnd
   * @type {Function|undefined}
   */
  
  
  /**
   * Options for sonifying a point.
   * @interface Highcharts.PointSonifyOptionsObject
   *//**
   * The instrument definitions for this point.
   * @name Highcharts.PointSonifyOptionsObject#instruments
   * @type {Array<Highcharts.PointInstrumentObject>}
   *//**
   * Optionally provide the minimum/maximum values for the points. If this is not
   * supplied, it is calculated from the points in the chart on demand. This
   * option is supplied in the following format, as a map of point data properties
   * to objects with min/max values:
   *  ```js
   *      dataExtremes: {
   *          y: {
   *              min: 0,
   *              max: 100
   *          },
   *          z: {
   *              min: -10,
   *              max: 10
   *          }
   *          // Properties used and not provided are calculated on demand
   *      }
   *  ```
   * @name Highcharts.PointSonifyOptionsObject#dataExtremes
   * @type {object|undefined}
   *//**
   * Callback called when the sonification has finished.
   * @name Highcharts.PointSonifyOptionsObject#onEnd
   * @type {Function|undefined}
   */
  
  
  'use strict';
  
  import H from '../../parts/Globals.js';
  import U from '../../parts/Utilities.js';
  var pick = U.pick;
  
  import utilities from './utilities.js';
  
  // Defaults for the instrument options
  // NOTE: Also change defaults in Highcharts.PointInstrumentOptionsObject if
  //       making changes here.
  var defaultInstrumentOptions = {
      minDuration: 20,
      maxDuration: 2000,
      minVolume: 0.1,
      maxVolume: 1,
      minPan: -1,
      maxPan: 1,
      minFrequency: 220,
      maxFrequency: 2200
  };
  
  
  /**
   * Sonify a single point.
   *
   * @sample highcharts/sonification/point-basic/
   *         Click on points to sonify
   * @sample highcharts/sonification/point-advanced/
   *         Sonify bubbles
   *
   * @requires module:modules/sonification
   *
   * @function Highcharts.Point#sonify
   *
   * @param {Highcharts.PointSonifyOptionsObject} options
   *        Options for the sonification of the point.
   */
  function pointSonify(options) {
      var point = this,
          chart = point.series.chart,
          dataExtremes = options.dataExtremes || {},
          // Get the value to pass to instrument.play from the mapping value
          // passed in.
          getMappingValue = function (
              value, makeFunction, allowedExtremes, allowedValues
          ) {
              // Fixed number, just use that
              if (typeof value === 'number' || value === undefined) {
                  return value;
              }
              // Function. Return new function if we try to use callback,
              // otherwise call it now and return result.
              if (typeof value === 'function') {
                  return makeFunction ?
                      function (time) {
                          return value(point, dataExtremes, time);
                      } :
                      value(point, dataExtremes);
              }
              // String, this is a data prop.
              if (typeof value === 'string') {
                  // Find data extremes if we don't have them
                  dataExtremes[value] = dataExtremes[value] ||
                      utilities.calculateDataExtremes(
                          point.series.chart, value
                      );
                  // Find the value
                  return utilities.virtualAxisTranslate(
                      pick(point[value], point.options[value]),
                      dataExtremes[value],
                      allowedExtremes,
                      allowedValues
                  );
              }
          };
  
      // Register playing point on chart
      chart.sonification.currentlyPlayingPoint = point;
  
      // Keep track of instruments playing
      point.sonification = point.sonification || {};
      point.sonification.instrumentsPlaying =
          point.sonification.instrumentsPlaying || {};
  
      // Register signal handler for the point
      var signalHandler = point.sonification.signalHandler =
          point.sonification.signalHandler ||
          new utilities.SignalHandler(['onEnd']);
  
      signalHandler.clearSignalCallbacks();
      signalHandler.registerSignalCallbacks({ onEnd: options.onEnd });
  
      // If we have a null point or invisible point, just return
      if (point.isNull || !point.visible || !point.series.visible) {
          signalHandler.emitSignal('onEnd');
          return;
      }
  
      // Go through instruments and play them
      options.instruments.forEach(function (instrumentDefinition) {
          var instrument = typeof instrumentDefinition.instrument === 'string' ?
                  H.sonification.instruments[instrumentDefinition.instrument] :
                  instrumentDefinition.instrument,
              mapping = instrumentDefinition.instrumentMapping || {},
              extremes = H.merge(
                  defaultInstrumentOptions,
                  instrumentDefinition.instrumentOptions
              ),
              id = instrument.id,
              onEnd = function (cancelled) {
                  // Instrument on end
                  if (instrumentDefinition.onEnd) {
                      instrumentDefinition.onEnd.apply(this, arguments);
                  }
  
                  // Remove currently playing point reference on chart
                  if (
                      chart.sonification &&
                      chart.sonification.currentlyPlayingPoint
                  ) {
                      delete chart.sonification.currentlyPlayingPoint;
                  }
  
                  // Remove reference from instruments playing
                  if (
                      point.sonification && point.sonification.instrumentsPlaying
                  ) {
                      delete point.sonification.instrumentsPlaying[id];
  
                      // This was the last instrument?
                      if (
                          !Object.keys(
                              point.sonification.instrumentsPlaying
                          ).length
                      ) {
                          signalHandler.emitSignal('onEnd', cancelled);
                      }
                  }
              };
  
          // Play the note on the instrument
          if (instrument && instrument.play) {
              point.sonification.instrumentsPlaying[instrument.id] = instrument;
              instrument.play({
                  frequency: getMappingValue(
                      mapping.frequency,
                      true,
                      { min: extremes.minFrequency, max: extremes.maxFrequency }
                  ),
                  duration: getMappingValue(
                      mapping.duration,
                      false,
                      { min: extremes.minDuration, max: extremes.maxDuration }
                  ),
                  pan: getMappingValue(
                      mapping.pan,
                      true,
                      { min: extremes.minPan, max: extremes.maxPan }
                  ),
                  volume: getMappingValue(
                      mapping.volume,
                      true,
                      { min: extremes.minVolume, max: extremes.maxVolume }
                  ),
                  onEnd: onEnd,
                  minFrequency: extremes.minFrequency,
                  maxFrequency: extremes.maxFrequency
              });
          } else {
              H.error(30);
          }
      });
  }
  
  
  /**
   * Cancel sonification of a point. Calls onEnd functions.
   *
   * @requires module:modules/sonification
   *
   * @function Highcharts.Point#cancelSonify
   *
   * @param {boolean} [fadeOut=false]
   *        Whether or not to fade out as we stop. If false, the points are
   *        cancelled synchronously.
   */
  function pointCancelSonify(fadeOut) {
      var playing = this.sonification && this.sonification.instrumentsPlaying,
          instrIds = playing && Object.keys(playing);
  
      if (instrIds && instrIds.length) {
          instrIds.forEach(function (instr) {
              playing[instr].stop(!fadeOut, null, 'cancelled');
          });
          this.sonification.instrumentsPlaying = {};
          this.sonification.signalHandler.emitSignal('onEnd', 'cancelled');
      }
  }
  
  
  var pointSonifyFunctions = {
      pointSonify: pointSonify,
      pointCancelSonify: pointCancelSonify
  };
  
  export default pointSonifyFunctions;
  