├── .editorconfig
├── .eslintrc.js
├── .eslintrc.json
├── .gitignore
├── .npmignore
├── .travis.yml
├── README.md
├── index.js
├── jsdoc.template.hbs
├── package-lock.json
├── package.json
├── scripts
└── commit-msg
├── src
├── audio-player.js
├── data-types.js
├── midi-transformer.js
├── midi-visualizer.js
├── renderers.js
└── renderers
│ ├── d3.js
│ ├── three.js
│ └── utils.js
└── test
├── audio-player.spec.js
├── data-types.spec.js
├── fixtures
├── MIDIOkFormat1.mid
└── minimal-valid-midi.mid
├── helpers.js
├── midi-transformer.spec.js
├── midi-visualizer.spec.js
└── renderers
├── d3.spec.js
├── three.spec.js
└── utils.spec.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: http://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | # Unix-style newlines with a newline ending every file
7 | [*]
8 | end_of_line = lf
9 | insert_final_newline = true
10 |
11 | # Matches multiple files with brace expansion notation
12 | # Set default charset
13 | [*.{js}]
14 | charset = utf-8
15 |
16 | # Tab indentation (no size specified)
17 | [*.js]
18 | indent_style = tab
19 |
20 | # Matches the exact files either package.json or .travis.yml
21 | [{package.json,.travis.yml,.jshintrc}]
22 | indent_style = space
23 | indent_size = 2
24 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "env": {
3 | "browser": true,
4 | "commonjs": true,
5 | "es6": true
6 | },
7 | "extends": "eslint:recommended",
8 | "rules": {
9 | "indent": [
10 | 2,
11 | "tab"
12 | ],
13 | "linebreak-style": [
14 | 2,
15 | "unix"
16 | ],
17 | "quotes": [
18 | 2,
19 | "single"
20 | ],
21 | "semi": [
22 | 2,
23 | "always"
24 | ]
25 | }
26 | };
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "indent": [
4 | 2,
5 | "tab"
6 | ],
7 | "quotes": [
8 | 2,
9 | "single"
10 | ],
11 | "linebreak-style": [
12 | 2,
13 | "unix"
14 | ],
15 | "semi": [
16 | 2,
17 | "always"
18 | ]
19 | },
20 | "env": {
21 | "browser": true
22 | },
23 | "extends": "eslint:recommended"
24 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .vscode
3 | *.swp
4 | *.swo
5 | *.js.map
6 | node_modules
7 | npm-debug.log
8 | dist
9 | coverage
10 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/edhille/midi-visualizer/c23c1bd8d0983cba9fe95c0f85f3a790cb44da8b/.npmignore
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 8
4 | - 10
5 | after_success:
6 | - npm run ci-coverage
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # midi-visualizer
2 |
3 | [](https://travis-ci.org/edhille/midi-visualizer)
4 | [](https://coveralls.io/github/edhille/midi-visualizer?branch=master)
5 | [](https://david-dm.org/edhille/midi-visualizer)
6 |
7 | A simple, functional-based midi visualization library
8 |
9 | ## Example
10 |
11 | ```
12 | const initMidiVisualizer = import 'midi-visualizer';
13 | const config = {
14 | window: window,
15 | root: document.getElementById('#my-root'),
16 | width: 500,
17 | height: 500,
18 | midi: {
19 | data: myFnToFetchMidiData()
20 | },
21 | audio: {
22 | data: myFnToFetchAudioData()
23 | },
24 | renderer: setupMyCustomRenderer()
25 | };
26 |
27 | initMidiVisualizer(config).then((visualizer) => {
28 | const playingVisualizer = visualizer.play();
29 | // all your other fun operations...
30 | }).catch((error) => console.error('Oh man, something bad happened:', error));
31 | ```
32 |
33 | ## API Reference
34 |
35 |
36 |
37 | ## midiVisualizer
38 | Monad managing visualization animation of midi data
39 |
40 |
41 | * [midiVisualizer](#module_midiVisualizer)
42 | * [~restart(playheadSec)](#module_midiVisualizer..restart) ⇒
43 | * [~pause()](#module_midiVisualizer..pause) ⇒
44 | * [~stop()](#module_midiVisualizer..stop) ⇒
45 | * [~resize()](#module_midiVisualizer..resize) ⇒
46 |
47 |
48 |
49 | ### midiVisualizer~restart(playheadSec) ⇒
50 | put MidiVisualizer into "play" state
51 |
52 | **Kind**: inner method of [midiVisualizer](#module_midiVisualizer)
53 | **Returns**: MidiVisualizer
54 |
55 | | Param | Type | Description |
56 | | --- | --- | --- |
57 | | playheadSec | number
| offset in seconds to start playback |
58 |
59 |
60 |
61 | ### midiVisualizer~pause() ⇒
62 | put MidiVisualizer into "pause" state
63 |
64 | **Kind**: inner method of [midiVisualizer](#module_midiVisualizer)
65 | **Returns**: MidiVisualizer
66 |
67 |
68 | ### midiVisualizer~stop() ⇒
69 | put MidiVisualizer into "stop" state
70 |
71 | **Kind**: inner method of [midiVisualizer](#module_midiVisualizer)
72 | **Returns**: MidiVisualizer
73 |
74 |
75 | ### midiVisualizer~resize() ⇒
76 | handle resize of page MidiVisualizer is rendering into
77 |
78 | **Kind**: inner method of [midiVisualizer](#module_midiVisualizer)
79 | **Returns**: MidiVisualizer
80 |
81 |
82 |
83 |
84 | ## midi-visualizer : object
85 | **Kind**: global namespace
86 |
87 |
88 | ### midi-visualizer~initMidiVisualizer(config) ⇒ Promise(MidiVisualizer, Error)
89 | initializes MidiVisualizer monad
90 |
91 | **Kind**: inner method of [midi-visualizer](#midi-visualizer)
92 | **Returns**: Promise(MidiVisualizer, Error)
- promise that fulfills with MidiVisualizer instance
93 |
94 | | Param | Type | Description |
95 | | --- | --- | --- |
96 | | config | object
| configuration data to set up MidiVisualizer |
97 | | config.midi.data | UInt8Array
| array of unsigned 8-bit integers representing Midi data |
98 | | config.audio.data | UInt8Array
| array of unsigned 8-bit integers representing audio data |
99 | | config.window | Window
| Window of page holding the player |
100 | | config.root | HTMLElement
| HTMLElement that will be the root node of the visualizer |
101 | | config.render | Renderer
| Renderer strategy to use |
102 | | config.width | number
| the width of our canvans |
103 | | config.height | number
| the height of our canvans |
104 |
105 |
106 |
107 |
108 |
109 | ## RenderUtils
110 |
111 | * [RenderUtils](#module_RenderUtils)
112 | * [~MAX_RAF_DELTA_MS](#module_RenderUtils..MAX_RAF_DELTA_MS) : number
113 | * [~play(state, player, renderFn, resumeFn)](#module_RenderUtils..play) ⇒ RendererState
114 | * [~pause(state)](#module_RenderUtils..pause) ⇒ RendererState
115 | * [~stop(state)](#module_RenderUtils..stop) ⇒ RendererState
116 | * [~transformEvents(state, trackTransforms, animEvents)](#module_RenderUtils..transformEvents) ⇒ Array.<RenderEvent>
117 | * [~mapEvents(state, midi, config)](#module_RenderUtils..mapEvents) ⇒ RendererState
118 | * [~maxNote(currentMaxNote, event)](#module_RenderUtils..maxNote) ⇒ number
119 | * [~minNote(currentMinNote, event)](#module_RenderUtils..minNote) ⇒ number
120 | * [~isNoteToggleEvent(event)](#module_RenderUtils..isNoteToggleEvent) ⇒ boolean
121 | * [~isNoteOnEvent(event)](#module_RenderUtils..isNoteOnEvent) ⇒ boolean
122 | * [~render(state, cleanupFn, rafFn, currentRunningEvents, renderEvents, nowMs)](#module_RenderUtils..render) ⇒ Array.<RenderEvent>
123 |
124 |
125 |
126 | ### RenderUtils~MAX_RAF_DELTA_MS : number
127 | **Kind**: inner constant of [RenderUtils](#module_RenderUtils)
128 | **Default**: 16
129 |
130 |
131 | ### RenderUtils~play(state, player, renderFn, resumeFn) ⇒ RendererState
132 | Put visualizer in "play" state (where audio player is playing and animations are running)
133 |
134 | **Kind**: inner method of [RenderUtils](#module_RenderUtils)
135 | **Returns**: RendererState
- - new monad state
136 |
137 | | Param | Type | Description |
138 | | --- | --- | --- |
139 | | state | RendererState
| current monad state |
140 | | player | [AudioPlayer](#AudioPlayer)
| audio player used for audio playback we are syncing to |
141 | | renderFn | RenderUtils~render
| callback for actual rendering |
142 | | resumeFn | RenderUtils~resume
| callback for resuming playback after stopping |
143 |
144 |
145 |
146 | ### RenderUtils~pause(state) ⇒ RendererState
147 | Put visualizer in "paused" state (where audio player is paused and animations are not running)
148 |
149 | **Kind**: inner method of [RenderUtils](#module_RenderUtils)
150 | **Returns**: RendererState
- - new monad state
151 |
152 | | Param | Type | Description |
153 | | --- | --- | --- |
154 | | state | RendererState
| current monad state |
155 |
156 |
157 |
158 | ### RenderUtils~stop(state) ⇒ RendererState
159 | Put visualizer in "stopped" state (where audio player is stopped and animations are not running)
160 |
161 | **Kind**: inner method of [RenderUtils](#module_RenderUtils)
162 | **Returns**: RendererState
- - new monad state
163 |
164 | | Param | Type | Description |
165 | | --- | --- | --- |
166 | | state | RendererState
| current monad state |
167 |
168 |
169 |
170 | ### RenderUtils~transformEvents(state, trackTransforms, animEvents) ⇒ Array.<RenderEvent>
171 | Applies given track transforms to animation events
172 |
173 | **Kind**: inner method of [RenderUtils](#module_RenderUtils)
174 | **Returns**: Array.<RenderEvent>
- array of transformed renderEvents
175 |
176 | | Param | Type | Description |
177 | | --- | --- | --- |
178 | | state | RendererState
| state monad |
179 | | trackTransforms | Array.<function()>
| callback functions (TODO: document) |
180 | | animEvents | Array.<AnimEvent>
| given animation events to transform |
181 |
182 |
183 |
184 | ### RenderUtils~mapEvents(state, midi, config) ⇒ RendererState
185 | Map over given Midi data, transforming MidiEvents into RenderEvents
186 |
187 | **Kind**: inner method of [RenderUtils](#module_RenderUtils)
188 | **Returns**: RendererState
- - new monad state
189 |
190 | | Param | Type | Description |
191 | | --- | --- | --- |
192 | | state | RendererState
| current monad state |
193 | | midi | Midi
| midi data to map to RenderEvents |
194 | | config | object
| configuration data |
195 |
196 |
197 |
198 | ### RenderUtils~maxNote(currentMaxNote, event) ⇒ number
199 | Compare given note with note in given RenderEvent, returning whichever is larger
200 |
201 | **Kind**: inner method of [RenderUtils](#module_RenderUtils)
202 | **Returns**: number
- - largest of two notes
203 |
204 | | Param | Type | Description |
205 | | --- | --- | --- |
206 | | currentMaxNote | number
| value of current "max" note |
207 | | event | RenderEvent
| RenderEvent containing note to compare |
208 |
209 |
210 |
211 | ### RenderUtils~minNote(currentMinNote, event) ⇒ number
212 | Compare given note with note in given RenderEvent, returning whichever is smaller
213 |
214 | **Kind**: inner method of [RenderUtils](#module_RenderUtils)
215 | **Returns**: number
- - smallest of two notes
216 |
217 | | Param | Type | Description |
218 | | --- | --- | --- |
219 | | currentMinNote | number
| value of current "min" note |
220 | | event | RenderEvent
| RenderEvent containing note to compare |
221 |
222 |
223 |
224 | ### RenderUtils~isNoteToggleEvent(event) ⇒ boolean
225 | Predicate to test whether given RenderEvent is for a note on/off event
226 |
227 | **Kind**: inner method of [RenderUtils](#module_RenderUtils)
228 | **Returns**: boolean
- - is it a note on/off event
229 |
230 | | Param | Type | Description |
231 | | --- | --- | --- |
232 | | event | RenderEvent
| RenderEvent to test |
233 |
234 |
235 |
236 | ### RenderUtils~isNoteOnEvent(event) ⇒ boolean
237 | Predicate to test whether given RenderEvent is for a note on event
238 |
239 | **Kind**: inner method of [RenderUtils](#module_RenderUtils)
240 | **Returns**: boolean
- - is it a note on event
241 |
242 | | Param | Type | Description |
243 | | --- | --- | --- |
244 | | event | RenderEvent
| RenderEvent to test |
245 |
246 |
247 |
248 | ### RenderUtils~render(state, cleanupFn, rafFn, currentRunningEvents, renderEvents, nowMs) ⇒ Array.<RenderEvent>
249 | render function
250 |
251 | **Kind**: inner method of [RenderUtils](#module_RenderUtils)
252 | **Returns**: Array.<RenderEvent>
- - active running render events for this render call
253 |
254 | | Param | Type | Description |
255 | | --- | --- | --- |
256 | | state | RendererState
| monad state |
257 | | cleanupFn | function
| callback to remove expired animation artifacts |
258 | | rafFn | function
| RAF callback to do actual animation |
259 | | currentRunningEvents | Array.<RenderEvent>
| RenderEvents currently being animated |
260 | | renderEvents | Array.<RenderEvent>
| new RenderEvents to animate |
261 | | nowMs | number
| current time in milliseconds |
262 |
263 |
264 |
265 |
266 |
267 | ## ThreeJsRenderer
268 |
269 | * [ThreeJsRenderer](#module_ThreeJsRenderer)
270 | * [~prepDOM(midi, config)](#module_ThreeJsRenderer..prepDOM) ⇒ ThreeJsRendererState
271 | * [~resize(state, dimension)](#module_ThreeJsRenderer..resize) ⇒ ThreeJsRendererState
272 | * [~cleanup(state, currentRunningEvents[, expiredEvents[)](#module_ThreeJsRenderer..cleanup) ⇒ undefined
273 | * [~generateReturnFn(midi, config)](#module_ThreeJsRenderer..generateReturnFn) ⇒
274 | * [~generate(renderConfig, frameRenderer, cleanupFn)](#module_ThreeJsRenderer..generate) ⇒ ThreeJsRenderer~generateReturnFn
275 | * [~frameRenderCb](#module_ThreeJsRenderer..frameRenderCb) ⇒
276 |
277 |
278 |
279 | ### ThreeJsRenderer~prepDOM(midi, config) ⇒ ThreeJsRendererState
280 | handles initialization of DOM for renderer
281 |
282 | **Kind**: inner method of [ThreeJsRenderer](#module_ThreeJsRenderer)
283 |
284 | | Param | Type | Description |
285 | | --- | --- | --- |
286 | | midi | Midi
| Midi instance of song information |
287 | | config | object
| configuration information |
288 | | config.window | Window
| Window where rendering will take place |
289 | | config.root | HTMLElement
| DOM Element that will hold render canvas |
290 | | dimension.width | number
| width of the rendering area |
291 | | dimension.height | number
| height of the renderering area |
292 |
293 |
294 |
295 | ### ThreeJsRenderer~resize(state, dimension) ⇒ ThreeJsRendererState
296 | deals with resizing of the browser window
297 |
298 | **Kind**: inner method of [ThreeJsRenderer](#module_ThreeJsRenderer)
299 |
300 | | Param | Type | Description |
301 | | --- | --- | --- |
302 | | state | ThreeJsRendererState
| current renderer state |
303 | | dimension | object
| dimensions of render area |
304 | | dimension.width | number
| |
305 | | dimension.height | number
| |
306 |
307 |
308 |
309 | ### ThreeJsRenderer~cleanup(state, currentRunningEvents[, expiredEvents[) ⇒ undefined
310 | removes any object from the scene
311 |
312 | **Kind**: inner method of [ThreeJsRenderer](#module_ThreeJsRenderer)
313 |
314 | | Param | Type | Description |
315 | | --- | --- | --- |
316 | | state | ThreeJsRendererState
| current renderer state |
317 | | currentRunningEvents[ | RenderEvent
| array of RenderEvents currently active |
318 | | expiredEvents[ | RenderEvent
| array of RenderEvents that are no longer active and should be cleaned up |
319 |
320 |
321 |
322 | ### ThreeJsRenderer~generateReturnFn(midi, config) ⇒
323 | function returned to user for creating instance of ThreeJsRenderer
324 |
325 | **Kind**: inner method of [ThreeJsRenderer](#module_ThreeJsRenderer)
326 | **Returns**: ThreeJsRenderer
327 |
328 | | Param | Type | Description |
329 | | --- | --- | --- |
330 | | midi | Midi
| Midi data to be renderered |
331 | | config | object
| configuration information |
332 | | config.window | Window
| Window where rendering will take place |
333 | | config.root | HTMLElement
| DOM Element that will hold render canvas |
334 | | dimension.width | number
| width of the rendering area |
335 | | dimension.height | number
| height of the renderering area |
336 |
337 |
338 |
339 | ### ThreeJsRenderer~generate(renderConfig, frameRenderer, cleanupFn) ⇒ ThreeJsRenderer~generateReturnFn
340 | generator to create ThreeJsRenderer
341 |
342 | **Kind**: inner method of [ThreeJsRenderer](#module_ThreeJsRenderer)
343 |
344 | | Param | Type | Description |
345 | | --- | --- | --- |
346 | | renderConfig | object
| configuration information for setup |
347 | | frameRenderer | ThreeJsRenderer~frameRenderCb
| callback for rendering events |
348 | | cleanupFn | [cleanupCb](#ThreeJsRenderer..cleanupCb)
| callback for cleaning up THREEJS |
349 |
350 |
351 |
352 | ### ThreeJsRenderer~frameRenderCb ⇒
353 | callback for actual rendering of frame
354 |
355 | **Kind**: inner typedef of [ThreeJsRenderer](#module_ThreeJsRenderer)
356 | **Returns**: undefined
357 |
358 | | Param | Type | Description |
359 | | --- | --- | --- |
360 | | eventsToAdd[ | ThreeJsRenderEvent
| events that are queued up to be rendered in the next frame |
361 | | scene | THREEJS~Scene
| ThreeJS scene events should be renderered in |
362 | | camera | THREEJS~Camera
| ThreeJS camera for given scene |
363 | | THREE | THREEJS
| ThreeJS |
364 |
365 |
366 |
367 |
368 |
369 | ## D3Renderer
370 |
371 | * [D3Renderer](#module_D3Renderer)
372 | * [~prepDOM(midi, config)](#module_D3Renderer..prepDOM) ⇒ D3RendererState
373 | * [~resize(state, dimension)](#module_D3Renderer..resize) ⇒ D3RendererState
374 | * [~generateReturnFn(midi, config)](#module_D3Renderer..generateReturnFn) ⇒
375 | * [~generate(renderConfig)](#module_D3Renderer..generate) ⇒ D3Renderer
376 |
377 |
378 |
379 | ### D3Renderer~prepDOM(midi, config) ⇒ D3RendererState
380 | handles initialization of DOM for renderer
381 |
382 | **Kind**: inner method of [D3Renderer](#module_D3Renderer)
383 |
384 | | Param | Type | Description |
385 | | --- | --- | --- |
386 | | midi | Midi
| Midi instance of song information |
387 | | config | object
| configuration information |
388 | | config.window | Window
| Window where rendering will take place |
389 | | config.root | HTMLElement
| DOM Element that will hold render canvas |
390 | | dimension.width | number
| width of the rendering area |
391 | | dimension.height | number
| height of the renderering area |
392 |
393 |
394 |
395 | ### D3Renderer~resize(state, dimension) ⇒ D3RendererState
396 | deals with resizing of the browser window
397 |
398 | **Kind**: inner method of [D3Renderer](#module_D3Renderer)
399 |
400 | | Param | Type | Description |
401 | | --- | --- | --- |
402 | | state | D3RendererState
| current renderer state |
403 | | dimension | object
| dimensions of render area |
404 | | dimension.width | number
| |
405 | | dimension.height | number
| |
406 |
407 |
408 |
409 | ### D3Renderer~generateReturnFn(midi, config) ⇒
410 | function returned to user for creating instance of D3Renderer
411 |
412 | **Kind**: inner method of [D3Renderer](#module_D3Renderer)
413 | **Returns**: D3Renderer
414 |
415 | | Param | Type | Description |
416 | | --- | --- | --- |
417 | | midi | Midi
| Midi data to be renderered |
418 | | config | object
| configuration information |
419 | | config.window | Window
| Window where rendering will take place |
420 | | config.root | HTMLElement
| DOM Element that will hold render canvas |
421 | | dimension.width | number
| width of the rendering area |
422 | | dimension.height | number
| height of the renderering area |
423 |
424 |
425 |
426 | ### D3Renderer~generate(renderConfig) ⇒ D3Renderer
427 | generator to create D3Renderer
428 |
429 | **Kind**: inner method of [D3Renderer](#module_D3Renderer)
430 |
431 | | Param | Type | Description |
432 | | --- | --- | --- |
433 | | renderConfig | object
| configuration data for renderer |
434 | | renderConfig.frameRenderer | frameRenderCb
| callback for rendering individual frames |
435 |
436 |
437 |
438 |
439 |
440 | ## AudioPlayer
441 | **Kind**: global class
442 |
443 | * [AudioPlayer](#AudioPlayer)
444 | * [new AudioPlayer(params)](#new_AudioPlayer_new)
445 | * _instance_
446 | * [.getPlayheadTime()](#AudioPlayer+getPlayheadTime) ⇒
447 | * [.play([startTimeOffset], [playheadSec])](#AudioPlayer+play)
448 | * [.pause(stopAfter)](#AudioPlayer+pause)
449 | * _static_
450 | * [.getAudioContextFromWindow(window)](#AudioPlayer.getAudioContextFromWindow) ⇒
451 | * _inner_
452 | * [~loadDataCallback](#AudioPlayer..loadDataCallback) : function
453 |
454 |
455 |
456 | ### new AudioPlayer(params)
457 | manages audio playback
458 |
459 | **Returns**: AudioPlayer
460 |
461 | | Param | Type | Description |
462 | | --- | --- | --- |
463 | | params | object
| settings for audio player |
464 | | params.window | Window
| Window used to retrieve AudioContext |
465 |
466 |
467 |
468 | ### audioPlayer.getPlayheadTime() ⇒
469 | gets the playhead time in milliseconds
470 |
471 | **Kind**: instance method of [AudioPlayer](#AudioPlayer)
472 | **Returns**: playheadTimeMs
473 |
474 |
475 | ### audioPlayer.play([startTimeOffset], [playheadSec])
476 | initiates playing of audio
477 |
478 | **Kind**: instance method of [AudioPlayer](#AudioPlayer)
479 |
480 | | Param | Type | Default | Description |
481 | | --- | --- | --- | --- |
482 | | [startTimeOffset] | number
| 0
| offset in seconds to wait before playing |
483 | | [playheadSec] | number
| 0
| where to start playback within audio in seconds |
484 |
485 |
486 |
487 | ### audioPlayer.pause(stopAfter)
488 | pauses playing of audio
489 |
490 | **Kind**: instance method of [AudioPlayer](#AudioPlayer)
491 |
492 | | Param | Type | Description |
493 | | --- | --- | --- |
494 | | stopAfter | number
| number of seconds to wait before stopping |
495 |
496 |
497 |
498 | ### AudioPlayer.getAudioContextFromWindow(window) ⇒
499 | cross-browser fetch of AudioContext from given window
500 |
501 | **Kind**: static method of [AudioPlayer](#AudioPlayer)
502 | **Returns**: AudioContext
503 |
504 | | Param | Type | Description |
505 | | --- | --- | --- |
506 | | window | Window
| Window to fetch AudioContext from |
507 |
508 |
509 |
510 | ### AudioPlayer~loadDataCallback : function
511 | loads given audio data and invokes callback when done
512 |
513 | **Kind**: inner typedef of [AudioPlayer](#AudioPlayer)
514 |
515 | | Param | Type | Default | Description |
516 | | --- | --- | --- | --- |
517 | | audioData | ArrayBuffer
| | ArrayBuffer of data for audio to play |
518 | | callback | [loadDataCallback](#AudioPlayer..loadDataCallback)
| | callback to invoke when audioData is finished loading |
519 | | [err] | string
| null
| string of error message (null if no error) |
520 | | [self] | [AudioPlayer](#AudioPlayer)
| | ref to AudioPlayer instance if loading successful (undefined otherwise) |
521 |
522 |
523 |
524 |
525 |
526 | ## DataTypes
527 |
528 | * [DataTypes](#module_DataTypes)
529 | * [~MidiVisualizerState](#module_DataTypes..MidiVisualizerState)
530 | * [new MidiVisualizerState(params)](#new_module_DataTypes..MidiVisualizerState_new)
531 | * [~RendererState](#module_DataTypes..RendererState)
532 | * [new RendererState(params)](#new_module_DataTypes..RendererState_new)
533 | * [~D3RendererState](#module_DataTypes..D3RendererState) ⇐ RendererState
534 | * [new D3RendererState()](#new_module_DataTypes..D3RendererState_new)
535 | * [~ThreeJsRendererState](#module_DataTypes..ThreeJsRendererState) ⇐ RendererState
536 | * [new ThreeJsRendererState()](#new_module_DataTypes..ThreeJsRendererState_new)
537 | * [~AnimEvent](#module_DataTypes..AnimEvent)
538 | * [new AnimEvent([id])](#new_module_DataTypes..AnimEvent_new)
539 | * [~RenderEvent](#module_DataTypes..RenderEvent)
540 | * [new RenderEvent()](#new_module_DataTypes..RenderEvent_new)
541 | * [~D3RenderEvent](#module_DataTypes..D3RenderEvent) ⇐ RenderEvent
542 | * [new D3RenderEvent()](#new_module_DataTypes..D3RenderEvent_new)
543 | * [~ThreeJsRenderEvent](#module_DataTypes..ThreeJsRenderEvent) ⇐ RenderEvent
544 | * [new ThreeJsRenderEvent()](#new_module_DataTypes..ThreeJsRenderEvent_new)
545 |
546 |
547 |
548 | ### DataTypes~MidiVisualizerState
549 | **Kind**: inner class of [DataTypes](#module_DataTypes)
550 |
551 |
552 | #### new MidiVisualizerState(params)
553 | top-level data type representing state of MidiVisualizer
554 |
555 | **Returns**: MidiVisualizerState
556 |
557 | | Param | Type | Default | Description |
558 | | --- | --- | --- | --- |
559 | | params | object
| | properties to set |
560 | | params.audioPlayer | [AudioPlayer](#AudioPlayer)
| | AudioPlayer instance managing audio to sync with |
561 | | params.renderer | Renderer
| | Renderer used to draw visualization |
562 | | [params.animEventsByTimeMs] | object
| {}
| AnimEvent to render, grouped by millisecond-based mark where they should be rendered |
563 | | [params.isPlaying] | boolean
| false
| flag indicating whether currently playing |
564 |
565 |
566 |
567 | ### DataTypes~RendererState
568 | **Kind**: inner class of [DataTypes](#module_DataTypes)
569 |
570 |
571 | #### new RendererState(params)
572 | top-level data type representing state of Renderer
573 |
574 | **Returns**: RendererState
575 |
576 | | Param | Type | Default | Description |
577 | | --- | --- | --- | --- |
578 | | params | object
| | properties to set |
579 | | params.id | string
| | unique identifier for renderer |
580 | | params.root | HTMLElement
| | HTMLElement to use as root node for renderer canvas placement |
581 | | params.window | Window
| | Window we are rendering in (note, Window must have a 'document') |
582 | | [params.width] | number
| 0
| width for rendering canvas |
583 | | [params.height] | number
| 0
| height for rendering canvas |
584 | | [param.renderEvents] | Array.<RenderEvents>
| []
| RenderEvents to render |
585 | | [params.scales] | Array.<object>
| []
| Scales for normalizing position/sizing |
586 | | [params.isPlaying] | boolean
| false
| flag indicating whether currently playing |
587 |
588 |
589 |
590 | ### DataTypes~D3RendererState ⇐ RendererState
591 | **Kind**: inner class of [DataTypes](#module_DataTypes)
592 | **Extends:** RendererState
593 |
594 |
595 | #### new D3RendererState()
596 | data type representing state of Renderer that uses D3
597 |
598 | **Returns**: D3RendererState
599 |
600 | | Param | Type | Description |
601 | | --- | --- | --- |
602 | | params.svg | SVGElement
| SVGElement for renderering |
603 |
604 |
605 |
606 | ### DataTypes~ThreeJsRendererState ⇐ RendererState
607 | **Kind**: inner class of [DataTypes](#module_DataTypes)
608 | **Extends:** RendererState
609 |
610 |
611 | #### new ThreeJsRendererState()
612 | data type representing state of Renderer that uses D3
613 |
614 | **Returns**: ThreeJsRendererState
615 |
616 | | Param | Type | Description |
617 | | --- | --- | --- |
618 | | params.THREE | THREEJS
| ThreeJs object |
619 | | params.camera | Camera
| ThreeJs Camera to use |
620 | | params.scene | Scene
| ThreeJs Scene to use |
621 | | params.renderer | Renderer
| Renderer monad to use |
622 |
623 |
624 |
625 | ### DataTypes~AnimEvent
626 | **Kind**: inner class of [DataTypes](#module_DataTypes)
627 |
628 |
629 | #### new AnimEvent([id])
630 | data type representing individual animation event
631 |
632 | **Returns**: AnimEvent
633 |
634 | | Param | Type | Default | Description |
635 | | --- | --- | --- | --- |
636 | | params.event | MidiEvent
| | MidiEvent being renderered |
637 | | [params.track] | number
| 0
| index of midi track event belongs to |
638 | | [params.startTimeMicroSec] | number
| 0
| offset in microseconds from beginning of song when event starts |
639 | | [params.lengthMicroSec] | number
| 0
| length of event in microseconds |
640 | | [params.microSecPerBeat] | number
| 500000
| number of microseconds per beat |
641 | | [id] | string
| "<track>-<event.note || startTimeInMicroSec>"
| unique ID of event |
642 |
643 |
644 |
645 | ### DataTypes~RenderEvent
646 | **Kind**: inner class of [DataTypes](#module_DataTypes)
647 |
648 |
649 | #### new RenderEvent()
650 | base data type representing individual render event
651 |
652 | **Returns**: RenderEvent
653 |
654 | | Param | Type | Default | Description |
655 | | --- | --- | --- | --- |
656 | | params.id | id
| | unique string identifier for event |
657 | | params.track | number
| | index of midi track event belongs to |
658 | | params.subtype | string
| | midi event subtype |
659 | | params.x | number
| | x position for element |
660 | | params.y | number
| | y position for element |
661 | | params.lengthMicroSec | number
| | length of event in microseconds |
662 | | params.microSecPerBeat | number
| | number of microseconds per beat |
663 | | [params.z] | number
| 0
| z position for element |
664 | | [params.microSecPerBeat] | number
| 500000
| number of microseconds per beat |
665 | | [params.color] | string
| "'#FFFFFF'"
| color of element to render |
666 |
667 |
668 |
669 | ### DataTypes~D3RenderEvent ⇐ RenderEvent
670 | **Kind**: inner class of [DataTypes](#module_DataTypes)
671 | **Extends:** RenderEvent
672 |
673 |
674 | #### new D3RenderEvent()
675 | data type representing individual render event using D3
676 |
677 | **Returns**: D3RenderEvent
678 |
679 | | Param | Type | Description |
680 | | --- | --- | --- |
681 | | [params.path] | string
| SVG path string (required if no 'radius' given) |
682 | | [params.radius] | number
| radius to use for rendering circle (required if no 'path' given) |
683 | | [params.scale] | d3.Scale
| D3.Scale (required if 'path' is given) |
684 |
685 |
686 |
687 | ### DataTypes~ThreeJsRenderEvent ⇐ RenderEvent
688 | **Kind**: inner class of [DataTypes](#module_DataTypes)
689 | **Extends:** RenderEvent
690 |
691 |
692 | #### new ThreeJsRenderEvent()
693 | data type representing individual render event using ThreeJS
694 |
695 | **Returns**: ThreeJsRenderEvent
696 |
697 | | Param | Type | Default | Description |
698 | | --- | --- | --- | --- |
699 | | [params.scale] | number
| 1
| scaling factor |
700 | | [params.zRot] | number
| 0
| z-rotation |
701 | | [params.xRot] | number
| 0
| x-rotation |
702 | | [params.yRot] | number
| 0
| y-rotation |
703 | | [params.note] | number
| | midi note value (0-127) |
704 | | [params.shape] | THREEJS~Object3D
| | ThreeJs Object3D of shape representing this event |
705 |
706 |
707 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | visualizer: require('./src/midi-visualizer'),
5 | renderers: require('./src/renderers'),
6 | dataTypes: require('./src/data-types')
7 | };
8 |
--------------------------------------------------------------------------------
/jsdoc.template.hbs:
--------------------------------------------------------------------------------
1 | # midi-visualizer
2 |
3 | [](https://travis-ci.org/edhille/midi-visualizer)
4 | [](https://coveralls.io/github/edhille/midi-visualizer?branch=master)
5 |
6 | A simple, functional-based midi visualization library
7 |
8 | ## Example
9 |
10 | ```
11 | const initMidiVisualizer = import 'midi-visualizer';
12 | const config = {
13 | window: window,
14 | root: document.getElementById('#my-root'),
15 | width: 500,
16 | height: 500,
17 | midi: {
18 | data: myFnToFetchMidiData()
19 | },
20 | audio: {
21 | data: myFnToFetchAudioData()
22 | },
23 | renderer: setupMyCustomRenderer()
24 | };
25 |
26 | initMidiVisualizer(config).then((visualizer) => {
27 | const playingVisualizer = visualizer.play();
28 | // all your other fun operations...
29 | }).catch((error) => console.error('Oh man, something bad happened:', error));
30 | ```
31 |
32 | ## API Reference
33 |
34 | {{#module name="midiVisualizer"}}{{>docs}}{{/module}}
35 |
36 | {{#namespace name="midi-visualizer"}}{{>docs}}{{/namespace}}
37 |
38 | {{#module name="RenderUtils"}}{{>docs}}{{/module}}
39 |
40 | {{#module name="ThreeJsRenderer"}}{{>docs}}{{/module}}
41 |
42 | {{#module name="D3Renderer"}}{{>docs}}{{/module}}
43 |
44 | {{#class name="AudioPlayer"}}{{>docs}}{{/class}}
45 |
46 | {{#module name="DataTypes"}}{{>docs}}{{/module}}
47 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "func-midi-visualizer",
3 | "version": "3.1.2",
4 | "description": "A functional-based visualizer for midi data, syncrhonized with audio file",
5 | "engines": {
6 | "node": ">=6.0.0"
7 | },
8 | "main": "index.js",
9 | "directories": {
10 | "test": "test"
11 | },
12 | "scripts": {
13 | "preinstall": "if test -d ./.git && test ! -h ./.git/hooks/commit-msg; then ln -s ./scripts/commit-msg ./.git/hooks/commit-msg; fi;",
14 | "clean": "rm -Rf ./coverage",
15 | "lint": "./node_modules/eslint/bin/eslint.js src",
16 | "test": "./node_modules/mocha/bin/mocha test",
17 | "ci-coverage": "./node_modules/istanbul/lib/cli.js cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage",
18 | "local-coverage": "npm run clean && ./node_modules/istanbul/lib/cli.js cover _mocha test/**.spec.js",
19 | "doc": "./node_modules/jsdoc-to-markdown/bin/cli.js --template jsdoc.template.hbs 'src/**/*.js'> README.md",
20 | "check-deps": "./node_modules/npm-check-updates/bin/ncu -e 2",
21 | "update-deps": "./node_modules/npm-check-updates/bin/ncu -u -a"
22 | },
23 | "repository": {
24 | "type": "git",
25 | "url": "https://github.com/edhille/midi-visualizer.git"
26 | },
27 | "keywords": [
28 | "JavaScript",
29 | "Midi",
30 | "Visualization",
31 | "Functional"
32 | ],
33 | "author": "Ted Hille",
34 | "license": "MIT",
35 | "bugs": {
36 | "url": "https://github.com/edhille/midi-visualizer/issues"
37 | },
38 | "homepage": "https://github.com/edhille/midi-visualizer",
39 | "devDependencies": {
40 | "chai": "^4.2.0",
41 | "coveralls": "^3.0.3",
42 | "del": "^4.0.0",
43 | "eslint": "^5.16.0",
44 | "istanbul": "^0.4.5",
45 | "jsdoc": "^3.5.5",
46 | "jsdoc-to-markdown": "^4.0.1",
47 | "mocha": "^6.1.3",
48 | "npm-check-updates": "^3.1.3",
49 | "rewire": "^4.0.1",
50 | "sinon": "^7.3.1",
51 | "through2": "^3.0.1"
52 | },
53 | "dependencies": {
54 | "d3": "^5.9.2",
55 | "fadt": "^2.2.4",
56 | "func-midi-parser": "^2.1.3",
57 | "funtils": "^0.5.0",
58 | "graceful-readlink": "^1.0.1",
59 | "lodash": "^4.17.11",
60 | "three": "^0.103.0"
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/scripts/commit-msg:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | if npm run doc >/dev/null 2>&1
4 | then
5 | if git status --porcelain | grep ' M README.md' >/dev/null 2>&1
6 | then
7 | echo 'You have unstaged README updates that need to be committed'
8 | git status -s
9 | exit 1
10 | elif npm run check-deps >/dev/null 2>&1
11 | then
12 | echo 'You have outdated dependencies'
13 | exit 1
14 | fi
15 | else
16 | exit 1
17 | fi
18 |
19 | exit 0
20 |
--------------------------------------------------------------------------------
/src/audio-player.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const utils = require('funtils');
4 | const existy = utils.existy;
5 |
6 | const SEC_TO_MS = 1000;
7 |
8 | function calcPlayhead(currTimeSec, lastStartTime, lastPlayheadOffsetSec, startOffsetSec, durationSec) {
9 | const calculatedTimeSec = startOffsetSec + lastPlayheadOffsetSec + (currTimeSec - lastStartTime);
10 | return calculatedTimeSec % durationSec;
11 | }
12 |
13 | /**
14 | * @class AudioPlayer
15 | * @description manages audio playback
16 | * @param {object} params - settings for audio player
17 | * @param {Window} params.window - Window used to retrieve AudioContext
18 | * @return AudioPlayer
19 | */
20 | function AudioPlayer(params) {
21 | params = params || {};
22 |
23 | const ContextClass = AudioPlayer.getAudioContextFromWindow(params.window);
24 |
25 | if (ContextClass) {
26 | this.context = new ContextClass();
27 | } else {
28 | throw new TypeError('AudioContext not supported');
29 | }
30 |
31 | this.isLoading = false;
32 | this.isLoaded = false;
33 | this.isPlaying = false;
34 |
35 | this.buffer = null;
36 |
37 | this.lastPlayheadOffsetSec = 0;
38 | this.lastStartTimeSec = 0;
39 | this.startOffsetSec = 0;
40 | this.lengthMs = 0;
41 | }
42 |
43 | Object.defineProperties(AudioPlayer, {
44 | context: {
45 | value: null,
46 | writable: false,
47 | configurable: false,
48 | enumerable: false
49 | },
50 | audioSource: {
51 | value: null,
52 | writable: true,
53 | configurable: false,
54 | enumerable: true
55 | },
56 | isLoading: {
57 | value: false,
58 | writeable: false,
59 | configurable: false,
60 | enumerable: true
61 | },
62 | isLoaded: {
63 | value: false,
64 | writeable: false,
65 | configurable: false,
66 | enumerable: true
67 | },
68 | isPlaying: {
69 | value: false,
70 | writeable: false,
71 | configurable: false,
72 | enumerable: true
73 | },
74 | buffer: {
75 | value: null,
76 | writeable: false,
77 | configurable: false,
78 | enumerable: false
79 | },
80 | lastStartTimeSec: {
81 | value: 0,
82 | writeable: false,
83 | configurable: false,
84 | enumerable: false
85 | },
86 | startOffsetSec: {
87 | value: 0,
88 | writeable: false,
89 | configurable: false,
90 | enumerable: false
91 | },
92 | lastPlayheadOffsetSec: {
93 | value: 0,
94 | writeable: false,
95 | configurable: false,
96 | enumerable: false
97 | },
98 | lengthMs: {
99 | value: 0,
100 | writeable: false,
101 | configurable: false,
102 | enumerable: true
103 | }
104 | });
105 |
106 | /**
107 | * @method AudioPlayer#loadData
108 | * @description loads given audio data and invokes callback when done
109 | * @param {ArrayBuffer} audioData - ArrayBuffer of data for audio to play
110 | * @param {AudioPlayer~loadDataCallback} callback - callback to invoke when audioData is finished loading
111 | *
112 | * @callback AudioPlayer~loadDataCallback
113 | * @param {string} [err=null] - string of error message (null if no error)
114 | * @param {AudioPlayer} [self] - ref to AudioPlayer instance if loading successful (undefined otherwise)
115 | */
116 | AudioPlayer.prototype.loadData = function loadData(audioData, callback) { /* jshint expr: true */
117 | const self = this;
118 |
119 | if (!existy(audioData)) throw new Error('must provide an AudioData source');
120 |
121 | if (!existy(callback) && typeof callback !== 'function') throw new Error('callback required');
122 |
123 | if (self.isLoading) {
124 | callback('Already loading audio data');
125 |
126 | return;
127 | }
128 |
129 | try {
130 | self.isLoading = true;
131 | self.context.decodeAudioData(audioData, setupAudioSource);
132 | } catch (e) {
133 | callback('error decoding audio: ' + e.message);
134 | }
135 |
136 | function setupAudioSource(buffer) {
137 | self.buffer = buffer;
138 | self.isLoading = false;
139 | self.isLoaded = true;
140 | self.lengthMs = buffer.duration * SEC_TO_MS;
141 |
142 | callback(null, self);
143 | }
144 | };
145 |
146 | /**
147 | * @method AudioPlayer#getPlayheadTime
148 | * @description gets the playhead time in milliseconds
149 | * @return playheadTimeMs
150 | */
151 | AudioPlayer.prototype.getPlayheadTime = function getPlayheadTime() {
152 | if (!this.isLoaded || this.isLoading) return 0;
153 | if (!this.isPlaying) return this.lastPlayheadOffsetSec * SEC_TO_MS;
154 |
155 | return calcPlayhead(this.context.currentTime, this.lastStartTimeSec, this.lastPlayheadOffsetSec, this.startOffsetSec, this.buffer.duration) * SEC_TO_MS;
156 | };
157 |
158 | /**
159 | * @method AudioPlayer#play
160 | * @description initiates playing of audio
161 | * @param {number} [startTimeOffset=0] - offset in seconds to wait before playing
162 | * @param {number} [playheadSec=0] - where to start playback within audio in seconds
163 | */
164 | AudioPlayer.prototype.play = function play(startTimeOffsetSec, playheadSec) {
165 | if (!this.isLoaded) return false; // nothing to play...
166 | if (this.isPlaying) return true; // already playing
167 |
168 | // remove our handler to pause playback at the end of audio if we are restarting
169 | // play because the old audio source will end and call this...
170 | if (this.audioSource) this.audioSource.onended = null;
171 |
172 | this.audioSource = this.context.createBufferSource();
173 | this.audioSource.buffer = this.buffer;
174 | this.audioSource.connect(this.context.destination);
175 |
176 | this.startOffsetSec = startTimeOffsetSec || 0;
177 | if (typeof playheadSec !== 'undefined') this.lastPlayheadOffsetSec = playheadSec;
178 | this.lastStartTimeSec = this.context.currentTime;
179 |
180 | const newPlayheadSec = calcPlayhead(this.context.currentTime, this.lastStartTimeSec, this.lastPlayheadOffsetSec, this.startOffsetSec, this.buffer.duration);
181 |
182 | this.audioSource.start(this.startOffsetSec, newPlayheadSec);
183 |
184 | this.isPlaying = true;
185 |
186 | this.audioSource.onended = function () {
187 | this.pause();
188 | }.bind(this);
189 |
190 | return this.isPlaying;
191 | };
192 |
193 | /**
194 | * @method AudioPlayer#pause
195 | * @description pauses playing of audio
196 | * @param {number} stopAfter - number of seconds to wait before stopping
197 | */
198 | AudioPlayer.prototype.pause = function pause( /* AudioBufferSourceNode.stop params */ ) {
199 | if (!this.isLoaded) return false; // nothing to play...
200 | if (!this.isPlaying) return true; // already paused
201 |
202 | this.isPlaying = false;
203 | this.lastPlayheadOffsetSec = calcPlayhead(this.context.currentTime, this.lastStartTimeSec, this.lastPlayheadOffsetSec, this.startOffsetSec, this.buffer.duration);
204 |
205 | return this.audioSource.stop.apply(this.audioSource, arguments);
206 | };
207 |
208 | /**
209 | * @method
210 | * @static
211 | * @description cross-browser fetch of AudioContext from given window
212 | * @param {Window} window - Window to fetch AudioContext from
213 | * @return AudioContext
214 | */
215 | AudioPlayer.getAudioContextFromWindow = function getAudioContextFromWindow(window) {
216 | return window.AudioContext ||
217 | window.webkitAudioContext ||
218 | window.mozAudioContext ||
219 | window.oAudioContext ||
220 | window.msAudioContext;
221 | };
222 |
223 | module.exports = AudioPlayer;
224 |
225 |
--------------------------------------------------------------------------------
/src/data-types.js:
--------------------------------------------------------------------------------
1 | /** @module DataTypes */
2 | 'use strict';
3 |
4 | const createDataType = require('fadt');
5 |
6 | const DEFAULT_STROKE = 1;
7 | const DEFAULT_STROKE_LINE_CAP = 'round';
8 |
9 | /**
10 | * @class MidiVisualizerState
11 | * @description top-level data type representing state of MidiVisualizer
12 | * @param {object} params - properties to set
13 | * @param {AudioPlayer} params.audioPlayer - AudioPlayer instance managing audio to sync with
14 | * @param {Renderer} params.renderer - Renderer used to draw visualization
15 | * @param {object} [params.animEventsByTimeMs={}] - AnimEvent to render, grouped by millisecond-based mark where they should be rendered
16 | * @param {boolean} [params.isPlaying=false] - flag indicating whether currently playing
17 | * @returns MidiVisualizerState
18 | */
19 | const MidiVisualizerState = createDataType(function (params) {
20 | if (!params.audioPlayer) throw new TypeError('audioPlayer is required');
21 | if (!params.renderer) throw new TypeError('renderer is required');
22 |
23 | this.audioPlayer = params.audioPlayer;
24 | this.renderer = params.renderer;
25 |
26 | this.animEventsByTimeMs = params.animEventsByTimeMs || {};
27 | this.isPlaying = params.isPlaying || false;
28 | });
29 |
30 | /**
31 | * @class RendererState
32 | * @description top-level data type representing state of Renderer
33 | * @param {object} params - properties to set
34 | * @param {string} params.id - unique identifier for renderer
35 | * @param {HTMLElement} params.root - HTMLElement to use as root node for renderer canvas placement
36 | * @param {Window} params.window - Window we are rendering in (note, Window must have a 'document')
37 | * @param {number} [params.width=0] - width for rendering canvas
38 | * @param {number} [params.height=0] - height for rendering canvas
39 | * @param {RenderEvents[]} [param.renderEvents=[]] - RenderEvents to render
40 | * @param {object[]} [params.scales=[]] - Scales for normalizing position/sizing
41 | * @param {boolean} [params.isPlaying=false] - flag indicating whether currently playing
42 | * @returns RendererState
43 | */
44 | const RendererState = createDataType(function (params) {
45 | if (!params.id) throw new TypeError('id required');
46 | if (!params.root) throw new TypeError('root required');
47 | if (!params.window) throw new TypeError('window required');
48 | if (!params.window.document) throw new TypeError('window must have document property');
49 | if (!params.animEventsByTimeMs) throw new TypeError('animEventsByTimeMs required');
50 |
51 | this.id = params.id;
52 | this.root = params.root;
53 | this.window = params.window;
54 | this.document = params.window.document;
55 |
56 | this.width = params.width || 0;
57 | this.height = params.height || 0;
58 | this.renderEvents = params.renderEvents || [];
59 | this.scales = params.scales || [];
60 | this.isPlaying = params.isPlaying || false;
61 |
62 | this.animEventsByTimeMs = params.animEventsByTimeMs || {};
63 | });
64 |
65 | /**
66 | * @class D3RendererState
67 | * @augments RendererState
68 | * @description data type representing state of Renderer that uses D3
69 | * @param {SVGElement} params.svg - SVGElement for renderering
70 | * @returns D3RendererState
71 | */
72 | const D3RendererState = createDataType(function (params) {
73 | if(!params.svg) throw new TypeError('svg is required');
74 | if(!params.d3) throw new TypeError('d3 is required');
75 |
76 | this.svg = params.svg;
77 | this.d3 = params.d3;
78 | }, RendererState);
79 |
80 | /**
81 | * @class ThreeJsRendererState
82 | * @augments RendererState
83 | * @description data type representing state of Renderer that uses D3
84 | * @param {THREEJS} params.THREE - ThreeJs object
85 | * @param {Camera} params.camera - ThreeJs Camera to use
86 | * @param {Scene} params.scene - ThreeJs Scene to use
87 | * @param {Renderer} params.renderer - Renderer monad to use
88 | * @returns ThreeJsRendererState
89 | */
90 | const ThreeJsRendererState = createDataType(function (params) {
91 | if (!params.THREE) throw new TypeError('THREE is required');
92 | if (!params.camera) throw new TypeError('camera is required');
93 | if (!params.scene) throw new TypeError('scene is required');
94 | if (!params.renderer) throw new TypeError('renderer is required');
95 |
96 | this.THREE = params.THREE;
97 | this.camera = params.camera;
98 | this.scene = params.scene;
99 | this.renderer = params.renderer;
100 | }, RendererState);
101 |
102 | /**
103 | * @class AnimEvent
104 | * @description data type representing individual animation event
105 | * @param {MidiEvent} params.event - MidiEvent being renderered
106 | * @param {number} [params.track=0] - index of midi track event belongs to
107 | * @param {number} [params.startTimeMicroSec=0] - offset in microseconds from beginning of song when event starts
108 | * @param {number} [params.lengthMicroSec=0] - length of event in microseconds
109 | * @param {number} [params.microSecPerBeat=500000] - number of microseconds per beat
110 | * @param {string} [id=