/* * * * (c) 2009-2020 Øystein Moseng * * Code for sonifying single points. * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ 'use strict'; import H from '../../parts/Globals.js'; import U from '../../parts/Utilities.js'; var error = U.error, merge = U.merge, pick = U.pick; /** * 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} */ /** * 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} */ 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 }; /* eslint-disable no-invalid-this, valid-jsdoc */ /** * 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. * * @return {void} */ 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) { // 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); } // Fixed number or something else weird, just use that return value; }; // 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 = 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 { 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. * * @return {void} */ 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;