|
|
var parseKeyVoice = require('./abc_parse_key_voice'); |
|
|
var transpose = require('./abc_transpose'); |
|
|
|
|
|
var tokenizer; |
|
|
var warn; |
|
|
var multilineVars; |
|
|
var tune; |
|
|
var tuneBuilder; |
|
|
var header; |
|
|
|
|
|
var { |
|
|
legalAccents, |
|
|
volumeDecorations, |
|
|
dynamicDecorations, |
|
|
accentPseudonyms, |
|
|
accentDynamicPseudonyms, |
|
|
nonDecorations, |
|
|
durations, |
|
|
pitches, |
|
|
rests, |
|
|
accMap, |
|
|
tripletQ |
|
|
} = require('./abc_parse_settings') |
|
|
|
|
|
var MusicParser = function(_tokenizer, _warn, _multilineVars, _tune, _tuneBuilder, _header) { |
|
|
tokenizer = _tokenizer; |
|
|
warn = _warn; |
|
|
multilineVars = _multilineVars; |
|
|
tune = _tune; |
|
|
tuneBuilder = _tuneBuilder; |
|
|
header = _header; |
|
|
this.lineContinuation = false; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var isInTie = function(multilineVars, overlayLevel, el) { |
|
|
if (multilineVars.inTie[overlayLevel] === undefined) |
|
|
return false; |
|
|
|
|
|
var voiceIndex = multilineVars.currentVoice ? multilineVars.currentVoice.staffNum * 100 + multilineVars.currentVoice.index : 0; |
|
|
if (multilineVars.inTie[overlayLevel][voiceIndex]) { |
|
|
if (el.pitches !== undefined || el.rest.type !== 'spacer') |
|
|
return true; |
|
|
} |
|
|
return false; |
|
|
}; |
|
|
|
|
|
var el = { }; |
|
|
MusicParser.prototype.parseMusic = function(line) { |
|
|
header.resolveTempo(); |
|
|
|
|
|
multilineVars.is_in_header = false; |
|
|
var i = 0; |
|
|
var startOfLine = multilineVars.iChar; |
|
|
|
|
|
while (tokenizer.isWhiteSpace(line[i]) && i < line.length) |
|
|
i++; |
|
|
if (i === line.length || line[i] === '%') |
|
|
return; |
|
|
|
|
|
|
|
|
var delayStartNewLine = multilineVars.start_new_line; |
|
|
if (multilineVars.continueall === undefined) |
|
|
multilineVars.start_new_line = true; |
|
|
else |
|
|
multilineVars.start_new_line = false; |
|
|
var tripletNotesLeft = 0; |
|
|
|
|
|
|
|
|
var retHeader = header.letter_to_body_header(line, i); |
|
|
if (retHeader[0] > 0) { |
|
|
i += retHeader[0]; |
|
|
|
|
|
if (retHeader[1] === 'V') |
|
|
this.startNewLine(); |
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
var overlayLevel = 0; |
|
|
while (i < line.length) |
|
|
{ |
|
|
var startI = i; |
|
|
if (line[i] === '%') |
|
|
break; |
|
|
|
|
|
var retInlineHeader = header.letter_to_inline_header(line, i, delayStartNewLine); |
|
|
if (retInlineHeader[0] > 0) { |
|
|
i += retInlineHeader[0]; |
|
|
|
|
|
if (retInlineHeader[1] === 'V') |
|
|
delayStartNewLine = true; |
|
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
if (!tuneBuilder.hasBeginMusic() || (delayStartNewLine && !this.lineContinuation)) { |
|
|
this.startNewLine(); |
|
|
delayStartNewLine = false; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var ret; |
|
|
while (1) { |
|
|
ret = tokenizer.eatWhiteSpace(line, i); |
|
|
if (ret > 0) { |
|
|
i += ret; |
|
|
} |
|
|
if (i > 0 && line[i-1] === '\x12') { |
|
|
|
|
|
ret = header.letter_to_body_header(line, i); |
|
|
if (ret[0] > 0) { |
|
|
if (ret[1] === 'V') |
|
|
this.startNewLine(); |
|
|
|
|
|
i = ret[0]; |
|
|
multilineVars.start_new_line = false; |
|
|
} |
|
|
} |
|
|
|
|
|
ret = letter_to_spacer(line, i); |
|
|
if (ret[0] > 0) { |
|
|
i += ret[0]; |
|
|
} |
|
|
|
|
|
ret = letter_to_chord(line, i); |
|
|
if (ret[0] > 0) { |
|
|
|
|
|
|
|
|
if (!el.chord) |
|
|
el.chord = []; |
|
|
var chordName = tokenizer.translateString(ret[1]); |
|
|
chordName = chordName.replace(/;/g, "\n"); |
|
|
var addedChord = false; |
|
|
for (var ci = 0; ci < el.chord.length; ci++) { |
|
|
if (el.chord[ci].position === ret[2]) { |
|
|
addedChord = true; |
|
|
el.chord[ci].name += "\n" + chordName; |
|
|
} |
|
|
} |
|
|
if (addedChord === false) { |
|
|
if (ret[2] === null && ret[3]) |
|
|
el.chord.push({name: chordName, rel_position: ret[3]}); |
|
|
else |
|
|
el.chord.push({name: chordName, position: ret[2]}); |
|
|
} |
|
|
|
|
|
i += ret[0]; |
|
|
var ii = tokenizer.skipWhiteSpace(line.substring(i)); |
|
|
if (ii > 0) |
|
|
el.force_end_beam_last = true; |
|
|
i += ii; |
|
|
} else { |
|
|
if (nonDecorations.indexOf(line[i]) === -1) |
|
|
ret = letter_to_accent(line, i); |
|
|
else ret = [ 0 ]; |
|
|
if (ret[0] > 0) { |
|
|
if (ret[1] === null) { |
|
|
if (i + 1 < line.length) |
|
|
this.startNewLine(); |
|
|
} else if (ret[1].length > 0) { |
|
|
if (ret[1].indexOf("style=") === 0) { |
|
|
el.style = ret[1].substr(6); |
|
|
} else { |
|
|
if (el.decoration === undefined) |
|
|
el.decoration = []; |
|
|
if (ret[1] === 'beambr1') |
|
|
el.beambr = 1; |
|
|
else if (ret[1] === "beambr2") |
|
|
el.beambr = 2; |
|
|
else el.decoration.push(ret[1]); |
|
|
} |
|
|
} |
|
|
i += ret[0]; |
|
|
} else { |
|
|
ret = letter_to_grace(line, i); |
|
|
|
|
|
if (ret[0] > 0) { |
|
|
el.gracenotes = ret[1]; |
|
|
i += ret[0]; |
|
|
} else |
|
|
break; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
ret = letter_to_bar(line, i); |
|
|
if (ret[0] > 0) { |
|
|
|
|
|
overlayLevel = 0; |
|
|
if (el.gracenotes !== undefined) { |
|
|
|
|
|
el.rest = { type: 'spacer' }; |
|
|
el.duration = 0.125; |
|
|
multilineVars.addFormattingOptions(el, tune.formatting, 'note'); |
|
|
tuneBuilder.appendElement('note', startOfLine+i, startOfLine+i+ret[0], el); |
|
|
multilineVars.measureNotEmpty = true; |
|
|
el = {}; |
|
|
} |
|
|
var bar = {type: ret[1]}; |
|
|
if (bar.type.length === 0) |
|
|
warn("Unknown bar type", line, i); |
|
|
else { |
|
|
if (multilineVars.inEnding && bar.type !== 'bar_thin') { |
|
|
bar.endEnding = true; |
|
|
multilineVars.inEnding = false; |
|
|
} |
|
|
if (ret[2]) { |
|
|
bar.startEnding = ret[2]; |
|
|
if (multilineVars.inEnding) |
|
|
bar.endEnding = true; |
|
|
multilineVars.inEnding = true; |
|
|
if (ret[1] === "bar_right_repeat") { |
|
|
|
|
|
multilineVars.restoreStartEndingHoldOvers(); |
|
|
} else { |
|
|
|
|
|
multilineVars.duplicateStartEndingHoldOvers(); |
|
|
} |
|
|
} |
|
|
if (el.decoration !== undefined) |
|
|
bar.decoration = el.decoration; |
|
|
if (el.chord !== undefined) |
|
|
bar.chord = el.chord; |
|
|
if (bar.startEnding && multilineVars.barFirstEndingNum === undefined) |
|
|
multilineVars.barFirstEndingNum = multilineVars.currBarNumber; |
|
|
else if (bar.startEnding && bar.endEnding && multilineVars.barFirstEndingNum) |
|
|
multilineVars.currBarNumber = multilineVars.barFirstEndingNum; |
|
|
else if (bar.endEnding) |
|
|
multilineVars.barFirstEndingNum = undefined; |
|
|
if (bar.type !== 'bar_invisible' && multilineVars.measureNotEmpty) { |
|
|
var isFirstVoice = multilineVars.currentVoice === undefined || (multilineVars.currentVoice.staffNum === 0 && multilineVars.currentVoice.index === 0); |
|
|
if (isFirstVoice) { |
|
|
multilineVars.currBarNumber++; |
|
|
if (multilineVars.barNumbers && multilineVars.currBarNumber % multilineVars.barNumbers === 0) |
|
|
bar.barNumber = multilineVars.currBarNumber; |
|
|
} |
|
|
} |
|
|
multilineVars.addFormattingOptions(el, tune.formatting, 'bar'); |
|
|
tuneBuilder.appendElement('bar', startOfLine+startI, startOfLine+i+ret[0], bar); |
|
|
multilineVars.measureNotEmpty = false; |
|
|
el = {}; |
|
|
} |
|
|
i += ret[0]; |
|
|
} else if (line[i] === '&') { |
|
|
ret = letter_to_overlay(line, i); |
|
|
if (ret[0] > 0) { |
|
|
tuneBuilder.appendElement('overlay', startOfLine, startOfLine+1, {}); |
|
|
i += 1; |
|
|
overlayLevel++; |
|
|
} |
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
|
|
|
|
ret = letter_to_open_slurs_and_triplets(line, i); |
|
|
if (ret.consumed > 0) { |
|
|
if (ret.startSlur !== undefined) |
|
|
el.startSlur = ret.startSlur; |
|
|
if (ret.dottedSlur) |
|
|
el.dottedSlur = true; |
|
|
if (ret.triplet !== undefined) { |
|
|
if (tripletNotesLeft > 0) |
|
|
warn("Can't nest triplets", line, i); |
|
|
else { |
|
|
el.startTriplet = ret.triplet; |
|
|
el.tripletMultiplier = ret.tripletQ / ret.triplet; |
|
|
el.tripletR = ret.num_notes; |
|
|
tripletNotesLeft = ret.num_notes === undefined ? ret.triplet : ret.num_notes; |
|
|
} |
|
|
} |
|
|
i += ret.consumed; |
|
|
} |
|
|
|
|
|
|
|
|
if (line[i] === '[') { |
|
|
var chordStartChar = i; |
|
|
i++; |
|
|
var chordDuration = null; |
|
|
var rememberEndBeam = false; |
|
|
|
|
|
var done = false; |
|
|
while (!done) { |
|
|
var accent = letter_to_accent(line, i); |
|
|
if (accent[0] > 0) { |
|
|
i += accent[0]; |
|
|
} |
|
|
|
|
|
var chordNote = getCoreNote(line, i, {}, false); |
|
|
if (chordNote !== null && chordNote.pitch !== undefined) { |
|
|
if (accent[0] > 0) { |
|
|
if (accent[1].indexOf("style=") !== 0) { |
|
|
if (el.decoration === undefined) |
|
|
el.decoration = []; |
|
|
el.decoration.push(accent[1]); |
|
|
} |
|
|
} |
|
|
if (chordNote.end_beam) { |
|
|
el.end_beam = true; |
|
|
delete chordNote.end_beam; |
|
|
} |
|
|
if (el.pitches === undefined) { |
|
|
el.duration = chordNote.duration; |
|
|
el.pitches = [ chordNote ]; |
|
|
} else |
|
|
el.pitches.push(chordNote); |
|
|
delete chordNote.duration; |
|
|
if (accent[0] > 0) { |
|
|
if (accent[1].indexOf("style=") === 0) { |
|
|
el.pitches[el.pitches.length-1].style = accent[1].substr(6); |
|
|
} |
|
|
} |
|
|
|
|
|
if (multilineVars.inTieChord[el.pitches.length]) { |
|
|
chordNote.endTie = true; |
|
|
multilineVars.inTieChord[el.pitches.length] = undefined; |
|
|
} |
|
|
if (chordNote.startTie) |
|
|
multilineVars.inTieChord[el.pitches.length] = true; |
|
|
|
|
|
i = chordNote.endChar; |
|
|
delete chordNote.endChar; |
|
|
} else if (line[i] === ' ') { |
|
|
|
|
|
warn("Spaces are not allowed in chords", line, i); |
|
|
i++; |
|
|
} else { |
|
|
if (i < line.length && line[i] === ']') { |
|
|
|
|
|
i++; |
|
|
|
|
|
if (multilineVars.next_note_duration !== 0) { |
|
|
el.duration = el.duration * multilineVars.next_note_duration; |
|
|
multilineVars.next_note_duration = 0; |
|
|
} |
|
|
|
|
|
if (isInTie(multilineVars, overlayLevel, el)) { |
|
|
el.pitches.forEach(function(pitch) { pitch.endTie = true; }); |
|
|
setIsInTie(multilineVars, overlayLevel, false); |
|
|
} |
|
|
|
|
|
if (tripletNotesLeft > 0 && !(el.rest && el.rest.type === "spacer")) { |
|
|
tripletNotesLeft--; |
|
|
if (tripletNotesLeft === 0) { |
|
|
el.endTriplet = true; |
|
|
} |
|
|
} |
|
|
|
|
|
var postChordDone = false; |
|
|
while (i < line.length && !postChordDone) { |
|
|
switch (line[i]) { |
|
|
case ' ': |
|
|
case '\t': |
|
|
addEndBeam(el); |
|
|
break; |
|
|
case ')': |
|
|
if (el.endSlur === undefined) el.endSlur = 1; else el.endSlur++; |
|
|
break; |
|
|
case '-': |
|
|
el.pitches.forEach(function(pitch) { pitch.startTie = {}; }); |
|
|
setIsInTie(multilineVars, overlayLevel, true); |
|
|
break; |
|
|
case '>': |
|
|
case '<': |
|
|
var br2 = getBrokenRhythm(line, i); |
|
|
i += br2[0] - 1; |
|
|
multilineVars.next_note_duration = br2[2]; |
|
|
if (chordDuration) |
|
|
chordDuration = chordDuration * br2[1]; |
|
|
else |
|
|
chordDuration = br2[1]; |
|
|
break; |
|
|
case '1': |
|
|
case '2': |
|
|
case '3': |
|
|
case '4': |
|
|
case '5': |
|
|
case '6': |
|
|
case '7': |
|
|
case '8': |
|
|
case '9': |
|
|
case '/': |
|
|
var fraction = tokenizer.getFraction(line, i); |
|
|
chordDuration = fraction.value; |
|
|
i = fraction.index; |
|
|
var ch = line[i] |
|
|
if (ch === ' ') |
|
|
rememberEndBeam = true; |
|
|
if (ch === '-' || ch === ')' || ch === ' ' || ch === '<' || ch === '>') |
|
|
i--; |
|
|
else |
|
|
postChordDone = true; |
|
|
break; |
|
|
case '0': |
|
|
chordDuration = 0; |
|
|
break; |
|
|
default: |
|
|
postChordDone = true; |
|
|
break; |
|
|
} |
|
|
if (!postChordDone) { |
|
|
i++; |
|
|
} |
|
|
} |
|
|
} else |
|
|
warn("Expected ']' to end the chords", line, i); |
|
|
|
|
|
if (el.pitches !== undefined) { |
|
|
if (chordDuration !== null) { |
|
|
el.duration = el.duration * chordDuration; |
|
|
if (rememberEndBeam) |
|
|
addEndBeam(el); |
|
|
} |
|
|
|
|
|
multilineVars.addFormattingOptions(el, tune.formatting, 'note'); |
|
|
tuneBuilder.appendElement('note', startOfLine+startI, startOfLine+i, el); |
|
|
multilineVars.measureNotEmpty = true; |
|
|
el = {}; |
|
|
} |
|
|
done = true; |
|
|
} |
|
|
} |
|
|
|
|
|
} else { |
|
|
|
|
|
var el2 = {}; |
|
|
var core = getCoreNote(line, i, el2, true); |
|
|
if (el2.endTie !== undefined) setIsInTie(multilineVars, overlayLevel, true); |
|
|
if (core !== null) { |
|
|
if (core.pitch !== undefined) { |
|
|
el.pitches = [ { } ]; |
|
|
|
|
|
if (core.accidental !== undefined) el.pitches[0].accidental = core.accidental; |
|
|
el.pitches[0].pitch = core.pitch; |
|
|
el.pitches[0].name = core.name; |
|
|
if (core.midipitch || core.midipitch === 0) |
|
|
el.pitches[0].midipitch = core.midipitch; |
|
|
if (core.endSlur !== undefined) el.pitches[0].endSlur = core.endSlur; |
|
|
if (core.endTie !== undefined) el.pitches[0].endTie = core.endTie; |
|
|
if (core.startSlur !== undefined) el.pitches[0].startSlur = core.startSlur; |
|
|
if (el.startSlur !== undefined) el.pitches[0].startSlur = el.startSlur; |
|
|
if (el.dottedSlur !== undefined) el.pitches[0].dottedSlur = true; |
|
|
if (core.startTie !== undefined) el.pitches[0].startTie = core.startTie; |
|
|
if (el.startTie !== undefined) el.pitches[0].startTie = el.startTie; |
|
|
} else { |
|
|
el.rest = core.rest; |
|
|
if (core.endSlur !== undefined) el.endSlur = core.endSlur; |
|
|
if (core.endTie !== undefined) el.rest.endTie = core.endTie; |
|
|
if (core.startSlur !== undefined) el.startSlur = core.startSlur; |
|
|
if (core.startTie !== undefined) el.rest.startTie = core.startTie; |
|
|
if (el.startTie !== undefined) el.rest.startTie = el.startTie; |
|
|
} |
|
|
|
|
|
if (core.chord !== undefined) el.chord = core.chord; |
|
|
if (core.duration !== undefined) el.duration = core.duration; |
|
|
if (core.decoration !== undefined) el.decoration = core.decoration; |
|
|
if (core.graceNotes !== undefined) el.graceNotes = core.graceNotes; |
|
|
delete el.startSlur; |
|
|
delete el.dottedSlur; |
|
|
if (isInTie(multilineVars, overlayLevel, el)) { |
|
|
if (el.pitches !== undefined) { |
|
|
el.pitches[0].endTie = true; |
|
|
} else if (el.rest.type !== 'spacer') { |
|
|
el.rest.endTie = true; |
|
|
} |
|
|
setIsInTie(multilineVars, overlayLevel, false); |
|
|
} |
|
|
if (core.startTie || el.startTie) |
|
|
setIsInTie(multilineVars, overlayLevel, true); |
|
|
i = core.endChar; |
|
|
|
|
|
if (tripletNotesLeft > 0 && !(core.rest && core.rest.type === "spacer")) { |
|
|
tripletNotesLeft--; |
|
|
if (tripletNotesLeft === 0) { |
|
|
el.endTriplet = true; |
|
|
} |
|
|
} |
|
|
|
|
|
if (core.end_beam) |
|
|
addEndBeam(el); |
|
|
|
|
|
|
|
|
|
|
|
if (el.rest && el.rest.type === 'rest' && el.duration === 1 && durationOfMeasure(multilineVars) <= 1) { |
|
|
el.rest.type = 'whole'; |
|
|
|
|
|
el.duration = durationOfMeasure(multilineVars); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (el.duration < 1 && durations.indexOf(el.duration) === -1 && el.duration !== 0) { |
|
|
if (!el.rest || el.rest.type !== 'spacer') |
|
|
warn("Duration not representable: " + line.substring(startI, i), line, i); |
|
|
} |
|
|
|
|
|
multilineVars.addFormattingOptions(el, tune.formatting, 'note'); |
|
|
var succeeded = tuneBuilder.appendElement('note', startOfLine+startI, startOfLine+i, el); |
|
|
if (!succeeded) { |
|
|
this.startNewLine() |
|
|
tuneBuilder.appendElement('note', startOfLine+startI, startOfLine+i, el); |
|
|
} |
|
|
multilineVars.measureNotEmpty = true; |
|
|
el = {}; |
|
|
} |
|
|
} |
|
|
|
|
|
if (i === startI) { |
|
|
if (line[i] !== ' ' && line[i] !== '`') |
|
|
warn("Unknown character ignored", line, i); |
|
|
i++; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
this.lineContinuation = line.indexOf('\x12') >= 0 || (retHeader[0] > 0) |
|
|
if (!this.lineContinuation) { el = { } } |
|
|
}; |
|
|
|
|
|
var setIsInTie =function(multilineVars, overlayLevel, value) { |
|
|
|
|
|
var voiceIndex = multilineVars.currentVoice ? multilineVars.currentVoice.staffNum * 100 + multilineVars.currentVoice.index : 0; |
|
|
if (multilineVars.inTie[overlayLevel] === undefined) |
|
|
multilineVars.inTie[overlayLevel] = []; |
|
|
multilineVars.inTie[overlayLevel][voiceIndex] = value; |
|
|
}; |
|
|
|
|
|
var letter_to_chord = function(line, i) { |
|
|
if (line[i] === '"') |
|
|
{ |
|
|
var chord = tokenizer.getBrackettedSubstring(line, i, 5); |
|
|
if (!chord[2]) |
|
|
warn("Missing the closing quote while parsing the chord symbol", line , i); |
|
|
|
|
|
|
|
|
|
|
|
if (chord[0] > 0 && chord[1].length > 0 && chord[1][0] === '^') { |
|
|
chord[1] = chord[1].substring(1); |
|
|
chord[2] = 'above'; |
|
|
} else if (chord[0] > 0 && chord[1].length > 0 && chord[1][0] === '_') { |
|
|
chord[1] = chord[1].substring(1); |
|
|
chord[2] = 'below'; |
|
|
} else if (chord[0] > 0 && chord[1].length > 0 && chord[1][0] === '<') { |
|
|
chord[1] = chord[1].substring(1); |
|
|
chord[2] = 'left'; |
|
|
} else if (chord[0] > 0 && chord[1].length > 0 && chord[1][0] === '>') { |
|
|
chord[1] = chord[1].substring(1); |
|
|
chord[2] = 'right'; |
|
|
} else if (chord[0] > 0 && chord[1].length > 0 && chord[1][0] === '@') { |
|
|
|
|
|
chord[1] = chord[1].substring(1); |
|
|
var x = tokenizer.getFloat(chord[1]); |
|
|
if (x.digits === 0){ |
|
|
warn("Missing first position in absolutely positioned annotation.", line, i); |
|
|
chord[1] = chord[1].replace("@",""); |
|
|
chord[2] = 'above'; |
|
|
return chord; |
|
|
} |
|
|
chord[1] = chord[1].substring(x.digits); |
|
|
if (chord[1][0] !== ','){ |
|
|
warn("Missing comma absolutely positioned annotation.", line, i); |
|
|
chord[1] = chord[1].replace("@",""); |
|
|
chord[2] = 'above'; |
|
|
return chord; |
|
|
} |
|
|
chord[1] = chord[1].substring(1); |
|
|
var y = tokenizer.getFloat(chord[1]); |
|
|
if (y.digits === 0){ |
|
|
warn("Missing second position in absolutely positioned annotation.", line, i); |
|
|
chord[1] = chord[1].replace("@",""); |
|
|
chord[2] = 'above'; |
|
|
return chord; |
|
|
} |
|
|
chord[1] = chord[1].substring(y.digits); |
|
|
var ws = tokenizer.skipWhiteSpace(chord[1]); |
|
|
chord[1] = chord[1].substring(ws); |
|
|
chord[2] = null; |
|
|
chord[3] = { |
|
|
x: x.value, |
|
|
y: y.value |
|
|
}; |
|
|
} else { |
|
|
if (multilineVars.freegchord !== true) { |
|
|
chord[1] = chord[1].replace(/([ABCDEFG0-9])b/g, "$1♭"); |
|
|
chord[1] = chord[1].replace(/([ABCDEFG0-9])#/g, "$1♯"); |
|
|
chord[1] = chord[1].replace(/^([ABCDEFG])([♯♭]?)o([^A-Za-z])/g, "$1$2°$3"); |
|
|
chord[1] = chord[1].replace(/^([ABCDEFG])([♯♭]?)o$/g, "$1$2°"); |
|
|
chord[1] = chord[1].replace(/^([ABCDEFG])([♯♭]?)0([^A-Za-z])/g, "$1$2ø$3"); |
|
|
chord[1] = chord[1].replace(/^([ABCDEFG])([♯♭]?)\^([^A-Za-z])/g, "$1$2∆$3"); |
|
|
} |
|
|
chord[2] = 'default'; |
|
|
chord[1] = transpose.chordName(multilineVars, chord[1]); |
|
|
} |
|
|
return chord; |
|
|
} |
|
|
return [0, ""]; |
|
|
}; |
|
|
|
|
|
var letter_to_grace = function(line, i) { |
|
|
|
|
|
if (line[i] === '{') { |
|
|
|
|
|
var gra = tokenizer.getBrackettedSubstring(line, i, 1, '}'); |
|
|
if (!gra[2]) |
|
|
warn("Missing the closing '}' while parsing grace note", line, i); |
|
|
|
|
|
if (line[i+gra[0]] === ')') { |
|
|
gra[0]++; |
|
|
gra[1] += ')'; |
|
|
} |
|
|
|
|
|
var gracenotes = []; |
|
|
var ii = 0; |
|
|
var inTie = false; |
|
|
while (ii < gra[1].length) { |
|
|
var acciaccatura = false; |
|
|
if (gra[1][ii] === '/') { |
|
|
acciaccatura = true; |
|
|
ii++; |
|
|
} |
|
|
var note = getCoreNote(gra[1], ii, {}, false); |
|
|
if (note !== null) { |
|
|
|
|
|
note.duration = note.duration / (multilineVars.default_length * 8); |
|
|
if (acciaccatura) |
|
|
note.acciaccatura = true; |
|
|
if (note.rest) { |
|
|
|
|
|
warn("Rests not allowed as grace notes '" + gra[1][ii] + "' while parsing grace note", line, i); |
|
|
} else |
|
|
gracenotes.push(note); |
|
|
|
|
|
if (inTie) { |
|
|
note.endTie = true; |
|
|
inTie = false; |
|
|
} |
|
|
if (note.startTie) |
|
|
inTie = true; |
|
|
|
|
|
ii = note.endChar; |
|
|
delete note.endChar; |
|
|
|
|
|
if (note.end_beam) { |
|
|
note.endBeam = true; |
|
|
delete note.end_beam; |
|
|
} |
|
|
} |
|
|
else { |
|
|
|
|
|
if (gra[1][ii] === ' ') { |
|
|
if (gracenotes.length > 0) |
|
|
gracenotes[gracenotes.length-1].endBeam = true; |
|
|
} else |
|
|
warn("Unknown character '" + gra[1][ii] + "' while parsing grace note", line, i); |
|
|
ii++; |
|
|
} |
|
|
} |
|
|
if (gracenotes.length) |
|
|
return [gra[0], gracenotes]; |
|
|
} |
|
|
return [ 0 ]; |
|
|
}; |
|
|
|
|
|
function letter_to_overlay(line, i) { |
|
|
if (line[i] === '&') { |
|
|
var start = i; |
|
|
while (line[i] && line[i] !== ':' && line[i] !== '|') |
|
|
i++; |
|
|
return [ i-start, line.substring(start+1, i) ]; |
|
|
} |
|
|
return [ 0 ]; |
|
|
} |
|
|
|
|
|
function durationOfMeasure(multilineVars) { |
|
|
|
|
|
var meter = multilineVars.origMeter; |
|
|
if (!meter || meter.type !== 'specified') |
|
|
return 1; |
|
|
if (!meter.value || meter.value.length === 0) |
|
|
return 1; |
|
|
return parseInt(meter.value[0].num, 10) / parseInt(meter.value[0].den, 10); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var letter_to_accent = function(line, i) { |
|
|
var macro = multilineVars.macros[line[i]]; |
|
|
|
|
|
if (macro !== undefined) { |
|
|
if (macro[0] === '!' || macro[0] === '+') |
|
|
macro = macro.substring(1); |
|
|
if (macro[macro.length-1] === '!' || macro[macro.length-1] === '+') |
|
|
macro = macro.substring(0, macro.length-1); |
|
|
if (legalAccents.includes(macro)) |
|
|
return [ 1, macro ]; |
|
|
else if (volumeDecorations.includes(macro)) { |
|
|
if (multilineVars.volumePosition === 'hidden') |
|
|
macro = ""; |
|
|
return [1, macro]; |
|
|
} else if (dynamicDecorations.includes(macro)) { |
|
|
if (multilineVars.dynamicPosition === 'hidden') |
|
|
macro = ""; |
|
|
return [1, macro]; |
|
|
} else { |
|
|
if (!multilineVars.ignoredDecorations.includes(macro)) |
|
|
warn("Unknown macro: " + macro, line, i); |
|
|
return [1, '' ]; |
|
|
} |
|
|
} |
|
|
switch (line[i]) |
|
|
{ |
|
|
case '.': |
|
|
if (line[i+1] === '(' || line[i+1] === '-') |
|
|
break; |
|
|
return [1, 'staccato']; |
|
|
case 'u':return [1, 'upbow']; |
|
|
case 'v':return [1, 'downbow']; |
|
|
case '~':return [1, 'irishroll']; |
|
|
case '!': |
|
|
case '+': |
|
|
var ret = tokenizer.getBrackettedSubstring(line, i, 5); |
|
|
|
|
|
if (ret[1].length > 1 && (ret[1][0] === '^' || ret[1][0] ==='_')) |
|
|
ret[1] = ret[1].substring(1); |
|
|
if (legalAccents.includes(ret[1])) |
|
|
return ret; |
|
|
if (volumeDecorations.includes(ret[1])) { |
|
|
if (multilineVars.volumePosition === 'hidden' ) |
|
|
ret[1] = ''; |
|
|
return ret; |
|
|
} |
|
|
if (dynamicDecorations.includes(ret[1])) { |
|
|
if (multilineVars.dynamicPosition === 'hidden' ) |
|
|
ret[1] = ''; |
|
|
return ret; |
|
|
} |
|
|
|
|
|
var ind = accentPseudonyms.findIndex(function (acc) { return ret[1] === acc[0]}) |
|
|
if (ind >= 0) { |
|
|
ret[1] = accentPseudonyms[ind][1]; |
|
|
return ret; |
|
|
} |
|
|
|
|
|
ind = accentDynamicPseudonyms.findIndex(function (acc) { return ret[1] === acc[0]}) |
|
|
if (ind >= 0) { |
|
|
ret[1] = accentDynamicPseudonyms[ind][1]; |
|
|
if (multilineVars.dynamicPosition === 'hidden' ) |
|
|
ret[1] = ''; |
|
|
return ret; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (line[i] === '!' && (ret[0] === 1 || line[i+ret[0]-1] !== '!')) |
|
|
return [1, null ]; |
|
|
warn("Unknown decoration: " + ret[1], line, i); |
|
|
ret[1] = ""; |
|
|
return ret; |
|
|
case 'H':return [1, 'fermata']; |
|
|
case 'J':return [1, 'slide']; |
|
|
case 'L':return [1, 'accent']; |
|
|
case 'M':return [1, 'mordent']; |
|
|
case 'O':return[1, 'coda']; |
|
|
case 'P':return[1, 'pralltriller']; |
|
|
case 'R':return [1, 'roll']; |
|
|
case 'S':return [1, 'segno']; |
|
|
case 'T':return [1, 'trill']; |
|
|
} |
|
|
return [0, 0]; |
|
|
}; |
|
|
|
|
|
var letter_to_spacer = function(line, i) { |
|
|
var start = i; |
|
|
while (tokenizer.isWhiteSpace(line[i])) |
|
|
i++; |
|
|
return [ i-start ]; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var letter_to_bar = function(line, curr_pos) { |
|
|
var ret = tokenizer.getBarLine(line, curr_pos); |
|
|
if (ret.len === 0) |
|
|
return [0,""]; |
|
|
if (ret.warn) { |
|
|
warn(ret.warn, line, curr_pos); |
|
|
return [ret.len,""]; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (var ws = 0; ws < line.length; ws++) |
|
|
if (line[curr_pos + ret.len + ws] !== ' ') |
|
|
break; |
|
|
var orig_bar_len = ret.len; |
|
|
if (line[curr_pos+ret.len+ws] === '[') { |
|
|
ret.len += ws + 1; |
|
|
} |
|
|
|
|
|
|
|
|
if (line[curr_pos+ret.len] === '"' && line[curr_pos+ret.len-1] === '[') { |
|
|
var ending = tokenizer.getBrackettedSubstring(line, curr_pos+ret.len, 5); |
|
|
return [ret.len+ending[0], ret.token, ending[1]]; |
|
|
} |
|
|
var retRep = tokenizer.getTokenOf(line.substring(curr_pos+ret.len), "1234567890-,"); |
|
|
if (retRep.len === 0 || retRep.token[0] === '-') |
|
|
return [orig_bar_len, ret.token]; |
|
|
|
|
|
return [ret.len+retRep.len, ret.token, retRep.token]; |
|
|
}; |
|
|
|
|
|
var letter_to_open_slurs_and_triplets = function(line, i) { |
|
|
|
|
|
|
|
|
var ret = {}; |
|
|
var start = i; |
|
|
if (line[i] === '.' && line[i+1] === '(') { |
|
|
ret.dottedSlur = true; |
|
|
i++; |
|
|
} |
|
|
while (line[i] === '(' || tokenizer.isWhiteSpace(line[i])) { |
|
|
if (line[i] === '(') { |
|
|
if (i+1 < line.length && (line[i+1] >= '2' && line[i+1] <= '9')) { |
|
|
if (ret.triplet !== undefined) |
|
|
warn("Can't nest triplets", line, i); |
|
|
else { |
|
|
ret.triplet = line[i+1] - '0'; |
|
|
ret.tripletQ = tripletQ[ret.triplet]; |
|
|
ret.num_notes = ret.triplet; |
|
|
if (i+2 < line.length && line[i+2] === ':') { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (i+3 < line.length && line[i+3] === ':') { |
|
|
|
|
|
if (i+4 < line.length && (line[i+4] >= '1' && line[i+4] <= '9')) { |
|
|
ret.num_notes = line[i+4] - '0'; |
|
|
i += 3; |
|
|
} else |
|
|
warn("expected number after the two colons after the triplet to mark the duration", line, i); |
|
|
} else if (i+3 < line.length && (line[i+3] >= '1' && line[i+3] <= '9')) { |
|
|
ret.tripletQ = line[i+3] - '0'; |
|
|
if (i+4 < line.length && line[i+4] === ':') { |
|
|
if (i+5 < line.length && (line[i+5] >= '1' && line[i+5] <= '9')) { |
|
|
ret.num_notes = line[i+5] - '0'; |
|
|
i += 4; |
|
|
} |
|
|
} else { |
|
|
i += 2; |
|
|
} |
|
|
} else |
|
|
warn("expected number after the triplet to mark the duration", line, i); |
|
|
} |
|
|
} |
|
|
i++; |
|
|
} |
|
|
else { |
|
|
if (ret.startSlur === undefined) |
|
|
ret.startSlur = 1; |
|
|
else |
|
|
ret.startSlur++; |
|
|
} |
|
|
} |
|
|
i++; |
|
|
} |
|
|
ret.consumed = i-start; |
|
|
return ret; |
|
|
}; |
|
|
|
|
|
MusicParser.prototype.startNewLine = function() { |
|
|
var params = { startChar: -1, endChar: -1}; |
|
|
if (multilineVars.partForNextLine.title) |
|
|
params.part = multilineVars.partForNextLine; |
|
|
params.clef = multilineVars.currentVoice && multilineVars.staves[multilineVars.currentVoice.staffNum].clef !== undefined ? Object.assign({},multilineVars.staves[multilineVars.currentVoice.staffNum].clef) : Object.assign({},multilineVars.clef); |
|
|
var scoreTranspose = multilineVars.currentVoice ? multilineVars.currentVoice.scoreTranspose : 0; |
|
|
params.key = parseKeyVoice.standardKey(multilineVars.key.root+multilineVars.key.acc+multilineVars.key.mode, multilineVars.key.root, multilineVars.key.acc, scoreTranspose); |
|
|
params.key.mode = multilineVars.key.mode; |
|
|
if (multilineVars.key.impliedNaturals) |
|
|
params.key.impliedNaturals = multilineVars.key.impliedNaturals; |
|
|
if (multilineVars.key.explicitAccidentals) { |
|
|
for (var i = 0; i < multilineVars.key.explicitAccidentals.length; i++) { |
|
|
var found = false; |
|
|
for (var j = 0; j < params.key.accidentals.length; j++) { |
|
|
if (params.key.accidentals[j].note === multilineVars.key.explicitAccidentals[i].note) { |
|
|
|
|
|
params.key.accidentals[j].acc = multilineVars.key.explicitAccidentals[i].acc; |
|
|
found = true; |
|
|
} |
|
|
} |
|
|
if (!found) |
|
|
params.key.accidentals.push(multilineVars.key.explicitAccidentals[i]); |
|
|
} |
|
|
} |
|
|
multilineVars.targetKey = params.key; |
|
|
if (params.key.explicitAccidentals) |
|
|
delete params.key.explicitAccidentals; |
|
|
parseKeyVoice.addPosToKey(params.clef, params.key); |
|
|
if (multilineVars.meter !== null) { |
|
|
if (multilineVars.currentVoice) { |
|
|
multilineVars.staves.forEach(function(st) { |
|
|
st.meter = multilineVars.meter; |
|
|
}); |
|
|
params.meter = multilineVars.staves[multilineVars.currentVoice.staffNum].meter; |
|
|
multilineVars.staves[multilineVars.currentVoice.staffNum].meter = null; |
|
|
} else |
|
|
params.meter = multilineVars.meter; |
|
|
multilineVars.meter = null; |
|
|
} else if (multilineVars.currentVoice && multilineVars.staves[multilineVars.currentVoice.staffNum].meter) { |
|
|
|
|
|
params.meter = multilineVars.staves[multilineVars.currentVoice.staffNum].meter; |
|
|
multilineVars.staves[multilineVars.currentVoice.staffNum].meter = null; |
|
|
} |
|
|
if (multilineVars.currentVoice && multilineVars.currentVoice.name) |
|
|
params.name = multilineVars.currentVoice.name; |
|
|
if (multilineVars.vocalfont) |
|
|
params.vocalfont = multilineVars.vocalfont; |
|
|
if (multilineVars.tripletfont) |
|
|
params.tripletfont = multilineVars.tripletfont; |
|
|
if (multilineVars.gchordfont) |
|
|
params.gchordfont = multilineVars.gchordfont; |
|
|
if (multilineVars.style) |
|
|
params.style = multilineVars.style; |
|
|
if (multilineVars.currentVoice) { |
|
|
var staff = multilineVars.staves[multilineVars.currentVoice.staffNum]; |
|
|
if (staff.brace) params.brace = staff.brace; |
|
|
if (staff.bracket) params.bracket = staff.bracket; |
|
|
if (staff.connectBarLines) params.connectBarLines = staff.connectBarLines; |
|
|
if (staff.name) params.name = staff.name[multilineVars.currentVoice.index]; |
|
|
if (staff.subname) params.subname = staff.subname[multilineVars.currentVoice.index]; |
|
|
if (multilineVars.currentVoice.stem) |
|
|
params.stem = multilineVars.currentVoice.stem; |
|
|
if (multilineVars.currentVoice.stafflines) |
|
|
params.stafflines = multilineVars.currentVoice.stafflines; |
|
|
if (multilineVars.currentVoice.staffscale) |
|
|
params.staffscale = multilineVars.currentVoice.staffscale; |
|
|
if (multilineVars.currentVoice.scale) |
|
|
params.scale = multilineVars.currentVoice.scale; |
|
|
if (multilineVars.currentVoice.color) |
|
|
params.color = multilineVars.currentVoice.color; |
|
|
if (multilineVars.currentVoice.style) |
|
|
params.style = multilineVars.currentVoice.style; |
|
|
if (multilineVars.currentVoice.transpose) |
|
|
params.clef.transpose = multilineVars.currentVoice.transpose; |
|
|
params.currentVoice = multilineVars.currentVoice |
|
|
var voices = Object.keys(multilineVars.voices) |
|
|
for (var mv = 0; mv < voices.length; mv++) { |
|
|
if (params.currentVoice.staffNum === multilineVars.voices[voices[mv]].staffNum && params.currentVoice.index === multilineVars.voices[voices[mv]].index) |
|
|
params.currentVoiceName = voices[mv] |
|
|
} |
|
|
} |
|
|
var isFirstVoice = multilineVars.currentVoice === undefined || (multilineVars.currentVoice.staffNum === 0 && multilineVars.currentVoice.index === 0); |
|
|
if (multilineVars.barNumbers === 0 && isFirstVoice && multilineVars.currBarNumber !== 1) |
|
|
params.barNumber = multilineVars.currBarNumber; |
|
|
tuneBuilder.startNewLine(params); |
|
|
if (multilineVars.key.impliedNaturals) |
|
|
delete multilineVars.key.impliedNaturals; |
|
|
|
|
|
multilineVars.partForNextLine = {}; |
|
|
if (multilineVars.tempoForNextLine.length === 4) |
|
|
tuneBuilder.appendElement(multilineVars.tempoForNextLine[0],multilineVars.tempoForNextLine[1],multilineVars.tempoForNextLine[2],multilineVars.tempoForNextLine[3]); |
|
|
multilineVars.tempoForNextLine = []; |
|
|
} |
|
|
|
|
|
|
|
|
var addEndBeam = function(el) { |
|
|
if (el.duration !== undefined && el.duration < 0.25) |
|
|
el.end_beam = true; |
|
|
return el; |
|
|
}; |
|
|
|
|
|
var getCoreNote = function(line, index, el, canHaveBrokenRhythm) { |
|
|
|
|
|
var isComplete = function(state) { |
|
|
return (state === 'octave' || state === 'duration' || state === 'Zduration' || state === 'broken_rhythm' || state === 'end_slur'); |
|
|
}; |
|
|
var dottedTie; |
|
|
if (line[index] === '.' && line[index+1] === '-') { |
|
|
dottedTie = true; |
|
|
index++; |
|
|
} |
|
|
var state = 'startSlur'; |
|
|
var durationSetByPreviousNote = false; |
|
|
while (1) { |
|
|
switch(line[index]) { |
|
|
case '(': |
|
|
if (state === 'startSlur') { |
|
|
if (el.startSlur === undefined) el.startSlur = 1; else el.startSlur++; |
|
|
} else if (isComplete(state)) {el.endChar = index;return el;} |
|
|
else return null; |
|
|
break; |
|
|
case ')': |
|
|
if (isComplete(state)) { |
|
|
if (el.endSlur === undefined) el.endSlur = 1; else el.endSlur++; |
|
|
} else return null; |
|
|
break; |
|
|
case '^': |
|
|
if (state === 'startSlur') {el.accidental = 'sharp';state = 'sharp2';} |
|
|
else if (state === 'sharp2') {el.accidental = 'dblsharp';state = 'pitch';} |
|
|
else if (isComplete(state)) {el.endChar = index;return el;} |
|
|
else return null; |
|
|
break; |
|
|
case '_': |
|
|
if (state === 'startSlur') {el.accidental = 'flat';state = 'flat2';} |
|
|
else if (state === 'flat2') {el.accidental = 'dblflat';state = 'pitch';} |
|
|
else if (isComplete(state)) {el.endChar = index;return el;} |
|
|
else return null; |
|
|
break; |
|
|
case '=': |
|
|
if (state === 'startSlur') {el.accidental = 'natural';state = 'pitch';} |
|
|
else if (isComplete(state)) {el.endChar = index;return el;} |
|
|
else return null; |
|
|
break; |
|
|
case 'A': |
|
|
case 'B': |
|
|
case 'C': |
|
|
case 'D': |
|
|
case 'E': |
|
|
case 'F': |
|
|
case 'G': |
|
|
case 'a': |
|
|
case 'b': |
|
|
case 'c': |
|
|
case 'd': |
|
|
case 'e': |
|
|
case 'f': |
|
|
case 'g': |
|
|
if (state === 'startSlur' || state === 'sharp2' || state === 'flat2' || state === 'pitch') { |
|
|
el.pitch = pitches[line[index]]; |
|
|
el.pitch += 7 * (multilineVars.currentVoice && multilineVars.currentVoice.octave !== undefined ? multilineVars.currentVoice.octave : multilineVars.octave); |
|
|
el.name = line[index]; |
|
|
if (el.accidental) |
|
|
el.name = accMap[el.accidental] + el.name; |
|
|
transpose.note(multilineVars, el); |
|
|
state = 'octave'; |
|
|
|
|
|
if (canHaveBrokenRhythm && multilineVars.next_note_duration !== 0) { |
|
|
el.duration = multilineVars.default_length * multilineVars.next_note_duration; |
|
|
multilineVars.next_note_duration = 0; |
|
|
durationSetByPreviousNote = true; |
|
|
} else |
|
|
el.duration = multilineVars.default_length; |
|
|
|
|
|
if ((multilineVars.clef && multilineVars.clef.type === "perc") || |
|
|
(multilineVars.currentVoice && multilineVars.currentVoice.clef === "perc")) { |
|
|
var key = line[index]; |
|
|
if (el.accidental) { |
|
|
key = accMap[el.accidental] + key; |
|
|
} |
|
|
if (tune.formatting && tune.formatting.midi && tune.formatting.midi.drummap) |
|
|
el.midipitch = tune.formatting.midi.drummap[key]; |
|
|
} |
|
|
} else if (isComplete(state)) {el.endChar = index;return el;} |
|
|
else return null; |
|
|
break; |
|
|
case ',': |
|
|
if (state === 'octave') {el.pitch -= 7; el.name += ','; } |
|
|
else if (isComplete(state)) {el.endChar = index;return el;} |
|
|
else return null; |
|
|
break; |
|
|
case '\'': |
|
|
if (state === 'octave') {el.pitch += 7; el.name += "'"; } |
|
|
else if (isComplete(state)) {el.endChar = index;return el;} |
|
|
else return null; |
|
|
break; |
|
|
case 'x': |
|
|
case 'X': |
|
|
case 'y': |
|
|
case 'z': |
|
|
case 'Z': |
|
|
if (state === 'startSlur') { |
|
|
el.rest = { type: rests[line[index]] }; |
|
|
|
|
|
|
|
|
delete el.accidental; |
|
|
delete el.startSlur; |
|
|
delete el.startTie; |
|
|
delete el.endSlur; |
|
|
delete el.endTie; |
|
|
delete el.end_beam; |
|
|
delete el.grace_notes; |
|
|
|
|
|
if (el.rest.type.indexOf('multimeasure') >= 0) { |
|
|
el.duration = tune.getBarLength(); |
|
|
el.rest.text = 1; |
|
|
state = 'Zduration'; |
|
|
} else { |
|
|
if (canHaveBrokenRhythm && multilineVars.next_note_duration !== 0) { |
|
|
el.duration = multilineVars.default_length * multilineVars.next_note_duration; |
|
|
multilineVars.next_note_duration = 0; |
|
|
durationSetByPreviousNote = true; |
|
|
} else |
|
|
el.duration = multilineVars.default_length; |
|
|
state = 'duration'; |
|
|
} |
|
|
} else if (isComplete(state)) {el.endChar = index;return el;} |
|
|
else return null; |
|
|
break; |
|
|
case '1': |
|
|
case '2': |
|
|
case '3': |
|
|
case '4': |
|
|
case '5': |
|
|
case '6': |
|
|
case '7': |
|
|
case '8': |
|
|
case '9': |
|
|
case '0': |
|
|
case '/': |
|
|
if (state === 'octave' || state === 'duration') { |
|
|
var fraction = tokenizer.getFraction(line, index); |
|
|
|
|
|
el.duration = el.duration * fraction.value; |
|
|
|
|
|
el.endChar = fraction.index; |
|
|
while (fraction.index < line.length && (tokenizer.isWhiteSpace(line[fraction.index]) || line[fraction.index] === '-')) { |
|
|
if (line[fraction.index] === '-') |
|
|
el.startTie = {}; |
|
|
else |
|
|
el = addEndBeam(el); |
|
|
fraction.index++; |
|
|
} |
|
|
index = fraction.index-1; |
|
|
state = 'broken_rhythm'; |
|
|
} else if (state === 'sharp2') { |
|
|
el.accidental = 'quartersharp';state = 'pitch'; |
|
|
} else if (state === 'flat2') { |
|
|
el.accidental = 'quarterflat';state = 'pitch'; |
|
|
} else if (state === 'Zduration') { |
|
|
var num = tokenizer.getNumber(line, index); |
|
|
el.duration = num.num * tune.getBarLength(); |
|
|
el.rest.text = num.num; |
|
|
el.endChar = num.index; |
|
|
return el; |
|
|
} else return null; |
|
|
break; |
|
|
case '-': |
|
|
if (state === 'startSlur') { |
|
|
|
|
|
tuneBuilder.addTieToLastNote(dottedTie); |
|
|
el.endTie = true; |
|
|
} else if (state === 'octave' || state === 'duration' || state === 'end_slur') { |
|
|
el.startTie = {}; |
|
|
if (!durationSetByPreviousNote && canHaveBrokenRhythm) |
|
|
state = 'broken_rhythm'; |
|
|
else { |
|
|
|
|
|
if (tokenizer.isWhiteSpace(line[index + 1])) |
|
|
addEndBeam(el); |
|
|
el.endChar = index+1; |
|
|
return el; |
|
|
} |
|
|
} else if (state === 'broken_rhythm') {el.endChar = index;return el;} |
|
|
else return null; |
|
|
break; |
|
|
case ' ': |
|
|
case '\t': |
|
|
if (isComplete(state)) { |
|
|
el.end_beam = true; |
|
|
|
|
|
dottedTie = false; |
|
|
do { |
|
|
if (line[index] === '.' && line[index+1] === '-') { |
|
|
dottedTie = true; |
|
|
index++; |
|
|
} |
|
|
if (line[index] === '-') { |
|
|
el.startTie = {}; |
|
|
if (dottedTie) |
|
|
el.startTie.style = "dotted"; |
|
|
} |
|
|
index++; |
|
|
} while (index < line.length && |
|
|
(tokenizer.isWhiteSpace(line[index]) || line[index] === '-') || |
|
|
(line[index] === '.' && line[index+1] === '-')); |
|
|
el.endChar = index; |
|
|
if (!durationSetByPreviousNote && canHaveBrokenRhythm && (line[index] === '<' || line[index] === '>')) { |
|
|
index--; |
|
|
state = 'broken_rhythm'; |
|
|
} else |
|
|
return el; |
|
|
} |
|
|
else return null; |
|
|
break; |
|
|
case '>': |
|
|
case '<': |
|
|
if (isComplete(state)) { |
|
|
if (canHaveBrokenRhythm) { |
|
|
var br2 = getBrokenRhythm(line, index); |
|
|
index += br2[0] - 1; |
|
|
multilineVars.next_note_duration = br2[2]; |
|
|
el.duration = br2[1]*el.duration; |
|
|
state = 'end_slur'; |
|
|
} else { |
|
|
el.endChar = index; |
|
|
return el; |
|
|
} |
|
|
} else |
|
|
return null; |
|
|
break; |
|
|
default: |
|
|
if (isComplete(state)) { |
|
|
el.endChar = index; |
|
|
return el; |
|
|
} |
|
|
return null; |
|
|
} |
|
|
index++; |
|
|
if (index === line.length) { |
|
|
if (isComplete(state)) {el.endChar = index;return el;} |
|
|
else return null; |
|
|
} |
|
|
} |
|
|
return null; |
|
|
}; |
|
|
|
|
|
var getBrokenRhythm = function(line, index) { |
|
|
switch (line[index]) { |
|
|
case '>': |
|
|
if (index < line.length - 2 && line[index + 1] === '>' && line[index + 2] === '>') |
|
|
return [3, 1.875, 0.125]; |
|
|
else if (index < line.length - 1 && line[index + 1] === '>') |
|
|
return [2, 1.75, 0.25]; |
|
|
else |
|
|
return [1, 1.5, 0.5]; |
|
|
case '<': |
|
|
if (index < line.length - 2 && line[index + 1] === '<' && line[index + 2] === '<') |
|
|
return [3, 0.125, 1.875]; |
|
|
else if (index < line.length - 1 && line[index + 1] === '<') |
|
|
return [2, 0.25, 1.75]; |
|
|
else |
|
|
return [1, 0.5, 1.5]; |
|
|
} |
|
|
return null; |
|
|
}; |
|
|
|
|
|
module.exports = MusicParser; |
|
|
|