highcharts
Version:
JavaScript charting framework
1,227 lines (1,220 loc) • 168 kB
JavaScript
/**
* @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