abcjs 6.0.0-beta.8 → 6.0.1
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 +959 -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 +28232 -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 +239 -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 +8 -18
- 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 +15256 -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/parse/abc_parse.js
CHANGED
|
@@ -1,46 +1,36 @@
|
|
|
1
1
|
// abc_parse.js: parses a string representing ABC Music Notation into a usable internal structure.
|
|
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
|
-
|
|
17
|
-
/*global window */
|
|
18
2
|
|
|
19
3
|
var parseCommon = require('./abc_common');
|
|
20
4
|
var parseDirective = require('./abc_parse_directive');
|
|
21
5
|
var ParseHeader = require('./abc_parse_header');
|
|
22
|
-
var
|
|
6
|
+
var ParseMusic = require('./abc_parse_music');
|
|
23
7
|
var Tokenizer = require('./abc_tokenizer');
|
|
24
|
-
var transpose = require('./abc_transpose');
|
|
25
8
|
var wrap = require('./wrap_lines');
|
|
26
9
|
|
|
27
10
|
var Tune = require('../data/abc_tune');
|
|
11
|
+
var TuneBuilder = require('../parse/tune-builder');
|
|
28
12
|
|
|
29
13
|
var Parse = function() {
|
|
30
14
|
"use strict";
|
|
31
15
|
var tune = new Tune();
|
|
32
|
-
var
|
|
16
|
+
var tuneBuilder = new TuneBuilder(tune);
|
|
17
|
+
var tokenizer;
|
|
18
|
+
var wordsContinuation = '';
|
|
19
|
+
var symbolContinuation = '';
|
|
33
20
|
|
|
34
21
|
this.getTune = function() {
|
|
35
|
-
|
|
22
|
+
var t = {
|
|
36
23
|
formatting: tune.formatting,
|
|
37
24
|
lines: tune.lines,
|
|
38
25
|
media: tune.media,
|
|
39
26
|
metaText: tune.metaText,
|
|
27
|
+
metaTextInfo: tune.metaTextInfo,
|
|
40
28
|
version: tune.version,
|
|
41
29
|
|
|
42
30
|
addElementToEvents: tune.addElementToEvents,
|
|
43
31
|
addUsefulCallbackInfo: tune.addUsefulCallbackInfo,
|
|
32
|
+
getTotalTime: tune.getTotalTime,
|
|
33
|
+
getTotalBeats: tune.getTotalBeats,
|
|
44
34
|
getBarLength: tune.getBarLength,
|
|
45
35
|
getBeatLength: tune.getBeatLength,
|
|
46
36
|
getBeatsPerMeasure: tune.getBeatsPerMeasure,
|
|
@@ -49,11 +39,19 @@ var Parse = function() {
|
|
|
49
39
|
getMeterFraction: tune.getMeterFraction,
|
|
50
40
|
getPickupLength: tune.getPickupLength,
|
|
51
41
|
getKeySignature: tune.getKeySignature,
|
|
42
|
+
getElementFromChar: tune.getElementFromChar,
|
|
52
43
|
makeVoicesArray: tune.makeVoicesArray,
|
|
53
44
|
millisecondsPerMeasure: tune.millisecondsPerMeasure,
|
|
54
45
|
setupEvents: tune.setupEvents,
|
|
55
|
-
setTiming: tune.setTiming
|
|
46
|
+
setTiming: tune.setTiming,
|
|
47
|
+
setUpAudio: tune.setUpAudio,
|
|
48
|
+
deline: tune.deline,
|
|
56
49
|
};
|
|
50
|
+
if (tune.lineBreaks)
|
|
51
|
+
t.lineBreaks = tune.lineBreaks;
|
|
52
|
+
if (tune.visualTranspose)
|
|
53
|
+
t.visualTranspose = tune.visualTranspose;
|
|
54
|
+
return t;
|
|
57
55
|
};
|
|
58
56
|
|
|
59
57
|
function addPositioning(el, type, value) {
|
|
@@ -83,18 +81,15 @@ var Parse = function() {
|
|
|
83
81
|
this.next_note_duration = 0;
|
|
84
82
|
this.start_new_line = true;
|
|
85
83
|
this.is_in_header = true;
|
|
86
|
-
this.is_in_history = false;
|
|
87
84
|
this.partForNextLine = {};
|
|
85
|
+
this.tempoForNextLine = [];
|
|
88
86
|
this.havent_set_length = true;
|
|
89
87
|
this.voices = {};
|
|
90
88
|
this.staves = [];
|
|
91
89
|
this.macros = {};
|
|
92
90
|
this.currBarNumber = 1;
|
|
93
91
|
this.barCounter = {};
|
|
94
|
-
this.inTextBlock = false;
|
|
95
|
-
this.inPsBlock = false;
|
|
96
92
|
this.ignoredDecorations = [];
|
|
97
|
-
this.textBlock = "";
|
|
98
93
|
this.score_is_present = false; // Can't have original V: lines when there is the score directive
|
|
99
94
|
this.inEnding = false;
|
|
100
95
|
this.inTie = [];
|
|
@@ -106,6 +101,7 @@ var Parse = function() {
|
|
|
106
101
|
this.volumePosition = "auto";
|
|
107
102
|
this.openSlurs = [];
|
|
108
103
|
this.freegchord = false;
|
|
104
|
+
this.endingHoldOver = {};
|
|
109
105
|
},
|
|
110
106
|
differentFont: function(type, defaultFonts) {
|
|
111
107
|
if (this[type].decoration !== defaultFonts[type].decoration) return true;
|
|
@@ -134,7 +130,41 @@ var Parse = function() {
|
|
|
134
130
|
if (this.differentFont("measurefont", defaultFonts)) addFont(el, 'measurefont', this.measurefont);
|
|
135
131
|
if (this.differentFont("repeatfont", defaultFonts)) addFont(el, 'repeatfont', this.repeatfont);
|
|
136
132
|
}
|
|
137
|
-
}
|
|
133
|
+
},
|
|
134
|
+
duplicateStartEndingHoldOvers: function() {
|
|
135
|
+
this.endingHoldOver = {
|
|
136
|
+
inTie: [],
|
|
137
|
+
inTieChord: {}
|
|
138
|
+
};
|
|
139
|
+
for (var i = 0; i < this.inTie.length; i++) {
|
|
140
|
+
this.endingHoldOver.inTie.push([]);
|
|
141
|
+
if (this.inTie[i]) { // if a voice is suppressed there might be a gap in the array.
|
|
142
|
+
for (var j = 0; j < this.inTie[i].length; j++) {
|
|
143
|
+
this.endingHoldOver.inTie[i].push(this.inTie[i][j]);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
for (var key in this.inTieChord) {
|
|
148
|
+
if (this.inTieChord.hasOwnProperty(key))
|
|
149
|
+
this.endingHoldOver.inTieChord[key] = this.inTieChord[key];
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
restoreStartEndingHoldOvers: function() {
|
|
153
|
+
if (!this.endingHoldOver.inTie)
|
|
154
|
+
return;
|
|
155
|
+
this.inTie = [];
|
|
156
|
+
this.inTieChord = {};
|
|
157
|
+
for (var i = 0; i < this.endingHoldOver.inTie.length; i++) {
|
|
158
|
+
this.inTie.push([]);
|
|
159
|
+
for (var j = 0; j < this.endingHoldOver.inTie[i].length; j++) {
|
|
160
|
+
this.inTie[i].push(this.endingHoldOver.inTie[i][j]);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
for (var key in this.endingHoldOver.inTieChord) {
|
|
164
|
+
if (this.endingHoldOver.inTieChord.hasOwnProperty(key))
|
|
165
|
+
this.inTieChord[key] = this.endingHoldOver.inTieChord[key];
|
|
166
|
+
}
|
|
167
|
+
},
|
|
138
168
|
};
|
|
139
169
|
|
|
140
170
|
var addWarning = function(str) {
|
|
@@ -161,13 +191,13 @@ var Parse = function() {
|
|
|
161
191
|
var bad_char = line.charAt(col_num);
|
|
162
192
|
if (bad_char === ' ')
|
|
163
193
|
bad_char = "SPACE";
|
|
164
|
-
var clean_line = encode(line.substring(
|
|
165
|
-
|
|
166
|
-
encode(line.substring(col_num+1));
|
|
167
|
-
addWarning("Music Line:" + tune.getNumLines() + ":" + (col_num+1) + ': ' + str + ": " + clean_line);
|
|
194
|
+
var clean_line = encode(line.substring(col_num - 64, col_num)) + '<span style="text-decoration:underline;font-size:1.3em;font-weight:bold;">' + bad_char + '</span>' + encode(line.substring(col_num + 1).substring(0,64));
|
|
195
|
+
addWarning("Music Line:" + tokenizer.lineIndex + ":" + (col_num+1) + ': ' + str + ": " + clean_line);
|
|
168
196
|
addWarningObject({message:str, line:line, startChar: multilineVars.iChar + col_num, column: col_num});
|
|
169
197
|
};
|
|
170
|
-
|
|
198
|
+
|
|
199
|
+
var header;
|
|
200
|
+
var music;
|
|
171
201
|
|
|
172
202
|
this.getWarnings = function() {
|
|
173
203
|
return multilineVars.warnings;
|
|
@@ -176,300 +206,14 @@ var Parse = function() {
|
|
|
176
206
|
return multilineVars.warningObjects;
|
|
177
207
|
};
|
|
178
208
|
|
|
179
|
-
var
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
var chord = tokenizer.getBrackettedSubstring(line, i, 5);
|
|
184
|
-
if (!chord[2])
|
|
185
|
-
warn("Missing the closing quote while parsing the chord symbol", line , i);
|
|
186
|
-
// If it starts with ^, then the chord appears above.
|
|
187
|
-
// If it starts with _ then the chord appears below.
|
|
188
|
-
// (note that the 2.0 draft standard defines them as not chords, but annotations and also defines @.)
|
|
189
|
-
if (chord[0] > 0 && chord[1].length > 0 && chord[1].charAt(0) === '^') {
|
|
190
|
-
chord[1] = chord[1].substring(1);
|
|
191
|
-
chord[2] = 'above';
|
|
192
|
-
} else if (chord[0] > 0 && chord[1].length > 0 && chord[1].charAt(0) === '_') {
|
|
193
|
-
chord[1] = chord[1].substring(1);
|
|
194
|
-
chord[2] = 'below';
|
|
195
|
-
} else if (chord[0] > 0 && chord[1].length > 0 && chord[1].charAt(0) === '<') {
|
|
196
|
-
chord[1] = chord[1].substring(1);
|
|
197
|
-
chord[2] = 'left';
|
|
198
|
-
} else if (chord[0] > 0 && chord[1].length > 0 && chord[1].charAt(0) === '>') {
|
|
199
|
-
chord[1] = chord[1].substring(1);
|
|
200
|
-
chord[2] = 'right';
|
|
201
|
-
} else if (chord[0] > 0 && chord[1].length > 0 && chord[1].charAt(0) === '@') {
|
|
202
|
-
// @-15,5.7
|
|
203
|
-
chord[1] = chord[1].substring(1);
|
|
204
|
-
var x = tokenizer.getFloat(chord[1]);
|
|
205
|
-
if (x.digits === 0)
|
|
206
|
-
warn("Missing first position in absolutely positioned annotation.", line , i);
|
|
207
|
-
chord[1] = chord[1].substring(x.digits);
|
|
208
|
-
if (chord[1][0] !== ',')
|
|
209
|
-
warn("Missing comma absolutely positioned annotation.", line , i);
|
|
210
|
-
chord[1] = chord[1].substring(1);
|
|
211
|
-
var y = tokenizer.getFloat(chord[1]);
|
|
212
|
-
if (y.digits === 0)
|
|
213
|
-
warn("Missing second position in absolutely positioned annotation.", line , i);
|
|
214
|
-
chord[1] = chord[1].substring(y.digits);
|
|
215
|
-
var ws = tokenizer.skipWhiteSpace(chord[1]);
|
|
216
|
-
chord[1] = chord[1].substring(ws);
|
|
217
|
-
chord[2] = null;
|
|
218
|
-
chord[3] = { x: x.value, y: y.value };
|
|
219
|
-
} else {
|
|
220
|
-
if (multilineVars.freegchord !== true) {
|
|
221
|
-
chord[1] = chord[1].replace(/([ABCDEFG0-9])b/g, "$1♭");
|
|
222
|
-
chord[1] = chord[1].replace(/([ABCDEFG0-9])#/g, "$1♯");
|
|
223
|
-
}
|
|
224
|
-
chord[2] = 'default';
|
|
225
|
-
chord[1] = transpose.chordName(multilineVars, chord[1]);
|
|
226
|
-
}
|
|
227
|
-
return chord;
|
|
228
|
-
}
|
|
229
|
-
return [0, ""];
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
var legalAccents = [ "trill", "lowermordent", "uppermordent", "mordent", "pralltriller", "accent",
|
|
233
|
-
"fermata", "invertedfermata", "tenuto", "0", "1", "2", "3", "4", "5", "+", "wedge",
|
|
234
|
-
"open", "thumb", "snap", "turn", "roll", "breath", "shortphrase", "mediumphrase", "longphrase",
|
|
235
|
-
"segno", "coda", "D.S.", "D.C.", "fine",
|
|
236
|
-
"slide", "^", "marcato",
|
|
237
|
-
"upbow", "downbow", "/", "//", "///", "////", "trem1", "trem2", "trem3", "trem4",
|
|
238
|
-
"turnx", "invertedturn", "invertedturnx", "trill(", "trill)", "arpeggio", "xstem", "mark", "umarcato",
|
|
239
|
-
"style=normal", "style=harmonic", "style=rhythm", "style=x"
|
|
240
|
-
];
|
|
241
|
-
var volumeDecorations = [ "p", "pp", "f", "ff", "mf", "mp", "ppp", "pppp", "fff", "ffff", "sfz" ];
|
|
242
|
-
var dynamicDecorations = ["crescendo(", "crescendo)", "diminuendo(", "diminuendo)"];
|
|
243
|
-
|
|
244
|
-
var accentPseudonyms = [ ["<", "accent"], [">", "accent"], ["tr", "trill"],
|
|
245
|
-
["plus", "+"], [ "emphasis", "accent"],
|
|
246
|
-
[ "^", "umarcato" ], [ "marcato", "umarcato" ] ];
|
|
247
|
-
var accentDynamicPseudonyms = [ ["<(", "crescendo("], ["<)", "crescendo)"],
|
|
248
|
-
[">(", "diminuendo("], [">)", "diminuendo)"] ];
|
|
249
|
-
var letter_to_accent = function(line, i)
|
|
250
|
-
{
|
|
251
|
-
var macro = multilineVars.macros[line.charAt(i)];
|
|
252
|
-
|
|
253
|
-
if (macro !== undefined) {
|
|
254
|
-
if (macro.charAt(0) === '!' || macro.charAt(0) === '+')
|
|
255
|
-
macro = macro.substring(1);
|
|
256
|
-
if (macro.charAt(macro.length-1) === '!' || macro.charAt(macro.length-1) === '+')
|
|
257
|
-
macro = macro.substring(0, macro.length-1);
|
|
258
|
-
if (parseCommon.detect(legalAccents, function(acc) {
|
|
259
|
-
return (macro === acc);
|
|
260
|
-
}))
|
|
261
|
-
return [ 1, macro ];
|
|
262
|
-
else if (parseCommon.detect(volumeDecorations, function(acc) {
|
|
263
|
-
return (macro === acc);
|
|
264
|
-
})) {
|
|
265
|
-
if (multilineVars.volumePosition === 'hidden')
|
|
266
|
-
macro = "";
|
|
267
|
-
return [1, macro];
|
|
268
|
-
} else if (parseCommon.detect(dynamicDecorations, function(acc) {
|
|
269
|
-
if (multilineVars.dynamicPosition === 'hidden')
|
|
270
|
-
macro = "";
|
|
271
|
-
return (macro === acc);
|
|
272
|
-
})) {
|
|
273
|
-
return [1, macro];
|
|
274
|
-
} else {
|
|
275
|
-
if (!parseCommon.detect(multilineVars.ignoredDecorations, function(dec) {
|
|
276
|
-
return (macro === dec);
|
|
277
|
-
}))
|
|
278
|
-
warn("Unknown macro: " + macro, line, i);
|
|
279
|
-
return [1, '' ];
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
switch (line.charAt(i))
|
|
283
|
-
{
|
|
284
|
-
case '.':return [1, 'staccato'];
|
|
285
|
-
case 'u':return [1, 'upbow'];
|
|
286
|
-
case 'v':return [1, 'downbow'];
|
|
287
|
-
case '~':return [1, 'irishroll'];
|
|
288
|
-
case '!':
|
|
289
|
-
case '+':
|
|
290
|
-
var ret = tokenizer.getBrackettedSubstring(line, i, 5);
|
|
291
|
-
// Be sure that the accent is recognizable.
|
|
292
|
-
if (ret[1].length > 0 && (ret[1].charAt(0) === '^' || ret[1].charAt(0) ==='_'))
|
|
293
|
-
ret[1] = ret[1].substring(1); // TODO-PER: The test files have indicators forcing the ornament to the top or bottom, but that isn't in the standard. We'll just ignore them.
|
|
294
|
-
if (parseCommon.detect(legalAccents, function(acc) {
|
|
295
|
-
return (ret[1] === acc);
|
|
296
|
-
}))
|
|
297
|
-
return ret;
|
|
298
|
-
if (parseCommon.detect(volumeDecorations, function(acc) {
|
|
299
|
-
return (ret[1] === acc);
|
|
300
|
-
})) {
|
|
301
|
-
if (multilineVars.volumePosition === 'hidden' )
|
|
302
|
-
ret[1] = '';
|
|
303
|
-
return ret;
|
|
304
|
-
}
|
|
305
|
-
if (parseCommon.detect(dynamicDecorations, function(acc) {
|
|
306
|
-
return (ret[1] === acc);
|
|
307
|
-
})) {
|
|
308
|
-
if (multilineVars.dynamicPosition === 'hidden' )
|
|
309
|
-
ret[1] = '';
|
|
310
|
-
return ret;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
if (parseCommon.detect(accentPseudonyms, function(acc) {
|
|
314
|
-
if (ret[1] === acc[0]) {
|
|
315
|
-
ret[1] = acc[1];
|
|
316
|
-
return true;
|
|
317
|
-
} else
|
|
318
|
-
return false;
|
|
319
|
-
}))
|
|
320
|
-
return ret;
|
|
321
|
-
|
|
322
|
-
if (parseCommon.detect(accentDynamicPseudonyms, function(acc) {
|
|
323
|
-
if (ret[1] === acc[0]) {
|
|
324
|
-
ret[1] = acc[1];
|
|
325
|
-
return true;
|
|
326
|
-
} else
|
|
327
|
-
return false;
|
|
328
|
-
})) {
|
|
329
|
-
if (multilineVars.dynamicPosition === 'hidden' )
|
|
330
|
-
ret[1] = '';
|
|
331
|
-
return ret;
|
|
332
|
-
}
|
|
333
|
-
// We didn't find the accent in the list, so consume the space, but don't return an accent.
|
|
334
|
-
// Although it is possible that ! was used as a line break, so accept that.
|
|
335
|
-
if (line.charAt(i) === '!' && (ret[0] === 1 || line.charAt(i+ret[0]-1) !== '!'))
|
|
336
|
-
return [1, null ];
|
|
337
|
-
warn("Unknown decoration: " + ret[1], line, i);
|
|
338
|
-
ret[1] = "";
|
|
339
|
-
return ret;
|
|
340
|
-
case 'H':return [1, 'fermata'];
|
|
341
|
-
case 'J':return [1, 'slide'];
|
|
342
|
-
case 'L':return [1, 'accent'];
|
|
343
|
-
case 'M':return [1, 'mordent'];
|
|
344
|
-
case 'O':return[1, 'coda'];
|
|
345
|
-
case 'P':return[1, 'pralltriller'];
|
|
346
|
-
case 'R':return [1, 'roll'];
|
|
347
|
-
case 'S':return [1, 'segno'];
|
|
348
|
-
case 'T':return [1, 'trill'];
|
|
349
|
-
}
|
|
350
|
-
return [0, 0];
|
|
351
|
-
};
|
|
352
|
-
|
|
353
|
-
var letter_to_spacer = function(line, i)
|
|
354
|
-
{
|
|
355
|
-
var start = i;
|
|
356
|
-
while (tokenizer.isWhiteSpace(line.charAt(i)))
|
|
357
|
-
i++;
|
|
358
|
-
return [ i-start ];
|
|
359
|
-
};
|
|
360
|
-
|
|
361
|
-
// returns the class of the bar line
|
|
362
|
-
// the number of the repeat
|
|
363
|
-
// and the number of characters used up
|
|
364
|
-
// if 0 is returned, then the next element was not a bar line
|
|
365
|
-
var letter_to_bar = function(line, curr_pos)
|
|
366
|
-
{
|
|
367
|
-
var ret = tokenizer.getBarLine(line, curr_pos);
|
|
368
|
-
if (ret.len === 0)
|
|
369
|
-
return [0,""];
|
|
370
|
-
if (ret.warn) {
|
|
371
|
-
warn(ret.warn, line, curr_pos);
|
|
372
|
-
return [ret.len,""];
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// Now see if this is a repeated ending
|
|
376
|
-
// A repeated ending is all of the characters 1,2,3,4,5,6,7,8,9,0,-, and comma
|
|
377
|
-
// It can also optionally start with '[', which is ignored.
|
|
378
|
-
// Also, it can have white space before the '['.
|
|
379
|
-
for (var ws = 0; ws < line.length; ws++)
|
|
380
|
-
if (line.charAt(curr_pos + ret.len + ws) !== ' ')
|
|
381
|
-
break;
|
|
382
|
-
var orig_bar_len = ret.len;
|
|
383
|
-
if (line.charAt(curr_pos+ret.len+ws) === '[') {
|
|
384
|
-
ret.len += ws + 1;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
// It can also be a quoted string. It is unclear whether that construct requires '[', but it seems like it would. otherwise it would be confused with a regular chord.
|
|
388
|
-
if (line.charAt(curr_pos+ret.len) === '"' && line.charAt(curr_pos+ret.len-1) === '[') {
|
|
389
|
-
var ending = tokenizer.getBrackettedSubstring(line, curr_pos+ret.len, 5);
|
|
390
|
-
return [ret.len+ending[0], ret.token, ending[1]];
|
|
391
|
-
}
|
|
392
|
-
var retRep = tokenizer.getTokenOf(line.substring(curr_pos+ret.len), "1234567890-,");
|
|
393
|
-
if (retRep.len === 0 || retRep.token[0] === '-')
|
|
394
|
-
return [orig_bar_len, ret.token];
|
|
395
|
-
|
|
396
|
-
return [ret.len+retRep.len, ret.token, retRep.token];
|
|
397
|
-
};
|
|
398
|
-
|
|
399
|
-
var tripletQ = {
|
|
400
|
-
2: 3,
|
|
401
|
-
3: 2,
|
|
402
|
-
4: 3,
|
|
403
|
-
5: 2, // TODO-PER: not handling 6/8 rhythm yet
|
|
404
|
-
6: 2,
|
|
405
|
-
7: 2, // TODO-PER: not handling 6/8 rhythm yet
|
|
406
|
-
8: 3,
|
|
407
|
-
9: 2 // TODO-PER: not handling 6/8 rhythm yet
|
|
408
|
-
};
|
|
409
|
-
var letter_to_open_slurs_and_triplets = function(line, i) {
|
|
410
|
-
// consume spaces, and look for all the open parens. If there is a number after the open paren,
|
|
411
|
-
// that is a triplet. Otherwise that is a slur. Collect all the slurs and the first triplet.
|
|
412
|
-
var ret = {};
|
|
413
|
-
var start = i;
|
|
414
|
-
while (line.charAt(i) === '(' || tokenizer.isWhiteSpace(line.charAt(i))) {
|
|
415
|
-
if (line.charAt(i) === '(') {
|
|
416
|
-
if (i+1 < line.length && (line.charAt(i+1) >= '2' && line.charAt(i+1) <= '9')) {
|
|
417
|
-
if (ret.triplet !== undefined)
|
|
418
|
-
warn("Can't nest triplets", line, i);
|
|
419
|
-
else {
|
|
420
|
-
ret.triplet = line.charAt(i+1) - '0';
|
|
421
|
-
ret.tripletQ = tripletQ[ret.triplet];
|
|
422
|
-
ret.num_notes = ret.triplet;
|
|
423
|
-
if (i+2 < line.length && line.charAt(i+2) === ':') {
|
|
424
|
-
// We are expecting "(p:q:r" or "(p:q" or "(p::r"
|
|
425
|
-
// That is: "put p notes into the time of q for the next r notes"
|
|
426
|
-
// if r is missing, then it is equal to p.
|
|
427
|
-
// if q is missing, it is determined from this table:
|
|
428
|
-
// (2 notes in the time of 3
|
|
429
|
-
// (3 notes in the time of 2
|
|
430
|
-
// (4 notes in the time of 3
|
|
431
|
-
// (5 notes in the time of n | if time sig is (6/8, 9/8, 12/8), n=3, else n=2
|
|
432
|
-
// (6 notes in the time of 2
|
|
433
|
-
// (7 notes in the time of n
|
|
434
|
-
// (8 notes in the time of 3
|
|
435
|
-
// (9 notes in the time of n
|
|
436
|
-
if (i+3 < line.length && line.charAt(i+3) === ':') {
|
|
437
|
-
// The second number, 'q', is not present.
|
|
438
|
-
if (i+4 < line.length && (line.charAt(i+4) >= '1' && line.charAt(i+4) <= '9')) {
|
|
439
|
-
ret.num_notes = line.charAt(i+4) - '0';
|
|
440
|
-
i += 3;
|
|
441
|
-
} else
|
|
442
|
-
warn("expected number after the two colons after the triplet to mark the duration", line, i);
|
|
443
|
-
} else if (i+3 < line.length && (line.charAt(i+3) >= '1' && line.charAt(i+3) <= '9')) {
|
|
444
|
-
ret.tripletQ = line.charAt(i+3) - '0';
|
|
445
|
-
if (i+4 < line.length && line.charAt(i+4) === ':') {
|
|
446
|
-
if (i+5 < line.length && (line.charAt(i+5) >= '1' && line.charAt(i+5) <= '9')) {
|
|
447
|
-
ret.num_notes = line.charAt(i+5) - '0';
|
|
448
|
-
i += 4;
|
|
449
|
-
}
|
|
450
|
-
} else {
|
|
451
|
-
i += 2;
|
|
452
|
-
}
|
|
453
|
-
} else
|
|
454
|
-
warn("expected number after the triplet to mark the duration", line, i);
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
i++;
|
|
458
|
-
}
|
|
459
|
-
else {
|
|
460
|
-
if (ret.startSlur === undefined)
|
|
461
|
-
ret.startSlur = 1;
|
|
462
|
-
else
|
|
463
|
-
ret.startSlur++;
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
i++;
|
|
209
|
+
var addWords = function(line, words) {
|
|
210
|
+
if (words.indexOf('\x12') >= 0) {
|
|
211
|
+
wordsContinuation += words
|
|
212
|
+
return
|
|
467
213
|
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
};
|
|
214
|
+
words = wordsContinuation + words
|
|
215
|
+
wordsContinuation = ''
|
|
471
216
|
|
|
472
|
-
var addWords = function(line, words) {
|
|
473
217
|
if (!line) { warn("Can't add words before the first line of music", line, 0); return; }
|
|
474
218
|
words = parseCommon.strip(words);
|
|
475
219
|
if (words.charAt(words.length-1) !== '-')
|
|
@@ -554,6 +298,13 @@ var Parse = function() {
|
|
|
554
298
|
};
|
|
555
299
|
|
|
556
300
|
var addSymbols = function(line, words) {
|
|
301
|
+
if (words.indexOf('\x12') >= 0) {
|
|
302
|
+
symbolContinuation += words
|
|
303
|
+
return
|
|
304
|
+
}
|
|
305
|
+
words = symbolContinuation + words
|
|
306
|
+
symbolContinuation = ''
|
|
307
|
+
|
|
557
308
|
// TODO-PER: Currently copied from w: line. This needs to be read as symbols instead.
|
|
558
309
|
if (!line) { warn("Can't add symbols before the first line of music", line, 0); return; }
|
|
559
310
|
words = parseCommon.strip(words);
|
|
@@ -630,958 +381,43 @@ var Parse = function() {
|
|
|
630
381
|
});
|
|
631
382
|
};
|
|
632
383
|
|
|
633
|
-
var
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
if (
|
|
637
|
-
|
|
638
|
-
else
|
|
639
|
-
return [1, 1.5, 0.5];
|
|
640
|
-
break;
|
|
641
|
-
case '<':
|
|
642
|
-
if (index < line.length - 1 && line.charAt(index+1) === '<') // double <<
|
|
643
|
-
return [2, 0.25, 1.75];
|
|
644
|
-
else
|
|
645
|
-
return [1, 0.5, 1.5];
|
|
646
|
-
break;
|
|
647
|
-
}
|
|
648
|
-
return null;
|
|
649
|
-
};
|
|
650
|
-
|
|
651
|
-
// TODO-PER: make this a method in el.
|
|
652
|
-
var addEndBeam = function(el) {
|
|
653
|
-
if (el.duration !== undefined && el.duration < 0.25)
|
|
654
|
-
el.end_beam = true;
|
|
655
|
-
return el;
|
|
656
|
-
};
|
|
657
|
-
|
|
658
|
-
var pitches = {A: 5, B: 6, C: 0, D: 1, E: 2, F: 3, G: 4, a: 12, b: 13, c: 7, d: 8, e: 9, f: 10, g: 11};
|
|
659
|
-
var rests = {x: 'invisible', y: 'spacer', z: 'rest', Z: 'multimeasure' };
|
|
660
|
-
var getCoreNote = function(line, index, el, canHaveBrokenRhythm) {
|
|
661
|
-
//var el = { startChar: index };
|
|
662
|
-
var isComplete = function(state) {
|
|
663
|
-
return (state === 'octave' || state === 'duration' || state === 'Zduration' || state === 'broken_rhythm' || state === 'end_slur');
|
|
664
|
-
};
|
|
665
|
-
var state = 'startSlur';
|
|
666
|
-
var durationSetByPreviousNote = false;
|
|
667
|
-
while (1) {
|
|
668
|
-
switch(line.charAt(index)) {
|
|
669
|
-
case '(':
|
|
670
|
-
if (state === 'startSlur') {
|
|
671
|
-
if (el.startSlur === undefined) el.startSlur = 1; else el.startSlur++;
|
|
672
|
-
} else if (isComplete(state)) {el.endChar = index;return el;}
|
|
673
|
-
else return null;
|
|
674
|
-
break;
|
|
675
|
-
case ')':
|
|
676
|
-
if (isComplete(state)) {
|
|
677
|
-
if (el.endSlur === undefined) el.endSlur = 1; else el.endSlur++;
|
|
678
|
-
} else return null;
|
|
679
|
-
break;
|
|
680
|
-
case '^':
|
|
681
|
-
if (state === 'startSlur') {el.accidental = 'sharp';state = 'sharp2';}
|
|
682
|
-
else if (state === 'sharp2') {el.accidental = 'dblsharp';state = 'pitch';}
|
|
683
|
-
else if (isComplete(state)) {el.endChar = index;return el;}
|
|
684
|
-
else return null;
|
|
685
|
-
break;
|
|
686
|
-
case '_':
|
|
687
|
-
if (state === 'startSlur') {el.accidental = 'flat';state = 'flat2';}
|
|
688
|
-
else if (state === 'flat2') {el.accidental = 'dblflat';state = 'pitch';}
|
|
689
|
-
else if (isComplete(state)) {el.endChar = index;return el;}
|
|
690
|
-
else return null;
|
|
691
|
-
break;
|
|
692
|
-
case '=':
|
|
693
|
-
if (state === 'startSlur') {el.accidental = 'natural';state = 'pitch';}
|
|
694
|
-
else if (isComplete(state)) {el.endChar = index;return el;}
|
|
695
|
-
else return null;
|
|
696
|
-
break;
|
|
697
|
-
case 'A':
|
|
698
|
-
case 'B':
|
|
699
|
-
case 'C':
|
|
700
|
-
case 'D':
|
|
701
|
-
case 'E':
|
|
702
|
-
case 'F':
|
|
703
|
-
case 'G':
|
|
704
|
-
case 'a':
|
|
705
|
-
case 'b':
|
|
706
|
-
case 'c':
|
|
707
|
-
case 'd':
|
|
708
|
-
case 'e':
|
|
709
|
-
case 'f':
|
|
710
|
-
case 'g':
|
|
711
|
-
if (state === 'startSlur' || state === 'sharp2' || state === 'flat2' || state === 'pitch') {
|
|
712
|
-
el.pitch = pitches[line.charAt(index)];
|
|
713
|
-
transpose.note(multilineVars, el);
|
|
714
|
-
state = 'octave';
|
|
715
|
-
// At this point we have a valid note. The rest is optional. Set the duration in case we don't get one below
|
|
716
|
-
if (canHaveBrokenRhythm && multilineVars.next_note_duration !== 0) {
|
|
717
|
-
el.duration = multilineVars.default_length * multilineVars.next_note_duration;
|
|
718
|
-
multilineVars.next_note_duration = 0;
|
|
719
|
-
durationSetByPreviousNote = true;
|
|
720
|
-
} else
|
|
721
|
-
el.duration = multilineVars.default_length;
|
|
722
|
-
// If the clef is percussion, there is probably some translation of the pitch to a particular drum kit item.
|
|
723
|
-
if ((multilineVars.clef && multilineVars.clef.type === "perc") ||
|
|
724
|
-
(multilineVars.currentVoice && multilineVars.currentVoice.clef === "perc")) {
|
|
725
|
-
var key = line.charAt(index);
|
|
726
|
-
if (el.accidental) {
|
|
727
|
-
var accMap = { 'dblflat': '__', 'flat': '_', 'natural': '=', 'sharp': '^', 'dblsharp': '^^'};
|
|
728
|
-
key = accMap[el.accidental] + key;
|
|
729
|
-
}
|
|
730
|
-
if (tune.formatting && tune.formatting.midi && tune.formatting.midi.drummap)
|
|
731
|
-
el.midipitch = tune.formatting.midi.drummap[key];
|
|
732
|
-
}
|
|
733
|
-
} else if (isComplete(state)) {el.endChar = index;return el;}
|
|
734
|
-
else return null;
|
|
735
|
-
break;
|
|
736
|
-
case ',':
|
|
737
|
-
if (state === 'octave') {el.pitch -= 7;}
|
|
738
|
-
else if (isComplete(state)) {el.endChar = index;return el;}
|
|
739
|
-
else return null;
|
|
740
|
-
break;
|
|
741
|
-
case '\'':
|
|
742
|
-
if (state === 'octave') {el.pitch += 7;}
|
|
743
|
-
else if (isComplete(state)) {el.endChar = index;return el;}
|
|
744
|
-
else return null;
|
|
745
|
-
break;
|
|
746
|
-
case 'x':
|
|
747
|
-
case 'y':
|
|
748
|
-
case 'z':
|
|
749
|
-
case 'Z':
|
|
750
|
-
if (state === 'startSlur') {
|
|
751
|
-
el.rest = { type: rests[line.charAt(index)] };
|
|
752
|
-
// There shouldn't be some of the properties that notes have. If some sneak in due to bad syntax in the abc file,
|
|
753
|
-
// just nix them here.
|
|
754
|
-
delete el.accidental;
|
|
755
|
-
delete el.startSlur;
|
|
756
|
-
delete el.startTie;
|
|
757
|
-
delete el.endSlur;
|
|
758
|
-
delete el.endTie;
|
|
759
|
-
delete el.end_beam;
|
|
760
|
-
delete el.grace_notes;
|
|
761
|
-
// At this point we have a valid note. The rest is optional. Set the duration in case we don't get one below
|
|
762
|
-
if (el.rest.type === 'multimeasure') {
|
|
763
|
-
el.duration = 1;
|
|
764
|
-
state = 'Zduration';
|
|
765
|
-
} else {
|
|
766
|
-
if (canHaveBrokenRhythm && multilineVars.next_note_duration !== 0) {
|
|
767
|
-
el.duration = multilineVars.default_length * multilineVars.next_note_duration;
|
|
768
|
-
multilineVars.next_note_duration = 0;
|
|
769
|
-
durationSetByPreviousNote = true;
|
|
770
|
-
} else
|
|
771
|
-
el.duration = multilineVars.default_length;
|
|
772
|
-
state = 'duration';
|
|
773
|
-
}
|
|
774
|
-
} else if (isComplete(state)) {el.endChar = index;return el;}
|
|
775
|
-
else return null;
|
|
776
|
-
break;
|
|
777
|
-
case '1':
|
|
778
|
-
case '2':
|
|
779
|
-
case '3':
|
|
780
|
-
case '4':
|
|
781
|
-
case '5':
|
|
782
|
-
case '6':
|
|
783
|
-
case '7':
|
|
784
|
-
case '8':
|
|
785
|
-
case '9':
|
|
786
|
-
case '0':
|
|
787
|
-
case '/':
|
|
788
|
-
if (state === 'octave' || state === 'duration') {
|
|
789
|
-
var fraction = tokenizer.getFraction(line, index);
|
|
790
|
-
//if (!durationSetByPreviousNote)
|
|
791
|
-
el.duration = el.duration * fraction.value;
|
|
792
|
-
// TODO-PER: We can test the returned duration here and give a warning if it isn't the one expected.
|
|
793
|
-
el.endChar = fraction.index;
|
|
794
|
-
while (fraction.index < line.length && (tokenizer.isWhiteSpace(line.charAt(fraction.index)) || line.charAt(fraction.index) === '-')) {
|
|
795
|
-
if (line.charAt(fraction.index) === '-')
|
|
796
|
-
el.startTie = {};
|
|
797
|
-
else
|
|
798
|
-
el = addEndBeam(el);
|
|
799
|
-
fraction.index++;
|
|
800
|
-
}
|
|
801
|
-
index = fraction.index-1;
|
|
802
|
-
state = 'broken_rhythm';
|
|
803
|
-
} else if (state === 'sharp2') {
|
|
804
|
-
el.accidental = 'quartersharp';state = 'pitch';
|
|
805
|
-
} else if (state === 'flat2') {
|
|
806
|
-
el.accidental = 'quarterflat';state = 'pitch';
|
|
807
|
-
} else if (state === 'Zduration') {
|
|
808
|
-
var num = tokenizer.getNumber(line, index);
|
|
809
|
-
el.duration = num.num;
|
|
810
|
-
el.endChar = num.index;
|
|
811
|
-
return el;
|
|
812
|
-
} else return null;
|
|
813
|
-
break;
|
|
814
|
-
case '-':
|
|
815
|
-
if (state === 'startSlur') {
|
|
816
|
-
// This is the first character, so it must have been meant for the previous note. Correct that here.
|
|
817
|
-
tune.addTieToLastNote();
|
|
818
|
-
el.endTie = true;
|
|
819
|
-
} else if (state === 'octave' || state === 'duration' || state === 'end_slur') {
|
|
820
|
-
el.startTie = {};
|
|
821
|
-
if (!durationSetByPreviousNote && canHaveBrokenRhythm)
|
|
822
|
-
state = 'broken_rhythm';
|
|
823
|
-
else {
|
|
824
|
-
// Peek ahead to the next character. If it is a space, then we have an end beam.
|
|
825
|
-
if (tokenizer.isWhiteSpace(line.charAt(index + 1)))
|
|
826
|
-
addEndBeam(el);
|
|
827
|
-
el.endChar = index+1;
|
|
828
|
-
return el;
|
|
829
|
-
}
|
|
830
|
-
} else if (state === 'broken_rhythm') {el.endChar = index;return el;}
|
|
831
|
-
else return null;
|
|
832
|
-
break;
|
|
833
|
-
case ' ':
|
|
834
|
-
case '\t':
|
|
835
|
-
if (isComplete(state)) {
|
|
836
|
-
el.end_beam = true;
|
|
837
|
-
// look ahead to see if there is a tie
|
|
838
|
-
do {
|
|
839
|
-
if (line.charAt(index) === '-')
|
|
840
|
-
el.startTie = {};
|
|
841
|
-
index++;
|
|
842
|
-
} while (index < line.length && (tokenizer.isWhiteSpace(line.charAt(index)) || line.charAt(index) === '-'));
|
|
843
|
-
el.endChar = index;
|
|
844
|
-
if (!durationSetByPreviousNote && canHaveBrokenRhythm && (line.charAt(index) === '<' || line.charAt(index) === '>')) { // TODO-PER: Don't need the test for < and >, but that makes the endChar work out for the regression test.
|
|
845
|
-
index--;
|
|
846
|
-
state = 'broken_rhythm';
|
|
847
|
-
} else
|
|
848
|
-
return el;
|
|
849
|
-
}
|
|
850
|
-
else return null;
|
|
851
|
-
break;
|
|
852
|
-
case '>':
|
|
853
|
-
case '<':
|
|
854
|
-
if (isComplete(state)) {
|
|
855
|
-
if (canHaveBrokenRhythm) {
|
|
856
|
-
var br2 = getBrokenRhythm(line, index);
|
|
857
|
-
index += br2[0] - 1; // index gets incremented below, so we'll let that happen
|
|
858
|
-
multilineVars.next_note_duration = br2[2];
|
|
859
|
-
el.duration = br2[1]*el.duration;
|
|
860
|
-
state = 'end_slur';
|
|
861
|
-
} else {
|
|
862
|
-
el.endChar = index;
|
|
863
|
-
return el;
|
|
864
|
-
}
|
|
865
|
-
} else
|
|
866
|
-
return null;
|
|
867
|
-
break;
|
|
868
|
-
default:
|
|
869
|
-
if (isComplete(state)) {
|
|
870
|
-
el.endChar = index;
|
|
871
|
-
return el;
|
|
872
|
-
}
|
|
873
|
-
return null;
|
|
874
|
-
}
|
|
875
|
-
index++;
|
|
876
|
-
if (index === line.length) {
|
|
877
|
-
if (isComplete(state)) {el.endChar = index;return el;}
|
|
878
|
-
else return null;
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
return null;
|
|
882
|
-
};
|
|
883
|
-
|
|
884
|
-
function startNewLine() {
|
|
885
|
-
var params = { startChar: -1, endChar: -1};
|
|
886
|
-
if (multilineVars.partForNextLine.title)
|
|
887
|
-
params.part = multilineVars.partForNextLine;
|
|
888
|
-
params.clef = multilineVars.currentVoice && multilineVars.staves[multilineVars.currentVoice.staffNum].clef !== undefined ? parseCommon.clone(multilineVars.staves[multilineVars.currentVoice.staffNum].clef) : parseCommon.clone(multilineVars.clef);
|
|
889
|
-
var scoreTranspose = multilineVars.currentVoice ? multilineVars.currentVoice.scoreTranspose : 0;
|
|
890
|
-
params.key = parseKeyVoice.standardKey(multilineVars.key.root+multilineVars.key.acc+multilineVars.key.mode, multilineVars.key.root, multilineVars.key.acc, scoreTranspose);
|
|
891
|
-
params.key.mode = multilineVars.key.mode;
|
|
892
|
-
if (multilineVars.key.impliedNaturals)
|
|
893
|
-
params.key.impliedNaturals = multilineVars.key.impliedNaturals;
|
|
894
|
-
if (multilineVars.key.explicitAccidentals) {
|
|
895
|
-
for (var i = 0; i < multilineVars.key.explicitAccidentals.length; i++) {
|
|
896
|
-
var found = false;
|
|
897
|
-
for (var j = 0; j < params.key.accidentals.length; j++) {
|
|
898
|
-
if (params.key.accidentals[j].note === multilineVars.key.explicitAccidentals[i].note) {
|
|
899
|
-
// If the note is already in the list, override it with the new value
|
|
900
|
-
params.key.accidentals[j].acc = multilineVars.key.explicitAccidentals[i].acc;
|
|
901
|
-
found = true;
|
|
902
|
-
}
|
|
903
|
-
}
|
|
904
|
-
if (!found)
|
|
905
|
-
params.key.accidentals.push(multilineVars.key.explicitAccidentals[i]);
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
multilineVars.targetKey = params.key;
|
|
909
|
-
if (params.key.explicitAccidentals)
|
|
910
|
-
delete params.key.explicitAccidentals;
|
|
911
|
-
parseKeyVoice.addPosToKey(params.clef, params.key);
|
|
912
|
-
if (multilineVars.meter !== null) {
|
|
913
|
-
if (multilineVars.currentVoice) {
|
|
914
|
-
parseCommon.each(multilineVars.staves, function(st) {
|
|
915
|
-
st.meter = multilineVars.meter;
|
|
916
|
-
});
|
|
917
|
-
params.meter = multilineVars.staves[multilineVars.currentVoice.staffNum].meter;
|
|
918
|
-
multilineVars.staves[multilineVars.currentVoice.staffNum].meter = null;
|
|
919
|
-
} else
|
|
920
|
-
params.meter = multilineVars.meter;
|
|
921
|
-
multilineVars.meter = null;
|
|
922
|
-
} else if (multilineVars.currentVoice && multilineVars.staves[multilineVars.currentVoice.staffNum].meter) {
|
|
923
|
-
// Make sure that each voice gets the meter marking.
|
|
924
|
-
params.meter = multilineVars.staves[multilineVars.currentVoice.staffNum].meter;
|
|
925
|
-
multilineVars.staves[multilineVars.currentVoice.staffNum].meter = null;
|
|
926
|
-
}
|
|
927
|
-
if (multilineVars.currentVoice && multilineVars.currentVoice.name)
|
|
928
|
-
params.name = multilineVars.currentVoice.name;
|
|
929
|
-
if (multilineVars.vocalfont)
|
|
930
|
-
params.vocalfont = multilineVars.vocalfont;
|
|
931
|
-
if (multilineVars.tripletfont)
|
|
932
|
-
params.tripletfont = multilineVars.tripletfont;
|
|
933
|
-
if (multilineVars.style)
|
|
934
|
-
params.style = multilineVars.style;
|
|
935
|
-
if (multilineVars.currentVoice) {
|
|
936
|
-
var staff = multilineVars.staves[multilineVars.currentVoice.staffNum];
|
|
937
|
-
if (staff.brace) params.brace = staff.brace;
|
|
938
|
-
if (staff.bracket) params.bracket = staff.bracket;
|
|
939
|
-
if (staff.connectBarLines) params.connectBarLines = staff.connectBarLines;
|
|
940
|
-
if (staff.name) params.name = staff.name[multilineVars.currentVoice.index];
|
|
941
|
-
if (staff.subname) params.subname = staff.subname[multilineVars.currentVoice.index];
|
|
942
|
-
if (multilineVars.currentVoice.stem)
|
|
943
|
-
params.stem = multilineVars.currentVoice.stem;
|
|
944
|
-
if (multilineVars.currentVoice.stafflines)
|
|
945
|
-
params.stafflines = multilineVars.currentVoice.stafflines;
|
|
946
|
-
if (multilineVars.currentVoice.staffscale)
|
|
947
|
-
params.staffscale = multilineVars.currentVoice.staffscale;
|
|
948
|
-
if (multilineVars.currentVoice.scale)
|
|
949
|
-
params.scale = multilineVars.currentVoice.scale;
|
|
950
|
-
if (multilineVars.currentVoice.style)
|
|
951
|
-
params.style = multilineVars.currentVoice.style;
|
|
952
|
-
if (multilineVars.currentVoice.transpose)
|
|
953
|
-
params.clef.transpose = multilineVars.currentVoice.transpose;
|
|
954
|
-
}
|
|
955
|
-
var isFirstVoice = multilineVars.currentVoice === undefined || (multilineVars.currentVoice.staffNum === 0 && multilineVars.currentVoice.index === 0);
|
|
956
|
-
if (multilineVars.barNumbers === 0 && isFirstVoice && multilineVars.currBarNumber !== 1)
|
|
957
|
-
params.barNumber = multilineVars.currBarNumber;
|
|
958
|
-
tune.startNewLine(params);
|
|
959
|
-
if (multilineVars.key.impliedNaturals)
|
|
960
|
-
delete multilineVars.key.impliedNaturals;
|
|
961
|
-
|
|
962
|
-
multilineVars.partForNextLine = {};
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
var letter_to_grace = function(line, i) {
|
|
966
|
-
// Grace notes are an array of: startslur, note, endslur, space; where note is accidental, pitch, duration
|
|
967
|
-
if (line.charAt(i) === '{') {
|
|
968
|
-
// fetch the gracenotes string and consume that into the array
|
|
969
|
-
var gra = tokenizer.getBrackettedSubstring(line, i, 1, '}');
|
|
970
|
-
if (!gra[2])
|
|
971
|
-
warn("Missing the closing '}' while parsing grace note", line, i);
|
|
972
|
-
// If there is a slur after the grace construction, then move it to the last note inside the grace construction
|
|
973
|
-
if (line[i+gra[0]] === ')') {
|
|
974
|
-
gra[0]++;
|
|
975
|
-
gra[1] += ')';
|
|
976
|
-
}
|
|
977
|
-
|
|
978
|
-
var gracenotes = [];
|
|
979
|
-
var ii = 0;
|
|
980
|
-
var inTie = false;
|
|
981
|
-
while (ii < gra[1].length) {
|
|
982
|
-
var acciaccatura = false;
|
|
983
|
-
if (gra[1].charAt(ii) === '/') {
|
|
984
|
-
acciaccatura = true;
|
|
985
|
-
ii++;
|
|
986
|
-
}
|
|
987
|
-
var note = getCoreNote(gra[1], ii, {}, false);
|
|
988
|
-
if (note !== null) {
|
|
989
|
-
// The grace note durations should not be affected by the default length: they should be based on 1/16, so if that isn't the default, then multiply here.
|
|
990
|
-
note.duration = note.duration / (multilineVars.default_length * 8);
|
|
991
|
-
if (acciaccatura)
|
|
992
|
-
note.acciaccatura = true;
|
|
993
|
-
gracenotes.push(note);
|
|
994
|
-
|
|
995
|
-
if (inTie) {
|
|
996
|
-
note.endTie = true;
|
|
997
|
-
inTie = false;
|
|
998
|
-
}
|
|
999
|
-
if (note.startTie)
|
|
1000
|
-
inTie = true;
|
|
1001
|
-
|
|
1002
|
-
ii = note.endChar;
|
|
1003
|
-
delete note.endChar;
|
|
1004
|
-
}
|
|
1005
|
-
else {
|
|
1006
|
-
// We shouldn't get anything but notes or a space here, so report an error
|
|
1007
|
-
if (gra[1].charAt(ii) === ' ') {
|
|
1008
|
-
if (gracenotes.length > 0)
|
|
1009
|
-
gracenotes[gracenotes.length-1].end_beam = true;
|
|
1010
|
-
} else
|
|
1011
|
-
warn("Unknown character '" + gra[1].charAt(ii) + "' while parsing grace note", line, i);
|
|
1012
|
-
ii++;
|
|
1013
|
-
}
|
|
1014
|
-
}
|
|
1015
|
-
if (gracenotes.length)
|
|
1016
|
-
return [gra[0], gracenotes];
|
|
1017
|
-
}
|
|
1018
|
-
return [ 0 ];
|
|
1019
|
-
};
|
|
1020
|
-
|
|
1021
|
-
function letter_to_overlay(line, i) {
|
|
1022
|
-
if (line.charAt(i) === '&') {
|
|
1023
|
-
var start = i;
|
|
1024
|
-
while (line.charAt(i) && line.charAt(i) !== ':' && line.charAt(i) !== '|')
|
|
1025
|
-
i++;
|
|
1026
|
-
return [ i-start, line.substring(start+1, i) ];
|
|
384
|
+
var parseLine = function(line) {
|
|
385
|
+
if (parseCommon.startsWith(line, '%%')) {
|
|
386
|
+
var err = parseDirective.addDirective(line.substring(2));
|
|
387
|
+
if (err) warn(err, line, 2);
|
|
388
|
+
return;
|
|
1027
389
|
}
|
|
1028
|
-
return [ 0 ];
|
|
1029
|
-
}
|
|
1030
|
-
|
|
1031
|
-
function durationOfMeasure(multilineVars) {
|
|
1032
|
-
// TODO-PER: This could be more complicated if one of the unusual measures is used.
|
|
1033
|
-
var meter = multilineVars.origMeter;
|
|
1034
|
-
if (!meter || meter.type !== 'specified')
|
|
1035
|
-
return 1;
|
|
1036
|
-
if (!meter.value || meter.value.length === 0)
|
|
1037
|
-
return 1;
|
|
1038
|
-
return parseInt(meter.value[0].num, 10) / parseInt(meter.value[0].den, 10);
|
|
1039
|
-
}
|
|
1040
|
-
|
|
1041
|
-
//
|
|
1042
|
-
// Parse line of music
|
|
1043
|
-
//
|
|
1044
|
-
// This is a stream of <(bar-marking|header|note-group)...> in any order, with optional spaces between each element
|
|
1045
|
-
// core-note is <open-slur, accidental, pitch:required, octave, duration, close-slur&|tie> with no spaces within that
|
|
1046
|
-
// chord is <open-bracket:required, core-note:required... close-bracket:required duration> with no spaces within that
|
|
1047
|
-
// grace-notes is <open-brace:required, (open-slur|core-note:required|close-slur)..., close-brace:required> spaces are allowed
|
|
1048
|
-
// note-group is <grace-notes, chord symbols&|decorations..., grace-notes, slur&|triplet, chord|core-note, end-slur|tie> spaces are allowed between items
|
|
1049
|
-
// bar-marking is <ampersand> or <chord symbols&|decorations..., bar:required> spaces allowed
|
|
1050
|
-
// header is <open-bracket:required, K|M|L|V:required, colon:required, field:required, close-bracket:required> spaces can occur between the colon, in the field, and before the close bracket
|
|
1051
|
-
// header can also be the only thing on a line. This is true even if it is a continuation line. In this case the brackets are not required.
|
|
1052
|
-
// a space is a back-tick, a space, or a tab. If it is a back-tick, then there is no end-beam.
|
|
1053
|
-
|
|
1054
|
-
// Line preprocessing: anything after a % is ignored (the double %% should have been taken care of before this)
|
|
1055
|
-
// Then, all leading and trailing spaces are ignored.
|
|
1056
|
-
// If there was a line continuation, the \n was replaced by a \r and the \ was replaced by a space. This allows the construct
|
|
1057
|
-
// of having a header mid-line conceptually, but actually be at the start of the line. This is equivolent to putting the header in [ ].
|
|
1058
390
|
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
391
|
+
var i = line.indexOf('%');
|
|
392
|
+
if (i >= 0)
|
|
393
|
+
line = line.substring(0, i);
|
|
394
|
+
line = line.replace(/\s+$/, '');
|
|
1062
395
|
|
|
1063
|
-
|
|
1064
|
-
// "[" can be the start of a chord, the start of a header element or part of a bar line.
|
|
1065
|
-
// --- if it is immediately followed by "|", it is a bar line
|
|
1066
|
-
// --- if it is immediately followed by K: L: M: V: it is a header (note: there are other headers mentioned in the standard, but I'm not sure how they would be used.)
|
|
1067
|
-
// --- otherwise it is the beginning of a chord
|
|
1068
|
-
// "(" can be the start of a slur or a triplet
|
|
1069
|
-
// --- if it is followed by a number from 2-9, then it is a triplet
|
|
1070
|
-
// --- otherwise it is a slur
|
|
1071
|
-
// "]"
|
|
1072
|
-
// --- if there is a chord open, then this is the close
|
|
1073
|
-
// --- if it is after a [|, then it is an invisible bar line
|
|
1074
|
-
// --- otherwise, it is par of a bar
|
|
1075
|
-
// "." can be a bar modifier or a slur modifier, or a decoration
|
|
1076
|
-
// --- if it comes immediately before a bar, it is a bar modifier
|
|
1077
|
-
// --- if it comes immediately before a slur, it is a slur modifier
|
|
1078
|
-
// --- otherwise it is a decoration for the next note.
|
|
1079
|
-
// number:
|
|
1080
|
-
// --- if it is after a bar, with no space, it is an ending marker
|
|
1081
|
-
// --- if it is after a ( with no space, it is a triplet count
|
|
1082
|
-
// --- if it is after a pitch or octave or slash, then it is a duration
|
|
1083
|
-
|
|
1084
|
-
// Unambiguous symbols (except inside quoted strings):
|
|
1085
|
-
// vertical-bar, colon: part of a bar
|
|
1086
|
-
// ABCDEFGabcdefg: pitch
|
|
1087
|
-
// xyzZ: rest
|
|
1088
|
-
// comma, prime: octave
|
|
1089
|
-
// close-paren: end-slur
|
|
1090
|
-
// hyphen: tie
|
|
1091
|
-
// tilde, v, u, bang, plus, THLMPSO: decoration
|
|
1092
|
-
// carat, underscore, equal: accidental
|
|
1093
|
-
// ampersand: time reset
|
|
1094
|
-
// open-curly, close-curly: grace notes
|
|
1095
|
-
// double-quote: chord symbol
|
|
1096
|
-
// less-than, greater-than, slash: duration
|
|
1097
|
-
// back-tick, space, tab: space
|
|
1098
|
-
var nonDecorations = "ABCDEFGabcdefgxyzZ[]|^_{"; // use this to prescreen so we don't have to look for a decoration at every note.
|
|
1099
|
-
|
|
1100
|
-
var parseRegularMusicLine = function(line) {
|
|
1101
|
-
header.resolveTempo();
|
|
1102
|
-
//multilineVars.havent_set_length = false; // To late to set this now.
|
|
1103
|
-
multilineVars.is_in_header = false; // We should have gotten a key header by now, but just in case, this is definitely out of the header.
|
|
1104
|
-
var i = 0;
|
|
1105
|
-
var startOfLine = multilineVars.iChar;
|
|
1106
|
-
// see if there is nothing but a comment on this line. If so, just ignore it. A full line comment is optional white space followed by %
|
|
1107
|
-
while (tokenizer.isWhiteSpace(line.charAt(i)) && i < line.length)
|
|
1108
|
-
i++;
|
|
1109
|
-
if (i === line.length || line.charAt(i) === '%')
|
|
396
|
+
if (line.length === 0)
|
|
1110
397
|
return;
|
|
1111
398
|
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
multilineVars.start_new_line = true;
|
|
1116
|
-
else
|
|
1117
|
-
multilineVars.start_new_line = false;
|
|
1118
|
-
var tripletNotesLeft = 0;
|
|
1119
|
-
|
|
1120
|
-
// See if the line starts with a header field
|
|
1121
|
-
var retHeader = header.letter_to_body_header(line, i);
|
|
1122
|
-
if (retHeader[0] > 0) {
|
|
1123
|
-
i += retHeader[0];
|
|
1124
|
-
if (retHeader[1] === 'V')
|
|
1125
|
-
delayStartNewLine = true; // fixes bug on this: c[V:2]d
|
|
1126
|
-
// TODO-PER: Handle inline headers
|
|
399
|
+
if (wordsContinuation) {
|
|
400
|
+
addWords(tuneBuilder.getCurrentVoice(), line.substring(2));
|
|
401
|
+
return
|
|
1127
402
|
}
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
while (i < line.length)
|
|
1132
|
-
{
|
|
1133
|
-
var startI = i;
|
|
1134
|
-
if (line.charAt(i) === '%')
|
|
1135
|
-
break;
|
|
1136
|
-
|
|
1137
|
-
var retInlineHeader = header.letter_to_inline_header(line, i);
|
|
1138
|
-
if (retInlineHeader[0] > 0) {
|
|
1139
|
-
i += retInlineHeader[0];
|
|
1140
|
-
if (retInlineHeader[1] === 'V')
|
|
1141
|
-
delayStartNewLine = true; // fixes bug on this: c[V:2]d
|
|
1142
|
-
// TODO-PER: Handle inline headers
|
|
1143
|
-
//multilineVars.start_new_line = false;
|
|
1144
|
-
} else {
|
|
1145
|
-
// Wait until here to actually start the line because we know we're past the inline statements.
|
|
1146
|
-
if (delayStartNewLine) {
|
|
1147
|
-
startNewLine();
|
|
1148
|
-
delayStartNewLine = false;
|
|
1149
|
-
}
|
|
1150
|
-
|
|
1151
|
-
// We need to decide if the following characters are a bar-marking or a note-group.
|
|
1152
|
-
// Unfortunately, that is ambiguous. Both can contain chord symbols and decorations.
|
|
1153
|
-
// If there is a grace note either before or after the chord symbols and decorations, then it is definitely a note-group.
|
|
1154
|
-
// If there is a bar marker, it is definitely a bar-marking.
|
|
1155
|
-
// If there is either a core-note or chord, it is definitely a note-group.
|
|
1156
|
-
// So, loop while we find grace-notes, chords-symbols, or decorations. [It is an error to have more than one grace-note group in a row; the others can be multiple]
|
|
1157
|
-
// Then, if there is a grace-note, we know where to go.
|
|
1158
|
-
// Else see if we have a chord, core-note, slur, triplet, or bar.
|
|
1159
|
-
|
|
1160
|
-
var ret;
|
|
1161
|
-
while (1) {
|
|
1162
|
-
ret = tokenizer.eatWhiteSpace(line, i);
|
|
1163
|
-
if (ret > 0) {
|
|
1164
|
-
i += ret;
|
|
1165
|
-
}
|
|
1166
|
-
if (i > 0 && line.charAt(i-1) === '\x12') {
|
|
1167
|
-
// there is one case where a line continuation isn't the same as being on the same line, and that is if the next character after it is a header.
|
|
1168
|
-
ret = header.letter_to_body_header(line, i);
|
|
1169
|
-
if (ret[0] > 0) {
|
|
1170
|
-
if (ret[1] === 'V')
|
|
1171
|
-
startNewLine(); // fixes bug on this: c\\nV:2]\\nd
|
|
1172
|
-
// TODO: insert header here
|
|
1173
|
-
i = ret[0];
|
|
1174
|
-
multilineVars.start_new_line = false;
|
|
1175
|
-
}
|
|
1176
|
-
}
|
|
1177
|
-
// gather all the grace notes, chord symbols and decorations
|
|
1178
|
-
ret = letter_to_spacer(line, i);
|
|
1179
|
-
if (ret[0] > 0) {
|
|
1180
|
-
i += ret[0];
|
|
1181
|
-
}
|
|
1182
|
-
|
|
1183
|
-
ret = letter_to_chord(line, i);
|
|
1184
|
-
if (ret[0] > 0) {
|
|
1185
|
-
// There could be more than one chord here if they have different positions.
|
|
1186
|
-
// If two chords have the same position, then connect them with newline.
|
|
1187
|
-
if (!el.chord)
|
|
1188
|
-
el.chord = [];
|
|
1189
|
-
var chordName = tokenizer.translateString(ret[1]);
|
|
1190
|
-
chordName = chordName.replace(/;/g, "\n");
|
|
1191
|
-
var addedChord = false;
|
|
1192
|
-
for (var ci = 0; ci < el.chord.length; ci++) {
|
|
1193
|
-
if (el.chord[ci].position === ret[2]) {
|
|
1194
|
-
addedChord = true;
|
|
1195
|
-
el.chord[ci].name += "\n" + chordName;
|
|
1196
|
-
}
|
|
1197
|
-
}
|
|
1198
|
-
if (addedChord === false) {
|
|
1199
|
-
if (ret[2] === null && ret[3])
|
|
1200
|
-
el.chord.push({name: chordName, rel_position: ret[3]});
|
|
1201
|
-
else
|
|
1202
|
-
el.chord.push({name: chordName, position: ret[2]});
|
|
1203
|
-
}
|
|
1204
|
-
|
|
1205
|
-
i += ret[0];
|
|
1206
|
-
var ii = tokenizer.skipWhiteSpace(line.substring(i));
|
|
1207
|
-
if (ii > 0)
|
|
1208
|
-
el.force_end_beam_last = true;
|
|
1209
|
-
i += ii;
|
|
1210
|
-
} else {
|
|
1211
|
-
if (nonDecorations.indexOf(line.charAt(i)) === -1)
|
|
1212
|
-
ret = letter_to_accent(line, i);
|
|
1213
|
-
else ret = [ 0 ];
|
|
1214
|
-
if (ret[0] > 0) {
|
|
1215
|
-
if (ret[1] === null) {
|
|
1216
|
-
if (i + 1 < line.length)
|
|
1217
|
-
startNewLine(); // There was a ! in the middle of the line. Start a new line if there is anything after it.
|
|
1218
|
-
} else if (ret[1].length > 0) {
|
|
1219
|
-
if (ret[1].indexOf("style=") === 0) {
|
|
1220
|
-
el.style = ret[1].substr(6);
|
|
1221
|
-
} else {
|
|
1222
|
-
if (el.decoration === undefined)
|
|
1223
|
-
el.decoration = [];
|
|
1224
|
-
el.decoration.push(ret[1]);
|
|
1225
|
-
}
|
|
1226
|
-
}
|
|
1227
|
-
i += ret[0];
|
|
1228
|
-
} else {
|
|
1229
|
-
ret = letter_to_grace(line, i);
|
|
1230
|
-
// TODO-PER: Be sure there aren't already grace notes defined. That is an error.
|
|
1231
|
-
if (ret[0] > 0) {
|
|
1232
|
-
el.gracenotes = ret[1];
|
|
1233
|
-
i += ret[0];
|
|
1234
|
-
} else
|
|
1235
|
-
break;
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1238
|
-
}
|
|
1239
|
-
|
|
1240
|
-
ret = letter_to_bar(line, i);
|
|
1241
|
-
if (ret[0] > 0) {
|
|
1242
|
-
// This is definitely a bar
|
|
1243
|
-
overlayLevel = 0;
|
|
1244
|
-
if (el.gracenotes !== undefined) {
|
|
1245
|
-
// Attach the grace note to an invisible note
|
|
1246
|
-
el.rest = { type: 'spacer' };
|
|
1247
|
-
el.duration = 0.125; // TODO-PER: I don't think the duration of this matters much, but figure out if it does.
|
|
1248
|
-
multilineVars.addFormattingOptions(el, tune.formatting, 'note');
|
|
1249
|
-
tune.appendElement('note', startOfLine+i, startOfLine+i+ret[0], el);
|
|
1250
|
-
multilineVars.measureNotEmpty = true;
|
|
1251
|
-
el = {};
|
|
1252
|
-
}
|
|
1253
|
-
var bar = {type: ret[1]};
|
|
1254
|
-
if (bar.type.length === 0)
|
|
1255
|
-
warn("Unknown bar type", line, i);
|
|
1256
|
-
else {
|
|
1257
|
-
if (multilineVars.inEnding && bar.type !== 'bar_thin') {
|
|
1258
|
-
bar.endEnding = true;
|
|
1259
|
-
multilineVars.inEnding = false;
|
|
1260
|
-
}
|
|
1261
|
-
if (ret[2]) {
|
|
1262
|
-
bar.startEnding = ret[2];
|
|
1263
|
-
if (multilineVars.inEnding)
|
|
1264
|
-
bar.endEnding = true;
|
|
1265
|
-
multilineVars.inEnding = true;
|
|
1266
|
-
}
|
|
1267
|
-
if (el.decoration !== undefined)
|
|
1268
|
-
bar.decoration = el.decoration;
|
|
1269
|
-
if (el.chord !== undefined)
|
|
1270
|
-
bar.chord = el.chord;
|
|
1271
|
-
if (bar.startEnding && multilineVars.barFirstEndingNum === undefined)
|
|
1272
|
-
multilineVars.barFirstEndingNum = multilineVars.currBarNumber;
|
|
1273
|
-
else if (bar.startEnding && bar.endEnding && multilineVars.barFirstEndingNum)
|
|
1274
|
-
multilineVars.currBarNumber = multilineVars.barFirstEndingNum;
|
|
1275
|
-
else if (bar.endEnding)
|
|
1276
|
-
multilineVars.barFirstEndingNum = undefined;
|
|
1277
|
-
if (bar.type !== 'bar_invisible' && multilineVars.measureNotEmpty) {
|
|
1278
|
-
var isFirstVoice = multilineVars.currentVoice === undefined || (multilineVars.currentVoice.staffNum === 0 && multilineVars.currentVoice.index === 0);
|
|
1279
|
-
if (isFirstVoice) {
|
|
1280
|
-
multilineVars.currBarNumber++;
|
|
1281
|
-
if (multilineVars.barNumbers && multilineVars.currBarNumber % multilineVars.barNumbers === 0)
|
|
1282
|
-
bar.barNumber = multilineVars.currBarNumber;
|
|
1283
|
-
}
|
|
1284
|
-
}
|
|
1285
|
-
multilineVars.addFormattingOptions(el, tune.formatting, 'bar');
|
|
1286
|
-
tune.appendElement('bar', startOfLine+i, startOfLine+i+ret[0], bar);
|
|
1287
|
-
multilineVars.measureNotEmpty = false;
|
|
1288
|
-
el = {};
|
|
1289
|
-
}
|
|
1290
|
-
i += ret[0];
|
|
1291
|
-
var cv = multilineVars.currentVoice ? multilineVars.currentVoice.staffNum + '-' + multilineVars.currentVoice.index : 'ONLY';
|
|
1292
|
-
// if (multilineVars.lineBreaks) {
|
|
1293
|
-
// if (!multilineVars.barCounter[cv])
|
|
1294
|
-
// multilineVars.barCounter[cv] = 0;
|
|
1295
|
-
// var breakNow = multilineVars.lineBreaks[''+multilineVars.barCounter[cv]];
|
|
1296
|
-
// multilineVars.barCounter[cv]++;
|
|
1297
|
-
// if (breakNow)
|
|
1298
|
-
// startNewLine();
|
|
1299
|
-
// }
|
|
1300
|
-
} else if (line[i] === '&') { // backtrack to beginning of measure
|
|
1301
|
-
ret = letter_to_overlay(line, i);
|
|
1302
|
-
if (ret[0] > 0) {
|
|
1303
|
-
tune.appendElement('overlay', startOfLine, startOfLine+1, {});
|
|
1304
|
-
i += 1;
|
|
1305
|
-
overlayLevel++;
|
|
1306
|
-
}
|
|
1307
|
-
|
|
1308
|
-
} else {
|
|
1309
|
-
// This is definitely a note group
|
|
1310
|
-
//
|
|
1311
|
-
// Look for as many open slurs and triplets as there are. (Note: only the first triplet is valid.)
|
|
1312
|
-
ret = letter_to_open_slurs_and_triplets(line, i);
|
|
1313
|
-
if (ret.consumed > 0) {
|
|
1314
|
-
if (ret.startSlur !== undefined)
|
|
1315
|
-
el.startSlur = ret.startSlur;
|
|
1316
|
-
if (ret.triplet !== undefined) {
|
|
1317
|
-
if (tripletNotesLeft > 0)
|
|
1318
|
-
warn("Can't nest triplets", line, i);
|
|
1319
|
-
else {
|
|
1320
|
-
el.startTriplet = ret.triplet;
|
|
1321
|
-
el.tripletMultiplier = ret.tripletQ / ret.triplet;
|
|
1322
|
-
tripletNotesLeft = ret.num_notes === undefined ? ret.triplet : ret.num_notes;
|
|
1323
|
-
}
|
|
1324
|
-
}
|
|
1325
|
-
i += ret.consumed;
|
|
1326
|
-
}
|
|
1327
|
-
|
|
1328
|
-
// handle chords.
|
|
1329
|
-
if (line.charAt(i) === '[') {
|
|
1330
|
-
var chordStartChar = i;
|
|
1331
|
-
i++;
|
|
1332
|
-
var chordDuration = null;
|
|
1333
|
-
var rememberEndBeam = false;
|
|
1334
|
-
|
|
1335
|
-
var done = false;
|
|
1336
|
-
while (!done) {
|
|
1337
|
-
var accent = letter_to_accent(line, i);
|
|
1338
|
-
if (accent[0] > 0) {
|
|
1339
|
-
i += accent[0];
|
|
1340
|
-
}
|
|
1341
|
-
|
|
1342
|
-
var chordNote = getCoreNote(line, i, {}, false);
|
|
1343
|
-
if (chordNote !== null) {
|
|
1344
|
-
if (accent[0] > 0) { // If we found a decoration above, it modifies the entire chord. "style" is handled below.
|
|
1345
|
-
if (accent[1].indexOf("style=") !== 0) {
|
|
1346
|
-
if (el.decoration === undefined)
|
|
1347
|
-
el.decoration = [];
|
|
1348
|
-
el.decoration.push(accent[1]);
|
|
1349
|
-
}
|
|
1350
|
-
}
|
|
1351
|
-
if (chordNote.end_beam) {
|
|
1352
|
-
el.end_beam = true;
|
|
1353
|
-
delete chordNote.end_beam;
|
|
1354
|
-
}
|
|
1355
|
-
if (el.pitches === undefined) {
|
|
1356
|
-
el.duration = chordNote.duration;
|
|
1357
|
-
el.pitches = [ chordNote ];
|
|
1358
|
-
} else // Just ignore the note lengths of all but the first note. The standard isn't clear here, but this seems less confusing.
|
|
1359
|
-
el.pitches.push(chordNote);
|
|
1360
|
-
delete chordNote.duration;
|
|
1361
|
-
if (accent[0] > 0) { // If we found a style above, it modifies the individual pitch, not the entire chord.
|
|
1362
|
-
if (accent[1].indexOf("style=") === 0) {
|
|
1363
|
-
el.pitches[el.pitches.length-1].style = accent[1].substr(6);
|
|
1364
|
-
}
|
|
1365
|
-
}
|
|
1366
|
-
|
|
1367
|
-
if (multilineVars.inTieChord[el.pitches.length]) {
|
|
1368
|
-
chordNote.endTie = true;
|
|
1369
|
-
multilineVars.inTieChord[el.pitches.length] = undefined;
|
|
1370
|
-
}
|
|
1371
|
-
if (chordNote.startTie)
|
|
1372
|
-
multilineVars.inTieChord[el.pitches.length] = true;
|
|
1373
|
-
|
|
1374
|
-
i = chordNote.endChar;
|
|
1375
|
-
delete chordNote.endChar;
|
|
1376
|
-
} else if (line.charAt(i) === ' ') {
|
|
1377
|
-
// Spaces are not allowed in chords, but we can recover from it by ignoring it.
|
|
1378
|
-
warn("Spaces are not allowed in chords", line, i);
|
|
1379
|
-
i++;
|
|
1380
|
-
} else {
|
|
1381
|
-
if (i < line.length && line.charAt(i) === ']') {
|
|
1382
|
-
// consume the close bracket
|
|
1383
|
-
i++;
|
|
1384
|
-
|
|
1385
|
-
if (multilineVars.next_note_duration !== 0) {
|
|
1386
|
-
el.duration = el.duration * multilineVars.next_note_duration;
|
|
1387
|
-
multilineVars.next_note_duration = 0;
|
|
1388
|
-
}
|
|
1389
|
-
|
|
1390
|
-
if (isInTie(multilineVars, overlayLevel, el)) {
|
|
1391
|
-
parseCommon.each(el.pitches, function(pitch) { pitch.endTie = true; });
|
|
1392
|
-
setIsInTie(multilineVars, overlayLevel, false);
|
|
1393
|
-
}
|
|
1394
|
-
|
|
1395
|
-
if (tripletNotesLeft > 0) {
|
|
1396
|
-
tripletNotesLeft--;
|
|
1397
|
-
if (tripletNotesLeft === 0) {
|
|
1398
|
-
el.endTriplet = true;
|
|
1399
|
-
}
|
|
1400
|
-
}
|
|
1401
|
-
|
|
1402
|
-
var postChordDone = false;
|
|
1403
|
-
while (i < line.length && !postChordDone) {
|
|
1404
|
-
switch (line.charAt(i)) {
|
|
1405
|
-
case ' ':
|
|
1406
|
-
case '\t':
|
|
1407
|
-
addEndBeam(el);
|
|
1408
|
-
break;
|
|
1409
|
-
case ')':
|
|
1410
|
-
if (el.endSlur === undefined) el.endSlur = 1; else el.endSlur++;
|
|
1411
|
-
break;
|
|
1412
|
-
case '-':
|
|
1413
|
-
parseCommon.each(el.pitches, function(pitch) { pitch.startTie = {}; });
|
|
1414
|
-
setIsInTie(multilineVars, overlayLevel, true);
|
|
1415
|
-
break;
|
|
1416
|
-
case '>':
|
|
1417
|
-
case '<':
|
|
1418
|
-
var br2 = getBrokenRhythm(line, i);
|
|
1419
|
-
i += br2[0] - 1; // index gets incremented below, so we'll let that happen
|
|
1420
|
-
multilineVars.next_note_duration = br2[2];
|
|
1421
|
-
if (chordDuration)
|
|
1422
|
-
chordDuration = chordDuration * br2[1];
|
|
1423
|
-
else
|
|
1424
|
-
chordDuration = br2[1];
|
|
1425
|
-
break;
|
|
1426
|
-
case '1':
|
|
1427
|
-
case '2':
|
|
1428
|
-
case '3':
|
|
1429
|
-
case '4':
|
|
1430
|
-
case '5':
|
|
1431
|
-
case '6':
|
|
1432
|
-
case '7':
|
|
1433
|
-
case '8':
|
|
1434
|
-
case '9':
|
|
1435
|
-
case '/':
|
|
1436
|
-
var fraction = tokenizer.getFraction(line, i);
|
|
1437
|
-
chordDuration = fraction.value;
|
|
1438
|
-
i = fraction.index;
|
|
1439
|
-
if (line.charAt(i) === ' ')
|
|
1440
|
-
rememberEndBeam = true;
|
|
1441
|
-
if (line.charAt(i) === '-' || line.charAt(i) === ')' || line.charAt(i) === ' ' || line.charAt(i) === '<' || line.charAt(i) === '>')
|
|
1442
|
-
i--; // Subtracting one because one is automatically added below
|
|
1443
|
-
else
|
|
1444
|
-
postChordDone = true;
|
|
1445
|
-
break;
|
|
1446
|
-
default:
|
|
1447
|
-
postChordDone = true;
|
|
1448
|
-
break;
|
|
1449
|
-
}
|
|
1450
|
-
if (!postChordDone) {
|
|
1451
|
-
i++;
|
|
1452
|
-
}
|
|
1453
|
-
}
|
|
1454
|
-
} else
|
|
1455
|
-
warn("Expected ']' to end the chords", line, i);
|
|
1456
|
-
|
|
1457
|
-
if (el.pitches !== undefined) {
|
|
1458
|
-
if (chordDuration !== null) {
|
|
1459
|
-
el.duration = el.duration * chordDuration;
|
|
1460
|
-
if (rememberEndBeam)
|
|
1461
|
-
addEndBeam(el);
|
|
1462
|
-
}
|
|
1463
|
-
|
|
1464
|
-
multilineVars.addFormattingOptions(el, tune.formatting, 'note');
|
|
1465
|
-
tune.appendElement('note', startOfLine+chordStartChar, startOfLine+i, el);
|
|
1466
|
-
multilineVars.measureNotEmpty = true;
|
|
1467
|
-
el = {};
|
|
1468
|
-
}
|
|
1469
|
-
done = true;
|
|
1470
|
-
}
|
|
1471
|
-
}
|
|
1472
|
-
|
|
1473
|
-
} else {
|
|
1474
|
-
// Single pitch
|
|
1475
|
-
var el2 = {};
|
|
1476
|
-
var core = getCoreNote(line, i, el2, true);
|
|
1477
|
-
if (el2.endTie !== undefined) setIsInTie(multilineVars, overlayLevel, true);
|
|
1478
|
-
if (core !== null) {
|
|
1479
|
-
if (core.pitch !== undefined) {
|
|
1480
|
-
el.pitches = [ { } ];
|
|
1481
|
-
// TODO-PER: straighten this out so there is not so much copying: getCoreNote shouldn't change e'
|
|
1482
|
-
if (core.accidental !== undefined) el.pitches[0].accidental = core.accidental;
|
|
1483
|
-
el.pitches[0].pitch = core.pitch;
|
|
1484
|
-
if (core.midipitch)
|
|
1485
|
-
el.pitches[0].midipitch = core.midipitch;
|
|
1486
|
-
if (core.endSlur !== undefined) el.pitches[0].endSlur = core.endSlur;
|
|
1487
|
-
if (core.endTie !== undefined) el.pitches[0].endTie = core.endTie;
|
|
1488
|
-
if (core.startSlur !== undefined) el.pitches[0].startSlur = core.startSlur;
|
|
1489
|
-
if (el.startSlur !== undefined) el.pitches[0].startSlur = el.startSlur;
|
|
1490
|
-
if (core.startTie !== undefined) el.pitches[0].startTie = core.startTie;
|
|
1491
|
-
if (el.startTie !== undefined) el.pitches[0].startTie = el.startTie;
|
|
1492
|
-
} else {
|
|
1493
|
-
el.rest = core.rest;
|
|
1494
|
-
if (core.endSlur !== undefined) el.endSlur = core.endSlur;
|
|
1495
|
-
if (core.endTie !== undefined) el.rest.endTie = core.endTie;
|
|
1496
|
-
if (core.startSlur !== undefined) el.startSlur = core.startSlur;
|
|
1497
|
-
if (core.startTie !== undefined) el.rest.startTie = core.startTie;
|
|
1498
|
-
if (el.startTie !== undefined) el.rest.startTie = el.startTie;
|
|
1499
|
-
}
|
|
1500
|
-
|
|
1501
|
-
if (core.chord !== undefined) el.chord = core.chord;
|
|
1502
|
-
if (core.duration !== undefined) el.duration = core.duration;
|
|
1503
|
-
if (core.decoration !== undefined) el.decoration = core.decoration;
|
|
1504
|
-
if (core.graceNotes !== undefined) el.graceNotes = core.graceNotes;
|
|
1505
|
-
delete el.startSlur;
|
|
1506
|
-
if (isInTie(multilineVars, overlayLevel, el)) {
|
|
1507
|
-
if (el.pitches !== undefined) {
|
|
1508
|
-
el.pitches[0].endTie = true;
|
|
1509
|
-
} else if (el.rest.type !== 'spacer') {
|
|
1510
|
-
el.rest.endTie = true;
|
|
1511
|
-
}
|
|
1512
|
-
setIsInTie(multilineVars, overlayLevel, false);
|
|
1513
|
-
}
|
|
1514
|
-
if (core.startTie || el.startTie)
|
|
1515
|
-
setIsInTie(multilineVars, overlayLevel, true);
|
|
1516
|
-
i = core.endChar;
|
|
1517
|
-
|
|
1518
|
-
if (tripletNotesLeft > 0) {
|
|
1519
|
-
tripletNotesLeft--;
|
|
1520
|
-
if (tripletNotesLeft === 0) {
|
|
1521
|
-
el.endTriplet = true;
|
|
1522
|
-
}
|
|
1523
|
-
}
|
|
1524
|
-
|
|
1525
|
-
if (core.end_beam)
|
|
1526
|
-
addEndBeam(el);
|
|
1527
|
-
|
|
1528
|
-
// If there is a whole rest, then it should be the duration of the measure, not it's own duration. We need to special case it.
|
|
1529
|
-
// If the time signature length is greater than 4/4, though, then a whole rest has no special treatment.
|
|
1530
|
-
if (el.rest && el.rest.type === 'rest' && el.duration === 1 && durationOfMeasure(multilineVars) <= 1) {
|
|
1531
|
-
el.rest.type = 'whole';
|
|
1532
|
-
|
|
1533
|
-
el.duration = durationOfMeasure(multilineVars);
|
|
1534
|
-
}
|
|
1535
|
-
|
|
1536
|
-
multilineVars.addFormattingOptions(el, tune.formatting, 'note');
|
|
1537
|
-
tune.appendElement('note', startOfLine+startI, startOfLine+i, el);
|
|
1538
|
-
multilineVars.measureNotEmpty = true;
|
|
1539
|
-
el = {};
|
|
1540
|
-
}
|
|
1541
|
-
}
|
|
1542
|
-
|
|
1543
|
-
if (i === startI) { // don't know what this is, so ignore it.
|
|
1544
|
-
if (line.charAt(i) !== ' ' && line.charAt(i) !== '`')
|
|
1545
|
-
warn("Unknown character ignored", line, i);
|
|
1546
|
-
i++;
|
|
1547
|
-
}
|
|
1548
|
-
}
|
|
1549
|
-
}
|
|
403
|
+
if (symbolContinuation) {
|
|
404
|
+
addSymbols(tuneBuilder.getCurrentVoice(), line.substring(2));
|
|
405
|
+
return
|
|
1550
406
|
}
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
if (multilineVars.inTie[overlayLevel] === undefined)
|
|
1555
|
-
return false;
|
|
1556
|
-
// If this is single voice music then the voice index isn't set, so we use the first voice.
|
|
1557
|
-
var voiceIndex = multilineVars.currentVoice ? multilineVars.currentVoice.index : 0;
|
|
1558
|
-
if (multilineVars.inTie[overlayLevel][voiceIndex]) {
|
|
1559
|
-
if (el.pitches !== undefined || el.rest.type !== 'spacer')
|
|
1560
|
-
return true;
|
|
407
|
+
if (line.length < 2 || line.charAt(1) !== ':' || music.lineContinuation) {
|
|
408
|
+
music.parseMusic(line);
|
|
409
|
+
return
|
|
1561
410
|
}
|
|
1562
|
-
return false;
|
|
1563
|
-
};
|
|
1564
411
|
|
|
1565
|
-
var setIsInTie =function(multilineVars, overlayLevel, value) {
|
|
1566
|
-
// If this is single voice music then the voice index isn't set, so we use the first voice.
|
|
1567
|
-
var voiceIndex = multilineVars.currentVoice ? multilineVars.currentVoice.index : 0;
|
|
1568
|
-
if (multilineVars.inTie[overlayLevel] === undefined)
|
|
1569
|
-
multilineVars.inTie[overlayLevel] = [];
|
|
1570
|
-
multilineVars.inTie[overlayLevel][voiceIndex] = value;
|
|
1571
|
-
};
|
|
1572
|
-
|
|
1573
|
-
var parseLine = function(line) {
|
|
1574
412
|
var ret = header.parseHeader(line);
|
|
1575
413
|
if (ret.regular)
|
|
1576
|
-
|
|
414
|
+
music.parseMusic(line);
|
|
1577
415
|
if (ret.newline)
|
|
1578
|
-
startNewLine();
|
|
416
|
+
music.startNewLine();
|
|
1579
417
|
if (ret.words)
|
|
1580
|
-
addWords(
|
|
418
|
+
addWords(tuneBuilder.getCurrentVoice(), line.substring(2));
|
|
1581
419
|
if (ret.symbols)
|
|
1582
|
-
addSymbols(
|
|
1583
|
-
if (ret.recurse)
|
|
1584
|
-
parseLine(ret.str);
|
|
420
|
+
addSymbols(tuneBuilder.getCurrentVoice(), line.substring(2));
|
|
1585
421
|
};
|
|
1586
422
|
|
|
1587
423
|
function appendLastMeasure(voice, nextVoice) {
|
|
@@ -1639,6 +475,34 @@ var Parse = function() {
|
|
|
1639
475
|
if (!switches) switches = {};
|
|
1640
476
|
if (!startPos) startPos = 0;
|
|
1641
477
|
tune.reset();
|
|
478
|
+
|
|
479
|
+
// Take care of whatever line endings come our way
|
|
480
|
+
// Tack on newline temporarily to make the last line continuation work
|
|
481
|
+
strTune = strTune.replace(/\r\n?/g, '\n') + '\n';
|
|
482
|
+
|
|
483
|
+
// get rid of latex commands. If a line starts with a backslash, then it is replaced by spaces to keep the character count the same.
|
|
484
|
+
var arr = strTune.split("\n\\");
|
|
485
|
+
if (arr.length > 1) {
|
|
486
|
+
for (var i2 = 1; i2 < arr.length; i2++) {
|
|
487
|
+
while (arr[i2].length > 0 && arr[i2][0] !== "\n") {
|
|
488
|
+
arr[i2] = arr[i2].substr(1);
|
|
489
|
+
arr[i2-1] += ' ';
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
strTune = arr.join(" "); //. the split removed two characters, so this puts them back
|
|
493
|
+
}
|
|
494
|
+
// take care of line continuations right away, but keep the same number of characters
|
|
495
|
+
strTune = strTune.replace(/\\([ \t]*)(%.*)*\n/g, function(all, backslash, comment){
|
|
496
|
+
var padding = comment ? Array(comment.length +1).join(' ') : "";
|
|
497
|
+
return backslash + "\x12" + padding + '\n';
|
|
498
|
+
});
|
|
499
|
+
var lines = strTune.split('\n')
|
|
500
|
+
if (parseCommon.last(lines).length === 0) // remove the blank line we added above.
|
|
501
|
+
lines.pop();
|
|
502
|
+
tokenizer = new Tokenizer(lines, multilineVars);
|
|
503
|
+
header = new ParseHeader(tokenizer, warn, multilineVars, tune, tuneBuilder);
|
|
504
|
+
music = new ParseMusic(tokenizer, warn, multilineVars, tune, tuneBuilder, header);
|
|
505
|
+
|
|
1642
506
|
if (switches.print)
|
|
1643
507
|
tune.media = 'print';
|
|
1644
508
|
multilineVars.reset();
|
|
@@ -1647,92 +511,74 @@ var Parse = function() {
|
|
|
1647
511
|
multilineVars.globalTranspose = parseInt(switches.visualTranspose);
|
|
1648
512
|
if (multilineVars.globalTranspose === 0)
|
|
1649
513
|
multilineVars.globalTranspose = undefined;
|
|
514
|
+
else
|
|
515
|
+
tuneBuilder.setVisualTranspose(switches.visualTranspose);
|
|
1650
516
|
} else
|
|
1651
517
|
multilineVars.globalTranspose = undefined;
|
|
1652
518
|
if (switches.lineBreaks) {
|
|
1653
|
-
// change the format of the the line breaks for easy testing.
|
|
1654
519
|
// The line break numbers are 0-based and they reflect the last measure of the current line.
|
|
1655
|
-
multilineVars.lineBreaks =
|
|
520
|
+
multilineVars.lineBreaks = switches.lineBreaks;
|
|
1656
521
|
//multilineVars.continueall = true;
|
|
1657
|
-
for (var i = 0; i < switches.lineBreaks.length; i++)
|
|
1658
|
-
multilineVars.lineBreaks[''+(switches.lineBreaks[i]+1)] = true; // Add 1 so that the line break is the first measure of the next line.
|
|
1659
522
|
}
|
|
1660
523
|
header.reset(tokenizer, warn, multilineVars, tune);
|
|
1661
524
|
|
|
1662
|
-
// Take care of whatever line endings come our way
|
|
1663
|
-
strTune = parseCommon.gsub(strTune, '\r\n', '\n');
|
|
1664
|
-
strTune = parseCommon.gsub(strTune, '\r', '\n');
|
|
1665
|
-
strTune += '\n'; // Tacked on temporarily to make the last line continuation work
|
|
1666
|
-
strTune = strTune.replace(/\n\\.*\n/g, "\n"); // get rid of latex commands.
|
|
1667
|
-
var continuationReplacement = function(all, backslash, comment){
|
|
1668
|
-
var spaces = " ";
|
|
1669
|
-
var padding = comment ? spaces.substring(0, comment.length) : "";
|
|
1670
|
-
return backslash + " \x12" + padding;
|
|
1671
|
-
};
|
|
1672
|
-
strTune = strTune.replace(/\\([ \t]*)(%.*)*\n/g, continuationReplacement); // take care of line continuations right away, but keep the same number of characters
|
|
1673
|
-
var lines = strTune.split('\n');
|
|
1674
|
-
if (parseCommon.last(lines).length === 0) // remove the blank line we added above.
|
|
1675
|
-
lines.pop();
|
|
1676
525
|
try {
|
|
1677
526
|
if (switches.format) {
|
|
1678
527
|
parseDirective.globalFormatting(switches.format);
|
|
1679
528
|
}
|
|
1680
|
-
|
|
529
|
+
var line = tokenizer.nextLine();
|
|
530
|
+
while (line) {
|
|
1681
531
|
if (switches.header_only && multilineVars.is_in_header === false)
|
|
1682
532
|
throw "normal_abort";
|
|
1683
533
|
if (switches.stop_on_warning && multilineVars.warnings)
|
|
1684
534
|
throw "normal_abort";
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
multilineVars.textBlock += ' ' + line.substring(2);
|
|
1700
|
-
else
|
|
1701
|
-
multilineVars.textBlock += ' ' + line;
|
|
1702
|
-
}
|
|
1703
|
-
} else if (multilineVars.inPsBlock) {
|
|
1704
|
-
if (parseCommon.startsWith(line, "%%endps")) {
|
|
1705
|
-
// Just ignore postscript
|
|
1706
|
-
multilineVars.inPsBlock = false;
|
|
1707
|
-
}
|
|
1708
|
-
else
|
|
1709
|
-
multilineVars.textBlock += ' ' + line;
|
|
1710
|
-
} else
|
|
1711
|
-
parseLine(line);
|
|
1712
|
-
multilineVars.iChar += line.length + 1;
|
|
1713
|
-
});
|
|
1714
|
-
var ph = 11*72;
|
|
1715
|
-
var pl = 8.5*72;
|
|
1716
|
-
switch (multilineVars.papersize) {
|
|
1717
|
-
//case "letter": ph = 11*72; pl = 8.5*72; break;
|
|
1718
|
-
case "legal": ph = 14*72; pl = 8.5*72; break;
|
|
1719
|
-
case "A4": ph = 11.7*72; pl = 8.3*72; break;
|
|
535
|
+
|
|
536
|
+
var wasInHeader = multilineVars.is_in_header;
|
|
537
|
+
parseLine(line);
|
|
538
|
+
if (wasInHeader && !multilineVars.is_in_header) {
|
|
539
|
+
tuneBuilder.setRunningFont("annotationfont", multilineVars.annotationfont);
|
|
540
|
+
tuneBuilder.setRunningFont("gchordfont", multilineVars.gchordfont);
|
|
541
|
+
tuneBuilder.setRunningFont("tripletfont", multilineVars.tripletfont);
|
|
542
|
+
tuneBuilder.setRunningFont("vocalfont", multilineVars.vocalfont);
|
|
543
|
+
}
|
|
544
|
+
line = tokenizer.nextLine();
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
if (wordsContinuation) {
|
|
548
|
+
addWords(tuneBuilder.getCurrentVoice(), '');
|
|
1720
549
|
}
|
|
1721
|
-
if (
|
|
1722
|
-
|
|
1723
|
-
ph = pl;
|
|
1724
|
-
pl = x;
|
|
550
|
+
if (symbolContinuation) {
|
|
551
|
+
addSymbols(tuneBuilder.getCurrentVoice(), '');
|
|
1725
552
|
}
|
|
1726
|
-
multilineVars.openSlurs =
|
|
553
|
+
multilineVars.openSlurs = tuneBuilder.cleanUp(multilineVars.barsperstaff, multilineVars.staffnonote, multilineVars.openSlurs);
|
|
554
|
+
|
|
1727
555
|
} catch (err) {
|
|
1728
556
|
if (err !== "normal_abort")
|
|
1729
557
|
throw err;
|
|
1730
558
|
}
|
|
559
|
+
|
|
560
|
+
var ph = 11*72;
|
|
561
|
+
var pl = 8.5*72;
|
|
562
|
+
switch (multilineVars.papersize) {
|
|
563
|
+
//case "letter": ph = 11*72; pl = 8.5*72; break;
|
|
564
|
+
case "legal": ph = 14*72; pl = 8.5*72; break;
|
|
565
|
+
case "A4": ph = 11.7*72; pl = 8.3*72; break;
|
|
566
|
+
}
|
|
567
|
+
if (multilineVars.landscape) {
|
|
568
|
+
var x = ph;
|
|
569
|
+
ph = pl;
|
|
570
|
+
pl = x;
|
|
571
|
+
}
|
|
572
|
+
if (!tune.formatting.pagewidth)
|
|
573
|
+
tune.formatting.pagewidth = pl;
|
|
574
|
+
if (!tune.formatting.pageheight)
|
|
575
|
+
tune.formatting.pageheight = ph;
|
|
576
|
+
|
|
1731
577
|
if (switches.hint_measures) {
|
|
1732
578
|
addHintMeasures();
|
|
1733
579
|
}
|
|
1734
580
|
|
|
1735
|
-
wrap.wrapLines(tune, multilineVars.lineBreaks);
|
|
581
|
+
wrap.wrapLines(tune, multilineVars.lineBreaks, multilineVars.barNumbers);
|
|
1736
582
|
};
|
|
1737
583
|
};
|
|
1738
584
|
|