| | |
| |
|
| | var parseCommon = require('./abc_common'); |
| | var parseDirective = require('./abc_parse_directive'); |
| | var ParseHeader = require('./abc_parse_header'); |
| | var ParseMusic = require('./abc_parse_music'); |
| | var Tokenizer = require('./abc_tokenizer'); |
| | var wrap = require('./wrap_lines'); |
| |
|
| | var Tune = require('../data/abc_tune'); |
| | var TuneBuilder = require('../parse/tune-builder'); |
| |
|
| | var Parse = function() { |
| | "use strict"; |
| | var tune = new Tune(); |
| | var tuneBuilder = new TuneBuilder(tune); |
| | var tokenizer; |
| | var wordsContinuation = ''; |
| | var symbolContinuation = ''; |
| |
|
| | this.getTune = function() { |
| | var t = { |
| | formatting: tune.formatting, |
| | lines: tune.lines, |
| | media: tune.media, |
| | metaText: tune.metaText, |
| | metaTextInfo: tune.metaTextInfo, |
| | version: tune.version, |
| |
|
| | addElementToEvents: tune.addElementToEvents, |
| | addUsefulCallbackInfo: tune.addUsefulCallbackInfo, |
| | getTotalTime: tune.getTotalTime, |
| | getTotalBeats: tune.getTotalBeats, |
| | getBarLength: tune.getBarLength, |
| | getBeatLength: tune.getBeatLength, |
| | getBeatsPerMeasure: tune.getBeatsPerMeasure, |
| | getBpm: tune.getBpm, |
| | getMeter: tune.getMeter, |
| | getMeterFraction: tune.getMeterFraction, |
| | getPickupLength: tune.getPickupLength, |
| | getKeySignature: tune.getKeySignature, |
| | getElementFromChar: tune.getElementFromChar, |
| | makeVoicesArray: tune.makeVoicesArray, |
| | millisecondsPerMeasure: tune.millisecondsPerMeasure, |
| | setupEvents: tune.setupEvents, |
| | setTiming: tune.setTiming, |
| | setUpAudio: tune.setUpAudio, |
| | deline: tune.deline, |
| | findSelectableElement: tune.findSelectableElement, |
| | getSelectableArray: tune.getSelectableArray, |
| | }; |
| | if (tune.lineBreaks) |
| | t.lineBreaks = tune.lineBreaks; |
| | if (tune.visualTranspose) |
| | t.visualTranspose = tune.visualTranspose; |
| | return t; |
| | }; |
| |
|
| | function addPositioning(el, type, value) { |
| | if (!el.positioning) el.positioning = {}; |
| | el.positioning[type] = value; |
| | } |
| |
|
| | function addFont(el, type, value) { |
| | if (!el.fonts) el.fonts = {}; |
| | el.fonts[type] = value; |
| | } |
| |
|
| | var multilineVars = { |
| | reset: function() { |
| | for (var property in this) { |
| | if (this.hasOwnProperty(property) && typeof this[property] !== "function") { |
| | delete this[property]; |
| | } |
| | } |
| | this.iChar = 0; |
| | this.key = {accidentals: [], root: 'none', acc: '', mode: '' }; |
| | this.meter = null; |
| | this.origMeter = null; |
| | this.hasMainTitle = false; |
| | this.default_length = 0.125; |
| | this.clef = { type: 'treble', verticalPos: 0 }; |
| | this.octave = 0; |
| | this.next_note_duration = 0; |
| | this.start_new_line = true; |
| | this.is_in_header = true; |
| | this.partForNextLine = {}; |
| | this.tempoForNextLine = []; |
| | this.havent_set_length = true; |
| | this.voices = {}; |
| | this.staves = []; |
| | this.macros = {}; |
| | this.currBarNumber = 1; |
| | this.barCounter = {}; |
| | this.ignoredDecorations = []; |
| | this.score_is_present = false; |
| | this.inEnding = false; |
| | this.inTie = []; |
| | this.inTieChord = {}; |
| | this.vocalPosition = "auto"; |
| | this.dynamicPosition = "auto"; |
| | this.chordPosition = "auto"; |
| | this.ornamentPosition = "auto"; |
| | this.volumePosition = "auto"; |
| | this.openSlurs = []; |
| | this.freegchord = false; |
| | this.endingHoldOver = {}; |
| | }, |
| | differentFont: function(type, defaultFonts) { |
| | if (this[type].decoration !== defaultFonts[type].decoration) return true; |
| | if (this[type].face !== defaultFonts[type].face) return true; |
| | if (this[type].size !== defaultFonts[type].size) return true; |
| | if (this[type].style !== defaultFonts[type].style) return true; |
| | if (this[type].weight !== defaultFonts[type].weight) return true; |
| | return false; |
| | }, |
| | addFormattingOptions: function(el, defaultFonts, elType) { |
| | if (elType === 'note') { |
| | if (this.vocalPosition !== 'auto') addPositioning(el, 'vocalPosition', this.vocalPosition); |
| | if (this.dynamicPosition !== 'auto') addPositioning(el, 'dynamicPosition', this.dynamicPosition); |
| | if (this.chordPosition !== 'auto') addPositioning(el, 'chordPosition', this.chordPosition); |
| | if (this.ornamentPosition !== 'auto') addPositioning(el, 'ornamentPosition', this.ornamentPosition); |
| | if (this.volumePosition !== 'auto') addPositioning(el, 'volumePosition', this.volumePosition); |
| | if (this.differentFont("annotationfont", defaultFonts)) addFont(el, 'annotationfont', this.annotationfont); |
| | if (this.differentFont("gchordfont", defaultFonts)) addFont(el, 'gchordfont', this.gchordfont); |
| | if (this.differentFont("vocalfont", defaultFonts)) addFont(el, 'vocalfont', this.vocalfont); |
| | if (this.differentFont("tripletfont", defaultFonts)) addFont(el, 'tripletfont', this.tripletfont); |
| | } else if (elType === 'bar') { |
| | if (this.dynamicPosition !== 'auto') addPositioning(el, 'dynamicPosition', this.dynamicPosition); |
| | if (this.chordPosition !== 'auto') addPositioning(el, 'chordPosition', this.chordPosition); |
| | if (this.ornamentPosition !== 'auto') addPositioning(el, 'ornamentPosition', this.ornamentPosition); |
| | if (this.volumePosition !== 'auto') addPositioning(el, 'volumePosition', this.volumePosition); |
| | if (this.differentFont("measurefont", defaultFonts)) addFont(el, 'measurefont', this.measurefont); |
| | if (this.differentFont("repeatfont", defaultFonts)) addFont(el, 'repeatfont', this.repeatfont); |
| | } |
| | }, |
| | duplicateStartEndingHoldOvers: function() { |
| | this.endingHoldOver = { |
| | inTie: [], |
| | inTieChord: {} |
| | }; |
| | for (var i = 0; i < this.inTie.length; i++) { |
| | this.endingHoldOver.inTie.push([]); |
| | if (this.inTie[i]) { |
| | for (var j = 0; j < this.inTie[i].length; j++) { |
| | this.endingHoldOver.inTie[i].push(this.inTie[i][j]); |
| | } |
| | } |
| | } |
| | for (var key in this.inTieChord) { |
| | if (this.inTieChord.hasOwnProperty(key)) |
| | this.endingHoldOver.inTieChord[key] = this.inTieChord[key]; |
| | } |
| | }, |
| | restoreStartEndingHoldOvers: function() { |
| | if (!this.endingHoldOver.inTie) |
| | return; |
| | this.inTie = []; |
| | this.inTieChord = {}; |
| | for (var i = 0; i < this.endingHoldOver.inTie.length; i++) { |
| | this.inTie.push([]); |
| | for (var j = 0; j < this.endingHoldOver.inTie[i].length; j++) { |
| | this.inTie[i].push(this.endingHoldOver.inTie[i][j]); |
| | } |
| | } |
| | for (var key in this.endingHoldOver.inTieChord) { |
| | if (this.endingHoldOver.inTieChord.hasOwnProperty(key)) |
| | this.inTieChord[key] = this.endingHoldOver.inTieChord[key]; |
| | } |
| | }, |
| | }; |
| |
|
| | var addWarning = function(str) { |
| | if (!multilineVars.warnings) |
| | multilineVars.warnings = []; |
| | multilineVars.warnings.push(str); |
| | }; |
| |
|
| | var addWarningObject = function(warningObject) { |
| | if (!multilineVars.warningObjects) |
| | multilineVars.warningObjects = []; |
| | multilineVars.warningObjects.push(warningObject); |
| | }; |
| |
|
| | var encode = function(str) { |
| | var ret = str.replace(/\x12/g, ' '); |
| | ret = ret.replace(/&/g, '&'); |
| | ret = ret.replace(/</g, '<'); |
| | return ret.replace(/>/g, '>'); |
| | }; |
| |
|
| | var warn = function(str, line, col_num) { |
| | if (!line) line = " "; |
| | var bad_char = line[col_num]; |
| | if (bad_char === ' ' || !bad_char) |
| | bad_char = "SPACE"; |
| | var clean_line = encode(line.substring(col_num - 64, col_num)) + '<span style="text-decoration:underline;font-size:1.3em;font-weight:bold;">' + bad_char + '</span>' + encode(line.substring(col_num + 1).substring(0,64)); |
| | addWarning("Music Line:" + tokenizer.lineIndex + ":" + (col_num+1) + ': ' + str + ": " + clean_line); |
| | addWarningObject({message:str, line:line, startChar: multilineVars.iChar + col_num, column: col_num}); |
| | }; |
| |
|
| | var header; |
| | var music; |
| |
|
| | this.getWarnings = function() { |
| | return multilineVars.warnings; |
| | }; |
| | this.getWarningObjects = function() { |
| | return multilineVars.warningObjects; |
| | }; |
| |
|
| | var addWords = function(line, words) { |
| | if (words.indexOf('\x12') >= 0) { |
| | wordsContinuation += words |
| | return |
| | } |
| | words = wordsContinuation + words |
| | wordsContinuation = '' |
| |
|
| | if (!line) { warn("Can't add words before the first line of music", line, 0); return; } |
| | words = parseCommon.strip(words); |
| | if (words[words.length-1] !== '-') |
| | words = words + ' '; |
| | var word_list = []; |
| | |
| | var last_divider = 0; |
| | var replace = false; |
| | var addWord = function(i) { |
| | var word = parseCommon.strip(words.substring(last_divider, i)); |
| | word = word.replace(/\\([-_*|~])/g, '$1') |
| | last_divider = i+1; |
| | if (word.length > 0) { |
| | if (replace) |
| | word = word.replace(/~/g, ' '); |
| | var div = words[i]; |
| | if (div !== '_' && div !== '-') |
| | div = ' '; |
| | word_list.push({syllable: tokenizer.translateString(word), divider: div}); |
| | replace = false; |
| | return true; |
| | } |
| | return false; |
| | }; |
| | var escNext = false; |
| | for (var i = 0; i < words.length; i++) { |
| | switch (words[i]) { |
| | case ' ': |
| | case '\x12': |
| | addWord(i); |
| | break; |
| | case '-': |
| | if (!escNext && !addWord(i) && word_list.length > 0) { |
| | parseCommon.last(word_list).divider = '-'; |
| | word_list.push({skip: true, to: 'next'}); |
| | } |
| | break; |
| | case '_': |
| | if (!escNext) { |
| | addWord(i); |
| | word_list.push({skip: true, to: 'slur'}); |
| | } |
| | break; |
| | case '*': |
| | if (!escNext) { |
| | addWord(i); |
| | word_list.push({skip: true, to: 'next'}); |
| | } |
| | break; |
| | case '|': |
| | if (!escNext) { |
| | addWord(i); |
| | word_list.push({skip: true, to: 'bar'}); |
| | } |
| | break; |
| | case '~': |
| | if (!escNext) { |
| | replace = true; |
| | } |
| | break; |
| | } |
| | escNext = words[i] === '\\' |
| | } |
| |
|
| | var inSlur = false; |
| | line.forEach(function(el) { |
| | if (word_list.length !== 0) { |
| | if (word_list[0].skip) { |
| | switch (word_list[0].to) { |
| | case 'next': if (el.el_type === 'note' && el.pitches !== null && !inSlur) word_list.shift(); break; |
| | case 'slur': if (el.el_type === 'note' && el.pitches !== null) word_list.shift(); break; |
| | case 'bar': if (el.el_type === 'bar') word_list.shift(); break; |
| | } |
| | if (el.el_type !== 'bar') { |
| | if (el.lyric === undefined) |
| | el.lyric = [{syllable: "", divider: " "}]; |
| | else |
| | el.lyric.push({syllable: "", divider: " "}); |
| | } |
| | } else { |
| | if (el.el_type === 'note' && el.rest === undefined && !inSlur) { |
| | var lyric = word_list.shift(); |
| | if (lyric.syllable) |
| | lyric.syllable = lyric.syllable.replace(/ +/g,'\xA0'); |
| | if (el.lyric === undefined) |
| | el.lyric = [ lyric ]; |
| | else |
| | el.lyric.push(lyric); |
| | } |
| | } |
| | } |
| | }); |
| | }; |
| |
|
| | var addSymbols = function(line, words) { |
| | if (words.indexOf('\x12') >= 0) { |
| | symbolContinuation += words |
| | return |
| | } |
| | words = symbolContinuation + words |
| | symbolContinuation = '' |
| |
|
| | |
| | if (!line) { warn("Can't add symbols before the first line of music", line, 0); return; } |
| | words = parseCommon.strip(words); |
| | if (words[words.length-1] !== '-') |
| | words = words + ' '; |
| | var word_list = []; |
| | |
| | var last_divider = 0; |
| | var replace = false; |
| | var addWord = function(i) { |
| | var word = parseCommon.strip(words.substring(last_divider, i)); |
| | last_divider = i+1; |
| | if (word.length > 0) { |
| | if (replace) |
| | word = word.replace(/~/g, ' '); |
| | var div = words[i]; |
| | if (div !== '_' && div !== '-') |
| | div = ' '; |
| | word_list.push({syllable: tokenizer.translateString(word), divider: div}); |
| | replace = false; |
| | return true; |
| | } |
| | return false; |
| | }; |
| | for (var i = 0; i < words.length; i++) { |
| | switch (words[i]) { |
| | case ' ': |
| | case '\x12': |
| | addWord(i); |
| | break; |
| | case '-': |
| | if (!addWord(i) && word_list.length > 0) { |
| | parseCommon.last(word_list).divider = '-'; |
| | word_list.push({skip: true, to: 'next'}); |
| | } |
| | break; |
| | case '_': |
| | addWord(i); |
| | word_list.push({skip: true, to: 'slur'}); |
| | break; |
| | case '*': |
| | addWord(i); |
| | word_list.push({skip: true, to: 'next'}); |
| | break; |
| | case '|': |
| | addWord(i); |
| | word_list.push({skip: true, to: 'bar'}); |
| | break; |
| | case '~': |
| | replace = true; |
| | break; |
| | } |
| | } |
| |
|
| | var inSlur = false; |
| | line.forEach(function(el) { |
| | if (word_list.length !== 0) { |
| | if (word_list[0].skip) { |
| | switch (word_list[0].to) { |
| | case 'next': if (el.el_type === 'note' && el.pitches !== null && !inSlur) word_list.shift(); break; |
| | case 'slur': if (el.el_type === 'note' && el.pitches !== null) word_list.shift(); break; |
| | case 'bar': if (el.el_type === 'bar') word_list.shift(); break; |
| | } |
| | } else { |
| | if (el.el_type === 'note' && el.rest === undefined && !inSlur) { |
| | var lyric = word_list.shift(); |
| | if (el.lyric === undefined) |
| | el.lyric = [ lyric ]; |
| | else |
| | el.lyric.push(lyric); |
| | } |
| | } |
| | } |
| | }); |
| | }; |
| |
|
| | var parseLine = function(line) { |
| | if (parseCommon.startsWith(line, '%%')) { |
| | var err = parseDirective.addDirective(line.substring(2)); |
| | if (err) warn(err, line, 2); |
| | return; |
| | } |
| |
|
| | var i = line.indexOf('%'); |
| | if (i >= 0) |
| | line = line.substring(0, i); |
| | line = line.replace(/\s+$/, ''); |
| |
|
| | if (line.length === 0) |
| | return; |
| |
|
| | if (wordsContinuation) { |
| | addWords(tuneBuilder.getCurrentVoice(), line.substring(2)); |
| | return |
| | } |
| | if (symbolContinuation) { |
| | addSymbols(tuneBuilder.getCurrentVoice(), line.substring(2)); |
| | return |
| | } |
| | if (line.length < 2 || line[1] !== ':' || music.lineContinuation) { |
| | music.parseMusic(line); |
| | return |
| | } |
| |
|
| | var ret = header.parseHeader(line); |
| | if (ret.regular) |
| | music.parseMusic(line); |
| | if (ret.newline) |
| | music.startNewLine(); |
| | if (ret.words) |
| | addWords(tuneBuilder.getCurrentVoice(), line.substring(2)); |
| | if (ret.symbols) |
| | addSymbols(tuneBuilder.getCurrentVoice(), line.substring(2)); |
| | }; |
| |
|
| | function appendLastMeasure(voice, nextVoice) { |
| | voice.push({ |
| | el_type: 'hint' |
| | }); |
| | for (var i = 0; i < nextVoice.length; i++) { |
| | var element = nextVoice[i]; |
| | var hint = Object.assign({},element); |
| | voice.push(hint); |
| | if (element.el_type === 'bar') |
| | return; |
| | } |
| | } |
| |
|
| | function addHintMeasure(staff, nextStaff) { |
| | for (var i = 0; i < staff.length; i++) { |
| | var stave = staff[i]; |
| | var nextStave = nextStaff[i]; |
| | if (nextStave) { |
| | for (var j = 0; j < nextStave.voices.length; j++) { |
| | var nextVoice = nextStave.voices[j]; |
| | var voice = stave.voices[j]; |
| | if (voice) { |
| | appendLastMeasure(voice, nextVoice); |
| | } |
| | } |
| | } |
| | } |
| | } |
| |
|
| | function addHintMeasures() { |
| | for (var i = 0; i < tune.lines.length; i++) { |
| | var line = tune.lines[i].staff; |
| | if (line) { |
| | var j = i+1; |
| | while (j < tune.lines.length && tune.lines[j].staff === undefined) |
| | j++; |
| | if (j < tune.lines.length) { |
| | var nextLine = tune.lines[j].staff; |
| | addHintMeasure(line, nextLine); |
| | } |
| | } |
| | } |
| | } |
| |
|
| | this.parse = function(strTune, switches, startPos) { |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | if (!switches) switches = {}; |
| | if (!startPos) startPos = 0; |
| | tune.reset(); |
| |
|
| | |
| | |
| | strTune = strTune.replace(/\r\n?/g, '\n') + '\n'; |
| |
|
| | |
| | var arr = strTune.split("\n\\"); |
| | if (arr.length > 1) { |
| | for (var i2 = 1; i2 < arr.length; i2++) { |
| | while (arr[i2].length > 0 && arr[i2][0] !== "\n") { |
| | arr[i2] = arr[i2].substr(1); |
| | arr[i2-1] += ' '; |
| | } |
| | } |
| | strTune = arr.join(" "); |
| | } |
| | |
| | strTune = strTune.replace(/\\([ \t]*)(%.*)*\n/g, function(all, backslash, comment){ |
| | var padding = comment ? Array(comment.length +1).join(' ') : ""; |
| | return backslash + "\x12" + padding + '\n'; |
| | }); |
| | var lines = strTune.split('\n') |
| | if (parseCommon.last(lines).length === 0) |
| | lines.pop(); |
| | tokenizer = new Tokenizer(lines, multilineVars); |
| | header = new ParseHeader(tokenizer, warn, multilineVars, tune, tuneBuilder); |
| | music = new ParseMusic(tokenizer, warn, multilineVars, tune, tuneBuilder, header); |
| |
|
| | if (switches.print) |
| | tune.media = 'print'; |
| | multilineVars.reset(); |
| | multilineVars.iChar = startPos; |
| | if (switches.visualTranspose) { |
| | multilineVars.globalTranspose = parseInt(switches.visualTranspose); |
| | if (multilineVars.globalTranspose === 0) |
| | multilineVars.globalTranspose = undefined; |
| | else |
| | tuneBuilder.setVisualTranspose(switches.visualTranspose); |
| | } else |
| | multilineVars.globalTranspose = undefined; |
| | if (switches.lineBreaks) { |
| | |
| | multilineVars.lineBreaks = switches.lineBreaks; |
| | |
| | } |
| | header.reset(tokenizer, warn, multilineVars, tune); |
| |
|
| | try { |
| | if (switches.format) { |
| | parseDirective.globalFormatting(switches.format); |
| | } |
| | var line = tokenizer.nextLine(); |
| | while (line) { |
| | if (switches.header_only && multilineVars.is_in_header === false) |
| | throw "normal_abort"; |
| | if (switches.stop_on_warning && multilineVars.warnings) |
| | throw "normal_abort"; |
| |
|
| | var wasInHeader = multilineVars.is_in_header; |
| | parseLine(line); |
| | if (wasInHeader && !multilineVars.is_in_header) { |
| | tuneBuilder.setRunningFont("annotationfont", multilineVars.annotationfont); |
| | tuneBuilder.setRunningFont("gchordfont", multilineVars.gchordfont); |
| | tuneBuilder.setRunningFont("tripletfont", multilineVars.tripletfont); |
| | tuneBuilder.setRunningFont("vocalfont", multilineVars.vocalfont); |
| | } |
| | line = tokenizer.nextLine(); |
| | } |
| |
|
| | if (wordsContinuation) { |
| | addWords(tuneBuilder.getCurrentVoice(), ''); |
| | } |
| | if (symbolContinuation) { |
| | addSymbols(tuneBuilder.getCurrentVoice(), ''); |
| | } |
| | multilineVars.openSlurs = tuneBuilder.cleanUp(multilineVars.barsperstaff, multilineVars.staffnonote, multilineVars.openSlurs); |
| |
|
| | } catch (err) { |
| | if (err !== "normal_abort") |
| | throw err; |
| | } |
| |
|
| | var ph = 11*72; |
| | var pl = 8.5*72; |
| | switch (multilineVars.papersize) { |
| | |
| | case "legal": ph = 14*72; pl = 8.5*72; break; |
| | case "A4": ph = 11.7*72; pl = 8.3*72; break; |
| | } |
| | if (multilineVars.landscape) { |
| | var x = ph; |
| | ph = pl; |
| | pl = x; |
| | } |
| | if (!tune.formatting.pagewidth) |
| | tune.formatting.pagewidth = pl; |
| | if (!tune.formatting.pageheight) |
| | tune.formatting.pageheight = ph; |
| |
|
| | if (switches.hint_measures) { |
| | addHintMeasures(); |
| | } |
| |
|
| | wrap.wrapLines(tune, multilineVars.lineBreaks, multilineVars.barNumbers); |
| | }; |
| | }; |
| |
|
| | module.exports = Parse; |
| |
|