abcjs 6.0.0-beta.9 → 6.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (197) hide show
  1. package/.github/workflows/tests.yml +29 -0
  2. package/CODE_OF_CONDUCT.md +76 -0
  3. package/CONTRIBUTING.md +1 -0
  4. package/LICENSE.md +1 -1
  5. package/README.md +88 -7
  6. package/RELEASE.md +961 -1
  7. package/abcjs-audio.css +14 -5
  8. package/dist/.gitignore +1 -2
  9. package/dist/abcjs-basic-min.js +3 -0
  10. package/dist/abcjs-basic-min.js.LICENSE +23 -0
  11. package/dist/abcjs-basic.js +28232 -0
  12. package/dist/abcjs-basic.js.map +1 -0
  13. package/dist/abcjs-plugin-min.js +3 -0
  14. package/dist/abcjs-plugin-min.js.LICENSE +23 -0
  15. package/index.js +27 -2
  16. package/{static-wrappers/license.js → license.js} +1 -1
  17. package/package.json +26 -29
  18. package/{src/plugin/abc_plugin.js → plugin.js} +31 -19
  19. package/src/api/abc_animation.js +1 -17
  20. package/src/api/abc_tablatures.js +144 -0
  21. package/src/api/abc_timing_callbacks.js +216 -117
  22. package/src/api/abc_tunebook.js +18 -67
  23. package/src/api/abc_tunebook_svg.js +40 -48
  24. package/src/data/abc_tune.js +232 -972
  25. package/src/data/deline-tune.js +199 -0
  26. package/src/edit/abc_editarea.js +112 -0
  27. package/src/edit/abc_editor.js +95 -221
  28. package/src/midi/abc_midi_create.js +48 -50
  29. package/src/parse/abc_common.js +0 -14
  30. package/src/parse/abc_parse.js +167 -1321
  31. package/src/parse/abc_parse_book.js +62 -0
  32. package/src/parse/abc_parse_directive.js +164 -41
  33. package/src/parse/abc_parse_header.js +116 -145
  34. package/src/parse/abc_parse_key_voice.js +26 -20
  35. package/src/parse/abc_parse_music.js +1337 -0
  36. package/src/parse/abc_tokenizer.js +21 -15
  37. package/src/parse/abc_transpose.js +3 -15
  38. package/src/parse/tune-builder.js +896 -0
  39. package/src/parse/wrap_lines.js +205 -453
  40. package/src/synth/abc_midi_flattener.js +1292 -0
  41. package/src/{midi → synth}/abc_midi_renderer.js +44 -17
  42. package/src/synth/abc_midi_sequencer.js +648 -0
  43. package/src/synth/active-audio-context.js +3 -14
  44. package/src/synth/cents-to-factor.js +10 -0
  45. package/src/synth/create-note-map.js +21 -32
  46. package/src/synth/create-synth-control.js +20 -103
  47. package/src/synth/create-synth.js +185 -77
  48. package/src/synth/download-buffer.js +7 -21
  49. package/src/synth/get-midi-file.js +13 -20
  50. package/src/synth/images/{loading.svg → loading.svg.js} +4 -0
  51. package/src/synth/images/loop.svg.js +65 -0
  52. package/src/synth/images/pause.svg.js +10 -0
  53. package/src/synth/images/play.svg.js +9 -0
  54. package/src/synth/images/{reset.svg → reset.svg.js} +5 -1
  55. package/src/synth/instrument-index-to-name.js +1 -16
  56. package/src/synth/load-note.js +37 -76
  57. package/src/synth/pitch-to-note-name.js +0 -15
  58. package/src/synth/pitches-to-perc.js +64 -0
  59. package/src/synth/place-note.js +78 -68
  60. package/src/synth/play-event.js +17 -18
  61. package/src/synth/register-audio-context.js +11 -23
  62. package/src/synth/sounds-cache.js +0 -15
  63. package/src/synth/supports-audio.js +9 -23
  64. package/src/synth/synth-controller.js +80 -49
  65. package/src/synth/synth-sequence.js +20 -34
  66. package/src/tablatures/instruments/guitar/guitar-fonts.js +19 -0
  67. package/src/tablatures/instruments/guitar/guitar-patterns.js +23 -0
  68. package/src/tablatures/instruments/guitar/tab-guitar.js +50 -0
  69. package/src/tablatures/instruments/string-patterns.js +277 -0
  70. package/src/tablatures/instruments/string-tablature.js +56 -0
  71. package/src/tablatures/instruments/tab-note.js +282 -0
  72. package/src/tablatures/instruments/tab-notes.js +41 -0
  73. package/src/tablatures/instruments/violin/tab-violin.js +47 -0
  74. package/src/tablatures/instruments/violin/violin-fonts.js +19 -0
  75. package/src/tablatures/instruments/violin/violin-patterns.js +23 -0
  76. package/src/tablatures/tab-absolute-elements.js +310 -0
  77. package/src/tablatures/tab-common.js +29 -0
  78. package/src/tablatures/tab-renderer.js +243 -0
  79. package/src/tablatures/transposer.js +110 -0
  80. package/src/test/abc_midi_lint.js +5 -22
  81. package/src/test/abc_midi_sequencer_lint.js +11 -14
  82. package/src/test/abc_parser_lint.js +136 -32
  83. package/src/test/abc_vertical_lint.js +94 -32
  84. package/src/test/rendering-lint.js +38 -5
  85. package/src/write/abc_absolute_element.js +112 -120
  86. package/src/write/abc_abstract_engraver.js +102 -253
  87. package/src/write/abc_beam_element.js +30 -290
  88. package/src/write/abc_brace_element.js +12 -121
  89. package/src/write/abc_create_clef.js +21 -32
  90. package/src/write/abc_create_key_signature.js +8 -26
  91. package/src/write/abc_create_note_head.js +107 -0
  92. package/src/write/abc_create_time_signature.js +2 -21
  93. package/src/write/abc_crescendo_element.js +3 -50
  94. package/src/write/abc_decoration.js +7 -30
  95. package/src/write/abc_dynamic_decoration.js +3 -37
  96. package/src/write/abc_ending_element.js +1 -57
  97. package/src/write/abc_engraver_controller.js +111 -234
  98. package/src/write/abc_glyphs.js +8 -18
  99. package/src/write/abc_relative_element.js +57 -97
  100. package/src/write/abc_renderer.js +10 -832
  101. package/src/write/abc_spacing.js +0 -15
  102. package/src/write/abc_staff_group_element.js +14 -349
  103. package/src/write/abc_tempo_element.js +9 -117
  104. package/src/write/abc_tie_element.js +5 -68
  105. package/src/write/abc_triplet_element.js +6 -124
  106. package/src/write/abc_voice_element.js +7 -222
  107. package/src/write/add-chord.js +103 -0
  108. package/src/write/add-text-if.js +33 -0
  109. package/src/write/bottom-text.js +79 -0
  110. package/src/write/calcHeight.js +17 -0
  111. package/src/write/classes.js +100 -0
  112. package/src/write/draw/absolute.js +68 -0
  113. package/src/write/draw/beam.js +56 -0
  114. package/src/write/draw/brace.js +106 -0
  115. package/src/write/draw/crescendo.js +38 -0
  116. package/src/write/draw/debug-box.js +8 -0
  117. package/src/write/draw/draw.js +56 -0
  118. package/src/write/draw/dynamics.js +20 -0
  119. package/src/write/draw/ending.js +46 -0
  120. package/src/write/draw/group-elements.js +66 -0
  121. package/src/write/draw/horizontal-line.js +25 -0
  122. package/src/write/draw/non-music.js +50 -0
  123. package/src/write/draw/print-line.js +24 -0
  124. package/src/write/draw/print-path.js +7 -0
  125. package/src/write/draw/print-stem.js +30 -0
  126. package/src/write/draw/print-symbol.js +59 -0
  127. package/src/write/draw/print-vertical-line.js +18 -0
  128. package/src/write/draw/relative.js +77 -0
  129. package/src/write/draw/round-number.js +5 -0
  130. package/src/write/draw/selectables.js +59 -0
  131. package/src/write/draw/separator.js +16 -0
  132. package/src/write/draw/set-paper-size.js +45 -0
  133. package/src/write/{sprintf.js → draw/sprintf.js} +0 -0
  134. package/src/write/draw/staff-group.js +226 -0
  135. package/src/write/draw/staff-line.js +9 -0
  136. package/src/write/draw/staff.js +33 -0
  137. package/src/write/draw/tab-line.js +40 -0
  138. package/src/write/draw/tempo.js +45 -0
  139. package/src/write/draw/text.js +71 -0
  140. package/src/write/draw/tie.js +97 -0
  141. package/src/write/draw/triplet.js +46 -0
  142. package/src/write/draw/voice.js +102 -0
  143. package/src/write/format-jazz-chord.js +15 -0
  144. package/src/write/free-text.js +41 -0
  145. package/src/write/get-font-and-attr.js +37 -0
  146. package/src/write/get-text-size.js +56 -0
  147. package/src/write/highlight.js +11 -0
  148. package/src/write/layout/VoiceElements.js +121 -0
  149. package/src/write/layout/beam.js +213 -0
  150. package/src/write/layout/get-left-edge-of-staff.js +56 -0
  151. package/src/write/layout/getBarYAt.js +6 -0
  152. package/src/write/layout/layout.js +94 -0
  153. package/src/write/layout/setUpperAndLowerElements.js +232 -0
  154. package/src/write/layout/staffGroup.js +146 -0
  155. package/src/write/layout/triplet.js +75 -0
  156. package/src/write/layout/voice.js +137 -0
  157. package/src/write/selection.js +188 -70
  158. package/src/write/separator.js +10 -0
  159. package/src/write/set-class.js +21 -0
  160. package/src/write/subtitle.js +12 -0
  161. package/src/write/svg.js +95 -43
  162. package/src/write/top-text.js +54 -0
  163. package/src/write/unhighlight.js +11 -0
  164. package/test.js +27 -64
  165. package/types/index.d.ts +1095 -0
  166. package/version.js +1 -1
  167. package/.babelrc +0 -5
  168. package/.eslintrc +0 -3
  169. package/.gitmodules +0 -3
  170. package/Dockerfile +0 -8
  171. package/abcjs-midi.css +0 -166
  172. package/build-utils/loadPresets.js +0 -14
  173. package/build-utils/presets/webpack.analyze.js +0 -6
  174. package/build-utils/presets/webpack.optimize.js +0 -30
  175. package/build-utils/webpack.development.js +0 -14
  176. package/build-utils/webpack.production.js +0 -35
  177. package/deploy-docs.sh +0 -25
  178. package/docker-compose.yml +0 -13
  179. package/docs/README.md +0 -33
  180. package/fix-versions.sh +0 -23
  181. package/midi.js +0 -62
  182. package/src/api/abc_tunebook_midi.js +0 -116
  183. package/src/midi/abc_midi_controls.js +0 -701
  184. package/src/midi/abc_midi_flattener.js +0 -1119
  185. package/src/midi/abc_midi_js_preparer.js +0 -243
  186. package/src/midi/abc_midi_sequencer.js +0 -401
  187. package/src/midi/abc_midi_ui_generator.js +0 -86
  188. package/src/plugin/abc_plugin_midi.js +0 -220
  189. package/src/synth/images/loop.svg +0 -61
  190. package/src/synth/images/pause.svg +0 -6
  191. package/src/synth/images/play.svg +0 -5
  192. package/src/transform/abc2abc_write.js +0 -395
  193. package/static-wrappers/basic.js +0 -2
  194. package/static-wrappers/midi.js +0 -2
  195. package/static-wrappers/plugin-midi.js +0 -6
  196. package/static-wrappers/plugin.js +0 -6
  197. package/webpack.config.js +0 -29
@@ -1,18 +1,3 @@
1
- // Copyright (C) 2019-2020 Paul Rosen (paul at paulrosen dot net)
2
- //
3
- // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
4
- // documentation files (the "Software"), to deal in the Software without restriction, including without limitation
5
- // the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
6
- // to permit persons to whom the Software is furnished to do so, subject to the following conditions:
7
- //
8
- // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
9
- //
10
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
11
- // BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
12
- // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
13
- // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
14
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
15
-
16
1
  // Convert the input structure to a more useful structure where each item has a length of its own.
17
2
 
18
3
  var instrumentIndexToName = require('./instrument-index-to-name');
@@ -25,26 +10,30 @@ var createNoteMap = function(sequence) {
25
10
  // TODO-PER: handle more than one note in a track
26
11
  var nextNote = {};
27
12
  var currentInstrument = instrumentIndexToName[0];
13
+ // ev.start and ev.duration are in whole notes. Need to turn them into
28
14
  sequence.tracks.forEach(function(track, i) {
29
- var currentTime = 0;
30
15
  track.forEach(function(ev) {
31
16
  switch (ev.cmd) {
32
- case "start":
33
- nextNote[ev.pitch] = { time: currentTime, instrument: currentInstrument, volume: ev.volume };
34
- break;
35
- case "move":
36
- currentTime += ev.duration;
37
- break;
38
- case "stop":
39
- if (nextNote[ev.pitch]) { // If unisons are requested, then there might be two starts and two stops for the same note. Only use one of them.
40
- map[i].push({
17
+ case "note":
18
+ // ev contains:
19
+ // {"cmd":"note","pitch":72,"volume":95,"start":0.125,"duration":0.25,"instrument":0,"gap":0}
20
+ // where start and duration are in whole notes, gap is in 1/1920 of a second (i.e. MIDI ticks)
21
+ if (ev.duration > 0) {
22
+ var gap = ev.gap ? ev.gap : 0;
23
+ var len = ev.duration;
24
+ gap = Math.min(gap, len * 2 / 3);
25
+ var obj = {
41
26
  pitch: ev.pitch,
42
- instrument: nextNote[ev.pitch].instrument,
43
- start: nextNote[ev.pitch].time,
44
- end: currentTime,
45
- volume: nextNote[ev.pitch].volume
46
- });
47
- delete nextNote[ev.pitch];
27
+ instrument: currentInstrument,
28
+ start: Math.round((ev.start) * 1000000)/1000000,
29
+ end: Math.round((ev.start + len - gap) * 1000000)/1000000,
30
+ volume: ev.volume
31
+ };
32
+ if (ev.style)
33
+ obj.style = ev.style;
34
+ if (ev.cents)
35
+ obj.cents = ev.cents;
36
+ map[i].push(obj);
48
37
  }
49
38
  break;
50
39
  case "program":
@@ -55,7 +44,7 @@ var createNoteMap = function(sequence) {
55
44
  break;
56
45
  default:
57
46
  // TODO-PER: handle other event types
58
- console.log("Unhanded midi event", ev);
47
+ console.log("Unhandled midi event", ev);
59
48
  }
60
49
  });
61
50
  });
@@ -1,109 +1,13 @@
1
- // Copyright (C) 2019-2020 Paul Rosen (paul at paulrosen dot net)
2
- //
3
- // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
4
- // documentation files (the "Software"), to deal in the Software without restriction, including without limitation
5
- // the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
6
- // to permit persons to whom the Software is furnished to do so, subject to the following conditions:
7
- //
8
- // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
9
- //
10
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
11
- // BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
12
- // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
13
- // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
14
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
15
-
16
1
  var supportsAudio = require('./supports-audio');
17
2
  var registerAudioContext = require('./register-audio-context');
18
3
  var activeAudioContext = require('./active-audio-context');
19
4
  var parseCommon = require('../parse/abc_common');
20
- // TODO-PER: The require statements for svg don't play well for node apps without extra plugins. The following lines would be clearer than inlining the SVG
21
- // var loopImage = require('./images/loop.svg');
22
- // var playImage = require('./images/play.svg');
23
- // var pauseImage = require('./images/pause.svg');
24
- // var loadingImage = require('./images/loading.svg');
25
- // var resetImage = require('./images/reset.svg');
26
- var loopImage = '<svg version="1.0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 700 700" preserveAspectRatio="xMidYMid meet">\n' +
27
- '<g transform="translate(0,700) scale(0.1,-0.1)" >\n' +
28
- '<path d="M3111 6981 c-20 -37 -90 -55 -364 -96 -120 -18 -190 -33 -244 -55 ' +
29
- '-42 -17 -124 -42 -182 -56 -78 -18 -119 -34 -157 -60 -28 -19 -86 -46 -128 ' +
30
- '-60 -43 -13 -107 -42 -144 -64 -37 -23 -84 -46 -106 -52 -21 -7 -56 -29 -79 ' +
31
- '-50 -22 -22 -61 -50 -86 -63 -26 -13 -67 -40 -91 -60 -24 -20 -65 -47 -90 -60 ' +
32
- '-25 -13 -53 -31 -61 -41 -8 -9 -32 -30 -54 -46 -75 -54 -486 -460 -512 -507 ' +
33
- '-15 -25 -48 -69 -75 -98 -26 -28 -48 -57 -48 -63 0 -6 -18 -29 -39 -53 -21 ' +
34
- '-23 -56 -71 -77 -107 -20 -36 -50 -80 -65 -97 -16 -18 -33 -52 -40 -75 -12 ' +
35
- '-47 -47 -115 -84 -166 -13 -18 -30 -56 -38 -83 -8 -27 -34 -80 -56 -118 -33 ' +
36
- '-53 -46 -91 -62 -167 -12 -63 -34 -127 -59 -179 -42 -84 -60 -166 -60 -270 0 ' +
37
- '-90 26 -122 125 -154 54 -17 96 -19 430 -20 305 -1 381 2 430 14 82 22 140 51 ' +
38
- '153 78 6 12 22 47 37 77 14 30 38 77 54 103 15 27 34 73 40 103 7 30 28 78 48 ' +
39
- '107 19 28 44 74 55 101 10 28 34 67 53 87 18 20 49 61 68 90 19 30 44 63 57 ' +
40
- '74 13 11 36 40 52 65 59 94 232 270 306 313 20 11 57 37 82 58 25 20 70 52 ' +
41
- '100 72 30 19 66 47 79 61 13 14 49 35 80 46 30 12 80 37 111 56 31 19 95 45 ' +
42
- '143 58 48 12 110 37 139 55 63 40 127 55 323 76 83 9 208 28 279 41 156 29 ' +
43
- '165 29 330 4 453 -71 514 -84 606 -130 31 -16 83 -36 116 -45 32 -9 84 -34 ' +
44
- '115 -56 31 -21 82 -48 113 -60 32 -11 72 -33 89 -48 18 -16 59 -45 92 -65 33 ' +
45
- '-21 74 -51 90 -66 17 -15 49 -40 73 -54 52 -32 65 -61 50 -113 -8 -31 -61 -90 ' +
46
- '-277 -308 -300 -303 -361 -382 -369 -481 -2 -29 0 -66 6 -81 13 -40 88 -138 ' +
47
- '115 -151 12 -6 54 -26 92 -44 l70 -33 945 -2 c520 -1 975 2 1012 7 64 8 191 ' +
48
- '50 231 76 11 7 33 34 50 60 22 34 42 51 65 58 l32 9 0 1101 0 1102 -32 9 c-21 ' +
49
- '7 -44 26 -64 55 -60 84 -77 97 -140 110 -44 9 -76 10 -127 2 -59 -9 -77 -17 ' +
50
- '-134 -62 -37 -28 -172 -155 -301 -281 -129 -127 -249 -237 -267 -245 -25 -10 ' +
51
- '-41 -11 -71 -2 -58 15 -112 45 -124 69 -6 11 -35 35 -64 54 -28 18 -58 41 -66 ' +
52
- '50 -8 9 -41 35 -75 58 -33 22 -77 56 -99 75 -21 18 -64 46 -95 61 -31 14 -73 ' +
53
- '39 -93 55 -20 15 -70 40 -110 55 -40 15 -97 44 -127 64 -29 21 -78 44 -107 53 ' +
54
- '-30 8 -77 31 -105 51 -42 28 -73 39 -173 60 -68 14 -154 39 -196 58 -95 43 ' +
55
- '-131 51 -343 76 -209 24 -242 32 -279 70 l-30 29 -328 0 c-312 0 -330 -1 -339 ' +
56
- '-19z"></path>\n' +
57
- '<path d="M254 2875 c-89 -16 -107 -26 -145 -78 -32 -44 -62 -66 -91 -67 -17 0 ' +
58
- '-18 -61 -18 -1140 l0 -1140 24 0 c16 0 41 -17 72 -50 40 -42 61 -55 117 -72 ' +
59
- 'l69 -21 82 23 c44 12 96 30 114 39 18 9 148 132 290 272 141 141 267 261 279 ' +
60
- '268 51 26 86 14 176 -61 32 -26 62 -48 66 -48 5 0 36 -25 70 -55 34 -30 74 ' +
61
- '-61 89 -69 15 -8 37 -28 50 -45 12 -17 50 -45 84 -62 34 -17 78 -44 98 -60 19 ' +
62
- '-16 61 -37 93 -48 32 -11 81 -37 107 -56 27 -20 76 -45 109 -56 33 -12 75 -31 ' +
63
- '93 -44 62 -45 93 -58 191 -82 54 -12 130 -37 168 -54 68 -29 180 -58 226 -59 ' +
64
- '62 0 183 -64 183 -96 0 -12 88 -14 639 -14 l639 0 12 30 c18 44 76 66 233 89 ' +
65
- '89 14 160 30 200 47 34 15 106 42 159 60 54 18 112 44 130 57 47 35 85 52 146 ' +
66
- '67 29 7 76 28 105 48 29 20 77 48 107 63 30 15 66 39 80 54 14 15 50 40 81 56 ' +
67
- '31 15 78 46 104 69 26 22 61 46 79 54 17 7 43 26 56 42 14 16 41 41 60 56 64 ' +
68
- '48 380 362 408 405 15 23 40 51 55 63 15 12 36 38 46 58 11 21 37 57 58 82 22 ' +
69
- '25 49 62 62 83 13 20 38 56 57 78 19 23 50 74 69 113 19 39 46 86 59 104 14 ' +
70
- '18 34 62 46 98 12 36 32 77 45 92 31 38 60 97 80 167 9 33 26 76 37 95 29 50 ' +
71
- '47 103 68 206 10 52 32 117 51 155 29 56 33 74 34 140 0 94 -10 108 -101 138 ' +
72
- '-61 20 -83 21 -463 21 -226 0 -421 -4 -451 -10 -63 -12 -86 -30 -110 -85 -10 ' +
73
- '-22 -33 -63 -52 -92 -21 -31 -42 -80 -53 -123 -11 -44 -32 -93 -56 -128 -20 ' +
74
- '-32 -47 -83 -59 -115 -12 -32 -37 -77 -56 -100 -19 -23 -50 -65 -69 -94 -19 ' +
75
- '-29 -44 -57 -54 -63 -11 -5 -29 -27 -42 -47 -52 -85 -234 -277 -300 -315 -25 ' +
76
- '-15 -53 -38 -62 -51 -9 -14 -42 -39 -74 -57 -32 -18 -75 -48 -95 -66 -21 -18 ' +
77
- '-59 -44 -85 -58 -26 -13 -72 -40 -100 -59 -35 -24 -78 -41 -128 -52 -47 -11 ' +
78
- '-99 -31 -139 -56 -69 -42 -94 -49 -391 -110 -245 -51 -425 -66 -595 -50 -168 ' +
79
- '16 -230 27 -330 61 -47 16 -123 35 -170 44 -98 17 -123 25 -172 58 -20 14 -71 ' +
80
- '37 -114 53 -44 15 -95 40 -115 56 -20 16 -70 42 -110 59 -40 16 -88 45 -108 ' +
81
- '63 -20 19 -55 46 -78 61 -24 14 -49 35 -55 47 -7 11 -34 33 -60 49 -50 31 -65 ' +
82
- '61 -53 102 4 13 130 147 281 298 236 238 277 283 299 335 15 32 35 71 46 86 ' +
83
- '12 18 19 44 19 76 0 42 -8 63 -53 138 -92 151 11 139 -1207 141 -798 2 -1030 ' +
84
- '0 -1086 -11z"></path>\n' +
85
- '</g>\n' +
86
- '</svg>\n';
87
- var playImage = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 25 25" class="abcjs-play-svg">\n' +
88
- ' <g>\n' +
89
- ' <polygon points="4 0 23 12.5 4 25"/>\n' +
90
- ' </g>\n' +
91
- '</svg>';
92
- var pauseImage = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 25 25" class="abcjs-pause-svg">\n' +
93
- ' <g>\n' +
94
- ' <rect width="8.23" height="25"/>\n' +
95
- ' <rect width="8.23" height="25" x="17"/>\n' +
96
- ' </g>\n' +
97
- '</svg>';
98
- var loadingImage = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" class="abcjs-loading-svg">\n' +
99
- ' <circle cx="50" cy="50" fill="none" stroke-width="20" r="35" stroke-dasharray="160 55"></circle>\n' +
100
- '</svg>';
101
- var resetImage = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 25 25">\n' +
102
- ' <g>\n' +
103
- ' <polygon points="5 12.5 24 0 24 25"/>\n' +
104
- ' <rect width="3" height="25" x="0" y="0"/>\n' +
105
- ' </g>\n' +
106
- '</svg>';
5
+
6
+ var loopImage = require('./images/loop.svg.js');
7
+ var playImage = require('./images/play.svg.js');
8
+ var pauseImage = require('./images/pause.svg.js');
9
+ var loadingImage = require('./images/loading.svg.js');
10
+ var resetImage = require('./images/reset.svg.js');
107
11
 
108
12
  function CreateSynthControl(parent, options) {
109
13
  var self = this;
@@ -135,10 +39,22 @@ function CreateSynthControl(parent, options) {
135
39
  buildDom(self.parent, self.options);
136
40
  attachListeners(self);
137
41
 
42
+ self.disable = function(isDisabled) {
43
+ var el = self.parent.querySelector(".abcjs-inline-audio");
44
+ if (isDisabled)
45
+ el.classList.add("abcjs-disabled");
46
+ else
47
+ el.classList.remove("abcjs-disabled");
48
+ };
49
+ self.setWarp = function(tempo, warp) {
50
+ var el = self.parent.querySelector(".abcjs-midi-tempo");
51
+ el.value = Math.round(warp);
52
+ self.setTempo(tempo)
53
+ };
138
54
  self.setTempo = function(tempo) {
139
55
  var el = self.parent.querySelector(".abcjs-midi-current-tempo");
140
56
  if (el)
141
- el.innerHTML = tempo;
57
+ el.innerHTML = Math.round(tempo);
142
58
  };
143
59
  self.resetAll = function() {
144
60
  var pushedButtons = self.parent.querySelectorAll(".abcjs-pushed");
@@ -235,6 +151,7 @@ function buildDom(parent, options) {
235
151
  var bpm = options.bpm ? options.bpm : "BPM";
236
152
  html += '<span class="abcjs-tempo-wrapper"><label><input class="abcjs-midi-tempo" type="number" min="1" max="300" value="100" title="' + warpTitle + '" aria-label="' + warpAria + '">%</label><span>&nbsp;(<span class="abcjs-midi-current-tempo"></span> ' + bpm + ')</span></span>\n';
237
153
  }
154
+ html += '<div class="abcjs-css-warning" style="font-size: 12px;color:red;border: 1px solid red;text-align: center;width: 300px;margin-top: 4px;font-weight: bold;border-radius: 4px;">CSS required: load abcjs-audio.css</div>';
238
155
  html += '</div>\n';
239
156
  parent.innerHTML = html;
240
157
  }
@@ -1,18 +1,3 @@
1
- // Copyright (C) 2019-2020 Paul Rosen (paul at paulrosen dot net)
2
- //
3
- // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
4
- // documentation files (the "Software"), to deal in the Software without restriction, including without limitation
5
- // the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
6
- // to permit persons to whom the Software is furnished to do so, subject to the following conditions:
7
- //
8
- // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
9
- //
10
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
11
- // BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
12
- // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
13
- // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
14
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
15
-
16
1
  var getNote = require('./load-note');
17
2
  var createNoteMap = require('./create-note-map');
18
3
  var registerAudioContext = require('./register-audio-context');
@@ -21,22 +6,23 @@ var supportsAudio = require('./supports-audio');
21
6
  var pitchToNoteName = require('./pitch-to-note-name');
22
7
  var instrumentIndexToName = require('./instrument-index-to-name');
23
8
  var downloadBuffer = require('./download-buffer');
24
- var sequence = require('../midi/abc_midi_sequencer');
25
- var flatten = require('../midi/abc_midi_flattener');
26
9
  var placeNote = require('./place-note');
10
+ var soundsCache = require('./sounds-cache');
27
11
 
28
12
  // TODO-PER: remove the midi tests from here: I don't think the object can be constructed unless it passes.
29
13
  var notSupportedMessage = "MIDI is not supported in this browser.";
30
14
 
15
+ var originalSoundFontUrl = "https://paulrosen.github.io/midi-js-soundfonts/abcjs/";
16
+ // These are the original soundfonts supplied. They will need a volume boost:
31
17
  var defaultSoundFontUrl = "https://paulrosen.github.io/midi-js-soundfonts/FluidR3_GM/";
32
-
18
+ var alternateSoundFontUrl = "https://paulrosen.github.io/midi-js-soundfonts/MusyngKite/";
33
19
 
34
20
  function CreateSynth() {
35
21
  var self = this;
36
22
  self.audioBufferPossible = undefined;
37
23
  self.directSource = []; // type: AudioBufferSourceNode
38
- self.startTimeSec = undefined; // the time that the midi started: used for pause/resume.
39
- self.pausedTimeSec = undefined; // the time that the midi was paused: used for resume.
24
+ self.startTimeSec = undefined; // the time (in seconds) that the audio started: used for pause to get the pausedTimeSec.
25
+ self.pausedTimeSec = undefined; // the position (in seconds) that the audio was paused: used for resume.
40
26
  self.audioBuffers = []; // cache of the buffers so starting play can be fast.
41
27
  self.duration = undefined; // the duration of the tune in seconds.
42
28
  self.isRunning = false; // whether there is currently a sound buffer running.
@@ -53,38 +39,117 @@ function CreateSynth() {
53
39
  self.audioBufferPossible = self._deviceCapable();
54
40
  if (!self.audioBufferPossible)
55
41
  return Promise.reject({ status: "NotSupported", message: notSupportedMessage});
56
- self.soundFontUrl = options.soundFontUrl ? options.soundFontUrl : defaultSoundFontUrl;
57
- self.millisecondsPerMeasure = options.millisecondsPerMeasure ? options.millisecondsPerMeasure : (options.visualObj ? options.visualObj.millisecondsPerMeasure() : 1000);
58
42
  var params = options.options ? options.options : {};
43
+ self.soundFontUrl = params.soundFontUrl ? params.soundFontUrl : defaultSoundFontUrl;
44
+ if (self.soundFontUrl[self.soundFontUrl.length-1] !== '/')
45
+ self.soundFontUrl += '/';
46
+ if (params.soundFontVolumeMultiplier || params.soundFontVolumeMultiplier === 0)
47
+ self.soundFontVolumeMultiplier = params.soundFontVolumeMultiplier;
48
+ else if (self.soundFontUrl === defaultSoundFontUrl || self.soundFontUrl === alternateSoundFontUrl)
49
+ self.soundFontVolumeMultiplier = 3.0;
50
+ else if (self.soundFontUrl === originalSoundFontUrl)
51
+ self.soundFontVolumeMultiplier = 0.4;
52
+ else
53
+ self.soundFontVolumeMultiplier = 1.0;
54
+ if (params.programOffsets)
55
+ self.programOffsets = params.programOffsets;
56
+ else if (self.soundFontUrl === originalSoundFontUrl)
57
+ self.programOffsets = {
58
+ "bright_acoustic_piano": 20,
59
+ "honkytonk_piano": 20,
60
+ "electric_piano_1": 30,
61
+ "electric_piano_2": 30,
62
+ "harpsichord": 40,
63
+ "clavinet": 20,
64
+ "celesta": 20,
65
+ "glockenspiel": 40,
66
+ "vibraphone": 30,
67
+ "marimba": 35,
68
+ "xylophone": 30,
69
+ "tubular_bells": 35,
70
+ "dulcimer": 30,
71
+ "drawbar_organ": 20,
72
+ "percussive_organ": 25,
73
+ "rock_organ": 20,
74
+ "church_organ": 40,
75
+ "reed_organ": 40,
76
+ "accordion": 40,
77
+ "harmonica": 40,
78
+ "acoustic_guitar_nylon": 20,
79
+ "acoustic_guitar_steel": 30,
80
+ "electric_guitar_jazz": 25,
81
+ "electric_guitar_clean": 15,
82
+ "electric_guitar_muted": 35,
83
+ "overdriven_guitar": 25,
84
+ "distortion_guitar": 20,
85
+ "guitar_harmonics": 30,
86
+ "electric_bass_finger": 15,
87
+ "electric_bass_pick": 30,
88
+ "fretless_bass": 40,
89
+ "violin": 105,
90
+ "viola": 50,
91
+ "cello": 40,
92
+ "contrabass": 60,
93
+ "trumpet": 10,
94
+ "trombone": 90,
95
+ "alto_sax": 20,
96
+ "tenor_sax": 20,
97
+ "clarinet": 20,
98
+ "flute": 50,
99
+ "banjo": 50,
100
+ "woodblock": 20,
101
+ };
102
+ else
103
+ self.programOffsets = {};
104
+ var p = params.fadeLength !== undefined ? parseInt(params.fadeLength,10) : NaN;
105
+ self.fadeLength = isNaN(p) ? 200 : p;
106
+ p = params.noteEnd !== undefined ? parseInt(params.noteEnd,10) : NaN;
107
+ self.noteEnd = isNaN(p) ? 0 : p;
108
+
59
109
  self.pan = params.pan;
60
110
  self.meterSize = 1;
61
111
  if (options.visualObj) {
62
- var seq = sequence(options.visualObj, params);
63
- self.flattened = flatten(seq, params);
64
- self.meterSize = options.visualObj.getMeterFraction().num / options.visualObj.getMeterFraction().den;
112
+ self.flattened = options.visualObj.setUpAudio(params);
113
+ var meter = options.visualObj.getMeterFraction();
114
+ if (meter.den)
115
+ self.meterSize = options.visualObj.getMeterFraction().num / options.visualObj.getMeterFraction().den;
65
116
  } else if (options.sequence)
66
117
  self.flattened = options.sequence;
67
118
  else
68
119
  return Promise.reject(new Error("Must pass in either a visualObj or a sequence"));
120
+ self.millisecondsPerMeasure = options.millisecondsPerMeasure ? options.millisecondsPerMeasure : (options.visualObj ? options.visualObj.millisecondsPerMeasure(self.flattened.tempo) : 1000);
121
+ self.beatsPerMeasure = options.visualObj ? options.visualObj.getBeatsPerMeasure() : 4;
69
122
  self.sequenceCallback = params.sequenceCallback;
70
123
  self.callbackContext = params.callbackContext;
71
- self.onEnded = options.onEnded;
124
+ self.onEnded = params.onEnded;
72
125
 
73
126
  var allNotes = {};
127
+ var cached = [];
128
+ var errorNotes = [];
74
129
  var currentInstrument = instrumentIndexToName[0];
75
130
  self.flattened.tracks.forEach(function(track) {
76
131
  track.forEach(function(event) {
77
132
  if (event.cmd === "program" && instrumentIndexToName[event.instrument])
78
133
  currentInstrument = instrumentIndexToName[event.instrument];
79
134
  if (event.pitch !== undefined) {
80
- var pitchNumber = event.pitch + 60;
135
+ var pitchNumber = event.pitch;
81
136
  var noteName = pitchToNoteName[pitchNumber];
82
137
  if (noteName) {
83
138
  if (!allNotes[currentInstrument])
84
139
  allNotes[currentInstrument] = {};
85
- allNotes[currentInstrument][pitchToNoteName[pitchNumber]] = true;
86
- } else
87
- console.log("Can't find note: ", pitchNumber);
140
+ if (!soundsCache[currentInstrument] || !soundsCache[currentInstrument][noteName])
141
+ allNotes[currentInstrument][noteName] = true;
142
+ else {
143
+ var label2 = currentInstrument+":"+noteName
144
+ if (cached.indexOf(label2) < 0)
145
+ cached.push(label2);
146
+ }
147
+ } else {
148
+ var label = currentInstrument+":"+noteName
149
+ console.log("Can't find note: ", pitchNumber, label);
150
+ if (errorNotes.indexOf(label) < 0)
151
+ errorNotes.push(label)
152
+ }
88
153
  }
89
154
  });
90
155
  });
@@ -106,14 +171,23 @@ function CreateSynth() {
106
171
  }
107
172
 
108
173
  return new Promise(function(resolve, reject) {
109
- var results = [];
174
+ var results = {
175
+ cached: cached,
176
+ error: errorNotes,
177
+ loaded: []
178
+ };
110
179
 
111
180
  var index = 0;
112
181
  var next = function() {
113
182
  if (index < batches.length) {
114
183
  self._loadBatch(batches[index], self.soundFontUrl, startTime).then(function(data) {
115
184
  startTime = activeAudioContext().currentTime;
116
- results.push(data);
185
+ if (data) {
186
+ if (data.error)
187
+ results.error = results.error.concat(data.error);
188
+ if (data.loaded)
189
+ results.loaded = results.loaded.concat(data.loaded);
190
+ }
117
191
  index++;
118
192
  next();
119
193
  }, reject);
@@ -167,13 +241,20 @@ function CreateSynth() {
167
241
  }
168
242
  self._loadBatch(newBatch, soundFontUrl, startTime, delay).then(function (response) {
169
243
  resolve(response);
244
+ }).catch(function (error) {
245
+ reject(error);
170
246
  });
171
247
  }, delay);
172
248
  });
173
- } else
174
- return Promise.reject(new Error("time out attempting to load: " + batch.join("/")));
249
+ } else {
250
+ var list = [];
251
+ for (var j = 0; j < batch.length; j++)
252
+ list.push(batch[j].instrument+'/'+batch[j].note)
253
+ return Promise.reject(new Error("timeout attempting to load: " + list.join(", ")));
254
+ }
175
255
  } else
176
256
  return Promise.resolve({loaded: loaded, cached: cached, error: error});
257
+ }).catch(function (error) {
177
258
  });
178
259
  });
179
260
 
@@ -181,6 +262,7 @@ function CreateSynth() {
181
262
  // At this point all of the notes are loaded. This function writes them into the output buffer.
182
263
  // Most music has a lot of repeating notes. If a note is the same pitch, volume, length, etc. as another one,
183
264
  // It saves a lot of time to just create it once and place it repeatedly where ever it needs to be.
265
+ var fadeTimeSec = self.fadeLength/1000;
184
266
  self.isRunning = false;
185
267
  if (!self.audioBufferPossible)
186
268
  return Promise.reject(new Error(notSupportedMessage));
@@ -194,6 +276,7 @@ function CreateSynth() {
194
276
  self.audioBuffers = [];
195
277
  return resolve({ status: "empty", seconds: 0});
196
278
  }
279
+ self.duration += fadeTimeSec;
197
280
  var totalSamples = Math.floor(activeAudioContext().sampleRate * self.duration);
198
281
 
199
282
  // There might be a previous run that needs to be turned off.
@@ -211,7 +294,7 @@ function CreateSynth() {
211
294
  noteMapTracks.forEach(function(noteMap, trackNumber) {
212
295
  var panDistance = panDistances && panDistances.length > trackNumber ? panDistances[trackNumber] : 0;
213
296
  noteMap.forEach(function(note) {
214
- var key = note.instrument + ':' + note.pitch + ':' +note.volume + ':' + (note.end-note.start) + ':' + panDistance + ':' + tempoMultiplier;
297
+ var key = note.instrument + ':' + note.pitch + ':' +note.volume + ':' + Math.round((note.end-note.start)*1000)/1000 + ':' + panDistance + ':' + tempoMultiplier + ':' + (note.cents ? note.cents : 0);
215
298
  if (!uniqueSounds[key])
216
299
  uniqueSounds[key] = [];
217
300
  uniqueSounds[key].push(note.start);
@@ -224,8 +307,9 @@ function CreateSynth() {
224
307
  for (var key2 = 0; key2 < Object.keys(uniqueSounds).length; key2++) {
225
308
  var k = Object.keys(uniqueSounds)[key2];
226
309
  var parts = k.split(":");
227
- parts = { instrument: parts[0], pitch: parseInt(parts[1],10), volume: parseInt(parts[2], 10), len: parseFloat(parts[3]), pan: parseFloat(parts[4]), tempoMultiplier: parseFloat(parts[5])};
228
- allPromises.push(placeNote(audioBuffer, activeAudioContext().sampleRate, parts, uniqueSounds[k]));
310
+ var cents = parts[6] !== undefined ? parseFloat(parts[6]) : 0;
311
+ parts = {instrument: parts[0], pitch: parseInt(parts[1], 10), volume: parseInt(parts[2], 10), len: parseFloat(parts[3]), pan: parseFloat(parts[4]), tempoMultiplier: parseFloat(parts[5]), cents: cents};
312
+ allPromises.push(placeNote(audioBuffer, activeAudioContext().sampleRate, parts, uniqueSounds[k], self.soundFontVolumeMultiplier, self.programOffsets[parts.instrument], fadeTimeSec, self.noteEnd/1000));
229
313
  }
230
314
  self.audioBuffers = [audioBuffer];
231
315
 
@@ -234,33 +318,53 @@ function CreateSynth() {
234
318
  self.debugCallback("totalSamples = " + totalSamples);
235
319
  self.debugCallback("creationTime = " + Math.floor((activeAudioContext().currentTime - startTime)*1000) + "ms");
236
320
  }
321
+ function resolveData(me) {
322
+ var duration = me && me.audioBuffers && me.audioBuffers.length > 0 ? me.audioBuffers[0].duration : 0;
323
+ return { status: activeAudioContext().state, duration: duration}
324
+ }
237
325
  Promise.all(allPromises).then(function() {
238
- resolve({
239
- status: "ok",
240
- seconds: 0
241
- });
326
+ // Safari iOS can mess with the audioContext state, so resume if needed.
327
+ if (activeAudioContext().state === "suspended") {
328
+ activeAudioContext().resume().then(function () {
329
+ resolve(resolveData(self));
330
+ })
331
+ } else if (activeAudioContext().state === "interrupted") {
332
+ activeAudioContext().suspend().then(function () {
333
+ activeAudioContext().resume().then(function () {
334
+ resolve(resolveData(self));
335
+ })
336
+ })
337
+ } else {
338
+ resolve(resolveData(self));
339
+ }
242
340
  });
243
341
  });
244
342
  };
245
343
 
246
344
  function setPan(numTracks, panParam) {
345
+ // panParam, if it is set, can be either a number representing the separation between each track,
346
+ // or an array, which is the absolute pan position for each track.
247
347
  if (panParam === null || panParam === undefined)
248
348
  return null;
249
349
 
250
350
  var panDistances = [];
251
351
  if (panParam.length) {
252
- if (numTracks === panParam.length) {
253
- var ok = true;
254
- for (var pp = 0; pp < panParam.length; pp++){
352
+ // We received an array. If there are the same number of items in the pan array as the number of tracks,
353
+ // it all lines up perfectly. If there are more items in the pan array than the tracks then the excess items are ignored.
354
+ // If there are more tracks than items in the pan array then the remaining tracks are placed in the middle.
355
+ // If any of the pan numbers are out of range then they are adjusted.
356
+ for (var pp = 0; pp < numTracks; pp++) {
357
+ if (pp < panParam.length) {
255
358
  var x = parseFloat(panParam[pp]);
256
- if (x >= -1 && x <= 1)
257
- panDistances.push(x);
258
- else
259
- ok = false;
260
- }
261
- if (ok)
262
- return panDistances;
359
+ if (x < -1)
360
+ x = -1;
361
+ else if (x > 1)
362
+ x = 1;
363
+ panDistances.push(x);
364
+ } else
365
+ panDistances.push(0)
263
366
  }
367
+ return panDistances;
264
368
  } else {
265
369
  var panNumber = parseFloat(panParam);
266
370
  // the separation needs to be no further than 2 (i.e. -1 to 1) so test to see if there are too many tracks for the passed in distance
@@ -290,18 +394,14 @@ function CreateSynth() {
290
394
 
291
395
  // This is called after everything is set up, so it can quickly make sound
292
396
  self.start = function() {
293
- if (self.pausedTimeSec) {
294
- self.resume();
295
- return;
296
- }
297
-
298
397
  if (!self.audioBufferPossible)
299
398
  throw new Error(notSupportedMessage);
300
399
  if (self.debugCallback)
301
400
  self.debugCallback("start called");
302
401
 
303
- self._kickOffSound(0);
304
- self.startTimeSec = activeAudioContext().currentTime;
402
+ var resumePosition = self.pausedTimeSec ? self.pausedTimeSec : 0;
403
+ self._kickOffSound(resumePosition);
404
+ self.startTimeSec = activeAudioContext().currentTime - resumePosition;
305
405
  self.pausedTimeSec = undefined;
306
406
 
307
407
  if (self.debugCallback)
@@ -314,26 +414,28 @@ function CreateSynth() {
314
414
  if (self.debugCallback)
315
415
  self.debugCallback("pause called");
316
416
 
317
- if (!self.pausedTimeSec) { // ignore if self is already paused.
318
- self.stop();
319
- self.pausedTimeSec = activeAudioContext().currentTime;
320
- }
417
+ self.pausedTimeSec = self.stop();
418
+ return self.pausedTimeSec;
321
419
  };
322
420
 
323
421
  self.resume = function() {
324
- if (!self.audioBufferPossible)
325
- throw new Error(notSupportedMessage);
326
- if (self.debugCallback)
327
- self.debugCallback("resume called");
328
-
329
- var offset = self.pausedTimeSec - self.startTimeSec;
330
- self.startTimeSec = activeAudioContext().currentTime - offset; // We move the start time in case there is another pause/resume.
331
- self.pausedTimeSec = undefined;
332
- self._kickOffSound(offset);
422
+ self.start();
333
423
  };
334
424
 
335
- self.seek = function(percent) {
336
- var offset = self.duration * percent;
425
+ self.seek = function(position, units) {
426
+ var offset;
427
+ switch (units) {
428
+ case "seconds":
429
+ offset = position;
430
+ break;
431
+ case "beats":
432
+ offset = position * self.millisecondsPerMeasure / self.beatsPerMeasure / 1000;
433
+ break;
434
+ default:
435
+ // this is "percent" or any illegal value
436
+ offset = (self.duration-self.fadeLength/1000) * position;
437
+ break;
438
+ }
337
439
 
338
440
  // TODO-PER: can seek when paused or when playing
339
441
  if (!self.audioBufferPossible)
@@ -344,11 +446,10 @@ function CreateSynth() {
344
446
  if (self.isRunning) {
345
447
  self.stop();
346
448
  self._kickOffSound(offset);
449
+ } else {
450
+ self.pausedTimeSec = offset;
347
451
  }
348
- var pauseDistance = self.pausedTimeSec ? self.pausedTimeSec - self.startTimeSec : undefined;
349
- self.startTimeSec = activeAudioContext().currentTime - offset;
350
- if (self.pausedTimeSec)
351
- self.pausedTimeSec = self.startTimeSec + pauseDistance;
452
+ self.pausedTimeSec = offset;
352
453
  };
353
454
 
354
455
  self.stop = function() {
@@ -363,6 +464,13 @@ function CreateSynth() {
363
464
  }
364
465
  });
365
466
  self.directSource = [];
467
+ var elapsed = activeAudioContext().currentTime - self.startTimeSec;
468
+ return elapsed;
469
+ };
470
+ self.finished = function() {
471
+ self.startTimeSec = undefined;
472
+ self.pausedTimeSec = undefined;
473
+ self.isRunning = false;
366
474
  };
367
475
 
368
476
  self.download = function() {