UNPKG

highcharts

Version:
1,227 lines (1,220 loc) 168 kB
/** * @license Highcharts JS v10.0.0 (2022-03-07) * * Sonification module * * (c) 2012-2021 Øystein Moseng * * License: www.highcharts.com/license */ (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) { 'use strict'; var _modules = Highcharts ? Highcharts._modules : {}; function _registerModule(obj, path, args, fn) { if (!obj.hasOwnProperty(path)) { obj[path] = fn.apply(null, args); if (typeof CustomEvent === 'function') { window.dispatchEvent( new CustomEvent( 'HighchartsModuleLoaded', { detail: { path: path, module: obj[path] } }) ); } } } _registerModule(_modules, 'Extensions/Sonification/MusicalFrequencies.js', [], function () { /* * * * (c) 2009-2021 Øystein Moseng * * List of musical frequencies from C0 to C8. * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ /* * * * Constants * * */ 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 ]; /* * * * Default export * * */ return frequencies; }); _registerModule(_modules, 'Extensions/Sonification/SignalHandler.js', [], function () { /* * * * (c) 2009-2021 Øystein Moseng * * Utility functions for sonification. * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ /* eslint-disable no-invalid-this, valid-jsdoc */ /* * * * Class * * */ /** * 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. */ var SignalHandler = /** @class */ (function () { /* * * * Constructors * * */ function SignalHandler(supportedSignals) { /* * * * Properties * * */ this.signals = void 0; this.supportedSignals = void 0; this.init(supportedSignals || []); } /* * * * Functions * * */ 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. */ 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. */ 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} signalName * Name of signal to emit. * @param {*} [data] * Data to pass to the callback. */ 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; }; return SignalHandler; }()); /* * * * Default Export * * */ return SignalHandler; }); _registerModule(_modules, 'Extensions/Sonification/SonificationUtilities.js', [_modules['Extensions/Sonification/MusicalFrequencies.js'], _modules['Extensions/Sonification/SignalHandler.js'], _modules['Core/Utilities.js']], function (MusicalFrequencies, SignalHandler, U) { /* * * * (c) 2009-2021 Øystein Moseng * * Utility functions for sonification. * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var clamp = U.clamp, merge = U.merge; /* eslint-disable no-invalid-this, valid-jsdoc */ /* * * * Constants * * */ var SonificationUtilities = { // List of musical frequencies from C0 to C8 musicalFrequencies: MusicalFrequencies, // SignalHandler class SignalHandler: SignalHandler, getExtremesForInstrumentProps: getExtremesForInstrumentProps, /** * 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} DataExtremesObject * The possible extremes for this value. * @param {Object} limits * Limits for the virtual axis. * @param {boolean} [invert] * Invert the virtual axis. * @return {number} * The value mapped to the virtual axis. */ virtualAxisTranslate: function (value, dataExtremes, limits, invert) { var lenValueAxis = dataExtremes.max - dataExtremes.min, lenVirtualAxis = Math.abs(limits.max - limits.min), valueDelta = invert ? dataExtremes.max - value : value - dataExtremes.min, virtualValueDelta = lenVirtualAxis * valueDelta / lenValueAxis, virtualAxisValue = limits.min + virtualValueDelta; return lenValueAxis > 0 ? clamp(virtualAxisValue, limits.min, limits.max) : limits.min; } }; /* * * * Functions * * */ /** * Calculate value extremes for used instrument data properties on a chart. * @private * @param {Highcharts.Chart} chart * The chart to calculate extremes from. * @param {Array<Highcharts.PointInstrumentObject>} [instruments] * Additional instrument definitions to inspect for data props used, in * addition to the instruments defined in the chart options. * @param {Highcharts.Dictionary<Highcharts.RangeObject>} [dataExtremes] * Predefined extremes for each data prop. * @return {Highcharts.Dictionary<Highcharts.RangeObject>} * New extremes with data properties mapped to min/max objects. */ function getExtremesForInstrumentProps(chart, instruments, dataExtremes) { var defaultInstrumentDef = (chart.options.sonification && chart.options.sonification.defaultInstrumentOptions), optionDefToInstrDef = function (optionDef) { return ({ instrumentMapping: optionDef.mapping }); }; var allInstrumentDefinitions = (instruments || []).slice(0); if (defaultInstrumentDef) { allInstrumentDefinitions.push(optionDefToInstrDef(defaultInstrumentDef)); } chart.series.forEach(function (series) { var instrOptions = (series.options.sonification && series.options.sonification.instruments); if (instrOptions) { allInstrumentDefinitions = allInstrumentDefinitions.concat(instrOptions.map(optionDefToInstrDef)); } }); return (allInstrumentDefinitions).reduce(function (newExtremes, instrumentDefinition) { Object.keys(instrumentDefinition.instrumentMapping || {}).forEach(function (instrumentParameter) { var value = instrumentDefinition.instrumentMapping[instrumentParameter]; if (typeof value === 'string' && !newExtremes[value]) { // This instrument parameter is mapped to a data prop. If we // don't have predefined data extremes, find them. newExtremes[value] = SonificationUtilities .calculateDataExtremes(chart, value); } }); return newExtremes; }, merge(dataExtremes)); } /* * * * Default export * * */ return SonificationUtilities; }); _registerModule(_modules, 'Extensions/Sonification/Options.js', [], function () { /* * * * (c) 2009-2021 Øystein Moseng * * Default options for sonification. * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ /* * * * Constants * * */ // Experimental, disabled by default, not exposed in API var options = { sonification: { enabled: false, duration: 2500, afterSeriesWait: 700, masterVolume: 1, order: 'sequential', defaultInstrumentOptions: { instrument: 'sineMusical', // Start at G4 note, end at C6 minFrequency: 392, maxFrequency: 1046, mapping: { pointPlayTime: 'x', duration: 200, frequency: 'y' } } } }; /* * * * Default Export * * */ return options; }); _registerModule(_modules, 'Extensions/Sonification/Sonification.js', [_modules['Core/DefaultOptions.js'], _modules['Core/Utilities.js'], _modules['Extensions/Sonification/SonificationUtilities.js'], _modules['Extensions/Sonification/Options.js']], function (D, U, SU, sonificationOptions) { /* * * * (c) 2009-2021 Øystein Moseng * * Sonification module for Highcharts * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ /* * * * Imports * * */ var defaultOptions = D.defaultOptions; var merge = U.merge; /* * * * Functions * * */ // Expose on the Highcharts object // Add default options merge(true, defaultOptions, sonificationOptions); var Sonification = { fadeOutDuration: 20, // Classes and functions utilities: SU }; /* * * * Default Export * * */ /** * Global classes and objects related to sonification. * * @requires module:modules/sonification * * @name Highcharts.sonification * @type {Highcharts.SonificationObject} */ /** * Global classes and objects related to sonification. * * @requires module:modules/sonification * * @interface Highcharts.SonificationObject */ /** * Note fade-out-time in milliseconds. Most notes are faded out quickly by * default if there is time. This is to avoid abrupt stops which will cause * perceived clicks. * @name Highcharts.SonificationObject#fadeOutDuration * @type {number} */ /** * Utility functions. * @name Highcharts.SonificationObject#utilities * @private * @type {Object} */ /** * The Instrument class. * @name Highcharts.SonificationObject#Instrument * @type {Function} */ /** * Predefined instruments, given as an object with a map between the instrument * name and the Highcharts.Instrument object. * @name Highcharts.SonificationObject#instruments * @type {Object} */ /** * The Earcon class. * @name Highcharts.SonificationObject#Earcon * @type {Function} */ /** * The TimelineEvent class. * @private * @name Highcharts.SonificationObject#TimelineEvent * @type {Function} */ /** * The TimelinePath class. * @private * @name Highcharts.SonificationObject#TimelinePath * @type {Function} */ /** * The Timeline class. * @private * @name Highcharts.SonificationObject#Timeline * @type {Function} */ (''); // detach doclets above return Sonification; }); _registerModule(_modules, 'Extensions/Sonification/Instrument.js', [_modules['Core/Globals.js'], _modules['Extensions/Sonification/Sonification.js'], _modules['Extensions/Sonification/SonificationUtilities.js'], _modules['Core/Utilities.js']], function (H, Sonification, SU, U) { /* * * * (c) 2009-2021 Øystein Moseng * * Instrument class for sonification module. * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ /* * * * Imports * * */ var win = H.win; var error = U.error, merge = U.merge, pick = U.pick, uniqueKey = U.uniqueKey; /* eslint-disable no-invalid-this, valid-jsdoc */ /* * * * Class * * */ /** * 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. */ var Instrument = /** @class */ (function () { /* * * * Constructor * * */ function Instrument(options) { this.id = void 0; this.masterVolume = void 0; this.options = void 0; this.playCallbackTimers = void 0; this.init(options); } /* * * * Functions * * */ Instrument.prototype.init = function (options) { if (!this.initAudioContext()) { error(29); return; } this.options = merge(Instrument.defaultOptions, options); this.id = this.options.id = options && options.id || uniqueKey(); this.masterVolume = this.options.masterVolume || 0; // Init the audio nodes var ctx = Instrument.audioContext, // Note: Destination node can be overridden by setting // Highcharts.sonification.Instrument.prototype.destinationNode. // This allows for inserting an additional chain of nodes after // the default processing. destination = this.destinationNode || ctx.destination; 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(destination); } else { this.gainNode.connect(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(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 = win.AudioContext || win.webkitAudioContext, hasOldContext = !!Instrument.audioContext; if (Context) { Instrument.audioContext = Instrument.audioContext || new Context(); if (!hasOldContext && Instrument.audioContext && Instrument.audioContext.state === 'running') { Instrument.audioContext.suspend(); // Pause until we need it } return !!(Instrument.audioContext && (Instrument.audioContext.createOscillator) && (Instrument.audioContext.createGain)); } return false; }; /** * Init an oscillator instrument. * @private * @param {Highcharts.OscillatorOptionsObject} oscillatorOptions * The oscillator options passed to Highcharts.Instrument#init. */ Instrument.prototype.initOscillator = function (options) { var ctx = Instrument.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. */ Instrument.prototype.setPan = function (panValue) { if (this.panNode) { this.panNode.pan.setValueAtTime(panValue, Instrument.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. This * function also handles the Instrument's master volume. * @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. */ Instrument.prototype.setGain = function (gainValue, rampTime) { var gainNode = this.gainNode; var newVal = gainValue * this.masterVolume; if (gainNode) { if (newVal > 1.2) { console.warn(// eslint-disable-line 'Highcharts sonification warning: ' + 'Volume of instrument set too high.'); newVal = 1.2; } if (rampTime) { gainNode.gain.setValueAtTime(gainNode.gain.value, Instrument.audioContext.currentTime); gainNode.gain.linearRampToValueAtTime(newVal, Instrument.audioContext.currentTime + rampTime / 1000); } else { gainNode.gain.setValueAtTime(newVal, Instrument.audioContext.currentTime); } } }; /** * Cancel ongoing gain ramps. * @private */ Instrument.prototype.cancelGainRamp = function () { if (this.gainNode) { this.gainNode.gain.cancelScheduledValues(0); } }; /** * Set the master volume multiplier of the instrument after creation. * @param {number} volumeMultiplier * The gain level to set for the instrument. */ Instrument.prototype.setMasterVolume = function (volumeMultiplier) { this.masterVolume = volumeMultiplier || 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 */ 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 */ 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, Instrument.audioContext.currentTime); }; /** * Prepare instrument before playing. Resumes the audio context and starts * the oscillator. * @private */ Instrument.prototype.preparePlay = function () { this.setGain(0.001); if (Instrument.audioContext.state === 'suspended') { Instrument.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. * */ 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, callbackInterval = instrument.options.playCallbackInterval; var currentDurationIx = 0; if (typeof value === 'function') { var timer_1 = setInterval(function () { currentDurationIx++; var curTime = (currentDurationIx * callbackInterval / target); if (curTime >= 1) { instrument[setter](value(1), setterData); clearInterval(timer_1); } else { instrument[setter](value(curTime), setterData); } }, callbackInterval); instrument.playCallbackTimers.push(timer_1); } 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 (Instrument.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 < 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 - 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, 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. * */ Instrument.prototype.stop = function (immediately, onStopped, callbackData) { var instr = this, reset = function () { // Remove timeout reference if (instr.stopOscillatorTimeout) { delete instr.stopOscillatorTimeout; } if (instr.oscillator && instr.options.oscillator) { // 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 } if (instr.gainNode) { 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, Sonification.fadeOutDuration + 100); } }; // Default options for Instrument constructor Instrument.defaultOptions = { type: 'oscillator', playCallbackInterval: 20, masterVolume: 1, oscillator: { waveformShape: 'sine' } }; Instrument.definitions = {}; return Instrument; }()); /* * * * Class Prototype * * */ // ['sine', 'square', 'triangle', 'sawtooth'].forEach(function ( ['sine', 'square', 'triangle', 'sawtooth'].forEach(function (waveform) { // Add basic instruments Instrument.definitions[waveform] = new Instrument({ oscillator: { waveformShape: waveform } }); // Add musical instruments Instrument.definitions[waveform + 'Musical'] = new Instrument({ allowedFrequencies: SU.musicalFrequencies, oscillator: { waveformShape: waveform } }); // Add scaled instruments Instrument.definitions[waveform + 'Major'] = new Instrument({ allowedFrequencies: SU.getMusicalScale([1, 3, 5, 6, 8, 10, 12]), oscillator: { waveformShape: waveform } }); }); /* * * * Default Export * * */ /* * * * API Declarations * * */ /** * 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} */ /** * The master volume multiplier to apply to the instrument, regardless of other * volume changes. Defaults to 1. * @name Highcharts.InstrumentPlayOptionsObject#masterVolume * @type {number|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} */ (''); // keeps doclets above in JS file return Instrument; }); _registerModule(_modules, 'Extensions/Sonification/Earcon.js', [_modules['Extensions/Sonification/Instrument.js'], _modules['Core/Utilities.js']], function (Instrument, U) { /* * * * (c) 2009-2021 Øystein Moseng * * Earcons for the sonification module in Highcharts. * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var error = U.error, merge = U.merge, pick = U.pick, uniqueKey = U.uniqueKey; /* * * * Class * * */ /* eslint-disable no-invalid-this, valid-jsdoc */ /** * The Earcon class. Earcon ob