pointSonify.js 14 KB

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