abcjs 6.0.0-beta.7 → 6.0.0

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 (204) 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 +92 -3
  6. package/RELEASE.md +957 -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 +28231 -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/dist/report-basic.html +37 -0
  16. package/dist/report-before-glyph-compress.html +37 -0
  17. package/dist/report-brown-ts-target-es5.html +37 -0
  18. package/dist/report-dev-orig-no-babel.html +37 -0
  19. package/dist/report-synth.html +37 -0
  20. package/docker-build.sh +1 -0
  21. package/glyphs.json +1 -0
  22. package/index.js +27 -2
  23. package/{static-wrappers/license.js → license.js} +1 -1
  24. package/package.json +26 -29
  25. package/{src/plugin/abc_plugin.js → plugin.js} +31 -19
  26. package/src/api/abc_animation.js +1 -17
  27. package/src/api/abc_tablatures.js +144 -0
  28. package/src/api/abc_timing_callbacks.js +234 -116
  29. package/src/api/abc_tunebook.js +18 -67
  30. package/src/api/abc_tunebook_svg.js +38 -46
  31. package/src/data/abc_tune.js +232 -972
  32. package/src/data/deline-tune.js +199 -0
  33. package/src/edit/abc_editarea.js +112 -0
  34. package/src/edit/abc_editor.js +95 -221
  35. package/src/midi/abc_midi_create.js +48 -50
  36. package/src/parse/abc_common.js +0 -14
  37. package/src/parse/abc_parse.js +167 -1321
  38. package/src/parse/abc_parse_book.js +62 -0
  39. package/src/parse/abc_parse_directive.js +164 -41
  40. package/src/parse/abc_parse_header.js +116 -145
  41. package/src/parse/abc_parse_key_voice.js +26 -20
  42. package/src/parse/abc_parse_music.js +1337 -0
  43. package/src/parse/abc_tokenizer.js +21 -15
  44. package/src/parse/abc_transpose.js +3 -15
  45. package/src/parse/tune-builder.js +896 -0
  46. package/src/parse/wrap_lines.js +205 -453
  47. package/src/synth/abc_midi_flattener.js +1292 -0
  48. package/src/{midi → synth}/abc_midi_renderer.js +44 -17
  49. package/src/synth/abc_midi_sequencer.js +648 -0
  50. package/src/synth/active-audio-context.js +3 -14
  51. package/src/synth/cents-to-factor.js +10 -0
  52. package/src/synth/create-note-map.js +21 -32
  53. package/src/synth/create-synth-control.js +20 -103
  54. package/src/synth/create-synth.js +185 -77
  55. package/src/synth/download-buffer.js +7 -21
  56. package/src/synth/get-midi-file.js +13 -20
  57. package/src/synth/images/{loading.svg → loading.svg.js} +4 -0
  58. package/src/synth/images/loop.svg.js +65 -0
  59. package/src/synth/images/pause.svg.js +10 -0
  60. package/src/synth/images/play.svg.js +9 -0
  61. package/src/synth/images/{reset.svg → reset.svg.js} +5 -1
  62. package/src/synth/instrument-index-to-name.js +1 -16
  63. package/src/synth/load-note.js +37 -76
  64. package/src/synth/pitch-to-note-name.js +0 -15
  65. package/src/synth/pitches-to-perc.js +64 -0
  66. package/src/synth/place-note.js +78 -68
  67. package/src/synth/play-event.js +17 -18
  68. package/src/synth/register-audio-context.js +11 -23
  69. package/src/synth/sounds-cache.js +0 -15
  70. package/src/synth/supports-audio.js +9 -23
  71. package/src/synth/synth-controller.js +80 -49
  72. package/src/synth/synth-sequence.js +20 -34
  73. package/src/tablatures/instruments/guitar/guitar-fonts.js +19 -0
  74. package/src/tablatures/instruments/guitar/guitar-patterns.js +23 -0
  75. package/src/tablatures/instruments/guitar/tab-guitar.js +50 -0
  76. package/src/tablatures/instruments/string-patterns.js +277 -0
  77. package/src/tablatures/instruments/string-tablature.js +56 -0
  78. package/src/tablatures/instruments/tab-note.js +282 -0
  79. package/src/tablatures/instruments/tab-notes.js +41 -0
  80. package/src/tablatures/instruments/violin/tab-violin.js +47 -0
  81. package/src/tablatures/instruments/violin/violin-fonts.js +19 -0
  82. package/src/tablatures/instruments/violin/violin-patterns.js +23 -0
  83. package/src/tablatures/tab-absolute-elements.js +310 -0
  84. package/src/tablatures/tab-common.js +29 -0
  85. package/src/tablatures/tab-renderer.js +243 -0
  86. package/src/tablatures/transposer.js +110 -0
  87. package/src/test/abc_midi_lint.js +5 -22
  88. package/src/test/abc_midi_sequencer_lint.js +11 -14
  89. package/src/test/abc_parser_lint.js +136 -32
  90. package/src/test/abc_vertical_lint.js +94 -32
  91. package/src/test/rendering-lint.js +38 -5
  92. package/src/write/abc_absolute_element.js +112 -120
  93. package/src/write/abc_abstract_engraver.js +102 -253
  94. package/src/write/abc_beam_element.js +30 -290
  95. package/src/write/abc_brace_element.js +12 -121
  96. package/src/write/abc_create_clef.js +21 -32
  97. package/src/write/abc_create_key_signature.js +8 -26
  98. package/src/write/abc_create_note_head.js +107 -0
  99. package/src/write/abc_create_time_signature.js +2 -21
  100. package/src/write/abc_crescendo_element.js +3 -50
  101. package/src/write/abc_decoration.js +7 -30
  102. package/src/write/abc_dynamic_decoration.js +3 -37
  103. package/src/write/abc_ending_element.js +1 -57
  104. package/src/write/abc_engraver_controller.js +111 -234
  105. package/src/write/abc_glyphs.js +9 -19
  106. package/src/write/abc_relative_element.js +57 -97
  107. package/src/write/abc_renderer.js +10 -832
  108. package/src/write/abc_spacing.js +0 -15
  109. package/src/write/abc_staff_group_element.js +14 -349
  110. package/src/write/abc_tempo_element.js +9 -117
  111. package/src/write/abc_tie_element.js +5 -68
  112. package/src/write/abc_triplet_element.js +6 -124
  113. package/src/write/abc_voice_element.js +7 -222
  114. package/src/write/add-chord.js +103 -0
  115. package/src/write/add-text-if.js +33 -0
  116. package/src/write/bottom-text.js +79 -0
  117. package/src/write/calcHeight.js +17 -0
  118. package/src/write/classes.js +100 -0
  119. package/src/write/draw/absolute.js +68 -0
  120. package/src/write/draw/beam.js +56 -0
  121. package/src/write/draw/brace.js +106 -0
  122. package/src/write/draw/crescendo.js +38 -0
  123. package/src/write/draw/debug-box.js +8 -0
  124. package/src/write/draw/draw.js +56 -0
  125. package/src/write/draw/dynamics.js +20 -0
  126. package/src/write/draw/ending.js +46 -0
  127. package/src/write/draw/group-elements.js +66 -0
  128. package/src/write/draw/horizontal-line.js +25 -0
  129. package/src/write/draw/non-music.js +50 -0
  130. package/src/write/draw/print-line.js +24 -0
  131. package/src/write/draw/print-path.js +7 -0
  132. package/src/write/draw/print-stem.js +30 -0
  133. package/src/write/draw/print-symbol.js +59 -0
  134. package/src/write/draw/print-vertical-line.js +18 -0
  135. package/src/write/draw/relative.js +77 -0
  136. package/src/write/draw/round-number.js +5 -0
  137. package/src/write/draw/selectables.js +59 -0
  138. package/src/write/draw/separator.js +16 -0
  139. package/src/write/draw/set-paper-size.js +45 -0
  140. package/src/write/{sprintf.js → draw/sprintf.js} +0 -0
  141. package/src/write/draw/staff-group.js +226 -0
  142. package/src/write/draw/staff-line.js +9 -0
  143. package/src/write/draw/staff.js +33 -0
  144. package/src/write/draw/tab-line.js +40 -0
  145. package/src/write/draw/tempo.js +45 -0
  146. package/src/write/draw/text.js +71 -0
  147. package/src/write/draw/tie.js +97 -0
  148. package/src/write/draw/triplet.js +46 -0
  149. package/src/write/draw/voice.js +102 -0
  150. package/src/write/format-jazz-chord.js +15 -0
  151. package/src/write/free-text.js +41 -0
  152. package/src/write/get-font-and-attr.js +37 -0
  153. package/src/write/get-text-size.js +56 -0
  154. package/src/write/highlight.js +11 -0
  155. package/src/write/layout/VoiceElements.js +121 -0
  156. package/src/write/layout/beam.js +213 -0
  157. package/src/write/layout/get-left-edge-of-staff.js +56 -0
  158. package/src/write/layout/getBarYAt.js +6 -0
  159. package/src/write/layout/layout.js +94 -0
  160. package/src/write/layout/setUpperAndLowerElements.js +232 -0
  161. package/src/write/layout/staffGroup.js +146 -0
  162. package/src/write/layout/triplet.js +75 -0
  163. package/src/write/layout/voice.js +137 -0
  164. package/src/write/selection.js +188 -70
  165. package/src/write/separator.js +10 -0
  166. package/src/write/set-class.js +21 -0
  167. package/src/write/subtitle.js +12 -0
  168. package/src/write/svg.js +95 -43
  169. package/src/write/top-text.js +54 -0
  170. package/src/write/unhighlight.js +11 -0
  171. package/temp.txt +17 -0
  172. package/test.js +27 -64
  173. package/types/index.d.ts +1095 -0
  174. package/version.js +1 -1
  175. package/.babelrc +0 -5
  176. package/.eslintrc +0 -3
  177. package/.gitmodules +0 -3
  178. package/abcjs-midi.css +0 -166
  179. package/build-utils/loadPresets.js +0 -14
  180. package/build-utils/presets/webpack.analyze.js +0 -6
  181. package/build-utils/presets/webpack.optimize.js +0 -30
  182. package/build-utils/webpack.development.js +0 -14
  183. package/build-utils/webpack.production.js +0 -35
  184. package/deploy-docs.sh +0 -25
  185. package/docs/README.md +0 -33
  186. package/fix-versions.sh +0 -23
  187. package/mei.js +0 -43
  188. package/midi.js +0 -62
  189. package/src/api/abc_tunebook_midi.js +0 -116
  190. package/src/midi/abc_midi_controls.js +0 -701
  191. package/src/midi/abc_midi_flattener.js +0 -1119
  192. package/src/midi/abc_midi_js_preparer.js +0 -243
  193. package/src/midi/abc_midi_sequencer.js +0 -401
  194. package/src/midi/abc_midi_ui_generator.js +0 -86
  195. package/src/plugin/abc_plugin_midi.js +0 -220
  196. package/src/synth/images/loop.svg +0 -61
  197. package/src/synth/images/pause.svg +0 -6
  198. package/src/synth/images/play.svg +0 -5
  199. package/src/transform/abc2abc_write.js +0 -395
  200. package/static-wrappers/basic.js +0 -2
  201. package/static-wrappers/midi.js +0 -2
  202. package/static-wrappers/plugin-midi.js +0 -6
  203. package/static-wrappers/plugin.js +0 -6
  204. package/webpack.config.js +0 -29
@@ -1,18 +1,3 @@
1
- // Copyright (C) 2015-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 TimingCallbacks = function(target, params) {
17
2
  var self = this;
18
3
  if (!params) params = {};
@@ -27,166 +12,299 @@ var TimingCallbacks = function(target, params) {
27
12
  self.lineEndCallback = params.lineEndCallback; // This is called when the end of a line is approaching.
28
13
  self.lineEndAnticipation = params.lineEndAnticipation ? parseInt(params.lineEndAnticipation, 10) : 0; // How many milliseconds before the end should the call happen.
29
14
  self.beatSubdivisions = params.beatSubdivisions ? parseInt(params.beatSubdivisions, 10) : 1; // how many callbacks per beat is desired.
15
+ self.joggerTimer = null;
30
16
 
31
17
  self.replaceTarget = function(newTarget) {
32
- newTarget.setTiming(self.qpm, self.extraMeasuresAtBeginning);
18
+ self.noteTimings = newTarget.setTiming(self.qpm, self.extraMeasuresAtBeginning);
33
19
  if (newTarget.noteTimings.length === 0)
34
20
  newTarget.setTiming(0,0);
35
21
  if (self.lineEndCallback) {
36
22
  self.lineEndTimings = getLineEndTimings(newTarget.noteTimings, self.lineEndAnticipation);
37
23
  }
38
- self.noteTimings = newTarget.noteTimings;
24
+ self.startTime = null;
25
+ self.currentBeat = 0;
26
+ self.currentEvent = 0;
27
+ self.currentLine = 0;
28
+ self.currentTime = 0;
29
+ self.isPaused = false;
30
+ self.isRunning = false;
31
+ self.pausedPercent = null;
32
+ self.justUnpaused = false;
33
+ self.newSeekPercent = 0;
34
+ self.lastTimestamp = 0;
35
+
36
+ if (self.noteTimings.length === 0)
37
+ return;
38
+ // noteTimings contains an array of events sorted by time. Events that happen at the same time are in the same element of the array.
39
+ self.millisecondsPerBeat = 1000 / (self.qpm / 60) / self.beatSubdivisions;
40
+ self.lastMoment = self.noteTimings[self.noteTimings.length-1].milliseconds;
41
+ self.totalBeats = Math.round(self.lastMoment / self.millisecondsPerBeat);
39
42
  };
40
43
 
41
44
  self.replaceTarget(target);
42
- if (self.noteTimings.length === 0)
43
- return;
44
-
45
- // noteTimings contains an array of events sorted by time. Events that happen at the same time are in the same element of the array.
46
- self.noteTimings = target.noteTimings;
47
- self.millisecondsPerBeat = 1000 / (self.qpm / 60) / self.beatSubdivisions;
48
- self.lastMoment = self.noteTimings[self.noteTimings.length-1].milliseconds;
49
- self.totalBeats = Math.round(self.lastMoment / self.millisecondsPerBeat);
50
-
51
- self.startTime = null;
52
- self.currentBeat = 0;
53
- self.currentEvent = 0;
54
- self.isPaused = false;
55
- self.isRunning = false;
56
- self.pausedTime = null;
57
- self.justUnpaused = false;
58
-
59
- self.newSeekPercent = 0;
60
- self.justSeeked = false;
61
-
62
- function setCurrentLocation(timestamp) {
63
- // First find the relative amount to move: that is, the difference between the current percentage and the passed in percent.
64
- var currentPercent = (timestamp - self.startTime) / self.lastMoment;
65
- var percentDifference = currentPercent - self.newSeekPercent;
66
- var timeDifference = self.lastMoment * percentDifference;
67
- self.startTime = self.startTime + timeDifference;
68
-
69
- var currentTime = timestamp - self.startTime;
70
- currentTime += 16; // Add a little slop because this function isn't called exactly.
71
-
72
- var oldBeat = self.currentBeat;
73
- self.currentBeat = Math.floor(currentTime / self.millisecondsPerBeat);
74
- if (self.beatCallback && oldBeat !== self.currentBeat) // If the movement caused the beat to change, then immediately report it to the client.
75
- self.beatCallback(self.currentBeat / self.beatSubdivisions, self.totalBeats / self.beatSubdivisions, self.lastMoment);
76
-
77
- self.currentEvent = 0;
78
- while (self.noteTimings.length > self.currentEvent && self.noteTimings[self.currentEvent].milliseconds < currentTime) {
79
- self.currentEvent++;
80
- }
81
- if (self.eventCallback && self.currentEvent > 0 && self.noteTimings[self.currentEvent - 1].type === 'event')
82
- self.eventCallback(self.noteTimings[self.currentEvent - 1]);
83
-
84
- // console.log("currentPercent="+currentPercent+
85
- // " newSeekPercent="+self.newSeekPercent+
86
- // " percentDifference="+percentDifference+
87
- // " timeDifference=",timeDifference+
88
- // " currentBeat="+self.currentBeat+
89
- // " currentEvent="+self.currentEvent);
90
- }
91
45
 
92
- self.lastTimestamp = 0;
93
46
  self.doTiming = function (timestamp) {
94
47
  // This is called 60 times a second, that is, every 16 msecs.
95
48
  //console.log("doTiming", timestamp, timestamp-self.lastTimestamp);
96
49
  if (self.lastTimestamp === timestamp)
97
- return; // If there are multiple seeks or other calls, then we can easily get multiple callbacks for the same instance.
50
+ return; // If there are multiple seeks or other calls, then we can easily get multiple callbacks for the same instant.
98
51
  self.lastTimestamp = timestamp;
99
52
  if (!self.startTime) {
100
53
  self.startTime = timestamp;
101
- } else if (self.justUnpaused) {
102
- // Add the amount we paused to the start time to get the right place.
103
- var timePaused = (timestamp - self.pausedTime);
104
- self.startTime += timePaused;
105
54
  }
106
- self.justUnpaused = false;
107
55
 
108
- if (self.justSeeked) {
109
- setCurrentLocation(timestamp);
110
- self.justSeeked = false;
111
- }
112
- if (self.isPaused) {
113
- self.pausedTime = timestamp;
114
- } else if (self.isRunning) {
115
- var currentTime = timestamp - self.startTime;
116
- currentTime += 16; // Add a little slop because this function isn't called exactly.
117
- while (self.noteTimings.length > self.currentEvent && self.noteTimings[self.currentEvent].milliseconds < currentTime) {
118
- if (self.eventCallback && self.noteTimings[self.currentEvent].type === 'event')
56
+ if (!self.isPaused && self.isRunning) {
57
+ self.currentTime = timestamp - self.startTime;
58
+ self.currentTime += 16; // Add a little slop because this function isn't called exactly.
59
+ while (self.noteTimings.length > self.currentEvent && self.noteTimings[self.currentEvent].milliseconds < self.currentTime) {
60
+ if (self.eventCallback && self.noteTimings[self.currentEvent].type === 'event') {
61
+ var thisStartTime = self.startTime; // the event callback can call seek and change the position from beneath us.
119
62
  self.eventCallback(self.noteTimings[self.currentEvent]);
63
+ if (thisStartTime !== self.startTime) {
64
+ self.currentTime = timestamp - self.startTime;
65
+ }
66
+ }
120
67
  self.currentEvent++;
121
68
  }
122
- if (currentTime < self.lastMoment) {
69
+ if (self.lineEndCallback && self.lineEndTimings.length > self.currentLine && self.lineEndTimings[self.currentLine].milliseconds < self.currentTime && self.currentEvent < self.noteTimings.length) {
70
+ var leftEvent = self.noteTimings[self.currentEvent].milliseconds === self.currentTime ? self.noteTimings[self.currentEvent] : self.noteTimings[self.currentEvent-1]
71
+ self.lineEndCallback(self.lineEndTimings[self.currentLine], leftEvent, { line: self.currentLine, endTimings: self.lineEndTimings, currentTime: self.currentTime });
72
+ self.currentLine++;
73
+ }
74
+ if (self.currentTime < self.lastMoment) {
123
75
  requestAnimationFrame(self.doTiming);
124
- if (self.currentBeat * self.millisecondsPerBeat < currentTime) {
125
- if (self.beatCallback)
126
- self.beatCallback(self.currentBeat / self.beatSubdivisions, self.totalBeats / self.beatSubdivisions, self.lastMoment);
127
- self.currentBeat++;
76
+ if (self.currentBeat * self.millisecondsPerBeat < self.currentTime) {
77
+ var ret = self.doBeatCallback(timestamp);
78
+ if (ret !== null)
79
+ self.currentTime = ret;
128
80
  }
129
81
  } else if (self.currentBeat <= self.totalBeats) {
130
82
  // Because of timing issues (for instance, if the browser tab isn't active), the beat callbacks might not have happened when they are supposed to. To keep the client programs from having to deal with that, this will keep calling the loop until all of them have been sent.
131
83
  if (self.beatCallback) {
132
- self.beatCallback(self.currentBeat / self.beatSubdivisions, self.totalBeats / self.beatSubdivisions, self.lastMoment);
133
- self.currentBeat++;
84
+ var ret2 = self.doBeatCallback(timestamp);
85
+ if (ret2 !== null)
86
+ self.currentTime = ret2;
134
87
  requestAnimationFrame(self.doTiming);
135
88
  }
136
89
  }
137
90
 
138
- if (self.lineEndCallback && self.lineEndTimings.length && self.lineEndTimings[0].milliseconds <= currentTime) {
139
- self.lineEndCallback(self.lineEndTimings[0]);
140
- self.lineEndTimings.shift();
91
+ if (self.currentTime >= self.lastMoment) {
92
+ if (self.eventCallback) {
93
+ // At the end, the event callback can return "continue" to keep from stopping.
94
+ // The event callback can either be a promise or not.
95
+ var promise = self.eventCallback(null);
96
+ self.shouldStop(promise).then(function(shouldStop) {
97
+ if (shouldStop)
98
+ self.stop();
99
+ })
100
+ } else
101
+ self.stop();
102
+ }
103
+ }
104
+ };
105
+
106
+ self.shouldStop = function(promise) {
107
+ // The return of the last event callback can be "continue" or a promise that returns "continue".
108
+ // If it is then don't call stop. Any other value calls stop.
109
+ return new Promise(function (resolve) {
110
+ if (!promise)
111
+ return resolve(true);
112
+ if (promise === "continue")
113
+ return resolve(false);
114
+ if (promise.then) {
115
+ promise.then(function (result) {
116
+ resolve(result !== "continue");
117
+ });
118
+ }
119
+ });
120
+ };
121
+
122
+ self.doBeatCallback = function(timestamp) {
123
+ if (self.beatCallback) {
124
+ var next = self.currentEvent;
125
+ while (next < self.noteTimings.length && self.noteTimings[next].left === null)
126
+ next++;
127
+ var endMs;
128
+ var ev;
129
+ if (next < self.noteTimings.length) {
130
+ endMs = self.noteTimings[next].milliseconds;
131
+ next = self.currentEvent - 1;
132
+ while (next >= 0 && self.noteTimings[next].left === null)
133
+ next--;
134
+
135
+ ev = self.noteTimings[next];
141
136
  }
142
137
 
143
- if (currentTime >= self.lastMoment && self.eventCallback)
144
- self.eventCallback(null);
138
+ var position = {};
139
+ var debugInfo = {};
140
+ if (ev) {
141
+ position.top = ev.top;
142
+ position.height = ev.height;
143
+
144
+ // timestamp = the time passed in from the animation timer
145
+ // self.startTime = the time that the tune was started (if there was seeking or pausing, it is adjusted to keep the math the same)
146
+ // ev = the event that is either happening now or has most recently passed.
147
+ // ev.milliseconds = the time that the current event starts (relative to self.startTime)
148
+ // endMs = the time that the next event starts
149
+ // ev.endX = the x coordinate that the next event happens (or the end of the line or repeat measure)
150
+ // ev.left = the x coordinate of the current event
151
+ //
152
+ // The output is the X coordinate of the current cursor location. It is calculated with the ratio of the length of the event and the width of it.
153
+ var offMs = Math.max(0, timestamp-self.startTime-ev.milliseconds); // Offset in time from the last beat
154
+ var gapMs = endMs - ev.milliseconds; // Length of this event in time
155
+ var gapPx = ev.endX - ev.left; // The length in pixels
156
+ var offPx = offMs * gapPx / gapMs;
157
+ position.left = ev.left + offPx;
158
+ debugInfo = {
159
+ timestamp: timestamp,
160
+ startTime: self.startTime,
161
+ ev: ev,
162
+ endMs: endMs,
163
+ offMs: offMs,
164
+ offPs: offPx,
165
+ gapMs: gapMs,
166
+ gapPx: gapPx
167
+ };
168
+ } else {
169
+ debugInfo = {
170
+ timestamp: timestamp,
171
+ startTime: self.startTime,
172
+ };
173
+ }
174
+
175
+ var thisStartTime = self.startTime; // the beat callback can call seek and change the position from beneath us.
176
+ self.beatCallback(
177
+ self.currentBeat / self.beatSubdivisions,
178
+ self.totalBeats / self.beatSubdivisions,
179
+ self.lastMoment,
180
+ position,
181
+ debugInfo);
182
+ if (thisStartTime !== self.startTime) {
183
+ return timestamp - self.startTime;
184
+ } else
185
+ self.currentBeat++;
145
186
  }
187
+ return null;
146
188
  };
147
189
 
148
- self.start = function(offsetPercent) {
190
+ // In general music doesn't need a timer at 60 fps because notes don't happen that fast.
191
+ // For instance, at 120 beats per minute, a sixteenth note takes 125ms. So just as a
192
+ // compromise value between performance and jank this is set about half that.
193
+ var JOGGING_INTERVAL = 60;
194
+
195
+ self.animationJogger = function() {
196
+ // There are some cases where the animation timer doesn't work: for instance when
197
+ // this isn't running in a visible tab and sometimes on mobile devices. We compensate
198
+ // by having a backup timer using setTimeout. This won't be accurate so the performance
199
+ // will be jerky, but without it the requestAnimationFrame might be skipped and so
200
+ // not called again.
201
+ if (self.isRunning) {
202
+ self.doTiming(performance.now());
203
+ self.joggerTimer = setTimeout(self.animationJogger, JOGGING_INTERVAL);
204
+ }
205
+ };
206
+
207
+ self.start = function(offsetPercent, units) {
149
208
  self.isRunning = true;
150
209
  if (self.isPaused) {
151
210
  self.isPaused = false;
152
- self.justUnpaused = true;
211
+ if (offsetPercent === undefined)
212
+ self.justUnpaused = true;
153
213
  }
154
214
  if (offsetPercent) {
155
- self.setProgress(offsetPercent);
156
- } else {
157
- requestAnimationFrame(self.doTiming);
215
+ self.setProgress(offsetPercent, units);
216
+ } else if (offsetPercent === 0) {
217
+ self.reset();
218
+ } else if (self.pausedPercent !== null) {
219
+ var now = performance.now();
220
+ self.currentTime = self.lastMoment * self.pausedPercent;
221
+ self.startTime = now - self.currentTime;
222
+ self.pausedPercent = null;
223
+ self.reportNext = true;
158
224
  }
225
+ requestAnimationFrame(self.doTiming);
226
+ self.joggerTimer = setTimeout(self.animationJogger, JOGGING_INTERVAL);
159
227
  };
160
228
  self.pause = function() {
161
229
  self.isPaused = true;
230
+ var now = performance.now();
231
+ self.pausedPercent = (now - self.startTime) / self.lastMoment;
162
232
  self.isRunning = false;
233
+ if (self.joggerTimer) {
234
+ clearTimeout(self.joggerTimer);
235
+ self.joggerTimer = null;
236
+ }
237
+ };
238
+ self.currentMillisecond = function() {
239
+ return self.currentTime;
163
240
  };
164
241
  self.reset = function() {
165
242
  self.currentBeat = 0;
166
243
  self.currentEvent = 0;
244
+ self.currentLine = 0;
167
245
  self.startTime = null;
168
- self.pausedTime = null;
169
- if (self.lineEndCallback) {
170
- self.lineEndTimings = getLineEndTimings(self.noteTimings, self.lineEndAnticipation);
171
- }
246
+ self.pausedPercent = null;
172
247
  };
173
248
  self.stop = function() {
174
249
  self.pause();
175
250
  self.reset();
176
251
  };
177
- self.setProgress = function(percent) {
178
- // this is passed a value between 0 and 1.
252
+ self.setProgress = function(position, units) {
179
253
  // the effect of this function is to move startTime so that the callbacks happen correctly for the new seek.
180
- if (percent < 0) percent = 0;
181
- if (percent > 1) percent = 1;
254
+ var percent;
255
+ switch (units) {
256
+ case "seconds":
257
+ self.currentTime = position * 1000;
258
+ if (self.currentTime < 0) self.currentTime = 0;
259
+ if (self.currentTime > self.lastMoment) self.currentTime = self.lastMoment;
260
+ percent = self.currentTime / self.lastMoment;
261
+ break;
262
+ case "beats":
263
+ self.currentTime = position * self.millisecondsPerBeat * self.beatSubdivisions;
264
+ if (self.currentTime < 0) self.currentTime = 0;
265
+ if (self.currentTime > self.lastMoment) self.currentTime = self.lastMoment;
266
+ percent = self.currentTime / self.lastMoment;
267
+ break;
268
+ default:
269
+ // this is "percent" or any illegal value
270
+ // this is passed a value between 0 and 1.
271
+ percent = position;
272
+ if (percent < 0) percent = 0;
273
+ if (percent > 1) percent = 1;
274
+ self.currentTime = self.lastMoment * percent;
275
+ break;
276
+ }
277
+
278
+ if (!self.isRunning)
279
+ self.pausedPercent = percent;
280
+
281
+ var now = performance.now();
282
+ self.startTime = now - self.currentTime;
283
+
284
+ var oldEvent = self.currentEvent;
285
+ self.currentEvent = 0;
286
+ while (self.noteTimings.length > self.currentEvent && self.noteTimings[self.currentEvent].milliseconds < self.currentTime) {
287
+ self.currentEvent++;
288
+ }
182
289
 
183
- self.newSeekPercent = percent;
184
- self.justSeeked = true;
185
- self.doTiming(performance.now());
186
- //requestAnimationFrame(self.doTiming);
187
290
  if (self.lineEndCallback) {
188
- self.lineEndTimings = getLineEndTimings(self.noteTimings, self.lineEndAnticipation);
291
+ self.currentLine = 0;
292
+ while (self.lineEndTimings.length > self.currentLine && self.lineEndTimings[self.currentLine].milliseconds + self.lineEndAnticipation < self.currentTime) {
293
+ self.currentLine++;
294
+ }
189
295
  }
296
+
297
+ var oldBeat = self.currentBeat;
298
+ self.currentBeat = Math.floor(self.currentTime / self.millisecondsPerBeat);
299
+ if (self.beatCallback && oldBeat !== self.currentBeat) // If the movement caused the beat to change, then immediately report it to the client.
300
+ self.doBeatCallback(self.startTime+self.currentTime);
301
+
302
+ if (self.eventCallback && self.currentEvent >= 0 && self.noteTimings[self.currentEvent].type === 'event')
303
+ self.eventCallback(self.noteTimings[self.currentEvent]);
304
+ if (self.lineEndCallback)
305
+ self.lineEndCallback(self.lineEndTimings[self.currentLine], self.noteTimings[self.currentEvent], { line: self.currentLine, endTimings: self.lineEndTimings });
306
+
307
+ self.joggerTimer = setTimeout(self.animationJogger, JOGGING_INTERVAL);
190
308
  };
191
309
  };
192
310
 
@@ -197,8 +315,8 @@ function getLineEndTimings(timings, anticipation) {
197
315
  var lastTop = null;
198
316
  for (var i = 0; i < timings.length; i++) {
199
317
  var timing = timings[i];
200
- if (timing.top !== lastTop) {
201
- callbackTimes.push({ milliseconds: timing.milliseconds - anticipation, top: timing.top, bottom: timing.top+timing.height });
318
+ if (timing.type !== 'end' && timing.top !== lastTop) {
319
+ callbackTimes.push({ measureNumber: timing.measureNumber, milliseconds: timing.milliseconds-anticipation, top: timing.top, bottom: timing.top+timing.height });
202
320
  lastTop = timing.top;
203
321
  }
204
322
  }
@@ -1,24 +1,9 @@
1
1
  // abc_tunebook.js: splits a string representing ABC Music Notation into individual tunes.
2
- // Copyright (C) 2010-2020 Paul Rosen (paul at paulrosen dot net)
3
- //
4
- // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5
- // documentation files (the "Software"), to deal in the Software without restriction, including without limitation
6
- // the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
7
- // to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8
- //
9
- // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10
- //
11
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
12
- // BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
13
- // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
14
- // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
15
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
16
2
 
17
- /*global document */
18
- /*global window, ABCJS, console */
19
-
20
- var parseCommon = require('../parse/abc_common');
21
3
  var Parse = require('../parse/abc_parse');
4
+ var bookParser = require('../parse/abc_parse_book');
5
+ var tablatures = require('./abc_tablatures');
6
+
22
7
 
23
8
  var tunebook = {};
24
9
 
@@ -33,52 +18,9 @@ var tunebook = {};
33
18
  };
34
19
 
35
20
  var TuneBook = tunebook.TuneBook = function(book) {
36
- var This = this;
37
- var directives = "";
38
- book = parseCommon.strip(book);
39
- var tunes = book.split("\nX:");
40
- for (var i = 1; i < tunes.length; i++) // Put back the X: that we lost when splitting the tunes.
41
- tunes[i] = "X:" + tunes[i];
42
- // Keep track of the character position each tune starts with.
43
- var pos = 0;
44
- This.tunes = [];
45
- parseCommon.each(tunes, function(tune) {
46
- This.tunes.push({ abc: tune, startPos: pos});
47
- pos += tune.length + 1; // We also lost a newline when splitting, so count that.
48
- });
49
- if (This.tunes.length > 1 && !parseCommon.startsWith(This.tunes[0].abc, 'X:')) { // If there is only one tune, the X: might be missing, otherwise assume the top of the file is "intertune"
50
- // There could be file-wide directives in this, if so, we need to insert it into each tune. We can probably get away with
51
- // just looking for file-wide directives here (before the first tune) and inserting them at the bottom of each tune, since
52
- // the tune is parsed all at once. The directives will be seen before the engraver begins processing.
53
- var dir = This.tunes.shift();
54
- var arrDir = dir.abc.split('\n');
55
- parseCommon.each(arrDir, function(line) {
56
- if (parseCommon.startsWith(line, '%%'))
57
- directives += line + '\n';
58
- });
59
- }
60
- This.header = directives;
61
-
62
- // Now, the tune ends at a blank line, so truncate it if needed. There may be "intertune" stuff.
63
- parseCommon.each(This.tunes, function(tune) {
64
- var end = tune.abc.indexOf('\n\n');
65
- if (end > 0)
66
- tune.abc = tune.abc.substring(0, end);
67
- tune.pure = tune.abc;
68
- tune.abc = directives + tune.abc;
69
-
70
- // for the user's convenience, parse and store the title separately. The title is between the first T: and the next \n
71
- var title = tune.pure.split("T:");
72
- if (title.length > 1) {
73
- title = title[1].split("\n");
74
- tune.title = title[0].replace(/^\s+|\s+$/g, '');
75
- } else
76
- tune.title = "";
77
-
78
- // for the user's convenience, parse and store the id separately. The id is between the first X: and the next \n
79
- var id = tune.pure.substring(2, tune.pure.indexOf("\n"));
80
- tune.id = id.replace(/^\s+|\s+$/g, '');
81
- });
21
+ var parsed = bookParser(book);
22
+ this.header = parsed.header;
23
+ this.tunes = parsed.tunes;
82
24
  };
83
25
 
84
26
  TuneBook.prototype.getTuneById = function(id) {
@@ -98,7 +40,6 @@ var tunebook = {};
98
40
  };
99
41
 
100
42
  tunebook.parseOnly = function(abc, params) {
101
- var tunes = [];
102
43
  var numTunes = tunebook.numberOfTunes(abc);
103
44
 
104
45
  // this just needs to be passed in because this tells the engine how many tunes to process.
@@ -142,16 +83,26 @@ var tunebook = {};
142
83
  if (currentTune >= 0 && currentTune < book.tunes.length) {
143
84
  abcParser.parse(book.tunes[currentTune].abc, params, book.tunes[currentTune].startPos - book.header.length);
144
85
  var tune = abcParser.getTune();
86
+ //
87
+ // Init tablatures plugins
88
+ //
89
+ if (params.tablature) {
90
+ tablatures.init();
91
+ tune.tablatures = tablatures.preparePlugins(tune, currentTune, params);
92
+ }
93
+ var warnings = abcParser.getWarnings();
94
+ if (warnings)
95
+ tune.warnings = warnings;
145
96
  var override = callback(div, tune, i, book.tunes[currentTune].abc);
146
97
  ret.push(override ? override : tune);
147
98
  } else {
148
- if (div.hasOwnProperty('innerHTML'))
99
+ if (div['innerHTML'])
149
100
  div.innerHTML = "";
150
101
  }
151
102
  }
152
103
  currentTune++;
153
104
  }
154
- return ret;
105
+ return ret;
155
106
  };
156
107
 
157
108
  function flattenTune(tuneObj) {