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
package/src/data/abc_tune.js
CHANGED
|
@@ -1,22 +1,10 @@
|
|
|
1
1
|
// abc_tune.js: a computer usable internal structure representing one tune.
|
|
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
3
|
var parseCommon = require('../parse/abc_common');
|
|
18
|
-
var parseKeyVoice = require('../parse/abc_parse_key_voice');
|
|
19
4
|
var spacing = require('../write/abc_spacing');
|
|
5
|
+
var sequence = require('../synth/abc_midi_sequencer');
|
|
6
|
+
var flatten = require('../synth/abc_midi_flattener');
|
|
7
|
+
var delineTune = require("./deline-tune");
|
|
20
8
|
|
|
21
9
|
/**
|
|
22
10
|
* This is the data for a single ABC tune. It is created and populated by the window.ABCJS.parse.Parse class.
|
|
@@ -24,6 +12,48 @@ var spacing = require('../write/abc_spacing');
|
|
|
24
12
|
* @alternateClassName ABCJS.Tune
|
|
25
13
|
*/
|
|
26
14
|
var Tune = function() {
|
|
15
|
+
this.reset = function () {
|
|
16
|
+
this.version = "1.1.0";
|
|
17
|
+
this.media = "screen";
|
|
18
|
+
this.metaText = {};
|
|
19
|
+
this.metaTextInfo = {};
|
|
20
|
+
this.formatting = {};
|
|
21
|
+
this.lines = [];
|
|
22
|
+
this.staffNum = 0;
|
|
23
|
+
this.voiceNum = 0;
|
|
24
|
+
this.lineNum = 0;
|
|
25
|
+
this.runningFonts = {};
|
|
26
|
+
delete this.visualTranspose;
|
|
27
|
+
};
|
|
28
|
+
this.reset();
|
|
29
|
+
|
|
30
|
+
function copy(dest, src, prop, attrs) {
|
|
31
|
+
for (var i = 0; i < attrs.length; i++)
|
|
32
|
+
dest[prop][attrs[i]] = src[prop][attrs[i]];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
this.copyTopInfo = function(src) {
|
|
36
|
+
var attrs = ['tempo', 'title', 'header', 'rhythm', 'origin', 'composer', 'author', 'partOrder'];
|
|
37
|
+
copy(this, src, "metaText", attrs);
|
|
38
|
+
copy(this, src, "metaTextInfo", attrs);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
this.copyBottomInfo = function(src) {
|
|
42
|
+
var attrs = ['unalignedWords',
|
|
43
|
+
'book',
|
|
44
|
+
'source',
|
|
45
|
+
'discography',
|
|
46
|
+
'notes',
|
|
47
|
+
'transcription',
|
|
48
|
+
'history',
|
|
49
|
+
'abc-copyright',
|
|
50
|
+
'abc-creator',
|
|
51
|
+
'abc-edited-by',
|
|
52
|
+
'footer']
|
|
53
|
+
copy(this, src, "metaText", attrs);
|
|
54
|
+
copy(this, src, "metaTextInfo", attrs);
|
|
55
|
+
};
|
|
56
|
+
|
|
27
57
|
// The structure consists of a hash with the following two items:
|
|
28
58
|
// metaText: a hash of {key, value}, where key is one of: title, author, rhythm, source, transcription, unalignedWords, etc...
|
|
29
59
|
// tempo: { noteLength: number (e.g. .125), bpm: number }
|
|
@@ -58,69 +88,49 @@ var Tune = function() {
|
|
|
58
88
|
// if specified, { num: 99, den: 99 }
|
|
59
89
|
|
|
60
90
|
this.getBeatLength = function() {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
91
|
+
// This returns a fraction: for instance 1/4 for a quarter
|
|
92
|
+
// There are two types of meters: compound and regular. Compound meter has 3 beats counted as one.
|
|
93
|
+
var meter = this.getMeterFraction();
|
|
94
|
+
var multiplier = 1;
|
|
95
|
+
if (meter.num === 6 || meter.num === 9 || meter.num === 12)
|
|
96
|
+
multiplier = 3;
|
|
97
|
+
else if (meter.num === 3 && meter.den === 8)
|
|
98
|
+
multiplier = 3;
|
|
99
|
+
|
|
100
|
+
return multiplier / meter.den;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
function computePickupLength(lines, barLength) {
|
|
104
|
+
var pickupLength = 0;
|
|
105
|
+
for (var i = 0; i < lines.length; i++) {
|
|
106
|
+
if (lines[i].staff) {
|
|
107
|
+
for (var j = 0; j < lines[i].staff.length; j++) {
|
|
108
|
+
for (var v = 0; v < lines[i].staff[j].voices.length; v++) {
|
|
109
|
+
var voice = lines[i].staff[j].voices[v];
|
|
110
|
+
var tripletMultiplier = 1;
|
|
111
|
+
for (var el = 0; el < voice.length; el++) {
|
|
112
|
+
var isSpacer = voice[el].rest && voice[el].rest.type === "spacer";
|
|
113
|
+
if (voice[el].startTriplet)
|
|
114
|
+
tripletMultiplier = voice[el].tripletMultiplier;
|
|
115
|
+
if (voice[el].duration && !isSpacer && voice[el].el_type !== "tempo")
|
|
116
|
+
pickupLength += voice[el].duration * tripletMultiplier;
|
|
117
|
+
if (voice[el].endTriplet)
|
|
118
|
+
tripletMultiplier = 1;
|
|
119
|
+
if (pickupLength >= barLength)
|
|
120
|
+
pickupLength -= barLength;
|
|
121
|
+
if (voice[el].el_type === 'bar')
|
|
122
|
+
return pickupLength;
|
|
83
123
|
}
|
|
84
124
|
}
|
|
85
125
|
}
|
|
86
126
|
}
|
|
87
127
|
}
|
|
88
|
-
return 1/4; // No meter was specified, so use this default
|
|
89
|
-
};
|
|
90
128
|
|
|
129
|
+
return pickupLength;
|
|
130
|
+
}
|
|
91
131
|
this.getPickupLength = function() {
|
|
92
132
|
var barLength = this.getBarLength();
|
|
93
|
-
var
|
|
94
|
-
var pickupLength = 0;
|
|
95
|
-
for (var i = 0; i < this.lines.length; i++) {
|
|
96
|
-
if (this.lines[i].staff) {
|
|
97
|
-
for (var j = 0; j < this.lines[i].staff.length; j++) {
|
|
98
|
-
for (var v = 0; v < this.lines[i].staff[j].voices.length; v++) {
|
|
99
|
-
var voice = this.lines[i].staff[j].voices[v];
|
|
100
|
-
var hasNote = false;
|
|
101
|
-
var tripletMultiplier = 1;
|
|
102
|
-
for (var el = 0; el < voice.length; el++) {
|
|
103
|
-
var isSpacer = voice[el].rest && voice[el].rest.type === "spacer";
|
|
104
|
-
if (voice[el].startTriplet)
|
|
105
|
-
tripletMultiplier = voice[el].tripletMultiplier;
|
|
106
|
-
if (voice[el].duration && !isSpacer)
|
|
107
|
-
pickupLength += voice[el].duration * tripletMultiplier;
|
|
108
|
-
if (voice[el].endTriplet)
|
|
109
|
-
tripletMultiplier = 1;
|
|
110
|
-
if (pickupLength >= barLength)
|
|
111
|
-
pickupLength -= barLength;
|
|
112
|
-
if (voice[el].el_type === 'bar')
|
|
113
|
-
return pickupLength;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
return pickupLength;
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
var pickupLength = computePickupLength.apply(this);
|
|
133
|
+
var pickupLength = computePickupLength(this.lines, barLength);
|
|
124
134
|
|
|
125
135
|
// If computed pickup length is very close to 0 or the bar length, we assume
|
|
126
136
|
// that we actually have a full bar and hence no pickup.
|
|
@@ -132,6 +142,14 @@ var Tune = function() {
|
|
|
132
142
|
return meter.num / meter.den;
|
|
133
143
|
};
|
|
134
144
|
|
|
145
|
+
this.getTotalTime = function() {
|
|
146
|
+
return this.totalTime;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
this.getTotalBeats = function() {
|
|
150
|
+
return this.totalBeats;
|
|
151
|
+
};
|
|
152
|
+
|
|
135
153
|
this.millisecondsPerMeasure = function(bpmOverride) {
|
|
136
154
|
var bpm;
|
|
137
155
|
if (bpmOverride) {
|
|
@@ -150,831 +168,9 @@ var Tune = function() {
|
|
|
150
168
|
};
|
|
151
169
|
|
|
152
170
|
this.getBeatsPerMeasure = function() {
|
|
153
|
-
var
|
|
154
|
-
var
|
|
155
|
-
|
|
156
|
-
beatsPerMeasure = meter.num / 3;
|
|
157
|
-
} else {
|
|
158
|
-
beatsPerMeasure = meter.num;
|
|
159
|
-
}
|
|
160
|
-
if (beatsPerMeasure <= 0) // This probably won't happen in any normal case - but it is possible that the meter could be set to something nonsensical.
|
|
161
|
-
beatsPerMeasure = 1;
|
|
162
|
-
return beatsPerMeasure;
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
this.reset = function () {
|
|
166
|
-
this.version = "1.0.1";
|
|
167
|
-
this.media = "screen";
|
|
168
|
-
this.metaText = {};
|
|
169
|
-
this.formatting = {};
|
|
170
|
-
this.lines = [];
|
|
171
|
-
this.staffNum = 0;
|
|
172
|
-
this.voiceNum = 0;
|
|
173
|
-
this.lineNum = 0;
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
this.resolveOverlays = function() {
|
|
177
|
-
var madeChanges = false;
|
|
178
|
-
for (var i = 0; i < this.lines.length; i++) {
|
|
179
|
-
var line = this.lines[i];
|
|
180
|
-
if (line.staff) {
|
|
181
|
-
for (var j = 0; j < line.staff.length; j++) {
|
|
182
|
-
var staff = line.staff[j];
|
|
183
|
-
var overlayVoice = [];
|
|
184
|
-
for (var k = 0; k < staff.voices.length; k++) {
|
|
185
|
-
var voice = staff.voices[k];
|
|
186
|
-
overlayVoice.push({ hasOverlay: false, voice: [], snip: []});
|
|
187
|
-
var durationThisBar = 0;
|
|
188
|
-
var inOverlay = false;
|
|
189
|
-
var snipStart = -1;
|
|
190
|
-
for (var kk = 0; kk < voice.length; kk++) {
|
|
191
|
-
var event = voice[kk];
|
|
192
|
-
if (event.el_type === "overlay" && !inOverlay) {
|
|
193
|
-
madeChanges = true;
|
|
194
|
-
inOverlay = true;
|
|
195
|
-
snipStart = kk;
|
|
196
|
-
overlayVoice[k].hasOverlay = true;
|
|
197
|
-
} else if (event.el_type === "bar") {
|
|
198
|
-
if (inOverlay) {
|
|
199
|
-
// delete the overlay events from this array without messing up this loop.
|
|
200
|
-
inOverlay = false;
|
|
201
|
-
overlayVoice[k].snip.push({ start: snipStart, len: kk - snipStart});
|
|
202
|
-
overlayVoice[k].voice.push(event); // Also end the overlay with the barline.
|
|
203
|
-
} else {
|
|
204
|
-
// This keeps the voices lined up: if the overlay isn't in the first measure then we need a bunch of invisible rests.
|
|
205
|
-
if (durationThisBar > 0)
|
|
206
|
-
overlayVoice[k].voice.push({ el_type: "note", duration: durationThisBar, rest: {type: "invisible"}, startChar: event.startChar, endChar: event.endChar });
|
|
207
|
-
overlayVoice[k].voice.push(event);
|
|
208
|
-
}
|
|
209
|
-
durationThisBar = 0;
|
|
210
|
-
} else if (event.el_type === "note") {
|
|
211
|
-
if (inOverlay) {
|
|
212
|
-
overlayVoice[k].voice.push(event);
|
|
213
|
-
} else {
|
|
214
|
-
durationThisBar += event.duration;
|
|
215
|
-
}
|
|
216
|
-
} else if (event.el_type === "scale" || event.el_type === "stem" || event.el_type === "overlay" || event.el_type === "style" || event.el_type === "transpose") {
|
|
217
|
-
// These types of events are duplicated on the overlay layer.
|
|
218
|
-
overlayVoice[k].voice.push(event);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
if (overlayVoice[k].hasOverlay && overlayVoice[k].snip.length === 0) {
|
|
222
|
-
// there was no closing bar, so we didn't set the snip amount.
|
|
223
|
-
overlayVoice[k].snip.push({ start: snipStart, len: voice.length - snipStart});
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
for (k = 0; k < overlayVoice.length; k++) {
|
|
227
|
-
var ov = overlayVoice[k];
|
|
228
|
-
if (ov.hasOverlay) {
|
|
229
|
-
staff.voices.push(ov.voice);
|
|
230
|
-
for (var kkk = ov.snip.length-1; kkk >= 0; kkk--) {
|
|
231
|
-
var snip = ov.snip[kkk];
|
|
232
|
-
staff.voices[k].splice(snip.start, snip.len);
|
|
233
|
-
}
|
|
234
|
-
// remove ending marks from the overlay voice so they are not repeated
|
|
235
|
-
for (kkk = 0; kkk < staff.voices[staff.voices.length-1].length; kkk++) {
|
|
236
|
-
staff.voices[staff.voices.length-1][kkk] = parseCommon.clone(staff.voices[staff.voices.length-1][kkk]);
|
|
237
|
-
var el = staff.voices[staff.voices.length-1][kkk];
|
|
238
|
-
if (el.el_type === 'bar' && el.startEnding) {
|
|
239
|
-
delete el.startEnding;
|
|
240
|
-
}
|
|
241
|
-
if (el.el_type === 'bar' && el.endEnding)
|
|
242
|
-
delete el.endEnding;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
return madeChanges;
|
|
250
|
-
};
|
|
251
|
-
|
|
252
|
-
function fixTitles(lines) {
|
|
253
|
-
// We might have name and subname defined. We now know what line everything is on, so we can determine which to use.
|
|
254
|
-
var firstMusicLine = true;
|
|
255
|
-
for (var i = 0; i < lines.length; i++) {
|
|
256
|
-
var line = lines[i];
|
|
257
|
-
if (line.staff) {
|
|
258
|
-
for (var j = 0; j < line.staff.length; j++) {
|
|
259
|
-
var staff = line.staff[j];
|
|
260
|
-
if (staff.title) {
|
|
261
|
-
var hasATitle = false;
|
|
262
|
-
for (var k = 0; k < staff.title.length; k++) {
|
|
263
|
-
if (staff.title[k]) {
|
|
264
|
-
staff.title[k] = (firstMusicLine) ? staff.title[k].name : staff.title[k].subname;
|
|
265
|
-
if (staff.title[k])
|
|
266
|
-
hasATitle = true;
|
|
267
|
-
else
|
|
268
|
-
staff.title[k] = '';
|
|
269
|
-
} else
|
|
270
|
-
staff.title[k] = '';
|
|
271
|
-
}
|
|
272
|
-
if (!hasATitle)
|
|
273
|
-
delete staff.title;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
firstMusicLine = false;
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
this.cleanUp = function(defWidth, defLength, barsperstaff, staffnonote, currSlur) {
|
|
282
|
-
this.closeLine(); // Close the last line.
|
|
283
|
-
|
|
284
|
-
// If the tempo was created with a string like "Allegro", then the duration of a beat needs to be set at the last moment, when it is most likely known.
|
|
285
|
-
if (this.metaText.tempo && this.metaText.tempo.bpm && !this.metaText.tempo.duration)
|
|
286
|
-
this.metaText.tempo.duration = [ this.getBeatLength() ];
|
|
287
|
-
|
|
288
|
-
// Remove any blank lines
|
|
289
|
-
var anyDeleted = false;
|
|
290
|
-
var i, s, v;
|
|
291
|
-
for (i = 0; i < this.lines.length; i++) {
|
|
292
|
-
if (this.lines[i].staff !== undefined) {
|
|
293
|
-
var hasAny = false;
|
|
294
|
-
for (s = 0; s < this.lines[i].staff.length; s++) {
|
|
295
|
-
if (this.lines[i].staff[s] === undefined) {
|
|
296
|
-
anyDeleted = true;
|
|
297
|
-
this.lines[i].staff[s] = null;
|
|
298
|
-
//this.lines[i].staff[s] = { voices: []}; // TODO-PER: There was a part missing in the abc music. How should we recover?
|
|
299
|
-
} else {
|
|
300
|
-
for (v = 0; v < this.lines[i].staff[s].voices.length; v++) {
|
|
301
|
-
if (this.lines[i].staff[s].voices[v] === undefined)
|
|
302
|
-
this.lines[i].staff[s].voices[v] = []; // TODO-PER: There was a part missing in the abc music. How should we recover?
|
|
303
|
-
else
|
|
304
|
-
if (this.containsNotes(this.lines[i].staff[s].voices[v])) hasAny = true;
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
if (!hasAny) {
|
|
309
|
-
this.lines[i] = null;
|
|
310
|
-
anyDeleted = true;
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
if (anyDeleted) {
|
|
315
|
-
this.lines = parseCommon.compact(this.lines);
|
|
316
|
-
parseCommon.each(this.lines, function(line) {
|
|
317
|
-
if (line.staff)
|
|
318
|
-
line.staff = parseCommon.compact(line.staff);
|
|
319
|
-
});
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
// if we exceeded the number of bars allowed on a line, then force a new line
|
|
323
|
-
if (barsperstaff) {
|
|
324
|
-
while (wrapMusicLines(this.lines, barsperstaff)) {
|
|
325
|
-
// This will keep wrapping until the end of the piece.
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// If we were passed staffnonote, then we want to get rid of all staffs that contain only rests.
|
|
330
|
-
if (staffnonote) {
|
|
331
|
-
anyDeleted = false;
|
|
332
|
-
for (i = 0; i < this.lines.length; i++) {
|
|
333
|
-
if (this.lines[i].staff !== undefined) {
|
|
334
|
-
for (s = 0; s < this.lines[i].staff.length; s++) {
|
|
335
|
-
var keepThis = false;
|
|
336
|
-
for (v = 0; v < this.lines[i].staff[s].voices.length; v++) {
|
|
337
|
-
if (this.containsNotesStrict(this.lines[i].staff[s].voices[v])) {
|
|
338
|
-
keepThis = true;
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
if (!keepThis) {
|
|
342
|
-
anyDeleted = true;
|
|
343
|
-
this.lines[i].staff[s] = null;
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
if (anyDeleted) {
|
|
349
|
-
parseCommon.each(this.lines, function(line) {
|
|
350
|
-
if (line.staff)
|
|
351
|
-
line.staff = parseCommon.compact(line.staff);
|
|
352
|
-
});
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
fixTitles(this.lines);
|
|
357
|
-
|
|
358
|
-
// Remove the temporary working variables
|
|
359
|
-
for (i = 0; i < this.lines.length; i++) {
|
|
360
|
-
if (this.lines[i].staff) {
|
|
361
|
-
for (s = 0; s < this.lines[i].staff.length; s++)
|
|
362
|
-
delete this.lines[i].staff[s].workingClef;
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
// If there are overlays, create new voices for them.
|
|
367
|
-
while (this.resolveOverlays()) {
|
|
368
|
-
// keep resolving overlays as long as any are found.
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
function cleanUpSlursInLine(line) {
|
|
372
|
-
var x;
|
|
373
|
-
// var lyr = null; // TODO-PER: debugging.
|
|
374
|
-
|
|
375
|
-
var addEndSlur = function(obj, num, chordPos) {
|
|
376
|
-
if (currSlur[chordPos] === undefined) {
|
|
377
|
-
// There isn't an exact match for note position, but we'll take any other open slur.
|
|
378
|
-
for (x = 0; x < currSlur.length; x++) {
|
|
379
|
-
if (currSlur[x] !== undefined) {
|
|
380
|
-
chordPos = x;
|
|
381
|
-
break;
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
if (currSlur[chordPos] === undefined) {
|
|
385
|
-
var offNum = chordPos*100+1;
|
|
386
|
-
parseCommon.each(obj.endSlur, function(x) { if (offNum === x) --offNum; });
|
|
387
|
-
currSlur[chordPos] = [offNum];
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
var slurNum;
|
|
391
|
-
for (var i = 0; i < num; i++) {
|
|
392
|
-
slurNum = currSlur[chordPos].pop();
|
|
393
|
-
obj.endSlur.push(slurNum);
|
|
394
|
-
// lyr.syllable += '<' + slurNum; // TODO-PER: debugging
|
|
395
|
-
}
|
|
396
|
-
if (currSlur[chordPos].length === 0)
|
|
397
|
-
delete currSlur[chordPos];
|
|
398
|
-
return slurNum;
|
|
399
|
-
};
|
|
400
|
-
|
|
401
|
-
var addStartSlur = function(obj, num, chordPos, usedNums) {
|
|
402
|
-
obj.startSlur = [];
|
|
403
|
-
if (currSlur[chordPos] === undefined) {
|
|
404
|
-
currSlur[chordPos] = [];
|
|
405
|
-
}
|
|
406
|
-
var nextNum = chordPos*100+1;
|
|
407
|
-
for (var i = 0; i < num; i++) {
|
|
408
|
-
if (usedNums) {
|
|
409
|
-
parseCommon.each(usedNums, function(x) { if (nextNum === x) ++nextNum; });
|
|
410
|
-
parseCommon.each(usedNums, function(x) { if (nextNum === x) ++nextNum; });
|
|
411
|
-
parseCommon.each(usedNums, function(x) { if (nextNum === x) ++nextNum; });
|
|
412
|
-
}
|
|
413
|
-
parseCommon.each(currSlur[chordPos], function(x) { if (nextNum === x) ++nextNum; });
|
|
414
|
-
parseCommon.each(currSlur[chordPos], function(x) { if (nextNum === x) ++nextNum; });
|
|
415
|
-
|
|
416
|
-
currSlur[chordPos].push(nextNum);
|
|
417
|
-
obj.startSlur.push({ label: nextNum });
|
|
418
|
-
// lyr.syllable += ' ' + nextNum + '>'; // TODO-PER:debugging
|
|
419
|
-
nextNum++;
|
|
420
|
-
}
|
|
421
|
-
};
|
|
422
|
-
|
|
423
|
-
for (var i = 0; i < line.length; i++) {
|
|
424
|
-
var el = line[i];
|
|
425
|
-
// if (el.lyric === undefined) // TODO-PER: debugging
|
|
426
|
-
// el.lyric = [{ divider: '-' }]; // TODO-PER: debugging
|
|
427
|
-
// lyr = el.lyric[0]; // TODO-PER: debugging
|
|
428
|
-
// lyr.syllable = ''; // TODO-PER: debugging
|
|
429
|
-
if (el.el_type === 'note') {
|
|
430
|
-
if (el.gracenotes) {
|
|
431
|
-
for (var g = 0; g < el.gracenotes.length; g++) {
|
|
432
|
-
if (el.gracenotes[g].endSlur) {
|
|
433
|
-
var gg = el.gracenotes[g].endSlur;
|
|
434
|
-
el.gracenotes[g].endSlur = [];
|
|
435
|
-
for (var ggg = 0; ggg < gg; ggg++)
|
|
436
|
-
addEndSlur(el.gracenotes[g], 1, 20);
|
|
437
|
-
}
|
|
438
|
-
if (el.gracenotes[g].startSlur) {
|
|
439
|
-
x = el.gracenotes[g].startSlur;
|
|
440
|
-
addStartSlur(el.gracenotes[g], x, 20);
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
if (el.endSlur) {
|
|
445
|
-
x = el.endSlur;
|
|
446
|
-
el.endSlur = [];
|
|
447
|
-
addEndSlur(el, x, 0);
|
|
448
|
-
}
|
|
449
|
-
if (el.startSlur) {
|
|
450
|
-
x = el.startSlur;
|
|
451
|
-
addStartSlur(el, x, 0);
|
|
452
|
-
}
|
|
453
|
-
if (el.pitches) {
|
|
454
|
-
var usedNums = [];
|
|
455
|
-
for (var p = 0; p < el.pitches.length; p++) {
|
|
456
|
-
if (el.pitches[p].endSlur) {
|
|
457
|
-
var k = el.pitches[p].endSlur;
|
|
458
|
-
el.pitches[p].endSlur = [];
|
|
459
|
-
for (var j = 0; j < k; j++) {
|
|
460
|
-
var slurNum = addEndSlur(el.pitches[p], 1, p+1);
|
|
461
|
-
usedNums.push(slurNum);
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
for (p = 0; p < el.pitches.length; p++) {
|
|
466
|
-
if (el.pitches[p].startSlur) {
|
|
467
|
-
x = el.pitches[p].startSlur;
|
|
468
|
-
addStartSlur(el.pitches[p], x, p+1, usedNums);
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
// Correct for the weird gracenote case where ({g}a) should match.
|
|
472
|
-
// The end slur was already assigned to the note, and needs to be moved to the first note of the graces.
|
|
473
|
-
if (el.gracenotes && el.pitches[0].endSlur && el.pitches[0].endSlur[0] === 100 && el.pitches[0].startSlur) {
|
|
474
|
-
if (el.gracenotes[0].endSlur)
|
|
475
|
-
el.gracenotes[0].endSlur.push(el.pitches[0].startSlur[0].label);
|
|
476
|
-
else
|
|
477
|
-
el.gracenotes[0].endSlur = [el.pitches[0].startSlur[0].label];
|
|
478
|
-
if (el.pitches[0].endSlur.length === 1)
|
|
479
|
-
delete el.pitches[0].endSlur;
|
|
480
|
-
else if (el.pitches[0].endSlur[0] === 100)
|
|
481
|
-
el.pitches[0].endSlur.shift();
|
|
482
|
-
else if (el.pitches[0].endSlur[el.pitches[0].endSlur.length-1] === 100)
|
|
483
|
-
el.pitches[0].endSlur.pop();
|
|
484
|
-
if (currSlur[1].length === 1)
|
|
485
|
-
delete currSlur[1];
|
|
486
|
-
else
|
|
487
|
-
currSlur[1].pop();
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
// TODO-PER: This could be done faster as we go instead of as the last step.
|
|
495
|
-
function fixClefPlacement(el) {
|
|
496
|
-
parseKeyVoice.fixClef(el);
|
|
497
|
-
//if (el.el_type === 'clef') {
|
|
498
|
-
// var min = -2;
|
|
499
|
-
// var max = 5;
|
|
500
|
-
// switch(el.type) {
|
|
501
|
-
// case 'treble+8':
|
|
502
|
-
// case 'treble-8':
|
|
503
|
-
// break;
|
|
504
|
-
// case 'bass':
|
|
505
|
-
// case 'bass+8':
|
|
506
|
-
// case 'bass-8':
|
|
507
|
-
// el.verticalPos = 20 + el.verticalPos; min += 6; max += 6;
|
|
508
|
-
// break;
|
|
509
|
-
// case 'tenor':
|
|
510
|
-
// case 'tenor+8':
|
|
511
|
-
// case 'tenor-8':
|
|
512
|
-
// el.verticalPos = - el.verticalPos; min = -40; max = 40;
|
|
513
|
-
//// el.verticalPos+=2; min += 6; max += 6;
|
|
514
|
-
// break;
|
|
515
|
-
// case 'alto':
|
|
516
|
-
// case 'alto+8':
|
|
517
|
-
// case 'alto-8':
|
|
518
|
-
// el.verticalPos = - el.verticalPos; min = -40; max = 40;
|
|
519
|
-
//// el.verticalPos-=2; min += 4; max += 4;
|
|
520
|
-
// break;
|
|
521
|
-
// }
|
|
522
|
-
// if (el.verticalPos < min) {
|
|
523
|
-
// while (el.verticalPos < min)
|
|
524
|
-
// el.verticalPos += 7;
|
|
525
|
-
// } else if (el.verticalPos > max) {
|
|
526
|
-
// while (el.verticalPos > max)
|
|
527
|
-
// el.verticalPos -= 7;
|
|
528
|
-
// }
|
|
529
|
-
//}
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
function wrapMusicLines(lines, barsperstaff) {
|
|
533
|
-
for (i = 0; i < lines.length; i++) {
|
|
534
|
-
if (lines[i].staff !== undefined) {
|
|
535
|
-
for (s = 0; s < lines[i].staff.length; s++) {
|
|
536
|
-
var permanentItems = [];
|
|
537
|
-
for (v = 0; v < lines[i].staff[s].voices.length; v++) {
|
|
538
|
-
var voice = lines[i].staff[s].voices[v];
|
|
539
|
-
var barNumThisLine = 0;
|
|
540
|
-
for (var n = 0; n < voice.length; n++) {
|
|
541
|
-
if (voice[n].el_type === 'bar') {
|
|
542
|
-
barNumThisLine++;
|
|
543
|
-
if (barNumThisLine >= barsperstaff) {
|
|
544
|
-
// push everything else to the next line, if there is anything else,
|
|
545
|
-
// and there is a next line. If there isn't a next line, create one.
|
|
546
|
-
if (n < voice.length - 1) {
|
|
547
|
-
var nextLine = getNextMusicLine(lines, i);
|
|
548
|
-
if (!nextLine) {
|
|
549
|
-
var cp = JSON.parse(JSON.stringify(lines[i]));
|
|
550
|
-
lines.push(parseCommon.clone(cp));
|
|
551
|
-
nextLine = lines[lines.length - 1];
|
|
552
|
-
for (var ss = 0; ss < nextLine.staff.length; ss++) {
|
|
553
|
-
for (var vv = 0; vv < nextLine.staff[ss].voices.length; vv++)
|
|
554
|
-
nextLine.staff[ss].voices[vv] = [];
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
var startElement = n + 1;
|
|
558
|
-
var section = lines[i].staff[s].voices[v].slice(startElement);
|
|
559
|
-
lines[i].staff[s].voices[v] = lines[i].staff[s].voices[v].slice(0, startElement);
|
|
560
|
-
nextLine.staff[s].voices[v] = permanentItems.concat(section.concat(nextLine.staff[s].voices[v]));
|
|
561
|
-
return true;
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
} else if (!voice[n].duration) {
|
|
565
|
-
permanentItems.push(voice[n]);
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
return false;
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
function getNextMusicLine(lines, currentLine) {
|
|
576
|
-
currentLine++;
|
|
577
|
-
while (lines.length > currentLine) {
|
|
578
|
-
if (lines[currentLine].staff)
|
|
579
|
-
return lines[currentLine];
|
|
580
|
-
currentLine++;
|
|
581
|
-
}
|
|
582
|
-
return null;
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
for (this.lineNum = 0; this.lineNum < this.lines.length; this.lineNum++) {
|
|
586
|
-
var staff = this.lines[this.lineNum].staff;
|
|
587
|
-
if (staff) {
|
|
588
|
-
for (this.staffNum = 0; this.staffNum < staff.length; this.staffNum++) {
|
|
589
|
-
if (staff[this.staffNum].clef)
|
|
590
|
-
fixClefPlacement(staff[this.staffNum].clef);
|
|
591
|
-
for (this.voiceNum = 0; this.voiceNum < staff[this.staffNum].voices.length; this.voiceNum++) {
|
|
592
|
-
var voice = staff[this.staffNum].voices[this.voiceNum];
|
|
593
|
-
cleanUpSlursInLine(voice);
|
|
594
|
-
for (var j = 0; j < voice.length; j++) {
|
|
595
|
-
if (voice[j].el_type === 'clef')
|
|
596
|
-
fixClefPlacement(voice[j]);
|
|
597
|
-
}
|
|
598
|
-
if (voice.length > 0 && voice[voice.length-1].barNumber) {
|
|
599
|
-
// Don't hang a bar number on the last bar line: it should go on the next line.
|
|
600
|
-
var nextLine = getNextMusicLine(this.lines, this.lineNum);
|
|
601
|
-
if (nextLine)
|
|
602
|
-
nextLine.staff[0].barNumber = voice[voice.length-1].barNumber;
|
|
603
|
-
delete voice[voice.length-1].barNumber;
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
if (!this.formatting.pagewidth)
|
|
611
|
-
this.formatting.pagewidth = defWidth;
|
|
612
|
-
if (!this.formatting.pageheight)
|
|
613
|
-
this.formatting.pageheight = defLength;
|
|
614
|
-
|
|
615
|
-
// Remove temporary variables that the outside doesn't need to know about
|
|
616
|
-
delete this.staffNum;
|
|
617
|
-
delete this.voiceNum;
|
|
618
|
-
delete this.lineNum;
|
|
619
|
-
delete this.potentialStartBeam;
|
|
620
|
-
delete this.potentialEndBeam;
|
|
621
|
-
delete this.vskipPending;
|
|
622
|
-
|
|
623
|
-
return currSlur;
|
|
624
|
-
};
|
|
625
|
-
|
|
626
|
-
this.reset();
|
|
627
|
-
|
|
628
|
-
this.getLastNote = function() {
|
|
629
|
-
if (this.lines[this.lineNum] && this.lines[this.lineNum].staff && this.lines[this.lineNum].staff[this.staffNum] &&
|
|
630
|
-
this.lines[this.lineNum].staff[this.staffNum].voices[this.voiceNum]) {
|
|
631
|
-
for (var i = this.lines[this.lineNum].staff[this.staffNum].voices[this.voiceNum].length-1; i >= 0; i--) {
|
|
632
|
-
var el = this.lines[this.lineNum].staff[this.staffNum].voices[this.voiceNum][i];
|
|
633
|
-
if (el.el_type === 'note') {
|
|
634
|
-
return el;
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
return null;
|
|
639
|
-
};
|
|
640
|
-
|
|
641
|
-
this.addTieToLastNote = function() {
|
|
642
|
-
// TODO-PER: if this is a chord, which note?
|
|
643
|
-
var el = this.getLastNote();
|
|
644
|
-
if (el && el.pitches && el.pitches.length > 0) {
|
|
645
|
-
el.pitches[0].startTie = {};
|
|
646
|
-
return true;
|
|
647
|
-
}
|
|
648
|
-
return false;
|
|
649
|
-
};
|
|
650
|
-
|
|
651
|
-
this.getDuration = function(el) {
|
|
652
|
-
if (el.duration) return el.duration;
|
|
653
|
-
//if (el.pitches && el.pitches.length > 0) return el.pitches[0].duration;
|
|
654
|
-
return 0;
|
|
655
|
-
};
|
|
656
|
-
|
|
657
|
-
this.closeLine = function() {
|
|
658
|
-
if (this.potentialStartBeam && this.potentialEndBeam) {
|
|
659
|
-
this.potentialStartBeam.startBeam = true;
|
|
660
|
-
this.potentialEndBeam.endBeam = true;
|
|
661
|
-
}
|
|
662
|
-
delete this.potentialStartBeam;
|
|
663
|
-
delete this.potentialEndBeam;
|
|
664
|
-
};
|
|
665
|
-
|
|
666
|
-
this.appendElement = function(type, startChar, endChar, hashParams)
|
|
667
|
-
{
|
|
668
|
-
var This = this;
|
|
669
|
-
var pushNote = function(hp) {
|
|
670
|
-
var currStaff = This.lines[This.lineNum].staff[This.staffNum];
|
|
671
|
-
if (!currStaff) {
|
|
672
|
-
// TODO-PER: This prevents a crash, but it drops the element. Need to figure out how to start a new line, or delay adding this.
|
|
673
|
-
return;
|
|
674
|
-
}
|
|
675
|
-
if (hp.pitches !== undefined) {
|
|
676
|
-
var mid = currStaff.workingClef.verticalPos;
|
|
677
|
-
parseCommon.each(hp.pitches, function(p) { p.verticalPos = p.pitch - mid; });
|
|
678
|
-
}
|
|
679
|
-
if (hp.gracenotes !== undefined) {
|
|
680
|
-
var mid2 = currStaff.workingClef.verticalPos;
|
|
681
|
-
parseCommon.each(hp.gracenotes, function(p) { p.verticalPos = p.pitch - mid2; });
|
|
682
|
-
}
|
|
683
|
-
currStaff.voices[This.voiceNum].push(hp);
|
|
684
|
-
};
|
|
685
|
-
hashParams.el_type = type;
|
|
686
|
-
if (startChar !== null)
|
|
687
|
-
hashParams.startChar = startChar;
|
|
688
|
-
if (endChar !== null)
|
|
689
|
-
hashParams.endChar = endChar;
|
|
690
|
-
var endBeamHere = function() {
|
|
691
|
-
This.potentialStartBeam.startBeam = true;
|
|
692
|
-
hashParams.endBeam = true;
|
|
693
|
-
delete This.potentialStartBeam;
|
|
694
|
-
delete This.potentialEndBeam;
|
|
695
|
-
};
|
|
696
|
-
var endBeamLast = function() {
|
|
697
|
-
if (This.potentialStartBeam !== undefined && This.potentialEndBeam !== undefined) { // Do we have a set of notes to beam?
|
|
698
|
-
This.potentialStartBeam.startBeam = true;
|
|
699
|
-
This.potentialEndBeam.endBeam = true;
|
|
700
|
-
}
|
|
701
|
-
delete This.potentialStartBeam;
|
|
702
|
-
delete This.potentialEndBeam;
|
|
703
|
-
};
|
|
704
|
-
if (type === 'note') { // && (hashParams.rest !== undefined || hashParams.end_beam === undefined)) {
|
|
705
|
-
// Now, add the startBeam and endBeam where it is needed.
|
|
706
|
-
// end_beam is already set on the places where there is a forced end_beam. We'll remove that here after using that info.
|
|
707
|
-
// this.potentialStartBeam either points to null or the start beam.
|
|
708
|
-
// this.potentialEndBeam either points to null or the start beam.
|
|
709
|
-
// If we have a beam break (note is longer than a quarter, or an end_beam is on this element), then set the beam if we have one.
|
|
710
|
-
// reset the variables for the next notes.
|
|
711
|
-
var dur = This.getDuration(hashParams);
|
|
712
|
-
if (dur >= 0.25) { // The beam ends on the note before this.
|
|
713
|
-
endBeamLast();
|
|
714
|
-
} else if (hashParams.force_end_beam_last && This.potentialStartBeam !== undefined) {
|
|
715
|
-
endBeamLast();
|
|
716
|
-
} else if (hashParams.end_beam && This.potentialStartBeam !== undefined) { // the beam is forced to end on this note, probably because of a space in the ABC
|
|
717
|
-
if (hashParams.rest === undefined)
|
|
718
|
-
endBeamHere();
|
|
719
|
-
else
|
|
720
|
-
endBeamLast();
|
|
721
|
-
} else if (hashParams.rest === undefined) { // this a short note and we aren't about to end the beam
|
|
722
|
-
if (This.potentialStartBeam === undefined) { // We aren't collecting notes for a beam, so start here.
|
|
723
|
-
if (!hashParams.end_beam) {
|
|
724
|
-
This.potentialStartBeam = hashParams;
|
|
725
|
-
delete This.potentialEndBeam;
|
|
726
|
-
}
|
|
727
|
-
} else {
|
|
728
|
-
This.potentialEndBeam = hashParams; // Continue the beaming, look for the end next note.
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
// end_beam goes on rests and notes which precede rests _except_ when a rest (or set of adjacent rests) has normal notes on both sides (no spaces)
|
|
733
|
-
// if (hashParams.rest !== undefined)
|
|
734
|
-
// {
|
|
735
|
-
// hashParams.end_beam = true;
|
|
736
|
-
// var el2 = this.getLastNote();
|
|
737
|
-
// if (el2) el2.end_beam = true;
|
|
738
|
-
// // TODO-PER: implement exception mentioned in the comment.
|
|
739
|
-
// }
|
|
740
|
-
} else { // It's not a note, so there definitely isn't beaming after it.
|
|
741
|
-
endBeamLast();
|
|
742
|
-
}
|
|
743
|
-
delete hashParams.end_beam; // We don't want this temporary variable hanging around.
|
|
744
|
-
delete hashParams.force_end_beam_last; // We don't want this temporary variable hanging around.
|
|
745
|
-
pushNote(hashParams);
|
|
746
|
-
};
|
|
747
|
-
|
|
748
|
-
this.appendStartingElement = function(type, startChar, endChar, hashParams2)
|
|
749
|
-
{
|
|
750
|
-
// If we're in the middle of beaming, then end the beam.
|
|
751
|
-
this.closeLine();
|
|
752
|
-
|
|
753
|
-
// We only ever want implied naturals the first time.
|
|
754
|
-
var impliedNaturals;
|
|
755
|
-
if (type === 'key') {
|
|
756
|
-
impliedNaturals = hashParams2.impliedNaturals;
|
|
757
|
-
delete hashParams2.impliedNaturals;
|
|
758
|
-
delete hashParams2.explicitAccidentals;
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
// Clone the object because it will be sticking around for the next line and we don't want the extra fields in it.
|
|
762
|
-
var hashParams = parseCommon.clone(hashParams2);
|
|
763
|
-
|
|
764
|
-
if (this.lines[this.lineNum].staff) { // be sure that we are on a music type line before doing the following.
|
|
765
|
-
// If this is the first item in this staff, then we might have to initialize the staff, first.
|
|
766
|
-
if (this.lines[this.lineNum].staff.length <= this.staffNum) {
|
|
767
|
-
this.lines[this.lineNum].staff[this.staffNum] = {};
|
|
768
|
-
this.lines[this.lineNum].staff[this.staffNum].clef = parseCommon.clone(this.lines[this.lineNum].staff[0].clef);
|
|
769
|
-
this.lines[this.lineNum].staff[this.staffNum].key = parseCommon.clone(this.lines[this.lineNum].staff[0].key);
|
|
770
|
-
if (this.lines[this.lineNum].staff[0].meter)
|
|
771
|
-
this.lines[this.lineNum].staff[this.staffNum].meter = parseCommon.clone(this.lines[this.lineNum].staff[0].meter);
|
|
772
|
-
this.lines[this.lineNum].staff[this.staffNum].workingClef = parseCommon.clone(this.lines[this.lineNum].staff[0].workingClef);
|
|
773
|
-
this.lines[this.lineNum].staff[this.staffNum].voices = [[]];
|
|
774
|
-
}
|
|
775
|
-
// If this is a clef type, then we replace the working clef on the line. This is kept separate from
|
|
776
|
-
// the clef in case there is an inline clef field. We need to know what the current position for
|
|
777
|
-
// the note is.
|
|
778
|
-
if (type === 'clef') {
|
|
779
|
-
this.lines[this.lineNum].staff[this.staffNum].workingClef = hashParams;
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
// These elements should not be added twice, so if the element exists on this line without a note or bar before it, just replace the staff version.
|
|
783
|
-
var voice = this.lines[this.lineNum].staff[this.staffNum].voices[this.voiceNum];
|
|
784
|
-
for (var i = 0; i < voice.length; i++) {
|
|
785
|
-
if (voice[i].el_type === 'note' || voice[i].el_type === 'bar') {
|
|
786
|
-
hashParams.el_type = type;
|
|
787
|
-
hashParams.startChar = startChar;
|
|
788
|
-
hashParams.endChar = endChar;
|
|
789
|
-
if (impliedNaturals)
|
|
790
|
-
hashParams.accidentals = impliedNaturals.concat(hashParams.accidentals);
|
|
791
|
-
voice.push(hashParams);
|
|
792
|
-
return;
|
|
793
|
-
}
|
|
794
|
-
if (voice[i].el_type === type) {
|
|
795
|
-
hashParams.el_type = type;
|
|
796
|
-
hashParams.startChar = startChar;
|
|
797
|
-
hashParams.endChar = endChar;
|
|
798
|
-
if (impliedNaturals)
|
|
799
|
-
hashParams.accidentals = impliedNaturals.concat(hashParams.accidentals);
|
|
800
|
-
voice[i] = hashParams;
|
|
801
|
-
return;
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
// We didn't see either that type or a note, so replace the element to the staff.
|
|
805
|
-
this.lines[this.lineNum].staff[this.staffNum][type] = hashParams2;
|
|
806
|
-
}
|
|
807
|
-
};
|
|
808
|
-
|
|
809
|
-
this.getNumLines = function() {
|
|
810
|
-
return this.lines.length;
|
|
811
|
-
};
|
|
812
|
-
|
|
813
|
-
this.pushLine = function(hash) {
|
|
814
|
-
if (this.vskipPending) {
|
|
815
|
-
hash.vskip = this.vskipPending;
|
|
816
|
-
delete this.vskipPending;
|
|
817
|
-
}
|
|
818
|
-
this.lines.push(hash);
|
|
819
|
-
};
|
|
820
|
-
|
|
821
|
-
this.addSubtitle = function(str) {
|
|
822
|
-
this.pushLine({subtitle: str});
|
|
823
|
-
};
|
|
824
|
-
|
|
825
|
-
this.addSpacing = function(num) {
|
|
826
|
-
this.vskipPending = num;
|
|
827
|
-
};
|
|
828
|
-
|
|
829
|
-
this.addNewPage = function(num) {
|
|
830
|
-
this.pushLine({newpage: num});
|
|
831
|
-
};
|
|
832
|
-
|
|
833
|
-
this.addSeparator = function(spaceAbove, spaceBelow, lineLength) {
|
|
834
|
-
this.pushLine({separator: {spaceAbove: Math.round(spaceAbove), spaceBelow: Math.round(spaceBelow), lineLength: Math.round(lineLength)}});
|
|
835
|
-
};
|
|
836
|
-
|
|
837
|
-
this.addText = function(str) {
|
|
838
|
-
this.pushLine({text: str});
|
|
839
|
-
};
|
|
840
|
-
|
|
841
|
-
this.addCentered = function(str) {
|
|
842
|
-
this.pushLine({text: [{text: str, center: true }]});
|
|
843
|
-
};
|
|
844
|
-
|
|
845
|
-
this.containsNotes = function(voice) {
|
|
846
|
-
for (var i = 0; i < voice.length; i++) {
|
|
847
|
-
if (voice[i].el_type === 'note' || voice[i].el_type === 'bar')
|
|
848
|
-
return true;
|
|
849
|
-
}
|
|
850
|
-
return false;
|
|
851
|
-
};
|
|
852
|
-
|
|
853
|
-
this.containsNotesStrict = function(voice) {
|
|
854
|
-
for (var i = 0; i < voice.length; i++) {
|
|
855
|
-
if (voice[i].el_type === 'note' && voice[i].rest === undefined)
|
|
856
|
-
return true;
|
|
857
|
-
}
|
|
858
|
-
return false;
|
|
859
|
-
};
|
|
860
|
-
|
|
861
|
-
// anyVoiceContainsNotes: function(line) {
|
|
862
|
-
// for (var i = 0; i < line.staff.voices.length; i++) {
|
|
863
|
-
// if (this.containsNotes(line.staff.voices[i]))
|
|
864
|
-
// return true;
|
|
865
|
-
// }
|
|
866
|
-
// return false;
|
|
867
|
-
// },
|
|
868
|
-
this.changeVoiceScale = function(scale) {
|
|
869
|
-
var This = this;
|
|
870
|
-
This.appendElement('scale', null, null, { size: scale} );
|
|
871
|
-
};
|
|
872
|
-
|
|
873
|
-
this.startNewLine = function(params) {
|
|
874
|
-
// If the pointed to line doesn't exist, just create that. If the line does exist, but doesn't have any music on it, just use it.
|
|
875
|
-
// If it does exist and has music, then increment the line number. If the new element doesn't exist, create it.
|
|
876
|
-
var This = this;
|
|
877
|
-
this.closeLine(); // Close the previous line.
|
|
878
|
-
var createVoice = function(params) {
|
|
879
|
-
var thisStaff = This.lines[This.lineNum].staff[This.staffNum];
|
|
880
|
-
thisStaff.voices[This.voiceNum] = [];
|
|
881
|
-
if (!thisStaff.title)
|
|
882
|
-
thisStaff.title = [];
|
|
883
|
-
thisStaff.title[This.voiceNum] = { name: params.name, subname: params.subname };
|
|
884
|
-
if (params.style)
|
|
885
|
-
This.appendElement('style', null, null, {head: params.style});
|
|
886
|
-
if (params.stem)
|
|
887
|
-
This.appendElement('stem', null, null, {direction: params.stem});
|
|
888
|
-
else if (This.voiceNum > 0) {
|
|
889
|
-
if (thisStaff.voices[0]!== undefined) {
|
|
890
|
-
var found = false;
|
|
891
|
-
for (var i = 0; i < thisStaff.voices[0].length; i++) {
|
|
892
|
-
if (thisStaff.voices[0].el_type === 'stem')
|
|
893
|
-
found = true;
|
|
894
|
-
}
|
|
895
|
-
if (!found) {
|
|
896
|
-
var stem = { el_type: 'stem', direction: 'up' };
|
|
897
|
-
thisStaff.voices[0].splice(0,0,stem);
|
|
898
|
-
}
|
|
899
|
-
}
|
|
900
|
-
This.appendElement('stem', null, null, {direction: 'down'});
|
|
901
|
-
}
|
|
902
|
-
if (params.scale)
|
|
903
|
-
This.appendElement('scale', null, null, { size: params.scale} );
|
|
904
|
-
};
|
|
905
|
-
var createStaff = function(params) {
|
|
906
|
-
if (params.key && params.key.impliedNaturals) {
|
|
907
|
-
params.key.accidentals = params.key.accidentals.concat(params.key.impliedNaturals);
|
|
908
|
-
delete params.key.impliedNaturals;
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
This.lines[This.lineNum].staff[This.staffNum] = {voices: [ ], clef: params.clef, key: params.key, workingClef: params.clef };
|
|
912
|
-
if (params.stafflines !== undefined) {
|
|
913
|
-
This.lines[This.lineNum].staff[This.staffNum].clef.stafflines = params.stafflines;
|
|
914
|
-
This.lines[This.lineNum].staff[This.staffNum].workingClef.stafflines = params.stafflines;
|
|
915
|
-
}
|
|
916
|
-
if (params.staffscale) {
|
|
917
|
-
This.lines[This.lineNum].staff[This.staffNum].staffscale = params.staffscale;
|
|
918
|
-
}
|
|
919
|
-
if (params.tripletfont) This.lines[This.lineNum].staff[This.staffNum].tripletfont = params.tripletfont;
|
|
920
|
-
if (params.vocalfont) This.lines[This.lineNum].staff[This.staffNum].vocalfont = params.vocalfont;
|
|
921
|
-
if (params.bracket) This.lines[This.lineNum].staff[This.staffNum].bracket = params.bracket;
|
|
922
|
-
if (params.brace) This.lines[This.lineNum].staff[This.staffNum].brace = params.brace;
|
|
923
|
-
if (params.connectBarLines) This.lines[This.lineNum].staff[This.staffNum].connectBarLines = params.connectBarLines;
|
|
924
|
-
if (params.barNumber) This.lines[This.lineNum].staff[This.staffNum].barNumber = params.barNumber;
|
|
925
|
-
createVoice(params);
|
|
926
|
-
// Some stuff just happens for the first voice
|
|
927
|
-
if (params.part)
|
|
928
|
-
This.appendElement('part', params.part.startChar, params.part.endChar, {title: params.part.title});
|
|
929
|
-
if (params.meter !== undefined) This.lines[This.lineNum].staff[This.staffNum].meter = params.meter;
|
|
930
|
-
};
|
|
931
|
-
var createLine = function(params) {
|
|
932
|
-
This.lines[This.lineNum] = {staff: []};
|
|
933
|
-
createStaff(params);
|
|
934
|
-
};
|
|
935
|
-
if (this.lines[this.lineNum] === undefined) createLine(params);
|
|
936
|
-
else if (this.lines[this.lineNum].staff === undefined) {
|
|
937
|
-
this.lineNum++;
|
|
938
|
-
this.startNewLine(params);
|
|
939
|
-
} else if (this.lines[this.lineNum].staff[this.staffNum] === undefined) createStaff(params);
|
|
940
|
-
else if (this.lines[this.lineNum].staff[this.staffNum].voices[this.voiceNum] === undefined) createVoice(params);
|
|
941
|
-
else if (!this.containsNotes(this.lines[this.lineNum].staff[this.staffNum].voices[this.voiceNum])) return;
|
|
942
|
-
else {
|
|
943
|
-
this.lineNum++;
|
|
944
|
-
this.startNewLine(params);
|
|
945
|
-
}
|
|
946
|
-
};
|
|
947
|
-
|
|
948
|
-
this.setBarNumberImmediate = function(barNumber) {
|
|
949
|
-
// If this is called right at the beginning of a line, then correct the measure number that is already written.
|
|
950
|
-
// If this is called at the beginning of a measure, then correct the measure number that was just created.
|
|
951
|
-
// If this is called in the middle of a measure, then subtract one from it, because it will be incremented before applied.
|
|
952
|
-
var currentVoice = this.getCurrentVoice();
|
|
953
|
-
if (currentVoice && currentVoice.length > 0) {
|
|
954
|
-
var lastElement = currentVoice[currentVoice.length-1];
|
|
955
|
-
if (lastElement.el_type === 'bar') {
|
|
956
|
-
if (lastElement.barNumber !== undefined) // the measure number might not be written for this bar, don't override that.
|
|
957
|
-
lastElement.barNumber = barNumber;
|
|
958
|
-
} else
|
|
959
|
-
return barNumber-1;
|
|
960
|
-
}
|
|
961
|
-
return barNumber;
|
|
962
|
-
};
|
|
963
|
-
|
|
964
|
-
this.hasBeginMusic = function() {
|
|
965
|
-
// return true if there exists at least one line that contains "staff"
|
|
966
|
-
for (var i = 0; i < this.lines.length; i++) {
|
|
967
|
-
if (this.lines[i].staff)
|
|
968
|
-
return true;
|
|
969
|
-
}
|
|
970
|
-
return false;
|
|
971
|
-
};
|
|
972
|
-
|
|
973
|
-
this.isFirstLine = function(index) {
|
|
974
|
-
for (var i = index-1; i >= 0; i--) {
|
|
975
|
-
if (this.lines[i].staff !== undefined) return false;
|
|
976
|
-
}
|
|
977
|
-
return true;
|
|
171
|
+
var beatLen = this.getBeatLength();
|
|
172
|
+
var barLen = this.getBarLength();
|
|
173
|
+
return barLen / beatLen;
|
|
978
174
|
};
|
|
979
175
|
|
|
980
176
|
this.getMeter = function() {
|
|
@@ -1025,42 +221,25 @@ var Tune = function() {
|
|
|
1025
221
|
return { };
|
|
1026
222
|
};
|
|
1027
223
|
|
|
1028
|
-
this.
|
|
1029
|
-
if (this.lines[this.lineNum] !== undefined && this.lines[this.lineNum].staff[this.staffNum] !== undefined && this.lines[this.lineNum].staff[this.staffNum].voices[this.voiceNum] !== undefined)
|
|
1030
|
-
return this.lines[this.lineNum].staff[this.staffNum].voices[this.voiceNum];
|
|
1031
|
-
else return null;
|
|
1032
|
-
};
|
|
1033
|
-
|
|
1034
|
-
this.setCurrentVoice = function(staffNum, voiceNum) {
|
|
1035
|
-
this.staffNum = staffNum;
|
|
1036
|
-
this.voiceNum = voiceNum;
|
|
224
|
+
this.getElementFromChar = function(char) {
|
|
1037
225
|
for (var i = 0; i < this.lines.length; i++) {
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
226
|
+
var line = this.lines[i];
|
|
227
|
+
if (line.staff) {
|
|
228
|
+
for (var j = 0; j < line.staff.length; j++) {
|
|
229
|
+
var staff = line.staff[j];
|
|
230
|
+
for (var k = 0; k < staff.voices.length; k++) {
|
|
231
|
+
var voice = staff.voices[k];
|
|
232
|
+
for (var ii = 0; ii < voice.length; ii++) {
|
|
233
|
+
var elem = voice[ii];
|
|
234
|
+
if (elem.startChar && elem.endChar &&
|
|
235
|
+
elem.startChar <= char && elem.endChar > char)
|
|
236
|
+
return elem;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
1043
239
|
}
|
|
1044
240
|
}
|
|
1045
241
|
}
|
|
1046
|
-
|
|
1047
|
-
};
|
|
1048
|
-
|
|
1049
|
-
this.addMetaText = function(key, value) {
|
|
1050
|
-
if (this.metaText[key] === undefined)
|
|
1051
|
-
this.metaText[key] = value;
|
|
1052
|
-
else
|
|
1053
|
-
this.metaText[key] += "\n" + value;
|
|
1054
|
-
};
|
|
1055
|
-
|
|
1056
|
-
this.addMetaTextArray = function(key, value) {
|
|
1057
|
-
if (this.metaText[key] === undefined)
|
|
1058
|
-
this.metaText[key] = [value];
|
|
1059
|
-
else
|
|
1060
|
-
this.metaText[key].push(value);
|
|
1061
|
-
};
|
|
1062
|
-
this.addMetaTextObj = function(key, value) {
|
|
1063
|
-
this.metaText[key] = value;
|
|
242
|
+
return null;
|
|
1064
243
|
};
|
|
1065
244
|
|
|
1066
245
|
function addVerticalInfo(timingEvents) {
|
|
@@ -1205,36 +384,45 @@ var Tune = function() {
|
|
|
1205
384
|
this.makeVoicesArray = function() {
|
|
1206
385
|
// First make a new array that is arranged by voice so that the repeats that span different lines are handled correctly.
|
|
1207
386
|
var voicesArr = [];
|
|
387
|
+
var measureNumber = [];
|
|
388
|
+
var tempos = {};
|
|
1208
389
|
for (var line = 0; line < this.engraver.staffgroups.length; line++) {
|
|
1209
390
|
var group = this.engraver.staffgroups[line];
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
var
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
391
|
+
if (group && group.staffs && group.staffs.length > 0) {
|
|
392
|
+
var firstStaff = group.staffs[0];
|
|
393
|
+
var middleC = firstStaff.absoluteY;
|
|
394
|
+
var top = middleC - firstStaff.top * spacing.STEP;
|
|
395
|
+
var lastStaff = group.staffs[group.staffs.length - 1];
|
|
396
|
+
middleC = lastStaff.absoluteY;
|
|
397
|
+
var bottom = middleC - lastStaff.bottom * spacing.STEP;
|
|
398
|
+
var height = bottom - top;
|
|
399
|
+
|
|
400
|
+
var voices = group.voices;
|
|
401
|
+
for (var v = 0; v < voices.length; v++) {
|
|
402
|
+
var noteFound = false;
|
|
403
|
+
if (!voicesArr[v])
|
|
404
|
+
voicesArr[v] = [];
|
|
405
|
+
if (measureNumber[v] === undefined)
|
|
406
|
+
measureNumber[v] = 0;
|
|
407
|
+
var elements = voices[v].children;
|
|
408
|
+
for (var elem = 0; elem < elements.length; elem++) {
|
|
409
|
+
if (elements[elem].type === "tempo")
|
|
410
|
+
tempos[measureNumber[v]] = this.getBpm(elements[elem].abcelem);
|
|
411
|
+
voicesArr[v].push({top: top, height: height, line: group.line, measureNumber: measureNumber[v], elem: elements[elem]});
|
|
412
|
+
if (elements[elem].type === 'bar' && noteFound) // Count the measures by counting the bar lines, but skip a bar line that appears at the left of the music, before any notes.
|
|
413
|
+
measureNumber[v]++;
|
|
414
|
+
if (elements[elem].type === 'note' || elements[elem].type === 'rest')
|
|
415
|
+
noteFound = true;
|
|
416
|
+
}
|
|
1231
417
|
}
|
|
1232
418
|
}
|
|
1233
419
|
}
|
|
420
|
+
this.tempoLocations = tempos; // This should be passed back, but the function is accessible publicly so that would break the interface.
|
|
1234
421
|
return voicesArr;
|
|
1235
422
|
};
|
|
1236
423
|
|
|
1237
|
-
this.setupEvents = function(startingDelay, timeDivider,
|
|
424
|
+
this.setupEvents = function(startingDelay, timeDivider, startingBpm, warp) {
|
|
425
|
+
if (!warp) warp = 1;
|
|
1238
426
|
var timingEvents = [];
|
|
1239
427
|
|
|
1240
428
|
var eventHash = {};
|
|
@@ -1244,24 +432,31 @@ var Tune = function() {
|
|
|
1244
432
|
var isTiedState;
|
|
1245
433
|
var nextIsBar = true;
|
|
1246
434
|
var voices = this.makeVoicesArray();
|
|
435
|
+
var maxVoiceTimeMilliseconds = 0;
|
|
1247
436
|
for (var v = 0; v < voices.length; v++) {
|
|
1248
437
|
var voiceTime = time;
|
|
1249
438
|
var voiceTimeMilliseconds = Math.round(voiceTime * 1000);
|
|
1250
439
|
var startingRepeatElem = 0;
|
|
1251
440
|
var endingRepeatElem = -1;
|
|
1252
441
|
var elements = voices[v];
|
|
442
|
+
var bpm = startingBpm;
|
|
443
|
+
timeDivider = this.getBeatLength() * bpm / 60;
|
|
444
|
+
var tempoDone = -1;
|
|
1253
445
|
for (var elem = 0; elem < elements.length; elem++) {
|
|
1254
|
-
var
|
|
1255
|
-
if (
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
timeDivider = beatLength * beatsPerSecond;
|
|
446
|
+
var thisMeasure = elements[elem].measureNumber;
|
|
447
|
+
if (tempoDone !== thisMeasure && this.tempoLocations[thisMeasure]) {
|
|
448
|
+
bpm = this.tempoLocations[thisMeasure];
|
|
449
|
+
timeDivider = warp * this.getBeatLength() * bpm / 60;
|
|
450
|
+
tempoDone = thisMeasure;
|
|
1260
451
|
}
|
|
452
|
+
var element = elements[elem].elem;
|
|
1261
453
|
var ret = this.addElementToEvents(eventHash, element, voiceTimeMilliseconds, elements[elem].top, elements[elem].height, elements[elem].line, elements[elem].measureNumber, timeDivider, isTiedState, nextIsBar);
|
|
1262
454
|
isTiedState = ret.isTiedState;
|
|
1263
455
|
nextIsBar = ret.nextIsBar;
|
|
1264
456
|
voiceTime += ret.duration;
|
|
457
|
+
var lastHash;
|
|
458
|
+
if (element.duration > 0 && eventHash["event" + voiceTimeMilliseconds]) // This won't exist if this is the end of a tie.
|
|
459
|
+
lastHash = "event" + voiceTimeMilliseconds;
|
|
1265
460
|
voiceTimeMilliseconds = Math.round(voiceTime * 1000);
|
|
1266
461
|
if (element.type === 'bar') {
|
|
1267
462
|
var barType = element.abcelem.type;
|
|
@@ -1269,16 +464,32 @@ var Tune = function() {
|
|
|
1269
464
|
var startEnding = (element.abcelem.startEnding === '1');
|
|
1270
465
|
var startRepeat = (barType === "bar_left_repeat" || barType === "bar_dbl_repeat" || barType === "bar_right_repeat");
|
|
1271
466
|
if (endRepeat) {
|
|
467
|
+
// Force the end of the previous note to the position of the measure - the cursor won't go past the end repeat
|
|
468
|
+
if (elem > 0) {
|
|
469
|
+
eventHash[lastHash].endX = element.x;
|
|
470
|
+
}
|
|
471
|
+
|
|
1272
472
|
if (endingRepeatElem === -1)
|
|
1273
473
|
endingRepeatElem = elem;
|
|
474
|
+
var lastVoiceTimeMilliseconds = 0;
|
|
475
|
+
tempoDone = -1;
|
|
1274
476
|
for (var el2 = startingRepeatElem; el2 < endingRepeatElem; el2++) {
|
|
477
|
+
thisMeasure = elements[el2].measureNumber;
|
|
478
|
+
if (tempoDone !== thisMeasure && this.tempoLocations[thisMeasure]) {
|
|
479
|
+
bpm = this.tempoLocations[thisMeasure];
|
|
480
|
+
timeDivider = warp * this.getBeatLength() * bpm / 60;
|
|
481
|
+
tempoDone = thisMeasure;
|
|
482
|
+
}
|
|
1275
483
|
var element2 = elements[el2].elem;
|
|
1276
484
|
ret = this.addElementToEvents(eventHash, element2, voiceTimeMilliseconds, elements[el2].top, elements[el2].height, elements[el2].line, elements[el2].measureNumber, timeDivider, isTiedState, nextIsBar);
|
|
1277
485
|
isTiedState = ret.isTiedState;
|
|
1278
486
|
nextIsBar = ret.nextIsBar;
|
|
1279
487
|
voiceTime += ret.duration;
|
|
488
|
+
lastVoiceTimeMilliseconds = voiceTimeMilliseconds;
|
|
1280
489
|
voiceTimeMilliseconds = Math.round(voiceTime * 1000);
|
|
1281
490
|
}
|
|
491
|
+
if (eventHash["event" + lastVoiceTimeMilliseconds]) // This won't exist if it is the beginning of the next line. That's ok because we will just count the end of the last line as the end.
|
|
492
|
+
eventHash["event" + lastVoiceTimeMilliseconds].endX = elements[endingRepeatElem].elem.x;
|
|
1282
493
|
nextIsBar = true;
|
|
1283
494
|
endingRepeatElem = -1;
|
|
1284
495
|
}
|
|
@@ -1288,12 +499,14 @@ var Tune = function() {
|
|
|
1288
499
|
startingRepeatElem = elem;
|
|
1289
500
|
}
|
|
1290
501
|
}
|
|
502
|
+
maxVoiceTimeMilliseconds = Math.max(maxVoiceTimeMilliseconds, voiceTimeMilliseconds)
|
|
1291
503
|
}
|
|
1292
504
|
// now we have all the events, but if there are multiple voices then there may be events out of order or duplicated, so normalize it.
|
|
1293
505
|
timingEvents = makeSortedArray(eventHash);
|
|
1294
506
|
addVerticalInfo(timingEvents);
|
|
1295
|
-
|
|
1296
|
-
|
|
507
|
+
addEndPoints(this.lines, timingEvents)
|
|
508
|
+
timingEvents.push({ type: "end", milliseconds: maxVoiceTimeMilliseconds });
|
|
509
|
+
this.addUsefulCallbackInfo(timingEvents, bpm*warp);
|
|
1297
510
|
return timingEvents;
|
|
1298
511
|
};
|
|
1299
512
|
|
|
@@ -1305,16 +518,32 @@ var Tune = function() {
|
|
|
1305
518
|
}
|
|
1306
519
|
};
|
|
1307
520
|
|
|
1308
|
-
function
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
var
|
|
1317
|
-
|
|
521
|
+
function skipTies(elements, index) {
|
|
522
|
+
while (index < elements.length && elements[index].left === null)
|
|
523
|
+
index++;
|
|
524
|
+
return elements[index];
|
|
525
|
+
}
|
|
526
|
+
function addEndPoints(lines, elements) {
|
|
527
|
+
if (elements.length < 1)
|
|
528
|
+
return;
|
|
529
|
+
for (var i = 0; i < elements.length-1; i++) {
|
|
530
|
+
var el = elements[i];
|
|
531
|
+
var next = skipTies(elements, i+1);
|
|
532
|
+
if (el.left !== null) {
|
|
533
|
+
// If there is no left element that is because this is a tie so it should be skipped.
|
|
534
|
+
var endX = (next && el.top === next.top) ? next.left : lines[el.line].staffGroup.w;
|
|
535
|
+
// If this is already set, it is because the notes aren't sequential here, like the next thing is a repeat bar line.
|
|
536
|
+
// In that case, the right-most position is passed in. There could still be an intervening note in another voice, so always look for the closest position.
|
|
537
|
+
// If there is a repeat that stays on the same line, the endX set above won't be right because the next note will be before. In that case, use the endX that was calculated.
|
|
538
|
+
if (el.endX !== undefined) {
|
|
539
|
+
if (endX > el.left)
|
|
540
|
+
el.endX = Math.min(el.endX, endX);
|
|
541
|
+
} else
|
|
542
|
+
el.endX = endX;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
var lastEl = elements[elements.length-1];
|
|
546
|
+
lastEl.endX = lines[lastEl.line].staffGroup.w;
|
|
1318
547
|
}
|
|
1319
548
|
|
|
1320
549
|
this.getBpm = function(tempo) {
|
|
@@ -1329,7 +558,7 @@ var Tune = function() {
|
|
|
1329
558
|
bpm = 180;
|
|
1330
559
|
// Compensate for compound meter, where the beat isn't a beat.
|
|
1331
560
|
var meter = this.getMeterFraction();
|
|
1332
|
-
if (meter && (meter.num % 3 === 0)) {
|
|
561
|
+
if (meter && meter.num !== 3 && (meter.num % 3 === 0)) {
|
|
1333
562
|
bpm = 120;
|
|
1334
563
|
}
|
|
1335
564
|
}
|
|
@@ -1337,11 +566,25 @@ var Tune = function() {
|
|
|
1337
566
|
};
|
|
1338
567
|
|
|
1339
568
|
this.setTiming = function (bpm, measuresOfDelay) {
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
569
|
+
measuresOfDelay = measuresOfDelay || 0;
|
|
570
|
+
if (!this.engraver || !this.engraver.staffgroups) {
|
|
571
|
+
console.log("setTiming cannot be called before the tune is drawn.");
|
|
572
|
+
this.noteTimings = [];
|
|
573
|
+
return;
|
|
1343
574
|
}
|
|
1344
575
|
|
|
576
|
+
var tempo = this.metaText ? this.metaText.tempo : null;
|
|
577
|
+
var naturalBpm = this.getBpm(tempo);
|
|
578
|
+
var warp = 1;
|
|
579
|
+
if (bpm) {
|
|
580
|
+
if (tempo)
|
|
581
|
+
warp = bpm / naturalBpm;
|
|
582
|
+
} else
|
|
583
|
+
bpm = naturalBpm;
|
|
584
|
+
|
|
585
|
+
// Calculate the basic midi data. We only care about the qpm variable here.
|
|
586
|
+
//this.setUpAudio({qpm: bpm});
|
|
587
|
+
|
|
1345
588
|
var beatLength = this.getBeatLength();
|
|
1346
589
|
var beatsPerSecond = bpm / 60;
|
|
1347
590
|
|
|
@@ -1352,8 +595,25 @@ var Tune = function() {
|
|
|
1352
595
|
startingDelay -= this.getPickupLength() / beatLength / beatsPerSecond;
|
|
1353
596
|
var timeDivider = beatLength * beatsPerSecond;
|
|
1354
597
|
|
|
1355
|
-
this.noteTimings = this.setupEvents(startingDelay, timeDivider, bpm);
|
|
598
|
+
this.noteTimings = this.setupEvents(startingDelay, timeDivider, bpm, warp);
|
|
599
|
+
if (this.noteTimings.length > 0) {
|
|
600
|
+
this.totalTime = this.noteTimings[this.noteTimings.length - 1].milliseconds / 1000;
|
|
601
|
+
this.totalBeats = this.totalTime * beatsPerSecond;
|
|
602
|
+
} else {
|
|
603
|
+
this.totalTime = undefined;
|
|
604
|
+
this.totalBeats = undefined;
|
|
605
|
+
}
|
|
606
|
+
return this.noteTimings;
|
|
607
|
+
};
|
|
608
|
+
|
|
609
|
+
this.setUpAudio = function(options) {
|
|
610
|
+
if (!options) options = {};
|
|
611
|
+
var seq = sequence(this, options);
|
|
612
|
+
return flatten(seq, options, this.formatting.percmap);
|
|
1356
613
|
};
|
|
614
|
+
this.deline = function(options) {
|
|
615
|
+
return delineTune(this.lines, options);
|
|
616
|
+
}
|
|
1357
617
|
};
|
|
1358
618
|
|
|
1359
619
|
module.exports = Tune;
|