pointSonify.js 14 KB

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