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.
- package/.github/workflows/tests.yml +29 -0
- package/CODE_OF_CONDUCT.md +76 -0
- package/CONTRIBUTING.md +1 -0
- package/LICENSE.md +1 -1
- package/README.md +92 -3
- package/RELEASE.md +957 -1
- package/abcjs-audio.css +14 -5
- package/dist/.gitignore +1 -2
- package/dist/abcjs-basic-min.js +3 -0
- package/dist/abcjs-basic-min.js.LICENSE +23 -0
- package/dist/abcjs-basic.js +28231 -0
- package/dist/abcjs-basic.js.map +1 -0
- package/dist/abcjs-plugin-min.js +3 -0
- package/dist/abcjs-plugin-min.js.LICENSE +23 -0
- package/dist/report-basic.html +37 -0
- package/dist/report-before-glyph-compress.html +37 -0
- package/dist/report-brown-ts-target-es5.html +37 -0
- package/dist/report-dev-orig-no-babel.html +37 -0
- package/dist/report-synth.html +37 -0
- package/docker-build.sh +1 -0
- package/glyphs.json +1 -0
- package/index.js +27 -2
- package/{static-wrappers/license.js → license.js} +1 -1
- package/package.json +26 -29
- package/{src/plugin/abc_plugin.js → plugin.js} +31 -19
- package/src/api/abc_animation.js +1 -17
- package/src/api/abc_tablatures.js +144 -0
- package/src/api/abc_timing_callbacks.js +234 -116
- package/src/api/abc_tunebook.js +18 -67
- package/src/api/abc_tunebook_svg.js +38 -46
- package/src/data/abc_tune.js +232 -972
- package/src/data/deline-tune.js +199 -0
- package/src/edit/abc_editarea.js +112 -0
- package/src/edit/abc_editor.js +95 -221
- package/src/midi/abc_midi_create.js +48 -50
- package/src/parse/abc_common.js +0 -14
- package/src/parse/abc_parse.js +167 -1321
- package/src/parse/abc_parse_book.js +62 -0
- package/src/parse/abc_parse_directive.js +164 -41
- package/src/parse/abc_parse_header.js +116 -145
- package/src/parse/abc_parse_key_voice.js +26 -20
- package/src/parse/abc_parse_music.js +1337 -0
- package/src/parse/abc_tokenizer.js +21 -15
- package/src/parse/abc_transpose.js +3 -15
- package/src/parse/tune-builder.js +896 -0
- package/src/parse/wrap_lines.js +205 -453
- package/src/synth/abc_midi_flattener.js +1292 -0
- package/src/{midi → synth}/abc_midi_renderer.js +44 -17
- package/src/synth/abc_midi_sequencer.js +648 -0
- package/src/synth/active-audio-context.js +3 -14
- package/src/synth/cents-to-factor.js +10 -0
- package/src/synth/create-note-map.js +21 -32
- package/src/synth/create-synth-control.js +20 -103
- package/src/synth/create-synth.js +185 -77
- package/src/synth/download-buffer.js +7 -21
- package/src/synth/get-midi-file.js +13 -20
- package/src/synth/images/{loading.svg → loading.svg.js} +4 -0
- package/src/synth/images/loop.svg.js +65 -0
- package/src/synth/images/pause.svg.js +10 -0
- package/src/synth/images/play.svg.js +9 -0
- package/src/synth/images/{reset.svg → reset.svg.js} +5 -1
- package/src/synth/instrument-index-to-name.js +1 -16
- package/src/synth/load-note.js +37 -76
- package/src/synth/pitch-to-note-name.js +0 -15
- package/src/synth/pitches-to-perc.js +64 -0
- package/src/synth/place-note.js +78 -68
- package/src/synth/play-event.js +17 -18
- package/src/synth/register-audio-context.js +11 -23
- package/src/synth/sounds-cache.js +0 -15
- package/src/synth/supports-audio.js +9 -23
- package/src/synth/synth-controller.js +80 -49
- package/src/synth/synth-sequence.js +20 -34
- package/src/tablatures/instruments/guitar/guitar-fonts.js +19 -0
- package/src/tablatures/instruments/guitar/guitar-patterns.js +23 -0
- package/src/tablatures/instruments/guitar/tab-guitar.js +50 -0
- package/src/tablatures/instruments/string-patterns.js +277 -0
- package/src/tablatures/instruments/string-tablature.js +56 -0
- package/src/tablatures/instruments/tab-note.js +282 -0
- package/src/tablatures/instruments/tab-notes.js +41 -0
- package/src/tablatures/instruments/violin/tab-violin.js +47 -0
- package/src/tablatures/instruments/violin/violin-fonts.js +19 -0
- package/src/tablatures/instruments/violin/violin-patterns.js +23 -0
- package/src/tablatures/tab-absolute-elements.js +310 -0
- package/src/tablatures/tab-common.js +29 -0
- package/src/tablatures/tab-renderer.js +243 -0
- package/src/tablatures/transposer.js +110 -0
- package/src/test/abc_midi_lint.js +5 -22
- package/src/test/abc_midi_sequencer_lint.js +11 -14
- package/src/test/abc_parser_lint.js +136 -32
- package/src/test/abc_vertical_lint.js +94 -32
- package/src/test/rendering-lint.js +38 -5
- package/src/write/abc_absolute_element.js +112 -120
- package/src/write/abc_abstract_engraver.js +102 -253
- package/src/write/abc_beam_element.js +30 -290
- package/src/write/abc_brace_element.js +12 -121
- package/src/write/abc_create_clef.js +21 -32
- package/src/write/abc_create_key_signature.js +8 -26
- package/src/write/abc_create_note_head.js +107 -0
- package/src/write/abc_create_time_signature.js +2 -21
- package/src/write/abc_crescendo_element.js +3 -50
- package/src/write/abc_decoration.js +7 -30
- package/src/write/abc_dynamic_decoration.js +3 -37
- package/src/write/abc_ending_element.js +1 -57
- package/src/write/abc_engraver_controller.js +111 -234
- package/src/write/abc_glyphs.js +9 -19
- package/src/write/abc_relative_element.js +57 -97
- package/src/write/abc_renderer.js +10 -832
- package/src/write/abc_spacing.js +0 -15
- package/src/write/abc_staff_group_element.js +14 -349
- package/src/write/abc_tempo_element.js +9 -117
- package/src/write/abc_tie_element.js +5 -68
- package/src/write/abc_triplet_element.js +6 -124
- package/src/write/abc_voice_element.js +7 -222
- package/src/write/add-chord.js +103 -0
- package/src/write/add-text-if.js +33 -0
- package/src/write/bottom-text.js +79 -0
- package/src/write/calcHeight.js +17 -0
- package/src/write/classes.js +100 -0
- package/src/write/draw/absolute.js +68 -0
- package/src/write/draw/beam.js +56 -0
- package/src/write/draw/brace.js +106 -0
- package/src/write/draw/crescendo.js +38 -0
- package/src/write/draw/debug-box.js +8 -0
- package/src/write/draw/draw.js +56 -0
- package/src/write/draw/dynamics.js +20 -0
- package/src/write/draw/ending.js +46 -0
- package/src/write/draw/group-elements.js +66 -0
- package/src/write/draw/horizontal-line.js +25 -0
- package/src/write/draw/non-music.js +50 -0
- package/src/write/draw/print-line.js +24 -0
- package/src/write/draw/print-path.js +7 -0
- package/src/write/draw/print-stem.js +30 -0
- package/src/write/draw/print-symbol.js +59 -0
- package/src/write/draw/print-vertical-line.js +18 -0
- package/src/write/draw/relative.js +77 -0
- package/src/write/draw/round-number.js +5 -0
- package/src/write/draw/selectables.js +59 -0
- package/src/write/draw/separator.js +16 -0
- package/src/write/draw/set-paper-size.js +45 -0
- package/src/write/{sprintf.js → draw/sprintf.js} +0 -0
- package/src/write/draw/staff-group.js +226 -0
- package/src/write/draw/staff-line.js +9 -0
- package/src/write/draw/staff.js +33 -0
- package/src/write/draw/tab-line.js +40 -0
- package/src/write/draw/tempo.js +45 -0
- package/src/write/draw/text.js +71 -0
- package/src/write/draw/tie.js +97 -0
- package/src/write/draw/triplet.js +46 -0
- package/src/write/draw/voice.js +102 -0
- package/src/write/format-jazz-chord.js +15 -0
- package/src/write/free-text.js +41 -0
- package/src/write/get-font-and-attr.js +37 -0
- package/src/write/get-text-size.js +56 -0
- package/src/write/highlight.js +11 -0
- package/src/write/layout/VoiceElements.js +121 -0
- package/src/write/layout/beam.js +213 -0
- package/src/write/layout/get-left-edge-of-staff.js +56 -0
- package/src/write/layout/getBarYAt.js +6 -0
- package/src/write/layout/layout.js +94 -0
- package/src/write/layout/setUpperAndLowerElements.js +232 -0
- package/src/write/layout/staffGroup.js +146 -0
- package/src/write/layout/triplet.js +75 -0
- package/src/write/layout/voice.js +137 -0
- package/src/write/selection.js +188 -70
- package/src/write/separator.js +10 -0
- package/src/write/set-class.js +21 -0
- package/src/write/subtitle.js +12 -0
- package/src/write/svg.js +95 -43
- package/src/write/top-text.js +54 -0
- package/src/write/unhighlight.js +11 -0
- package/temp.txt +17 -0
- package/test.js +27 -64
- package/types/index.d.ts +1095 -0
- package/version.js +1 -1
- package/.babelrc +0 -5
- package/.eslintrc +0 -3
- package/.gitmodules +0 -3
- package/abcjs-midi.css +0 -166
- package/build-utils/loadPresets.js +0 -14
- package/build-utils/presets/webpack.analyze.js +0 -6
- package/build-utils/presets/webpack.optimize.js +0 -30
- package/build-utils/webpack.development.js +0 -14
- package/build-utils/webpack.production.js +0 -35
- package/deploy-docs.sh +0 -25
- package/docs/README.md +0 -33
- package/fix-versions.sh +0 -23
- package/mei.js +0 -43
- package/midi.js +0 -62
- package/src/api/abc_tunebook_midi.js +0 -116
- package/src/midi/abc_midi_controls.js +0 -701
- package/src/midi/abc_midi_flattener.js +0 -1119
- package/src/midi/abc_midi_js_preparer.js +0 -243
- package/src/midi/abc_midi_sequencer.js +0 -401
- package/src/midi/abc_midi_ui_generator.js +0 -86
- package/src/plugin/abc_plugin_midi.js +0 -220
- package/src/synth/images/loop.svg +0 -61
- package/src/synth/images/pause.svg +0 -6
- package/src/synth/images/play.svg +0 -5
- package/src/transform/abc2abc_write.js +0 -395
- package/static-wrappers/basic.js +0 -2
- package/static-wrappers/midi.js +0 -2
- package/static-wrappers/plugin-midi.js +0 -6
- package/static-wrappers/plugin.js +0 -6
- 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.
|
|
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
|
|
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.
|
|
109
|
-
|
|
110
|
-
self.
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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.
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
133
|
-
|
|
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.
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
144
|
-
|
|
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
|
-
|
|
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
|
-
|
|
211
|
+
if (offsetPercent === undefined)
|
|
212
|
+
self.justUnpaused = true;
|
|
153
213
|
}
|
|
154
214
|
if (offsetPercent) {
|
|
155
|
-
self.setProgress(offsetPercent);
|
|
156
|
-
} else {
|
|
157
|
-
|
|
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.
|
|
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(
|
|
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
|
-
|
|
181
|
-
|
|
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.
|
|
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
|
|
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
|
}
|
package/src/api/abc_tunebook.js
CHANGED
|
@@ -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
|
|
37
|
-
|
|
38
|
-
|
|
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
|
|
99
|
+
if (div['innerHTML'])
|
|
149
100
|
div.innerHTML = "";
|
|
150
101
|
}
|
|
151
102
|
}
|
|
152
103
|
currentTune++;
|
|
153
104
|
}
|
|
154
|
-
|
|
105
|
+
return ret;
|
|
155
106
|
};
|
|
156
107
|
|
|
157
108
|
function flattenTune(tuneObj) {
|