pointSonify.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. /* *
  2. *
  3. * (c) 2009-2020 Øystein Moseng
  4. *
  5. * Code for sonifying single points.
  6. *
  7. * License: www.highcharts.com/license
  8. *
  9. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  10. *
  11. * */
  12. 'use strict';
  13. import H from '../../parts/Globals.js';
  14. import U from '../../parts/Utilities.js';
  15. var error = U.error, merge = U.merge, pick = U.pick;
  16. /**
  17. * Define the parameter mapping for an instrument.
  18. *
  19. * @requires module:modules/sonification
  20. *
  21. * @interface Highcharts.PointInstrumentMappingObject
  22. */ /**
  23. * Define the volume of the instrument. This can be a string with a data
  24. * property name, e.g. `'y'`, in which case this data property is used to define
  25. * the volume relative to the `y`-values of the other points. A higher `y` value
  26. * would then result in a higher volume. This option can also be a fixed number
  27. * or a function. If it is a function, this function is called in regular
  28. * intervals while the note is playing. It receives three arguments: The point,
  29. * the dataExtremes, and the current relative time - where 0 is the beginning of
  30. * the note and 1 is the end. The function should return the volume of the note
  31. * as a number between 0 and 1.
  32. * @name Highcharts.PointInstrumentMappingObject#volume
  33. * @type {string|number|Function}
  34. */ /**
  35. * Define the duration of the notes for this instrument. This can be a string
  36. * with a data property name, e.g. `'y'`, in which case this data property is
  37. * used to define the duration relative to the `y`-values of the other points. A
  38. * higher `y` value would then result in a longer duration. This option can also
  39. * be a fixed number or a function. If it is a function, this function is called
  40. * once before the note starts playing, and should return the duration in
  41. * milliseconds. It receives two arguments: The point, and the dataExtremes.
  42. * @name Highcharts.PointInstrumentMappingObject#duration
  43. * @type {string|number|Function}
  44. */ /**
  45. * Define the panning of the instrument. This can be a string with a data
  46. * property name, e.g. `'x'`, in which case this data property is used to define
  47. * the panning relative to the `x`-values of the other points. A higher `x`
  48. * value would then result in a higher panning value (panned further to the
  49. * right). This option can also be a fixed number or a function. If it is a
  50. * function, this function is called in regular intervals while the note is
  51. * playing. It receives three arguments: The point, the dataExtremes, and the
  52. * current relative time - where 0 is the beginning of the note and 1 is the
  53. * end. The function should return the panning of the note as a number between
  54. * -1 and 1.
  55. * @name Highcharts.PointInstrumentMappingObject#pan
  56. * @type {string|number|Function|undefined}
  57. */ /**
  58. * Define the frequency of the instrument. This can be a string with a data
  59. * property name, e.g. `'y'`, in which case this data property is used to define
  60. * the frequency relative to the `y`-values of the other points. A higher `y`
  61. * value would then result in a higher frequency. This option can also be a
  62. * fixed number or a function. If it is a function, this function is called in
  63. * regular intervals while the note is playing. It receives three arguments:
  64. * The point, the dataExtremes, and the current relative time - where 0 is the
  65. * beginning of the note and 1 is the end. The function should return the
  66. * frequency of the note as a number (in Hz).
  67. * @name Highcharts.PointInstrumentMappingObject#frequency
  68. * @type {string|number|Function}
  69. */
  70. /**
  71. * @requires module:modules/sonification
  72. *
  73. * @interface Highcharts.PointInstrumentOptionsObject
  74. */ /**
  75. * The minimum duration for a note when using a data property for duration. Can
  76. * be overridden by using either a fixed number or a function for
  77. * instrumentMapping.duration. Defaults to 20.
  78. * @name Highcharts.PointInstrumentOptionsObject#minDuration
  79. * @type {number|undefined}
  80. */ /**
  81. * The maximum duration for a note when using a data property for duration. Can
  82. * be overridden by using either a fixed number or a function for
  83. * instrumentMapping.duration. Defaults to 2000.
  84. * @name Highcharts.PointInstrumentOptionsObject#maxDuration
  85. * @type {number|undefined}
  86. */ /**
  87. * The minimum pan value for a note when using a data property for panning. Can
  88. * be overridden by using either a fixed number or a function for
  89. * instrumentMapping.pan. Defaults to -1 (fully left).
  90. * @name Highcharts.PointInstrumentOptionsObject#minPan
  91. * @type {number|undefined}
  92. */ /**
  93. * The maximum pan value for a note when using a data property for panning. Can
  94. * be overridden by using either a fixed number or a function for
  95. * instrumentMapping.pan. Defaults to 1 (fully right).
  96. * @name Highcharts.PointInstrumentOptionsObject#maxPan
  97. * @type {number|undefined}
  98. */ /**
  99. * The minimum volume for a note when using a data property for volume. Can be
  100. * overridden by using either a fixed number or a function for
  101. * instrumentMapping.volume. Defaults to 0.1.
  102. * @name Highcharts.PointInstrumentOptionsObject#minVolume
  103. * @type {number|undefined}
  104. */ /**
  105. * The maximum volume for a note when using a data property for volume. Can be
  106. * overridden by using either a fixed number or a function for
  107. * instrumentMapping.volume. Defaults to 1.
  108. * @name Highcharts.PointInstrumentOptionsObject#maxVolume
  109. * @type {number|undefined}
  110. */ /**
  111. * The minimum frequency for a note when using a data property for frequency.
  112. * Can be overridden by using either a fixed number or a function for
  113. * instrumentMapping.frequency. Defaults to 220.
  114. * @name Highcharts.PointInstrumentOptionsObject#minFrequency
  115. * @type {number|undefined}
  116. */ /**
  117. * The maximum frequency for a note when using a data property for frequency.
  118. * Can be overridden by using either a fixed number or a function for
  119. * instrumentMapping.frequency. Defaults to 2200.
  120. * @name Highcharts.PointInstrumentOptionsObject#maxFrequency
  121. * @type {number|undefined}
  122. */
  123. /**
  124. * An instrument definition for a point, specifying the instrument to play and
  125. * how to play it.
  126. *
  127. * @interface Highcharts.PointInstrumentObject
  128. */ /**
  129. * An Instrument instance or the name of the instrument in the
  130. * Highcharts.sonification.instruments map.
  131. * @name Highcharts.PointInstrumentObject#instrument
  132. * @type {Highcharts.Instrument|string}
  133. */ /**
  134. * Mapping of instrument parameters for this instrument.
  135. * @name Highcharts.PointInstrumentObject#instrumentMapping
  136. * @type {Highcharts.PointInstrumentMappingObject}
  137. */ /**
  138. * Options for this instrument.
  139. * @name Highcharts.PointInstrumentObject#instrumentOptions
  140. * @type {Highcharts.PointInstrumentOptionsObject|undefined}
  141. */ /**
  142. * Callback to call when the instrument has stopped playing.
  143. * @name Highcharts.PointInstrumentObject#onEnd
  144. * @type {Function|undefined}
  145. */
  146. /**
  147. * Options for sonifying a point.
  148. * @interface Highcharts.PointSonifyOptionsObject
  149. */ /**
  150. * The instrument definitions for this point.
  151. * @name Highcharts.PointSonifyOptionsObject#instruments
  152. * @type {Array<Highcharts.PointInstrumentObject>}
  153. */ /**
  154. * Optionally provide the minimum/maximum values for the points. If this is not
  155. * supplied, it is calculated from the points in the chart on demand. This
  156. * option is supplied in the following format, as a map of point data properties
  157. * to objects with min/max values:
  158. * ```js
  159. * dataExtremes: {
  160. * y: {
  161. * min: 0,
  162. * max: 100
  163. * },
  164. * z: {
  165. * min: -10,
  166. * max: 10
  167. * }
  168. * // Properties used and not provided are calculated on demand
  169. * }
  170. * ```
  171. * @name Highcharts.PointSonifyOptionsObject#dataExtremes
  172. * @type {object|undefined}
  173. */ /**
  174. * Callback called when the sonification has finished.
  175. * @name Highcharts.PointSonifyOptionsObject#onEnd
  176. * @type {Function|undefined}
  177. */
  178. import utilities from './utilities.js';
  179. // Defaults for the instrument options
  180. // NOTE: Also change defaults in Highcharts.PointInstrumentOptionsObject if
  181. // making changes here.
  182. var defaultInstrumentOptions = {
  183. minDuration: 20,
  184. maxDuration: 2000,
  185. minVolume: 0.1,
  186. maxVolume: 1,
  187. minPan: -1,
  188. maxPan: 1,
  189. minFrequency: 220,
  190. maxFrequency: 2200
  191. };
  192. /* eslint-disable no-invalid-this, valid-jsdoc */
  193. /**
  194. * Sonify a single point.
  195. *
  196. * @sample highcharts/sonification/point-basic/
  197. * Click on points to sonify
  198. * @sample highcharts/sonification/point-advanced/
  199. * Sonify bubbles
  200. *
  201. * @requires module:modules/sonification
  202. *
  203. * @function Highcharts.Point#sonify
  204. *
  205. * @param {Highcharts.PointSonifyOptionsObject} options
  206. * Options for the sonification of the point.
  207. *
  208. * @return {void}
  209. */
  210. function pointSonify(options) {
  211. var point = this, chart = point.series.chart, dataExtremes = options.dataExtremes || {},
  212. // Get the value to pass to instrument.play from the mapping value
  213. // passed in.
  214. getMappingValue = function (value, makeFunction, allowedExtremes) {
  215. // Function. Return new function if we try to use callback,
  216. // otherwise call it now and return result.
  217. if (typeof value === 'function') {
  218. return makeFunction ?
  219. function (time) {
  220. return value(point, dataExtremes, time);
  221. } :
  222. value(point, dataExtremes);
  223. }
  224. // String, this is a data prop.
  225. if (typeof value === 'string') {
  226. // Find data extremes if we don't have them
  227. dataExtremes[value] = dataExtremes[value] ||
  228. utilities.calculateDataExtremes(point.series.chart, value);
  229. // Find the value
  230. return utilities.virtualAxisTranslate(pick(point[value], point.options[value]), dataExtremes[value], allowedExtremes);
  231. }
  232. // Fixed number or something else weird, just use that
  233. return value;
  234. };
  235. // Register playing point on chart
  236. chart.sonification.currentlyPlayingPoint = point;
  237. // Keep track of instruments playing
  238. point.sonification = point.sonification || {};
  239. point.sonification.instrumentsPlaying =
  240. point.sonification.instrumentsPlaying || {};
  241. // Register signal handler for the point
  242. var signalHandler = point.sonification.signalHandler =
  243. point.sonification.signalHandler ||
  244. new utilities.SignalHandler(['onEnd']);
  245. signalHandler.clearSignalCallbacks();
  246. signalHandler.registerSignalCallbacks({ onEnd: options.onEnd });
  247. // If we have a null point or invisible point, just return
  248. if (point.isNull || !point.visible || !point.series.visible) {
  249. signalHandler.emitSignal('onEnd');
  250. return;
  251. }
  252. // Go through instruments and play them
  253. options.instruments.forEach(function (instrumentDefinition) {
  254. var instrument = typeof instrumentDefinition.instrument === 'string' ?
  255. H.sonification.instruments[instrumentDefinition.instrument] :
  256. instrumentDefinition.instrument, mapping = instrumentDefinition.instrumentMapping || {}, extremes = merge(defaultInstrumentOptions, instrumentDefinition.instrumentOptions), id = instrument.id, onEnd = function (cancelled) {
  257. // Instrument on end
  258. if (instrumentDefinition.onEnd) {
  259. instrumentDefinition.onEnd.apply(this, arguments);
  260. }
  261. // Remove currently playing point reference on chart
  262. if (chart.sonification &&
  263. chart.sonification.currentlyPlayingPoint) {
  264. delete chart.sonification.currentlyPlayingPoint;
  265. }
  266. // Remove reference from instruments playing
  267. if (point.sonification && point.sonification.instrumentsPlaying) {
  268. delete point.sonification.instrumentsPlaying[id];
  269. // This was the last instrument?
  270. if (!Object.keys(point.sonification.instrumentsPlaying).length) {
  271. signalHandler.emitSignal('onEnd', cancelled);
  272. }
  273. }
  274. };
  275. // Play the note on the instrument
  276. if (instrument && instrument.play) {
  277. point.sonification.instrumentsPlaying[instrument.id] =
  278. instrument;
  279. instrument.play({
  280. frequency: getMappingValue(mapping.frequency, true, { min: extremes.minFrequency, max: extremes.maxFrequency }),
  281. duration: getMappingValue(mapping.duration, false, { min: extremes.minDuration, max: extremes.maxDuration }),
  282. pan: getMappingValue(mapping.pan, true, { min: extremes.minPan, max: extremes.maxPan }),
  283. volume: getMappingValue(mapping.volume, true, { min: extremes.minVolume, max: extremes.maxVolume }),
  284. onEnd: onEnd,
  285. minFrequency: extremes.minFrequency,
  286. maxFrequency: extremes.maxFrequency
  287. });
  288. }
  289. else {
  290. error(30);
  291. }
  292. });
  293. }
  294. /**
  295. * Cancel sonification of a point. Calls onEnd functions.
  296. *
  297. * @requires module:modules/sonification
  298. *
  299. * @function Highcharts.Point#cancelSonify
  300. *
  301. * @param {boolean} [fadeOut=false]
  302. * Whether or not to fade out as we stop. If false, the points are
  303. * cancelled synchronously.
  304. *
  305. * @return {void}
  306. */
  307. function pointCancelSonify(fadeOut) {
  308. var playing = this.sonification && this.sonification.instrumentsPlaying, instrIds = playing && Object.keys(playing);
  309. if (instrIds && instrIds.length) {
  310. instrIds.forEach(function (instr) {
  311. playing[instr].stop(!fadeOut, null, 'cancelled');
  312. });
  313. this.sonification.instrumentsPlaying = {};
  314. this.sonification.signalHandler.emitSignal('onEnd', 'cancelled');
  315. }
  316. }
  317. var pointSonifyFunctions = {
  318. pointSonify: pointSonify,
  319. pointCancelSonify: pointCancelSonify
  320. };
  321. export default pointSonifyFunctions;