| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341 | /** * @license Highcharts JS v7.0.2 (2019-01-17) * 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(function () {			return factory;		});	} else {		factory(typeof Highcharts !== 'undefined' ? Highcharts : undefined);	}}(function (Highcharts) {	var Instrument = (function (H) {		/* *		 *		 *  (c) 2009-2019 Øystein Moseng		 *		 *  Instrument class for sonification module.		 *		 *  License: www.highcharts.com/license		 *		 * */		/**		 * 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'		    }		};		/**		 * 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 {object} oscillatorOptions - The oscillator options passed to		 * Highcharts.Instrument#init.		 */		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.		 */		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.		 */		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		 */		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 = H.pick(max, Infinity),		        minimum = H.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 {object} [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, 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.		 */		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', null, {		            minFrequency: options.minFrequency,		            maxFrequency: options.maxFrequency		        });		        // Set the volume and panning		        setOrStartTimer(H.pick(options.volume, 1), 'setGain', 4); // Slight ramp		        setOrStartTimer(H.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.		 */		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) {}		            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;	}(Highcharts));	var frequencies = (function () {		/* *		 *		 *  (c) 2009-2019 Øystein Moseng		 *		 *  List of musical frequencies from C0 to C8.		 *		 *  License: www.highcharts.com/license		 *		 * */		var frequencies = [		    16.351597831287414, // C0		    17.323914436054505,		    18.354047994837977,		    19.445436482630058,		    20.601722307054366,		    21.826764464562746,		    23.12465141947715,		    24.499714748859326,		    25.956543598746574,		    27.5, // A0		    29.13523509488062,		    30.86770632850775,		    32.70319566257483, // C1		    34.64782887210901,		    36.70809598967594,		    38.890872965260115,		    41.20344461410875,		    43.653528929125486,		    46.2493028389543,		    48.999429497718666,		    51.91308719749314,		    55, // A1		    58.27047018976124,		    61.7354126570155,		    65.40639132514966, // C2		    69.29565774421802,		    73.41619197935188,		    77.78174593052023,		    82.4068892282175,		    87.30705785825097,		    92.4986056779086,		    97.99885899543733,		    103.82617439498628,		    110, // A2		    116.54094037952248,		    123.47082531403103,		    130.8127826502993, // C3		    138.59131548843604,		    146.8323839587038,		    155.56349186104046,		    164.81377845643496,		    174.61411571650194,		    184.9972113558172,		    195.99771799087463,		    207.65234878997256,		    220, // A3		    233.08188075904496,		    246.94165062806206,		    261.6255653005986, // C4		    277.1826309768721,		    293.6647679174076,		    311.1269837220809,		    329.6275569128699,		    349.2282314330039,		    369.9944227116344,		    391.99543598174927,		    415.3046975799451,		    440, // A4		    466.1637615180899,		    493.8833012561241,		    523.2511306011972, // C5		    554.3652619537442,		    587.3295358348151,		    622.2539674441618,		    659.2551138257398,		    698.4564628660078,		    739.9888454232688,		    783.9908719634985,		    830.6093951598903,		    880, // A5		    932.3275230361799,		    987.7666025122483,		    1046.5022612023945, // C6		    1108.7305239074883,		    1174.6590716696303,		    1244.5079348883237,		    1318.5102276514797,		    1396.9129257320155,		    1479.9776908465376,		    1567.981743926997,		    1661.2187903197805,		    1760, // A6		    1864.6550460723597,		    1975.533205024496,		    2093.004522404789, // C7		    2217.4610478149766,		    2349.31814333926,		    2489.0158697766474,		    2637.02045530296,		    2793.825851464031,		    2959.955381693075,		    3135.9634878539946,		    3322.437580639561,		    3520, // A7		    3729.3100921447194,		    3951.066410048992,		    4186.009044809578 // C8		];		return frequencies;	}());	var utilities = (function (musicalFrequencies) {		/* *		 *		 *  (c) 2009-2019 Øystein Moseng		 *		 *  Utility functions for sonification.		 *		 *  License: www.highcharts.com/license		 *		 * */		/**		 * 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 {object} 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) {		        if (signals[supportedSignal]) {		            (		                signalHandler.signals[supportedSignal] =		                signalHandler.signals[supportedSignal] || []		            ).push(		                signals[supportedSignal]		            );		        }		    });		};		/**		 * 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} signalNames - 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 = 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 {object} 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 = 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 {object} 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 ?		            Math.max(Math.min(virtualAxisValue, limits.max), limits.min) :		            limits.min;		    }		};		return utilities;	}(frequencies));	var instruments = (function (Instrument, utilities) {		/* *		 *		 *  (c) 2009-2019 Øystein Moseng		 *		 *  Instrument definitions for sonification module.		 *		 *  License: www.highcharts.com/license		 *		 * */		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;	}(Instrument, utilities));	var Earcon = (function (H) {		/* *		 *		 *  (c) 2009-2019 Øystein Moseng		 *		 *  Earcons for the sonification module in Highcharts.		 *		 *  License: www.highcharts.com/license		 *		 * */		/**		 * 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 {Highcharts.Instrument|String}		 *//**		 * The options to pass to Instrument.play.		 * @name Highcharts.EarconInstrument#playOptions		 * @type {object}		 */		/**		 * 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}		 */		/**		 * 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.		 */		Earcon.prototype.sonify = function (options) {		    var playOptions = H.merge(this.options, options);		    // Find master volume/pan settings		    var masterVolume = H.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 = H.pick(masterVolume, 1) *		                        H.pick(opts.playOptions.volume, 1);		                }		                instrumentOpts.pan = H.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.		 */		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;	}(Highcharts));	var pointSonifyFunctions = (function (H, utilities) {		/* *		 *		 *  (c) 2009-2019 Øystein Moseng		 *		 *  Code for sonifying single points.		 *		 *  License: www.highcharts.com/license		 *		 * */		/**		 * Define the parameter mapping for an instrument.		 *		 * @requires module:modules/sonification		 *		 * @interface Highcharts.PointInstrumentMappingObject		 *//**		 * Define the volume of the instrument. This can be a string with a data		 * property name, e.g. `'y'`, in which case this data property is used to define		 * the volume relative to the `y`-values of the other points. A higher `y` value		 * would then result in a higher volume. This option can also be a fixed number		 * or a function. If it is a function, this function is called in regular		 * intervals while the note is playing. It receives three arguments: The point,		 * the dataExtremes, and the current relative time - where 0 is the beginning of		 * the note and 1 is the end. The function should return the volume of the note		 * as a number between 0 and 1.		 * @name Highcharts.PointInstrumentMappingObject#volume		 * @type {string|number|Function}		 *//**		 * Define the duration of the notes for this instrument. This can be a string		 * with a data property name, e.g. `'y'`, in which case this data property is		 * used to define the duration relative to the `y`-values of the other points. A		 * higher `y` value would then result in a longer duration. This option can also		 * be a fixed number or a function. If it is a function, this function is called		 * once before the note starts playing, and should return the duration in		 * milliseconds. It receives two arguments: The point, and the dataExtremes.		 * @name Highcharts.PointInstrumentMappingObject#duration		 * @type {string|number|Function}		 *//**		 * Define the panning of the instrument. This can be a string with a data		 * property name, e.g. `'x'`, in which case this data property is used to define		 * the panning relative to the `x`-values of the other points. A higher `x`		 * value would then result in a higher panning value (panned further to the		 * right). This option can also be a fixed number or a function. If it is a		 * function, this function is called in regular intervals while the note is		 * playing. It receives three arguments: The point, the dataExtremes, and the		 * current relative time - where 0 is the beginning of the note and 1 is the		 * end. The function should return the panning of the note as a number between		 * -1 and 1.		 * @name Highcharts.PointInstrumentMappingObject#pan		 * @type {string|number|Function|undefined}		 *//**		 * Define the frequency of the instrument. This can be a string with a data		 * property name, e.g. `'y'`, in which case this data property is used to define		 * the frequency relative to the `y`-values of the other points. A higher `y`		 * value would then result in a higher frequency. This option can also be a		 * fixed number or a function. If it is a function, this function is called in		 * regular intervals while the note is playing. It receives three arguments:		 * The point, the dataExtremes, and the current relative time - where 0 is the		 * beginning of the note and 1 is the end. The function should return the		 * frequency of the note as a number (in Hz).		 * @name Highcharts.PointInstrumentMappingObject#frequency		 * @type {string|number|Function}		 */		/**		 * @requires module:modules/sonification		 *		 * @interface Highcharts.PointInstrumentOptionsObject		 *//**		 * The minimum duration for a note when using a data property for duration. Can		 * be overridden by using either a fixed number or a function for		 * instrumentMapping.duration. Defaults to 20.		 * @name Highcharts.PointInstrumentOptionsObject#minDuration		 * @type {number|undefined}		 *//**		 * The maximum duration for a note when using a data property for duration. Can		 * be overridden by using either a fixed number or a function for		 * instrumentMapping.duration. Defaults to 2000.		 * @name Highcharts.PointInstrumentOptionsObject#maxDuration		 * @type {number|undefined}		 *//**		 * The minimum pan value for a note when using a data property for panning. Can		 * be overridden by using either a fixed number or a function for		 * instrumentMapping.pan. Defaults to -1 (fully left).		 * @name Highcharts.PointInstrumentOptionsObject#minPan		 * @type {number|undefined}		 *//**		 * The maximum pan value for a note when using a data property for panning. Can		 * be overridden by using either a fixed number or a function for		 * instrumentMapping.pan. Defaults to 1 (fully right).		 * @name Highcharts.PointInstrumentOptionsObject#maxPan		 * @type {number|undefined}		 *//**		 * The minimum volume for a note when using a data property for volume. Can be		 * overridden by using either a fixed number or a function for		 * instrumentMapping.volume. Defaults to 0.1.		 * @name Highcharts.PointInstrumentOptionsObject#minVolume		 * @type {number|undefined}		 *//**		 * The maximum volume for a note when using a data property for volume. Can be		 * overridden by using either a fixed number or a function for		 * instrumentMapping.volume. Defaults to 1.		 * @name Highcharts.PointInstrumentOptionsObject#maxVolume		 * @type {number|undefined}		 *//**		 * The minimum frequency for a note when using a data property for frequency.		 * Can be overridden by using either a fixed number or a function for		 * instrumentMapping.frequency. Defaults to 220.		 * @name Highcharts.PointInstrumentOptionsObject#minFrequency		 * @type {number|undefined}		 *//**		 * The maximum frequency for a note when using a data property for frequency.		 * Can be overridden by using either a fixed number or a function for		 * instrumentMapping.frequency. Defaults to 2200.		 * @name Highcharts.PointInstrumentOptionsObject#maxFrequency		 * @type {number|undefined}		 */		/**		 * An instrument definition for a point, specifying the instrument to play and		 * how to play it.		 *		 * @interface Highcharts.PointInstrumentObject		 *//**		 * An Instrument instance or the name of the instrument in the		 * Highcharts.sonification.instruments map.		 * @name Highcharts.PointInstrumentObject#instrument		 * @type {Highcharts.Instrument|string}		 *//**		 * Mapping of instrument parameters for this instrument.		 * @name Highcharts.PointInstrumentObject#instrumentMapping		 * @type {Highcharts.PointInstrumentMappingObject}		 *//**		 * Options for this instrument.		 * @name Highcharts.PointInstrumentObject#instrumentOptions		 * @type {Highcharts.PointInstrumentOptionsObject|undefined}		 *//**		 * Callback to call when the instrument has stopped playing.		 * @name Highcharts.PointInstrumentObject#onEnd		 * @type {Function|undefined}		 */		/**		 * Options for sonifying a point.		 * @interface Highcharts.PointSonifyOptionsObject		 *//**		 * The instrument definitions for this point.		 * @name Highcharts.PointSonifyOptionsObject#instruments		 * @type {Array<Highcharts.PointInstrumentObject>}		 *//**		 * Optionally provide the minimum/maximum values for the points. If this is not		 * supplied, it is calculated from the points in the chart on demand. This		 * option is supplied in the following format, as a map of point data properties		 * to objects with min/max values:		 *  ```js		 *      dataExtremes: {		 *          y: {		 *              min: 0,		 *              max: 100		 *          },		 *          z: {		 *              min: -10,		 *              max: 10		 *          }		 *          // Properties used and not provided are calculated on demand		 *      }		 *  ```		 * @name Highcharts.PointSonifyOptionsObject#dataExtremes		 * @type {object|undefined}		 *//**		 * Callback called when the sonification has finished.		 * @name Highcharts.PointSonifyOptionsObject#onEnd		 * @type {Function|undefined}		 */		// Defaults for the instrument options		// NOTE: Also change defaults in Highcharts.PointInstrumentOptionsObject if		//       making changes here.		var defaultInstrumentOptions = {		    minDuration: 20,		    maxDuration: 2000,		    minVolume: 0.1,		    maxVolume: 1,		    minPan: -1,		    maxPan: 1,		    minFrequency: 220,		    maxFrequency: 2200		};		/**		 * Sonify a single point.		 *		 * @sample highcharts/sonification/point-basic/		 *         Click on points to sonify		 * @sample highcharts/sonification/point-advanced/		 *         Sonify bubbles		 *		 * @requires module:modules/sonification		 *		 * @function Highcharts.Point#sonify		 *		 * @param {Highcharts.PointSonifyOptionsObject} options		 *        Options for the sonification of the point.		 */		function pointSonify(options) {		    var point = this,		        chart = point.series.chart,		        dataExtremes = options.dataExtremes || {},		        // Get the value to pass to instrument.play from the mapping value		        // passed in.		        getMappingValue = function (		            value, makeFunction, allowedExtremes, allowedValues		        ) {		            // Fixed number, just use that		            if (typeof value === 'number' || value === undefined) {		                return value;		            }		            // Function. Return new function if we try to use callback,		            // otherwise call it now and return result.		            if (typeof value === 'function') {		                return makeFunction ?		                    function (time) {		                        return value(point, dataExtremes, time);		                    } :		                    value(point, dataExtremes);		            }		            // String, this is a data prop.		            if (typeof value === 'string') {		                // Find data extremes if we don't have them		                dataExtremes[value] = dataExtremes[value] ||		                    utilities.calculateDataExtremes(		                        point.series.chart, value		                    );		                // Find the value		                return utilities.virtualAxisTranslate(		                    H.pick(point[value], point.options[value]),		                    dataExtremes[value],		                    allowedExtremes,		                    allowedValues		                );		            }		        };		    // Register playing point on chart		    chart.sonification.currentlyPlayingPoint = point;		    // Keep track of instruments playing		    point.sonification = point.sonification || {};		    point.sonification.instrumentsPlaying =		        point.sonification.instrumentsPlaying || {};		    // Register signal handler for the point		    var signalHandler = point.sonification.signalHandler =		        point.sonification.signalHandler ||		        new utilities.SignalHandler(['onEnd']);		    signalHandler.clearSignalCallbacks();		    signalHandler.registerSignalCallbacks({ onEnd: options.onEnd });		    // If we have a null point or invisible point, just return		    if (point.isNull || !point.visible || !point.series.visible) {		        signalHandler.emitSignal('onEnd');		        return;		    }		    // Go through instruments and play them		    options.instruments.forEach(function (instrumentDefinition) {		        var instrument = typeof instrumentDefinition.instrument === 'string' ?		                H.sonification.instruments[instrumentDefinition.instrument] :		                instrumentDefinition.instrument,		            mapping = instrumentDefinition.instrumentMapping || {},		            extremes = H.merge(		                defaultInstrumentOptions,		                instrumentDefinition.instrumentOptions		            ),		            id = instrument.id,		            onEnd = function (cancelled) {		                // Instrument on end		                if (instrumentDefinition.onEnd) {		                    instrumentDefinition.onEnd.apply(this, arguments);		                }		                // Remove currently playing point reference on chart		                if (		                    chart.sonification &&		                    chart.sonification.currentlyPlayingPoint		                ) {		                    delete chart.sonification.currentlyPlayingPoint;		                }		                // Remove reference from instruments playing		                if (		                    point.sonification && point.sonification.instrumentsPlaying		                ) {		                    delete point.sonification.instrumentsPlaying[id];		                    // This was the last instrument?		                    if (		                        !Object.keys(		                            point.sonification.instrumentsPlaying		                        ).length		                    ) {		                        signalHandler.emitSignal('onEnd', cancelled);		                    }		                }		            };		        // Play the note on the instrument		        if (instrument && instrument.play) {		            point.sonification.instrumentsPlaying[instrument.id] = instrument;		            instrument.play({		                frequency: getMappingValue(		                    mapping.frequency,		                    true,		                    { min: extremes.minFrequency, max: extremes.maxFrequency }		                ),		                duration: getMappingValue(		                    mapping.duration,		                    false,		                    { min: extremes.minDuration, max: extremes.maxDuration }		                ),		                pan: getMappingValue(		                    mapping.pan,		                    true,		                    { min: extremes.minPan, max: extremes.maxPan }		                ),		                volume: getMappingValue(		                    mapping.volume,		                    true,		                    { min: extremes.minVolume, max: extremes.maxVolume }		                ),		                onEnd: onEnd,		                minFrequency: extremes.minFrequency,		                maxFrequency: extremes.maxFrequency		            });		        } else {		            H.error(30);		        }		    });		}		/**		 * Cancel sonification of a point. Calls onEnd functions.		 *		 * @requires module:modules/sonification		 *		 * @function Highcharts.Point#cancelSonify		 *		 * @param {boolean} [fadeOut=false]		 *        Whether or not to fade out as we stop. If false, the points are		 *        cancelled synchronously.		 */		function pointCancelSonify(fadeOut) {		    var playing = this.sonification && this.sonification.instrumentsPlaying,		        instrIds = playing && Object.keys(playing);		    if (instrIds && instrIds.length) {		        instrIds.forEach(function (instr) {		            playing[instr].stop(!fadeOut, null, 'cancelled');		        });		        this.sonification.instrumentsPlaying = {};		        this.sonification.signalHandler.emitSignal('onEnd', 'cancelled');		    }		}		var pointSonifyFunctions = {		    pointSonify: pointSonify,		    pointCancelSonify: pointCancelSonify		};		return pointSonifyFunctions;	}(Highcharts, utilities));	var chartSonifyFunctions = (function (H, utilities) {		/* *		 *		 *  (c) 2009-2019 Øystein Moseng		 *		 *  Sonification functions for chart/series.		 *		 *  License: www.highcharts.com/license		 *		 * */		/**		 * An Earcon configuration, specifying an Earcon and when to play it.		 *		 * @requires module:modules/sonification		 *		 * @interface Highcharts.EarconConfiguration		 *//**		 * An Earcon instance.		 * @name Highcharts.EarconConfiguration#earcon		 * @type {Highcharts.Earcon}		 *//**		 * The ID of the point to play the Earcon on.		 * @name Highcharts.EarconConfiguration#onPoint		 * @type {string|undefined}		 *//**		 * A function to determine whether or not to play this earcon on a point. The		 * function is called for every point, receiving that point as parameter. It		 * should return either a boolean indicating whether or not to play the earcon,		 * or a new Earcon instance - in which case the new Earcon will be played.		 * @name Highcharts.EarconConfiguration#condition		 * @type {Function|undefined}		 */		/**		 * Options for sonifying a series.		 *		 * @requires module:modules/sonification		 *		 * @interface Highcharts.SonifySeriesOptionsObject		 *//**		 * The duration for playing the points. Note that points might continue to play		 * after the duration has passed, but no new points will start playing.		 * @name Highcharts.SonifySeriesOptionsObject#duration		 * @type {number}		 *//**		 * The axis to use for when to play the points. Can be a string with a data		 * property (e.g. `x`), or a function. If it is a function, this function		 * receives the point as argument, and should return a numeric value. The points		 * with the lowest numeric values are then played first, and the time between		 * points will be proportional to the distance between the numeric values.		 * @name Highcharts.SonifySeriesOptionsObject#pointPlayTime		 * @type {string|Function}		 *//**		 * The instrument definitions for the points in this series.		 * @name Highcharts.SonifySeriesOptionsObject#instruments		 * @type {Array<Highcharts.PointInstrumentObject>}		 *//**		 * Earcons to add to the series.		 * @name Highcharts.SonifySeriesOptionsObject#earcons		 * @type {Array<Highcharts.EarconConfiguration>|undefined}		 *//**		 * Optionally provide the minimum/maximum data values for the points. If this is		 * not supplied, it is calculated from all points in the chart on demand. This		 * option is supplied in the following format, as a map of point data properties		 * to objects with min/max values:		 * ```js		 *     dataExtremes: {		 *         y: {		 *             min: 0,		 *             max: 100		 *         },		 *         z: {		 *             min: -10,		 *             max: 10		 *         }		 *         // Properties used and not provided are calculated on demand		 *     }		 * ```		 * @name Highcharts.SonifySeriesOptionsObject#dataExtremes		 * @type {object|undefined}		 *//**		 * Callback before a point is played.		 * @name Highcharts.SonifySeriesOptionsObject#onPointStart		 * @type {Function|undefined}		 *//**		 * Callback after a point has finished playing.		 * @name Highcharts.SonifySeriesOptionsObject#onPointEnd		 * @type {Function|undefined}		 *//**		 * Callback after the series has played.		 * @name Highcharts.SonifySeriesOptionsObject#onEnd		 * @type {Function|undefined}		 */		/**		 * Get the relative time value of a point.		 * @private		 * @param {Highcharts.Point} point - The point.		 * @param {Function|string} timeProp - The time axis data prop or the time		 * function.		 * @return {number} The time value.		 */		function getPointTimeValue(point, timeProp) {		    return typeof timeProp === 'function' ?		        timeProp(point) :		        H.pick(point[timeProp], point.options[timeProp]);		}		/**		 * Get the time extremes of this series. This is handled outside of the		 * dataExtremes, as we always want to just sonify the visible points, and we		 * always want the extremes to be the extremes of the visible points.		 * @private		 * @param {Highcharts.Series} series - The series to compute on.		 * @param {Function|string} timeProp - The time axis data prop or the time		 * function.		 * @return {object} Object with min/max extremes for the time values.		 */		function getTimeExtremes(series, timeProp) {		    // Compute the extremes from the visible points.		    return series.points.reduce(function (acc, point) {		        var value = getPointTimeValue(point, timeProp);		        acc.min = Math.min(acc.min, value);		        acc.max = Math.max(acc.max, value);		        return acc;		    }, {		        min: Infinity,		        max: -Infinity		    });		}		/**		 * Calculate value extremes for used instrument data properties.		 * @private		 * @param {Highcharts.Chart} chart - The chart to calculate extremes from.		 * @param {Array<Highcharts.PointInstrumentObject>} instruments - The instrument		 * definitions used.		 * @param {object} [dataExtremes] - Predefined extremes for each data prop.		 * @return {object} New extremes with data properties mapped to min/max objects.		 */		function getExtremesForInstrumentProps(chart, instruments, dataExtremes) {		    return (		        instruments || []		    ).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] = utilities.calculateDataExtremes(		                        chart, value		                    );		                }		            }		        );		        return newExtremes;		    }, H.merge(dataExtremes));		}		/**		 * Get earcons for the point if there are any.		 * @private		 * @param {Highcharts.Point} point - The point to find earcons for.		 * @param {Array<Highcharts.EarconConfiguration>} earconDefinitions - Earcons to		 * check.		 * @return {Array<Highcharts.Earcon>} Array of earcons to be played with this		 * point.		 */		function getPointEarcons(point, earconDefinitions) {		    return earconDefinitions.reduce(		        function (earcons, earconDefinition) {		            var cond,		                earcon = earconDefinition.earcon;		            if (earconDefinition.condition) {		                // We have a condition. This overrides onPoint		                cond = earconDefinition.condition(point);		                if (cond instanceof H.sonification.Earcon) {		                    // Condition returned an earcon		                    earcons.push(cond);		                } else if (cond) {		                    // Condition returned true		                    earcons.push(earcon);		                }		            } else if (		                earconDefinition.onPoint &&		                point.id === earconDefinition.onPoint		            ) {		                // We have earcon onPoint		                earcons.push(earcon);		            }		            return earcons;		        }, []		    );		}		/**		 * Utility function to get a new list of instrument options where all the		 * instrument references are copies.		 * @private		 * @param {Array<Highcharts.PointInstrumentObject>} instruments - The instrument		 * options.		 * @return {Array<Highcharts.PointInstrumentObject>} Array of copied instrument		 * options.		 */		function makeInstrumentCopies(instruments) {		    return instruments.map(function (instrumentDef) {		        var instrument = instrumentDef.instrument,		            copy = (typeof instrument === 'string' ?		                H.sonification.instruments[instrument] :		                instrument).copy();		        return H.merge(instrumentDef, { instrument: copy });		    });		}		/**		 * Create a TimelinePath from a series. Takes the same options as seriesSonify.		 * To intuitively allow multiple series to play simultaneously we make copies of		 * the instruments for each series.		 * @private		 * @param {Highcharts.Series} series - The series to build from.		 * @param {object} options - The options for building the TimelinePath.		 * @return {Highcharts.TimelinePath} A timeline path with events.		 */		function buildTimelinePathFromSeries(series, options) {		    // options.timeExtremes is internal and used so that the calculations from		    // chart.sonify can be reused.		    var timeExtremes = options.timeExtremes || getTimeExtremes(		            series, options.pointPlayTime, options.dataExtremes		        ),		        // Get time offset for a point, relative to duration		        pointToTime = function (point) {		            return utilities.virtualAxisTranslate(		                getPointTimeValue(point, options.pointPlayTime),		                timeExtremes,		                { min: 0, max: options.duration }		            );		        },		        // Compute any data extremes that aren't defined yet		        dataExtremes = getExtremesForInstrumentProps(		            series.chart, options.instruments, options.dataExtremes		        ),		        // Make copies of the instruments used for this series, to allow		        // multiple series with the same instrument to play together		        instruments = makeInstrumentCopies(options.instruments),		        // Go through the points, convert to events, optionally add Earcons		        timelineEvents = series.points.reduce(function (events, point) {		            var earcons = getPointEarcons(point, options.earcons || []),		                time = pointToTime(point);		            return events.concat(		                // Event object for point		                new H.sonification.TimelineEvent({		                    eventObject: point,		                    time: time,		                    id: point.id,		                    playOptions: {		                        instruments: instruments,		                        dataExtremes: dataExtremes		                    }		                }),		                // Earcons		                earcons.map(function (earcon) {		                    return new H.sonification.TimelineEvent({		                        eventObject: earcon,		                        time: time		                    });		                })		            );		        }, []);		    // Build the timeline path		    return new H.sonification.TimelinePath({		        events: timelineEvents,		        onStart: function () {		            if (options.onStart) {		                options.onStart(series);		            }		        },		        onEventStart: function (event) {		            var eventObject = event.options && event.options.eventObject;		            if (eventObject instanceof H.Point) {		                // Check for hidden series		                if (		                    !eventObject.series.visible &&		                    !eventObject.series.chart.series.some(function (series) {		                        return series.visible;		                    })		                ) {		                    // We have no visible series, stop the path.		                    event.timelinePath.timeline.pause();		                    event.timelinePath.timeline.resetCursor();		                    return false;		                }		                // Emit onPointStart		                if (options.onPointStart) {		                    options.onPointStart(event, eventObject);		                }		            }		        },		        onEventEnd: function (eventData) {		            var eventObject = eventData.event && eventData.event.options &&		                    eventData.event.options.eventObject;		            if (eventObject instanceof H.Point && options.onPointEnd) {		                options.onPointEnd(eventData.event, eventObject);		            }		        },		        onEnd: function () {		            if (options.onEnd) {		                options.onEnd(series);		            }		        }		    });		}		/**		 * Sonify a series.		 *		 * @sample highcharts/sonification/series-basic/		 *         Click on series to sonify		 * @sample highcharts/sonification/series-earcon/		 *         Series with earcon		 * @sample highcharts/sonification/point-play-time/		 *         Play y-axis by time		 * @sample highcharts/sonification/earcon-on-point/		 *         Earcon set on point		 *		 * @requires module:modules/sonification		 *		 * @function Highcharts.Series#sonify		 *		 * @param {Highcharts.SonifySeriesOptionsObject} options		 *        The options for sonifying this series.		 */		function seriesSonify(options) {		    var timelinePath = buildTimelinePathFromSeries(this, options),		        chartSonification = this.chart.sonification;		    // Only one timeline can play at a time. If we want multiple series playing		    // at the same time, use chart.sonify.		    if (chartSonification.timeline) {		        chartSonification.timeline.pause();		    }		    // Create new timeline for this series, and play it.		    chartSonification.timeline = new H.sonification.Timeline({		        paths: [timelinePath]		    });		    chartSonification.timeline.play();		}		/**		 * Utility function to assemble options for creating a TimelinePath from a		 * series when sonifying an entire chart.		 * @private		 * @param {Highcharts.Series} series - The series to return options for.		 * @param {object} dataExtremes - Pre-calculated data extremes for the chart.		 * @param {object} chartSonifyOptions - Options passed in to chart.sonify.		 * @return {object} Options for buildTimelinePathFromSeries.		 */		function buildSeriesOptions(series, dataExtremes, chartSonifyOptions) {		    var seriesOptions = chartSonifyOptions.seriesOptions || {};		    return H.merge(		        {		            // Calculated dataExtremes for chart		            dataExtremes: dataExtremes,		            // We need to get timeExtremes for each series. We pass this		            // in when building the TimelinePath objects to avoid		            // calculating twice.		            timeExtremes: getTimeExtremes(		                series, chartSonifyOptions.pointPlayTime		            ),		            // Some options we just pass on		            instruments: chartSonifyOptions.instruments,		            onStart: chartSonifyOptions.onSeriesStart,		            onEnd: chartSonifyOptions.onSeriesEnd,		            earcons: chartSonifyOptions.earcons		        },		        // Merge in the specific series options by ID		        H.isArray(seriesOptions) ? (		            H.find(seriesOptions, function (optEntry) {		                return optEntry.id === H.pick(series.id, series.options.id);		            }) || {}		        ) : seriesOptions,		        {		            // Forced options		            pointPlayTime: chartSonifyOptions.pointPlayTime		        }		    );		}		/**		 * Utility function to normalize the ordering of timeline paths when sonifying		 * a chart.		 * @private		 * @param {string|Array<string|Highcharts.Earcon|Array<string|Highcharts.Earcon>>} orderOptions -		 * Order options for the sonification.		 * @param {Highcharts.Chart} chart - The chart we are sonifying.		 * @param {Function} seriesOptionsCallback - A function that takes a series as		 * argument, and returns the series options for that series to be used with		 * buildTimelinePathFromSeries.		 * @return {Array<object|Array<object|Highcharts.TimelinePath>>} If order is		 * sequential, we return an array of objects to create series paths from. If		 * order is simultaneous we return an array of an array with the same. If there		 * is a custom order, we return an array of arrays of either objects (for		 * series) or TimelinePaths (for earcons and delays).		 */		function buildPathOrder(orderOptions, chart, seriesOptionsCallback) {		    var order;		    if (orderOptions === 'sequential' || orderOptions === 'simultaneous') {		        // Just add the series from the chart		        order = chart.series.reduce(function (seriesList, series) {		            if (series.visible) {		                seriesList.push({		                    series: series,		                    seriesOptions: seriesOptionsCallback(series)		                });		            }		            return seriesList;		        }, []);		        // If order is simultaneous, group all series together		        if (orderOptions === 'simultaneous') {		            order = [order];		        }		    } else {		        // We have a specific order, and potentially custom items - like		        // earcons or silent waits.		        order = orderOptions.reduce(function (orderList, orderDef) {		            // Return set of items to play simultaneously. Could be only one.		            var simulItems = H.splat(orderDef).reduce(function (items, item) {		                var itemObject;		                // Is this item a series ID?		                if (typeof item === 'string') {		                    var series = chart.get(item);		                    if (series.visible) {		                        itemObject = {		                            series: series,		                            seriesOptions: seriesOptionsCallback(series)		                        };		                    }		                // Is it an earcon? If so, just create the path.		                } else if (item instanceof H.sonification.Earcon) {		                    // Path with a single event		                    itemObject = new H.sonification.TimelinePath({		                        events: [new H.sonification.TimelineEvent({		                            eventObject: item		                        })]		                    });		                }		                // Is this item a silent wait? If so, just create the path.		                if (item.silentWait) {		                    itemObject = new H.sonification.TimelinePath({		                        silentWait: item.silentWait		                    });		                }		                // Add to items to play simultaneously		                if (itemObject) {		                    items.push(itemObject);		                }		                return items;		            }, []);		            // Add to order list		            if (simulItems.length) {		                orderList.push(simulItems);		            }		            return orderList;		        }, []);		    }		    return order;		}		/**		 * Utility function to add a silent wait after all series.		 * @private		 * @param {Array<object|Array<object|TimelinePath>>} order - The order of items.		 * @param {number} wait - The wait in milliseconds to add.		 * @return {Array<object|Array<object|TimelinePath>>} The order with waits inserted.		 */		function addAfterSeriesWaits(order, wait) {		    if (!wait) {		        return order;		    }		    return order.reduce(function (newOrder, orderDef, i) {		        var simultaneousPaths = H.splat(orderDef);		        newOrder.push(simultaneousPaths);		        // Go through the simultaneous paths and see if there is a series there		        if (		            i < order.length - 1 && // Do not add wait after last series		            simultaneousPaths.some(function (item) {		                return item.series;		            })		        ) {		            // We have a series, meaning we should add a wait after these		            // paths have finished.		            newOrder.push(new H.sonification.TimelinePath({		                silentWait: wait		            }));		        }		        return newOrder;		    }, []);		}		/**		 * Utility function to find the total amout of wait time in the TimelinePaths.		 * @private		 * @param {Array<object|Array<object|TimelinePath>>} order - The order of		 * TimelinePaths/items.		 * @return {number} The total time in ms spent on wait paths between playing.		 */		function getWaitTime(order) {		    return order.reduce(function (waitTime, orderDef) {		        var def = H.splat(orderDef);		        return waitTime + (		            def.length === 1 && def[0].options && def[0].options.silentWait || 0		        );		    }, 0);		}		/**		 * Utility function to ensure simultaneous paths have start/end events at the		 * same time, to sync them.		 * @private		 * @param {Array<Highcharts.TimelinePath>} paths - The paths to sync.		 */		function syncSimultaneousPaths(paths) {		    // Find the extremes for these paths		    var extremes = paths.reduce(function (extremes, path) {		        var events = path.events;		        if (events && events.length) {		            extremes.min = Math.min(events[0].time, extremes.min);		            extremes.max = Math.max(		                events[events.length - 1].time, extremes.max		            );		        }		        return extremes;		    }, {		        min: Infinity,		        max: -Infinity		    });		    // Go through the paths and add events to make them fit the same timespan		    paths.forEach(function (path) {		        var events = path.events,		            hasEvents = events && events.length,		            eventsToAdd = [];		        if (!(hasEvents && events[0].time <= extremes.min)) {		            eventsToAdd.push(new H.sonification.TimelineEvent({		                time: extremes.min		            }));		        }		        if (!(hasEvents && events[events.length - 1].time >= extremes.max)) {		            eventsToAdd.push(new H.sonification.TimelineEvent({		                time: extremes.max		            }));		        }		        if (eventsToAdd.length) {		            path.addTimelineEvents(eventsToAdd);		        }		    });		}		/**		 * Utility function to find the total duration span for all simul path sets		 * that include series.		 * @private		 * @param {Array<object|Array<object|Highcharts.TimelinePath>>} order - The		 * order of TimelinePaths/items.		 * @return {number} The total time value span difference for all series.		 */		function getSimulPathDurationTotal(order) {		    return order.reduce(function (durationTotal, orderDef) {		        return durationTotal + H.splat(orderDef).reduce(		            function (maxPathDuration, item) {		                var timeExtremes = item.series && item.seriesOptions &&		                        item.seriesOptions.timeExtremes;		                return timeExtremes ?		                    Math.max(		                        maxPathDuration, timeExtremes.max - timeExtremes.min		                    ) : maxPathDuration;		            },		            0		        );		    }, 0);		}		/**		 * Function to calculate the duration in ms for a series.		 * @private		 * @param {number} seriesValueDuration - The duration of the series in value		 * difference.		 * @param {number} totalValueDuration - The total duration of all (non		 * simultaneous) series in value difference.		 * @param {number} totalDurationMs - The desired total duration for all series		 * in milliseconds.		 * @return {number} The duration for the series in milliseconds.		 */		function getSeriesDurationMs(		    seriesValueDuration, totalValueDuration, totalDurationMs		) {		    // A series spanning the whole chart would get the full duration.		    return utilities.virtualAxisTranslate(		        seriesValueDuration,		        { min: 0, max: totalValueDuration },		        { min: 0, max: totalDurationMs }		    );		}		/**		 * Convert series building objects into paths and return a new list of		 * TimelinePaths.		 * @private		 * @param {Array<object|Array<object|Highcharts.TimelinePath>>} order - The		 * order list.		 * @param {number} duration - Total duration to aim for in milliseconds.		 * @return {Array<Array<Highcharts.TimelinePath>>} Array of TimelinePath objects		 * to play.		 */		function buildPathsFromOrder(order, duration) {		    // Find time used for waits (custom or after series), and subtract it from		    // available duration.		    var totalAvailableDurationMs = Math.max(		            duration - getWaitTime(order), 0		        ),		        // Add up simultaneous path durations to find total value span duration		        // of everything		        totalUsedDuration = getSimulPathDurationTotal(order);		    // Go through the order list and convert the items		    return order.reduce(function (allPaths, orderDef) {		        var simultaneousPaths = H.splat(orderDef).reduce(		            function (simulPaths, item) {		                if (item instanceof H.sonification.TimelinePath) {		                    // This item is already a path object		                    simulPaths.push(item);		                } else if (item.series) {		                    // We have a series.		                    // We need to set the duration of the series		                    item.seriesOptions.duration =		                        item.seriesOptions.duration || getSeriesDurationMs(		                            item.seriesOptions.timeExtremes.max -		                            item.seriesOptions.timeExtremes.min,		                            totalUsedDuration,		                            totalAvailableDurationMs		                        );		                    // Add the path		                    simulPaths.push(buildTimelinePathFromSeries(		                        item.series,		                        item.seriesOptions		                    ));		                }		                return simulPaths;		            }, []		        );		        // Add in the simultaneous paths		        allPaths.push(simultaneousPaths);		        return allPaths;		    }, []);		}		/**		 * Options for sonifying a chart.		 *		 * @requires module:modules/sonification		 *		 * @interface Highcharts.SonifyChartOptionsObject		 *//**		 * Duration for sonifying the entire chart. The duration is distributed across		 * the different series intelligently, but does not take earcons into account.		 * It is also possible to set the duration explicitly per series, using		 * `seriesOptions`. Note that points may continue to play after the duration has		 * passed, but no new points will start playing.		 * @name Highcharts.SonifyChartOptionsObject#duration		 * @type {number}		 *//**		 * Define the order to play the series in. This can be given as a string, or an		 * array specifying a custom ordering. If given as a string, valid values are		 * `sequential` - where each series is played in order - or `simultaneous`,		 * where all series are played at once. For custom ordering, supply an array as		 * the order. Each element in the array can be either a string with a series ID,		 * an Earcon object, or an object with a numeric `silentWait` property		 * designating a number of milliseconds to wait before continuing. Each element		 * of the array will be played in order. To play elements simultaneously, group		 * the elements in an array.		 * @name Highcharts.SonifyChartOptionsObject#order		 * @type {string|Array<string|Highcharts.Earcon|Array<string|Highcharts.Earcon>>}		 *//**		 * The axis to use for when to play the points. Can be a string with a data		 * property (e.g. `x`), or a function. If it is a function, this function		 * receives the point as argument, and should return a numeric value. The points		 * with the lowest numeric values are then played first, and the time between		 * points will be proportional to the distance between the numeric values. This		 * option can not be overridden per series.		 * @name Highcharts.SonifyChartOptionsObject#pointPlayTime		 * @type {string|Function}		 *//**		 * Milliseconds of silent waiting to add between series. Note that waiting time		 * is considered part of the sonify duration.		 * @name Highcharts.SonifyChartOptionsObject#afterSeriesWait		 * @type {number|undefined}		 *//**		 * Options as given to `series.sonify` to override options per series. If the		 * option is supplied as an array of options objects, the `id` property of the		 * object should correspond to the series' id. If the option is supplied as a		 * single object, the options apply to all series.		 * @name Highcharts.SonifyChartOptionsObject#seriesOptions		 * @type {Object|Array<object>|undefined}		 *//**		 * The instrument definitions for the points in this chart.		 * @name Highcharts.SonifyChartOptionsObject#instruments		 * @type {Array<Highcharts.PointInstrumentObject>|undefined}		 *//**		 * Earcons to add to the chart. Note that earcons can also be added per series		 * using `seriesOptions`.		 * @name Highcharts.SonifyChartOptionsObject#earcons		 * @type {Array<Highcharts.EarconConfiguration>|undefined}		 *//**		 * Optionally provide the minimum/maximum data values for the points. If this is		 * not supplied, it is calculated from all points in the chart on demand. This		 * option is supplied in the following format, as a map of point data properties		 * to objects with min/max values:		 *  ```js		 *      dataExtremes: {		 *          y: {		 *              min: 0,		 *              max: 100		 *          },		 *          z: {		 *              min: -10,		 *              max: 10		 *          }		 *          // Properties used and not provided are calculated on demand		 *      }		 *  ```		 * @name Highcharts.SonifyChartOptionsObject#dataExtremes		 * @type {object|undefined}		 *//**		 * Callback before a series is played.		 * @name Highcharts.SonifyChartOptionsObject#onSeriesStart		 * @type {Function|undefined}		 *//**		 * Callback after a series has finished playing.		 * @name Highcharts.SonifyChartOptionsObject#onSeriesEnd		 * @type {Function|undefined}		 *//**		 * Callback after the chart has played.		 * @name Highcharts.SonifyChartOptionsObject#onEnd		 * @type {Function|undefined}		 */		/**		 * Sonify a chart.		 *		 * @sample highcharts/sonification/chart-sequential/		 *         Sonify a basic chart		 * @sample highcharts/sonification/chart-simultaneous/		 *         Sonify series simultaneously		 * @sample highcharts/sonification/chart-custom-order/		 *         Custom defined order of series		 * @sample highcharts/sonification/chart-earcon/		 *         Earcons on chart		 * @sample highcharts/sonification/chart-events/		 *         Sonification events on chart		 *		 * @requires module:modules/sonification		 *		 * @function Highcharts.Chart#sonify		 *		 * @param {Highcharts.SonifyChartOptionsObject} options		 *        The options for sonifying this chart.		 */		function chartSonify(options) {		    // Only one timeline can play at a time.		    if (this.sonification.timeline) {		        this.sonification.timeline.pause();		    }		    // Calculate data extremes for the props used		    var dataExtremes = getExtremesForInstrumentProps(		        this, options.instruments, options.dataExtremes		    );		    // Figure out ordering of series and custom paths		    var order = buildPathOrder(options.order, this, function (series) {		        return buildSeriesOptions(series, dataExtremes, options);		    });		    // Add waits after simultaneous paths with series in them.		    order = addAfterSeriesWaits(order, options.afterSeriesWait || 0);		    // We now have a list of either TimelinePath objects or series that need to		    // be converted to TimelinePath objects. Convert everything to paths.		    var paths = buildPathsFromOrder(order, options.duration);		    // Sync simultaneous paths		    paths.forEach(function (simultaneousPaths) {		        syncSimultaneousPaths(simultaneousPaths);		    });		    // We have a set of paths. Create the timeline, and play it.		    this.sonification.timeline = new H.sonification.Timeline({		        paths: paths,		        onEnd: options.onEnd		    });		    this.sonification.timeline.play();		}		/**		 * Get a list of the points currently under cursor.		 *		 * @requires module:modules/sonification		 *		 * @function Highcharts.Chart#getCurrentSonifyPoints		 *		 * @return {Array<Highcharts.Point>}		 *         The points currently under the cursor.		 */		function getCurrentPoints() {		    var cursorObj;		    if (this.sonification.timeline) {		        cursorObj = this.sonification.timeline.getCursor(); // Cursor per pathID		        return Object.keys(cursorObj).map(function (path) {		            // Get the event objects under cursor for each path		            return cursorObj[path].eventObject;		        }).filter(function (eventObj) {		            // Return the events that are points		            return eventObj instanceof H.Point;		        });		    }		    return [];		}		/**		 * Set the cursor to a point or set of points in different series.		 *		 * @requires module:modules/sonification		 *		 * @function Highcharts.Chart#setSonifyCursor		 *		 * @param {Highcharts.Point|Array<Highcharts.Point>} points		 *        The point or points to set the cursor to. If setting multiple points		 *        under the cursor, the points have to be in different series that are		 *        being played simultaneously.		 */		function setCursor(points) {		    var timeline = this.sonification.timeline;		    if (timeline) {		        H.splat(points).forEach(function (point) {		            // We created the events with the ID of the points, which makes		            // this easy. Just call setCursor for each ID.		            timeline.setCursor(point.id);		        });		    }		}		/**		 * Pause the running sonification.		 *		 * @requires module:modules/sonification		 *		 * @function Highcharts.Chart#pauseSonify		 *		 * @param {boolean} [fadeOut=true]		 *        Fade out as we pause to avoid clicks.		 */		function pause(fadeOut) {		    if (this.sonification.timeline) {		        this.sonification.timeline.pause(H.pick(fadeOut, true));		    } else if (this.sonification.currentlyPlayingPoint) {		        this.sonification.currentlyPlayingPoint.cancelSonify(fadeOut);		    }		}		/**		 * Resume the currently running sonification. Requires series.sonify or		 * chart.sonify to have been played at some point earlier.		 *		 * @requires module:modules/sonification		 *		 * @function Highcharts.Chart#resumeSonify		 *		 * @param {Function} onEnd		 *        Callback to call when play finished.		 */		function resume(onEnd) {		    if (this.sonification.timeline) {		        this.sonification.timeline.play(onEnd);		    }		}		/**		 * Play backwards from cursor. Requires series.sonify or chart.sonify to have		 * been played at some point earlier.		 *		 * @requires module:modules/sonification		 *		 * @function Highcharts.Chart#rewindSonify		 *		 * @param {Function} onEnd		 *        Callback to call when play finished.		 */		function rewind(onEnd) {		    if (this.sonification.timeline) {		        this.sonification.timeline.rewind(onEnd);		    }		}		/**		 * Cancel current sonification and reset cursor.		 *		 * @requires module:modules/sonification		 *		 * @function Highcharts.Chart#cancelSonify		 *		 * @param {boolean} [fadeOut=true]		 *        Fade out as we pause to avoid clicks.		 */		function cancel(fadeOut) {		    this.pauseSonify(fadeOut);		    this.resetSonifyCursor();		}		/**		 * Reset cursor to start. Requires series.sonify or chart.sonify to have been		 * played at some point earlier.		 *		 * @requires module:modules/sonification		 *		 * @function Highcharts.Chart#resetSonifyCursor		 */		function resetCursor() {		    if (this.sonification.timeline) {		        this.sonification.timeline.resetCursor();		    }		}		/**		 * Reset cursor to end. Requires series.sonify or chart.sonify to have been		 * played at some point earlier.		 *		 * @requires module:modules/sonification		 *		 * @function Highcharts.Chart#resetSonifyCursorEnd		 */		function resetCursorEnd() {		    if (this.sonification.timeline) {		        this.sonification.timeline.resetCursorEnd();		    }		}		// Export functions		var chartSonifyFunctions = {		    chartSonify: chartSonify,		    seriesSonify: seriesSonify,		    pause: pause,		    resume: resume,		    rewind: rewind,		    cancel: cancel,		    getCurrentPoints: getCurrentPoints,		    setCursor: setCursor,		    resetCursor: resetCursor,		    resetCursorEnd: resetCursorEnd		};		return chartSonifyFunctions;	}(Highcharts, utilities));	var timelineClasses = (function (H, utilities) {		/* *		 *		 *  (c) 2009-2019 Øystein Moseng		 *		 *  TimelineEvent class definition.		 *		 *  License: www.highcharts.com/license		 *		 * */		/**		 * A set of options for the TimelineEvent class.		 *		 * @requires module:modules/sonification		 *		 * @private		 * @interface Highcharts.TimelineEventOptionsObject		 *//**		 * The object we want to sonify when playing the TimelineEvent. Can be any		 * object that implements the `sonify` and `cancelSonify` functions. If this is		 * not supplied, the TimelineEvent is considered a silent event, and the onEnd		 * event is immediately called.		 * @name Highcharts.TimelineEventOptionsObject#eventObject		 * @type {*}		 *//**		 * Options to pass on to the eventObject when playing it.		 * @name Highcharts.TimelineEventOptionsObject#playOptions		 * @type {object|undefined}		 *//**		 * The time at which we want this event to play (in milliseconds offset). This		 * is not used for the TimelineEvent.play function, but rather intended as a		 * property to decide when to call TimelineEvent.play. Defaults to 0.		 * @name Highcharts.TimelineEventOptionsObject#time		 * @type {number|undefined}		 *//**		 * Unique ID for the event. Generated automatically if not supplied.		 * @name Highcharts.TimelineEventOptionsObject#id		 * @type {string|undefined}		 *//**		 * Callback called when the play has finished.		 * @name Highcharts.TimelineEventOptionsObject#onEnd		 * @type {Function|undefined}		 */		/**		 * The TimelineEvent class. Represents a sound event on a timeline.		 *		 * @requires module:modules/sonification		 *		 * @private		 * @class		 * @name Highcharts.TimelineEvent		 *		 * @param {Highcharts.TimelineEventOptionsObject} options		 *        Options for the TimelineEvent.		 */		function TimelineEvent(options) {		    this.init(options || {});		}		TimelineEvent.prototype.init = function (options) {		    this.options = options;		    this.time = options.time || 0;		    this.id = this.options.id = options.id || H.uniqueKey();		};		/**		 * Play the event. Does not take the TimelineEvent.time option into account,		 * and plays the event immediately.		 *		 * @function Highcharts.TimelineEvent#play		 *		 * @param {Highcharts.TimelineEventOptionsObject} [options]		 *        Options to pass in to the eventObject when playing it.		 */		TimelineEvent.prototype.play = function (options) {		    var eventObject = this.options.eventObject,		        masterOnEnd = this.options.onEnd,		        playOnEnd = options && options.onEnd,		        playOptionsOnEnd = this.options.playOptions &&		            this.options.playOptions.onEnd,		        playOptions = H.merge(this.options.playOptions, options);		    if (eventObject && eventObject.sonify) {		        // If we have multiple onEnds defined, use all		        playOptions.onEnd = masterOnEnd || playOnEnd || playOptionsOnEnd ?		            function () {		                var args = arguments;		                [masterOnEnd, playOnEnd, playOptionsOnEnd].forEach(		                    function (onEnd) {		                        if (onEnd) {		                            onEnd.apply(this, args);		                        }		                    }		                );		            } : undefined;		        eventObject.sonify(playOptions);		    } else {		        if (playOnEnd) {		            playOnEnd();		        }		        if (masterOnEnd) {		            masterOnEnd();		        }		    }		};		/**		 * Cancel the sonification of this event. Does nothing if the event is not		 * currently sonifying.		 *		 * @function Highcharts.TimelineEvent#cancel		 *		 * @param {boolean} [fadeOut=false]		 *        Whether or not to fade out as we stop. If false, the event is		 *        cancelled synchronously.		 */		TimelineEvent.prototype.cancel = function (fadeOut) {		    this.options.eventObject.cancelSonify(fadeOut);		};		/**		 * A set of options for the TimelinePath class.		 *		 * @requires module:modules/		 *		 * @private		 * @interface Highcharts.TimelinePathOptionsObject		 *//**		 * List of TimelineEvents to play on this track.		 * @name Highcharts.TimelinePathOptionsObject#events		 * @type {Array<Highcharts.TimelineEvent>}		 *//**		 * If this option is supplied, this path ignores all events and just waits for		 * the specified number of milliseconds before calling onEnd.		 * @name Highcharts.TimelinePathOptionsObject#silentWait		 * @type {number|undefined}		 *//**		 * Unique ID for this timeline path. Automatically generated if not supplied.		 * @name Highcharts.TimelinePathOptionsObject#id		 * @type {string|undefined}		 *//**		 * Callback called before the path starts playing.		 * @name Highcharts.TimelinePathOptionsObject#onStart		 * @type {Function|undefined}		 *//**		 * Callback function to call before an event plays.		 * @name Highcharts.TimelinePathOptionsObject#onEventStart		 * @type {Function|undefined}		 *//**		 * Callback function to call after an event has stopped playing.		 * @name Highcharts.TimelinePathOptionsObject#onEventEnd		 * @type {Function|undefined}		 *//**		 * Callback called when the whole path is finished.		 * @name Highcharts.TimelinePathOptionsObject#onEnd		 * @type {Function|undefined}		 */		/**		 * The TimelinePath class. Represents a track on a timeline with a list of		 * sound events to play at certain times relative to each other.		 *		 * @requires module:modules/sonification		 *		 * @private		 * @class		 * @name Highcharts.TimelinePath		 *		 * @param {Highcharts.TimelinePathOptionsObject} options		 *        Options for the TimelinePath.		 */		function TimelinePath(options) {		    this.init(options);		}		TimelinePath.prototype.init = function (options) {		    this.options = options;		    this.id = this.options.id = options.id || H.uniqueKey();		    this.cursor = 0;		    this.eventsPlaying = {};		    // Handle silent wait, otherwise use events from options		    this.events = options.silentWait ?		        [		            new TimelineEvent({ time: 0 }),		            new TimelineEvent({ time: options.silentWait })		        ] :		        this.options.events;		    // We need to sort our events by time		    this.sortEvents();		    // Get map from event ID to index		    this.updateEventIdMap();		    // Signal events to fire		    this.signalHandler = new utilities.SignalHandler(		        ['playOnEnd', 'masterOnEnd', 'onStart', 'onEventStart', 'onEventEnd']		    );		    this.signalHandler.registerSignalCallbacks(		        H.merge(options, { masterOnEnd: options.onEnd })		    );		};		/**		 * Sort the internal event list by time.		 * @private		 */		TimelinePath.prototype.sortEvents = function () {		    this.events = this.events.sort(function (a, b) {		        return a.time - b.time;		    });		};		/**		 * Update the internal eventId to index map.		 * @private		 */		TimelinePath.prototype.updateEventIdMap = function () {		    this.eventIdMap = this.events.reduce(function (acc, cur, i) {		        acc[cur.id] = i;		        return acc;		    }, {});		};		/**		 * Add events to the path. Should not be done while the path is playing.		 * The new events are inserted according to their time property.		 * @private		 * @param {Array<Highcharts.TimelineEvent>} newEvents - The new timeline events		 * to add.		 */		TimelinePath.prototype.addTimelineEvents = function (newEvents) {		    this.events = this.events.concat(newEvents);		    this.sortEvents(); // Sort events by time		    this.updateEventIdMap(); // Update the event ID to index map		};		/**		 * Get the current TimelineEvent under the cursor.		 * @private		 * @return {Highcharts.TimelineEvent} The current timeline event.		 */		TimelinePath.prototype.getCursor = function () {		    return this.events[this.cursor];		};		/**		 * Set the current TimelineEvent under the cursor.		 * @private		 * @param {string} eventId - The ID of the timeline event to set as current.		 * @return {boolean} True if there is an event with this ID in the path. False		 * otherwise.		 */		TimelinePath.prototype.setCursor = function (eventId) {		    var ix = this.eventIdMap[eventId];		    if (ix !== undefined) {		        this.cursor = ix;		        return true;		    }		    return false;		};		/**		 * Play the timeline from the current cursor.		 * @private		 * @param {Function} onEnd - Callback to call when play finished. Does not		 * override other onEnd callbacks.		 */		TimelinePath.prototype.play = function (onEnd) {		    this.pause();		    this.signalHandler.emitSignal('onStart');		    this.signalHandler.clearSignalCallbacks(['playOnEnd']);		    this.signalHandler.registerSignalCallbacks({ playOnEnd: onEnd });		    this.playEvents(1);		};		/**		 * Play the timeline backwards from the current cursor.		 * @private		 * @param {Function} onEnd - Callback to call when play finished. Does not		 * override other onEnd callbacks.		 */		TimelinePath.prototype.rewind = function (onEnd) {		    this.pause();		    this.signalHandler.emitSignal('onStart');		    this.signalHandler.clearSignalCallbacks(['playOnEnd']);		    this.signalHandler.registerSignalCallbacks({ playOnEnd: onEnd });		    this.playEvents(-1);		};		/**		 * Reset the cursor to the beginning.		 * @private		 */		TimelinePath.prototype.resetCursor = function () {		    this.cursor = 0;		};		/**		 * Reset the cursor to the end.		 * @private		 */		TimelinePath.prototype.resetCursorEnd = function () {		    this.cursor = this.events.length - 1;		};		/**		 * Cancel current playing. Leaves the cursor intact.		 * @private		 * @param {boolean} [fadeOut=false] - Whether or not to fade out as we stop. If		 * false, the path is cancelled synchronously.		 */		TimelinePath.prototype.pause = function (fadeOut) {		    var timelinePath = this;		    // Cancel next scheduled play		    clearTimeout(timelinePath.nextScheduledPlay);		    // Cancel currently playing events		    Object.keys(timelinePath.eventsPlaying).forEach(function (id) {		        if (timelinePath.eventsPlaying[id]) {		            timelinePath.eventsPlaying[id].cancel(fadeOut);		        }		    });		    timelinePath.eventsPlaying = {};		};		/**		 * Play the events, starting from current cursor, and going in specified		 * direction.		 * @private		 * @param {number} direction - The direction to play, 1 for forwards and -1 for		 * backwards.		 */		TimelinePath.prototype.playEvents = function (direction) {		    var timelinePath = this,		        curEvent = timelinePath.events[this.cursor],		        nextEvent = timelinePath.events[this.cursor + direction],		        timeDiff,		        onEnd = function (signalData) {		            timelinePath.signalHandler.emitSignal(		                'masterOnEnd', signalData		            );		            timelinePath.signalHandler.emitSignal(		                'playOnEnd', signalData		            );		        };		    // Store reference to path on event		    curEvent.timelinePath = timelinePath;		    // Emit event, cancel if returns false		    if (		        timelinePath.signalHandler.emitSignal(		            'onEventStart', curEvent		        ) === false		    ) {		        onEnd({		            event: curEvent,		            cancelled: true		        });		        return;		    }		    // Play the current event		    timelinePath.eventsPlaying[curEvent.id] = curEvent;		    curEvent.play({		        onEnd: function (cancelled) {		            var signalData = {		                event: curEvent,		                cancelled: !!cancelled		            };		            // Keep track of currently playing events for cancelling		            delete timelinePath.eventsPlaying[curEvent.id];		            // Handle onEventEnd		            timelinePath.signalHandler.emitSignal('onEventEnd', signalData);		            // Reached end of path?		            if (!nextEvent) {		                onEnd(signalData);		            }		        }		    });		    // Schedule next		    if (nextEvent) {		        timeDiff = Math.abs(nextEvent.time - curEvent.time);		        if (timeDiff < 1) {		            // Play immediately		            timelinePath.cursor += direction;		            timelinePath.playEvents(direction);		        } else {		            // Schedule after the difference in ms		            this.nextScheduledPlay = setTimeout(function () {		                timelinePath.cursor += direction;		                timelinePath.playEvents(direction);		            }, timeDiff);		        }		    }		};		/* ************************************************************************** *		 *  TIMELINE                                                                  *		 * ************************************************************************** */		/**		 * A set of options for the Timeline class.		 *		 * @requires module:modules/sonification		 *		 * @private		 * @interface Highcharts.TimelineOptionsObject		 *//**		 * List of TimelinePaths to play. Multiple paths can be grouped together and		 * played simultaneously by supplying an array of paths in place of a single		 * path.		 * @name Highcharts.TimelineOptionsObject#paths		 * @type {Array<Highcharts.TimelinePath|Array<Highcharts.TimelinePath>>}		 *//**		 * Callback function to call before a path plays.		 * @name Highcharts.TimelineOptionsObject#onPathStart		 * @type {Function|undefined}		 *//**		 * Callback function to call after a path has stopped playing.		 * @name Highcharts.TimelineOptionsObject#onPathEnd		 * @type {Function|undefined}		 *//**		 * Callback called when the whole path is finished.		 * @name Highcharts.TimelineOptionsObject#onEnd		 * @type {Function|undefined}		 */		/**		 * The Timeline class. Represents a sonification timeline with a list of		 * timeline paths with events to play at certain times relative to each other.		 *		 * @requires module:modules/sonification		 *		 * @private		 * @class		 * @name Highcharts.Timeline		 *		 * @param {Highcharts.TimelineOptionsObject} options		 *        Options for the Timeline.		 */		function Timeline(options) {		    this.init(options || {});		}		Timeline.prototype.init = function (options) {		    this.options = options;		    this.cursor = 0;		    this.paths = options.paths;		    this.pathsPlaying = {};		    this.signalHandler = new utilities.SignalHandler(		        ['playOnEnd', 'masterOnEnd', 'onPathStart', 'onPathEnd']		    );		    this.signalHandler.registerSignalCallbacks(		        H.merge(options, { masterOnEnd: options.onEnd })		    );		};		/**		 * Play the timeline forwards from cursor.		 * @private		 * @param {Function} onEnd - Callback to call when play finished. Does not		 * override other onEnd callbacks.		 */		Timeline.prototype.play = function (onEnd) {		    this.pause();		    this.signalHandler.clearSignalCallbacks(['playOnEnd']);		    this.signalHandler.registerSignalCallbacks({ playOnEnd: onEnd });		    this.playPaths(1);		};		/**		 * Play the timeline backwards from cursor.		 * @private		 * @param {Function} onEnd - Callback to call when play finished. Does not		 * override other onEnd callbacks.		 */		Timeline.prototype.rewind = function (onEnd) {		    this.pause();		    this.signalHandler.clearSignalCallbacks(['playOnEnd']);		    this.signalHandler.registerSignalCallbacks({ playOnEnd: onEnd });		    this.playPaths(-1);		};		/**		 * Play the timeline in the specified direction.		 * @private		 * @param {number} direction - Direction to play in. 1 for forwards, -1 for		 * backwards.		 */		Timeline.prototype.playPaths = function (direction) {		    var curPaths = H.splat(this.paths[this.cursor]),		        nextPaths = this.paths[this.cursor + direction],		        timeline = this,		        signalHandler = this.signalHandler,		        pathsEnded = 0,		        // Play a path		        playPath = function (path) {		            // Emit signal and set playing state		            signalHandler.emitSignal('onPathStart', path);		            timeline.pathsPlaying[path.id] = path;		            // Do the play		            path[direction > 0 ? 'play' : 'rewind'](function (callbackData) {		                // Play ended callback		                // Data to pass to signal callbacks		                var cancelled = callbackData && callbackData.cancelled,		                    signalData = {		                        path: path,		                        cancelled: cancelled		                    };		                // Clear state and send signal		                delete timeline.pathsPlaying[path.id];		                signalHandler.emitSignal('onPathEnd', signalData);		                // Handle next paths		                pathsEnded++;		                if (pathsEnded >= curPaths.length) {		                    // We finished all of the current paths for cursor.		                    if (nextPaths && !cancelled) {		                        // We have more paths, move cursor along		                        timeline.cursor += direction;		                        // Reset upcoming path cursors before playing		                        H.splat(nextPaths).forEach(function (nextPath) {		                            nextPath[		                                direction > 0 ? 'resetCursor' : 'resetCursorEnd'		                            ]();		                        });		                        // Play next		                        timeline.playPaths(direction);		                    } else {		                        // If it is the last path in this direction, call onEnd		                        signalHandler.emitSignal('playOnEnd', signalData);		                        signalHandler.emitSignal('masterOnEnd', signalData);		                    }		                }		            });		        };		    // Go through the paths under cursor and play them		    curPaths.forEach(function (path) {		        if (path) {		            // Store reference to timeline		            path.timeline = timeline;		            // Leave a timeout to let notes fade out before next play		            setTimeout(function () {		                playPath(path);		            }, H.sonification.fadeOutTime);		        }		    });		};		/**		 * Stop the playing of the timeline. Cancels all current sounds, but does not		 * affect the cursor.		 * @private		 * @param {boolean} [fadeOut=false] - Whether or not to fade out as we stop. If		 * false, the timeline is cancelled synchronously.		 */		Timeline.prototype.pause = function (fadeOut) {		    var timeline = this;		    // Cancel currently playing events		    Object.keys(timeline.pathsPlaying).forEach(function (id) {		        if (timeline.pathsPlaying[id]) {		            timeline.pathsPlaying[id].pause(fadeOut);		        }		    });		    timeline.pathsPlaying = {};		};		/**		 * Reset the cursor to the beginning of the timeline.		 * @private		 */		Timeline.prototype.resetCursor = function () {		    this.paths.forEach(function (paths) {		        H.splat(paths).forEach(function (path) {		            path.resetCursor();		        });		    });		    this.cursor = 0;		};		/**		 * Reset the cursor to the end of the timeline.		 * @private		 */		Timeline.prototype.resetCursorEnd = function () {		    this.paths.forEach(function (paths) {		        H.splat(paths).forEach(function (path) {		            path.resetCursorEnd();		        });		    });		    this.cursor = this.paths.length - 1;		};		/**		 * Set the current TimelineEvent under the cursor. If multiple paths are being		 * played at the same time, this function only affects a single path (the one		 * that contains the eventId that is passed in).		 * @private		 * @param {string} eventId - The ID of the timeline event to set as current.		 * @return {boolean} True if the cursor was set, false if no TimelineEvent was		 * found for this ID.		 */		Timeline.prototype.setCursor = function (eventId) {		    return this.paths.some(function (paths) {		        return H.splat(paths).some(function (path) {		            return path.setCursor(eventId);		        });		    });		};		/**		 * Get the current TimelineEvents under the cursors. This function will return		 * the event under the cursor for each currently playing path, as an object		 * where the path ID is mapped to the TimelineEvent under that path's cursor.		 * @private		 * @return {object} The TimelineEvents under each path's cursors.		 */		Timeline.prototype.getCursor = function () {		    return this.getCurrentPlayingPaths().reduce(function (acc, cur) {		        acc[cur.id] = cur.getCursor();		        return acc;		    }, {});		};		/**		 * Check if timeline is reset or at start.		 * @private		 * @return {boolean} True if timeline is at the beginning.		 */		Timeline.prototype.atStart = function () {		    return !this.getCurrentPlayingPaths().some(function (path) {		        return path.cursor;		    });		};		/**		 * Get the current TimelinePaths being played.		 * @private		 * @return {Array<Highcharts.TimelinePath>} The TimelinePaths currently being		 * played.		 */		Timeline.prototype.getCurrentPlayingPaths = function () {		    return H.splat(this.paths[this.cursor]);		};		// Export the classes		var timelineClasses = {		    TimelineEvent: TimelineEvent,		    TimelinePath: TimelinePath,		    Timeline: Timeline		};		return timelineClasses;	}(Highcharts, utilities));	(function (H, Instrument, instruments, Earcon, pointSonifyFunctions, chartSonifyFunctions, utilities, TimelineClasses) {		/* *		 *		 *  (c) 2009-2019 Øystein Moseng		 *		 *  Sonification module for Highcharts		 *		 *  License: www.highcharts.com/license		 *		 * */		// Expose on the Highcharts object		/**		 * 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}		 */		H.sonification = {		    fadeOutDuration: 20,		    // Classes and functions		    utilities: utilities,		    Instrument: Instrument,		    instruments: instruments,		    Earcon: Earcon,		    TimelineEvent: TimelineClasses.TimelineEvent,		    TimelinePath: TimelineClasses.TimelinePath,		    Timeline: TimelineClasses.Timeline		};		// Chart specific		H.Point.prototype.sonify = pointSonifyFunctions.pointSonify;		H.Point.prototype.cancelSonify = pointSonifyFunctions.pointCancelSonify;		H.Series.prototype.sonify = chartSonifyFunctions.seriesSonify;		H.extend(H.Chart.prototype, {		    sonify: chartSonifyFunctions.chartSonify,		    pauseSonify: chartSonifyFunctions.pause,		    resumeSonify: chartSonifyFunctions.resume,		    rewindSonify: chartSonifyFunctions.rewind,		    cancelSonify: chartSonifyFunctions.cancel,		    getCurrentSonifyPoints: chartSonifyFunctions.getCurrentPoints,		    setSonifyCursor: chartSonifyFunctions.setCursor,		    resetSonifyCursor: chartSonifyFunctions.resetCursor,		    resetSonifyCursorEnd: chartSonifyFunctions.resetCursorEnd,		    sonification: {}		});	}(Highcharts, Instrument, instruments, Earcon, pointSonifyFunctions, chartSonifyFunctions, utilities, timelineClasses));	return (function () {	}());}));
 |