abcjs 6.0.0-beta.9 → 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 (205) 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 +939 -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 +216 -117
  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 +8 -18
  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/Dockerfile +0 -8
  179. package/abcjs-midi.css +0 -166
  180. package/build-utils/loadPresets.js +0 -14
  181. package/build-utils/presets/webpack.analyze.js +0 -6
  182. package/build-utils/presets/webpack.optimize.js +0 -30
  183. package/build-utils/webpack.development.js +0 -14
  184. package/build-utils/webpack.production.js +0 -35
  185. package/deploy-docs.sh +0 -25
  186. package/docker-compose.yml +0 -13
  187. package/docs/README.md +0 -33
  188. package/fix-versions.sh +0 -23
  189. package/midi.js +0 -62
  190. package/src/api/abc_tunebook_midi.js +0 -116
  191. package/src/midi/abc_midi_controls.js +0 -701
  192. package/src/midi/abc_midi_flattener.js +0 -1119
  193. package/src/midi/abc_midi_js_preparer.js +0 -243
  194. package/src/midi/abc_midi_sequencer.js +0 -401
  195. package/src/midi/abc_midi_ui_generator.js +0 -86
  196. package/src/plugin/abc_plugin_midi.js +0 -220
  197. package/src/synth/images/loop.svg +0 -61
  198. package/src/synth/images/pause.svg +0 -6
  199. package/src/synth/images/play.svg +0 -5
  200. package/src/transform/abc2abc_write.js +0 -395
  201. package/static-wrappers/basic.js +0 -2
  202. package/static-wrappers/midi.js +0 -2
  203. package/static-wrappers/plugin-midi.js +0 -6
  204. package/static-wrappers/plugin.js +0 -6
  205. 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 = {};
@@ -30,124 +15,178 @@ var TimingCallbacks = function(target, params) {
30
15
  self.joggerTimer = null;
31
16
 
32
17
  self.replaceTarget = function(newTarget) {
33
- newTarget.setTiming(self.qpm, self.extraMeasuresAtBeginning);
18
+ self.noteTimings = newTarget.setTiming(self.qpm, self.extraMeasuresAtBeginning);
34
19
  if (newTarget.noteTimings.length === 0)
35
20
  newTarget.setTiming(0,0);
36
21
  if (self.lineEndCallback) {
37
22
  self.lineEndTimings = getLineEndTimings(newTarget.noteTimings, self.lineEndAnticipation);
38
23
  }
39
- 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);
40
42
  };
41
43
 
42
44
  self.replaceTarget(target);
43
- if (self.noteTimings.length === 0)
44
- return;
45
-
46
- // noteTimings contains an array of events sorted by time. Events that happen at the same time are in the same element of the array.
47
- self.noteTimings = target.noteTimings;
48
- self.millisecondsPerBeat = 1000 / (self.qpm / 60) / self.beatSubdivisions;
49
- self.lastMoment = self.noteTimings[self.noteTimings.length-1].milliseconds;
50
- self.totalBeats = Math.round(self.lastMoment / self.millisecondsPerBeat);
51
-
52
- self.startTime = null;
53
- self.currentBeat = 0;
54
- self.currentEvent = 0;
55
- self.isPaused = false;
56
- self.isRunning = false;
57
- self.pausedTime = null;
58
- self.justUnpaused = false;
59
-
60
- self.newSeekPercent = 0;
61
- self.justSeeked = false;
62
-
63
- function setCurrentLocation(timestamp) {
64
- // First find the relative amount to move: that is, the difference between the current percentage and the passed in percent.
65
- var currentPercent = (timestamp - self.startTime) / self.lastMoment;
66
- var percentDifference = currentPercent - self.newSeekPercent;
67
- var timeDifference = self.lastMoment * percentDifference;
68
- self.startTime = self.startTime + timeDifference;
69
-
70
- var currentTime = timestamp - self.startTime;
71
- currentTime += 16; // Add a little slop because this function isn't called exactly.
72
45
 
73
- var oldBeat = self.currentBeat;
74
- self.currentBeat = Math.floor(currentTime / self.millisecondsPerBeat);
75
- if (self.beatCallback && oldBeat !== self.currentBeat) // If the movement caused the beat to change, then immediately report it to the client.
76
- self.beatCallback(self.currentBeat / self.beatSubdivisions, self.totalBeats / self.beatSubdivisions, self.lastMoment);
77
-
78
- var lineStart = 0;
79
- self.currentEvent = 0;
80
- while (self.noteTimings.length > self.currentEvent && self.noteTimings[self.currentEvent].milliseconds < currentTime) {
81
- self.currentEvent++;
82
- if (self.lineEndCallback && self.lineEndTimings['e'+self.currentEvent])
83
- lineStart = self.currentEvent;
84
- }
85
- if (self.eventCallback && self.currentEvent > 0 && self.noteTimings[self.currentEvent - 1].type === 'event')
86
- self.eventCallback(self.noteTimings[self.currentEvent - 1]);
87
- if (self.lineEndCallback)
88
- self.lineEndCallback(self.lineEndTimings['e'+lineStart])
89
-
90
- // console.log("currentPercent="+currentPercent+
91
- // " newSeekPercent="+self.newSeekPercent+
92
- // " percentDifference="+percentDifference+
93
- // " timeDifference=",timeDifference+
94
- // " currentBeat="+self.currentBeat+
95
- // " currentEvent="+self.currentEvent);
96
- }
97
-
98
- self.lastTimestamp = 0;
99
46
  self.doTiming = function (timestamp) {
100
47
  // This is called 60 times a second, that is, every 16 msecs.
101
48
  //console.log("doTiming", timestamp, timestamp-self.lastTimestamp);
102
49
  if (self.lastTimestamp === timestamp)
103
- 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.
104
51
  self.lastTimestamp = timestamp;
105
52
  if (!self.startTime) {
106
53
  self.startTime = timestamp;
107
- } else if (self.justUnpaused) {
108
- // Add the amount we paused to the start time to get the right place.
109
- var timePaused = (timestamp - self.pausedTime);
110
- self.startTime += timePaused;
111
54
  }
112
- self.justUnpaused = false;
113
55
 
114
- if (self.justSeeked) {
115
- setCurrentLocation(timestamp);
116
- self.justSeeked = false;
117
- }
118
- if (self.isPaused) {
119
- self.pausedTime = timestamp;
120
- } else if (self.isRunning) {
121
- var currentTime = timestamp - self.startTime;
122
- currentTime += 16; // Add a little slop because this function isn't called exactly.
123
- while (self.noteTimings.length > self.currentEvent && self.noteTimings[self.currentEvent].milliseconds < currentTime) {
124
- 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.
125
62
  self.eventCallback(self.noteTimings[self.currentEvent]);
63
+ if (thisStartTime !== self.startTime) {
64
+ self.currentTime = timestamp - self.startTime;
65
+ }
66
+ }
126
67
  self.currentEvent++;
127
- if (self.lineEndCallback && self.lineEndTimings['e'+self.currentEvent])
128
- self.lineEndCallback(self.lineEndTimings['e'+self.currentEvent]);
129
68
  }
130
- 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) {
131
75
  requestAnimationFrame(self.doTiming);
132
- if (self.currentBeat * self.millisecondsPerBeat < currentTime) {
133
- if (self.beatCallback)
134
- self.beatCallback(self.currentBeat / self.beatSubdivisions, self.totalBeats / self.beatSubdivisions, self.lastMoment);
135
- self.currentBeat++;
76
+ if (self.currentBeat * self.millisecondsPerBeat < self.currentTime) {
77
+ var ret = self.doBeatCallback(timestamp);
78
+ if (ret !== null)
79
+ self.currentTime = ret;
136
80
  }
137
81
  } else if (self.currentBeat <= self.totalBeats) {
138
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.
139
83
  if (self.beatCallback) {
140
- self.beatCallback(self.currentBeat / self.beatSubdivisions, self.totalBeats / self.beatSubdivisions, self.lastMoment);
141
- self.currentBeat++;
84
+ var ret2 = self.doBeatCallback(timestamp);
85
+ if (ret2 !== null)
86
+ self.currentTime = ret2;
142
87
  requestAnimationFrame(self.doTiming);
143
88
  }
144
89
  }
145
90
 
146
- if (currentTime >= self.lastMoment && self.eventCallback)
147
- self.eventCallback(null);
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
+ }
148
103
  }
149
104
  };
150
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];
136
+ }
137
+
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++;
186
+ }
187
+ return null;
188
+ };
189
+
151
190
  // In general music doesn't need a timer at 60 fps because notes don't happen that fast.
152
191
  // For instance, at 120 beats per minute, a sixteenth note takes 125ms. So just as a
153
192
  // compromise value between performance and jank this is set about half that.
@@ -165,46 +204,106 @@ var TimingCallbacks = function(target, params) {
165
204
  }
166
205
  };
167
206
 
168
- self.start = function(offsetPercent) {
207
+ self.start = function(offsetPercent, units) {
169
208
  self.isRunning = true;
170
209
  if (self.isPaused) {
171
210
  self.isPaused = false;
172
- self.justUnpaused = true;
211
+ if (offsetPercent === undefined)
212
+ self.justUnpaused = true;
173
213
  }
174
214
  if (offsetPercent) {
175
- self.setProgress(offsetPercent);
176
- } else {
177
- requestAnimationFrame(self.doTiming);
178
- self.joggerTimer = setTimeout(self.animationJogger, JOGGING_INTERVAL);
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;
179
224
  }
225
+ requestAnimationFrame(self.doTiming);
226
+ self.joggerTimer = setTimeout(self.animationJogger, JOGGING_INTERVAL);
180
227
  };
181
228
  self.pause = function() {
182
229
  self.isPaused = true;
230
+ var now = performance.now();
231
+ self.pausedPercent = (now - self.startTime) / self.lastMoment;
183
232
  self.isRunning = false;
184
233
  if (self.joggerTimer) {
185
234
  clearTimeout(self.joggerTimer);
186
235
  self.joggerTimer = null;
187
236
  }
188
237
  };
238
+ self.currentMillisecond = function() {
239
+ return self.currentTime;
240
+ };
189
241
  self.reset = function() {
190
242
  self.currentBeat = 0;
191
243
  self.currentEvent = 0;
244
+ self.currentLine = 0;
192
245
  self.startTime = null;
193
- self.pausedTime = null;
246
+ self.pausedPercent = null;
194
247
  };
195
248
  self.stop = function() {
196
249
  self.pause();
197
250
  self.reset();
198
251
  };
199
- self.setProgress = function(percent) {
200
- // this is passed a value between 0 and 1.
252
+ self.setProgress = function(position, units) {
201
253
  // the effect of this function is to move startTime so that the callbacks happen correctly for the new seek.
202
- if (percent < 0) percent = 0;
203
- 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
+ }
289
+
290
+ if (self.lineEndCallback) {
291
+ self.currentLine = 0;
292
+ while (self.lineEndTimings.length > self.currentLine && self.lineEndTimings[self.currentLine].milliseconds + self.lineEndAnticipation < self.currentTime) {
293
+ self.currentLine++;
294
+ }
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 });
204
306
 
205
- self.newSeekPercent = percent;
206
- self.justSeeked = true;
207
- self.doTiming(performance.now());
208
307
  self.joggerTimer = setTimeout(self.animationJogger, JOGGING_INTERVAL);
209
308
  };
210
309
  };
@@ -212,12 +311,12 @@ var TimingCallbacks = function(target, params) {
212
311
  function getLineEndTimings(timings, anticipation) {
213
312
  // Returns an array of milliseconds to call the lineEndCallback.
214
313
  // This figures out the timing of the beginning of each line and subtracts the anticipation from it.
215
- var callbackTimes = {};
314
+ var callbackTimes = [];
216
315
  var lastTop = null;
217
316
  for (var i = 0; i < timings.length; i++) {
218
317
  var timing = timings[i];
219
- if (timing.top !== lastTop) {
220
- callbackTimes['e'+i] = { measureNumber: Math.floor(timing.milliseconds/timing.millisecondsPerMeasure), 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 });
221
320
  lastTop = timing.top;
222
321
  }
223
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) {