|
|
var parseKeyVoice = require('../parse/abc_parse_key_voice'); |
|
|
|
|
|
|
|
|
|
|
|
var TuneBuilder = function (tune) { |
|
|
var self = this; |
|
|
var voiceDefs = {} |
|
|
var currentVoiceName = '' |
|
|
tune.reset(); |
|
|
|
|
|
this.setVisualTranspose = function (visualTranspose) { |
|
|
if (visualTranspose) |
|
|
tune.visualTranspose = visualTranspose; |
|
|
}; |
|
|
|
|
|
this.cleanUp = function (barsperstaff, staffnonote, currSlur) { |
|
|
closeLine(tune); |
|
|
delete tune.runningFonts; |
|
|
|
|
|
simplifyMetaText(tune) |
|
|
|
|
|
|
|
|
|
|
|
if (tune.metaText.tempo && tune.metaText.tempo.bpm && !tune.metaText.tempo.duration) |
|
|
tune.metaText.tempo.duration = [tune.getBeatLength()]; |
|
|
|
|
|
|
|
|
var anyDeleted = false; |
|
|
var i, s, v; |
|
|
for (i = 0; i < tune.lines.length; i++) { |
|
|
if (tune.lines[i].staff !== undefined) { |
|
|
var hasAny = false; |
|
|
for (s = 0; s < tune.lines[i].staff.length; s++) { |
|
|
if (tune.lines[i].staff[s] === undefined) { |
|
|
anyDeleted = true; |
|
|
tune.lines[i].staff[s] = null; |
|
|
|
|
|
} else { |
|
|
for (v = 0; v < tune.lines[i].staff[s].voices.length; v++) { |
|
|
if (tune.lines[i].staff[s].voices[v] === undefined) |
|
|
tune.lines[i].staff[s].voices[v] = []; |
|
|
else |
|
|
if (containsNotes(tune.lines[i].staff[s].voices[v])) hasAny = true; |
|
|
} |
|
|
} |
|
|
} |
|
|
if (!hasAny) { |
|
|
tune.lines[i] = null; |
|
|
anyDeleted = true; |
|
|
} |
|
|
} |
|
|
} |
|
|
if (anyDeleted) { |
|
|
tune.lines = tune.lines.filter(function (line) { return !!line }); |
|
|
tune.lines.forEach(function (line) { |
|
|
if (line.staff) |
|
|
line.staff = line.staff.filter(function (line) { return !!line }); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
if (barsperstaff) { |
|
|
while (wrapMusicLines(tune.lines, barsperstaff)) { |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (staffnonote) { |
|
|
anyDeleted = false; |
|
|
for (i = 0; i < tune.lines.length; i++) { |
|
|
if (tune.lines[i].staff !== undefined) { |
|
|
for (s = 0; s < tune.lines[i].staff.length; s++) { |
|
|
var keepThis = false; |
|
|
for (v = 0; v < tune.lines[i].staff[s].voices.length; v++) { |
|
|
if (containsNotesStrict(tune.lines[i].staff[s].voices[v])) { |
|
|
keepThis = true; |
|
|
} |
|
|
} |
|
|
if (!keepThis) { |
|
|
anyDeleted = true; |
|
|
tune.lines[i].staff[s] = null; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
if (anyDeleted) { |
|
|
tune.lines.forEach(function (line) { |
|
|
if (line.staff) |
|
|
line.staff = line.staff.filter(function (staff) { return !!staff }); |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
fixTitles(tune.lines); |
|
|
|
|
|
|
|
|
for (i = 0; i < tune.lines.length; i++) { |
|
|
if (tune.lines[i].staff) { |
|
|
for (s = 0; s < tune.lines[i].staff.length; s++) |
|
|
delete tune.lines[i].staff[s].workingClef; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
while (resolveOverlays(tune)) { |
|
|
|
|
|
} |
|
|
|
|
|
for (var i = 0; i < tune.lines.length; i++) { |
|
|
var staff = tune.lines[i].staff; |
|
|
if (staff) { |
|
|
for (tune.staffNum = 0; tune.staffNum < staff.length; tune.staffNum++) { |
|
|
if (staff[tune.staffNum].clef) |
|
|
parseKeyVoice.fixClef(staff[tune.staffNum].clef); |
|
|
for (tune.voiceNum = 0; tune.voiceNum < staff[tune.staffNum].voices.length; tune.voiceNum++) { |
|
|
var voice = staff[tune.staffNum].voices[tune.voiceNum]; |
|
|
cleanUpSlursInLine(voice, tune.staffNum, tune.voiceNum, currSlur); |
|
|
for (var j = 0; j < voice.length; j++) { |
|
|
if (voice[j].el_type === 'clef') |
|
|
parseKeyVoice.fixClef(voice[j]); |
|
|
} |
|
|
if (voice.length > 0 && voice[voice.length - 1].barNumber) { |
|
|
|
|
|
var nextLine = getNextMusicLine(tune.lines, i); |
|
|
if (nextLine) |
|
|
nextLine.staff[0].barNumber = voice[voice.length - 1].barNumber; |
|
|
delete voice[voice.length - 1].barNumber; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
delete tune.staffNum; |
|
|
delete tune.voiceNum; |
|
|
delete tune.lineNum; |
|
|
delete tune.potentialStartBeam; |
|
|
delete tune.potentialEndBeam; |
|
|
delete tune.vskipPending; |
|
|
|
|
|
return currSlur; |
|
|
}; |
|
|
|
|
|
this.addTieToLastNote = function (dottedTie) { |
|
|
|
|
|
var el = getLastNote(tune); |
|
|
if (el && el.pitches && el.pitches.length > 0) { |
|
|
el.pitches[0].startTie = {}; |
|
|
if (dottedTie) |
|
|
el.pitches[0].startTie.style = 'dotted'; |
|
|
return true; |
|
|
} |
|
|
return false; |
|
|
}; |
|
|
|
|
|
this.appendElement = function (type, startChar, endChar, hashParams) { |
|
|
hashParams.el_type = type; |
|
|
if (startChar !== null) |
|
|
hashParams.startChar = startChar; |
|
|
if (endChar !== null) |
|
|
hashParams.endChar = endChar; |
|
|
if (type === 'note') { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var dur = getDuration(hashParams); |
|
|
if (dur >= 0.25) { |
|
|
endBeamLast(tune); |
|
|
} else if (hashParams.force_end_beam_last && tune.potentialStartBeam !== undefined) { |
|
|
endBeamLast(tune); |
|
|
} else if (hashParams.end_beam && tune.potentialStartBeam !== undefined) { |
|
|
if (hashParams.rest === undefined) |
|
|
endBeamHere(hashParams, tune); |
|
|
else |
|
|
endBeamLast(tune); |
|
|
} else if (hashParams.rest === undefined) { |
|
|
if (tune.potentialStartBeam === undefined) { |
|
|
if (!hashParams.end_beam) { |
|
|
tune.potentialStartBeam = hashParams; |
|
|
delete tune.potentialEndBeam; |
|
|
} |
|
|
} else { |
|
|
tune.potentialEndBeam = hashParams; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} else { |
|
|
endBeamLast(tune); |
|
|
} |
|
|
delete hashParams.end_beam; |
|
|
delete hashParams.force_end_beam_last; |
|
|
if (hashParams.rest && hashParams.rest.type === 'invisible') { |
|
|
delete hashParams.decoration |
|
|
} |
|
|
if (tune.lines.length <= tune.lineNum || tune.lines[tune.lineNum].staff.length <= tune.staffNum) { |
|
|
|
|
|
|
|
|
return false; |
|
|
} |
|
|
|
|
|
pushNote(self, tune, hashParams, voiceDefs, currentVoiceName); |
|
|
return true |
|
|
}; |
|
|
|
|
|
this.appendStartingElement = function (type, startChar, endChar, hashParams2) { |
|
|
|
|
|
|
|
|
closeLine(tune); |
|
|
|
|
|
|
|
|
var impliedNaturals; |
|
|
if (type === 'key') { |
|
|
impliedNaturals = hashParams2.impliedNaturals; |
|
|
delete hashParams2.impliedNaturals; |
|
|
delete hashParams2.explicitAccidentals; |
|
|
} |
|
|
|
|
|
|
|
|
var hashParams = Object.assign({}, hashParams2); |
|
|
|
|
|
|
|
|
if (!tune.lines[tune.lineNum]) return |
|
|
var staff = tune.lines[tune.lineNum].staff |
|
|
if (!staff) return |
|
|
|
|
|
|
|
|
if (staff.length <= tune.staffNum) { |
|
|
staff[tune.staffNum] = {}; |
|
|
staff[tune.staffNum].clef = Object.assign({}, staff[0].clef); |
|
|
staff[tune.staffNum].key = Object.assign({}, staff[0].key); |
|
|
if (staff[0].meter) |
|
|
staff[tune.staffNum].meter = Object.assign({}, staff[0].meter); |
|
|
staff[tune.staffNum].workingClef = Object.assign({}, staff[0].workingClef); |
|
|
staff[tune.staffNum].voices = [[]]; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (type === 'clef') { |
|
|
staff[tune.staffNum].workingClef = hashParams; |
|
|
} |
|
|
|
|
|
|
|
|
var voice = staff[tune.staffNum].voices[tune.voiceNum]; |
|
|
for (var i = 0; i < voice.length; i++) { |
|
|
if (voice[i].el_type === 'note' || voice[i].el_type === 'bar') { |
|
|
hashParams.el_type = type; |
|
|
hashParams.startChar = startChar; |
|
|
hashParams.endChar = endChar; |
|
|
if (impliedNaturals) |
|
|
hashParams.accidentals = impliedNaturals.concat(hashParams.accidentals); |
|
|
voice.push(hashParams); |
|
|
return; |
|
|
} |
|
|
if (voice[i].el_type === type) { |
|
|
hashParams.el_type = type; |
|
|
hashParams.startChar = startChar; |
|
|
hashParams.endChar = endChar; |
|
|
if (impliedNaturals) |
|
|
hashParams.accidentals = impliedNaturals.concat(hashParams.accidentals); |
|
|
voice[i] = hashParams; |
|
|
return; |
|
|
} |
|
|
} |
|
|
|
|
|
staff[tune.staffNum][type] = hashParams2; |
|
|
}; |
|
|
|
|
|
this.addSubtitle = function (str, info) { |
|
|
pushLine(tune, { subtitle: { text: str, startChar: info.startChar, endChar: info.endChar } }); |
|
|
}; |
|
|
|
|
|
this.addSpacing = function (num) { |
|
|
tune.vskipPending = num; |
|
|
}; |
|
|
|
|
|
this.addNewPage = function (num) { |
|
|
pushLine(tune, { newpage: num }); |
|
|
}; |
|
|
|
|
|
this.addSeparator = function (spaceAbove, spaceBelow, lineLength, info) { |
|
|
pushLine(tune, { separator: { spaceAbove: Math.round(spaceAbove), spaceBelow: Math.round(spaceBelow), lineLength: Math.round(lineLength), startChar: info.startChar, endChar: info.endChar } }); |
|
|
}; |
|
|
|
|
|
this.addText = function (str, info) { |
|
|
pushLine(tune, { text: { text: str, startChar: info.startChar, endChar: info.endChar } }); |
|
|
}; |
|
|
|
|
|
this.addCentered = function (str) { |
|
|
pushLine(tune, { text: [{ text: str, center: true }] }); |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.changeVoiceScale = function (scale) { |
|
|
self.appendElement('scale', null, null, { size: scale }); |
|
|
}; |
|
|
this.changeVoiceColor = function (color) { |
|
|
self.appendElement('color', null, null, { color: color }); |
|
|
}; |
|
|
|
|
|
this.startNewLine = function (params) { |
|
|
|
|
|
|
|
|
|
|
|
closeLine(tune); |
|
|
if (params.currentVoiceName) { |
|
|
currentVoiceName = params.currentVoiceName |
|
|
voiceDefs[params.currentVoiceName] = params |
|
|
} |
|
|
|
|
|
if (tune.lines[tune.lineNum] === undefined) createLine(self, tune, params); |
|
|
else if (tune.lines[tune.lineNum].staff === undefined) { |
|
|
tune.lineNum++; |
|
|
this.startNewLine(params); |
|
|
} else if (tune.lines[tune.lineNum].staff[tune.staffNum] === undefined) createStaff(self, tune, params); |
|
|
else if (tune.lines[tune.lineNum].staff[tune.staffNum].voices[tune.voiceNum] === undefined) createVoice(self, tune, params); |
|
|
else if (!containsNotes(tune.lines[tune.lineNum].staff[tune.staffNum].voices[tune.voiceNum])) { |
|
|
|
|
|
if (params.part) |
|
|
self.appendElement('part', params.part.startChar, params.part.endChar, { title: params.part.title }); |
|
|
} else { |
|
|
tune.lineNum++; |
|
|
this.startNewLine(params); |
|
|
} |
|
|
}; |
|
|
|
|
|
this.setRunningFont = function (type, font) { |
|
|
|
|
|
tune.runningFonts[type] = font; |
|
|
}; |
|
|
|
|
|
this.setBarNumberImmediate = function (barNumber) { |
|
|
|
|
|
|
|
|
|
|
|
var currentVoice = this.getCurrentVoice(); |
|
|
if (currentVoice && currentVoice.length > 0) { |
|
|
var lastElement = currentVoice[currentVoice.length - 1]; |
|
|
if (lastElement.el_type === 'bar') { |
|
|
if (lastElement.barNumber !== undefined) |
|
|
lastElement.barNumber = barNumber; |
|
|
} else |
|
|
return barNumber - 1; |
|
|
} |
|
|
return barNumber; |
|
|
}; |
|
|
|
|
|
this.hasBeginMusic = function () { |
|
|
|
|
|
for (var i = 0; i < tune.lines.length; i++) { |
|
|
if (tune.lines[i].staff) |
|
|
return true; |
|
|
} |
|
|
return false; |
|
|
}; |
|
|
|
|
|
this.isFirstLine = function (index) { |
|
|
for (var i = index - 1; i >= 0; i--) { |
|
|
if (tune.lines[i].staff !== undefined) return false; |
|
|
} |
|
|
return true; |
|
|
}; |
|
|
|
|
|
this.getCurrentVoice = function () { |
|
|
|
|
|
var currLine = tune.lines[tune.lineNum]; |
|
|
if (!currLine) |
|
|
return null; |
|
|
var currStaff = currLine.staff[tune.staffNum]; |
|
|
if (!currStaff) |
|
|
return null; |
|
|
if (currStaff.voices[tune.voiceNum] !== undefined) |
|
|
return currStaff.voices[tune.voiceNum]; |
|
|
else return null; |
|
|
}; |
|
|
|
|
|
this.setCurrentVoice = function (staffNum, voiceNum, name) { |
|
|
|
|
|
tune.staffNum = staffNum; |
|
|
tune.voiceNum = voiceNum; |
|
|
currentVoiceName = name |
|
|
for (var i = 0; i < tune.lines.length; i++) { |
|
|
if (tune.lines[i].staff) { |
|
|
if (tune.lines[i].staff[staffNum] === undefined || tune.lines[i].staff[staffNum].voices[voiceNum] === undefined || |
|
|
!containsNotes(tune.lines[i].staff[staffNum].voices[voiceNum])) { |
|
|
|
|
|
tune.lineNum = i; |
|
|
if (!tune.lines[i].staff[staffNum] || !!tune.lines[i].staff[staffNum].voices[voiceNum]) return true |
|
|
return false; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
tune.lineNum = i; |
|
|
return false |
|
|
}; |
|
|
|
|
|
this.addMetaText = function (key, value, info) { |
|
|
if (tune.metaText[key] === undefined) { |
|
|
tune.metaText[key] = value; |
|
|
tune.metaTextInfo[key] = info; |
|
|
} else { |
|
|
if (typeof tune.metaText[key] === 'string' && typeof value === 'string') |
|
|
tune.metaText[key] += "\n" + value; |
|
|
else { |
|
|
if (tune.metaText[key] === 'string') |
|
|
tune.metaText[key] = [{ text: tune.metaText[key] }] |
|
|
if (typeof value === 'string') |
|
|
value = [{ text: value }] |
|
|
tune.metaText[key] = tune.metaText[key].concat(value) |
|
|
} |
|
|
tune.metaTextInfo[key].endChar = info.endChar; |
|
|
} |
|
|
}; |
|
|
|
|
|
this.addMetaTextArray = function (key, value, info) { |
|
|
if (tune.metaText[key] === undefined) { |
|
|
tune.metaText[key] = [value]; |
|
|
tune.metaTextInfo[key] = info; |
|
|
} else { |
|
|
tune.metaText[key].push(value); |
|
|
tune.metaTextInfo[key].endChar = info.endChar; |
|
|
} |
|
|
}; |
|
|
this.addMetaTextObj = function (key, value, info) { |
|
|
tune.metaText[key] = value; |
|
|
tune.metaTextInfo[key] = info; |
|
|
}; |
|
|
}; |
|
|
|
|
|
function isArrayOfStrings(arr) { |
|
|
if (!arr) return false |
|
|
if (typeof arr === "string") return false |
|
|
|
|
|
for (var i = 0; i < arr.length; i++) { |
|
|
if (typeof arr[i] !== 'string') |
|
|
return false |
|
|
} |
|
|
return true |
|
|
} |
|
|
|
|
|
function simplifyMetaText(tune) { |
|
|
if (isArrayOfStrings(tune.metaText.notes)) |
|
|
tune.metaText.notes = tune.metaText.notes.join("\n") |
|
|
if (isArrayOfStrings(tune.metaText.history)) |
|
|
tune.metaText.history = tune.metaText.history.join("\n") |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function resolveOverlays(tune) { |
|
|
var madeChanges = false; |
|
|
var durationsPerLines = []; |
|
|
for (var i = 0; i < tune.lines.length; i++) { |
|
|
var line = tune.lines[i]; |
|
|
if (line.staff) { |
|
|
for (var j = 0; j < line.staff.length; j++) { |
|
|
var staff = line.staff[j]; |
|
|
var overlayVoice = []; |
|
|
for (var k = 0; k < staff.voices.length; k++) { |
|
|
var voice = staff.voices[k]; |
|
|
overlayVoice.push({ hasOverlay: false, voice: [], snip: [] }); |
|
|
durationsPerLines[i] = 0; |
|
|
var durationThisBar = 0; |
|
|
var inOverlay = false; |
|
|
var overlayDuration = 0; |
|
|
var snipStart = -1; |
|
|
for (var kk = 0; kk < voice.length; kk++) { |
|
|
var event = voice[kk]; |
|
|
if (event.el_type === "overlay" && !inOverlay) { |
|
|
madeChanges = true; |
|
|
inOverlay = true; |
|
|
snipStart = kk; |
|
|
overlayVoice[k].hasOverlay = true; |
|
|
if (overlayDuration === 0) |
|
|
overlayDuration = durationsPerLines[i]; |
|
|
|
|
|
|
|
|
for (var ii = 0; ii < i; ii++) { |
|
|
if (durationsPerLines[ii] && tune.lines[ii].staff && staff.voices.length >= tune.lines[ii].staff[0].voices.length) { |
|
|
tune.lines[ii].staff[0].voices.push([{ |
|
|
el_type: "note", |
|
|
duration: durationsPerLines[ii], |
|
|
rest: { type: "invisible" }, |
|
|
startChar: event.startChar, |
|
|
endChar: event.endChar |
|
|
}]); |
|
|
} |
|
|
} |
|
|
} else if (event.el_type === "bar") { |
|
|
if (inOverlay) { |
|
|
|
|
|
inOverlay = false; |
|
|
overlayVoice[k].snip.push({ start: snipStart, len: kk - snipStart }); |
|
|
overlayVoice[k].voice.push(event); |
|
|
} else { |
|
|
|
|
|
if (durationThisBar > 0) |
|
|
overlayVoice[k].voice.push({ el_type: "note", duration: durationThisBar, rest: { type: "invisible" }, startChar: event.startChar, endChar: event.endChar }); |
|
|
overlayVoice[k].voice.push(event); |
|
|
} |
|
|
durationThisBar = 0; |
|
|
} else if (event.el_type === "note") { |
|
|
if (inOverlay) { |
|
|
overlayVoice[k].voice.push(event); |
|
|
} else { |
|
|
durationThisBar += event.duration; |
|
|
durationsPerLines[i] += event.duration; |
|
|
} |
|
|
} else if (event.el_type === "scale" || event.el_type === "stem" || event.el_type === "overlay" || event.el_type === "style" || event.el_type === "transpose" || event.el_type === "color") { |
|
|
|
|
|
overlayVoice[k].voice.push(event); |
|
|
} |
|
|
} |
|
|
if (overlayVoice[k].hasOverlay && overlayVoice[k].snip.length === 0) { |
|
|
|
|
|
overlayVoice[k].snip.push({ start: snipStart, len: voice.length - snipStart }); |
|
|
} |
|
|
} |
|
|
for (k = 0; k < overlayVoice.length; k++) { |
|
|
var ov = overlayVoice[k]; |
|
|
if (ov.hasOverlay) { |
|
|
ov.voice.splice(0, 0, { el_type: "stem", direction: "down" }) |
|
|
staff.voices.push(ov.voice); |
|
|
for (var kkk = ov.snip.length - 1; kkk >= 0; kkk--) { |
|
|
var snip = ov.snip[kkk]; |
|
|
staff.voices[k].splice(snip.start, snip.len); |
|
|
staff.voices[k].splice(snip.start + 1, 0, { el_type: "stem", direction: "auto" }); |
|
|
var indexOfLastBar = findLastBar(staff.voices[k], snip.start); |
|
|
staff.voices[k].splice(indexOfLastBar, 0, { el_type: "stem", direction: "up" }); |
|
|
} |
|
|
|
|
|
for (kkk = 0; kkk < staff.voices[staff.voices.length - 1].length; kkk++) { |
|
|
staff.voices[staff.voices.length - 1][kkk] = Object.assign({}, staff.voices[staff.voices.length - 1][kkk]); |
|
|
var el = staff.voices[staff.voices.length - 1][kkk]; |
|
|
if (el.el_type === 'bar' && el.startEnding) { |
|
|
delete el.startEnding; |
|
|
} |
|
|
if (el.el_type === 'bar' && el.endEnding) |
|
|
delete el.endEnding; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
return madeChanges; |
|
|
}; |
|
|
|
|
|
function findLastBar(voice, start) { |
|
|
for (var i = start - 1; i > 0 && voice[i].el_type !== "bar"; i--) { |
|
|
|
|
|
} |
|
|
return i; |
|
|
} |
|
|
|
|
|
function fixTitles(lines) { |
|
|
|
|
|
var firstMusicLine = true; |
|
|
for (var i = 0; i < lines.length; i++) { |
|
|
var line = lines[i]; |
|
|
if (line.staff) { |
|
|
for (var j = 0; j < line.staff.length; j++) { |
|
|
var staff = line.staff[j]; |
|
|
if (staff.title) { |
|
|
var hasATitle = false; |
|
|
for (var k = 0; k < staff.title.length; k++) { |
|
|
if (staff.title[k]) { |
|
|
staff.title[k] = (firstMusicLine) ? staff.title[k].name : staff.title[k].subname; |
|
|
if (staff.title[k]) |
|
|
hasATitle = true; |
|
|
else |
|
|
staff.title[k] = ''; |
|
|
} else |
|
|
staff.title[k] = ''; |
|
|
} |
|
|
if (!hasATitle) |
|
|
delete staff.title; |
|
|
} |
|
|
} |
|
|
firstMusicLine = false; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
function cleanUpSlursInLine(line, staffNum, voiceNum, currSlur) { |
|
|
if (!currSlur[staffNum]) |
|
|
currSlur[staffNum] = []; |
|
|
if (!currSlur[staffNum][voiceNum]) |
|
|
currSlur[staffNum][voiceNum] = []; |
|
|
var x; |
|
|
|
|
|
|
|
|
var addEndSlur = function (obj, num, chordPos) { |
|
|
if (currSlur[staffNum][voiceNum][chordPos] === undefined) { |
|
|
|
|
|
for (x = 0; x < currSlur[staffNum][voiceNum].length; x++) { |
|
|
if (currSlur[staffNum][voiceNum][x] !== undefined) { |
|
|
chordPos = x; |
|
|
break; |
|
|
} |
|
|
} |
|
|
if (currSlur[staffNum][voiceNum][chordPos] === undefined) { |
|
|
var offNum = chordPos * 100 + 1; |
|
|
obj.endSlur.forEach(function (x) { if (offNum === x) --offNum; }); |
|
|
currSlur[staffNum][voiceNum][chordPos] = [offNum]; |
|
|
} |
|
|
} |
|
|
var slurNum; |
|
|
for (var i = 0; i < num; i++) { |
|
|
slurNum = currSlur[staffNum][voiceNum][chordPos].pop(); |
|
|
obj.endSlur.push(slurNum); |
|
|
|
|
|
} |
|
|
if (currSlur[staffNum][voiceNum][chordPos].length === 0) |
|
|
delete currSlur[staffNum][voiceNum][chordPos]; |
|
|
return slurNum; |
|
|
}; |
|
|
|
|
|
var addStartSlur = function (obj, num, chordPos, usedNums) { |
|
|
obj.startSlur = []; |
|
|
if (currSlur[staffNum][voiceNum][chordPos] === undefined) { |
|
|
currSlur[staffNum][voiceNum][chordPos] = []; |
|
|
} |
|
|
var nextNum = chordPos * 100 + 1; |
|
|
for (var i = 0; i < num; i++) { |
|
|
if (usedNums) { |
|
|
usedNums.forEach(function (x) { if (nextNum === x) ++nextNum; }); |
|
|
usedNums.forEach(function (x) { if (nextNum === x) ++nextNum; }); |
|
|
usedNums.forEach(function (x) { if (nextNum === x) ++nextNum; }); |
|
|
} |
|
|
currSlur[staffNum][voiceNum][chordPos].forEach(function (x) { if (nextNum === x) ++nextNum; }); |
|
|
currSlur[staffNum][voiceNum][chordPos].forEach(function (x) { if (nextNum === x) ++nextNum; }); |
|
|
|
|
|
currSlur[staffNum][voiceNum][chordPos].push(nextNum); |
|
|
obj.startSlur.push({ label: nextNum }); |
|
|
if (obj.dottedSlur) { |
|
|
obj.startSlur[obj.startSlur.length - 1].style = 'dotted'; |
|
|
delete obj.dottedSlur; |
|
|
} |
|
|
|
|
|
nextNum++; |
|
|
} |
|
|
}; |
|
|
|
|
|
for (var i = 0; i < line.length; i++) { |
|
|
var el = line[i]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (el.el_type === 'note') { |
|
|
if (el.gracenotes) { |
|
|
for (var g = 0; g < el.gracenotes.length; g++) { |
|
|
if (el.gracenotes[g].endSlur) { |
|
|
var gg = el.gracenotes[g].endSlur; |
|
|
el.gracenotes[g].endSlur = []; |
|
|
for (var ggg = 0; ggg < gg; ggg++) |
|
|
addEndSlur(el.gracenotes[g], 1, 20); |
|
|
} |
|
|
if (el.gracenotes[g].startSlur) { |
|
|
x = el.gracenotes[g].startSlur; |
|
|
addStartSlur(el.gracenotes[g], x, 20); |
|
|
} |
|
|
} |
|
|
} |
|
|
if (el.endSlur) { |
|
|
x = el.endSlur; |
|
|
el.endSlur = []; |
|
|
addEndSlur(el, x, 0); |
|
|
} |
|
|
if (el.startSlur) { |
|
|
x = el.startSlur; |
|
|
addStartSlur(el, x, 0); |
|
|
} |
|
|
if (el.pitches) { |
|
|
var usedNums = []; |
|
|
for (var p = 0; p < el.pitches.length; p++) { |
|
|
if (el.pitches[p].endSlur) { |
|
|
var k = el.pitches[p].endSlur; |
|
|
el.pitches[p].endSlur = []; |
|
|
for (var j = 0; j < k; j++) { |
|
|
var slurNum = addEndSlur(el.pitches[p], 1, p + 1); |
|
|
usedNums.push(slurNum); |
|
|
} |
|
|
} |
|
|
} |
|
|
for (p = 0; p < el.pitches.length; p++) { |
|
|
if (el.pitches[p].startSlur) { |
|
|
x = el.pitches[p].startSlur; |
|
|
addStartSlur(el.pitches[p], x, p + 1, usedNums); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (el.gracenotes && el.pitches[0].endSlur && el.pitches[0].endSlur[0] === 100 && el.pitches[0].startSlur) { |
|
|
if (el.gracenotes[0].endSlur) |
|
|
el.gracenotes[0].endSlur.push(el.pitches[0].startSlur[0].label); |
|
|
else |
|
|
el.gracenotes[0].endSlur = [el.pitches[0].startSlur[0].label]; |
|
|
if (el.pitches[0].endSlur.length === 1) |
|
|
delete el.pitches[0].endSlur; |
|
|
else if (el.pitches[0].endSlur[0] === 100) |
|
|
el.pitches[0].endSlur.shift(); |
|
|
else if (el.pitches[0].endSlur[el.pitches[0].endSlur.length - 1] === 100) |
|
|
el.pitches[0].endSlur.pop(); |
|
|
if (currSlur[staffNum][voiceNum][1].length === 1) |
|
|
delete currSlur[staffNum][voiceNum][1]; |
|
|
else |
|
|
currSlur[staffNum][voiceNum][1].pop(); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
function wrapMusicLines(lines, barsperstaff) { |
|
|
for (i = 0; i < lines.length; i++) { |
|
|
if (lines[i].staff !== undefined) { |
|
|
for (s = 0; s < lines[i].staff.length; s++) { |
|
|
var permanentItems = []; |
|
|
for (v = 0; v < lines[i].staff[s].voices.length; v++) { |
|
|
var voice = lines[i].staff[s].voices[v]; |
|
|
var barNumThisLine = 0; |
|
|
for (var n = 0; n < voice.length; n++) { |
|
|
if (voice[n].el_type === 'bar') { |
|
|
barNumThisLine++; |
|
|
if (barNumThisLine >= barsperstaff) { |
|
|
|
|
|
|
|
|
if (n < voice.length - 1) { |
|
|
var nextLine = getNextMusicLine(lines, i); |
|
|
if (!nextLine) { |
|
|
var cp = JSON.parse(JSON.stringify(lines[i])); |
|
|
lines.push(Object.assign({}, cp)); |
|
|
nextLine = lines[lines.length - 1]; |
|
|
for (var ss = 0; ss < nextLine.staff.length; ss++) { |
|
|
for (var vv = 0; vv < nextLine.staff[ss].voices.length; vv++) |
|
|
nextLine.staff[ss].voices[vv] = []; |
|
|
} |
|
|
} |
|
|
var startElement = n + 1; |
|
|
var section = lines[i].staff[s].voices[v].slice(startElement); |
|
|
lines[i].staff[s].voices[v] = lines[i].staff[s].voices[v].slice(0, startElement); |
|
|
nextLine.staff[s].voices[v] = permanentItems.concat(section.concat(nextLine.staff[s].voices[v])); |
|
|
return true; |
|
|
} |
|
|
} |
|
|
} else if (!voice[n].duration) { |
|
|
permanentItems.push(voice[n]); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
return false; |
|
|
} |
|
|
|
|
|
function getNextMusicLine(lines, currentLine) { |
|
|
currentLine++; |
|
|
while (lines.length > currentLine) { |
|
|
if (lines[currentLine].staff) |
|
|
return lines[currentLine]; |
|
|
currentLine++; |
|
|
} |
|
|
return null; |
|
|
} |
|
|
|
|
|
function getLastNote(tune) { |
|
|
if (!tune.lines[tune.lineNum]) return null |
|
|
if (!tune.lines[tune.lineNum].staff) return null |
|
|
if (!tune.lines[tune.lineNum].staff[tune.staffNum]) return null |
|
|
var voice = tune.lines[tune.lineNum].staff[tune.staffNum].voices[tune.voiceNum] |
|
|
if (!voice) return null |
|
|
for (var i = voice.length - 1; i >= 0; i--) { |
|
|
var el = voice[i]; |
|
|
if (el.el_type === 'note') { |
|
|
return el; |
|
|
} |
|
|
} |
|
|
return null; |
|
|
}; |
|
|
|
|
|
function getDuration(el) { |
|
|
if (el.duration) return el.duration; |
|
|
return 0; |
|
|
}; |
|
|
|
|
|
function closeLine(tune) { |
|
|
if (tune.potentialStartBeam && tune.potentialEndBeam) { |
|
|
tune.potentialStartBeam.startBeam = true; |
|
|
tune.potentialEndBeam.endBeam = true; |
|
|
} |
|
|
delete tune.potentialStartBeam; |
|
|
delete tune.potentialEndBeam; |
|
|
}; |
|
|
|
|
|
function containsNotes(voice) { |
|
|
for (var i = 0; i < voice.length; i++) { |
|
|
if (voice[i].el_type === 'note' || voice[i].el_type === 'bar') |
|
|
return true; |
|
|
} |
|
|
return false; |
|
|
}; |
|
|
|
|
|
function containsNotesStrict(voice) { |
|
|
for (var i = 0; i < voice.length; i++) { |
|
|
if (voice[i].el_type === 'note' && (voice[i].rest === undefined || voice[i].chord !== undefined)) |
|
|
return true; |
|
|
} |
|
|
return false; |
|
|
}; |
|
|
|
|
|
function pushLine(tune, hash) { |
|
|
if (tune.vskipPending) { |
|
|
hash.vskip = tune.vskipPending; |
|
|
delete tune.vskipPending; |
|
|
} |
|
|
tune.lines.push(hash); |
|
|
}; |
|
|
|
|
|
function pushNote(self, tune, hp, voiceDefs, currentVoiceName) { |
|
|
|
|
|
var currStaff = tune.lines[tune.lineNum].staff[tune.staffNum]; |
|
|
|
|
|
if (hp.pitches !== undefined) { |
|
|
var mid = currStaff.workingClef.verticalPos; |
|
|
hp.pitches.forEach(function (p) { p.verticalPos = p.pitch - mid; }); |
|
|
} |
|
|
if (hp.gracenotes !== undefined) { |
|
|
var mid2 = currStaff.workingClef.verticalPos; |
|
|
hp.gracenotes.forEach(function (p) { p.verticalPos = p.pitch - mid2; }); |
|
|
} |
|
|
if (currStaff.voices.length <= tune.voiceNum) { |
|
|
|
|
|
if (!voiceDefs[currentVoiceName]) |
|
|
voiceDefs[currentVoiceName] = {} |
|
|
createVoice(self, tune, voiceDefs[currentVoiceName]) |
|
|
} |
|
|
currStaff.voices[tune.voiceNum].push(hp); |
|
|
} |
|
|
|
|
|
function endBeamHere(hashParams, tune) { |
|
|
tune.potentialStartBeam.startBeam = true; |
|
|
hashParams.endBeam = true; |
|
|
delete tune.potentialStartBeam; |
|
|
delete tune.potentialEndBeam; |
|
|
} |
|
|
function endBeamLast(tune) { |
|
|
if (tune.potentialStartBeam !== undefined && tune.potentialEndBeam !== undefined) { |
|
|
tune.potentialStartBeam.startBeam = true; |
|
|
tune.potentialEndBeam.endBeam = true; |
|
|
} |
|
|
delete tune.potentialStartBeam; |
|
|
delete tune.potentialEndBeam; |
|
|
} |
|
|
|
|
|
function setLineFont(tune, type, font) { |
|
|
|
|
|
|
|
|
if (tune.runningFonts[type]) { |
|
|
var isDifferent = false; |
|
|
var keys = Object.keys(font); |
|
|
for (var i = 0; i < keys.length; i++) { |
|
|
if (tune.runningFonts[type][keys[i]] !== font[keys[i]]) |
|
|
isDifferent = true; |
|
|
} |
|
|
if (isDifferent) { |
|
|
tune.lines[tune.lineNum].staff[tune.staffNum][type] = font; |
|
|
} |
|
|
} |
|
|
tune.runningFonts[type] = font; |
|
|
} |
|
|
|
|
|
function createVoice(self, tune, params) { |
|
|
|
|
|
var thisStaff = tune.lines[tune.lineNum].staff[tune.staffNum]; |
|
|
thisStaff.voices[tune.voiceNum] = []; |
|
|
if (!thisStaff.title) |
|
|
thisStaff.title = []; |
|
|
thisStaff.title[tune.voiceNum] = { name: params.name, subname: params.subname }; |
|
|
if (params.style) |
|
|
self.appendElement('style', null, null, { head: params.style }); |
|
|
if (params.stem) |
|
|
self.appendElement('stem', null, null, { direction: params.stem }); |
|
|
else if (tune.voiceNum > 0) { |
|
|
if (thisStaff.voices[0] !== undefined) { |
|
|
var found = false; |
|
|
for (var i = 0; i < thisStaff.voices[0].length; i++) { |
|
|
if (thisStaff.voices[0].el_type === 'stem') |
|
|
found = true; |
|
|
} |
|
|
if (!found) { |
|
|
var stem = { el_type: 'stem', direction: 'up' }; |
|
|
thisStaff.voices[0].splice(0, 0, stem); |
|
|
} |
|
|
} |
|
|
self.appendElement('stem', null, null, { direction: 'down' }); |
|
|
} |
|
|
if (params.scale) |
|
|
self.appendElement('scale', null, null, { size: params.scale }); |
|
|
if (params.color) |
|
|
self.appendElement('color', null, null, { color: params.color }); |
|
|
} |
|
|
|
|
|
function createStaff(self, tune, params) { |
|
|
if (params.key && params.key.impliedNaturals) { |
|
|
params.key.accidentals = params.key.accidentals.concat(params.key.impliedNaturals); |
|
|
delete params.key.impliedNaturals; |
|
|
} |
|
|
|
|
|
tune.lines[tune.lineNum].staff[tune.staffNum] = { voices: [], clef: params.clef, key: params.key, workingClef: params.clef }; |
|
|
var staff = tune.lines[tune.lineNum].staff[tune.staffNum] |
|
|
if (params.stafflines !== undefined) { |
|
|
staff.clef.stafflines = params.stafflines; |
|
|
staff.workingClef.stafflines = params.stafflines; |
|
|
} |
|
|
if (params.staffscale) { |
|
|
staff.staffscale = params.staffscale; |
|
|
} |
|
|
if (params.annotationfont) setLineFont(tune, "annotationfont", params.annotationfont); |
|
|
if (params.gchordfont) setLineFont(tune, "gchordfont", params.gchordfont); |
|
|
if (params.tripletfont) setLineFont(tune, "tripletfont", params.tripletfont); |
|
|
if (params.vocalfont) setLineFont(tune, "vocalfont", params.vocalfont); |
|
|
if (params.bracket) staff.bracket = params.bracket; |
|
|
if (params.brace) staff.brace = params.brace; |
|
|
if (params.connectBarLines) staff.connectBarLines = params.connectBarLines; |
|
|
if (params.barNumber) staff.barNumber = params.barNumber; |
|
|
createVoice(self, tune, params); |
|
|
|
|
|
if (params.part) |
|
|
self.appendElement('part', params.part.startChar, params.part.endChar, { title: params.part.title }); |
|
|
if (params.meter !== undefined) staff.meter = params.meter; |
|
|
if (tune.vskipPending) { |
|
|
tune.lines[tune.lineNum].vskip = tune.vskipPending; |
|
|
delete tune.vskipPending; |
|
|
} |
|
|
} |
|
|
|
|
|
function createLine(self, tune, params) { |
|
|
tune.lines[tune.lineNum] = { staff: [] }; |
|
|
createStaff(self, tune, params); |
|
|
} |
|
|
|
|
|
module.exports = TuneBuilder; |
|
|
|