|
|
var parseCommon = require('./abc_common'); |
|
|
|
|
|
var parseDirective = {}; |
|
|
|
|
|
(function() { |
|
|
"use strict"; |
|
|
var tokenizer; |
|
|
var warn; |
|
|
var multilineVars; |
|
|
var tune; |
|
|
var tuneBuilder; |
|
|
parseDirective.initialize = function(tokenizer_, warn_, multilineVars_, tune_, tuneBuilder_) { |
|
|
tokenizer = tokenizer_; |
|
|
warn = warn_; |
|
|
multilineVars = multilineVars_; |
|
|
tune = tune_; |
|
|
tuneBuilder = tuneBuilder_; |
|
|
initializeFonts(); |
|
|
}; |
|
|
|
|
|
function initializeFonts() { |
|
|
multilineVars.annotationfont = { face: "Helvetica", size: 12, weight: "normal", style: "normal", decoration: "none" }; |
|
|
multilineVars.gchordfont = { face: "Helvetica", size: 12, weight: "normal", style: "normal", decoration: "none" }; |
|
|
multilineVars.historyfont = { face: "\"Times New Roman\"", size: 16, weight: "normal", style: "normal", decoration: "none" }; |
|
|
multilineVars.infofont = { face: "\"Times New Roman\"", size: 14, weight: "normal", style: "italic", decoration: "none" }; |
|
|
multilineVars.measurefont = { face: "\"Times New Roman\"", size: 14, weight: "normal", style: "italic", decoration: "none" }; |
|
|
multilineVars.partsfont = { face: "\"Times New Roman\"", size: 15, weight: "normal", style: "normal", decoration: "none" }; |
|
|
multilineVars.repeatfont = { face: "\"Times New Roman\"", size: 13, weight: "normal", style: "normal", decoration: "none" }; |
|
|
multilineVars.textfont = { face: "\"Times New Roman\"", size: 16, weight: "normal", style: "normal", decoration: "none" }; |
|
|
multilineVars.tripletfont = {face: "Times", size: 11, weight: "normal", style: "italic", decoration: "none"}; |
|
|
multilineVars.vocalfont = { face: "\"Times New Roman\"", size: 13, weight: "bold", style: "normal", decoration: "none" }; |
|
|
multilineVars.wordsfont = { face: "\"Times New Roman\"", size: 16, weight: "normal", style: "normal", decoration: "none" }; |
|
|
|
|
|
|
|
|
tune.formatting.composerfont = { face: "\"Times New Roman\"", size: 14, weight: "normal", style: "italic", decoration: "none" }; |
|
|
tune.formatting.subtitlefont = { face: "\"Times New Roman\"", size: 16, weight: "normal", style: "normal", decoration: "none" }; |
|
|
tune.formatting.tempofont = { face: "\"Times New Roman\"", size: 15, weight: "bold", style: "normal", decoration: "none" }; |
|
|
tune.formatting.titlefont = { face: "\"Times New Roman\"", size: 20, weight: "normal", style: "normal", decoration: "none" }; |
|
|
tune.formatting.footerfont = { face: "\"Times New Roman\"", size: 12, weight: "normal", style: "normal", decoration: "none" }; |
|
|
tune.formatting.headerfont = { face: "\"Times New Roman\"", size: 12, weight: "normal", style: "normal", decoration: "none" }; |
|
|
tune.formatting.voicefont = { face: "\"Times New Roman\"", size: 13, weight: "bold", style: "normal", decoration: "none" }; |
|
|
tune.formatting.tablabelfont = { face: "\"Trebuchet MS\"", size: 16, weight: "normal", style: "normal", decoration: "none" }; |
|
|
tune.formatting.tabnumberfont = { face: "\"Arial\"", size: 11, weight: "normal", style: "normal", decoration: "none" }; |
|
|
tune.formatting.tabgracefont = { face: "\"Arial\"", size: 8, weight: "normal", style: "normal", decoration: "none" }; |
|
|
|
|
|
|
|
|
tune.formatting.annotationfont = multilineVars.annotationfont; |
|
|
tune.formatting.gchordfont = multilineVars.gchordfont; |
|
|
tune.formatting.historyfont = multilineVars.historyfont; |
|
|
tune.formatting.infofont = multilineVars.infofont; |
|
|
tune.formatting.measurefont = multilineVars.measurefont; |
|
|
tune.formatting.partsfont = multilineVars.partsfont; |
|
|
tune.formatting.repeatfont = multilineVars.repeatfont; |
|
|
tune.formatting.textfont = multilineVars.textfont; |
|
|
tune.formatting.tripletfont = multilineVars.tripletfont; |
|
|
tune.formatting.vocalfont = multilineVars.vocalfont; |
|
|
tune.formatting.wordsfont = multilineVars.wordsfont; |
|
|
} |
|
|
|
|
|
var fontTypeCanHaveBox = { gchordfont: true, measurefont: true, partsfont: true, annotationfont: true, composerfont: true, historyfont: true, infofont: true, subtitlefont: true, textfont: true, titlefont: true, voicefont: true }; |
|
|
|
|
|
var fontTranslation = function(fontFace) { |
|
|
|
|
|
|
|
|
|
|
|
switch (fontFace) { |
|
|
case "Arial-Italic": |
|
|
return { face: "Arial", weight: "normal", style: "italic", decoration: "none" }; |
|
|
case "Arial-Bold": |
|
|
return { face: "Arial", weight: "bold", style: "normal", decoration: "none" }; |
|
|
case "Bookman-Demi": |
|
|
return { face: "Bookman,serif", weight: "bold", style: "normal", decoration: "none" }; |
|
|
case "Bookman-DemiItalic": |
|
|
return { face: "Bookman,serif", weight: "bold", style: "italic", decoration: "none" }; |
|
|
case "Bookman-Light": |
|
|
return { face: "Bookman,serif", weight: "normal", style: "normal", decoration: "none" }; |
|
|
case "Bookman-LightItalic": |
|
|
return { face: "Bookman,serif", weight: "normal", style: "italic", decoration: "none" }; |
|
|
case "Courier": |
|
|
return { face: "\"Courier New\"", weight: "normal", style: "normal", decoration: "none" }; |
|
|
case "Courier-Oblique": |
|
|
return { face: "\"Courier New\"", weight: "normal", style: "italic", decoration: "none" }; |
|
|
case "Courier-Bold": |
|
|
return { face: "\"Courier New\"", weight: "bold", style: "normal", decoration: "none" }; |
|
|
case "Courier-BoldOblique": |
|
|
return { face: "\"Courier New\"", weight: "bold", style: "italic", decoration: "none" }; |
|
|
case "AvantGarde-Book": |
|
|
return { face: "AvantGarde,Arial", weight: "normal", style: "normal", decoration: "none" }; |
|
|
case "AvantGarde-BookOblique": |
|
|
return { face: "AvantGarde,Arial", weight: "normal", style: "italic", decoration: "none" }; |
|
|
case "AvantGarde-Demi": |
|
|
case "Avant-Garde-Demi": |
|
|
return { face: "AvantGarde,Arial", weight: "bold", style: "normal", decoration: "none" }; |
|
|
case "AvantGarde-DemiOblique": |
|
|
return { face: "AvantGarde,Arial", weight: "bold", style: "italic", decoration: "none" }; |
|
|
case "Helvetica-Oblique": |
|
|
return { face: "Helvetica", weight: "normal", style: "italic", decoration: "none" }; |
|
|
case "Helvetica-Bold": |
|
|
return { face: "Helvetica", weight: "bold", style: "normal", decoration: "none" }; |
|
|
case "Helvetica-BoldOblique": |
|
|
return { face: "Helvetica", weight: "bold", style: "italic", decoration: "none" }; |
|
|
case "Helvetica-Narrow": |
|
|
return { face: "\"Helvetica Narrow\",Helvetica", weight: "normal", style: "normal", decoration: "none" }; |
|
|
case "Helvetica-Narrow-Oblique": |
|
|
return { face: "\"Helvetica Narrow\",Helvetica", weight: "normal", style: "italic", decoration: "none" }; |
|
|
case "Helvetica-Narrow-Bold": |
|
|
return { face: "\"Helvetica Narrow\",Helvetica", weight: "bold", style: "normal", decoration: "none" }; |
|
|
case "Helvetica-Narrow-BoldOblique": |
|
|
return { face: "\"Helvetica Narrow\",Helvetica", weight: "bold", style: "italic", decoration: "none" }; |
|
|
case "Palatino-Roman": |
|
|
return { face: "Palatino", weight: "normal", style: "normal", decoration: "none" }; |
|
|
case "Palatino-Italic": |
|
|
return { face: "Palatino", weight: "normal", style: "italic", decoration: "none" }; |
|
|
case "Palatino-Bold": |
|
|
return { face: "Palatino", weight: "bold", style: "normal", decoration: "none" }; |
|
|
case "Palatino-BoldItalic": |
|
|
return { face: "Palatino", weight: "bold", style: "italic", decoration: "none" }; |
|
|
case "NewCenturySchlbk-Roman": |
|
|
return { face: "\"New Century\",serif", weight: "normal", style: "normal", decoration: "none" }; |
|
|
case "NewCenturySchlbk-Italic": |
|
|
return { face: "\"New Century\",serif", weight: "normal", style: "italic", decoration: "none" }; |
|
|
case "NewCenturySchlbk-Bold": |
|
|
return { face: "\"New Century\",serif", weight: "bold", style: "normal", decoration: "none" }; |
|
|
case "NewCenturySchlbk-BoldItalic": |
|
|
return { face: "\"New Century\",serif", weight: "bold", style: "italic", decoration: "none" }; |
|
|
case "Times": |
|
|
case "Times-Roman": |
|
|
case "Times-Narrow": |
|
|
case "Times-Courier": |
|
|
case "Times-New-Roman": |
|
|
return { face: "\"Times New Roman\"", weight: "normal", style: "normal", decoration: "none" }; |
|
|
case "Times-Italic": |
|
|
case "Times-Italics": |
|
|
return { face: "\"Times New Roman\"", weight: "normal", style: "italic", decoration: "none" }; |
|
|
case "Times-Bold": |
|
|
return { face: "\"Times New Roman\"", weight: "bold", style: "normal", decoration: "none" }; |
|
|
case "Times-BoldItalic": |
|
|
return { face: "\"Times New Roman\"", weight: "bold", style: "italic", decoration: "none" }; |
|
|
case "ZapfChancery-MediumItalic": |
|
|
return { face: "\"Zapf Chancery\",cursive,serif", weight: "normal", style: "normal", decoration: "none" }; |
|
|
default: |
|
|
return null; |
|
|
} |
|
|
}; |
|
|
|
|
|
var getFontParameter = function(tokens, currentSetting, str, position, cmd) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function processNumberOnly() { |
|
|
var size = parseInt(tokens[0].token); |
|
|
tokens.shift(); |
|
|
if (!currentSetting) { |
|
|
warn("Can't set just the size of the font since there is no default value.", str, position); |
|
|
return { face: "\"Times New Roman\"", weight: "normal", style: "normal", decoration: "none", size: size}; |
|
|
} |
|
|
if (tokens.length === 0) { |
|
|
return { face: currentSetting.face, weight: currentSetting.weight, style: currentSetting.style, decoration: currentSetting.decoration, size: size}; |
|
|
} |
|
|
if (tokens.length === 1 && tokens[0].token === "box" && fontTypeCanHaveBox[cmd]) |
|
|
return { face: currentSetting.face, weight: currentSetting.weight, style: currentSetting.style, decoration: currentSetting.decoration, size: size, box: true}; |
|
|
warn("Extra parameters in font definition.", str, position); |
|
|
return { face: currentSetting.face, weight: currentSetting.weight, style: currentSetting.style, decoration: currentSetting.decoration, size: size}; |
|
|
} |
|
|
|
|
|
|
|
|
if (tokens[0].token === '*') { |
|
|
tokens.shift(); |
|
|
if (tokens[0].type === 'number') |
|
|
return processNumberOnly(); |
|
|
else { |
|
|
warn("Expected font size number after *.", str, position); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (tokens[0].type === 'number') { |
|
|
return processNumberOnly(); |
|
|
} |
|
|
|
|
|
|
|
|
var face = []; |
|
|
var size; |
|
|
var weight = "normal"; |
|
|
var style = "normal"; |
|
|
var decoration = "none"; |
|
|
var box = false; |
|
|
var state = 'face'; |
|
|
var hyphenLast = false; |
|
|
while (tokens.length) { |
|
|
var currToken = tokens.shift(); |
|
|
var word = currToken.token.toLowerCase(); |
|
|
switch (state) { |
|
|
case 'face': |
|
|
if (hyphenLast || (word !== 'utf' && currToken.type !== 'number' && word !== "bold" && word !== "italic" && word !== "underline" && word !== "box")) { |
|
|
if (face.length > 0 && currToken.token === '-') { |
|
|
hyphenLast = true; |
|
|
face[face.length-1] = face[face.length-1] + currToken.token; |
|
|
} |
|
|
else { |
|
|
if (hyphenLast) { |
|
|
hyphenLast = false; |
|
|
face[face.length-1] = face[face.length-1] + currToken.token; |
|
|
} else |
|
|
face.push(currToken.token); |
|
|
} |
|
|
} else { |
|
|
if (currToken.type === 'number') { |
|
|
if (size) { |
|
|
warn("Font size specified twice in font definition.", str, position); |
|
|
} else { |
|
|
size = currToken.token; |
|
|
} |
|
|
state = 'modifier'; |
|
|
} else if (word === "bold") |
|
|
weight = "bold"; |
|
|
else if (word === "italic") |
|
|
style = "italic"; |
|
|
else if (word === "underline") |
|
|
decoration = "underline"; |
|
|
else if (word === "box") { |
|
|
if (fontTypeCanHaveBox[cmd]) |
|
|
box = true; |
|
|
else |
|
|
warn("This font style doesn't support \"box\"", str, position); |
|
|
state = "finished"; |
|
|
} else if (word === "utf") { |
|
|
currToken = tokens.shift(); |
|
|
state = "size"; |
|
|
} else |
|
|
warn("Unknown parameter " + currToken.token + " in font definition.", str, position); |
|
|
} |
|
|
break; |
|
|
case "size": |
|
|
if (currToken.type === 'number') { |
|
|
if (size) { |
|
|
warn("Font size specified twice in font definition.", str, position); |
|
|
} else { |
|
|
size = currToken.token; |
|
|
} |
|
|
} else { |
|
|
warn("Expected font size in font definition.", str, position); |
|
|
} |
|
|
state = 'modifier'; |
|
|
break; |
|
|
case "modifier": |
|
|
if (word === "bold") |
|
|
weight = "bold"; |
|
|
else if (word === "italic") |
|
|
style = "italic"; |
|
|
else if (word === "underline") |
|
|
decoration = "underline"; |
|
|
else if (word === "box") { |
|
|
if (fontTypeCanHaveBox[cmd]) |
|
|
box = true; |
|
|
else |
|
|
warn("This font style doesn't support \"box\"", str, position); |
|
|
state = "finished"; |
|
|
} else |
|
|
warn("Unknown parameter " + currToken.token + " in font definition.", str, position); |
|
|
break; |
|
|
case "finished": |
|
|
warn("Extra characters found after \"box\" in font definition.", str, position); |
|
|
break; |
|
|
} |
|
|
} |
|
|
|
|
|
if (size === undefined) { |
|
|
if (!currentSetting) { |
|
|
warn("Must specify the size of the font since there is no default value.", str, position); |
|
|
size = 12; |
|
|
} else |
|
|
size = currentSetting.size; |
|
|
} else |
|
|
size = parseFloat(size); |
|
|
|
|
|
face = face.join(' '); |
|
|
if (face === '') { |
|
|
if (!currentSetting) { |
|
|
warn("Must specify the name of the font since there is no default value.", str, position); |
|
|
face = "sans-serif"; |
|
|
} else |
|
|
face = currentSetting.face; |
|
|
} |
|
|
var psFont = fontTranslation(face); |
|
|
var font = {}; |
|
|
if (psFont) { |
|
|
font.face = psFont.face; |
|
|
font.weight = psFont.weight; |
|
|
font.style = psFont.style; |
|
|
font.decoration = psFont.decoration; |
|
|
font.size = size; |
|
|
if (box) |
|
|
font.box = true; |
|
|
return font; |
|
|
} |
|
|
font.face = face; |
|
|
font.weight = weight; |
|
|
font.style = style; |
|
|
font.decoration = decoration; |
|
|
font.size = size; |
|
|
if (box) |
|
|
font.box = true; |
|
|
return font; |
|
|
}; |
|
|
|
|
|
var getChangingFont = function(cmd, tokens, str) { |
|
|
if (tokens.length === 0) |
|
|
return "Directive \"" + cmd + "\" requires a font as a parameter."; |
|
|
multilineVars[cmd] = getFontParameter(tokens, multilineVars[cmd], str, 0, cmd); |
|
|
if (multilineVars.is_in_header) |
|
|
tune.formatting[cmd] = multilineVars[cmd]; |
|
|
return null; |
|
|
}; |
|
|
var getGlobalFont = function(cmd, tokens, str) { |
|
|
if (tokens.length === 0) |
|
|
return "Directive \"" + cmd + "\" requires a font as a parameter."; |
|
|
tune.formatting[cmd] = getFontParameter(tokens, tune.formatting[cmd], str, 0, cmd); |
|
|
return null; |
|
|
}; |
|
|
|
|
|
var setScale = function(cmd, tokens) { |
|
|
var scratch = ""; |
|
|
tokens.forEach(function(tok) { |
|
|
scratch += tok.token; |
|
|
}); |
|
|
var num = parseFloat(scratch); |
|
|
if (isNaN(num) || num === 0) |
|
|
return "Directive \"" + cmd + "\" requires a number as a parameter."; |
|
|
tune.formatting.scale = num; |
|
|
|
|
|
}; |
|
|
|
|
|
var drumNames = [ |
|
|
"acoustic-bass-drum", |
|
|
"bass-drum-1", |
|
|
"side-stick", |
|
|
"acoustic-snare", |
|
|
"hand-clap", |
|
|
"electric-snare", |
|
|
"low-floor-tom", |
|
|
"closed-hi-hat", |
|
|
"high-floor-tom", |
|
|
"pedal-hi-hat", |
|
|
"low-tom", |
|
|
"open-hi-hat", |
|
|
"low-mid-tom", |
|
|
"hi-mid-tom", |
|
|
"crash-cymbal-1", |
|
|
"high-tom", |
|
|
"ride-cymbal-1", |
|
|
"chinese-cymbal", |
|
|
"ride-bell", |
|
|
"tambourine", |
|
|
"splash-cymbal", |
|
|
"cowbell", |
|
|
"crash-cymbal-2", |
|
|
"vibraslap", |
|
|
"ride-cymbal-2", |
|
|
"hi-bongo", |
|
|
"low-bongo", |
|
|
"mute-hi-conga", |
|
|
"open-hi-conga", |
|
|
"low-conga", |
|
|
"high-timbale", |
|
|
"low-timbale", |
|
|
"high-agogo", |
|
|
"low-agogo", |
|
|
"cabasa", |
|
|
"maracas", |
|
|
"short-whistle", |
|
|
"long-whistle", |
|
|
"short-guiro", |
|
|
"long-guiro", |
|
|
"claves", |
|
|
"hi-wood-block", |
|
|
"low-wood-block", |
|
|
"mute-cuica", |
|
|
"open-cuica", |
|
|
"mute-triangle", |
|
|
"open-triangle", |
|
|
]; |
|
|
|
|
|
var interpretPercMap = function(restOfString) { |
|
|
var tokens = restOfString.split(/\s+/); |
|
|
if (tokens.length !== 2 && tokens.length !== 3) |
|
|
return { error: 'Expected parameters "abc-note", "drum-sound", and optionally "note-head"'}; |
|
|
var key = tokens[0]; |
|
|
|
|
|
var pitch = parseInt(tokens[1], 10); |
|
|
if ((isNaN(pitch) || pitch < 35 || pitch > 81) && tokens[1]) { |
|
|
pitch = drumNames.indexOf(tokens[1].toLowerCase()) + 35; |
|
|
} |
|
|
if ((isNaN(pitch) || pitch < 35 || pitch > 81)) |
|
|
return { error: 'Expected drum name, received "' + tokens[1] + '"' }; |
|
|
var value = { sound: pitch }; |
|
|
if (tokens.length === 3) |
|
|
value.noteHead = tokens[2]; |
|
|
return { key: key, value: value }; |
|
|
}; |
|
|
|
|
|
var getRequiredMeasurement = function(cmd, tokens) { |
|
|
var points = tokenizer.getMeasurement(tokens); |
|
|
if (points.used === 0 || tokens.length !== 0) |
|
|
return { error: "Directive \"" + cmd + "\" requires a measurement as a parameter."}; |
|
|
return points.value; |
|
|
}; |
|
|
var oneParameterMeasurement = function(cmd, tokens) { |
|
|
var points = tokenizer.getMeasurement(tokens); |
|
|
if (points.used === 0 || tokens.length !== 0) |
|
|
return "Directive \"" + cmd + "\" requires a measurement as a parameter."; |
|
|
tune.formatting[cmd] = points.value; |
|
|
return null; |
|
|
}; |
|
|
|
|
|
var addMultilineVar = function(key, cmd, tokens, min, max) { |
|
|
if (tokens.length !== 1 || tokens[0].type !== 'number') |
|
|
return "Directive \"" + cmd + "\" requires a number as a parameter."; |
|
|
var i = tokens[0].intt; |
|
|
if (min !== undefined && i < min) |
|
|
return "Directive \"" + cmd + "\" requires a number greater than or equal to " + min + " as a parameter."; |
|
|
if (max !== undefined && i > max) |
|
|
return "Directive \"" + cmd + "\" requires a number less than or equal to " + max + " as a parameter."; |
|
|
multilineVars[key] = i; |
|
|
return null; |
|
|
}; |
|
|
|
|
|
var addMultilineVarBool = function(key, cmd, tokens) { |
|
|
if (tokens.length === 1 && (tokens[0].token === 'true' || tokens[0].token === 'false')) { |
|
|
multilineVars[key] = tokens[0].token === 'true'; |
|
|
return null; |
|
|
} |
|
|
var str = addMultilineVar(key, cmd, tokens, 0, 1); |
|
|
if (str !== null) return str; |
|
|
multilineVars[key] = (multilineVars[key] === 1); |
|
|
return null; |
|
|
}; |
|
|
|
|
|
var addMultilineVarOneParamChoice = function(key, cmd, tokens, choices) { |
|
|
if (tokens.length !== 1) |
|
|
return "Directive \"" + cmd + "\" requires one of [ " + choices.join(", ") + " ] as a parameter."; |
|
|
var choice = tokens[0].token; |
|
|
var found = false; |
|
|
for (var i = 0; !found && i < choices.length; i++) { |
|
|
if (choices[i] === choice) |
|
|
found = true; |
|
|
} |
|
|
if (!found) |
|
|
return "Directive \"" + cmd + "\" requires one of [ " + choices.join(", ") + " ] as a parameter."; |
|
|
multilineVars[key] = choice; |
|
|
return null; |
|
|
}; |
|
|
|
|
|
var midiCmdParam0 = [ |
|
|
"nobarlines", |
|
|
"barlines", |
|
|
"beataccents", |
|
|
"nobeataccents", |
|
|
"droneon", |
|
|
"droneoff", |
|
|
"drumon", |
|
|
"drumoff", |
|
|
"fermatafixed", |
|
|
"fermataproportional", |
|
|
"gchordon", |
|
|
"gchordoff", |
|
|
"controlcombo", |
|
|
"temperamentnormal", |
|
|
"noportamento" |
|
|
]; |
|
|
var midiCmdParam1String = [ |
|
|
"gchord", |
|
|
"ptstress", |
|
|
"beatstring" |
|
|
]; |
|
|
var midiCmdParam1Integer = [ |
|
|
"bassvol", |
|
|
"chordvol", |
|
|
"c", |
|
|
"channel", |
|
|
"beatmod", |
|
|
"deltaloudness", |
|
|
"drumbars", |
|
|
"gracedivider", |
|
|
"makechordchannels", |
|
|
"randomchordattack", |
|
|
"chordattack", |
|
|
"stressmodel", |
|
|
"transpose", |
|
|
"rtranspose", |
|
|
"vol", |
|
|
"volinc", |
|
|
"gchordbars" |
|
|
]; |
|
|
var midiCmdParam1Integer1OptionalInteger = [ |
|
|
"program" |
|
|
]; |
|
|
var midiCmdParam2Integer = [ |
|
|
"ratio", |
|
|
"snt", |
|
|
"bendvelocity", |
|
|
"pitchbend", |
|
|
"control", |
|
|
"temperamentlinear" |
|
|
]; |
|
|
var midiCmdParam4Integer = [ |
|
|
"beat" |
|
|
]; |
|
|
var midiCmdParam5Integer = [ |
|
|
"drone" |
|
|
]; |
|
|
var midiCmdParam1String1Integer = [ |
|
|
"portamento" |
|
|
]; |
|
|
var midiCmdParamFraction = [ |
|
|
"expand", |
|
|
"grace", |
|
|
"trim" |
|
|
]; |
|
|
var midiCmdParam1StringVariableIntegers = [ |
|
|
"drum", |
|
|
"chordname" |
|
|
]; |
|
|
var midiCmdParam1Integer1OptionalString = [ |
|
|
"bassprog", "chordprog" |
|
|
]; |
|
|
|
|
|
|
|
|
var parseMidiCommand = function(midi, tune, restOfString) { |
|
|
var midi_cmd = midi.shift().token; |
|
|
var midi_params = []; |
|
|
if (midiCmdParam0.indexOf(midi_cmd) >= 0) { |
|
|
|
|
|
if (midi.length !== 0) |
|
|
warn("Unexpected parameter in MIDI " + midi_cmd, restOfString, 0); |
|
|
} else if (midiCmdParam1String.indexOf(midi_cmd) >= 0) { |
|
|
|
|
|
if (midi.length !== 1) |
|
|
warn("Expected one parameter in MIDI " + midi_cmd, restOfString, 0); |
|
|
else |
|
|
midi_params.push(midi[0].token); |
|
|
} else if (midiCmdParam1Integer.indexOf(midi_cmd) >= 0) { |
|
|
|
|
|
if (midi.length !== 1) |
|
|
warn("Expected one parameter in MIDI " + midi_cmd, restOfString, 0); |
|
|
else if (midi[0].type !== "number") |
|
|
warn("Expected one integer parameter in MIDI " + midi_cmd, restOfString, 0); |
|
|
else |
|
|
midi_params.push(midi[0].intt); |
|
|
} else if (midiCmdParam1Integer1OptionalInteger.indexOf(midi_cmd) >= 0) { |
|
|
|
|
|
if (midi.length !== 1 && midi.length !== 2) |
|
|
warn("Expected one or two parameters in MIDI " + midi_cmd, restOfString, 0); |
|
|
else if (midi[0].type !== "number") |
|
|
warn("Expected integer parameter in MIDI " + midi_cmd, restOfString, 0); |
|
|
else if (midi.length === 2 && midi[1].type !== "number") |
|
|
warn("Expected integer parameter in MIDI " + midi_cmd, restOfString, 0); |
|
|
else { |
|
|
midi_params.push(midi[0].intt); |
|
|
if (midi.length === 2) |
|
|
midi_params.push(midi[1].intt); |
|
|
} |
|
|
} else if (midiCmdParam2Integer.indexOf(midi_cmd) >= 0) { |
|
|
|
|
|
if (midi.length !== 2) |
|
|
warn("Expected two parameters in MIDI " + midi_cmd, restOfString, 0); |
|
|
else if (midi[0].type !== "number" || midi[1].type !== "number") |
|
|
warn("Expected two integer parameters in MIDI " + midi_cmd, restOfString, 0); |
|
|
else { |
|
|
midi_params.push(midi[0].intt); |
|
|
midi_params.push(midi[1].intt); |
|
|
} |
|
|
} else if (midiCmdParam1String1Integer.indexOf(midi_cmd) >= 0) { |
|
|
|
|
|
if (midi.length !== 2) |
|
|
warn("Expected two parameters in MIDI " + midi_cmd, restOfString, 0); |
|
|
else if (midi[0].type !== "alpha" || midi[1].type !== "number") |
|
|
warn("Expected one string and one integer parameters in MIDI " + midi_cmd, restOfString, 0); |
|
|
else { |
|
|
midi_params.push(midi[0].token); |
|
|
midi_params.push(midi[1].intt); |
|
|
} |
|
|
} else if (midi_cmd === 'drummap') { |
|
|
|
|
|
if (midi.length === 2 && midi[0].type === 'alpha' && midi[1].type === 'number') { |
|
|
if (!tune.formatting) tune.formatting = {}; |
|
|
if (!tune.formatting.midi) tune.formatting.midi = {}; |
|
|
if (!tune.formatting.midi.drummap) tune.formatting.midi.drummap = {}; |
|
|
tune.formatting.midi.drummap[midi[0].token] = midi[1].intt; |
|
|
midi_params = tune.formatting.midi.drummap; |
|
|
} else if (midi.length === 3 && midi[0].type === 'punct' && midi[1].type === 'alpha' && midi[2].type === 'number') { |
|
|
if (!tune.formatting) tune.formatting = {}; |
|
|
if (!tune.formatting.midi) tune.formatting.midi = {}; |
|
|
if (!tune.formatting.midi.drummap) tune.formatting.midi.drummap = {}; |
|
|
tune.formatting.midi.drummap[midi[0].token+midi[1].token] = midi[2].intt; |
|
|
midi_params = tune.formatting.midi.drummap; |
|
|
} else { |
|
|
warn("Expected one note name and one integer parameter in MIDI " + midi_cmd, restOfString, 0); |
|
|
} |
|
|
} else if (midiCmdParamFraction.indexOf(midi_cmd) >= 0) { |
|
|
|
|
|
if (midi.length !== 3) |
|
|
warn("Expected fraction parameter in MIDI " + midi_cmd, restOfString, 0); |
|
|
else if (midi[0].type !== "number" || midi[1].token !== "/" || midi[2].type !== "number") |
|
|
warn("Expected fraction parameter in MIDI " + midi_cmd, restOfString, 0); |
|
|
else { |
|
|
midi_params.push(midi[0].intt); |
|
|
midi_params.push(midi[2].intt); |
|
|
} |
|
|
} else if (midiCmdParam4Integer.indexOf(midi_cmd) >= 0) { |
|
|
|
|
|
if (midi.length !== 4) |
|
|
warn("Expected four parameters in MIDI " + midi_cmd, restOfString, 0); |
|
|
else if (midi[0].type !== "number" || midi[1].type !== "number" || midi[2].type !== "number" || midi[3].type !== "number") |
|
|
warn("Expected four integer parameters in MIDI " + midi_cmd, restOfString, 0); |
|
|
else { |
|
|
midi_params.push(midi[0].intt); |
|
|
midi_params.push(midi[1].intt); |
|
|
midi_params.push(midi[2].intt); |
|
|
midi_params.push(midi[3].intt); |
|
|
} |
|
|
} else if (midiCmdParam5Integer.indexOf(midi_cmd) >= 0) { |
|
|
|
|
|
if (midi.length !== 5) |
|
|
warn("Expected five parameters in MIDI " + midi_cmd, restOfString, 0); |
|
|
else if (midi[0].type !== "number" || midi[1].type !== "number" || midi[2].type !== "number" || midi[3].type !== "number" || midi[4].type !== "number") |
|
|
warn("Expected five integer parameters in MIDI " + midi_cmd, restOfString, 0); |
|
|
else { |
|
|
midi_params.push(midi[0].intt); |
|
|
midi_params.push(midi[1].intt); |
|
|
midi_params.push(midi[2].intt); |
|
|
midi_params.push(midi[3].intt); |
|
|
midi_params.push(midi[4].intt); |
|
|
} |
|
|
} else if (midiCmdParam1Integer1OptionalInteger.indexOf(midi_cmd) >= 0) { |
|
|
|
|
|
if (midi.length !== 1 || midi.length !== 4) |
|
|
warn("Expected one or two parameters in MIDI " + midi_cmd, restOfString, 0); |
|
|
else if (midi[0].type !== "number") |
|
|
warn("Expected integer parameter in MIDI " + midi_cmd, restOfString, 0); |
|
|
else if (midi.length === 4) { |
|
|
if (midi[1].token !== "octave") |
|
|
warn("Expected octave parameter in MIDI " + midi_cmd, restOfString, 0); |
|
|
if (midi[2].token !== "=") |
|
|
warn("Expected octave parameter in MIDI " + midi_cmd, restOfString, 0); |
|
|
if (midi[3].type !== "number") |
|
|
warn("Expected integer parameter for octave in MIDI " + midi_cmd, restOfString, 0); |
|
|
} else { |
|
|
midi_params.push(midi[0].intt); |
|
|
if (midi.length === 4) |
|
|
midi_params.push(midi[3].intt); |
|
|
} |
|
|
} else if (midiCmdParam1StringVariableIntegers.indexOf(midi_cmd) >= 0) { |
|
|
|
|
|
if (midi.length < 2) |
|
|
warn("Expected string parameter and at least one integer parameter in MIDI " + midi_cmd, restOfString, 0); |
|
|
else if (midi[0].type !== "alpha") |
|
|
warn("Expected string parameter and at least one integer parameter in MIDI " + midi_cmd, restOfString, 0); |
|
|
else { |
|
|
var p = midi.shift(); |
|
|
midi_params.push(p.token); |
|
|
while (midi.length > 0) { |
|
|
p = midi.shift(); |
|
|
if (p.type !== "number") |
|
|
warn("Expected integer parameter in MIDI " + midi_cmd, restOfString, 0); |
|
|
midi_params.push(p.intt); |
|
|
} |
|
|
} |
|
|
} |
|
|
else if (midiCmdParam1Integer1OptionalString.indexOf(midi_cmd) >= 0){ |
|
|
|
|
|
|
|
|
if (midi.length !== 1 && midi.length !== 2) |
|
|
warn("Expected one or two parameters in MIDI " + midi_cmd, restOfString, 0); |
|
|
else if (midi[0].type !== "number") |
|
|
warn("Expected integer parameter in MIDI " + midi_cmd, restOfString, 0); |
|
|
else if (midi.length === 2 && midi[1].type !== "alpha") |
|
|
warn("Expected alpha parameter in MIDI " + midi_cmd, restOfString, 0); |
|
|
else { |
|
|
midi_params.push(midi[0].intt); |
|
|
|
|
|
|
|
|
if (midi.length === 2){ |
|
|
var cmd = midi[1].token; |
|
|
if (cmd.indexOf("octave=") != -1){ |
|
|
cmd = cmd.replace("octave=",""); |
|
|
cmd = parseInt(cmd); |
|
|
if (!isNaN(cmd)){ |
|
|
|
|
|
if (cmd < -1){ |
|
|
warn("Expected octave= in MIDI " + midi_cmd + ' to be >= -1 (recv:'+cmd+')'); |
|
|
cmd = -1; |
|
|
} |
|
|
if (cmd > 3){ |
|
|
warn("Expected octave= in MIDI " + midi_cmd + ' to be <= 3 (recv:'+cmd+')'); |
|
|
cmd = 3; |
|
|
} |
|
|
midi_params.push(cmd); |
|
|
} else |
|
|
warn("Expected octave value in MIDI" + midi_cmd); |
|
|
} |
|
|
else{ |
|
|
warn("Expected octave= in MIDI" + midi_cmd); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
if (tuneBuilder.hasBeginMusic()) |
|
|
tuneBuilder.appendElement('midi', -1, -1, { cmd: midi_cmd, params: midi_params }); |
|
|
else { |
|
|
if (tune.formatting['midi'] === undefined) |
|
|
tune.formatting['midi'] = {}; |
|
|
tune.formatting['midi'][midi_cmd] = midi_params; |
|
|
} |
|
|
}; |
|
|
|
|
|
parseDirective.parseFontChangeLine = function(textstr) { |
|
|
|
|
|
textstr = textstr.replace(/\$\$/g,"\x03") |
|
|
var textParts = textstr.split('$'); |
|
|
if (textParts.length > 1 && multilineVars.setfont) { |
|
|
var textarr = [ ]; |
|
|
if (textParts[0] !== '') |
|
|
textarr.push({ text: textParts[0] }) |
|
|
for (var i = 1; i < textParts.length; i++) { |
|
|
if (textParts[i][0] === '0') |
|
|
textarr.push({ text: textParts[i].substring(1).replace(/\x03/g,"$$") }); |
|
|
else { |
|
|
var whichFont = parseInt(textParts[i][0],10) |
|
|
if (multilineVars.setfont[whichFont]) |
|
|
textarr.push({font: multilineVars.setfont[whichFont], text: textParts[i].substring(1).replace(/\x03/g,"$$") }); |
|
|
else |
|
|
textarr[textarr.length-1].text += '$' + textParts[i].replace(/\x03/g,"$$"); |
|
|
} |
|
|
} |
|
|
return textarr; |
|
|
} |
|
|
return textstr.replace(/\x03/g,"$$"); |
|
|
}; |
|
|
|
|
|
var positionChoices = [ 'auto', 'above', 'below', 'hidden' ]; |
|
|
parseDirective.addDirective = function(str) { |
|
|
var tokens = tokenizer.tokenize(str, 0, str.length); |
|
|
if (tokens.length === 0 || tokens[0].type !== 'alpha') return null; |
|
|
var restOfString = str.substring(str.indexOf(tokens[0].token)+tokens[0].token.length); |
|
|
restOfString = tokenizer.stripComment(restOfString); |
|
|
var cmd = tokens.shift().token.toLowerCase(); |
|
|
var scratch = ""; |
|
|
var line; |
|
|
switch (cmd) |
|
|
{ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
case "bagpipes":tune.formatting.bagpipes = true;break; |
|
|
case "flatbeams":tune.formatting.flatbeams = true;break; |
|
|
case "jazzchords":tune.formatting.jazzchords = true;break; |
|
|
case "accentAbove":tune.formatting.accentAbove = true;break; |
|
|
case "germanAlphabet":tune.formatting.germanAlphabet = true;break; |
|
|
case "landscape":multilineVars.landscape = true;break; |
|
|
case "papersize":multilineVars.papersize = restOfString;break; |
|
|
case "graceslurs": |
|
|
if (tokens.length !== 1) |
|
|
return "Directive graceslurs requires one parameter: 0 or 1"; |
|
|
if (tokens[0].token === '0' || tokens[0].token === 'false') |
|
|
tune.formatting.graceSlurs = false; |
|
|
else if (tokens[0].token === '1' || tokens[0].token === 'true') |
|
|
tune.formatting.graceSlurs = true; |
|
|
else |
|
|
return "Directive graceslurs requires one parameter: 0 or 1 (received " + tokens[0].token + ')'; |
|
|
break; |
|
|
case "lineThickness": |
|
|
var lt = parseStretchLast(tokens); |
|
|
if (lt.value !== undefined) |
|
|
tune.formatting.lineThickness = lt.value; |
|
|
if (lt.error) |
|
|
return lt.error; |
|
|
break; |
|
|
case "stretchlast": |
|
|
var sl = parseStretchLast(tokens); |
|
|
if (sl.value !== undefined) |
|
|
tune.formatting.stretchlast = sl.value; |
|
|
if (sl.error) |
|
|
return sl.error; |
|
|
break; |
|
|
case "titlecaps":multilineVars.titlecaps = true;break; |
|
|
case "titleleft":tune.formatting.titleleft = true;break; |
|
|
case "measurebox":tune.formatting.measurebox = true;break; |
|
|
|
|
|
case "vocal": return addMultilineVarOneParamChoice("vocalPosition", cmd, tokens, positionChoices); |
|
|
case "dynamic": return addMultilineVarOneParamChoice("dynamicPosition", cmd, tokens, positionChoices); |
|
|
case "gchord": return addMultilineVarOneParamChoice("chordPosition", cmd, tokens, positionChoices); |
|
|
case "ornament": return addMultilineVarOneParamChoice("ornamentPosition", cmd, tokens, positionChoices); |
|
|
case "volume": return addMultilineVarOneParamChoice("volumePosition", cmd, tokens, positionChoices); |
|
|
|
|
|
case "botmargin": |
|
|
case "botspace": |
|
|
case "composerspace": |
|
|
case "indent": |
|
|
case "leftmargin": |
|
|
case "linesep": |
|
|
case "musicspace": |
|
|
case "partsspace": |
|
|
case "pageheight": |
|
|
case "pagewidth": |
|
|
case "rightmargin": |
|
|
case "stafftopmargin": |
|
|
case "staffsep": |
|
|
case "staffwidth": |
|
|
case "subtitlespace": |
|
|
case "sysstaffsep": |
|
|
case "systemsep": |
|
|
case "textspace": |
|
|
case "titlespace": |
|
|
case "topmargin": |
|
|
case "topspace": |
|
|
case "vocalspace": |
|
|
case "wordsspace": |
|
|
return oneParameterMeasurement(cmd, tokens); |
|
|
case "voicescale": |
|
|
if (tokens.length !== 1 || tokens[0].type !== 'number') |
|
|
return "voicescale requires one float as a parameter"; |
|
|
var voiceScale = tokens.shift(); |
|
|
if (multilineVars.currentVoice) { |
|
|
multilineVars.currentVoice.scale = voiceScale.floatt; |
|
|
tuneBuilder.changeVoiceScale(multilineVars.currentVoice.scale); |
|
|
} |
|
|
return null; |
|
|
case "voicecolor": |
|
|
if (tokens.length !== 1) |
|
|
return "voicecolor requires one string as a parameter"; |
|
|
var voiceColor = tokens.shift(); |
|
|
if (multilineVars.currentVoice) { |
|
|
multilineVars.currentVoice.color = voiceColor.token; |
|
|
tuneBuilder.changeVoiceColor(multilineVars.currentVoice.color); |
|
|
} |
|
|
return null; |
|
|
case "vskip": |
|
|
var vskip = Math.round(getRequiredMeasurement(cmd, tokens)); |
|
|
if (vskip.error) |
|
|
return vskip.error; |
|
|
tuneBuilder.addSpacing(vskip); |
|
|
return null; |
|
|
case "scale": |
|
|
setScale(cmd, tokens); |
|
|
break; |
|
|
case "sep": |
|
|
if (tokens.length === 0) |
|
|
tuneBuilder.addSeparator(14,14,85, { startChar: multilineVars.iChar, endChar: multilineVars.iChar+5}); |
|
|
else { |
|
|
var points = tokenizer.getMeasurement(tokens); |
|
|
if (points.used === 0) |
|
|
return "Directive \"" + cmd + "\" requires 3 numbers: space above, space below, length of line"; |
|
|
var spaceAbove = points.value; |
|
|
|
|
|
points = tokenizer.getMeasurement(tokens); |
|
|
if (points.used === 0) |
|
|
return "Directive \"" + cmd + "\" requires 3 numbers: space above, space below, length of line"; |
|
|
var spaceBelow = points.value; |
|
|
|
|
|
points = tokenizer.getMeasurement(tokens); |
|
|
if (points.used === 0 || tokens.length !== 0) |
|
|
return "Directive \"" + cmd + "\" requires 3 numbers: space above, space below, length of line"; |
|
|
var lenLine = points.value; |
|
|
tuneBuilder.addSeparator(spaceAbove, spaceBelow, lenLine, { startChar: multilineVars.iChar, endChar: multilineVars.iChar+restOfString.length}); |
|
|
} |
|
|
break; |
|
|
case "barsperstaff": |
|
|
scratch = addMultilineVar('barsperstaff', cmd, tokens); |
|
|
if (scratch !== null) return scratch; |
|
|
break; |
|
|
case "staffnonote": |
|
|
|
|
|
if (tokens.length !== 1) |
|
|
return "Directive staffnonote requires one parameter: 0 or 1"; |
|
|
if (tokens[0].token === '0') |
|
|
multilineVars.staffnonote = true; |
|
|
else if (tokens[0].token === '1') |
|
|
multilineVars.staffnonote = false; |
|
|
else |
|
|
return "Directive staffnonote requires one parameter: 0 or 1 (received " + tokens[0].token + ')'; |
|
|
break; |
|
|
case "printtempo": |
|
|
scratch = addMultilineVarBool('printTempo', cmd, tokens); |
|
|
if (scratch !== null) return scratch; |
|
|
break; |
|
|
case "partsbox": |
|
|
scratch = addMultilineVarBool('partsBox', cmd, tokens); |
|
|
if (scratch !== null) return scratch; |
|
|
multilineVars.partsfont.box = multilineVars.partsBox; |
|
|
break; |
|
|
case "freegchord": |
|
|
scratch = addMultilineVarBool('freegchord', cmd, tokens); |
|
|
if (scratch !== null) return scratch; |
|
|
break; |
|
|
case "measurenb": |
|
|
case "barnumbers": |
|
|
scratch = addMultilineVar('barNumbers', cmd, tokens); |
|
|
if (scratch !== null) return scratch; |
|
|
break; |
|
|
case "setbarnb": |
|
|
if (tokens.length !== 1 || tokens[0].type !== 'number') { |
|
|
return 'Directive setbarnb requires a number as a parameter.'; |
|
|
} |
|
|
multilineVars.currBarNumber = tuneBuilder.setBarNumberImmediate(tokens[0].intt); |
|
|
break; |
|
|
case "begintext": |
|
|
var textBlock = ''; |
|
|
line = tokenizer.nextLine(); |
|
|
while(line && line.indexOf('%%endtext') !== 0) { |
|
|
if (parseCommon.startsWith(line, "%%")) |
|
|
textBlock += line.substring(2) + "\n"; |
|
|
else |
|
|
textBlock += line + "\n"; |
|
|
line = tokenizer.nextLine(); |
|
|
} |
|
|
tuneBuilder.addText(textBlock, { startChar: multilineVars.iChar, endChar: multilineVars.iChar+textBlock.length+7}); |
|
|
break; |
|
|
case "continueall": |
|
|
multilineVars.continueall = true; |
|
|
break; |
|
|
case "beginps": |
|
|
line = tokenizer.nextLine(); |
|
|
while(line && line.indexOf('%%endps') !== 0) { |
|
|
tokenizer.nextLine(); |
|
|
} |
|
|
warn("Postscript ignored", str, 0); |
|
|
break; |
|
|
case "deco": |
|
|
if (restOfString.length > 0) |
|
|
multilineVars.ignoredDecorations.push(restOfString.substring(0, restOfString.indexOf(' '))); |
|
|
warn("Decoration redefinition ignored", str, 0); |
|
|
break; |
|
|
case "text": |
|
|
var textstr = tokenizer.translateString(restOfString); |
|
|
tuneBuilder.addText(parseDirective.parseFontChangeLine(textstr), { startChar: multilineVars.iChar, endChar: multilineVars.iChar+restOfString.length+7}); |
|
|
break; |
|
|
case "center": |
|
|
var centerstr = tokenizer.translateString(restOfString); |
|
|
tuneBuilder.addCentered(parseDirective.parseFontChangeLine(centerstr)); |
|
|
break; |
|
|
case "font": |
|
|
|
|
|
break; |
|
|
case "setfont": |
|
|
var sfTokens = tokenizer.tokenize(restOfString, 0, restOfString.length); |
|
|
|
|
|
if (sfTokens.length >= 4) { |
|
|
if (sfTokens[0].token === '-' && sfTokens[1].type === 'number') { |
|
|
var sfNum = parseInt(sfTokens[1].token); |
|
|
if (sfNum >= 1 && sfNum <= 9) { |
|
|
if (!multilineVars.setfont) |
|
|
multilineVars.setfont = []; |
|
|
sfTokens.shift(); |
|
|
sfTokens.shift(); |
|
|
multilineVars.setfont[sfNum] = getFontParameter(sfTokens, multilineVars.setfont[sfNum], str, 0, 'setfont'); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
break; |
|
|
case "gchordfont": |
|
|
case "partsfont": |
|
|
case "tripletfont": |
|
|
case "vocalfont": |
|
|
case "textfont": |
|
|
case "annotationfont": |
|
|
case "historyfont": |
|
|
case "infofont": |
|
|
case "measurefont": |
|
|
case "repeatfont": |
|
|
case "wordsfont": |
|
|
return getChangingFont(cmd, tokens, str); |
|
|
case "composerfont": |
|
|
case "subtitlefont": |
|
|
case "tempofont": |
|
|
case "titlefont": |
|
|
case "voicefont": |
|
|
case "footerfont": |
|
|
case "headerfont": |
|
|
return getGlobalFont(cmd, tokens, str); |
|
|
case "barlabelfont": |
|
|
case "barnumberfont": |
|
|
case "barnumfont": |
|
|
return getChangingFont("measurefont", tokens, str); |
|
|
case "staves": |
|
|
case "score": |
|
|
multilineVars.score_is_present = true; |
|
|
var addVoice = function(id, newStaff, bracket, brace, continueBar) { |
|
|
if (newStaff || multilineVars.staves.length === 0) { |
|
|
multilineVars.staves.push({index: multilineVars.staves.length, numVoices: 0}); |
|
|
} |
|
|
var staff = parseCommon.last(multilineVars.staves); |
|
|
if (bracket !== undefined && staff.bracket === undefined) staff.bracket = bracket; |
|
|
if (brace !== undefined && staff.brace === undefined) staff.brace = brace; |
|
|
if (continueBar) staff.connectBarLines = 'end'; |
|
|
if (multilineVars.voices[id] === undefined) { |
|
|
multilineVars.voices[id] = {staffNum: staff.index, index: staff.numVoices}; |
|
|
staff.numVoices++; |
|
|
} |
|
|
}; |
|
|
|
|
|
var openParen = false; |
|
|
var openBracket = false; |
|
|
var openBrace = false; |
|
|
var justOpenParen = false; |
|
|
var justOpenBracket = false; |
|
|
var justOpenBrace = false; |
|
|
var continueBar = false; |
|
|
var lastVoice; |
|
|
var addContinueBar = function() { |
|
|
continueBar = true; |
|
|
if (lastVoice) { |
|
|
var ty = 'start'; |
|
|
if (lastVoice.staffNum > 0) { |
|
|
if (multilineVars.staves[lastVoice.staffNum-1].connectBarLines === 'start' || |
|
|
multilineVars.staves[lastVoice.staffNum-1].connectBarLines === 'continue') |
|
|
ty = 'continue'; |
|
|
} |
|
|
multilineVars.staves[lastVoice.staffNum].connectBarLines = ty; |
|
|
} |
|
|
}; |
|
|
while (tokens.length) { |
|
|
var t = tokens.shift(); |
|
|
switch (t.token) { |
|
|
case '(': |
|
|
if (openParen) warn("Can't nest parenthesis in %%score", str, t.start); |
|
|
else {openParen = true;justOpenParen = true;} |
|
|
break; |
|
|
case ')': |
|
|
if (!openParen || justOpenParen) warn("Unexpected close parenthesis in %%score", str, t.start); |
|
|
else openParen = false; |
|
|
break; |
|
|
case '[': |
|
|
if (openBracket) warn("Can't nest brackets in %%score", str, t.start); |
|
|
else {openBracket = true;justOpenBracket = true;} |
|
|
break; |
|
|
case ']': |
|
|
if (!openBracket || justOpenBracket) warn("Unexpected close bracket in %%score", str, t.start); |
|
|
else {openBracket = false;multilineVars.staves[lastVoice.staffNum].bracket = 'end';} |
|
|
break; |
|
|
case '{': |
|
|
if (openBrace ) warn("Can't nest braces in %%score", str, t.start); |
|
|
else {openBrace = true;justOpenBrace = true;} |
|
|
break; |
|
|
case '}': |
|
|
if (!openBrace || justOpenBrace) warn("Unexpected close brace in %%score", str, t.start); |
|
|
else {openBrace = false;multilineVars.staves[lastVoice.staffNum].brace = 'end';} |
|
|
break; |
|
|
case '|': |
|
|
addContinueBar(); |
|
|
break; |
|
|
default: |
|
|
var vc = ""; |
|
|
while (t.type === 'alpha' || t.type === 'number') { |
|
|
vc += t.token; |
|
|
if (t.continueId) |
|
|
t = tokens.shift(); |
|
|
else |
|
|
break; |
|
|
} |
|
|
var newStaff = !openParen || justOpenParen; |
|
|
var bracket = justOpenBracket ? 'start' : openBracket ? 'continue' : undefined; |
|
|
var brace = justOpenBrace ? 'start' : openBrace ? 'continue' : undefined; |
|
|
addVoice(vc, newStaff, bracket, brace, continueBar); |
|
|
justOpenParen = false; |
|
|
justOpenBracket = false; |
|
|
justOpenBrace = false; |
|
|
continueBar = false; |
|
|
lastVoice = multilineVars.voices[vc]; |
|
|
if (cmd === 'staves') |
|
|
addContinueBar(); |
|
|
break; |
|
|
} |
|
|
} |
|
|
break; |
|
|
|
|
|
case "newpage": |
|
|
var pgNum = tokenizer.getInt(restOfString); |
|
|
tuneBuilder.addNewPage(pgNum.digits === 0 ? -1 : pgNum.value); |
|
|
break; |
|
|
|
|
|
case "abc": |
|
|
var arr = restOfString.split(' '); |
|
|
switch (arr[0]) { |
|
|
case "-copyright": |
|
|
case "-creator": |
|
|
case "-edited-by": |
|
|
case "-version": |
|
|
case "-charset": |
|
|
var subCmd = arr.shift(); |
|
|
tuneBuilder.addMetaText(cmd+subCmd, arr.join(' '), { startChar: multilineVars.iChar, endChar: multilineVars.iChar+restOfString.length+5}); |
|
|
break; |
|
|
default: |
|
|
return "Unknown directive: " + cmd+arr[0]; |
|
|
} |
|
|
break; |
|
|
case "header": |
|
|
case "footer": |
|
|
var footerStr = tokenizer.getMeat(restOfString, 0, restOfString.length); |
|
|
footerStr = restOfString.substring(footerStr.start, footerStr.end); |
|
|
if (footerStr[0] === '"' && footerStr[footerStr.length-1] === '"' ) |
|
|
footerStr = footerStr.substring(1, footerStr.length-1); |
|
|
var footerArr = footerStr.split('\t'); |
|
|
var footer = {}; |
|
|
if (footerArr.length === 1) |
|
|
footer = { left: "", center: footerArr[0], right: "" }; |
|
|
else if (footerArr.length === 2) |
|
|
footer = { left: footerArr[0], center: footerArr[1], right: "" }; |
|
|
else |
|
|
footer = { left: footerArr[0], center: footerArr[1], right: footerArr[2] }; |
|
|
if (footerArr.length > 3) |
|
|
warn("Too many tabs in " + cmd + ": " + footerArr.length + " found.", restOfString, 0); |
|
|
|
|
|
tuneBuilder.addMetaTextObj(cmd, footer, { startChar: multilineVars.iChar, endChar: multilineVars.iChar+str.length}); |
|
|
break; |
|
|
|
|
|
case "midi": |
|
|
var midi = tokenizer.tokenize(restOfString, 0, restOfString.length, true); |
|
|
if (midi.length > 0 && midi[0].token === '=') |
|
|
midi.shift(); |
|
|
if (midi.length === 0) |
|
|
warn("Expected midi command", restOfString, 0); |
|
|
else |
|
|
parseMidiCommand(midi, tune, restOfString); |
|
|
break; |
|
|
case "percmap": |
|
|
var percmap = interpretPercMap(restOfString); |
|
|
if (percmap.error) |
|
|
warn(percmap.error, str, 8); |
|
|
else { |
|
|
if (!tune.formatting.percmap) |
|
|
tune.formatting.percmap = {}; |
|
|
tune.formatting.percmap[percmap.key] = percmap.value; |
|
|
} |
|
|
break; |
|
|
|
|
|
case "map": |
|
|
case "playtempo": |
|
|
case "auquality": |
|
|
case "continuous": |
|
|
case "nobarcheck": |
|
|
|
|
|
tune.formatting[cmd] = restOfString; |
|
|
break; |
|
|
default: |
|
|
return "Unknown directive: " + cmd; |
|
|
} |
|
|
return null; |
|
|
}; |
|
|
parseDirective.globalFormatting = function(formatHash) { |
|
|
for (var cmd in formatHash) { |
|
|
if (formatHash.hasOwnProperty(cmd)) { |
|
|
var value = ''+formatHash[cmd]; |
|
|
var tokens = tokenizer.tokenize(value, 0, value.length); |
|
|
var scratch; |
|
|
switch (cmd) { |
|
|
case "titlefont": |
|
|
case "gchordfont": |
|
|
case "composerfont": |
|
|
case "footerfont": |
|
|
case "headerfont": |
|
|
case "historyfont": |
|
|
case "infofont": |
|
|
case "measurefont": |
|
|
case "partsfont": |
|
|
case "repeatfont": |
|
|
case "subtitlefont": |
|
|
case "tempofont": |
|
|
case "textfont": |
|
|
case "voicefont": |
|
|
case "tripletfont": |
|
|
case "vocalfont": |
|
|
case "wordsfont": |
|
|
case "annotationfont": |
|
|
case "tablabelfont": |
|
|
case "tabnumberfont": |
|
|
case "tabgracefont": |
|
|
getChangingFont(cmd, tokens, value); |
|
|
break; |
|
|
case "scale": |
|
|
setScale(cmd, tokens); |
|
|
break; |
|
|
case "partsbox": |
|
|
scratch = addMultilineVarBool('partsBox', cmd, tokens); |
|
|
if (scratch !== null) warn(scratch); |
|
|
multilineVars.partsfont.box = multilineVars.partsBox; |
|
|
break; |
|
|
case "freegchord": |
|
|
scratch = addMultilineVarBool('freegchord', cmd, tokens); |
|
|
if (scratch !== null) warn(scratch); |
|
|
break; |
|
|
case "fontboxpadding": |
|
|
if (tokens.length !== 1 || tokens[0].type !== 'number') |
|
|
warn("Directive \"" + cmd + "\" requires a number as a parameter."); |
|
|
tune.formatting.fontboxpadding = tokens[0].floatt; |
|
|
break; |
|
|
case "stafftopmargin": |
|
|
if (tokens.length !== 1 || tokens[0].type !== 'number') |
|
|
warn("Directive \"" + cmd + "\" requires a number as a parameter."); |
|
|
tune.formatting.stafftopmargin = tokens[0].floatt; |
|
|
break; |
|
|
case "stretchlast": |
|
|
var sl = parseStretchLast(tokens); |
|
|
if (sl.value !== undefined) |
|
|
tune.formatting.stretchlast = sl.value; |
|
|
if (sl.error) |
|
|
return sl.error; |
|
|
break; |
|
|
default: |
|
|
warn("Formatting directive unrecognized: ", cmd, 0); |
|
|
} |
|
|
} |
|
|
} |
|
|
}; |
|
|
|
|
|
function parseStretchLast(tokens) { |
|
|
if (tokens.length === 0) |
|
|
return { value: 1 }; |
|
|
else if (tokens.length === 1) { |
|
|
if (tokens[0].type === "number") { |
|
|
if (tokens[0].floatt >= 0 || tokens[0].floatt <= 1) |
|
|
return {value: tokens[0].floatt}; |
|
|
} else if (tokens[0].token === 'false') { |
|
|
return { value: 0 }; |
|
|
} else if (tokens[0].token === 'true') { |
|
|
return {value: 1}; |
|
|
} |
|
|
} |
|
|
return { error: "Directive stretchlast requires zero or one parameter: false, true, or number between 0 and 1 (received " + tokens[0].token + ')' }; |
|
|
} |
|
|
})(); |
|
|
|
|
|
module.exports = parseDirective; |
|
|
|