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