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
|
@@ -0,0 +1,1337 @@
|
|
|
1
|
+
var parseCommon = require('./abc_common');
|
|
2
|
+
var parseKeyVoice = require('./abc_parse_key_voice');
|
|
3
|
+
var transpose = require('./abc_transpose');
|
|
4
|
+
|
|
5
|
+
var tokenizer;
|
|
6
|
+
var warn;
|
|
7
|
+
var multilineVars;
|
|
8
|
+
var tune;
|
|
9
|
+
var tuneBuilder;
|
|
10
|
+
var header;
|
|
11
|
+
|
|
12
|
+
var MusicParser = function(_tokenizer, _warn, _multilineVars, _tune, _tuneBuilder, _header) {
|
|
13
|
+
tokenizer = _tokenizer;
|
|
14
|
+
warn = _warn;
|
|
15
|
+
multilineVars = _multilineVars;
|
|
16
|
+
tune = _tune;
|
|
17
|
+
tuneBuilder = _tuneBuilder;
|
|
18
|
+
header = _header;
|
|
19
|
+
this.lineContinuation = false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
//
|
|
23
|
+
// Parse line of music
|
|
24
|
+
//
|
|
25
|
+
// This is a stream of <(bar-marking|header|note-group)...> in any order, with optional spaces between each element
|
|
26
|
+
// core-note is <open-slur, accidental, pitch:required, octave, duration, close-slur&|tie> with no spaces within that
|
|
27
|
+
// chord is <open-bracket:required, core-note:required... close-bracket:required duration> with no spaces within that
|
|
28
|
+
// grace-notes is <open-brace:required, (open-slur|core-note:required|close-slur)..., close-brace:required> spaces are allowed
|
|
29
|
+
// note-group is <grace-notes, chord symbols&|decorations..., grace-notes, slur&|triplet, chord|core-note, end-slur|tie> spaces are allowed between items
|
|
30
|
+
// bar-marking is <ampersand> or <chord symbols&|decorations..., bar:required> spaces allowed
|
|
31
|
+
// 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
|
|
32
|
+
// 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.
|
|
33
|
+
// a space is a back-tick, a space, or a tab. If it is a back-tick, then there is no end-beam.
|
|
34
|
+
|
|
35
|
+
// Line preprocessing: anything after a % is ignored (the double %% should have been taken care of before this)
|
|
36
|
+
// Then, all leading and trailing spaces are ignored.
|
|
37
|
+
// If there was a line continuation, the \n was replaced by a \r and the \ was replaced by a space. This allows the construct
|
|
38
|
+
// of having a header mid-line conceptually, but actually be at the start of the line. This is equivolent to putting the header in [ ].
|
|
39
|
+
|
|
40
|
+
// TODO-PER: How to handle ! for line break?
|
|
41
|
+
// TODO-PER: dots before bar, dots before slur
|
|
42
|
+
// TODO-PER: U: redefinable symbols.
|
|
43
|
+
|
|
44
|
+
// Ambiguous symbols:
|
|
45
|
+
// "[" can be the start of a chord, the start of a header element or part of a bar line.
|
|
46
|
+
// --- if it is immediately followed by "|", it is a bar line
|
|
47
|
+
// --- 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.)
|
|
48
|
+
// --- otherwise it is the beginning of a chord
|
|
49
|
+
// "(" can be the start of a slur or a triplet
|
|
50
|
+
// --- if it is followed by a number from 2-9, then it is a triplet
|
|
51
|
+
// --- otherwise it is a slur
|
|
52
|
+
// "]"
|
|
53
|
+
// --- if there is a chord open, then this is the close
|
|
54
|
+
// --- if it is after a [|, then it is an invisible bar line
|
|
55
|
+
// --- otherwise, it is par of a bar
|
|
56
|
+
// "." can be a bar modifier or a slur modifier, or a decoration
|
|
57
|
+
// --- if it comes immediately before a bar, it is a bar modifier
|
|
58
|
+
// --- if it comes immediately before a slur, it is a slur modifier
|
|
59
|
+
// --- otherwise it is a decoration for the next note.
|
|
60
|
+
// number:
|
|
61
|
+
// --- if it is after a bar, with no space, it is an ending marker
|
|
62
|
+
// --- if it is after a ( with no space, it is a triplet count
|
|
63
|
+
// --- if it is after a pitch or octave or slash, then it is a duration
|
|
64
|
+
|
|
65
|
+
// Unambiguous symbols (except inside quoted strings):
|
|
66
|
+
// vertical-bar, colon: part of a bar
|
|
67
|
+
// ABCDEFGabcdefg: pitch
|
|
68
|
+
// xyzZ: rest
|
|
69
|
+
// comma, prime: octave
|
|
70
|
+
// close-paren: end-slur
|
|
71
|
+
// hyphen: tie
|
|
72
|
+
// tilde, v, u, bang, plus, THLMPSO: decoration
|
|
73
|
+
// carat, underscore, equal: accidental
|
|
74
|
+
// ampersand: time reset
|
|
75
|
+
// open-curly, close-curly: grace notes
|
|
76
|
+
// double-quote: chord symbol
|
|
77
|
+
// less-than, greater-than, slash: duration
|
|
78
|
+
// back-tick, space, tab: space
|
|
79
|
+
var nonDecorations = "ABCDEFGabcdefgxyzZ[]|^_{"; // use this to prescreen so we don't have to look for a decoration at every note.
|
|
80
|
+
|
|
81
|
+
var isInTie = function(multilineVars, overlayLevel, el) {
|
|
82
|
+
if (multilineVars.inTie[overlayLevel] === undefined)
|
|
83
|
+
return false;
|
|
84
|
+
// If this is single voice music then the voice index isn't set, so we use the first voice.
|
|
85
|
+
var voiceIndex = multilineVars.currentVoice ? multilineVars.currentVoice.index : 0;
|
|
86
|
+
if (multilineVars.inTie[overlayLevel][voiceIndex]) {
|
|
87
|
+
if (el.pitches !== undefined || el.rest.type !== 'spacer')
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
return false;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
var el = { };
|
|
94
|
+
MusicParser.prototype.parseMusic = function(line) {
|
|
95
|
+
header.resolveTempo();
|
|
96
|
+
//multilineVars.havent_set_length = false; // To late to set this now.
|
|
97
|
+
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.
|
|
98
|
+
var i = 0;
|
|
99
|
+
var startOfLine = multilineVars.iChar;
|
|
100
|
+
// 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 %
|
|
101
|
+
while (tokenizer.isWhiteSpace(line.charAt(i)) && i < line.length)
|
|
102
|
+
i++;
|
|
103
|
+
if (i === line.length || line.charAt(i) === '%')
|
|
104
|
+
return;
|
|
105
|
+
|
|
106
|
+
// Start with the standard staff, clef and key symbols on each line
|
|
107
|
+
var delayStartNewLine = multilineVars.start_new_line;
|
|
108
|
+
if (multilineVars.continueall === undefined)
|
|
109
|
+
multilineVars.start_new_line = true;
|
|
110
|
+
else
|
|
111
|
+
multilineVars.start_new_line = false;
|
|
112
|
+
var tripletNotesLeft = 0;
|
|
113
|
+
|
|
114
|
+
// See if the line starts with a header field
|
|
115
|
+
var retHeader = header.letter_to_body_header(line, i);
|
|
116
|
+
if (retHeader[0] > 0) {
|
|
117
|
+
i += retHeader[0];
|
|
118
|
+
// fixes bug on this: c[V:2]d
|
|
119
|
+
if (retHeader[1] === 'V')
|
|
120
|
+
this.startNewLine();
|
|
121
|
+
// delayStartNewLine = true;
|
|
122
|
+
// TODO-PER: Handle inline headers
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
var overlayLevel = 0;
|
|
126
|
+
while (i < line.length)
|
|
127
|
+
{
|
|
128
|
+
var startI = i;
|
|
129
|
+
if (line.charAt(i) === '%')
|
|
130
|
+
break;
|
|
131
|
+
|
|
132
|
+
var retInlineHeader = header.letter_to_inline_header(line, i, delayStartNewLine);
|
|
133
|
+
if (retInlineHeader[0] > 0) {
|
|
134
|
+
i += retInlineHeader[0];
|
|
135
|
+
if (retInlineHeader[1] === 'V')
|
|
136
|
+
delayStartNewLine = true; // fixes bug on this: c[V:2]d
|
|
137
|
+
// TODO-PER: Handle inline headers
|
|
138
|
+
//multilineVars.start_new_line = false;
|
|
139
|
+
} else {
|
|
140
|
+
// Wait until here to actually start the line because we know we're past the inline statements.
|
|
141
|
+
if (!tuneBuilder.hasBeginMusic() || (delayStartNewLine && !this.lineContinuation)) {
|
|
142
|
+
this.startNewLine();
|
|
143
|
+
delayStartNewLine = false;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// We need to decide if the following characters are a bar-marking or a note-group.
|
|
147
|
+
// Unfortunately, that is ambiguous. Both can contain chord symbols and decorations.
|
|
148
|
+
// If there is a grace note either before or after the chord symbols and decorations, then it is definitely a note-group.
|
|
149
|
+
// If there is a bar marker, it is definitely a bar-marking.
|
|
150
|
+
// If there is either a core-note or chord, it is definitely a note-group.
|
|
151
|
+
// 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]
|
|
152
|
+
// Then, if there is a grace-note, we know where to go.
|
|
153
|
+
// Else see if we have a chord, core-note, slur, triplet, or bar.
|
|
154
|
+
|
|
155
|
+
var ret;
|
|
156
|
+
while (1) {
|
|
157
|
+
ret = tokenizer.eatWhiteSpace(line, i);
|
|
158
|
+
if (ret > 0) {
|
|
159
|
+
i += ret;
|
|
160
|
+
}
|
|
161
|
+
if (i > 0 && line.charAt(i-1) === '\x12') {
|
|
162
|
+
// 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.
|
|
163
|
+
ret = header.letter_to_body_header(line, i);
|
|
164
|
+
if (ret[0] > 0) {
|
|
165
|
+
if (ret[1] === 'V')
|
|
166
|
+
this.startNewLine(); // fixes bug on this: c\\nV:2]\\nd
|
|
167
|
+
// TODO: insert header here
|
|
168
|
+
i = ret[0];
|
|
169
|
+
multilineVars.start_new_line = false;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
// gather all the grace notes, chord symbols and decorations
|
|
173
|
+
ret = letter_to_spacer(line, i);
|
|
174
|
+
if (ret[0] > 0) {
|
|
175
|
+
i += ret[0];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
ret = letter_to_chord(line, i);
|
|
179
|
+
if (ret[0] > 0) {
|
|
180
|
+
// There could be more than one chord here if they have different positions.
|
|
181
|
+
// If two chords have the same position, then connect them with newline.
|
|
182
|
+
if (!el.chord)
|
|
183
|
+
el.chord = [];
|
|
184
|
+
var chordName = tokenizer.translateString(ret[1]);
|
|
185
|
+
chordName = chordName.replace(/;/g, "\n");
|
|
186
|
+
var addedChord = false;
|
|
187
|
+
for (var ci = 0; ci < el.chord.length; ci++) {
|
|
188
|
+
if (el.chord[ci].position === ret[2]) {
|
|
189
|
+
addedChord = true;
|
|
190
|
+
el.chord[ci].name += "\n" + chordName;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (addedChord === false) {
|
|
194
|
+
if (ret[2] === null && ret[3])
|
|
195
|
+
el.chord.push({name: chordName, rel_position: ret[3]});
|
|
196
|
+
else
|
|
197
|
+
el.chord.push({name: chordName, position: ret[2]});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
i += ret[0];
|
|
201
|
+
var ii = tokenizer.skipWhiteSpace(line.substring(i));
|
|
202
|
+
if (ii > 0)
|
|
203
|
+
el.force_end_beam_last = true;
|
|
204
|
+
i += ii;
|
|
205
|
+
} else {
|
|
206
|
+
if (nonDecorations.indexOf(line.charAt(i)) === -1)
|
|
207
|
+
ret = letter_to_accent(line, i);
|
|
208
|
+
else ret = [ 0 ];
|
|
209
|
+
if (ret[0] > 0) {
|
|
210
|
+
if (ret[1] === null) {
|
|
211
|
+
if (i + 1 < line.length)
|
|
212
|
+
this.startNewLine(); // There was a ! in the middle of the line. Start a new line if there is anything after it.
|
|
213
|
+
} else if (ret[1].length > 0) {
|
|
214
|
+
if (ret[1].indexOf("style=") === 0) {
|
|
215
|
+
el.style = ret[1].substr(6);
|
|
216
|
+
} else {
|
|
217
|
+
if (el.decoration === undefined)
|
|
218
|
+
el.decoration = [];
|
|
219
|
+
if (ret[1] === 'beambr1')
|
|
220
|
+
el.beambr = 1;
|
|
221
|
+
else if (ret[1] === "beambr2")
|
|
222
|
+
el.beambr = 2;
|
|
223
|
+
else el.decoration.push(ret[1]);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
i += ret[0];
|
|
227
|
+
} else {
|
|
228
|
+
ret = letter_to_grace(line, i);
|
|
229
|
+
// TODO-PER: Be sure there aren't already grace notes defined. That is an error.
|
|
230
|
+
if (ret[0] > 0) {
|
|
231
|
+
el.gracenotes = ret[1];
|
|
232
|
+
i += ret[0];
|
|
233
|
+
} else
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
ret = letter_to_bar(line, i);
|
|
240
|
+
if (ret[0] > 0) {
|
|
241
|
+
// This is definitely a bar
|
|
242
|
+
overlayLevel = 0;
|
|
243
|
+
if (el.gracenotes !== undefined) {
|
|
244
|
+
// Attach the grace note to an invisible note
|
|
245
|
+
el.rest = { type: 'spacer' };
|
|
246
|
+
el.duration = 0.125; // TODO-PER: I don't think the duration of this matters much, but figure out if it does.
|
|
247
|
+
multilineVars.addFormattingOptions(el, tune.formatting, 'note');
|
|
248
|
+
tuneBuilder.appendElement('note', startOfLine+i, startOfLine+i+ret[0], el);
|
|
249
|
+
multilineVars.measureNotEmpty = true;
|
|
250
|
+
el = {};
|
|
251
|
+
}
|
|
252
|
+
var bar = {type: ret[1]};
|
|
253
|
+
if (bar.type.length === 0)
|
|
254
|
+
warn("Unknown bar type", line, i);
|
|
255
|
+
else {
|
|
256
|
+
if (multilineVars.inEnding && bar.type !== 'bar_thin') {
|
|
257
|
+
bar.endEnding = true;
|
|
258
|
+
multilineVars.inEnding = false;
|
|
259
|
+
}
|
|
260
|
+
if (ret[2]) {
|
|
261
|
+
bar.startEnding = ret[2];
|
|
262
|
+
if (multilineVars.inEnding)
|
|
263
|
+
bar.endEnding = true;
|
|
264
|
+
multilineVars.inEnding = true;
|
|
265
|
+
if (ret[1] === "bar_right_repeat") {
|
|
266
|
+
// restore the tie and slur state from the start repeat
|
|
267
|
+
multilineVars.restoreStartEndingHoldOvers();
|
|
268
|
+
} else {
|
|
269
|
+
// save inTie, inTieChord
|
|
270
|
+
multilineVars.duplicateStartEndingHoldOvers();
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
if (el.decoration !== undefined)
|
|
274
|
+
bar.decoration = el.decoration;
|
|
275
|
+
if (el.chord !== undefined)
|
|
276
|
+
bar.chord = el.chord;
|
|
277
|
+
if (bar.startEnding && multilineVars.barFirstEndingNum === undefined)
|
|
278
|
+
multilineVars.barFirstEndingNum = multilineVars.currBarNumber;
|
|
279
|
+
else if (bar.startEnding && bar.endEnding && multilineVars.barFirstEndingNum)
|
|
280
|
+
multilineVars.currBarNumber = multilineVars.barFirstEndingNum;
|
|
281
|
+
else if (bar.endEnding)
|
|
282
|
+
multilineVars.barFirstEndingNum = undefined;
|
|
283
|
+
if (bar.type !== 'bar_invisible' && multilineVars.measureNotEmpty) {
|
|
284
|
+
var isFirstVoice = multilineVars.currentVoice === undefined || (multilineVars.currentVoice.staffNum === 0 && multilineVars.currentVoice.index === 0);
|
|
285
|
+
if (isFirstVoice) {
|
|
286
|
+
multilineVars.currBarNumber++;
|
|
287
|
+
if (multilineVars.barNumbers && multilineVars.currBarNumber % multilineVars.barNumbers === 0)
|
|
288
|
+
bar.barNumber = multilineVars.currBarNumber;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
multilineVars.addFormattingOptions(el, tune.formatting, 'bar');
|
|
292
|
+
tuneBuilder.appendElement('bar', startOfLine+i, startOfLine+i+ret[0], bar);
|
|
293
|
+
multilineVars.measureNotEmpty = false;
|
|
294
|
+
el = {};
|
|
295
|
+
}
|
|
296
|
+
i += ret[0];
|
|
297
|
+
} else if (line[i] === '&') { // backtrack to beginning of measure
|
|
298
|
+
ret = letter_to_overlay(line, i);
|
|
299
|
+
if (ret[0] > 0) {
|
|
300
|
+
tuneBuilder.appendElement('overlay', startOfLine, startOfLine+1, {});
|
|
301
|
+
i += 1;
|
|
302
|
+
overlayLevel++;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
} else {
|
|
306
|
+
// This is definitely a note group
|
|
307
|
+
//
|
|
308
|
+
// Look for as many open slurs and triplets as there are. (Note: only the first triplet is valid.)
|
|
309
|
+
ret = letter_to_open_slurs_and_triplets(line, i);
|
|
310
|
+
if (ret.consumed > 0) {
|
|
311
|
+
if (ret.startSlur !== undefined)
|
|
312
|
+
el.startSlur = ret.startSlur;
|
|
313
|
+
if (ret.dottedSlur)
|
|
314
|
+
el.dottedSlur = true;
|
|
315
|
+
if (ret.triplet !== undefined) {
|
|
316
|
+
if (tripletNotesLeft > 0)
|
|
317
|
+
warn("Can't nest triplets", line, i);
|
|
318
|
+
else {
|
|
319
|
+
el.startTriplet = ret.triplet;
|
|
320
|
+
el.tripletMultiplier = ret.tripletQ / ret.triplet;
|
|
321
|
+
el.tripletR = ret.num_notes;
|
|
322
|
+
tripletNotesLeft = ret.num_notes === undefined ? ret.triplet : ret.num_notes;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
i += ret.consumed;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// handle chords.
|
|
329
|
+
if (line.charAt(i) === '[') {
|
|
330
|
+
var chordStartChar = i;
|
|
331
|
+
i++;
|
|
332
|
+
var chordDuration = null;
|
|
333
|
+
var rememberEndBeam = false;
|
|
334
|
+
|
|
335
|
+
var done = false;
|
|
336
|
+
while (!done) {
|
|
337
|
+
var accent = letter_to_accent(line, i);
|
|
338
|
+
if (accent[0] > 0) {
|
|
339
|
+
i += accent[0];
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
var chordNote = getCoreNote(line, i, {}, false);
|
|
343
|
+
if (chordNote !== null && chordNote.pitch !== undefined) {
|
|
344
|
+
if (accent[0] > 0) { // If we found a decoration above, it modifies the entire chord. "style" is handled below.
|
|
345
|
+
if (accent[1].indexOf("style=") !== 0) {
|
|
346
|
+
if (el.decoration === undefined)
|
|
347
|
+
el.decoration = [];
|
|
348
|
+
el.decoration.push(accent[1]);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
if (chordNote.end_beam) {
|
|
352
|
+
el.end_beam = true;
|
|
353
|
+
delete chordNote.end_beam;
|
|
354
|
+
}
|
|
355
|
+
if (el.pitches === undefined) {
|
|
356
|
+
el.duration = chordNote.duration;
|
|
357
|
+
el.pitches = [ chordNote ];
|
|
358
|
+
} else // Just ignore the note lengths of all but the first note. The standard isn't clear here, but this seems less confusing.
|
|
359
|
+
el.pitches.push(chordNote);
|
|
360
|
+
delete chordNote.duration;
|
|
361
|
+
if (accent[0] > 0) { // If we found a style above, it modifies the individual pitch, not the entire chord.
|
|
362
|
+
if (accent[1].indexOf("style=") === 0) {
|
|
363
|
+
el.pitches[el.pitches.length-1].style = accent[1].substr(6);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (multilineVars.inTieChord[el.pitches.length]) {
|
|
368
|
+
chordNote.endTie = true;
|
|
369
|
+
multilineVars.inTieChord[el.pitches.length] = undefined;
|
|
370
|
+
}
|
|
371
|
+
if (chordNote.startTie)
|
|
372
|
+
multilineVars.inTieChord[el.pitches.length] = true;
|
|
373
|
+
|
|
374
|
+
i = chordNote.endChar;
|
|
375
|
+
delete chordNote.endChar;
|
|
376
|
+
} else if (line.charAt(i) === ' ') {
|
|
377
|
+
// Spaces are not allowed in chords, but we can recover from it by ignoring it.
|
|
378
|
+
warn("Spaces are not allowed in chords", line, i);
|
|
379
|
+
i++;
|
|
380
|
+
} else {
|
|
381
|
+
if (i < line.length && line.charAt(i) === ']') {
|
|
382
|
+
// consume the close bracket
|
|
383
|
+
i++;
|
|
384
|
+
|
|
385
|
+
if (multilineVars.next_note_duration !== 0) {
|
|
386
|
+
el.duration = el.duration * multilineVars.next_note_duration;
|
|
387
|
+
multilineVars.next_note_duration = 0;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (isInTie(multilineVars, overlayLevel, el)) {
|
|
391
|
+
parseCommon.each(el.pitches, function(pitch) { pitch.endTie = true; });
|
|
392
|
+
setIsInTie(multilineVars, overlayLevel, false);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (tripletNotesLeft > 0 && !(el.rest && el.rest.type === "spacer")) {
|
|
396
|
+
tripletNotesLeft--;
|
|
397
|
+
if (tripletNotesLeft === 0) {
|
|
398
|
+
el.endTriplet = true;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
var postChordDone = false;
|
|
403
|
+
while (i < line.length && !postChordDone) {
|
|
404
|
+
switch (line.charAt(i)) {
|
|
405
|
+
case ' ':
|
|
406
|
+
case '\t':
|
|
407
|
+
addEndBeam(el);
|
|
408
|
+
break;
|
|
409
|
+
case ')':
|
|
410
|
+
if (el.endSlur === undefined) el.endSlur = 1; else el.endSlur++;
|
|
411
|
+
break;
|
|
412
|
+
case '-':
|
|
413
|
+
parseCommon.each(el.pitches, function(pitch) { pitch.startTie = {}; });
|
|
414
|
+
setIsInTie(multilineVars, overlayLevel, true);
|
|
415
|
+
break;
|
|
416
|
+
case '>':
|
|
417
|
+
case '<':
|
|
418
|
+
var br2 = getBrokenRhythm(line, i);
|
|
419
|
+
i += br2[0] - 1; // index gets incremented below, so we'll let that happen
|
|
420
|
+
multilineVars.next_note_duration = br2[2];
|
|
421
|
+
if (chordDuration)
|
|
422
|
+
chordDuration = chordDuration * br2[1];
|
|
423
|
+
else
|
|
424
|
+
chordDuration = br2[1];
|
|
425
|
+
break;
|
|
426
|
+
case '1':
|
|
427
|
+
case '2':
|
|
428
|
+
case '3':
|
|
429
|
+
case '4':
|
|
430
|
+
case '5':
|
|
431
|
+
case '6':
|
|
432
|
+
case '7':
|
|
433
|
+
case '8':
|
|
434
|
+
case '9':
|
|
435
|
+
case '/':
|
|
436
|
+
var fraction = tokenizer.getFraction(line, i);
|
|
437
|
+
chordDuration = fraction.value;
|
|
438
|
+
i = fraction.index;
|
|
439
|
+
if (line.charAt(i) === ' ')
|
|
440
|
+
rememberEndBeam = true;
|
|
441
|
+
if (line.charAt(i) === '-' || line.charAt(i) === ')' || line.charAt(i) === ' ' || line.charAt(i) === '<' || line.charAt(i) === '>')
|
|
442
|
+
i--; // Subtracting one because one is automatically added below
|
|
443
|
+
else
|
|
444
|
+
postChordDone = true;
|
|
445
|
+
break;
|
|
446
|
+
default:
|
|
447
|
+
postChordDone = true;
|
|
448
|
+
break;
|
|
449
|
+
}
|
|
450
|
+
if (!postChordDone) {
|
|
451
|
+
i++;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
} else
|
|
455
|
+
warn("Expected ']' to end the chords", line, i);
|
|
456
|
+
|
|
457
|
+
if (el.pitches !== undefined) {
|
|
458
|
+
if (chordDuration !== null) {
|
|
459
|
+
el.duration = el.duration * chordDuration;
|
|
460
|
+
if (rememberEndBeam)
|
|
461
|
+
addEndBeam(el);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
multilineVars.addFormattingOptions(el, tune.formatting, 'note');
|
|
465
|
+
tuneBuilder.appendElement('note', startOfLine+startI, startOfLine+i, el);
|
|
466
|
+
multilineVars.measureNotEmpty = true;
|
|
467
|
+
el = {};
|
|
468
|
+
}
|
|
469
|
+
done = true;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
} else {
|
|
474
|
+
// Single pitch
|
|
475
|
+
var el2 = {};
|
|
476
|
+
var core = getCoreNote(line, i, el2, true);
|
|
477
|
+
if (el2.endTie !== undefined) setIsInTie(multilineVars, overlayLevel, true);
|
|
478
|
+
if (core !== null) {
|
|
479
|
+
if (core.pitch !== undefined) {
|
|
480
|
+
el.pitches = [ { } ];
|
|
481
|
+
// TODO-PER: straighten this out so there is not so much copying: getCoreNote shouldn't change e'
|
|
482
|
+
if (core.accidental !== undefined) el.pitches[0].accidental = core.accidental;
|
|
483
|
+
el.pitches[0].pitch = core.pitch;
|
|
484
|
+
el.pitches[0].name = core.name;
|
|
485
|
+
if (core.midipitch || core.midipitch === 0)
|
|
486
|
+
el.pitches[0].midipitch = core.midipitch;
|
|
487
|
+
if (core.endSlur !== undefined) el.pitches[0].endSlur = core.endSlur;
|
|
488
|
+
if (core.endTie !== undefined) el.pitches[0].endTie = core.endTie;
|
|
489
|
+
if (core.startSlur !== undefined) el.pitches[0].startSlur = core.startSlur;
|
|
490
|
+
if (el.startSlur !== undefined) el.pitches[0].startSlur = el.startSlur;
|
|
491
|
+
if (el.dottedSlur !== undefined) el.pitches[0].dottedSlur = true;
|
|
492
|
+
if (core.startTie !== undefined) el.pitches[0].startTie = core.startTie;
|
|
493
|
+
if (el.startTie !== undefined) el.pitches[0].startTie = el.startTie;
|
|
494
|
+
} else {
|
|
495
|
+
el.rest = core.rest;
|
|
496
|
+
if (core.endSlur !== undefined) el.endSlur = core.endSlur;
|
|
497
|
+
if (core.endTie !== undefined) el.rest.endTie = core.endTie;
|
|
498
|
+
if (core.startSlur !== undefined) el.startSlur = core.startSlur;
|
|
499
|
+
if (core.startTie !== undefined) el.rest.startTie = core.startTie;
|
|
500
|
+
if (el.startTie !== undefined) el.rest.startTie = el.startTie;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (core.chord !== undefined) el.chord = core.chord;
|
|
504
|
+
if (core.duration !== undefined) el.duration = core.duration;
|
|
505
|
+
if (core.decoration !== undefined) el.decoration = core.decoration;
|
|
506
|
+
if (core.graceNotes !== undefined) el.graceNotes = core.graceNotes;
|
|
507
|
+
delete el.startSlur;
|
|
508
|
+
delete el.dottedSlur;
|
|
509
|
+
if (isInTie(multilineVars, overlayLevel, el)) {
|
|
510
|
+
if (el.pitches !== undefined) {
|
|
511
|
+
el.pitches[0].endTie = true;
|
|
512
|
+
} else if (el.rest.type !== 'spacer') {
|
|
513
|
+
el.rest.endTie = true;
|
|
514
|
+
}
|
|
515
|
+
setIsInTie(multilineVars, overlayLevel, false);
|
|
516
|
+
}
|
|
517
|
+
if (core.startTie || el.startTie)
|
|
518
|
+
setIsInTie(multilineVars, overlayLevel, true);
|
|
519
|
+
i = core.endChar;
|
|
520
|
+
|
|
521
|
+
if (tripletNotesLeft > 0 && !(core.rest && core.rest.type === "spacer")) {
|
|
522
|
+
tripletNotesLeft--;
|
|
523
|
+
if (tripletNotesLeft === 0) {
|
|
524
|
+
el.endTriplet = true;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
if (core.end_beam)
|
|
529
|
+
addEndBeam(el);
|
|
530
|
+
|
|
531
|
+
// 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.
|
|
532
|
+
// If the time signature length is greater than 4/4, though, then a whole rest has no special treatment.
|
|
533
|
+
if (el.rest && el.rest.type === 'rest' && el.duration === 1 && durationOfMeasure(multilineVars) <= 1) {
|
|
534
|
+
el.rest.type = 'whole';
|
|
535
|
+
|
|
536
|
+
el.duration = durationOfMeasure(multilineVars);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// Create a warning if this is not a displayable duration.
|
|
540
|
+
// The first item on a line is a regular note value, each item after that represents a dot placed after the previous note.
|
|
541
|
+
// Only durations less than a whole note are tested because whole note durations have some tricky rules.
|
|
542
|
+
var durations = [
|
|
543
|
+
0.5, 0.75, 0.875, 0.9375, 0.96875, 0.984375,
|
|
544
|
+
0.25, 0.375, 0.4375, 0.46875, 0.484375, 0.4921875,
|
|
545
|
+
0.125, 0.1875, 0.21875, 0.234375, 0.2421875, 0.24609375,
|
|
546
|
+
0.0625, 0.09375, 0.109375, 0.1171875, 0.12109375, 0.123046875,
|
|
547
|
+
0.03125, 0.046875, 0.0546875, 0.05859375, 0.060546875, 0.0615234375,
|
|
548
|
+
0.015625, 0.0234375, 0.02734375, 0.029296875, 0.0302734375, 0.03076171875,
|
|
549
|
+
];
|
|
550
|
+
if (el.duration < 1 && durations.indexOf(el.duration) === -1 && el.duration !== 0) {
|
|
551
|
+
if (!el.rest || el.rest.type !== 'spacer')
|
|
552
|
+
warn("Duration not representable: " + line.substring(startI, i), line, i);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
multilineVars.addFormattingOptions(el, tune.formatting, 'note');
|
|
556
|
+
tuneBuilder.appendElement('note', startOfLine+startI, startOfLine+i, el);
|
|
557
|
+
multilineVars.measureNotEmpty = true;
|
|
558
|
+
el = {};
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
if (i === startI) { // don't know what this is, so ignore it.
|
|
563
|
+
if (line.charAt(i) !== ' ' && line.charAt(i) !== '`')
|
|
564
|
+
warn("Unknown character ignored", line, i);
|
|
565
|
+
i++;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
this.lineContinuation = line.indexOf('\x12') >= 0 || (retHeader[0] > 0)
|
|
571
|
+
if (!this.lineContinuation) { el = { } }
|
|
572
|
+
};
|
|
573
|
+
|
|
574
|
+
var setIsInTie =function(multilineVars, overlayLevel, value) {
|
|
575
|
+
// If this is single voice music then the voice index isn't set, so we use the first voice.
|
|
576
|
+
var voiceIndex = multilineVars.currentVoice ? multilineVars.currentVoice.index : 0;
|
|
577
|
+
if (multilineVars.inTie[overlayLevel] === undefined)
|
|
578
|
+
multilineVars.inTie[overlayLevel] = [];
|
|
579
|
+
multilineVars.inTie[overlayLevel][voiceIndex] = value;
|
|
580
|
+
};
|
|
581
|
+
|
|
582
|
+
var letter_to_chord = function(line, i) {
|
|
583
|
+
if (line.charAt(i) === '"')
|
|
584
|
+
{
|
|
585
|
+
var chord = tokenizer.getBrackettedSubstring(line, i, 5);
|
|
586
|
+
if (!chord[2])
|
|
587
|
+
warn("Missing the closing quote while parsing the chord symbol", line , i);
|
|
588
|
+
// If it starts with ^, then the chord appears above.
|
|
589
|
+
// If it starts with _ then the chord appears below.
|
|
590
|
+
// (note that the 2.0 draft standard defines them as not chords, but annotations and also defines @.)
|
|
591
|
+
if (chord[0] > 0 && chord[1].length > 0 && chord[1].charAt(0) === '^') {
|
|
592
|
+
chord[1] = chord[1].substring(1);
|
|
593
|
+
chord[2] = 'above';
|
|
594
|
+
} else if (chord[0] > 0 && chord[1].length > 0 && chord[1].charAt(0) === '_') {
|
|
595
|
+
chord[1] = chord[1].substring(1);
|
|
596
|
+
chord[2] = 'below';
|
|
597
|
+
} else if (chord[0] > 0 && chord[1].length > 0 && chord[1].charAt(0) === '<') {
|
|
598
|
+
chord[1] = chord[1].substring(1);
|
|
599
|
+
chord[2] = 'left';
|
|
600
|
+
} else if (chord[0] > 0 && chord[1].length > 0 && chord[1].charAt(0) === '>') {
|
|
601
|
+
chord[1] = chord[1].substring(1);
|
|
602
|
+
chord[2] = 'right';
|
|
603
|
+
} else if (chord[0] > 0 && chord[1].length > 0 && chord[1].charAt(0) === '@') {
|
|
604
|
+
// @-15,5.7
|
|
605
|
+
chord[1] = chord[1].substring(1);
|
|
606
|
+
var x = tokenizer.getFloat(chord[1]);
|
|
607
|
+
if (x.digits === 0)
|
|
608
|
+
warn("Missing first position in absolutely positioned annotation.", line , i);
|
|
609
|
+
chord[1] = chord[1].substring(x.digits);
|
|
610
|
+
if (chord[1][0] !== ',')
|
|
611
|
+
warn("Missing comma absolutely positioned annotation.", line , i);
|
|
612
|
+
chord[1] = chord[1].substring(1);
|
|
613
|
+
var y = tokenizer.getFloat(chord[1]);
|
|
614
|
+
if (y.digits === 0)
|
|
615
|
+
warn("Missing second position in absolutely positioned annotation.", line , i);
|
|
616
|
+
chord[1] = chord[1].substring(y.digits);
|
|
617
|
+
var ws = tokenizer.skipWhiteSpace(chord[1]);
|
|
618
|
+
chord[1] = chord[1].substring(ws);
|
|
619
|
+
chord[2] = null;
|
|
620
|
+
chord[3] = { x: x.value, y: y.value };
|
|
621
|
+
} else {
|
|
622
|
+
if (multilineVars.freegchord !== true) {
|
|
623
|
+
chord[1] = chord[1].replace(/([ABCDEFG0-9])b/g, "$1♭");
|
|
624
|
+
chord[1] = chord[1].replace(/([ABCDEFG0-9])#/g, "$1♯");
|
|
625
|
+
chord[1] = chord[1].replace(/^([ABCDEFG])([♯♭]?)o([^A-Za-z])/g, "$1$2°$3");
|
|
626
|
+
chord[1] = chord[1].replace(/^([ABCDEFG])([♯♭]?)o$/g, "$1$2°");
|
|
627
|
+
chord[1] = chord[1].replace(/^([ABCDEFG])([♯♭]?)0([^A-Za-z])/g, "$1$2ø$3");
|
|
628
|
+
chord[1] = chord[1].replace(/^([ABCDEFG])([♯♭]?)\^([^A-Za-z])/g, "$1$2∆$3");
|
|
629
|
+
}
|
|
630
|
+
chord[2] = 'default';
|
|
631
|
+
chord[1] = transpose.chordName(multilineVars, chord[1]);
|
|
632
|
+
}
|
|
633
|
+
return chord;
|
|
634
|
+
}
|
|
635
|
+
return [0, ""];
|
|
636
|
+
};
|
|
637
|
+
|
|
638
|
+
var letter_to_grace = function(line, i) {
|
|
639
|
+
// Grace notes are an array of: startslur, note, endslur, space; where note is accidental, pitch, duration
|
|
640
|
+
if (line.charAt(i) === '{') {
|
|
641
|
+
// fetch the gracenotes string and consume that into the array
|
|
642
|
+
var gra = tokenizer.getBrackettedSubstring(line, i, 1, '}');
|
|
643
|
+
if (!gra[2])
|
|
644
|
+
warn("Missing the closing '}' while parsing grace note", line, i);
|
|
645
|
+
// If there is a slur after the grace construction, then move it to the last note inside the grace construction
|
|
646
|
+
if (line[i+gra[0]] === ')') {
|
|
647
|
+
gra[0]++;
|
|
648
|
+
gra[1] += ')';
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
var gracenotes = [];
|
|
652
|
+
var ii = 0;
|
|
653
|
+
var inTie = false;
|
|
654
|
+
while (ii < gra[1].length) {
|
|
655
|
+
var acciaccatura = false;
|
|
656
|
+
if (gra[1].charAt(ii) === '/') {
|
|
657
|
+
acciaccatura = true;
|
|
658
|
+
ii++;
|
|
659
|
+
}
|
|
660
|
+
var note = getCoreNote(gra[1], ii, {}, false);
|
|
661
|
+
if (note !== null) {
|
|
662
|
+
// 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.
|
|
663
|
+
note.duration = note.duration / (multilineVars.default_length * 8);
|
|
664
|
+
if (acciaccatura)
|
|
665
|
+
note.acciaccatura = true;
|
|
666
|
+
gracenotes.push(note);
|
|
667
|
+
|
|
668
|
+
if (inTie) {
|
|
669
|
+
note.endTie = true;
|
|
670
|
+
inTie = false;
|
|
671
|
+
}
|
|
672
|
+
if (note.startTie)
|
|
673
|
+
inTie = true;
|
|
674
|
+
|
|
675
|
+
ii = note.endChar;
|
|
676
|
+
delete note.endChar;
|
|
677
|
+
|
|
678
|
+
if (note.end_beam) {
|
|
679
|
+
note.endBeam = true;
|
|
680
|
+
delete note.end_beam;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
else {
|
|
684
|
+
// We shouldn't get anything but notes or a space here, so report an error
|
|
685
|
+
if (gra[1].charAt(ii) === ' ') {
|
|
686
|
+
if (gracenotes.length > 0)
|
|
687
|
+
gracenotes[gracenotes.length-1].endBeam = true;
|
|
688
|
+
} else
|
|
689
|
+
warn("Unknown character '" + gra[1].charAt(ii) + "' while parsing grace note", line, i);
|
|
690
|
+
ii++;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
if (gracenotes.length)
|
|
694
|
+
return [gra[0], gracenotes];
|
|
695
|
+
}
|
|
696
|
+
return [ 0 ];
|
|
697
|
+
};
|
|
698
|
+
|
|
699
|
+
function letter_to_overlay(line, i) {
|
|
700
|
+
if (line.charAt(i) === '&') {
|
|
701
|
+
var start = i;
|
|
702
|
+
while (line.charAt(i) && line.charAt(i) !== ':' && line.charAt(i) !== '|')
|
|
703
|
+
i++;
|
|
704
|
+
return [ i-start, line.substring(start+1, i) ];
|
|
705
|
+
}
|
|
706
|
+
return [ 0 ];
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
function durationOfMeasure(multilineVars) {
|
|
710
|
+
// TODO-PER: This could be more complicated if one of the unusual measures is used.
|
|
711
|
+
var meter = multilineVars.origMeter;
|
|
712
|
+
if (!meter || meter.type !== 'specified')
|
|
713
|
+
return 1;
|
|
714
|
+
if (!meter.value || meter.value.length === 0)
|
|
715
|
+
return 1;
|
|
716
|
+
return parseInt(meter.value[0].num, 10) / parseInt(meter.value[0].den, 10);
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
var legalAccents = [
|
|
720
|
+
"trill", "lowermordent", "uppermordent", "mordent", "pralltriller", "accent",
|
|
721
|
+
"fermata", "invertedfermata", "tenuto", "0", "1", "2", "3", "4", "5", "+", "wedge",
|
|
722
|
+
"open", "thumb", "snap", "turn", "roll", "breath", "shortphrase", "mediumphrase", "longphrase",
|
|
723
|
+
"segno", "coda", "D.S.", "D.C.", "fine", "beambr1", "beambr2",
|
|
724
|
+
"slide", "marcato",
|
|
725
|
+
"upbow", "downbow", "/", "//", "///", "////", "trem1", "trem2", "trem3", "trem4",
|
|
726
|
+
"turnx", "invertedturn", "invertedturnx", "trill(", "trill)", "arpeggio", "xstem", "mark", "umarcato",
|
|
727
|
+
"style=normal", "style=harmonic", "style=rhythm", "style=x", "style=triangle"
|
|
728
|
+
];
|
|
729
|
+
|
|
730
|
+
var volumeDecorations = [
|
|
731
|
+
"p", "pp", "f", "ff", "mf", "mp", "ppp", "pppp", "fff", "ffff", "sfz"
|
|
732
|
+
];
|
|
733
|
+
|
|
734
|
+
var dynamicDecorations = [
|
|
735
|
+
"crescendo(", "crescendo)", "diminuendo(", "diminuendo)"
|
|
736
|
+
];
|
|
737
|
+
|
|
738
|
+
var accentPseudonyms = [
|
|
739
|
+
["<", "accent"], [">", "accent"], ["tr", "trill"],
|
|
740
|
+
["plus", "+"], [ "emphasis", "accent"],
|
|
741
|
+
[ "^", "umarcato" ], [ "marcato", "umarcato" ]
|
|
742
|
+
];
|
|
743
|
+
|
|
744
|
+
var accentDynamicPseudonyms = [
|
|
745
|
+
["<(", "crescendo("], ["<)", "crescendo)"],
|
|
746
|
+
[">(", "diminuendo("], [">)", "diminuendo)"]
|
|
747
|
+
];
|
|
748
|
+
|
|
749
|
+
var letter_to_accent = function(line, i) {
|
|
750
|
+
var macro = multilineVars.macros[line.charAt(i)];
|
|
751
|
+
|
|
752
|
+
if (macro !== undefined) {
|
|
753
|
+
if (macro.charAt(0) === '!' || macro.charAt(0) === '+')
|
|
754
|
+
macro = macro.substring(1);
|
|
755
|
+
if (macro.charAt(macro.length-1) === '!' || macro.charAt(macro.length-1) === '+')
|
|
756
|
+
macro = macro.substring(0, macro.length-1);
|
|
757
|
+
if (parseCommon.detect(legalAccents, function(acc) {
|
|
758
|
+
return (macro === acc);
|
|
759
|
+
}))
|
|
760
|
+
return [ 1, macro ];
|
|
761
|
+
else if (parseCommon.detect(volumeDecorations, function(acc) {
|
|
762
|
+
return (macro === acc);
|
|
763
|
+
})) {
|
|
764
|
+
if (multilineVars.volumePosition === 'hidden')
|
|
765
|
+
macro = "";
|
|
766
|
+
return [1, macro];
|
|
767
|
+
} else if (parseCommon.detect(dynamicDecorations, function(acc) {
|
|
768
|
+
if (multilineVars.dynamicPosition === 'hidden')
|
|
769
|
+
macro = "";
|
|
770
|
+
return (macro === acc);
|
|
771
|
+
})) {
|
|
772
|
+
return [1, macro];
|
|
773
|
+
} else {
|
|
774
|
+
if (!parseCommon.detect(multilineVars.ignoredDecorations, function(dec) {
|
|
775
|
+
return (macro === dec);
|
|
776
|
+
}))
|
|
777
|
+
warn("Unknown macro: " + macro, line, i);
|
|
778
|
+
return [1, '' ];
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
switch (line.charAt(i))
|
|
782
|
+
{
|
|
783
|
+
case '.':
|
|
784
|
+
if (line[i+1] === '(' || line[i+1] === '-') // a dot then open paren is a dotted slur; likewise dot dash is dotted tie.
|
|
785
|
+
break;
|
|
786
|
+
return [1, 'staccato'];
|
|
787
|
+
case 'u':return [1, 'upbow'];
|
|
788
|
+
case 'v':return [1, 'downbow'];
|
|
789
|
+
case '~':return [1, 'irishroll'];
|
|
790
|
+
case '!':
|
|
791
|
+
case '+':
|
|
792
|
+
var ret = tokenizer.getBrackettedSubstring(line, i, 5);
|
|
793
|
+
// Be sure that the accent is recognizable.
|
|
794
|
+
if (ret[1].length > 1 && (ret[1].charAt(0) === '^' || ret[1].charAt(0) ==='_'))
|
|
795
|
+
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.
|
|
796
|
+
if (parseCommon.detect(legalAccents, function(acc) {
|
|
797
|
+
return (ret[1] === acc);
|
|
798
|
+
}))
|
|
799
|
+
return ret;
|
|
800
|
+
if (parseCommon.detect(volumeDecorations, function(acc) {
|
|
801
|
+
return (ret[1] === acc);
|
|
802
|
+
})) {
|
|
803
|
+
if (multilineVars.volumePosition === 'hidden' )
|
|
804
|
+
ret[1] = '';
|
|
805
|
+
return ret;
|
|
806
|
+
}
|
|
807
|
+
if (parseCommon.detect(dynamicDecorations, function(acc) {
|
|
808
|
+
return (ret[1] === acc);
|
|
809
|
+
})) {
|
|
810
|
+
if (multilineVars.dynamicPosition === 'hidden' )
|
|
811
|
+
ret[1] = '';
|
|
812
|
+
return ret;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
if (parseCommon.detect(accentPseudonyms, function(acc) {
|
|
816
|
+
if (ret[1] === acc[0]) {
|
|
817
|
+
ret[1] = acc[1];
|
|
818
|
+
return true;
|
|
819
|
+
} else
|
|
820
|
+
return false;
|
|
821
|
+
}))
|
|
822
|
+
return ret;
|
|
823
|
+
|
|
824
|
+
if (parseCommon.detect(accentDynamicPseudonyms, function(acc) {
|
|
825
|
+
if (ret[1] === acc[0]) {
|
|
826
|
+
ret[1] = acc[1];
|
|
827
|
+
return true;
|
|
828
|
+
} else
|
|
829
|
+
return false;
|
|
830
|
+
})) {
|
|
831
|
+
if (multilineVars.dynamicPosition === 'hidden' )
|
|
832
|
+
ret[1] = '';
|
|
833
|
+
return ret;
|
|
834
|
+
}
|
|
835
|
+
// We didn't find the accent in the list, so consume the space, but don't return an accent.
|
|
836
|
+
// Although it is possible that ! was used as a line break, so accept that.
|
|
837
|
+
if (line.charAt(i) === '!' && (ret[0] === 1 || line.charAt(i+ret[0]-1) !== '!'))
|
|
838
|
+
return [1, null ];
|
|
839
|
+
warn("Unknown decoration: " + ret[1], line, i);
|
|
840
|
+
ret[1] = "";
|
|
841
|
+
return ret;
|
|
842
|
+
case 'H':return [1, 'fermata'];
|
|
843
|
+
case 'J':return [1, 'slide'];
|
|
844
|
+
case 'L':return [1, 'accent'];
|
|
845
|
+
case 'M':return [1, 'mordent'];
|
|
846
|
+
case 'O':return[1, 'coda'];
|
|
847
|
+
case 'P':return[1, 'pralltriller'];
|
|
848
|
+
case 'R':return [1, 'roll'];
|
|
849
|
+
case 'S':return [1, 'segno'];
|
|
850
|
+
case 'T':return [1, 'trill'];
|
|
851
|
+
}
|
|
852
|
+
return [0, 0];
|
|
853
|
+
};
|
|
854
|
+
|
|
855
|
+
var letter_to_spacer = function(line, i) {
|
|
856
|
+
var start = i;
|
|
857
|
+
while (tokenizer.isWhiteSpace(line.charAt(i)))
|
|
858
|
+
i++;
|
|
859
|
+
return [ i-start ];
|
|
860
|
+
};
|
|
861
|
+
|
|
862
|
+
// returns the class of the bar line
|
|
863
|
+
// the number of the repeat
|
|
864
|
+
// and the number of characters used up
|
|
865
|
+
// if 0 is returned, then the next element was not a bar line
|
|
866
|
+
var letter_to_bar = function(line, curr_pos) {
|
|
867
|
+
var ret = tokenizer.getBarLine(line, curr_pos);
|
|
868
|
+
if (ret.len === 0)
|
|
869
|
+
return [0,""];
|
|
870
|
+
if (ret.warn) {
|
|
871
|
+
warn(ret.warn, line, curr_pos);
|
|
872
|
+
return [ret.len,""];
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
// Now see if this is a repeated ending
|
|
876
|
+
// A repeated ending is all of the characters 1,2,3,4,5,6,7,8,9,0,-, and comma
|
|
877
|
+
// It can also optionally start with '[', which is ignored.
|
|
878
|
+
// Also, it can have white space before the '['.
|
|
879
|
+
for (var ws = 0; ws < line.length; ws++)
|
|
880
|
+
if (line.charAt(curr_pos + ret.len + ws) !== ' ')
|
|
881
|
+
break;
|
|
882
|
+
var orig_bar_len = ret.len;
|
|
883
|
+
if (line.charAt(curr_pos+ret.len+ws) === '[') {
|
|
884
|
+
ret.len += ws + 1;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
// 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.
|
|
888
|
+
if (line.charAt(curr_pos+ret.len) === '"' && line.charAt(curr_pos+ret.len-1) === '[') {
|
|
889
|
+
var ending = tokenizer.getBrackettedSubstring(line, curr_pos+ret.len, 5);
|
|
890
|
+
return [ret.len+ending[0], ret.token, ending[1]];
|
|
891
|
+
}
|
|
892
|
+
var retRep = tokenizer.getTokenOf(line.substring(curr_pos+ret.len), "1234567890-,");
|
|
893
|
+
if (retRep.len === 0 || retRep.token[0] === '-')
|
|
894
|
+
return [orig_bar_len, ret.token];
|
|
895
|
+
|
|
896
|
+
return [ret.len+retRep.len, ret.token, retRep.token];
|
|
897
|
+
};
|
|
898
|
+
|
|
899
|
+
var tripletQ = {
|
|
900
|
+
2: 3,
|
|
901
|
+
3: 2,
|
|
902
|
+
4: 3,
|
|
903
|
+
5: 2, // TODO-PER: not handling 6/8 rhythm yet
|
|
904
|
+
6: 2,
|
|
905
|
+
7: 2, // TODO-PER: not handling 6/8 rhythm yet
|
|
906
|
+
8: 3,
|
|
907
|
+
9: 2 // TODO-PER: not handling 6/8 rhythm yet
|
|
908
|
+
};
|
|
909
|
+
|
|
910
|
+
var letter_to_open_slurs_and_triplets = function(line, i) {
|
|
911
|
+
// consume spaces, and look for all the open parens. If there is a number after the open paren,
|
|
912
|
+
// that is a triplet. Otherwise that is a slur. Collect all the slurs and the first triplet.
|
|
913
|
+
var ret = {};
|
|
914
|
+
var start = i;
|
|
915
|
+
if (line[i] === '.' && line[i+1] === '(') {
|
|
916
|
+
ret.dottedSlur = true;
|
|
917
|
+
i++;
|
|
918
|
+
}
|
|
919
|
+
while (line.charAt(i) === '(' || tokenizer.isWhiteSpace(line.charAt(i))) {
|
|
920
|
+
if (line.charAt(i) === '(') {
|
|
921
|
+
if (i+1 < line.length && (line.charAt(i+1) >= '2' && line.charAt(i+1) <= '9')) {
|
|
922
|
+
if (ret.triplet !== undefined)
|
|
923
|
+
warn("Can't nest triplets", line, i);
|
|
924
|
+
else {
|
|
925
|
+
ret.triplet = line.charAt(i+1) - '0';
|
|
926
|
+
ret.tripletQ = tripletQ[ret.triplet];
|
|
927
|
+
ret.num_notes = ret.triplet;
|
|
928
|
+
if (i+2 < line.length && line.charAt(i+2) === ':') {
|
|
929
|
+
// We are expecting "(p:q:r" or "(p:q" or "(p::r"
|
|
930
|
+
// That is: "put p notes into the time of q for the next r notes"
|
|
931
|
+
// if r is missing, then it is equal to p.
|
|
932
|
+
// if q is missing, it is determined from this table:
|
|
933
|
+
// (2 notes in the time of 3
|
|
934
|
+
// (3 notes in the time of 2
|
|
935
|
+
// (4 notes in the time of 3
|
|
936
|
+
// (5 notes in the time of n | if time sig is (6/8, 9/8, 12/8), n=3, else n=2
|
|
937
|
+
// (6 notes in the time of 2
|
|
938
|
+
// (7 notes in the time of n
|
|
939
|
+
// (8 notes in the time of 3
|
|
940
|
+
// (9 notes in the time of n
|
|
941
|
+
if (i+3 < line.length && line.charAt(i+3) === ':') {
|
|
942
|
+
// The second number, 'q', is not present.
|
|
943
|
+
if (i+4 < line.length && (line.charAt(i+4) >= '1' && line.charAt(i+4) <= '9')) {
|
|
944
|
+
ret.num_notes = line.charAt(i+4) - '0';
|
|
945
|
+
i += 3;
|
|
946
|
+
} else
|
|
947
|
+
warn("expected number after the two colons after the triplet to mark the duration", line, i);
|
|
948
|
+
} else if (i+3 < line.length && (line.charAt(i+3) >= '1' && line.charAt(i+3) <= '9')) {
|
|
949
|
+
ret.tripletQ = line.charAt(i+3) - '0';
|
|
950
|
+
if (i+4 < line.length && line.charAt(i+4) === ':') {
|
|
951
|
+
if (i+5 < line.length && (line.charAt(i+5) >= '1' && line.charAt(i+5) <= '9')) {
|
|
952
|
+
ret.num_notes = line.charAt(i+5) - '0';
|
|
953
|
+
i += 4;
|
|
954
|
+
}
|
|
955
|
+
} else {
|
|
956
|
+
i += 2;
|
|
957
|
+
}
|
|
958
|
+
} else
|
|
959
|
+
warn("expected number after the triplet to mark the duration", line, i);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
i++;
|
|
963
|
+
}
|
|
964
|
+
else {
|
|
965
|
+
if (ret.startSlur === undefined)
|
|
966
|
+
ret.startSlur = 1;
|
|
967
|
+
else
|
|
968
|
+
ret.startSlur++;
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
i++;
|
|
972
|
+
}
|
|
973
|
+
ret.consumed = i-start;
|
|
974
|
+
return ret;
|
|
975
|
+
};
|
|
976
|
+
|
|
977
|
+
MusicParser.prototype.startNewLine = function() {
|
|
978
|
+
var params = { startChar: -1, endChar: -1};
|
|
979
|
+
if (multilineVars.partForNextLine.title)
|
|
980
|
+
params.part = multilineVars.partForNextLine;
|
|
981
|
+
params.clef = multilineVars.currentVoice && multilineVars.staves[multilineVars.currentVoice.staffNum].clef !== undefined ? parseCommon.clone(multilineVars.staves[multilineVars.currentVoice.staffNum].clef) : parseCommon.clone(multilineVars.clef);
|
|
982
|
+
var scoreTranspose = multilineVars.currentVoice ? multilineVars.currentVoice.scoreTranspose : 0;
|
|
983
|
+
params.key = parseKeyVoice.standardKey(multilineVars.key.root+multilineVars.key.acc+multilineVars.key.mode, multilineVars.key.root, multilineVars.key.acc, scoreTranspose);
|
|
984
|
+
params.key.mode = multilineVars.key.mode;
|
|
985
|
+
if (multilineVars.key.impliedNaturals)
|
|
986
|
+
params.key.impliedNaturals = multilineVars.key.impliedNaturals;
|
|
987
|
+
if (multilineVars.key.explicitAccidentals) {
|
|
988
|
+
for (var i = 0; i < multilineVars.key.explicitAccidentals.length; i++) {
|
|
989
|
+
var found = false;
|
|
990
|
+
for (var j = 0; j < params.key.accidentals.length; j++) {
|
|
991
|
+
if (params.key.accidentals[j].note === multilineVars.key.explicitAccidentals[i].note) {
|
|
992
|
+
// If the note is already in the list, override it with the new value
|
|
993
|
+
params.key.accidentals[j].acc = multilineVars.key.explicitAccidentals[i].acc;
|
|
994
|
+
found = true;
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
if (!found)
|
|
998
|
+
params.key.accidentals.push(multilineVars.key.explicitAccidentals[i]);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
multilineVars.targetKey = params.key;
|
|
1002
|
+
if (params.key.explicitAccidentals)
|
|
1003
|
+
delete params.key.explicitAccidentals;
|
|
1004
|
+
parseKeyVoice.addPosToKey(params.clef, params.key);
|
|
1005
|
+
if (multilineVars.meter !== null) {
|
|
1006
|
+
if (multilineVars.currentVoice) {
|
|
1007
|
+
parseCommon.each(multilineVars.staves, function(st) {
|
|
1008
|
+
st.meter = multilineVars.meter;
|
|
1009
|
+
});
|
|
1010
|
+
params.meter = multilineVars.staves[multilineVars.currentVoice.staffNum].meter;
|
|
1011
|
+
multilineVars.staves[multilineVars.currentVoice.staffNum].meter = null;
|
|
1012
|
+
} else
|
|
1013
|
+
params.meter = multilineVars.meter;
|
|
1014
|
+
multilineVars.meter = null;
|
|
1015
|
+
} else if (multilineVars.currentVoice && multilineVars.staves[multilineVars.currentVoice.staffNum].meter) {
|
|
1016
|
+
// Make sure that each voice gets the meter marking.
|
|
1017
|
+
params.meter = multilineVars.staves[multilineVars.currentVoice.staffNum].meter;
|
|
1018
|
+
multilineVars.staves[multilineVars.currentVoice.staffNum].meter = null;
|
|
1019
|
+
}
|
|
1020
|
+
if (multilineVars.currentVoice && multilineVars.currentVoice.name)
|
|
1021
|
+
params.name = multilineVars.currentVoice.name;
|
|
1022
|
+
if (multilineVars.vocalfont)
|
|
1023
|
+
params.vocalfont = multilineVars.vocalfont;
|
|
1024
|
+
if (multilineVars.tripletfont)
|
|
1025
|
+
params.tripletfont = multilineVars.tripletfont;
|
|
1026
|
+
if (multilineVars.gchordfont)
|
|
1027
|
+
params.gchordfont = multilineVars.gchordfont;
|
|
1028
|
+
if (multilineVars.style)
|
|
1029
|
+
params.style = multilineVars.style;
|
|
1030
|
+
if (multilineVars.currentVoice) {
|
|
1031
|
+
var staff = multilineVars.staves[multilineVars.currentVoice.staffNum];
|
|
1032
|
+
if (staff.brace) params.brace = staff.brace;
|
|
1033
|
+
if (staff.bracket) params.bracket = staff.bracket;
|
|
1034
|
+
if (staff.connectBarLines) params.connectBarLines = staff.connectBarLines;
|
|
1035
|
+
if (staff.name) params.name = staff.name[multilineVars.currentVoice.index];
|
|
1036
|
+
if (staff.subname) params.subname = staff.subname[multilineVars.currentVoice.index];
|
|
1037
|
+
if (multilineVars.currentVoice.stem)
|
|
1038
|
+
params.stem = multilineVars.currentVoice.stem;
|
|
1039
|
+
if (multilineVars.currentVoice.stafflines)
|
|
1040
|
+
params.stafflines = multilineVars.currentVoice.stafflines;
|
|
1041
|
+
if (multilineVars.currentVoice.staffscale)
|
|
1042
|
+
params.staffscale = multilineVars.currentVoice.staffscale;
|
|
1043
|
+
if (multilineVars.currentVoice.scale)
|
|
1044
|
+
params.scale = multilineVars.currentVoice.scale;
|
|
1045
|
+
if (multilineVars.currentVoice.style)
|
|
1046
|
+
params.style = multilineVars.currentVoice.style;
|
|
1047
|
+
if (multilineVars.currentVoice.transpose)
|
|
1048
|
+
params.clef.transpose = multilineVars.currentVoice.transpose;
|
|
1049
|
+
}
|
|
1050
|
+
var isFirstVoice = multilineVars.currentVoice === undefined || (multilineVars.currentVoice.staffNum === 0 && multilineVars.currentVoice.index === 0);
|
|
1051
|
+
if (multilineVars.barNumbers === 0 && isFirstVoice && multilineVars.currBarNumber !== 1)
|
|
1052
|
+
params.barNumber = multilineVars.currBarNumber;
|
|
1053
|
+
tuneBuilder.startNewLine(params);
|
|
1054
|
+
if (multilineVars.key.impliedNaturals)
|
|
1055
|
+
delete multilineVars.key.impliedNaturals;
|
|
1056
|
+
|
|
1057
|
+
multilineVars.partForNextLine = {};
|
|
1058
|
+
if (multilineVars.tempoForNextLine.length === 4)
|
|
1059
|
+
tuneBuilder.appendElement(multilineVars.tempoForNextLine[0],multilineVars.tempoForNextLine[1],multilineVars.tempoForNextLine[2],multilineVars.tempoForNextLine[3]);
|
|
1060
|
+
multilineVars.tempoForNextLine = [];
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
// TODO-PER: make this a method in el.
|
|
1064
|
+
var addEndBeam = function(el) {
|
|
1065
|
+
if (el.duration !== undefined && el.duration < 0.25)
|
|
1066
|
+
el.end_beam = true;
|
|
1067
|
+
return el;
|
|
1068
|
+
};
|
|
1069
|
+
|
|
1070
|
+
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};
|
|
1071
|
+
var rests = {x: 'invisible', X: 'invisible-multimeasure', y: 'spacer', z: 'rest', Z: 'multimeasure' };
|
|
1072
|
+
var accMap = { 'dblflat': '__', 'flat': '_', 'natural': '=', 'sharp': '^', 'dblsharp': '^^', 'quarterflat': '_/', 'quartersharp': '^/'};
|
|
1073
|
+
var getCoreNote = function(line, index, el, canHaveBrokenRhythm) {
|
|
1074
|
+
//var el = { startChar: index };
|
|
1075
|
+
var isComplete = function(state) {
|
|
1076
|
+
return (state === 'octave' || state === 'duration' || state === 'Zduration' || state === 'broken_rhythm' || state === 'end_slur');
|
|
1077
|
+
};
|
|
1078
|
+
var dottedTie;
|
|
1079
|
+
if (line[index] === '.' && line[index+1] === '-') {
|
|
1080
|
+
dottedTie = true;
|
|
1081
|
+
index++;
|
|
1082
|
+
}
|
|
1083
|
+
var state = 'startSlur';
|
|
1084
|
+
var durationSetByPreviousNote = false;
|
|
1085
|
+
while (1) {
|
|
1086
|
+
switch(line.charAt(index)) {
|
|
1087
|
+
case '(':
|
|
1088
|
+
if (state === 'startSlur') {
|
|
1089
|
+
if (el.startSlur === undefined) el.startSlur = 1; else el.startSlur++;
|
|
1090
|
+
} else if (isComplete(state)) {el.endChar = index;return el;}
|
|
1091
|
+
else return null;
|
|
1092
|
+
break;
|
|
1093
|
+
case ')':
|
|
1094
|
+
if (isComplete(state)) {
|
|
1095
|
+
if (el.endSlur === undefined) el.endSlur = 1; else el.endSlur++;
|
|
1096
|
+
} else return null;
|
|
1097
|
+
break;
|
|
1098
|
+
case '^':
|
|
1099
|
+
if (state === 'startSlur') {el.accidental = 'sharp';state = 'sharp2';}
|
|
1100
|
+
else if (state === 'sharp2') {el.accidental = 'dblsharp';state = 'pitch';}
|
|
1101
|
+
else if (isComplete(state)) {el.endChar = index;return el;}
|
|
1102
|
+
else return null;
|
|
1103
|
+
break;
|
|
1104
|
+
case '_':
|
|
1105
|
+
if (state === 'startSlur') {el.accidental = 'flat';state = 'flat2';}
|
|
1106
|
+
else if (state === 'flat2') {el.accidental = 'dblflat';state = 'pitch';}
|
|
1107
|
+
else if (isComplete(state)) {el.endChar = index;return el;}
|
|
1108
|
+
else return null;
|
|
1109
|
+
break;
|
|
1110
|
+
case '=':
|
|
1111
|
+
if (state === 'startSlur') {el.accidental = 'natural';state = 'pitch';}
|
|
1112
|
+
else if (isComplete(state)) {el.endChar = index;return el;}
|
|
1113
|
+
else return null;
|
|
1114
|
+
break;
|
|
1115
|
+
case 'A':
|
|
1116
|
+
case 'B':
|
|
1117
|
+
case 'C':
|
|
1118
|
+
case 'D':
|
|
1119
|
+
case 'E':
|
|
1120
|
+
case 'F':
|
|
1121
|
+
case 'G':
|
|
1122
|
+
case 'a':
|
|
1123
|
+
case 'b':
|
|
1124
|
+
case 'c':
|
|
1125
|
+
case 'd':
|
|
1126
|
+
case 'e':
|
|
1127
|
+
case 'f':
|
|
1128
|
+
case 'g':
|
|
1129
|
+
if (state === 'startSlur' || state === 'sharp2' || state === 'flat2' || state === 'pitch') {
|
|
1130
|
+
el.pitch = pitches[line.charAt(index)];
|
|
1131
|
+
el.name = line.charAt(index);
|
|
1132
|
+
if (el.accidental)
|
|
1133
|
+
el.name = accMap[el.accidental] + el.name;
|
|
1134
|
+
transpose.note(multilineVars, el);
|
|
1135
|
+
state = 'octave';
|
|
1136
|
+
// At this point we have a valid note. The rest is optional. Set the duration in case we don't get one below
|
|
1137
|
+
if (canHaveBrokenRhythm && multilineVars.next_note_duration !== 0) {
|
|
1138
|
+
el.duration = multilineVars.default_length * multilineVars.next_note_duration;
|
|
1139
|
+
multilineVars.next_note_duration = 0;
|
|
1140
|
+
durationSetByPreviousNote = true;
|
|
1141
|
+
} else
|
|
1142
|
+
el.duration = multilineVars.default_length;
|
|
1143
|
+
// If the clef is percussion, there is probably some translation of the pitch to a particular drum kit item.
|
|
1144
|
+
if ((multilineVars.clef && multilineVars.clef.type === "perc") ||
|
|
1145
|
+
(multilineVars.currentVoice && multilineVars.currentVoice.clef === "perc")) {
|
|
1146
|
+
var key = line.charAt(index);
|
|
1147
|
+
if (el.accidental) {
|
|
1148
|
+
key = accMap[el.accidental] + key;
|
|
1149
|
+
}
|
|
1150
|
+
if (tune.formatting && tune.formatting.midi && tune.formatting.midi.drummap)
|
|
1151
|
+
el.midipitch = tune.formatting.midi.drummap[key];
|
|
1152
|
+
}
|
|
1153
|
+
} else if (isComplete(state)) {el.endChar = index;return el;}
|
|
1154
|
+
else return null;
|
|
1155
|
+
break;
|
|
1156
|
+
case ',':
|
|
1157
|
+
if (state === 'octave') {el.pitch -= 7; el.name += ','; }
|
|
1158
|
+
else if (isComplete(state)) {el.endChar = index;return el;}
|
|
1159
|
+
else return null;
|
|
1160
|
+
break;
|
|
1161
|
+
case '\'':
|
|
1162
|
+
if (state === 'octave') {el.pitch += 7; el.name += "'"; }
|
|
1163
|
+
else if (isComplete(state)) {el.endChar = index;return el;}
|
|
1164
|
+
else return null;
|
|
1165
|
+
break;
|
|
1166
|
+
case 'x':
|
|
1167
|
+
case 'X':
|
|
1168
|
+
case 'y':
|
|
1169
|
+
case 'z':
|
|
1170
|
+
case 'Z':
|
|
1171
|
+
if (state === 'startSlur') {
|
|
1172
|
+
el.rest = { type: rests[line.charAt(index)] };
|
|
1173
|
+
// There shouldn't be some of the properties that notes have. If some sneak in due to bad syntax in the abc file,
|
|
1174
|
+
// just nix them here.
|
|
1175
|
+
delete el.accidental;
|
|
1176
|
+
delete el.startSlur;
|
|
1177
|
+
delete el.startTie;
|
|
1178
|
+
delete el.endSlur;
|
|
1179
|
+
delete el.endTie;
|
|
1180
|
+
delete el.end_beam;
|
|
1181
|
+
delete el.grace_notes;
|
|
1182
|
+
// At this point we have a valid note. The rest is optional. Set the duration in case we don't get one below
|
|
1183
|
+
if (el.rest.type.indexOf('multimeasure') >= 0) {
|
|
1184
|
+
el.duration = tune.getBarLength();
|
|
1185
|
+
el.rest.text = 1;
|
|
1186
|
+
state = 'Zduration';
|
|
1187
|
+
} else {
|
|
1188
|
+
if (canHaveBrokenRhythm && multilineVars.next_note_duration !== 0) {
|
|
1189
|
+
el.duration = multilineVars.default_length * multilineVars.next_note_duration;
|
|
1190
|
+
multilineVars.next_note_duration = 0;
|
|
1191
|
+
durationSetByPreviousNote = true;
|
|
1192
|
+
} else
|
|
1193
|
+
el.duration = multilineVars.default_length;
|
|
1194
|
+
state = 'duration';
|
|
1195
|
+
}
|
|
1196
|
+
} else if (isComplete(state)) {el.endChar = index;return el;}
|
|
1197
|
+
else return null;
|
|
1198
|
+
break;
|
|
1199
|
+
case '1':
|
|
1200
|
+
case '2':
|
|
1201
|
+
case '3':
|
|
1202
|
+
case '4':
|
|
1203
|
+
case '5':
|
|
1204
|
+
case '6':
|
|
1205
|
+
case '7':
|
|
1206
|
+
case '8':
|
|
1207
|
+
case '9':
|
|
1208
|
+
case '0':
|
|
1209
|
+
case '/':
|
|
1210
|
+
if (state === 'octave' || state === 'duration') {
|
|
1211
|
+
var fraction = tokenizer.getFraction(line, index);
|
|
1212
|
+
//if (!durationSetByPreviousNote)
|
|
1213
|
+
el.duration = el.duration * fraction.value;
|
|
1214
|
+
// TODO-PER: We can test the returned duration here and give a warning if it isn't the one expected.
|
|
1215
|
+
el.endChar = fraction.index;
|
|
1216
|
+
while (fraction.index < line.length && (tokenizer.isWhiteSpace(line.charAt(fraction.index)) || line.charAt(fraction.index) === '-')) {
|
|
1217
|
+
if (line.charAt(fraction.index) === '-')
|
|
1218
|
+
el.startTie = {};
|
|
1219
|
+
else
|
|
1220
|
+
el = addEndBeam(el);
|
|
1221
|
+
fraction.index++;
|
|
1222
|
+
}
|
|
1223
|
+
index = fraction.index-1;
|
|
1224
|
+
state = 'broken_rhythm';
|
|
1225
|
+
} else if (state === 'sharp2') {
|
|
1226
|
+
el.accidental = 'quartersharp';state = 'pitch';
|
|
1227
|
+
} else if (state === 'flat2') {
|
|
1228
|
+
el.accidental = 'quarterflat';state = 'pitch';
|
|
1229
|
+
} else if (state === 'Zduration') {
|
|
1230
|
+
var num = tokenizer.getNumber(line, index);
|
|
1231
|
+
el.duration = num.num * tune.getBarLength();
|
|
1232
|
+
el.rest.text = num.num;
|
|
1233
|
+
el.endChar = num.index;
|
|
1234
|
+
return el;
|
|
1235
|
+
} else return null;
|
|
1236
|
+
break;
|
|
1237
|
+
case '-':
|
|
1238
|
+
if (state === 'startSlur') {
|
|
1239
|
+
// This is the first character, so it must have been meant for the previous note. Correct that here.
|
|
1240
|
+
tuneBuilder.addTieToLastNote(dottedTie);
|
|
1241
|
+
el.endTie = true;
|
|
1242
|
+
} else if (state === 'octave' || state === 'duration' || state === 'end_slur') {
|
|
1243
|
+
el.startTie = {};
|
|
1244
|
+
if (!durationSetByPreviousNote && canHaveBrokenRhythm)
|
|
1245
|
+
state = 'broken_rhythm';
|
|
1246
|
+
else {
|
|
1247
|
+
// Peek ahead to the next character. If it is a space, then we have an end beam.
|
|
1248
|
+
if (tokenizer.isWhiteSpace(line.charAt(index + 1)))
|
|
1249
|
+
addEndBeam(el);
|
|
1250
|
+
el.endChar = index+1;
|
|
1251
|
+
return el;
|
|
1252
|
+
}
|
|
1253
|
+
} else if (state === 'broken_rhythm') {el.endChar = index;return el;}
|
|
1254
|
+
else return null;
|
|
1255
|
+
break;
|
|
1256
|
+
case ' ':
|
|
1257
|
+
case '\t':
|
|
1258
|
+
if (isComplete(state)) {
|
|
1259
|
+
el.end_beam = true;
|
|
1260
|
+
// look ahead to see if there is a tie
|
|
1261
|
+
dottedTie = false;
|
|
1262
|
+
do {
|
|
1263
|
+
if (line.charAt(index) === '.' && line.charAt(index+1) === '-') {
|
|
1264
|
+
dottedTie = true;
|
|
1265
|
+
index++;
|
|
1266
|
+
}
|
|
1267
|
+
if (line.charAt(index) === '-') {
|
|
1268
|
+
el.startTie = {};
|
|
1269
|
+
if (dottedTie)
|
|
1270
|
+
el.startTie.style = "dotted";
|
|
1271
|
+
}
|
|
1272
|
+
index++;
|
|
1273
|
+
} while (index < line.length &&
|
|
1274
|
+
(tokenizer.isWhiteSpace(line.charAt(index)) || line.charAt(index) === '-') ||
|
|
1275
|
+
(line.charAt(index) === '.' && line.charAt(index+1) === '-'));
|
|
1276
|
+
el.endChar = index;
|
|
1277
|
+
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.
|
|
1278
|
+
index--;
|
|
1279
|
+
state = 'broken_rhythm';
|
|
1280
|
+
} else
|
|
1281
|
+
return el;
|
|
1282
|
+
}
|
|
1283
|
+
else return null;
|
|
1284
|
+
break;
|
|
1285
|
+
case '>':
|
|
1286
|
+
case '<':
|
|
1287
|
+
if (isComplete(state)) {
|
|
1288
|
+
if (canHaveBrokenRhythm) {
|
|
1289
|
+
var br2 = getBrokenRhythm(line, index);
|
|
1290
|
+
index += br2[0] - 1; // index gets incremented below, so we'll let that happen
|
|
1291
|
+
multilineVars.next_note_duration = br2[2];
|
|
1292
|
+
el.duration = br2[1]*el.duration;
|
|
1293
|
+
state = 'end_slur';
|
|
1294
|
+
} else {
|
|
1295
|
+
el.endChar = index;
|
|
1296
|
+
return el;
|
|
1297
|
+
}
|
|
1298
|
+
} else
|
|
1299
|
+
return null;
|
|
1300
|
+
break;
|
|
1301
|
+
default:
|
|
1302
|
+
if (isComplete(state)) {
|
|
1303
|
+
el.endChar = index;
|
|
1304
|
+
return el;
|
|
1305
|
+
}
|
|
1306
|
+
return null;
|
|
1307
|
+
}
|
|
1308
|
+
index++;
|
|
1309
|
+
if (index === line.length) {
|
|
1310
|
+
if (isComplete(state)) {el.endChar = index;return el;}
|
|
1311
|
+
else return null;
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
return null;
|
|
1315
|
+
};
|
|
1316
|
+
|
|
1317
|
+
var getBrokenRhythm = function(line, index) {
|
|
1318
|
+
switch (line.charAt(index)) {
|
|
1319
|
+
case '>':
|
|
1320
|
+
if (index < line.length - 2 && line.charAt(index + 1) === '>' && line.charAt(index + 2) === '>') // triple >>>
|
|
1321
|
+
return [3, 1.875, 0.125];
|
|
1322
|
+
else if (index < line.length - 1 && line.charAt(index + 1) === '>') // double >>
|
|
1323
|
+
return [2, 1.75, 0.25];
|
|
1324
|
+
else
|
|
1325
|
+
return [1, 1.5, 0.5];
|
|
1326
|
+
case '<':
|
|
1327
|
+
if (index < line.length - 2 && line.charAt(index + 1) === '<' && line.charAt(index + 2) === '<') // triple <<<
|
|
1328
|
+
return [3, 0.125, 1.875];
|
|
1329
|
+
else if (index < line.length - 1 && line.charAt(index + 1) === '<') // double <<
|
|
1330
|
+
return [2, 0.25, 1.75];
|
|
1331
|
+
else
|
|
1332
|
+
return [1, 0.5, 1.5];
|
|
1333
|
+
}
|
|
1334
|
+
return null;
|
|
1335
|
+
};
|
|
1336
|
+
|
|
1337
|
+
module.exports = MusicParser;
|