sonification.src.js 110 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341
  1. /**
  2. * @license Highcharts JS v7.0.2 (2019-01-17)
  3. * Sonification module
  4. *
  5. * (c) 2012-2019 Øystein Moseng
  6. *
  7. * License: www.highcharts.com/license
  8. */
  9. 'use strict';
  10. (function (factory) {
  11. if (typeof module === 'object' && module.exports) {
  12. factory['default'] = factory;
  13. module.exports = factory;
  14. } else if (typeof define === 'function' && define.amd) {
  15. define(function () {
  16. return factory;
  17. });
  18. } else {
  19. factory(typeof Highcharts !== 'undefined' ? Highcharts : undefined);
  20. }
  21. }(function (Highcharts) {
  22. var Instrument = (function (H) {
  23. /* *
  24. *
  25. * (c) 2009-2019 Øystein Moseng
  26. *
  27. * Instrument class for sonification module.
  28. *
  29. * License: www.highcharts.com/license
  30. *
  31. * */
  32. /**
  33. * A set of options for the Instrument class.
  34. *
  35. * @requires module:modules/sonification
  36. *
  37. * @interface Highcharts.InstrumentOptionsObject
  38. *//**
  39. * The type of instrument. Currently only `oscillator` is supported. Defaults
  40. * to `oscillator`.
  41. * @name Highcharts.InstrumentOptionsObject#type
  42. * @type {string|undefined}
  43. *//**
  44. * The unique ID of the instrument. Generated if not supplied.
  45. * @name Highcharts.InstrumentOptionsObject#id
  46. * @type {string|undefined}
  47. *//**
  48. * When using functions to determine frequency or other parameters during
  49. * playback, this options specifies how often to call the callback functions.
  50. * Number given in milliseconds. Defaults to 20.
  51. * @name Highcharts.InstrumentOptionsObject#playCallbackInterval
  52. * @type {number|undefined}
  53. *//**
  54. * A list of allowed frequencies for this instrument. If trying to play a
  55. * frequency not on this list, the closest frequency will be used. Set to `null`
  56. * to allow all frequencies to be used. Defaults to `null`.
  57. * @name Highcharts.InstrumentOptionsObject#allowedFrequencies
  58. * @type {Array<number>|undefined}
  59. *//**
  60. * Options specific to oscillator instruments.
  61. * @name Highcharts.InstrumentOptionsObject#oscillator
  62. * @type {Highcharts.OscillatorOptionsObject|undefined}
  63. */
  64. /**
  65. * Options for playing an instrument.
  66. *
  67. * @requires module:modules/sonification
  68. *
  69. * @interface Highcharts.InstrumentPlayOptionsObject
  70. *//**
  71. * The frequency of the note to play. Can be a fixed number, or a function. The
  72. * function receives one argument: the relative time of the note playing (0
  73. * being the start, and 1 being the end of the note). It should return the
  74. * frequency number for each point in time. The poll interval of this function
  75. * is specified by the Instrument.playCallbackInterval option.
  76. * @name Highcharts.InstrumentPlayOptionsObject#frequency
  77. * @type {number|Function}
  78. *//**
  79. * The duration of the note in milliseconds.
  80. * @name Highcharts.InstrumentPlayOptionsObject#duration
  81. * @type {number}
  82. *//**
  83. * The minimum frequency to allow. If the instrument has a set of allowed
  84. * frequencies, the closest frequency is used by default. Use this option to
  85. * stop too low frequencies from being used.
  86. * @name Highcharts.InstrumentPlayOptionsObject#minFrequency
  87. * @type {number|undefined}
  88. *//**
  89. * The maximum frequency to allow. If the instrument has a set of allowed
  90. * frequencies, the closest frequency is used by default. Use this option to
  91. * stop too high frequencies from being used.
  92. * @name Highcharts.InstrumentPlayOptionsObject#maxFrequency
  93. * @type {number|undefined}
  94. *//**
  95. * The volume of the instrument. Can be a fixed number between 0 and 1, or a
  96. * function. The function receives one argument: the relative time of the note
  97. * playing (0 being the start, and 1 being the end of the note). It should
  98. * return the volume for each point in time. The poll interval of this function
  99. * is specified by the Instrument.playCallbackInterval option. Defaults to 1.
  100. * @name Highcharts.InstrumentPlayOptionsObject#volume
  101. * @type {number|Function|undefined}
  102. *//**
  103. * The panning of the instrument. Can be a fixed number between -1 and 1, or a
  104. * function. The function receives one argument: the relative time of the note
  105. * playing (0 being the start, and 1 being the end of the note). It should
  106. * return the panning value for each point in time. The poll interval of this
  107. * function is specified by the Instrument.playCallbackInterval option.
  108. * Defaults to 0.
  109. * @name Highcharts.InstrumentPlayOptionsObject#pan
  110. * @type {number|Function|undefined}
  111. *//**
  112. * Callback function to be called when the play is completed.
  113. * @name Highcharts.InstrumentPlayOptionsObject#onEnd
  114. * @type {Function|undefined}
  115. */
  116. /**
  117. * @requires module:modules/sonification
  118. *
  119. * @interface Highcharts.OscillatorOptionsObject
  120. *//**
  121. * The waveform shape to use for oscillator instruments. Defaults to `sine`.
  122. * @name Highcharts.OscillatorOptionsObject#waveformShape
  123. * @type {string|undefined}
  124. */
  125. // Default options for Instrument constructor
  126. var defaultOptions = {
  127. type: 'oscillator',
  128. playCallbackInterval: 20,
  129. oscillator: {
  130. waveformShape: 'sine'
  131. }
  132. };
  133. /**
  134. * The Instrument class. Instrument objects represent an instrument capable of
  135. * playing a certain pitch for a specified duration.
  136. *
  137. * @sample highcharts/sonification/instrument/
  138. * Using Instruments directly
  139. * @sample highcharts/sonification/instrument-advanced/
  140. * Using callbacks for instrument parameters
  141. *
  142. * @requires module:modules/sonification
  143. *
  144. * @class
  145. * @name Highcharts.Instrument
  146. *
  147. * @param {Highcharts.InstrumentOptionsObject} options
  148. * Options for the instrument instance.
  149. */
  150. function Instrument(options) {
  151. this.init(options);
  152. }
  153. Instrument.prototype.init = function (options) {
  154. if (!this.initAudioContext()) {
  155. H.error(29);
  156. return;
  157. }
  158. this.options = H.merge(defaultOptions, options);
  159. this.id = this.options.id = options && options.id || H.uniqueKey();
  160. // Init the audio nodes
  161. var ctx = H.audioContext;
  162. this.gainNode = ctx.createGain();
  163. this.setGain(0);
  164. this.panNode = ctx.createStereoPanner && ctx.createStereoPanner();
  165. if (this.panNode) {
  166. this.setPan(0);
  167. this.gainNode.connect(this.panNode);
  168. this.panNode.connect(ctx.destination);
  169. } else {
  170. this.gainNode.connect(ctx.destination);
  171. }
  172. // Oscillator initialization
  173. if (this.options.type === 'oscillator') {
  174. this.initOscillator(this.options.oscillator);
  175. }
  176. // Init timer list
  177. this.playCallbackTimers = [];
  178. };
  179. /**
  180. * Return a copy of an instrument. Only one instrument instance can play at a
  181. * time, so use this to get a new copy of the instrument that can play alongside
  182. * it. The new instrument copy will receive a new ID unless one is supplied in
  183. * options.
  184. *
  185. * @function Highcharts.Instrument#copy
  186. *
  187. * @param {Highcharts.InstrumentOptionsObject} [options]
  188. * Options to merge in for the copy.
  189. *
  190. * @return {Highcharts.Instrument}
  191. * A new Instrument instance with the same options.
  192. */
  193. Instrument.prototype.copy = function (options) {
  194. return new Instrument(H.merge(this.options, { id: null }, options));
  195. };
  196. /**
  197. * Init the audio context, if we do not have one.
  198. * @private
  199. * @return {boolean} True if successful, false if not.
  200. */
  201. Instrument.prototype.initAudioContext = function () {
  202. var Context = H.win.AudioContext || H.win.webkitAudioContext,
  203. hasOldContext = !!H.audioContext;
  204. if (Context) {
  205. H.audioContext = H.audioContext || new Context();
  206. if (
  207. !hasOldContext &&
  208. H.audioContext &&
  209. H.audioContext.state === 'running'
  210. ) {
  211. H.audioContext.suspend(); // Pause until we need it
  212. }
  213. return !!(
  214. H.audioContext &&
  215. H.audioContext.createOscillator &&
  216. H.audioContext.createGain
  217. );
  218. }
  219. return false;
  220. };
  221. /**
  222. * Init an oscillator instrument.
  223. * @private
  224. * @param {object} oscillatorOptions - The oscillator options passed to
  225. * Highcharts.Instrument#init.
  226. */
  227. Instrument.prototype.initOscillator = function (options) {
  228. var ctx = H.audioContext;
  229. this.oscillator = ctx.createOscillator();
  230. this.oscillator.type = options.waveformShape;
  231. this.oscillator.connect(this.gainNode);
  232. this.oscillatorStarted = false;
  233. };
  234. /**
  235. * Set pan position.
  236. * @private
  237. * @param {number} panValue - The pan position to set for the instrument.
  238. */
  239. Instrument.prototype.setPan = function (panValue) {
  240. if (this.panNode) {
  241. this.panNode.pan.setValueAtTime(panValue, H.audioContext.currentTime);
  242. }
  243. };
  244. /**
  245. * Set gain level. A maximum of 1.2 is allowed before we emit a warning. The
  246. * actual volume is not set above this level regardless of input.
  247. * @private
  248. * @param {number} gainValue - The gain level to set for the instrument.
  249. * @param {number} [rampTime=0] - Gradually change the gain level, time given in
  250. * milliseconds.
  251. */
  252. Instrument.prototype.setGain = function (gainValue, rampTime) {
  253. if (this.gainNode) {
  254. if (gainValue > 1.2) {
  255. console.warn( // eslint-disable-line
  256. 'Highcharts sonification warning: ' +
  257. 'Volume of instrument set too high.'
  258. );
  259. gainValue = 1.2;
  260. }
  261. if (rampTime) {
  262. this.gainNode.gain.setValueAtTime(
  263. this.gainNode.gain.value, H.audioContext.currentTime
  264. );
  265. this.gainNode.gain.linearRampToValueAtTime(
  266. gainValue,
  267. H.audioContext.currentTime + rampTime / 1000
  268. );
  269. } else {
  270. this.gainNode.gain.setValueAtTime(
  271. gainValue, H.audioContext.currentTime
  272. );
  273. }
  274. }
  275. };
  276. /**
  277. * Cancel ongoing gain ramps.
  278. * @private
  279. */
  280. Instrument.prototype.cancelGainRamp = function () {
  281. if (this.gainNode) {
  282. this.gainNode.gain.cancelScheduledValues(0);
  283. }
  284. };
  285. /**
  286. * Get the closest valid frequency for this instrument.
  287. * @private
  288. * @param {number} frequency - The target frequency.
  289. * @param {number} [min] - Minimum frequency to return.
  290. * @param {number} [max] - Maximum frequency to return.
  291. * @return {number} The closest valid frequency to the input frequency.
  292. */
  293. Instrument.prototype.getValidFrequency = function (frequency, min, max) {
  294. var validFrequencies = this.options.allowedFrequencies,
  295. maximum = H.pick(max, Infinity),
  296. minimum = H.pick(min, -Infinity);
  297. return !validFrequencies || !validFrequencies.length ?
  298. // No valid frequencies for this instrument, return the target
  299. frequency :
  300. // Use the valid frequencies and return the closest match
  301. validFrequencies.reduce(function (acc, cur) {
  302. // Find the closest allowed value
  303. return Math.abs(cur - frequency) < Math.abs(acc - frequency) &&
  304. cur < maximum && cur > minimum ?
  305. cur : acc;
  306. }, Infinity);
  307. };
  308. /**
  309. * Clear existing play callback timers.
  310. * @private
  311. */
  312. Instrument.prototype.clearPlayCallbackTimers = function () {
  313. this.playCallbackTimers.forEach(function (timer) {
  314. clearInterval(timer);
  315. });
  316. this.playCallbackTimers = [];
  317. };
  318. /**
  319. * Set the current frequency being played by the instrument. The closest valid
  320. * frequency between the frequency limits is used.
  321. * @param {number} frequency - The frequency to set.
  322. * @param {object} [frequencyLimits] - Object with maxFrequency and minFrequency
  323. */
  324. Instrument.prototype.setFrequency = function (frequency, frequencyLimits) {
  325. var limits = frequencyLimits || {},
  326. validFrequency = this.getValidFrequency(
  327. frequency, limits.min, limits.max
  328. );
  329. if (this.options.type === 'oscillator') {
  330. this.oscillatorPlay(validFrequency);
  331. }
  332. };
  333. /**
  334. * Play oscillator instrument.
  335. * @private
  336. * @param {number} frequency - The frequency to play.
  337. */
  338. Instrument.prototype.oscillatorPlay = function (frequency) {
  339. if (!this.oscillatorStarted) {
  340. this.oscillator.start();
  341. this.oscillatorStarted = true;
  342. }
  343. this.oscillator.frequency.setValueAtTime(
  344. frequency, H.audioContext.currentTime
  345. );
  346. };
  347. /**
  348. * Prepare instrument before playing. Resumes the audio context and starts the
  349. * oscillator.
  350. * @private
  351. */
  352. Instrument.prototype.preparePlay = function () {
  353. this.setGain(0.001);
  354. if (H.audioContext.state === 'suspended') {
  355. H.audioContext.resume();
  356. }
  357. if (this.oscillator && !this.oscillatorStarted) {
  358. this.oscillator.start();
  359. this.oscillatorStarted = true;
  360. }
  361. };
  362. /**
  363. * Play the instrument according to options.
  364. *
  365. * @sample highcharts/sonification/instrument/
  366. * Using Instruments directly
  367. * @sample highcharts/sonification/instrument-advanced/
  368. * Using callbacks for instrument parameters
  369. *
  370. * @function Highcharts.Instrument#play
  371. *
  372. * @param {Highcharts.InstrumentPlayOptionsObject} options
  373. * Options for the playback of the instrument.
  374. */
  375. Instrument.prototype.play = function (options) {
  376. var instrument = this,
  377. duration = options.duration || 0,
  378. // Set a value, or if it is a function, set it continously as a timer.
  379. // Pass in the value/function to set, the setter function, and any
  380. // additional data to pass through to the setter function.
  381. setOrStartTimer = function (value, setter, setterData) {
  382. var target = options.duration,
  383. currentDurationIx = 0,
  384. callbackInterval = instrument.options.playCallbackInterval;
  385. if (typeof value === 'function') {
  386. var timer = setInterval(function () {
  387. currentDurationIx++;
  388. var curTime = currentDurationIx * callbackInterval / target;
  389. if (curTime >= 1) {
  390. instrument[setter](value(1), setterData);
  391. clearInterval(timer);
  392. } else {
  393. instrument[setter](value(curTime), setterData);
  394. }
  395. }, callbackInterval);
  396. instrument.playCallbackTimers.push(timer);
  397. } else {
  398. instrument[setter](value, setterData);
  399. }
  400. };
  401. if (!instrument.id) {
  402. // No audio support - do nothing
  403. return;
  404. }
  405. // If the AudioContext is suspended we have to resume it before playing
  406. if (
  407. H.audioContext.state === 'suspended' ||
  408. this.oscillator && !this.oscillatorStarted
  409. ) {
  410. instrument.preparePlay();
  411. // Try again in 10ms
  412. setTimeout(function () {
  413. instrument.play(options);
  414. }, 10);
  415. return;
  416. }
  417. // Clear any existing play timers
  418. if (instrument.playCallbackTimers.length) {
  419. instrument.clearPlayCallbackTimers();
  420. }
  421. // Clear any gain ramps
  422. instrument.cancelGainRamp();
  423. // Clear stop oscillator timer
  424. if (instrument.stopOscillatorTimeout) {
  425. clearTimeout(instrument.stopOscillatorTimeout);
  426. delete instrument.stopOscillatorTimeout;
  427. }
  428. // If a note is playing right now, clear the stop timeout, and call the
  429. // callback.
  430. if (instrument.stopTimeout) {
  431. clearTimeout(instrument.stopTimeout);
  432. delete instrument.stopTimeout;
  433. if (instrument.stopCallback) {
  434. // We have a callback for the play we are interrupting. We do not
  435. // allow this callback to start a new play, because that leads to
  436. // chaos. We pass in 'cancelled' to indicate that this note did not
  437. // finish, but still stopped.
  438. instrument._play = instrument.play;
  439. instrument.play = function () { };
  440. instrument.stopCallback('cancelled');
  441. instrument.play = instrument._play;
  442. }
  443. }
  444. // Stop the note without fadeOut if the duration is too short to hear the
  445. // note otherwise.
  446. var immediate = duration < H.sonification.fadeOutDuration + 20;
  447. // Stop the instrument after the duration of the note
  448. instrument.stopCallback = options.onEnd;
  449. var onStop = function () {
  450. delete instrument.stopTimeout;
  451. instrument.stop(immediate);
  452. };
  453. if (duration) {
  454. instrument.stopTimeout = setTimeout(
  455. onStop,
  456. immediate ? duration :
  457. duration - H.sonification.fadeOutDuration
  458. );
  459. // Play the note
  460. setOrStartTimer(options.frequency, 'setFrequency', null, {
  461. minFrequency: options.minFrequency,
  462. maxFrequency: options.maxFrequency
  463. });
  464. // Set the volume and panning
  465. setOrStartTimer(H.pick(options.volume, 1), 'setGain', 4); // Slight ramp
  466. setOrStartTimer(H.pick(options.pan, 0), 'setPan');
  467. } else {
  468. // No note duration, so just stop immediately
  469. onStop();
  470. }
  471. };
  472. /**
  473. * Mute an instrument that is playing. If the instrument is not currently
  474. * playing, this function does nothing.
  475. *
  476. * @function Highcharts.Instrument#mute
  477. */
  478. Instrument.prototype.mute = function () {
  479. this.setGain(0.0001, H.sonification.fadeOutDuration * 0.8);
  480. };
  481. /**
  482. * Stop the instrument playing.
  483. *
  484. * @function Highcharts.Instrument#stop
  485. *
  486. * @param {boolean} immediately
  487. * Whether to do the stop immediately or fade out.
  488. *
  489. * @param {Function} onStopped
  490. * Callback function to be called when the stop is completed.
  491. *
  492. * @param {*} callbackData
  493. * Data to send to the onEnd callback functions.
  494. */
  495. Instrument.prototype.stop = function (immediately, onStopped, callbackData) {
  496. var instr = this,
  497. reset = function () {
  498. // Remove timeout reference
  499. if (instr.stopOscillatorTimeout) {
  500. delete instr.stopOscillatorTimeout;
  501. }
  502. // The oscillator may have stopped in the meantime here, so allow
  503. // this function to fail if so.
  504. try {
  505. instr.oscillator.stop();
  506. } catch (e) {}
  507. instr.oscillator.disconnect(instr.gainNode);
  508. // We need a new oscillator in order to restart it
  509. instr.initOscillator(instr.options.oscillator);
  510. // Done stopping, call the callback from the stop
  511. if (onStopped) {
  512. onStopped(callbackData);
  513. }
  514. // Call the callback for the play we finished
  515. if (instr.stopCallback) {
  516. instr.stopCallback(callbackData);
  517. }
  518. };
  519. // Clear any existing timers
  520. if (instr.playCallbackTimers.length) {
  521. instr.clearPlayCallbackTimers();
  522. }
  523. if (instr.stopTimeout) {
  524. clearTimeout(instr.stopTimeout);
  525. }
  526. if (immediately) {
  527. instr.setGain(0);
  528. reset();
  529. } else {
  530. instr.mute();
  531. // Stop the oscillator after the mute fade-out has finished
  532. instr.stopOscillatorTimeout =
  533. setTimeout(reset, H.sonification.fadeOutDuration + 100);
  534. }
  535. };
  536. return Instrument;
  537. }(Highcharts));
  538. var frequencies = (function () {
  539. /* *
  540. *
  541. * (c) 2009-2019 Øystein Moseng
  542. *
  543. * List of musical frequencies from C0 to C8.
  544. *
  545. * License: www.highcharts.com/license
  546. *
  547. * */
  548. var frequencies = [
  549. 16.351597831287414, // C0
  550. 17.323914436054505,
  551. 18.354047994837977,
  552. 19.445436482630058,
  553. 20.601722307054366,
  554. 21.826764464562746,
  555. 23.12465141947715,
  556. 24.499714748859326,
  557. 25.956543598746574,
  558. 27.5, // A0
  559. 29.13523509488062,
  560. 30.86770632850775,
  561. 32.70319566257483, // C1
  562. 34.64782887210901,
  563. 36.70809598967594,
  564. 38.890872965260115,
  565. 41.20344461410875,
  566. 43.653528929125486,
  567. 46.2493028389543,
  568. 48.999429497718666,
  569. 51.91308719749314,
  570. 55, // A1
  571. 58.27047018976124,
  572. 61.7354126570155,
  573. 65.40639132514966, // C2
  574. 69.29565774421802,
  575. 73.41619197935188,
  576. 77.78174593052023,
  577. 82.4068892282175,
  578. 87.30705785825097,
  579. 92.4986056779086,
  580. 97.99885899543733,
  581. 103.82617439498628,
  582. 110, // A2
  583. 116.54094037952248,
  584. 123.47082531403103,
  585. 130.8127826502993, // C3
  586. 138.59131548843604,
  587. 146.8323839587038,
  588. 155.56349186104046,
  589. 164.81377845643496,
  590. 174.61411571650194,
  591. 184.9972113558172,
  592. 195.99771799087463,
  593. 207.65234878997256,
  594. 220, // A3
  595. 233.08188075904496,
  596. 246.94165062806206,
  597. 261.6255653005986, // C4
  598. 277.1826309768721,
  599. 293.6647679174076,
  600. 311.1269837220809,
  601. 329.6275569128699,
  602. 349.2282314330039,
  603. 369.9944227116344,
  604. 391.99543598174927,
  605. 415.3046975799451,
  606. 440, // A4
  607. 466.1637615180899,
  608. 493.8833012561241,
  609. 523.2511306011972, // C5
  610. 554.3652619537442,
  611. 587.3295358348151,
  612. 622.2539674441618,
  613. 659.2551138257398,
  614. 698.4564628660078,
  615. 739.9888454232688,
  616. 783.9908719634985,
  617. 830.6093951598903,
  618. 880, // A5
  619. 932.3275230361799,
  620. 987.7666025122483,
  621. 1046.5022612023945, // C6
  622. 1108.7305239074883,
  623. 1174.6590716696303,
  624. 1244.5079348883237,
  625. 1318.5102276514797,
  626. 1396.9129257320155,
  627. 1479.9776908465376,
  628. 1567.981743926997,
  629. 1661.2187903197805,
  630. 1760, // A6
  631. 1864.6550460723597,
  632. 1975.533205024496,
  633. 2093.004522404789, // C7
  634. 2217.4610478149766,
  635. 2349.31814333926,
  636. 2489.0158697766474,
  637. 2637.02045530296,
  638. 2793.825851464031,
  639. 2959.955381693075,
  640. 3135.9634878539946,
  641. 3322.437580639561,
  642. 3520, // A7
  643. 3729.3100921447194,
  644. 3951.066410048992,
  645. 4186.009044809578 // C8
  646. ];
  647. return frequencies;
  648. }());
  649. var utilities = (function (musicalFrequencies) {
  650. /* *
  651. *
  652. * (c) 2009-2019 Øystein Moseng
  653. *
  654. * Utility functions for sonification.
  655. *
  656. * License: www.highcharts.com/license
  657. *
  658. * */
  659. /**
  660. * The SignalHandler class. Stores signal callbacks (event handlers), and
  661. * provides an interface to register them, and emit signals. The word "event" is
  662. * not used to avoid confusion with TimelineEvents.
  663. *
  664. * @requires module:modules/sonification
  665. *
  666. * @private
  667. * @class
  668. * @name Highcharts.SignalHandler
  669. *
  670. * @param {Array<string>} supportedSignals
  671. * List of supported signal names.
  672. */
  673. function SignalHandler(supportedSignals) {
  674. this.init(supportedSignals || []);
  675. }
  676. SignalHandler.prototype.init = function (supportedSignals) {
  677. this.supportedSignals = supportedSignals;
  678. this.signals = {};
  679. };
  680. /**
  681. * Register a set of signal callbacks with this SignalHandler.
  682. * Multiple signal callbacks can be registered for the same signal.
  683. * @private
  684. * @param {object} signals - An object that contains a mapping from the signal
  685. * name to the callbacks. Only supported events are considered.
  686. */
  687. SignalHandler.prototype.registerSignalCallbacks = function (signals) {
  688. var signalHandler = this;
  689. signalHandler.supportedSignals.forEach(function (supportedSignal) {
  690. if (signals[supportedSignal]) {
  691. (
  692. signalHandler.signals[supportedSignal] =
  693. signalHandler.signals[supportedSignal] || []
  694. ).push(
  695. signals[supportedSignal]
  696. );
  697. }
  698. });
  699. };
  700. /**
  701. * Clear signal callbacks, optionally by name.
  702. * @private
  703. * @param {Array<string>} [signalNames] - A list of signal names to clear. If
  704. * not supplied, all signal callbacks are removed.
  705. */
  706. SignalHandler.prototype.clearSignalCallbacks = function (signalNames) {
  707. var signalHandler = this;
  708. if (signalNames) {
  709. signalNames.forEach(function (signalName) {
  710. if (signalHandler.signals[signalName]) {
  711. delete signalHandler.signals[signalName];
  712. }
  713. });
  714. } else {
  715. signalHandler.signals = {};
  716. }
  717. };
  718. /**
  719. * Emit a signal. Does nothing if the signal does not exist, or has no
  720. * registered callbacks.
  721. * @private
  722. * @param {string} signalNames - Name of signal to emit.
  723. * @param {*} data - Data to pass to the callback.
  724. */
  725. SignalHandler.prototype.emitSignal = function (signalName, data) {
  726. var retval;
  727. if (this.signals[signalName]) {
  728. this.signals[signalName].forEach(function (handler) {
  729. var result = handler(data);
  730. retval = result !== undefined ? result : retval;
  731. });
  732. }
  733. return retval;
  734. };
  735. var utilities = {
  736. // List of musical frequencies from C0 to C8
  737. musicalFrequencies: musicalFrequencies,
  738. // SignalHandler class
  739. SignalHandler: SignalHandler,
  740. /**
  741. * Get a musical scale by specifying the semitones from 1-12 to include.
  742. * 1: C, 2: C#, 3: D, 4: D#, 5: E, 6: F,
  743. * 7: F#, 8: G, 9: G#, 10: A, 11: Bb, 12: B
  744. * @private
  745. * @param {Array<number>} semitones - Array of semitones from 1-12 to
  746. * include in the scale. Duplicate entries are ignored.
  747. * @return {Array<number>} Array of frequencies from C0 to C8 that are
  748. * included in this scale.
  749. */
  750. getMusicalScale: function (semitones) {
  751. return musicalFrequencies.filter(function (freq, i) {
  752. var interval = i % 12 + 1;
  753. return semitones.some(function (allowedInterval) {
  754. return allowedInterval === interval;
  755. });
  756. });
  757. },
  758. /**
  759. * Calculate the extreme values in a chart for a data prop.
  760. * @private
  761. * @param {Highcharts.Chart} chart - The chart
  762. * @param {string} prop - The data prop to find extremes for
  763. * @return {object} Object with min and max properties
  764. */
  765. calculateDataExtremes: function (chart, prop) {
  766. return chart.series.reduce(function (extremes, series) {
  767. // We use cropped points rather than series.data here, to allow
  768. // users to zoom in for better fidelity.
  769. series.points.forEach(function (point) {
  770. var val = point[prop] !== undefined ?
  771. point[prop] : point.options[prop];
  772. extremes.min = Math.min(extremes.min, val);
  773. extremes.max = Math.max(extremes.max, val);
  774. });
  775. return extremes;
  776. }, {
  777. min: Infinity,
  778. max: -Infinity
  779. });
  780. },
  781. /**
  782. * Translate a value on a virtual axis. Creates a new, virtual, axis with a
  783. * min and max, and maps the relative value onto this axis.
  784. * @private
  785. * @param {number} value - The relative data value to translate.
  786. * @param {object} dataExtremes - The possible extremes for this value.
  787. * @param {object} limits - Limits for the virtual axis.
  788. * @return {number} The value mapped to the virtual axis.
  789. */
  790. virtualAxisTranslate: function (value, dataExtremes, limits) {
  791. var lenValueAxis = dataExtremes.max - dataExtremes.min,
  792. lenVirtualAxis = limits.max - limits.min,
  793. virtualAxisValue = limits.min +
  794. lenVirtualAxis * (value - dataExtremes.min) / lenValueAxis;
  795. return lenValueAxis > 0 ?
  796. Math.max(Math.min(virtualAxisValue, limits.max), limits.min) :
  797. limits.min;
  798. }
  799. };
  800. return utilities;
  801. }(frequencies));
  802. var instruments = (function (Instrument, utilities) {
  803. /* *
  804. *
  805. * (c) 2009-2019 Øystein Moseng
  806. *
  807. * Instrument definitions for sonification module.
  808. *
  809. * License: www.highcharts.com/license
  810. *
  811. * */
  812. var instruments = {};
  813. ['sine', 'square', 'triangle', 'sawtooth'].forEach(function (waveform) {
  814. // Add basic instruments
  815. instruments[waveform] = new Instrument({
  816. oscillator: { waveformShape: waveform }
  817. });
  818. // Add musical instruments
  819. instruments[waveform + 'Musical'] = new Instrument({
  820. allowedFrequencies: utilities.musicalFrequencies,
  821. oscillator: { waveformShape: waveform }
  822. });
  823. // Add scaled instruments
  824. instruments[waveform + 'Major'] = new Instrument({
  825. allowedFrequencies: utilities.getMusicalScale([1, 3, 5, 6, 8, 10, 12]),
  826. oscillator: { waveformShape: waveform }
  827. });
  828. });
  829. return instruments;
  830. }(Instrument, utilities));
  831. var Earcon = (function (H) {
  832. /* *
  833. *
  834. * (c) 2009-2019 Øystein Moseng
  835. *
  836. * Earcons for the sonification module in Highcharts.
  837. *
  838. * License: www.highcharts.com/license
  839. *
  840. * */
  841. /**
  842. * Define an Instrument and the options for playing it.
  843. *
  844. * @requires module:modules/sonification
  845. *
  846. * @interface Highcharts.EarconInstrument
  847. *//**
  848. * An instrument instance or the name of the instrument in the
  849. * Highcharts.sonification.instruments map.
  850. * @name Highcharts.EarconInstrument#instrument
  851. * @type {Highcharts.Instrument|String}
  852. *//**
  853. * The options to pass to Instrument.play.
  854. * @name Highcharts.EarconInstrument#playOptions
  855. * @type {object}
  856. */
  857. /**
  858. * Options for an Earcon.
  859. *
  860. * @requires module:modules/sonification
  861. *
  862. * @interface Highcharts.EarconOptionsObject
  863. *//**
  864. * The instruments and their options defining this earcon.
  865. * @name Highcharts.EarconOptionsObject#instruments
  866. * @type {Array<Highcharts.EarconInstrument>}
  867. *//**
  868. * The unique ID of the Earcon. Generated if not supplied.
  869. * @name Highcharts.EarconOptionsObject#id
  870. * @type {string|undefined}
  871. *//**
  872. * Global panning of all instruments. Overrides all panning on individual
  873. * instruments. Can be a number between -1 and 1.
  874. * @name Highcharts.EarconOptionsObject#pan
  875. * @type {number|undefined}
  876. *//**
  877. * Master volume for all instruments. Volume settings on individual instruments
  878. * can still be used for relative volume between the instruments. This setting
  879. * does not affect volumes set by functions in individual instruments. Can be a
  880. * number between 0 and 1. Defaults to 1.
  881. * @name Highcharts.EarconOptionsObject#volume
  882. * @type {number|undefined}
  883. *//**
  884. * Callback function to call when earcon has finished playing.
  885. * @name Highcharts.EarconOptionsObject#onEnd
  886. * @type {Function|undefined}
  887. */
  888. /**
  889. * The Earcon class. Earcon objects represent a certain sound consisting of
  890. * one or more instruments playing a predefined sound.
  891. *
  892. * @sample highcharts/sonification/earcon/
  893. * Using earcons directly
  894. *
  895. * @requires module:modules/sonification
  896. *
  897. * @class
  898. * @name Highcharts.Earcon
  899. *
  900. * @param {Highcharts.EarconOptionsObject} options
  901. * Options for the Earcon instance.
  902. */
  903. function Earcon(options) {
  904. this.init(options || {});
  905. }
  906. Earcon.prototype.init = function (options) {
  907. this.options = options;
  908. if (!this.options.id) {
  909. this.options.id = this.id = H.uniqueKey();
  910. }
  911. this.instrumentsPlaying = {};
  912. };
  913. /**
  914. * Play the earcon, optionally overriding init options.
  915. *
  916. * @sample highcharts/sonification/earcon/
  917. * Using earcons directly
  918. *
  919. * @function Highcharts.Earcon#sonify
  920. *
  921. * @param {Highcharts.EarconOptionsObject} options
  922. * Override existing options.
  923. */
  924. Earcon.prototype.sonify = function (options) {
  925. var playOptions = H.merge(this.options, options);
  926. // Find master volume/pan settings
  927. var masterVolume = H.pick(playOptions.volume, 1),
  928. masterPan = playOptions.pan,
  929. earcon = this,
  930. playOnEnd = options && options.onEnd,
  931. masterOnEnd = earcon.options.onEnd;
  932. // Go through the instruments and play them
  933. playOptions.instruments.forEach(function (opts) {
  934. var instrument = typeof opts.instrument === 'string' ?
  935. H.sonification.instruments[opts.instrument] : opts.instrument,
  936. instrumentOpts = H.merge(opts.playOptions),
  937. instrOnEnd,
  938. instrumentCopy,
  939. copyId;
  940. if (instrument && instrument.play) {
  941. if (opts.playOptions) {
  942. // Handle master pan/volume
  943. if (typeof opts.playOptions.volume !== 'function') {
  944. instrumentOpts.volume = H.pick(masterVolume, 1) *
  945. H.pick(opts.playOptions.volume, 1);
  946. }
  947. instrumentOpts.pan = H.pick(masterPan, instrumentOpts.pan);
  948. // Handle onEnd
  949. instrOnEnd = instrumentOpts.onEnd;
  950. instrumentOpts.onEnd = function () {
  951. delete earcon.instrumentsPlaying[copyId];
  952. if (instrOnEnd) {
  953. instrOnEnd.apply(this, arguments);
  954. }
  955. if (!Object.keys(earcon.instrumentsPlaying).length) {
  956. if (playOnEnd) {
  957. playOnEnd.apply(this, arguments);
  958. }
  959. if (masterOnEnd) {
  960. masterOnEnd.apply(this, arguments);
  961. }
  962. }
  963. };
  964. // Play the instrument. Use a copy so we can play multiple at
  965. // the same time.
  966. instrumentCopy = instrument.copy();
  967. copyId = instrumentCopy.id;
  968. earcon.instrumentsPlaying[copyId] = instrumentCopy;
  969. instrumentCopy.play(instrumentOpts);
  970. }
  971. } else {
  972. H.error(30);
  973. }
  974. });
  975. };
  976. /**
  977. * Cancel any current sonification of the Earcon. Calls onEnd functions.
  978. *
  979. * @function Highcharts.Earcon#cancelSonify
  980. *
  981. * @param {boolean} [fadeOut=false]
  982. * Whether or not to fade out as we stop. If false, the earcon is
  983. * cancelled synchronously.
  984. */
  985. Earcon.prototype.cancelSonify = function (fadeOut) {
  986. var playing = this.instrumentsPlaying,
  987. instrIds = playing && Object.keys(playing);
  988. if (instrIds && instrIds.length) {
  989. instrIds.forEach(function (instr) {
  990. playing[instr].stop(!fadeOut, null, 'cancelled');
  991. });
  992. this.instrumentsPlaying = {};
  993. }
  994. };
  995. return Earcon;
  996. }(Highcharts));
  997. var pointSonifyFunctions = (function (H, utilities) {
  998. /* *
  999. *
  1000. * (c) 2009-2019 Øystein Moseng
  1001. *
  1002. * Code for sonifying single points.
  1003. *
  1004. * License: www.highcharts.com/license
  1005. *
  1006. * */
  1007. /**
  1008. * Define the parameter mapping for an instrument.
  1009. *
  1010. * @requires module:modules/sonification
  1011. *
  1012. * @interface Highcharts.PointInstrumentMappingObject
  1013. *//**
  1014. * Define the volume of the instrument. This can be a string with a data
  1015. * property name, e.g. `'y'`, in which case this data property is used to define
  1016. * the volume relative to the `y`-values of the other points. A higher `y` value
  1017. * would then result in a higher volume. This option can also be a fixed number
  1018. * or a function. If it is a function, this function is called in regular
  1019. * intervals while the note is playing. It receives three arguments: The point,
  1020. * the dataExtremes, and the current relative time - where 0 is the beginning of
  1021. * the note and 1 is the end. The function should return the volume of the note
  1022. * as a number between 0 and 1.
  1023. * @name Highcharts.PointInstrumentMappingObject#volume
  1024. * @type {string|number|Function}
  1025. *//**
  1026. * Define the duration of the notes for this instrument. This can be a string
  1027. * with a data property name, e.g. `'y'`, in which case this data property is
  1028. * used to define the duration relative to the `y`-values of the other points. A
  1029. * higher `y` value would then result in a longer duration. This option can also
  1030. * be a fixed number or a function. If it is a function, this function is called
  1031. * once before the note starts playing, and should return the duration in
  1032. * milliseconds. It receives two arguments: The point, and the dataExtremes.
  1033. * @name Highcharts.PointInstrumentMappingObject#duration
  1034. * @type {string|number|Function}
  1035. *//**
  1036. * Define the panning of the instrument. This can be a string with a data
  1037. * property name, e.g. `'x'`, in which case this data property is used to define
  1038. * the panning relative to the `x`-values of the other points. A higher `x`
  1039. * value would then result in a higher panning value (panned further to the
  1040. * right). This option can also be a fixed number or a function. If it is a
  1041. * function, this function is called in regular intervals while the note is
  1042. * playing. It receives three arguments: The point, the dataExtremes, and the
  1043. * current relative time - where 0 is the beginning of the note and 1 is the
  1044. * end. The function should return the panning of the note as a number between
  1045. * -1 and 1.
  1046. * @name Highcharts.PointInstrumentMappingObject#pan
  1047. * @type {string|number|Function|undefined}
  1048. *//**
  1049. * Define the frequency of the instrument. This can be a string with a data
  1050. * property name, e.g. `'y'`, in which case this data property is used to define
  1051. * the frequency relative to the `y`-values of the other points. A higher `y`
  1052. * value would then result in a higher frequency. This option can also be a
  1053. * fixed number or a function. If it is a function, this function is called in
  1054. * regular intervals while the note is playing. It receives three arguments:
  1055. * The point, the dataExtremes, and the current relative time - where 0 is the
  1056. * beginning of the note and 1 is the end. The function should return the
  1057. * frequency of the note as a number (in Hz).
  1058. * @name Highcharts.PointInstrumentMappingObject#frequency
  1059. * @type {string|number|Function}
  1060. */
  1061. /**
  1062. * @requires module:modules/sonification
  1063. *
  1064. * @interface Highcharts.PointInstrumentOptionsObject
  1065. *//**
  1066. * The minimum duration for a note when using a data property for duration. Can
  1067. * be overridden by using either a fixed number or a function for
  1068. * instrumentMapping.duration. Defaults to 20.
  1069. * @name Highcharts.PointInstrumentOptionsObject#minDuration
  1070. * @type {number|undefined}
  1071. *//**
  1072. * The maximum duration for a note when using a data property for duration. Can
  1073. * be overridden by using either a fixed number or a function for
  1074. * instrumentMapping.duration. Defaults to 2000.
  1075. * @name Highcharts.PointInstrumentOptionsObject#maxDuration
  1076. * @type {number|undefined}
  1077. *//**
  1078. * The minimum pan value for a note when using a data property for panning. Can
  1079. * be overridden by using either a fixed number or a function for
  1080. * instrumentMapping.pan. Defaults to -1 (fully left).
  1081. * @name Highcharts.PointInstrumentOptionsObject#minPan
  1082. * @type {number|undefined}
  1083. *//**
  1084. * The maximum pan value for a note when using a data property for panning. Can
  1085. * be overridden by using either a fixed number or a function for
  1086. * instrumentMapping.pan. Defaults to 1 (fully right).
  1087. * @name Highcharts.PointInstrumentOptionsObject#maxPan
  1088. * @type {number|undefined}
  1089. *//**
  1090. * The minimum volume for a note when using a data property for volume. Can be
  1091. * overridden by using either a fixed number or a function for
  1092. * instrumentMapping.volume. Defaults to 0.1.
  1093. * @name Highcharts.PointInstrumentOptionsObject#minVolume
  1094. * @type {number|undefined}
  1095. *//**
  1096. * The maximum volume for a note when using a data property for volume. Can be
  1097. * overridden by using either a fixed number or a function for
  1098. * instrumentMapping.volume. Defaults to 1.
  1099. * @name Highcharts.PointInstrumentOptionsObject#maxVolume
  1100. * @type {number|undefined}
  1101. *//**
  1102. * The minimum frequency for a note when using a data property for frequency.
  1103. * Can be overridden by using either a fixed number or a function for
  1104. * instrumentMapping.frequency. Defaults to 220.
  1105. * @name Highcharts.PointInstrumentOptionsObject#minFrequency
  1106. * @type {number|undefined}
  1107. *//**
  1108. * The maximum frequency for a note when using a data property for frequency.
  1109. * Can be overridden by using either a fixed number or a function for
  1110. * instrumentMapping.frequency. Defaults to 2200.
  1111. * @name Highcharts.PointInstrumentOptionsObject#maxFrequency
  1112. * @type {number|undefined}
  1113. */
  1114. /**
  1115. * An instrument definition for a point, specifying the instrument to play and
  1116. * how to play it.
  1117. *
  1118. * @interface Highcharts.PointInstrumentObject
  1119. *//**
  1120. * An Instrument instance or the name of the instrument in the
  1121. * Highcharts.sonification.instruments map.
  1122. * @name Highcharts.PointInstrumentObject#instrument
  1123. * @type {Highcharts.Instrument|string}
  1124. *//**
  1125. * Mapping of instrument parameters for this instrument.
  1126. * @name Highcharts.PointInstrumentObject#instrumentMapping
  1127. * @type {Highcharts.PointInstrumentMappingObject}
  1128. *//**
  1129. * Options for this instrument.
  1130. * @name Highcharts.PointInstrumentObject#instrumentOptions
  1131. * @type {Highcharts.PointInstrumentOptionsObject|undefined}
  1132. *//**
  1133. * Callback to call when the instrument has stopped playing.
  1134. * @name Highcharts.PointInstrumentObject#onEnd
  1135. * @type {Function|undefined}
  1136. */
  1137. /**
  1138. * Options for sonifying a point.
  1139. * @interface Highcharts.PointSonifyOptionsObject
  1140. *//**
  1141. * The instrument definitions for this point.
  1142. * @name Highcharts.PointSonifyOptionsObject#instruments
  1143. * @type {Array<Highcharts.PointInstrumentObject>}
  1144. *//**
  1145. * Optionally provide the minimum/maximum values for the points. If this is not
  1146. * supplied, it is calculated from the points in the chart on demand. This
  1147. * option is supplied in the following format, as a map of point data properties
  1148. * to objects with min/max values:
  1149. * ```js
  1150. * dataExtremes: {
  1151. * y: {
  1152. * min: 0,
  1153. * max: 100
  1154. * },
  1155. * z: {
  1156. * min: -10,
  1157. * max: 10
  1158. * }
  1159. * // Properties used and not provided are calculated on demand
  1160. * }
  1161. * ```
  1162. * @name Highcharts.PointSonifyOptionsObject#dataExtremes
  1163. * @type {object|undefined}
  1164. *//**
  1165. * Callback called when the sonification has finished.
  1166. * @name Highcharts.PointSonifyOptionsObject#onEnd
  1167. * @type {Function|undefined}
  1168. */
  1169. // Defaults for the instrument options
  1170. // NOTE: Also change defaults in Highcharts.PointInstrumentOptionsObject if
  1171. // making changes here.
  1172. var defaultInstrumentOptions = {
  1173. minDuration: 20,
  1174. maxDuration: 2000,
  1175. minVolume: 0.1,
  1176. maxVolume: 1,
  1177. minPan: -1,
  1178. maxPan: 1,
  1179. minFrequency: 220,
  1180. maxFrequency: 2200
  1181. };
  1182. /**
  1183. * Sonify a single point.
  1184. *
  1185. * @sample highcharts/sonification/point-basic/
  1186. * Click on points to sonify
  1187. * @sample highcharts/sonification/point-advanced/
  1188. * Sonify bubbles
  1189. *
  1190. * @requires module:modules/sonification
  1191. *
  1192. * @function Highcharts.Point#sonify
  1193. *
  1194. * @param {Highcharts.PointSonifyOptionsObject} options
  1195. * Options for the sonification of the point.
  1196. */
  1197. function pointSonify(options) {
  1198. var point = this,
  1199. chart = point.series.chart,
  1200. dataExtremes = options.dataExtremes || {},
  1201. // Get the value to pass to instrument.play from the mapping value
  1202. // passed in.
  1203. getMappingValue = function (
  1204. value, makeFunction, allowedExtremes, allowedValues
  1205. ) {
  1206. // Fixed number, just use that
  1207. if (typeof value === 'number' || value === undefined) {
  1208. return value;
  1209. }
  1210. // Function. Return new function if we try to use callback,
  1211. // otherwise call it now and return result.
  1212. if (typeof value === 'function') {
  1213. return makeFunction ?
  1214. function (time) {
  1215. return value(point, dataExtremes, time);
  1216. } :
  1217. value(point, dataExtremes);
  1218. }
  1219. // String, this is a data prop.
  1220. if (typeof value === 'string') {
  1221. // Find data extremes if we don't have them
  1222. dataExtremes[value] = dataExtremes[value] ||
  1223. utilities.calculateDataExtremes(
  1224. point.series.chart, value
  1225. );
  1226. // Find the value
  1227. return utilities.virtualAxisTranslate(
  1228. H.pick(point[value], point.options[value]),
  1229. dataExtremes[value],
  1230. allowedExtremes,
  1231. allowedValues
  1232. );
  1233. }
  1234. };
  1235. // Register playing point on chart
  1236. chart.sonification.currentlyPlayingPoint = point;
  1237. // Keep track of instruments playing
  1238. point.sonification = point.sonification || {};
  1239. point.sonification.instrumentsPlaying =
  1240. point.sonification.instrumentsPlaying || {};
  1241. // Register signal handler for the point
  1242. var signalHandler = point.sonification.signalHandler =
  1243. point.sonification.signalHandler ||
  1244. new utilities.SignalHandler(['onEnd']);
  1245. signalHandler.clearSignalCallbacks();
  1246. signalHandler.registerSignalCallbacks({ onEnd: options.onEnd });
  1247. // If we have a null point or invisible point, just return
  1248. if (point.isNull || !point.visible || !point.series.visible) {
  1249. signalHandler.emitSignal('onEnd');
  1250. return;
  1251. }
  1252. // Go through instruments and play them
  1253. options.instruments.forEach(function (instrumentDefinition) {
  1254. var instrument = typeof instrumentDefinition.instrument === 'string' ?
  1255. H.sonification.instruments[instrumentDefinition.instrument] :
  1256. instrumentDefinition.instrument,
  1257. mapping = instrumentDefinition.instrumentMapping || {},
  1258. extremes = H.merge(
  1259. defaultInstrumentOptions,
  1260. instrumentDefinition.instrumentOptions
  1261. ),
  1262. id = instrument.id,
  1263. onEnd = function (cancelled) {
  1264. // Instrument on end
  1265. if (instrumentDefinition.onEnd) {
  1266. instrumentDefinition.onEnd.apply(this, arguments);
  1267. }
  1268. // Remove currently playing point reference on chart
  1269. if (
  1270. chart.sonification &&
  1271. chart.sonification.currentlyPlayingPoint
  1272. ) {
  1273. delete chart.sonification.currentlyPlayingPoint;
  1274. }
  1275. // Remove reference from instruments playing
  1276. if (
  1277. point.sonification && point.sonification.instrumentsPlaying
  1278. ) {
  1279. delete point.sonification.instrumentsPlaying[id];
  1280. // This was the last instrument?
  1281. if (
  1282. !Object.keys(
  1283. point.sonification.instrumentsPlaying
  1284. ).length
  1285. ) {
  1286. signalHandler.emitSignal('onEnd', cancelled);
  1287. }
  1288. }
  1289. };
  1290. // Play the note on the instrument
  1291. if (instrument && instrument.play) {
  1292. point.sonification.instrumentsPlaying[instrument.id] = instrument;
  1293. instrument.play({
  1294. frequency: getMappingValue(
  1295. mapping.frequency,
  1296. true,
  1297. { min: extremes.minFrequency, max: extremes.maxFrequency }
  1298. ),
  1299. duration: getMappingValue(
  1300. mapping.duration,
  1301. false,
  1302. { min: extremes.minDuration, max: extremes.maxDuration }
  1303. ),
  1304. pan: getMappingValue(
  1305. mapping.pan,
  1306. true,
  1307. { min: extremes.minPan, max: extremes.maxPan }
  1308. ),
  1309. volume: getMappingValue(
  1310. mapping.volume,
  1311. true,
  1312. { min: extremes.minVolume, max: extremes.maxVolume }
  1313. ),
  1314. onEnd: onEnd,
  1315. minFrequency: extremes.minFrequency,
  1316. maxFrequency: extremes.maxFrequency
  1317. });
  1318. } else {
  1319. H.error(30);
  1320. }
  1321. });
  1322. }
  1323. /**
  1324. * Cancel sonification of a point. Calls onEnd functions.
  1325. *
  1326. * @requires module:modules/sonification
  1327. *
  1328. * @function Highcharts.Point#cancelSonify
  1329. *
  1330. * @param {boolean} [fadeOut=false]
  1331. * Whether or not to fade out as we stop. If false, the points are
  1332. * cancelled synchronously.
  1333. */
  1334. function pointCancelSonify(fadeOut) {
  1335. var playing = this.sonification && this.sonification.instrumentsPlaying,
  1336. instrIds = playing && Object.keys(playing);
  1337. if (instrIds && instrIds.length) {
  1338. instrIds.forEach(function (instr) {
  1339. playing[instr].stop(!fadeOut, null, 'cancelled');
  1340. });
  1341. this.sonification.instrumentsPlaying = {};
  1342. this.sonification.signalHandler.emitSignal('onEnd', 'cancelled');
  1343. }
  1344. }
  1345. var pointSonifyFunctions = {
  1346. pointSonify: pointSonify,
  1347. pointCancelSonify: pointCancelSonify
  1348. };
  1349. return pointSonifyFunctions;
  1350. }(Highcharts, utilities));
  1351. var chartSonifyFunctions = (function (H, utilities) {
  1352. /* *
  1353. *
  1354. * (c) 2009-2019 Øystein Moseng
  1355. *
  1356. * Sonification functions for chart/series.
  1357. *
  1358. * License: www.highcharts.com/license
  1359. *
  1360. * */
  1361. /**
  1362. * An Earcon configuration, specifying an Earcon and when to play it.
  1363. *
  1364. * @requires module:modules/sonification
  1365. *
  1366. * @interface Highcharts.EarconConfiguration
  1367. *//**
  1368. * An Earcon instance.
  1369. * @name Highcharts.EarconConfiguration#earcon
  1370. * @type {Highcharts.Earcon}
  1371. *//**
  1372. * The ID of the point to play the Earcon on.
  1373. * @name Highcharts.EarconConfiguration#onPoint
  1374. * @type {string|undefined}
  1375. *//**
  1376. * A function to determine whether or not to play this earcon on a point. The
  1377. * function is called for every point, receiving that point as parameter. It
  1378. * should return either a boolean indicating whether or not to play the earcon,
  1379. * or a new Earcon instance - in which case the new Earcon will be played.
  1380. * @name Highcharts.EarconConfiguration#condition
  1381. * @type {Function|undefined}
  1382. */
  1383. /**
  1384. * Options for sonifying a series.
  1385. *
  1386. * @requires module:modules/sonification
  1387. *
  1388. * @interface Highcharts.SonifySeriesOptionsObject
  1389. *//**
  1390. * The duration for playing the points. Note that points might continue to play
  1391. * after the duration has passed, but no new points will start playing.
  1392. * @name Highcharts.SonifySeriesOptionsObject#duration
  1393. * @type {number}
  1394. *//**
  1395. * The axis to use for when to play the points. Can be a string with a data
  1396. * property (e.g. `x`), or a function. If it is a function, this function
  1397. * receives the point as argument, and should return a numeric value. The points
  1398. * with the lowest numeric values are then played first, and the time between
  1399. * points will be proportional to the distance between the numeric values.
  1400. * @name Highcharts.SonifySeriesOptionsObject#pointPlayTime
  1401. * @type {string|Function}
  1402. *//**
  1403. * The instrument definitions for the points in this series.
  1404. * @name Highcharts.SonifySeriesOptionsObject#instruments
  1405. * @type {Array<Highcharts.PointInstrumentObject>}
  1406. *//**
  1407. * Earcons to add to the series.
  1408. * @name Highcharts.SonifySeriesOptionsObject#earcons
  1409. * @type {Array<Highcharts.EarconConfiguration>|undefined}
  1410. *//**
  1411. * Optionally provide the minimum/maximum data values for the points. If this is
  1412. * not supplied, it is calculated from all points in the chart on demand. This
  1413. * option is supplied in the following format, as a map of point data properties
  1414. * to objects with min/max values:
  1415. * ```js
  1416. * dataExtremes: {
  1417. * y: {
  1418. * min: 0,
  1419. * max: 100
  1420. * },
  1421. * z: {
  1422. * min: -10,
  1423. * max: 10
  1424. * }
  1425. * // Properties used and not provided are calculated on demand
  1426. * }
  1427. * ```
  1428. * @name Highcharts.SonifySeriesOptionsObject#dataExtremes
  1429. * @type {object|undefined}
  1430. *//**
  1431. * Callback before a point is played.
  1432. * @name Highcharts.SonifySeriesOptionsObject#onPointStart
  1433. * @type {Function|undefined}
  1434. *//**
  1435. * Callback after a point has finished playing.
  1436. * @name Highcharts.SonifySeriesOptionsObject#onPointEnd
  1437. * @type {Function|undefined}
  1438. *//**
  1439. * Callback after the series has played.
  1440. * @name Highcharts.SonifySeriesOptionsObject#onEnd
  1441. * @type {Function|undefined}
  1442. */
  1443. /**
  1444. * Get the relative time value of a point.
  1445. * @private
  1446. * @param {Highcharts.Point} point - The point.
  1447. * @param {Function|string} timeProp - The time axis data prop or the time
  1448. * function.
  1449. * @return {number} The time value.
  1450. */
  1451. function getPointTimeValue(point, timeProp) {
  1452. return typeof timeProp === 'function' ?
  1453. timeProp(point) :
  1454. H.pick(point[timeProp], point.options[timeProp]);
  1455. }
  1456. /**
  1457. * Get the time extremes of this series. This is handled outside of the
  1458. * dataExtremes, as we always want to just sonify the visible points, and we
  1459. * always want the extremes to be the extremes of the visible points.
  1460. * @private
  1461. * @param {Highcharts.Series} series - The series to compute on.
  1462. * @param {Function|string} timeProp - The time axis data prop or the time
  1463. * function.
  1464. * @return {object} Object with min/max extremes for the time values.
  1465. */
  1466. function getTimeExtremes(series, timeProp) {
  1467. // Compute the extremes from the visible points.
  1468. return series.points.reduce(function (acc, point) {
  1469. var value = getPointTimeValue(point, timeProp);
  1470. acc.min = Math.min(acc.min, value);
  1471. acc.max = Math.max(acc.max, value);
  1472. return acc;
  1473. }, {
  1474. min: Infinity,
  1475. max: -Infinity
  1476. });
  1477. }
  1478. /**
  1479. * Calculate value extremes for used instrument data properties.
  1480. * @private
  1481. * @param {Highcharts.Chart} chart - The chart to calculate extremes from.
  1482. * @param {Array<Highcharts.PointInstrumentObject>} instruments - The instrument
  1483. * definitions used.
  1484. * @param {object} [dataExtremes] - Predefined extremes for each data prop.
  1485. * @return {object} New extremes with data properties mapped to min/max objects.
  1486. */
  1487. function getExtremesForInstrumentProps(chart, instruments, dataExtremes) {
  1488. return (
  1489. instruments || []
  1490. ).reduce(function (newExtremes, instrumentDefinition) {
  1491. Object.keys(instrumentDefinition.instrumentMapping || {}).forEach(
  1492. function (instrumentParameter) {
  1493. var value = instrumentDefinition.instrumentMapping[
  1494. instrumentParameter
  1495. ];
  1496. if (typeof value === 'string' && !newExtremes[value]) {
  1497. // This instrument parameter is mapped to a data prop.
  1498. // If we don't have predefined data extremes, find them.
  1499. newExtremes[value] = utilities.calculateDataExtremes(
  1500. chart, value
  1501. );
  1502. }
  1503. }
  1504. );
  1505. return newExtremes;
  1506. }, H.merge(dataExtremes));
  1507. }
  1508. /**
  1509. * Get earcons for the point if there are any.
  1510. * @private
  1511. * @param {Highcharts.Point} point - The point to find earcons for.
  1512. * @param {Array<Highcharts.EarconConfiguration>} earconDefinitions - Earcons to
  1513. * check.
  1514. * @return {Array<Highcharts.Earcon>} Array of earcons to be played with this
  1515. * point.
  1516. */
  1517. function getPointEarcons(point, earconDefinitions) {
  1518. return earconDefinitions.reduce(
  1519. function (earcons, earconDefinition) {
  1520. var cond,
  1521. earcon = earconDefinition.earcon;
  1522. if (earconDefinition.condition) {
  1523. // We have a condition. This overrides onPoint
  1524. cond = earconDefinition.condition(point);
  1525. if (cond instanceof H.sonification.Earcon) {
  1526. // Condition returned an earcon
  1527. earcons.push(cond);
  1528. } else if (cond) {
  1529. // Condition returned true
  1530. earcons.push(earcon);
  1531. }
  1532. } else if (
  1533. earconDefinition.onPoint &&
  1534. point.id === earconDefinition.onPoint
  1535. ) {
  1536. // We have earcon onPoint
  1537. earcons.push(earcon);
  1538. }
  1539. return earcons;
  1540. }, []
  1541. );
  1542. }
  1543. /**
  1544. * Utility function to get a new list of instrument options where all the
  1545. * instrument references are copies.
  1546. * @private
  1547. * @param {Array<Highcharts.PointInstrumentObject>} instruments - The instrument
  1548. * options.
  1549. * @return {Array<Highcharts.PointInstrumentObject>} Array of copied instrument
  1550. * options.
  1551. */
  1552. function makeInstrumentCopies(instruments) {
  1553. return instruments.map(function (instrumentDef) {
  1554. var instrument = instrumentDef.instrument,
  1555. copy = (typeof instrument === 'string' ?
  1556. H.sonification.instruments[instrument] :
  1557. instrument).copy();
  1558. return H.merge(instrumentDef, { instrument: copy });
  1559. });
  1560. }
  1561. /**
  1562. * Create a TimelinePath from a series. Takes the same options as seriesSonify.
  1563. * To intuitively allow multiple series to play simultaneously we make copies of
  1564. * the instruments for each series.
  1565. * @private
  1566. * @param {Highcharts.Series} series - The series to build from.
  1567. * @param {object} options - The options for building the TimelinePath.
  1568. * @return {Highcharts.TimelinePath} A timeline path with events.
  1569. */
  1570. function buildTimelinePathFromSeries(series, options) {
  1571. // options.timeExtremes is internal and used so that the calculations from
  1572. // chart.sonify can be reused.
  1573. var timeExtremes = options.timeExtremes || getTimeExtremes(
  1574. series, options.pointPlayTime, options.dataExtremes
  1575. ),
  1576. // Get time offset for a point, relative to duration
  1577. pointToTime = function (point) {
  1578. return utilities.virtualAxisTranslate(
  1579. getPointTimeValue(point, options.pointPlayTime),
  1580. timeExtremes,
  1581. { min: 0, max: options.duration }
  1582. );
  1583. },
  1584. // Compute any data extremes that aren't defined yet
  1585. dataExtremes = getExtremesForInstrumentProps(
  1586. series.chart, options.instruments, options.dataExtremes
  1587. ),
  1588. // Make copies of the instruments used for this series, to allow
  1589. // multiple series with the same instrument to play together
  1590. instruments = makeInstrumentCopies(options.instruments),
  1591. // Go through the points, convert to events, optionally add Earcons
  1592. timelineEvents = series.points.reduce(function (events, point) {
  1593. var earcons = getPointEarcons(point, options.earcons || []),
  1594. time = pointToTime(point);
  1595. return events.concat(
  1596. // Event object for point
  1597. new H.sonification.TimelineEvent({
  1598. eventObject: point,
  1599. time: time,
  1600. id: point.id,
  1601. playOptions: {
  1602. instruments: instruments,
  1603. dataExtremes: dataExtremes
  1604. }
  1605. }),
  1606. // Earcons
  1607. earcons.map(function (earcon) {
  1608. return new H.sonification.TimelineEvent({
  1609. eventObject: earcon,
  1610. time: time
  1611. });
  1612. })
  1613. );
  1614. }, []);
  1615. // Build the timeline path
  1616. return new H.sonification.TimelinePath({
  1617. events: timelineEvents,
  1618. onStart: function () {
  1619. if (options.onStart) {
  1620. options.onStart(series);
  1621. }
  1622. },
  1623. onEventStart: function (event) {
  1624. var eventObject = event.options && event.options.eventObject;
  1625. if (eventObject instanceof H.Point) {
  1626. // Check for hidden series
  1627. if (
  1628. !eventObject.series.visible &&
  1629. !eventObject.series.chart.series.some(function (series) {
  1630. return series.visible;
  1631. })
  1632. ) {
  1633. // We have no visible series, stop the path.
  1634. event.timelinePath.timeline.pause();
  1635. event.timelinePath.timeline.resetCursor();
  1636. return false;
  1637. }
  1638. // Emit onPointStart
  1639. if (options.onPointStart) {
  1640. options.onPointStart(event, eventObject);
  1641. }
  1642. }
  1643. },
  1644. onEventEnd: function (eventData) {
  1645. var eventObject = eventData.event && eventData.event.options &&
  1646. eventData.event.options.eventObject;
  1647. if (eventObject instanceof H.Point && options.onPointEnd) {
  1648. options.onPointEnd(eventData.event, eventObject);
  1649. }
  1650. },
  1651. onEnd: function () {
  1652. if (options.onEnd) {
  1653. options.onEnd(series);
  1654. }
  1655. }
  1656. });
  1657. }
  1658. /**
  1659. * Sonify a series.
  1660. *
  1661. * @sample highcharts/sonification/series-basic/
  1662. * Click on series to sonify
  1663. * @sample highcharts/sonification/series-earcon/
  1664. * Series with earcon
  1665. * @sample highcharts/sonification/point-play-time/
  1666. * Play y-axis by time
  1667. * @sample highcharts/sonification/earcon-on-point/
  1668. * Earcon set on point
  1669. *
  1670. * @requires module:modules/sonification
  1671. *
  1672. * @function Highcharts.Series#sonify
  1673. *
  1674. * @param {Highcharts.SonifySeriesOptionsObject} options
  1675. * The options for sonifying this series.
  1676. */
  1677. function seriesSonify(options) {
  1678. var timelinePath = buildTimelinePathFromSeries(this, options),
  1679. chartSonification = this.chart.sonification;
  1680. // Only one timeline can play at a time. If we want multiple series playing
  1681. // at the same time, use chart.sonify.
  1682. if (chartSonification.timeline) {
  1683. chartSonification.timeline.pause();
  1684. }
  1685. // Create new timeline for this series, and play it.
  1686. chartSonification.timeline = new H.sonification.Timeline({
  1687. paths: [timelinePath]
  1688. });
  1689. chartSonification.timeline.play();
  1690. }
  1691. /**
  1692. * Utility function to assemble options for creating a TimelinePath from a
  1693. * series when sonifying an entire chart.
  1694. * @private
  1695. * @param {Highcharts.Series} series - The series to return options for.
  1696. * @param {object} dataExtremes - Pre-calculated data extremes for the chart.
  1697. * @param {object} chartSonifyOptions - Options passed in to chart.sonify.
  1698. * @return {object} Options for buildTimelinePathFromSeries.
  1699. */
  1700. function buildSeriesOptions(series, dataExtremes, chartSonifyOptions) {
  1701. var seriesOptions = chartSonifyOptions.seriesOptions || {};
  1702. return H.merge(
  1703. {
  1704. // Calculated dataExtremes for chart
  1705. dataExtremes: dataExtremes,
  1706. // We need to get timeExtremes for each series. We pass this
  1707. // in when building the TimelinePath objects to avoid
  1708. // calculating twice.
  1709. timeExtremes: getTimeExtremes(
  1710. series, chartSonifyOptions.pointPlayTime
  1711. ),
  1712. // Some options we just pass on
  1713. instruments: chartSonifyOptions.instruments,
  1714. onStart: chartSonifyOptions.onSeriesStart,
  1715. onEnd: chartSonifyOptions.onSeriesEnd,
  1716. earcons: chartSonifyOptions.earcons
  1717. },
  1718. // Merge in the specific series options by ID
  1719. H.isArray(seriesOptions) ? (
  1720. H.find(seriesOptions, function (optEntry) {
  1721. return optEntry.id === H.pick(series.id, series.options.id);
  1722. }) || {}
  1723. ) : seriesOptions,
  1724. {
  1725. // Forced options
  1726. pointPlayTime: chartSonifyOptions.pointPlayTime
  1727. }
  1728. );
  1729. }
  1730. /**
  1731. * Utility function to normalize the ordering of timeline paths when sonifying
  1732. * a chart.
  1733. * @private
  1734. * @param {string|Array<string|Highcharts.Earcon|Array<string|Highcharts.Earcon>>} orderOptions -
  1735. * Order options for the sonification.
  1736. * @param {Highcharts.Chart} chart - The chart we are sonifying.
  1737. * @param {Function} seriesOptionsCallback - A function that takes a series as
  1738. * argument, and returns the series options for that series to be used with
  1739. * buildTimelinePathFromSeries.
  1740. * @return {Array<object|Array<object|Highcharts.TimelinePath>>} If order is
  1741. * sequential, we return an array of objects to create series paths from. If
  1742. * order is simultaneous we return an array of an array with the same. If there
  1743. * is a custom order, we return an array of arrays of either objects (for
  1744. * series) or TimelinePaths (for earcons and delays).
  1745. */
  1746. function buildPathOrder(orderOptions, chart, seriesOptionsCallback) {
  1747. var order;
  1748. if (orderOptions === 'sequential' || orderOptions === 'simultaneous') {
  1749. // Just add the series from the chart
  1750. order = chart.series.reduce(function (seriesList, series) {
  1751. if (series.visible) {
  1752. seriesList.push({
  1753. series: series,
  1754. seriesOptions: seriesOptionsCallback(series)
  1755. });
  1756. }
  1757. return seriesList;
  1758. }, []);
  1759. // If order is simultaneous, group all series together
  1760. if (orderOptions === 'simultaneous') {
  1761. order = [order];
  1762. }
  1763. } else {
  1764. // We have a specific order, and potentially custom items - like
  1765. // earcons or silent waits.
  1766. order = orderOptions.reduce(function (orderList, orderDef) {
  1767. // Return set of items to play simultaneously. Could be only one.
  1768. var simulItems = H.splat(orderDef).reduce(function (items, item) {
  1769. var itemObject;
  1770. // Is this item a series ID?
  1771. if (typeof item === 'string') {
  1772. var series = chart.get(item);
  1773. if (series.visible) {
  1774. itemObject = {
  1775. series: series,
  1776. seriesOptions: seriesOptionsCallback(series)
  1777. };
  1778. }
  1779. // Is it an earcon? If so, just create the path.
  1780. } else if (item instanceof H.sonification.Earcon) {
  1781. // Path with a single event
  1782. itemObject = new H.sonification.TimelinePath({
  1783. events: [new H.sonification.TimelineEvent({
  1784. eventObject: item
  1785. })]
  1786. });
  1787. }
  1788. // Is this item a silent wait? If so, just create the path.
  1789. if (item.silentWait) {
  1790. itemObject = new H.sonification.TimelinePath({
  1791. silentWait: item.silentWait
  1792. });
  1793. }
  1794. // Add to items to play simultaneously
  1795. if (itemObject) {
  1796. items.push(itemObject);
  1797. }
  1798. return items;
  1799. }, []);
  1800. // Add to order list
  1801. if (simulItems.length) {
  1802. orderList.push(simulItems);
  1803. }
  1804. return orderList;
  1805. }, []);
  1806. }
  1807. return order;
  1808. }
  1809. /**
  1810. * Utility function to add a silent wait after all series.
  1811. * @private
  1812. * @param {Array<object|Array<object|TimelinePath>>} order - The order of items.
  1813. * @param {number} wait - The wait in milliseconds to add.
  1814. * @return {Array<object|Array<object|TimelinePath>>} The order with waits inserted.
  1815. */
  1816. function addAfterSeriesWaits(order, wait) {
  1817. if (!wait) {
  1818. return order;
  1819. }
  1820. return order.reduce(function (newOrder, orderDef, i) {
  1821. var simultaneousPaths = H.splat(orderDef);
  1822. newOrder.push(simultaneousPaths);
  1823. // Go through the simultaneous paths and see if there is a series there
  1824. if (
  1825. i < order.length - 1 && // Do not add wait after last series
  1826. simultaneousPaths.some(function (item) {
  1827. return item.series;
  1828. })
  1829. ) {
  1830. // We have a series, meaning we should add a wait after these
  1831. // paths have finished.
  1832. newOrder.push(new H.sonification.TimelinePath({
  1833. silentWait: wait
  1834. }));
  1835. }
  1836. return newOrder;
  1837. }, []);
  1838. }
  1839. /**
  1840. * Utility function to find the total amout of wait time in the TimelinePaths.
  1841. * @private
  1842. * @param {Array<object|Array<object|TimelinePath>>} order - The order of
  1843. * TimelinePaths/items.
  1844. * @return {number} The total time in ms spent on wait paths between playing.
  1845. */
  1846. function getWaitTime(order) {
  1847. return order.reduce(function (waitTime, orderDef) {
  1848. var def = H.splat(orderDef);
  1849. return waitTime + (
  1850. def.length === 1 && def[0].options && def[0].options.silentWait || 0
  1851. );
  1852. }, 0);
  1853. }
  1854. /**
  1855. * Utility function to ensure simultaneous paths have start/end events at the
  1856. * same time, to sync them.
  1857. * @private
  1858. * @param {Array<Highcharts.TimelinePath>} paths - The paths to sync.
  1859. */
  1860. function syncSimultaneousPaths(paths) {
  1861. // Find the extremes for these paths
  1862. var extremes = paths.reduce(function (extremes, path) {
  1863. var events = path.events;
  1864. if (events && events.length) {
  1865. extremes.min = Math.min(events[0].time, extremes.min);
  1866. extremes.max = Math.max(
  1867. events[events.length - 1].time, extremes.max
  1868. );
  1869. }
  1870. return extremes;
  1871. }, {
  1872. min: Infinity,
  1873. max: -Infinity
  1874. });
  1875. // Go through the paths and add events to make them fit the same timespan
  1876. paths.forEach(function (path) {
  1877. var events = path.events,
  1878. hasEvents = events && events.length,
  1879. eventsToAdd = [];
  1880. if (!(hasEvents && events[0].time <= extremes.min)) {
  1881. eventsToAdd.push(new H.sonification.TimelineEvent({
  1882. time: extremes.min
  1883. }));
  1884. }
  1885. if (!(hasEvents && events[events.length - 1].time >= extremes.max)) {
  1886. eventsToAdd.push(new H.sonification.TimelineEvent({
  1887. time: extremes.max
  1888. }));
  1889. }
  1890. if (eventsToAdd.length) {
  1891. path.addTimelineEvents(eventsToAdd);
  1892. }
  1893. });
  1894. }
  1895. /**
  1896. * Utility function to find the total duration span for all simul path sets
  1897. * that include series.
  1898. * @private
  1899. * @param {Array<object|Array<object|Highcharts.TimelinePath>>} order - The
  1900. * order of TimelinePaths/items.
  1901. * @return {number} The total time value span difference for all series.
  1902. */
  1903. function getSimulPathDurationTotal(order) {
  1904. return order.reduce(function (durationTotal, orderDef) {
  1905. return durationTotal + H.splat(orderDef).reduce(
  1906. function (maxPathDuration, item) {
  1907. var timeExtremes = item.series && item.seriesOptions &&
  1908. item.seriesOptions.timeExtremes;
  1909. return timeExtremes ?
  1910. Math.max(
  1911. maxPathDuration, timeExtremes.max - timeExtremes.min
  1912. ) : maxPathDuration;
  1913. },
  1914. 0
  1915. );
  1916. }, 0);
  1917. }
  1918. /**
  1919. * Function to calculate the duration in ms for a series.
  1920. * @private
  1921. * @param {number} seriesValueDuration - The duration of the series in value
  1922. * difference.
  1923. * @param {number} totalValueDuration - The total duration of all (non
  1924. * simultaneous) series in value difference.
  1925. * @param {number} totalDurationMs - The desired total duration for all series
  1926. * in milliseconds.
  1927. * @return {number} The duration for the series in milliseconds.
  1928. */
  1929. function getSeriesDurationMs(
  1930. seriesValueDuration, totalValueDuration, totalDurationMs
  1931. ) {
  1932. // A series spanning the whole chart would get the full duration.
  1933. return utilities.virtualAxisTranslate(
  1934. seriesValueDuration,
  1935. { min: 0, max: totalValueDuration },
  1936. { min: 0, max: totalDurationMs }
  1937. );
  1938. }
  1939. /**
  1940. * Convert series building objects into paths and return a new list of
  1941. * TimelinePaths.
  1942. * @private
  1943. * @param {Array<object|Array<object|Highcharts.TimelinePath>>} order - The
  1944. * order list.
  1945. * @param {number} duration - Total duration to aim for in milliseconds.
  1946. * @return {Array<Array<Highcharts.TimelinePath>>} Array of TimelinePath objects
  1947. * to play.
  1948. */
  1949. function buildPathsFromOrder(order, duration) {
  1950. // Find time used for waits (custom or after series), and subtract it from
  1951. // available duration.
  1952. var totalAvailableDurationMs = Math.max(
  1953. duration - getWaitTime(order), 0
  1954. ),
  1955. // Add up simultaneous path durations to find total value span duration
  1956. // of everything
  1957. totalUsedDuration = getSimulPathDurationTotal(order);
  1958. // Go through the order list and convert the items
  1959. return order.reduce(function (allPaths, orderDef) {
  1960. var simultaneousPaths = H.splat(orderDef).reduce(
  1961. function (simulPaths, item) {
  1962. if (item instanceof H.sonification.TimelinePath) {
  1963. // This item is already a path object
  1964. simulPaths.push(item);
  1965. } else if (item.series) {
  1966. // We have a series.
  1967. // We need to set the duration of the series
  1968. item.seriesOptions.duration =
  1969. item.seriesOptions.duration || getSeriesDurationMs(
  1970. item.seriesOptions.timeExtremes.max -
  1971. item.seriesOptions.timeExtremes.min,
  1972. totalUsedDuration,
  1973. totalAvailableDurationMs
  1974. );
  1975. // Add the path
  1976. simulPaths.push(buildTimelinePathFromSeries(
  1977. item.series,
  1978. item.seriesOptions
  1979. ));
  1980. }
  1981. return simulPaths;
  1982. }, []
  1983. );
  1984. // Add in the simultaneous paths
  1985. allPaths.push(simultaneousPaths);
  1986. return allPaths;
  1987. }, []);
  1988. }
  1989. /**
  1990. * Options for sonifying a chart.
  1991. *
  1992. * @requires module:modules/sonification
  1993. *
  1994. * @interface Highcharts.SonifyChartOptionsObject
  1995. *//**
  1996. * Duration for sonifying the entire chart. The duration is distributed across
  1997. * the different series intelligently, but does not take earcons into account.
  1998. * It is also possible to set the duration explicitly per series, using
  1999. * `seriesOptions`. Note that points may continue to play after the duration has
  2000. * passed, but no new points will start playing.
  2001. * @name Highcharts.SonifyChartOptionsObject#duration
  2002. * @type {number}
  2003. *//**
  2004. * Define the order to play the series in. This can be given as a string, or an
  2005. * array specifying a custom ordering. If given as a string, valid values are
  2006. * `sequential` - where each series is played in order - or `simultaneous`,
  2007. * where all series are played at once. For custom ordering, supply an array as
  2008. * the order. Each element in the array can be either a string with a series ID,
  2009. * an Earcon object, or an object with a numeric `silentWait` property
  2010. * designating a number of milliseconds to wait before continuing. Each element
  2011. * of the array will be played in order. To play elements simultaneously, group
  2012. * the elements in an array.
  2013. * @name Highcharts.SonifyChartOptionsObject#order
  2014. * @type {string|Array<string|Highcharts.Earcon|Array<string|Highcharts.Earcon>>}
  2015. *//**
  2016. * The axis to use for when to play the points. Can be a string with a data
  2017. * property (e.g. `x`), or a function. If it is a function, this function
  2018. * receives the point as argument, and should return a numeric value. The points
  2019. * with the lowest numeric values are then played first, and the time between
  2020. * points will be proportional to the distance between the numeric values. This
  2021. * option can not be overridden per series.
  2022. * @name Highcharts.SonifyChartOptionsObject#pointPlayTime
  2023. * @type {string|Function}
  2024. *//**
  2025. * Milliseconds of silent waiting to add between series. Note that waiting time
  2026. * is considered part of the sonify duration.
  2027. * @name Highcharts.SonifyChartOptionsObject#afterSeriesWait
  2028. * @type {number|undefined}
  2029. *//**
  2030. * Options as given to `series.sonify` to override options per series. If the
  2031. * option is supplied as an array of options objects, the `id` property of the
  2032. * object should correspond to the series' id. If the option is supplied as a
  2033. * single object, the options apply to all series.
  2034. * @name Highcharts.SonifyChartOptionsObject#seriesOptions
  2035. * @type {Object|Array<object>|undefined}
  2036. *//**
  2037. * The instrument definitions for the points in this chart.
  2038. * @name Highcharts.SonifyChartOptionsObject#instruments
  2039. * @type {Array<Highcharts.PointInstrumentObject>|undefined}
  2040. *//**
  2041. * Earcons to add to the chart. Note that earcons can also be added per series
  2042. * using `seriesOptions`.
  2043. * @name Highcharts.SonifyChartOptionsObject#earcons
  2044. * @type {Array<Highcharts.EarconConfiguration>|undefined}
  2045. *//**
  2046. * Optionally provide the minimum/maximum data values for the points. If this is
  2047. * not supplied, it is calculated from all points in the chart on demand. This
  2048. * option is supplied in the following format, as a map of point data properties
  2049. * to objects with min/max values:
  2050. * ```js
  2051. * dataExtremes: {
  2052. * y: {
  2053. * min: 0,
  2054. * max: 100
  2055. * },
  2056. * z: {
  2057. * min: -10,
  2058. * max: 10
  2059. * }
  2060. * // Properties used and not provided are calculated on demand
  2061. * }
  2062. * ```
  2063. * @name Highcharts.SonifyChartOptionsObject#dataExtremes
  2064. * @type {object|undefined}
  2065. *//**
  2066. * Callback before a series is played.
  2067. * @name Highcharts.SonifyChartOptionsObject#onSeriesStart
  2068. * @type {Function|undefined}
  2069. *//**
  2070. * Callback after a series has finished playing.
  2071. * @name Highcharts.SonifyChartOptionsObject#onSeriesEnd
  2072. * @type {Function|undefined}
  2073. *//**
  2074. * Callback after the chart has played.
  2075. * @name Highcharts.SonifyChartOptionsObject#onEnd
  2076. * @type {Function|undefined}
  2077. */
  2078. /**
  2079. * Sonify a chart.
  2080. *
  2081. * @sample highcharts/sonification/chart-sequential/
  2082. * Sonify a basic chart
  2083. * @sample highcharts/sonification/chart-simultaneous/
  2084. * Sonify series simultaneously
  2085. * @sample highcharts/sonification/chart-custom-order/
  2086. * Custom defined order of series
  2087. * @sample highcharts/sonification/chart-earcon/
  2088. * Earcons on chart
  2089. * @sample highcharts/sonification/chart-events/
  2090. * Sonification events on chart
  2091. *
  2092. * @requires module:modules/sonification
  2093. *
  2094. * @function Highcharts.Chart#sonify
  2095. *
  2096. * @param {Highcharts.SonifyChartOptionsObject} options
  2097. * The options for sonifying this chart.
  2098. */
  2099. function chartSonify(options) {
  2100. // Only one timeline can play at a time.
  2101. if (this.sonification.timeline) {
  2102. this.sonification.timeline.pause();
  2103. }
  2104. // Calculate data extremes for the props used
  2105. var dataExtremes = getExtremesForInstrumentProps(
  2106. this, options.instruments, options.dataExtremes
  2107. );
  2108. // Figure out ordering of series and custom paths
  2109. var order = buildPathOrder(options.order, this, function (series) {
  2110. return buildSeriesOptions(series, dataExtremes, options);
  2111. });
  2112. // Add waits after simultaneous paths with series in them.
  2113. order = addAfterSeriesWaits(order, options.afterSeriesWait || 0);
  2114. // We now have a list of either TimelinePath objects or series that need to
  2115. // be converted to TimelinePath objects. Convert everything to paths.
  2116. var paths = buildPathsFromOrder(order, options.duration);
  2117. // Sync simultaneous paths
  2118. paths.forEach(function (simultaneousPaths) {
  2119. syncSimultaneousPaths(simultaneousPaths);
  2120. });
  2121. // We have a set of paths. Create the timeline, and play it.
  2122. this.sonification.timeline = new H.sonification.Timeline({
  2123. paths: paths,
  2124. onEnd: options.onEnd
  2125. });
  2126. this.sonification.timeline.play();
  2127. }
  2128. /**
  2129. * Get a list of the points currently under cursor.
  2130. *
  2131. * @requires module:modules/sonification
  2132. *
  2133. * @function Highcharts.Chart#getCurrentSonifyPoints
  2134. *
  2135. * @return {Array<Highcharts.Point>}
  2136. * The points currently under the cursor.
  2137. */
  2138. function getCurrentPoints() {
  2139. var cursorObj;
  2140. if (this.sonification.timeline) {
  2141. cursorObj = this.sonification.timeline.getCursor(); // Cursor per pathID
  2142. return Object.keys(cursorObj).map(function (path) {
  2143. // Get the event objects under cursor for each path
  2144. return cursorObj[path].eventObject;
  2145. }).filter(function (eventObj) {
  2146. // Return the events that are points
  2147. return eventObj instanceof H.Point;
  2148. });
  2149. }
  2150. return [];
  2151. }
  2152. /**
  2153. * Set the cursor to a point or set of points in different series.
  2154. *
  2155. * @requires module:modules/sonification
  2156. *
  2157. * @function Highcharts.Chart#setSonifyCursor
  2158. *
  2159. * @param {Highcharts.Point|Array<Highcharts.Point>} points
  2160. * The point or points to set the cursor to. If setting multiple points
  2161. * under the cursor, the points have to be in different series that are
  2162. * being played simultaneously.
  2163. */
  2164. function setCursor(points) {
  2165. var timeline = this.sonification.timeline;
  2166. if (timeline) {
  2167. H.splat(points).forEach(function (point) {
  2168. // We created the events with the ID of the points, which makes
  2169. // this easy. Just call setCursor for each ID.
  2170. timeline.setCursor(point.id);
  2171. });
  2172. }
  2173. }
  2174. /**
  2175. * Pause the running sonification.
  2176. *
  2177. * @requires module:modules/sonification
  2178. *
  2179. * @function Highcharts.Chart#pauseSonify
  2180. *
  2181. * @param {boolean} [fadeOut=true]
  2182. * Fade out as we pause to avoid clicks.
  2183. */
  2184. function pause(fadeOut) {
  2185. if (this.sonification.timeline) {
  2186. this.sonification.timeline.pause(H.pick(fadeOut, true));
  2187. } else if (this.sonification.currentlyPlayingPoint) {
  2188. this.sonification.currentlyPlayingPoint.cancelSonify(fadeOut);
  2189. }
  2190. }
  2191. /**
  2192. * Resume the currently running sonification. Requires series.sonify or
  2193. * chart.sonify to have been played at some point earlier.
  2194. *
  2195. * @requires module:modules/sonification
  2196. *
  2197. * @function Highcharts.Chart#resumeSonify
  2198. *
  2199. * @param {Function} onEnd
  2200. * Callback to call when play finished.
  2201. */
  2202. function resume(onEnd) {
  2203. if (this.sonification.timeline) {
  2204. this.sonification.timeline.play(onEnd);
  2205. }
  2206. }
  2207. /**
  2208. * Play backwards from cursor. Requires series.sonify or chart.sonify to have
  2209. * been played at some point earlier.
  2210. *
  2211. * @requires module:modules/sonification
  2212. *
  2213. * @function Highcharts.Chart#rewindSonify
  2214. *
  2215. * @param {Function} onEnd
  2216. * Callback to call when play finished.
  2217. */
  2218. function rewind(onEnd) {
  2219. if (this.sonification.timeline) {
  2220. this.sonification.timeline.rewind(onEnd);
  2221. }
  2222. }
  2223. /**
  2224. * Cancel current sonification and reset cursor.
  2225. *
  2226. * @requires module:modules/sonification
  2227. *
  2228. * @function Highcharts.Chart#cancelSonify
  2229. *
  2230. * @param {boolean} [fadeOut=true]
  2231. * Fade out as we pause to avoid clicks.
  2232. */
  2233. function cancel(fadeOut) {
  2234. this.pauseSonify(fadeOut);
  2235. this.resetSonifyCursor();
  2236. }
  2237. /**
  2238. * Reset cursor to start. Requires series.sonify or chart.sonify to have been
  2239. * played at some point earlier.
  2240. *
  2241. * @requires module:modules/sonification
  2242. *
  2243. * @function Highcharts.Chart#resetSonifyCursor
  2244. */
  2245. function resetCursor() {
  2246. if (this.sonification.timeline) {
  2247. this.sonification.timeline.resetCursor();
  2248. }
  2249. }
  2250. /**
  2251. * Reset cursor to end. Requires series.sonify or chart.sonify to have been
  2252. * played at some point earlier.
  2253. *
  2254. * @requires module:modules/sonification
  2255. *
  2256. * @function Highcharts.Chart#resetSonifyCursorEnd
  2257. */
  2258. function resetCursorEnd() {
  2259. if (this.sonification.timeline) {
  2260. this.sonification.timeline.resetCursorEnd();
  2261. }
  2262. }
  2263. // Export functions
  2264. var chartSonifyFunctions = {
  2265. chartSonify: chartSonify,
  2266. seriesSonify: seriesSonify,
  2267. pause: pause,
  2268. resume: resume,
  2269. rewind: rewind,
  2270. cancel: cancel,
  2271. getCurrentPoints: getCurrentPoints,
  2272. setCursor: setCursor,
  2273. resetCursor: resetCursor,
  2274. resetCursorEnd: resetCursorEnd
  2275. };
  2276. return chartSonifyFunctions;
  2277. }(Highcharts, utilities));
  2278. var timelineClasses = (function (H, utilities) {
  2279. /* *
  2280. *
  2281. * (c) 2009-2019 Øystein Moseng
  2282. *
  2283. * TimelineEvent class definition.
  2284. *
  2285. * License: www.highcharts.com/license
  2286. *
  2287. * */
  2288. /**
  2289. * A set of options for the TimelineEvent class.
  2290. *
  2291. * @requires module:modules/sonification
  2292. *
  2293. * @private
  2294. * @interface Highcharts.TimelineEventOptionsObject
  2295. *//**
  2296. * The object we want to sonify when playing the TimelineEvent. Can be any
  2297. * object that implements the `sonify` and `cancelSonify` functions. If this is
  2298. * not supplied, the TimelineEvent is considered a silent event, and the onEnd
  2299. * event is immediately called.
  2300. * @name Highcharts.TimelineEventOptionsObject#eventObject
  2301. * @type {*}
  2302. *//**
  2303. * Options to pass on to the eventObject when playing it.
  2304. * @name Highcharts.TimelineEventOptionsObject#playOptions
  2305. * @type {object|undefined}
  2306. *//**
  2307. * The time at which we want this event to play (in milliseconds offset). This
  2308. * is not used for the TimelineEvent.play function, but rather intended as a
  2309. * property to decide when to call TimelineEvent.play. Defaults to 0.
  2310. * @name Highcharts.TimelineEventOptionsObject#time
  2311. * @type {number|undefined}
  2312. *//**
  2313. * Unique ID for the event. Generated automatically if not supplied.
  2314. * @name Highcharts.TimelineEventOptionsObject#id
  2315. * @type {string|undefined}
  2316. *//**
  2317. * Callback called when the play has finished.
  2318. * @name Highcharts.TimelineEventOptionsObject#onEnd
  2319. * @type {Function|undefined}
  2320. */
  2321. /**
  2322. * The TimelineEvent class. Represents a sound event on a timeline.
  2323. *
  2324. * @requires module:modules/sonification
  2325. *
  2326. * @private
  2327. * @class
  2328. * @name Highcharts.TimelineEvent
  2329. *
  2330. * @param {Highcharts.TimelineEventOptionsObject} options
  2331. * Options for the TimelineEvent.
  2332. */
  2333. function TimelineEvent(options) {
  2334. this.init(options || {});
  2335. }
  2336. TimelineEvent.prototype.init = function (options) {
  2337. this.options = options;
  2338. this.time = options.time || 0;
  2339. this.id = this.options.id = options.id || H.uniqueKey();
  2340. };
  2341. /**
  2342. * Play the event. Does not take the TimelineEvent.time option into account,
  2343. * and plays the event immediately.
  2344. *
  2345. * @function Highcharts.TimelineEvent#play
  2346. *
  2347. * @param {Highcharts.TimelineEventOptionsObject} [options]
  2348. * Options to pass in to the eventObject when playing it.
  2349. */
  2350. TimelineEvent.prototype.play = function (options) {
  2351. var eventObject = this.options.eventObject,
  2352. masterOnEnd = this.options.onEnd,
  2353. playOnEnd = options && options.onEnd,
  2354. playOptionsOnEnd = this.options.playOptions &&
  2355. this.options.playOptions.onEnd,
  2356. playOptions = H.merge(this.options.playOptions, options);
  2357. if (eventObject && eventObject.sonify) {
  2358. // If we have multiple onEnds defined, use all
  2359. playOptions.onEnd = masterOnEnd || playOnEnd || playOptionsOnEnd ?
  2360. function () {
  2361. var args = arguments;
  2362. [masterOnEnd, playOnEnd, playOptionsOnEnd].forEach(
  2363. function (onEnd) {
  2364. if (onEnd) {
  2365. onEnd.apply(this, args);
  2366. }
  2367. }
  2368. );
  2369. } : undefined;
  2370. eventObject.sonify(playOptions);
  2371. } else {
  2372. if (playOnEnd) {
  2373. playOnEnd();
  2374. }
  2375. if (masterOnEnd) {
  2376. masterOnEnd();
  2377. }
  2378. }
  2379. };
  2380. /**
  2381. * Cancel the sonification of this event. Does nothing if the event is not
  2382. * currently sonifying.
  2383. *
  2384. * @function Highcharts.TimelineEvent#cancel
  2385. *
  2386. * @param {boolean} [fadeOut=false]
  2387. * Whether or not to fade out as we stop. If false, the event is
  2388. * cancelled synchronously.
  2389. */
  2390. TimelineEvent.prototype.cancel = function (fadeOut) {
  2391. this.options.eventObject.cancelSonify(fadeOut);
  2392. };
  2393. /**
  2394. * A set of options for the TimelinePath class.
  2395. *
  2396. * @requires module:modules/
  2397. *
  2398. * @private
  2399. * @interface Highcharts.TimelinePathOptionsObject
  2400. *//**
  2401. * List of TimelineEvents to play on this track.
  2402. * @name Highcharts.TimelinePathOptionsObject#events
  2403. * @type {Array<Highcharts.TimelineEvent>}
  2404. *//**
  2405. * If this option is supplied, this path ignores all events and just waits for
  2406. * the specified number of milliseconds before calling onEnd.
  2407. * @name Highcharts.TimelinePathOptionsObject#silentWait
  2408. * @type {number|undefined}
  2409. *//**
  2410. * Unique ID for this timeline path. Automatically generated if not supplied.
  2411. * @name Highcharts.TimelinePathOptionsObject#id
  2412. * @type {string|undefined}
  2413. *//**
  2414. * Callback called before the path starts playing.
  2415. * @name Highcharts.TimelinePathOptionsObject#onStart
  2416. * @type {Function|undefined}
  2417. *//**
  2418. * Callback function to call before an event plays.
  2419. * @name Highcharts.TimelinePathOptionsObject#onEventStart
  2420. * @type {Function|undefined}
  2421. *//**
  2422. * Callback function to call after an event has stopped playing.
  2423. * @name Highcharts.TimelinePathOptionsObject#onEventEnd
  2424. * @type {Function|undefined}
  2425. *//**
  2426. * Callback called when the whole path is finished.
  2427. * @name Highcharts.TimelinePathOptionsObject#onEnd
  2428. * @type {Function|undefined}
  2429. */
  2430. /**
  2431. * The TimelinePath class. Represents a track on a timeline with a list of
  2432. * sound events to play at certain times relative to each other.
  2433. *
  2434. * @requires module:modules/sonification
  2435. *
  2436. * @private
  2437. * @class
  2438. * @name Highcharts.TimelinePath
  2439. *
  2440. * @param {Highcharts.TimelinePathOptionsObject} options
  2441. * Options for the TimelinePath.
  2442. */
  2443. function TimelinePath(options) {
  2444. this.init(options);
  2445. }
  2446. TimelinePath.prototype.init = function (options) {
  2447. this.options = options;
  2448. this.id = this.options.id = options.id || H.uniqueKey();
  2449. this.cursor = 0;
  2450. this.eventsPlaying = {};
  2451. // Handle silent wait, otherwise use events from options
  2452. this.events = options.silentWait ?
  2453. [
  2454. new TimelineEvent({ time: 0 }),
  2455. new TimelineEvent({ time: options.silentWait })
  2456. ] :
  2457. this.options.events;
  2458. // We need to sort our events by time
  2459. this.sortEvents();
  2460. // Get map from event ID to index
  2461. this.updateEventIdMap();
  2462. // Signal events to fire
  2463. this.signalHandler = new utilities.SignalHandler(
  2464. ['playOnEnd', 'masterOnEnd', 'onStart', 'onEventStart', 'onEventEnd']
  2465. );
  2466. this.signalHandler.registerSignalCallbacks(
  2467. H.merge(options, { masterOnEnd: options.onEnd })
  2468. );
  2469. };
  2470. /**
  2471. * Sort the internal event list by time.
  2472. * @private
  2473. */
  2474. TimelinePath.prototype.sortEvents = function () {
  2475. this.events = this.events.sort(function (a, b) {
  2476. return a.time - b.time;
  2477. });
  2478. };
  2479. /**
  2480. * Update the internal eventId to index map.
  2481. * @private
  2482. */
  2483. TimelinePath.prototype.updateEventIdMap = function () {
  2484. this.eventIdMap = this.events.reduce(function (acc, cur, i) {
  2485. acc[cur.id] = i;
  2486. return acc;
  2487. }, {});
  2488. };
  2489. /**
  2490. * Add events to the path. Should not be done while the path is playing.
  2491. * The new events are inserted according to their time property.
  2492. * @private
  2493. * @param {Array<Highcharts.TimelineEvent>} newEvents - The new timeline events
  2494. * to add.
  2495. */
  2496. TimelinePath.prototype.addTimelineEvents = function (newEvents) {
  2497. this.events = this.events.concat(newEvents);
  2498. this.sortEvents(); // Sort events by time
  2499. this.updateEventIdMap(); // Update the event ID to index map
  2500. };
  2501. /**
  2502. * Get the current TimelineEvent under the cursor.
  2503. * @private
  2504. * @return {Highcharts.TimelineEvent} The current timeline event.
  2505. */
  2506. TimelinePath.prototype.getCursor = function () {
  2507. return this.events[this.cursor];
  2508. };
  2509. /**
  2510. * Set the current TimelineEvent under the cursor.
  2511. * @private
  2512. * @param {string} eventId - The ID of the timeline event to set as current.
  2513. * @return {boolean} True if there is an event with this ID in the path. False
  2514. * otherwise.
  2515. */
  2516. TimelinePath.prototype.setCursor = function (eventId) {
  2517. var ix = this.eventIdMap[eventId];
  2518. if (ix !== undefined) {
  2519. this.cursor = ix;
  2520. return true;
  2521. }
  2522. return false;
  2523. };
  2524. /**
  2525. * Play the timeline from the current cursor.
  2526. * @private
  2527. * @param {Function} onEnd - Callback to call when play finished. Does not
  2528. * override other onEnd callbacks.
  2529. */
  2530. TimelinePath.prototype.play = function (onEnd) {
  2531. this.pause();
  2532. this.signalHandler.emitSignal('onStart');
  2533. this.signalHandler.clearSignalCallbacks(['playOnEnd']);
  2534. this.signalHandler.registerSignalCallbacks({ playOnEnd: onEnd });
  2535. this.playEvents(1);
  2536. };
  2537. /**
  2538. * Play the timeline backwards from the current cursor.
  2539. * @private
  2540. * @param {Function} onEnd - Callback to call when play finished. Does not
  2541. * override other onEnd callbacks.
  2542. */
  2543. TimelinePath.prototype.rewind = function (onEnd) {
  2544. this.pause();
  2545. this.signalHandler.emitSignal('onStart');
  2546. this.signalHandler.clearSignalCallbacks(['playOnEnd']);
  2547. this.signalHandler.registerSignalCallbacks({ playOnEnd: onEnd });
  2548. this.playEvents(-1);
  2549. };
  2550. /**
  2551. * Reset the cursor to the beginning.
  2552. * @private
  2553. */
  2554. TimelinePath.prototype.resetCursor = function () {
  2555. this.cursor = 0;
  2556. };
  2557. /**
  2558. * Reset the cursor to the end.
  2559. * @private
  2560. */
  2561. TimelinePath.prototype.resetCursorEnd = function () {
  2562. this.cursor = this.events.length - 1;
  2563. };
  2564. /**
  2565. * Cancel current playing. Leaves the cursor intact.
  2566. * @private
  2567. * @param {boolean} [fadeOut=false] - Whether or not to fade out as we stop. If
  2568. * false, the path is cancelled synchronously.
  2569. */
  2570. TimelinePath.prototype.pause = function (fadeOut) {
  2571. var timelinePath = this;
  2572. // Cancel next scheduled play
  2573. clearTimeout(timelinePath.nextScheduledPlay);
  2574. // Cancel currently playing events
  2575. Object.keys(timelinePath.eventsPlaying).forEach(function (id) {
  2576. if (timelinePath.eventsPlaying[id]) {
  2577. timelinePath.eventsPlaying[id].cancel(fadeOut);
  2578. }
  2579. });
  2580. timelinePath.eventsPlaying = {};
  2581. };
  2582. /**
  2583. * Play the events, starting from current cursor, and going in specified
  2584. * direction.
  2585. * @private
  2586. * @param {number} direction - The direction to play, 1 for forwards and -1 for
  2587. * backwards.
  2588. */
  2589. TimelinePath.prototype.playEvents = function (direction) {
  2590. var timelinePath = this,
  2591. curEvent = timelinePath.events[this.cursor],
  2592. nextEvent = timelinePath.events[this.cursor + direction],
  2593. timeDiff,
  2594. onEnd = function (signalData) {
  2595. timelinePath.signalHandler.emitSignal(
  2596. 'masterOnEnd', signalData
  2597. );
  2598. timelinePath.signalHandler.emitSignal(
  2599. 'playOnEnd', signalData
  2600. );
  2601. };
  2602. // Store reference to path on event
  2603. curEvent.timelinePath = timelinePath;
  2604. // Emit event, cancel if returns false
  2605. if (
  2606. timelinePath.signalHandler.emitSignal(
  2607. 'onEventStart', curEvent
  2608. ) === false
  2609. ) {
  2610. onEnd({
  2611. event: curEvent,
  2612. cancelled: true
  2613. });
  2614. return;
  2615. }
  2616. // Play the current event
  2617. timelinePath.eventsPlaying[curEvent.id] = curEvent;
  2618. curEvent.play({
  2619. onEnd: function (cancelled) {
  2620. var signalData = {
  2621. event: curEvent,
  2622. cancelled: !!cancelled
  2623. };
  2624. // Keep track of currently playing events for cancelling
  2625. delete timelinePath.eventsPlaying[curEvent.id];
  2626. // Handle onEventEnd
  2627. timelinePath.signalHandler.emitSignal('onEventEnd', signalData);
  2628. // Reached end of path?
  2629. if (!nextEvent) {
  2630. onEnd(signalData);
  2631. }
  2632. }
  2633. });
  2634. // Schedule next
  2635. if (nextEvent) {
  2636. timeDiff = Math.abs(nextEvent.time - curEvent.time);
  2637. if (timeDiff < 1) {
  2638. // Play immediately
  2639. timelinePath.cursor += direction;
  2640. timelinePath.playEvents(direction);
  2641. } else {
  2642. // Schedule after the difference in ms
  2643. this.nextScheduledPlay = setTimeout(function () {
  2644. timelinePath.cursor += direction;
  2645. timelinePath.playEvents(direction);
  2646. }, timeDiff);
  2647. }
  2648. }
  2649. };
  2650. /* ************************************************************************** *
  2651. * TIMELINE *
  2652. * ************************************************************************** */
  2653. /**
  2654. * A set of options for the Timeline class.
  2655. *
  2656. * @requires module:modules/sonification
  2657. *
  2658. * @private
  2659. * @interface Highcharts.TimelineOptionsObject
  2660. *//**
  2661. * List of TimelinePaths to play. Multiple paths can be grouped together and
  2662. * played simultaneously by supplying an array of paths in place of a single
  2663. * path.
  2664. * @name Highcharts.TimelineOptionsObject#paths
  2665. * @type {Array<Highcharts.TimelinePath|Array<Highcharts.TimelinePath>>}
  2666. *//**
  2667. * Callback function to call before a path plays.
  2668. * @name Highcharts.TimelineOptionsObject#onPathStart
  2669. * @type {Function|undefined}
  2670. *//**
  2671. * Callback function to call after a path has stopped playing.
  2672. * @name Highcharts.TimelineOptionsObject#onPathEnd
  2673. * @type {Function|undefined}
  2674. *//**
  2675. * Callback called when the whole path is finished.
  2676. * @name Highcharts.TimelineOptionsObject#onEnd
  2677. * @type {Function|undefined}
  2678. */
  2679. /**
  2680. * The Timeline class. Represents a sonification timeline with a list of
  2681. * timeline paths with events to play at certain times relative to each other.
  2682. *
  2683. * @requires module:modules/sonification
  2684. *
  2685. * @private
  2686. * @class
  2687. * @name Highcharts.Timeline
  2688. *
  2689. * @param {Highcharts.TimelineOptionsObject} options
  2690. * Options for the Timeline.
  2691. */
  2692. function Timeline(options) {
  2693. this.init(options || {});
  2694. }
  2695. Timeline.prototype.init = function (options) {
  2696. this.options = options;
  2697. this.cursor = 0;
  2698. this.paths = options.paths;
  2699. this.pathsPlaying = {};
  2700. this.signalHandler = new utilities.SignalHandler(
  2701. ['playOnEnd', 'masterOnEnd', 'onPathStart', 'onPathEnd']
  2702. );
  2703. this.signalHandler.registerSignalCallbacks(
  2704. H.merge(options, { masterOnEnd: options.onEnd })
  2705. );
  2706. };
  2707. /**
  2708. * Play the timeline forwards from cursor.
  2709. * @private
  2710. * @param {Function} onEnd - Callback to call when play finished. Does not
  2711. * override other onEnd callbacks.
  2712. */
  2713. Timeline.prototype.play = function (onEnd) {
  2714. this.pause();
  2715. this.signalHandler.clearSignalCallbacks(['playOnEnd']);
  2716. this.signalHandler.registerSignalCallbacks({ playOnEnd: onEnd });
  2717. this.playPaths(1);
  2718. };
  2719. /**
  2720. * Play the timeline backwards from cursor.
  2721. * @private
  2722. * @param {Function} onEnd - Callback to call when play finished. Does not
  2723. * override other onEnd callbacks.
  2724. */
  2725. Timeline.prototype.rewind = function (onEnd) {
  2726. this.pause();
  2727. this.signalHandler.clearSignalCallbacks(['playOnEnd']);
  2728. this.signalHandler.registerSignalCallbacks({ playOnEnd: onEnd });
  2729. this.playPaths(-1);
  2730. };
  2731. /**
  2732. * Play the timeline in the specified direction.
  2733. * @private
  2734. * @param {number} direction - Direction to play in. 1 for forwards, -1 for
  2735. * backwards.
  2736. */
  2737. Timeline.prototype.playPaths = function (direction) {
  2738. var curPaths = H.splat(this.paths[this.cursor]),
  2739. nextPaths = this.paths[this.cursor + direction],
  2740. timeline = this,
  2741. signalHandler = this.signalHandler,
  2742. pathsEnded = 0,
  2743. // Play a path
  2744. playPath = function (path) {
  2745. // Emit signal and set playing state
  2746. signalHandler.emitSignal('onPathStart', path);
  2747. timeline.pathsPlaying[path.id] = path;
  2748. // Do the play
  2749. path[direction > 0 ? 'play' : 'rewind'](function (callbackData) {
  2750. // Play ended callback
  2751. // Data to pass to signal callbacks
  2752. var cancelled = callbackData && callbackData.cancelled,
  2753. signalData = {
  2754. path: path,
  2755. cancelled: cancelled
  2756. };
  2757. // Clear state and send signal
  2758. delete timeline.pathsPlaying[path.id];
  2759. signalHandler.emitSignal('onPathEnd', signalData);
  2760. // Handle next paths
  2761. pathsEnded++;
  2762. if (pathsEnded >= curPaths.length) {
  2763. // We finished all of the current paths for cursor.
  2764. if (nextPaths && !cancelled) {
  2765. // We have more paths, move cursor along
  2766. timeline.cursor += direction;
  2767. // Reset upcoming path cursors before playing
  2768. H.splat(nextPaths).forEach(function (nextPath) {
  2769. nextPath[
  2770. direction > 0 ? 'resetCursor' : 'resetCursorEnd'
  2771. ]();
  2772. });
  2773. // Play next
  2774. timeline.playPaths(direction);
  2775. } else {
  2776. // If it is the last path in this direction, call onEnd
  2777. signalHandler.emitSignal('playOnEnd', signalData);
  2778. signalHandler.emitSignal('masterOnEnd', signalData);
  2779. }
  2780. }
  2781. });
  2782. };
  2783. // Go through the paths under cursor and play them
  2784. curPaths.forEach(function (path) {
  2785. if (path) {
  2786. // Store reference to timeline
  2787. path.timeline = timeline;
  2788. // Leave a timeout to let notes fade out before next play
  2789. setTimeout(function () {
  2790. playPath(path);
  2791. }, H.sonification.fadeOutTime);
  2792. }
  2793. });
  2794. };
  2795. /**
  2796. * Stop the playing of the timeline. Cancels all current sounds, but does not
  2797. * affect the cursor.
  2798. * @private
  2799. * @param {boolean} [fadeOut=false] - Whether or not to fade out as we stop. If
  2800. * false, the timeline is cancelled synchronously.
  2801. */
  2802. Timeline.prototype.pause = function (fadeOut) {
  2803. var timeline = this;
  2804. // Cancel currently playing events
  2805. Object.keys(timeline.pathsPlaying).forEach(function (id) {
  2806. if (timeline.pathsPlaying[id]) {
  2807. timeline.pathsPlaying[id].pause(fadeOut);
  2808. }
  2809. });
  2810. timeline.pathsPlaying = {};
  2811. };
  2812. /**
  2813. * Reset the cursor to the beginning of the timeline.
  2814. * @private
  2815. */
  2816. Timeline.prototype.resetCursor = function () {
  2817. this.paths.forEach(function (paths) {
  2818. H.splat(paths).forEach(function (path) {
  2819. path.resetCursor();
  2820. });
  2821. });
  2822. this.cursor = 0;
  2823. };
  2824. /**
  2825. * Reset the cursor to the end of the timeline.
  2826. * @private
  2827. */
  2828. Timeline.prototype.resetCursorEnd = function () {
  2829. this.paths.forEach(function (paths) {
  2830. H.splat(paths).forEach(function (path) {
  2831. path.resetCursorEnd();
  2832. });
  2833. });
  2834. this.cursor = this.paths.length - 1;
  2835. };
  2836. /**
  2837. * Set the current TimelineEvent under the cursor. If multiple paths are being
  2838. * played at the same time, this function only affects a single path (the one
  2839. * that contains the eventId that is passed in).
  2840. * @private
  2841. * @param {string} eventId - The ID of the timeline event to set as current.
  2842. * @return {boolean} True if the cursor was set, false if no TimelineEvent was
  2843. * found for this ID.
  2844. */
  2845. Timeline.prototype.setCursor = function (eventId) {
  2846. return this.paths.some(function (paths) {
  2847. return H.splat(paths).some(function (path) {
  2848. return path.setCursor(eventId);
  2849. });
  2850. });
  2851. };
  2852. /**
  2853. * Get the current TimelineEvents under the cursors. This function will return
  2854. * the event under the cursor for each currently playing path, as an object
  2855. * where the path ID is mapped to the TimelineEvent under that path's cursor.
  2856. * @private
  2857. * @return {object} The TimelineEvents under each path's cursors.
  2858. */
  2859. Timeline.prototype.getCursor = function () {
  2860. return this.getCurrentPlayingPaths().reduce(function (acc, cur) {
  2861. acc[cur.id] = cur.getCursor();
  2862. return acc;
  2863. }, {});
  2864. };
  2865. /**
  2866. * Check if timeline is reset or at start.
  2867. * @private
  2868. * @return {boolean} True if timeline is at the beginning.
  2869. */
  2870. Timeline.prototype.atStart = function () {
  2871. return !this.getCurrentPlayingPaths().some(function (path) {
  2872. return path.cursor;
  2873. });
  2874. };
  2875. /**
  2876. * Get the current TimelinePaths being played.
  2877. * @private
  2878. * @return {Array<Highcharts.TimelinePath>} The TimelinePaths currently being
  2879. * played.
  2880. */
  2881. Timeline.prototype.getCurrentPlayingPaths = function () {
  2882. return H.splat(this.paths[this.cursor]);
  2883. };
  2884. // Export the classes
  2885. var timelineClasses = {
  2886. TimelineEvent: TimelineEvent,
  2887. TimelinePath: TimelinePath,
  2888. Timeline: Timeline
  2889. };
  2890. return timelineClasses;
  2891. }(Highcharts, utilities));
  2892. (function (H, Instrument, instruments, Earcon, pointSonifyFunctions, chartSonifyFunctions, utilities, TimelineClasses) {
  2893. /* *
  2894. *
  2895. * (c) 2009-2019 Øystein Moseng
  2896. *
  2897. * Sonification module for Highcharts
  2898. *
  2899. * License: www.highcharts.com/license
  2900. *
  2901. * */
  2902. // Expose on the Highcharts object
  2903. /**
  2904. * Global classes and objects related to sonification.
  2905. *
  2906. * @requires module:modules/sonification
  2907. *
  2908. * @name Highcharts.sonification
  2909. * @type {Highcharts.SonificationObject}
  2910. */
  2911. /**
  2912. * Global classes and objects related to sonification.
  2913. *
  2914. * @requires module:modules/sonification
  2915. *
  2916. * @interface Highcharts.SonificationObject
  2917. *//**
  2918. * Note fade-out-time in milliseconds. Most notes are faded out quickly by
  2919. * default if there is time. This is to avoid abrupt stops which will cause
  2920. * perceived clicks.
  2921. * @name Highcharts.SonificationObject#fadeOutDuration
  2922. * @type {number}
  2923. *//**
  2924. * Utility functions.
  2925. * @name Highcharts.SonificationObject#utilities
  2926. * @private
  2927. * @type {object}
  2928. *//**
  2929. * The Instrument class.
  2930. * @name Highcharts.SonificationObject#Instrument
  2931. * @type {Function}
  2932. *//**
  2933. * Predefined instruments, given as an object with a map between the instrument
  2934. * name and the Highcharts.Instrument object.
  2935. * @name Highcharts.SonificationObject#instruments
  2936. * @type {Object}
  2937. *//**
  2938. * The Earcon class.
  2939. * @name Highcharts.SonificationObject#Earcon
  2940. * @type {Function}
  2941. *//**
  2942. * The TimelineEvent class.
  2943. * @private
  2944. * @name Highcharts.SonificationObject#TimelineEvent
  2945. * @type {Function}
  2946. *//**
  2947. * The TimelinePath class.
  2948. * @private
  2949. * @name Highcharts.SonificationObject#TimelinePath
  2950. * @type {Function}
  2951. *//**
  2952. * The Timeline class.
  2953. * @private
  2954. * @name Highcharts.SonificationObject#Timeline
  2955. * @type {Function}
  2956. */
  2957. H.sonification = {
  2958. fadeOutDuration: 20,
  2959. // Classes and functions
  2960. utilities: utilities,
  2961. Instrument: Instrument,
  2962. instruments: instruments,
  2963. Earcon: Earcon,
  2964. TimelineEvent: TimelineClasses.TimelineEvent,
  2965. TimelinePath: TimelineClasses.TimelinePath,
  2966. Timeline: TimelineClasses.Timeline
  2967. };
  2968. // Chart specific
  2969. H.Point.prototype.sonify = pointSonifyFunctions.pointSonify;
  2970. H.Point.prototype.cancelSonify = pointSonifyFunctions.pointCancelSonify;
  2971. H.Series.prototype.sonify = chartSonifyFunctions.seriesSonify;
  2972. H.extend(H.Chart.prototype, {
  2973. sonify: chartSonifyFunctions.chartSonify,
  2974. pauseSonify: chartSonifyFunctions.pause,
  2975. resumeSonify: chartSonifyFunctions.resume,
  2976. rewindSonify: chartSonifyFunctions.rewind,
  2977. cancelSonify: chartSonifyFunctions.cancel,
  2978. getCurrentSonifyPoints: chartSonifyFunctions.getCurrentPoints,
  2979. setSonifyCursor: chartSonifyFunctions.setCursor,
  2980. resetSonifyCursor: chartSonifyFunctions.resetCursor,
  2981. resetSonifyCursorEnd: chartSonifyFunctions.resetCursorEnd,
  2982. sonification: {}
  2983. });
  2984. }(Highcharts, Instrument, instruments, Earcon, pointSonifyFunctions, chartSonifyFunctions, utilities, timelineClasses));
  2985. return (function () {
  2986. }());
  2987. }));