UNPKG

highcharts

Version:
1,166 lines (1,161 loc) 132 kB
/** * @license Highcharts JS v8.0.0 (2019-12-10) * * Sonification module * * (c) 2012-2019 Øystein Moseng * * License: www.highcharts.com/license */ 'use strict'; (function (factory) { if (typeof module === 'object' && module.exports) { factory['default'] = factory; module.exports = factory; } else if (typeof define === 'function' && define.amd) { define('highcharts/modules/sonification', ['highcharts'], function (Highcharts) { factory(Highcharts); factory.Highcharts = Highcharts; return factory; }); } else { factory(typeof Highcharts !== 'undefined' ? Highcharts : undefined); } }(function (Highcharts) { var _modules = Highcharts ? Highcharts._modules : {}; function _registerModule(obj, path, args, fn) { if (!obj.hasOwnProperty(path)) { obj[path] = fn.apply(null, args); } } _registerModule(_modules, 'modules/sonification/Instrument.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js']], function (H, U) { /* * * * (c) 2009-2019 Øystein Moseng * * Instrument class for sonification module. * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var pick = U.pick; /** * A set of options for the Instrument class. * * @requires module:modules/sonification * * @interface Highcharts.InstrumentOptionsObject */ /** * The type of instrument. Currently only `oscillator` is supported. Defaults * to `oscillator`. * @name Highcharts.InstrumentOptionsObject#type * @type {string|undefined} */ /** * The unique ID of the instrument. Generated if not supplied. * @name Highcharts.InstrumentOptionsObject#id * @type {string|undefined} */ /** * When using functions to determine frequency or other parameters during * playback, this options specifies how often to call the callback functions. * Number given in milliseconds. Defaults to 20. * @name Highcharts.InstrumentOptionsObject#playCallbackInterval * @type {number|undefined} */ /** * A list of allowed frequencies for this instrument. If trying to play a * frequency not on this list, the closest frequency will be used. Set to `null` * to allow all frequencies to be used. Defaults to `null`. * @name Highcharts.InstrumentOptionsObject#allowedFrequencies * @type {Array<number>|undefined} */ /** * Options specific to oscillator instruments. * @name Highcharts.InstrumentOptionsObject#oscillator * @type {Highcharts.OscillatorOptionsObject|undefined} */ /** * Options for playing an instrument. * * @requires module:modules/sonification * * @interface Highcharts.InstrumentPlayOptionsObject */ /** * The frequency of the note to play. Can be a fixed number, or a function. The * function receives one argument: the relative time of the note playing (0 * being the start, and 1 being the end of the note). It should return the * frequency number for each point in time. The poll interval of this function * is specified by the Instrument.playCallbackInterval option. * @name Highcharts.InstrumentPlayOptionsObject#frequency * @type {number|Function} */ /** * The duration of the note in milliseconds. * @name Highcharts.InstrumentPlayOptionsObject#duration * @type {number} */ /** * The minimum frequency to allow. If the instrument has a set of allowed * frequencies, the closest frequency is used by default. Use this option to * stop too low frequencies from being used. * @name Highcharts.InstrumentPlayOptionsObject#minFrequency * @type {number|undefined} */ /** * The maximum frequency to allow. If the instrument has a set of allowed * frequencies, the closest frequency is used by default. Use this option to * stop too high frequencies from being used. * @name Highcharts.InstrumentPlayOptionsObject#maxFrequency * @type {number|undefined} */ /** * The volume of the instrument. Can be a fixed number between 0 and 1, or a * function. The function receives one argument: the relative time of the note * playing (0 being the start, and 1 being the end of the note). It should * return the volume for each point in time. The poll interval of this function * is specified by the Instrument.playCallbackInterval option. Defaults to 1. * @name Highcharts.InstrumentPlayOptionsObject#volume * @type {number|Function|undefined} */ /** * The panning of the instrument. Can be a fixed number between -1 and 1, or a * function. The function receives one argument: the relative time of the note * playing (0 being the start, and 1 being the end of the note). It should * return the panning value for each point in time. The poll interval of this * function is specified by the Instrument.playCallbackInterval option. * Defaults to 0. * @name Highcharts.InstrumentPlayOptionsObject#pan * @type {number|Function|undefined} */ /** * Callback function to be called when the play is completed. * @name Highcharts.InstrumentPlayOptionsObject#onEnd * @type {Function|undefined} */ /** * @requires module:modules/sonification * * @interface Highcharts.OscillatorOptionsObject */ /** * The waveform shape to use for oscillator instruments. Defaults to `sine`. * @name Highcharts.OscillatorOptionsObject#waveformShape * @type {string|undefined} */ // Default options for Instrument constructor var defaultOptions = { type: 'oscillator', playCallbackInterval: 20, oscillator: { waveformShape: 'sine' } }; /* eslint-disable no-invalid-this, valid-jsdoc */ /** * The Instrument class. Instrument objects represent an instrument capable of * playing a certain pitch for a specified duration. * * @sample highcharts/sonification/instrument/ * Using Instruments directly * @sample highcharts/sonification/instrument-advanced/ * Using callbacks for instrument parameters * * @requires module:modules/sonification * * @class * @name Highcharts.Instrument * * @param {Highcharts.InstrumentOptionsObject} options * Options for the instrument instance. */ function Instrument(options) { this.init(options); } Instrument.prototype.init = function (options) { if (!this.initAudioContext()) { H.error(29); return; } this.options = H.merge(defaultOptions, options); this.id = this.options.id = options && options.id || H.uniqueKey(); // Init the audio nodes var ctx = H.audioContext; this.gainNode = ctx.createGain(); this.setGain(0); this.panNode = ctx.createStereoPanner && ctx.createStereoPanner(); if (this.panNode) { this.setPan(0); this.gainNode.connect(this.panNode); this.panNode.connect(ctx.destination); } else { this.gainNode.connect(ctx.destination); } // Oscillator initialization if (this.options.type === 'oscillator') { this.initOscillator(this.options.oscillator); } // Init timer list this.playCallbackTimers = []; }; /** * Return a copy of an instrument. Only one instrument instance can play at a * time, so use this to get a new copy of the instrument that can play alongside * it. The new instrument copy will receive a new ID unless one is supplied in * options. * * @function Highcharts.Instrument#copy * * @param {Highcharts.InstrumentOptionsObject} [options] * Options to merge in for the copy. * * @return {Highcharts.Instrument} * A new Instrument instance with the same options. */ Instrument.prototype.copy = function (options) { return new Instrument(H.merge(this.options, { id: null }, options)); }; /** * Init the audio context, if we do not have one. * @private * @return {boolean} True if successful, false if not. */ Instrument.prototype.initAudioContext = function () { var Context = H.win.AudioContext || H.win.webkitAudioContext, hasOldContext = !!H.audioContext; if (Context) { H.audioContext = H.audioContext || new Context(); if (!hasOldContext && H.audioContext && H.audioContext.state === 'running') { H.audioContext.suspend(); // Pause until we need it } return !!(H.audioContext && H.audioContext.createOscillator && H.audioContext.createGain); } return false; }; /** * Init an oscillator instrument. * @private * @param {Highcharts.OscillatorOptionsObject} oscillatorOptions * The oscillator options passed to Highcharts.Instrument#init. * @return {void} */ Instrument.prototype.initOscillator = function (options) { var ctx = H.audioContext; this.oscillator = ctx.createOscillator(); this.oscillator.type = options.waveformShape; this.oscillator.connect(this.gainNode); this.oscillatorStarted = false; }; /** * Set pan position. * @private * @param {number} panValue * The pan position to set for the instrument. * @return {void} */ Instrument.prototype.setPan = function (panValue) { if (this.panNode) { this.panNode.pan.setValueAtTime(panValue, H.audioContext.currentTime); } }; /** * Set gain level. A maximum of 1.2 is allowed before we emit a warning. The * actual volume is not set above this level regardless of input. * @private * @param {number} gainValue * The gain level to set for the instrument. * @param {number} [rampTime=0] * Gradually change the gain level, time given in milliseconds. * @return {void} */ Instrument.prototype.setGain = function (gainValue, rampTime) { if (this.gainNode) { if (gainValue > 1.2) { console.warn(// eslint-disable-line 'Highcharts sonification warning: ' + 'Volume of instrument set too high.'); gainValue = 1.2; } if (rampTime) { this.gainNode.gain.setValueAtTime(this.gainNode.gain.value, H.audioContext.currentTime); this.gainNode.gain.linearRampToValueAtTime(gainValue, H.audioContext.currentTime + rampTime / 1000); } else { this.gainNode.gain.setValueAtTime(gainValue, H.audioContext.currentTime); } } }; /** * Cancel ongoing gain ramps. * @private * @return {void} */ Instrument.prototype.cancelGainRamp = function () { if (this.gainNode) { this.gainNode.gain.cancelScheduledValues(0); } }; /** * Get the closest valid frequency for this instrument. * @private * @param {number} frequency - The target frequency. * @param {number} [min] - Minimum frequency to return. * @param {number} [max] - Maximum frequency to return. * @return {number} The closest valid frequency to the input frequency. */ Instrument.prototype.getValidFrequency = function (frequency, min, max) { var validFrequencies = this.options.allowedFrequencies, maximum = pick(max, Infinity), minimum = pick(min, -Infinity); return !validFrequencies || !validFrequencies.length ? // No valid frequencies for this instrument, return the target frequency : // Use the valid frequencies and return the closest match validFrequencies.reduce(function (acc, cur) { // Find the closest allowed value return Math.abs(cur - frequency) < Math.abs(acc - frequency) && cur < maximum && cur > minimum ? cur : acc; }, Infinity); }; /** * Clear existing play callback timers. * @private * @return {void} */ Instrument.prototype.clearPlayCallbackTimers = function () { this.playCallbackTimers.forEach(function (timer) { clearInterval(timer); }); this.playCallbackTimers = []; }; /** * Set the current frequency being played by the instrument. The closest valid * frequency between the frequency limits is used. * @param {number} frequency * The frequency to set. * @param {Highcharts.Dictionary<number>} [frequencyLimits] * Object with maxFrequency and minFrequency * @return {void} */ Instrument.prototype.setFrequency = function (frequency, frequencyLimits) { var limits = frequencyLimits || {}, validFrequency = this.getValidFrequency(frequency, limits.min, limits.max); if (this.options.type === 'oscillator') { this.oscillatorPlay(validFrequency); } }; /** * Play oscillator instrument. * @private * @param {number} frequency - The frequency to play. */ Instrument.prototype.oscillatorPlay = function (frequency) { if (!this.oscillatorStarted) { this.oscillator.start(); this.oscillatorStarted = true; } this.oscillator.frequency.setValueAtTime(frequency, H.audioContext.currentTime); }; /** * Prepare instrument before playing. Resumes the audio context and starts the * oscillator. * @private */ Instrument.prototype.preparePlay = function () { this.setGain(0.001); if (H.audioContext.state === 'suspended') { H.audioContext.resume(); } if (this.oscillator && !this.oscillatorStarted) { this.oscillator.start(); this.oscillatorStarted = true; } }; /** * Play the instrument according to options. * * @sample highcharts/sonification/instrument/ * Using Instruments directly * @sample highcharts/sonification/instrument-advanced/ * Using callbacks for instrument parameters * * @function Highcharts.Instrument#play * * @param {Highcharts.InstrumentPlayOptionsObject} options * Options for the playback of the instrument. * * @return {void} */ Instrument.prototype.play = function (options) { var instrument = this, duration = options.duration || 0, // Set a value, or if it is a function, set it continously as a timer. // Pass in the value/function to set, the setter function, and any // additional data to pass through to the setter function. setOrStartTimer = function (value, setter, setterData) { var target = options.duration, currentDurationIx = 0, callbackInterval = instrument.options.playCallbackInterval; if (typeof value === 'function') { var timer = setInterval(function () { currentDurationIx++; var curTime = (currentDurationIx * callbackInterval / target); if (curTime >= 1) { instrument[setter](value(1), setterData); clearInterval(timer); } else { instrument[setter](value(curTime), setterData); } }, callbackInterval); instrument.playCallbackTimers.push(timer); } else { instrument[setter](value, setterData); } }; if (!instrument.id) { // No audio support - do nothing return; } // If the AudioContext is suspended we have to resume it before playing if (H.audioContext.state === 'suspended' || this.oscillator && !this.oscillatorStarted) { instrument.preparePlay(); // Try again in 10ms setTimeout(function () { instrument.play(options); }, 10); return; } // Clear any existing play timers if (instrument.playCallbackTimers.length) { instrument.clearPlayCallbackTimers(); } // Clear any gain ramps instrument.cancelGainRamp(); // Clear stop oscillator timer if (instrument.stopOscillatorTimeout) { clearTimeout(instrument.stopOscillatorTimeout); delete instrument.stopOscillatorTimeout; } // If a note is playing right now, clear the stop timeout, and call the // callback. if (instrument.stopTimeout) { clearTimeout(instrument.stopTimeout); delete instrument.stopTimeout; if (instrument.stopCallback) { // We have a callback for the play we are interrupting. We do not // allow this callback to start a new play, because that leads to // chaos. We pass in 'cancelled' to indicate that this note did not // finish, but still stopped. instrument._play = instrument.play; instrument.play = function () { }; instrument.stopCallback('cancelled'); instrument.play = instrument._play; } } // Stop the note without fadeOut if the duration is too short to hear the // note otherwise. var immediate = duration < H.sonification.fadeOutDuration + 20; // Stop the instrument after the duration of the note instrument.stopCallback = options.onEnd; var onStop = function () { delete instrument.stopTimeout; instrument.stop(immediate); }; if (duration) { instrument.stopTimeout = setTimeout(onStop, immediate ? duration : duration - H.sonification.fadeOutDuration); // Play the note setOrStartTimer(options.frequency, 'setFrequency', { minFrequency: options.minFrequency, maxFrequency: options.maxFrequency }); // Set the volume and panning setOrStartTimer(pick(options.volume, 1), 'setGain', 4); // Slight ramp setOrStartTimer(pick(options.pan, 0), 'setPan'); } else { // No note duration, so just stop immediately onStop(); } }; /** * Mute an instrument that is playing. If the instrument is not currently * playing, this function does nothing. * * @function Highcharts.Instrument#mute */ Instrument.prototype.mute = function () { this.setGain(0.0001, H.sonification.fadeOutDuration * 0.8); }; /** * Stop the instrument playing. * * @function Highcharts.Instrument#stop * * @param {boolean} immediately * Whether to do the stop immediately or fade out. * * @param {Function} [onStopped] * Callback function to be called when the stop is completed. * * @param {*} [callbackData] * Data to send to the onEnd callback functions. * * @return {void} */ Instrument.prototype.stop = function (immediately, onStopped, callbackData) { var instr = this, reset = function () { // Remove timeout reference if (instr.stopOscillatorTimeout) { delete instr.stopOscillatorTimeout; } // The oscillator may have stopped in the meantime here, so allow // this function to fail if so. try { instr.oscillator.stop(); } catch (e) { // silent error } instr.oscillator.disconnect(instr.gainNode); // We need a new oscillator in order to restart it instr.initOscillator(instr.options.oscillator); // Done stopping, call the callback from the stop if (onStopped) { onStopped(callbackData); } // Call the callback for the play we finished if (instr.stopCallback) { instr.stopCallback(callbackData); } }; // Clear any existing timers if (instr.playCallbackTimers.length) { instr.clearPlayCallbackTimers(); } if (instr.stopTimeout) { clearTimeout(instr.stopTimeout); } if (immediately) { instr.setGain(0); reset(); } else { instr.mute(); // Stop the oscillator after the mute fade-out has finished instr.stopOscillatorTimeout = setTimeout(reset, H.sonification.fadeOutDuration + 100); } }; return Instrument; }); _registerModule(_modules, 'modules/sonification/musicalFrequencies.js', [], function () { /* * * * (c) 2009-2019 Øystein Moseng * * List of musical frequencies from C0 to C8. * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var frequencies = [ 16.351597831287414, 17.323914436054505, 18.354047994837977, 19.445436482630058, 20.601722307054366, 21.826764464562746, 23.12465141947715, 24.499714748859326, 25.956543598746574, 27.5, 29.13523509488062, 30.86770632850775, 32.70319566257483, 34.64782887210901, 36.70809598967594, 38.890872965260115, 41.20344461410875, 43.653528929125486, 46.2493028389543, 48.999429497718666, 51.91308719749314, 55, 58.27047018976124, 61.7354126570155, 65.40639132514966, 69.29565774421802, 73.41619197935188, 77.78174593052023, 82.4068892282175, 87.30705785825097, 92.4986056779086, 97.99885899543733, 103.82617439498628, 110, 116.54094037952248, 123.47082531403103, 130.8127826502993, 138.59131548843604, 146.8323839587038, 155.56349186104046, 164.81377845643496, 174.61411571650194, 184.9972113558172, 195.99771799087463, 207.65234878997256, 220, 233.08188075904496, 246.94165062806206, 261.6255653005986, 277.1826309768721, 293.6647679174076, 311.1269837220809, 329.6275569128699, 349.2282314330039, 369.9944227116344, 391.99543598174927, 415.3046975799451, 440, 466.1637615180899, 493.8833012561241, 523.2511306011972, 554.3652619537442, 587.3295358348151, 622.2539674441618, 659.2551138257398, 698.4564628660078, 739.9888454232688, 783.9908719634985, 830.6093951598903, 880, 932.3275230361799, 987.7666025122483, 1046.5022612023945, 1108.7305239074883, 1174.6590716696303, 1244.5079348883237, 1318.5102276514797, 1396.9129257320155, 1479.9776908465376, 1567.981743926997, 1661.2187903197805, 1760, 1864.6550460723597, 1975.533205024496, 2093.004522404789, 2217.4610478149766, 2349.31814333926, 2489.0158697766474, 2637.02045530296, 2793.825851464031, 2959.955381693075, 3135.9634878539946, 3322.437580639561, 3520, 3729.3100921447194, 3951.066410048992, 4186.009044809578 // C8 ]; return frequencies; }); _registerModule(_modules, 'modules/sonification/utilities.js', [_modules['modules/sonification/musicalFrequencies.js'], _modules['parts/Utilities.js']], function (musicalFrequencies, U) { /* * * * (c) 2009-2019 Øystein Moseng * * Utility functions for sonification. * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var clamp = U.clamp; /* eslint-disable no-invalid-this, valid-jsdoc */ /** * The SignalHandler class. Stores signal callbacks (event handlers), and * provides an interface to register them, and emit signals. The word "event" is * not used to avoid confusion with TimelineEvents. * * @requires module:modules/sonification * * @private * @class * @name Highcharts.SignalHandler * * @param {Array<string>} supportedSignals * List of supported signal names. */ function SignalHandler(supportedSignals) { this.init(supportedSignals || []); } SignalHandler.prototype.init = function (supportedSignals) { this.supportedSignals = supportedSignals; this.signals = {}; }; /** * Register a set of signal callbacks with this SignalHandler. * Multiple signal callbacks can be registered for the same signal. * @private * @param {Highcharts.Dictionary<(Function|undefined)>} signals * An object that contains a mapping from the signal name to the callbacks. Only * supported events are considered. * @return {void} */ SignalHandler.prototype.registerSignalCallbacks = function (signals) { var signalHandler = this; signalHandler.supportedSignals.forEach(function (supportedSignal) { var signal = signals[supportedSignal]; if (signal) { (signalHandler.signals[supportedSignal] = signalHandler.signals[supportedSignal] || []).push(signal); } }); }; /** * Clear signal callbacks, optionally by name. * @private * @param {Array<string>} [signalNames] - A list of signal names to clear. If * not supplied, all signal callbacks are removed. * @return {void} */ SignalHandler.prototype.clearSignalCallbacks = function (signalNames) { var signalHandler = this; if (signalNames) { signalNames.forEach(function (signalName) { if (signalHandler.signals[signalName]) { delete signalHandler.signals[signalName]; } }); } else { signalHandler.signals = {}; } }; /** * Emit a signal. Does nothing if the signal does not exist, or has no * registered callbacks. * @private * @param {string} signalNames * Name of signal to emit. * @param {*} [data] * Data to pass to the callback. * @return {*} */ SignalHandler.prototype.emitSignal = function (signalName, data) { var retval; if (this.signals[signalName]) { this.signals[signalName].forEach(function (handler) { var result = handler(data); retval = typeof result !== 'undefined' ? result : retval; }); } return retval; }; var utilities = { // List of musical frequencies from C0 to C8 musicalFrequencies: musicalFrequencies, // SignalHandler class SignalHandler: SignalHandler, /** * Get a musical scale by specifying the semitones from 1-12 to include. * 1: C, 2: C#, 3: D, 4: D#, 5: E, 6: F, * 7: F#, 8: G, 9: G#, 10: A, 11: Bb, 12: B * @private * @param {Array<number>} semitones * Array of semitones from 1-12 to include in the scale. Duplicate entries * are ignored. * @return {Array<number>} * Array of frequencies from C0 to C8 that are included in this scale. */ getMusicalScale: function (semitones) { return musicalFrequencies.filter(function (freq, i) { var interval = i % 12 + 1; return semitones.some(function (allowedInterval) { return allowedInterval === interval; }); }); }, /** * Calculate the extreme values in a chart for a data prop. * @private * @param {Highcharts.Chart} chart - The chart * @param {string} prop - The data prop to find extremes for * @return {Highcharts.RangeObject} Object with min and max properties */ calculateDataExtremes: function (chart, prop) { return chart.series.reduce(function (extremes, series) { // We use cropped points rather than series.data here, to allow // users to zoom in for better fidelity. series.points.forEach(function (point) { var val = typeof point[prop] !== 'undefined' ? point[prop] : point.options[prop]; extremes.min = Math.min(extremes.min, val); extremes.max = Math.max(extremes.max, val); }); return extremes; }, { min: Infinity, max: -Infinity }); }, /** * Translate a value on a virtual axis. Creates a new, virtual, axis with a * min and max, and maps the relative value onto this axis. * @private * @param {number} value * The relative data value to translate. * @param {Highcharts.RangeObject} dataExtremes * The possible extremes for this value. * @param {object} limits * Limits for the virtual axis. * @return {number} * The value mapped to the virtual axis. */ virtualAxisTranslate: function (value, dataExtremes, limits) { var lenValueAxis = dataExtremes.max - dataExtremes.min, lenVirtualAxis = limits.max - limits.min, virtualAxisValue = limits.min + lenVirtualAxis * (value - dataExtremes.min) / lenValueAxis; return lenValueAxis > 0 ? clamp(virtualAxisValue, limits.min, limits.max) : limits.min; } }; return utilities; }); _registerModule(_modules, 'modules/sonification/instrumentDefinitions.js', [_modules['modules/sonification/Instrument.js'], _modules['modules/sonification/utilities.js']], function (Instrument, utilities) { /* * * * (c) 2009-2019 Øystein Moseng * * Instrument definitions for sonification module. * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var instruments = {}; ['sine', 'square', 'triangle', 'sawtooth'].forEach(function (waveform) { // Add basic instruments instruments[waveform] = new Instrument({ oscillator: { waveformShape: waveform } }); // Add musical instruments instruments[waveform + 'Musical'] = new Instrument({ allowedFrequencies: utilities.musicalFrequencies, oscillator: { waveformShape: waveform } }); // Add scaled instruments instruments[waveform + 'Major'] = new Instrument({ allowedFrequencies: utilities.getMusicalScale([1, 3, 5, 6, 8, 10, 12]), oscillator: { waveformShape: waveform } }); }); return instruments; }); _registerModule(_modules, 'modules/sonification/Earcon.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js']], function (H, U) { /* * * * (c) 2009-2019 Øystein Moseng * * Earcons for the sonification module in Highcharts. * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var pick = U.pick; /** * Define an Instrument and the options for playing it. * * @requires module:modules/sonification * * @interface Highcharts.EarconInstrument */ /** * An instrument instance or the name of the instrument in the * Highcharts.sonification.instruments map. * @name Highcharts.EarconInstrument#instrument * @type {string|Highcharts.Instrument} */ /** * The options to pass to Instrument.play. * @name Highcharts.EarconInstrument#playOptions * @type {Highcharts.InstrumentPlayOptionsObject} */ /** * Options for an Earcon. * * @requires module:modules/sonification * * @interface Highcharts.EarconOptionsObject */ /** * The instruments and their options defining this earcon. * @name Highcharts.EarconOptionsObject#instruments * @type {Array<Highcharts.EarconInstrument>} */ /** * The unique ID of the Earcon. Generated if not supplied. * @name Highcharts.EarconOptionsObject#id * @type {string|undefined} */ /** * Global panning of all instruments. Overrides all panning on individual * instruments. Can be a number between -1 and 1. * @name Highcharts.EarconOptionsObject#pan * @type {number|undefined} */ /** * Master volume for all instruments. Volume settings on individual instruments * can still be used for relative volume between the instruments. This setting * does not affect volumes set by functions in individual instruments. Can be a * number between 0 and 1. Defaults to 1. * @name Highcharts.EarconOptionsObject#volume * @type {number|undefined} */ /** * Callback function to call when earcon has finished playing. * @name Highcharts.EarconOptionsObject#onEnd * @type {Function|undefined} */ /* eslint-disable no-invalid-this, valid-jsdoc */ /** * The Earcon class. Earcon objects represent a certain sound consisting of * one or more instruments playing a predefined sound. * * @sample highcharts/sonification/earcon/ * Using earcons directly * * @requires module:modules/sonification * * @class * @name Highcharts.Earcon * * @param {Highcharts.EarconOptionsObject} options * Options for the Earcon instance. */ function Earcon(options) { this.init(options || {}); } Earcon.prototype.init = function (options) { this.options = options; if (!this.options.id) { this.options.id = this.id = H.uniqueKey(); } this.instrumentsPlaying = {}; }; /** * Play the earcon, optionally overriding init options. * * @sample highcharts/sonification/earcon/ * Using earcons directly * * @function Highcharts.Earcon#sonify * * @param {Highcharts.EarconOptionsObject} options * Override existing options. * * @return {void} */ Earcon.prototype.sonify = function (options) { var playOptions = H.merge(this.options, options); // Find master volume/pan settings var masterVolume = pick(playOptions.volume, 1), masterPan = playOptions.pan, earcon = this, playOnEnd = options && options.onEnd, masterOnEnd = earcon.options.onEnd; // Go through the instruments and play them playOptions.instruments.forEach(function (opts) { var instrument = typeof opts.instrument === 'string' ? H.sonification.instruments[opts.instrument] : opts.instrument, instrumentOpts = H.merge(opts.playOptions), instrOnEnd, instrumentCopy, copyId = ''; if (instrument && instrument.play) { if (opts.playOptions) { // Handle master pan/volume if (typeof opts.playOptions.volume !== 'function') { instrumentOpts.volume = pick(masterVolume, 1) * pick(opts.playOptions.volume, 1); } instrumentOpts.pan = pick(masterPan, instrumentOpts.pan); // Handle onEnd instrOnEnd = instrumentOpts.onEnd; instrumentOpts.onEnd = function () { delete earcon.instrumentsPlaying[copyId]; if (instrOnEnd) { instrOnEnd.apply(this, arguments); } if (!Object.keys(earcon.instrumentsPlaying).length) { if (playOnEnd) { playOnEnd.apply(this, arguments); } if (masterOnEnd) { masterOnEnd.apply(this, arguments); } } }; // Play the instrument. Use a copy so we can play multiple at // the same time. instrumentCopy = instrument.copy(); copyId = instrumentCopy.id; earcon.instrumentsPlaying[copyId] = instrumentCopy; instrumentCopy.play(instrumentOpts); } } else { H.error(30); } }); }; /** * Cancel any current sonification of the Earcon. Calls onEnd functions. * * @function Highcharts.Earcon#cancelSonify * * @param {boolean} [fadeOut=false] * Whether or not to fade out as we stop. If false, the earcon is * cancelled synchronously. * * @return {void} */ Earcon.prototype.cancelSonify = function (fadeOut) { var playing = this.instrumentsPlaying, instrIds = playing && Object.keys(playing); if (instrIds && instrIds.length) { instrIds.forEach(function (instr) { playing[instr].stop(!fadeOut, null, 'cancelled'); }); this.instrumentsPlaying = {}; } }; return Earcon; }); _registerModule(_modules, 'modules/sonification/pointSonify.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js'], _modules['modules/sonification/utilities.js']], function (H, U, utilities) { /* * * * (c) 2009-2019 Øystein Moseng * * Code for sonifying single points. * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var 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. Default