|
|
|
|
|
|
|
|
var sequence; |
|
|
var parseCommon = require("../parse/abc_common"); |
|
|
|
|
|
(function() { |
|
|
"use strict"; |
|
|
|
|
|
var measureLength = 1; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var PERCUSSION_PROGRAM = 128; |
|
|
|
|
|
sequence = function(abctune, options) { |
|
|
|
|
|
options = options || {}; |
|
|
var qpm; |
|
|
var program = options.program || 0; |
|
|
var transpose = options.midiTranspose || 0; |
|
|
|
|
|
if (abctune.visualTranspose) |
|
|
transpose -= abctune.visualTranspose; |
|
|
var channel = options.channel || 0; |
|
|
var channelExplicitlySet = false; |
|
|
var drumPattern = options.drum || ""; |
|
|
var drumBars = options.drumBars || 1; |
|
|
var drumIntro = options.drumIntro || 0; |
|
|
var drumOn = drumPattern !== ""; |
|
|
var drumOffAfterIntro = !!options.drumOff |
|
|
var style = []; |
|
|
var rhythmHeadThisBar = false; |
|
|
var crescendoSize = 50; |
|
|
|
|
|
|
|
|
program = parseInt(program, 10); |
|
|
transpose = parseInt(transpose, 10); |
|
|
channel = parseInt(channel, 10); |
|
|
if (channel === 10) |
|
|
program = PERCUSSION_PROGRAM; |
|
|
drumPattern = drumPattern.split(" "); |
|
|
drumBars = parseInt(drumBars, 10); |
|
|
drumIntro = parseInt(drumIntro, 10); |
|
|
|
|
|
var bagpipes = abctune.formatting.bagpipes; |
|
|
if (bagpipes) |
|
|
program = 71; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var startingMidi = []; |
|
|
if (abctune.formatting.midi) { |
|
|
|
|
|
var globals = abctune.formatting.midi; |
|
|
if (globals.program && globals.program.length > 0) { |
|
|
program = globals.program[0]; |
|
|
if (globals.program.length > 1) { |
|
|
program = globals.program[1]; |
|
|
channel = globals.program[0]; |
|
|
} |
|
|
channelExplicitlySet = true; |
|
|
} |
|
|
if (globals.transpose) |
|
|
transpose = globals.transpose[0]; |
|
|
if (globals.channel) { |
|
|
channel = globals.channel[0]; |
|
|
channelExplicitlySet = true; |
|
|
} |
|
|
if (globals.drum) |
|
|
drumPattern = globals.drum; |
|
|
if (globals.drumbars) |
|
|
drumBars = globals.drumbars[0]; |
|
|
if (globals.drumon) |
|
|
drumOn = true; |
|
|
if (channel === 10) |
|
|
program = PERCUSSION_PROGRAM; |
|
|
if (globals.beat) |
|
|
startingMidi.push({ el_type: 'beat', beats: globals.beat }) |
|
|
if (globals.nobeataccents) |
|
|
startingMidi.push({ el_type: 'beataccents', value: false }); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (options.qpm) |
|
|
qpm = parseInt(options.qpm, 10); |
|
|
else if (abctune.metaText.tempo) |
|
|
qpm = interpretTempo(abctune.metaText.tempo, abctune.getBeatLength()); |
|
|
else if (options.defaultQpm) |
|
|
qpm = options.defaultQpm; |
|
|
else |
|
|
qpm = 180; |
|
|
|
|
|
var startVoice = []; |
|
|
if (bagpipes) |
|
|
startVoice.push({ el_type: 'bagpipes' }); |
|
|
startVoice.push({ el_type: 'instrument', program: program }); |
|
|
if (channel) |
|
|
startVoice.push({ el_type: 'channel', channel: channel }); |
|
|
if (transpose) |
|
|
startVoice.push({ el_type: 'transpose', transpose: transpose }); |
|
|
startVoice.push({ el_type: 'tempo', qpm: qpm }); |
|
|
for (var ss = 0; ss < startingMidi.length;ss++) |
|
|
startVoice.push(startingMidi[ss]); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var voices = []; |
|
|
var inCrescendo = []; |
|
|
var inDiminuendo = []; |
|
|
var durationCounter = [0]; |
|
|
var tempoChanges = {}; |
|
|
tempoChanges["0"] = { el_type: 'tempo', qpm: qpm, timing: 0 }; |
|
|
var currentVolume; |
|
|
var startRepeatPlaceholder = []; |
|
|
var skipEndingPlaceholder = []; |
|
|
var startingDrumSet = false; |
|
|
var lines = abctune.lines; |
|
|
for (var i = 0; i < lines.length; i++) { |
|
|
|
|
|
var line = lines[i]; |
|
|
if (line.staff) { |
|
|
var staves = line.staff; |
|
|
var voiceNumber = 0; |
|
|
for (var j = 0; j < staves.length; j++) { |
|
|
var staff = staves[j]; |
|
|
if (staff.clef && staff.clef.type === "TAB") |
|
|
continue; |
|
|
|
|
|
|
|
|
for (var k = 0; k < staff.voices.length; k++) { |
|
|
|
|
|
var voice = staff.voices[k]; |
|
|
if (!voices[voiceNumber]) { |
|
|
voices[voiceNumber] = [].concat(JSON.parse(JSON.stringify(startVoice))); |
|
|
var voiceName = getTrackTitle(line.staff, voiceNumber); |
|
|
if (voiceName) |
|
|
voices[voiceNumber].unshift({el_type: "name", trackName: voiceName}); |
|
|
} |
|
|
|
|
|
if (transpose && staff.clef.type === "perc") |
|
|
voices[voiceNumber].push({ el_type: 'transpose', transpose: 0 }); |
|
|
|
|
|
if (staff.clef && staff.clef.type === 'perc' && !channelExplicitlySet) { |
|
|
for (var cl = 0; cl < voices[voiceNumber].length; cl++) { |
|
|
if (voices[voiceNumber][cl].el_type === 'instrument') |
|
|
voices[voiceNumber][cl].program = PERCUSSION_PROGRAM; |
|
|
} |
|
|
} else if (staff.key) { |
|
|
addKey(voices[voiceNumber], staff.key); |
|
|
} |
|
|
if (staff.meter) { |
|
|
addMeter(voices[voiceNumber], staff.meter); |
|
|
} |
|
|
if (!startingDrumSet && drumOn) { |
|
|
voices[voiceNumber].push({el_type: 'drum', params: {pattern: drumPattern, bars: drumBars, on: drumOn, intro: drumIntro}}); |
|
|
startingDrumSet = true; |
|
|
} |
|
|
if (staff.clef && staff.clef.type !== "perc" && staff.clef.transpose) { |
|
|
staff.clef.el_type = 'clef'; |
|
|
voices[voiceNumber].push({ el_type: 'transpose', transpose: staff.clef.transpose }); |
|
|
} |
|
|
if (staff.clef && staff.clef.type) { |
|
|
if (staff.clef.type.indexOf("-8") >= 0) |
|
|
voices[voiceNumber].push({ el_type: 'transpose', transpose: -12 }); |
|
|
else if (staff.clef.type.indexOf("+8") >= 0) |
|
|
voices[voiceNumber].push({ el_type: 'transpose', transpose: 12 }); |
|
|
} |
|
|
|
|
|
if (abctune.formatting.midi && abctune.formatting.midi.drumoff) { |
|
|
|
|
|
|
|
|
voices[voiceNumber].push({ el_type: 'bar' }); |
|
|
voices[voiceNumber].push({el_type: 'drum', params: {pattern: "", on: false }}); |
|
|
} |
|
|
var noteEventsInBar = 0; |
|
|
var tripletMultiplier = 0; |
|
|
var tripletDurationTotal = 0; |
|
|
var tripletDurationCount = 0; |
|
|
currentVolume = [105, 95, 85, 1]; |
|
|
|
|
|
for (var v = 0; v < voice.length; v++) { |
|
|
|
|
|
var elem = voice[v]; |
|
|
switch (elem.el_type) { |
|
|
case "note": |
|
|
if (inCrescendo[k]) { |
|
|
currentVolume[0] += inCrescendo[k]; |
|
|
currentVolume[1] += inCrescendo[k]; |
|
|
currentVolume[2] += inCrescendo[k]; |
|
|
voices[voiceNumber].push({ el_type: 'beat', beats: currentVolume.slice(0) }); |
|
|
} |
|
|
|
|
|
if (inDiminuendo[k]) { |
|
|
currentVolume[0] += inDiminuendo[k]; |
|
|
currentVolume[1] += inDiminuendo[k]; |
|
|
currentVolume[2] += inDiminuendo[k]; |
|
|
voices[voiceNumber].push({ el_type: 'beat', beats: currentVolume.slice(0) }); |
|
|
} |
|
|
setDynamics(elem); |
|
|
|
|
|
|
|
|
if (!elem.rest || elem.rest.type !== 'spacer') { |
|
|
var noteElem = { elem: elem, el_type: "note", timing: durationCounter[voiceNumber] }; |
|
|
if (elem.style) |
|
|
noteElem.style = elem.style; |
|
|
else if (style[voiceNumber]) |
|
|
noteElem.style = style[voiceNumber]; |
|
|
noteElem.duration = (elem.duration === 0) ? 0.25 : elem.duration; |
|
|
if (elem.startTriplet) { |
|
|
tripletMultiplier = elem.tripletMultiplier; |
|
|
tripletDurationTotal = elem.startTriplet * tripletMultiplier * elem.duration; |
|
|
if (elem.startTriplet !== elem.tripletR) { |
|
|
if (v + elem.tripletR <= voice.length) { |
|
|
var durationTotal = 0; |
|
|
for (var w = v; w < v + elem.tripletR; w++) { |
|
|
durationTotal += voice[w].duration; |
|
|
} |
|
|
tripletDurationTotal = tripletMultiplier * durationTotal; |
|
|
} |
|
|
} |
|
|
noteElem.duration = noteElem.duration * tripletMultiplier; |
|
|
noteElem.duration = Math.round(noteElem.duration*1000000)/1000000; |
|
|
tripletDurationCount = noteElem.duration; |
|
|
} else if (tripletMultiplier) { |
|
|
if (elem.endTriplet) { |
|
|
tripletMultiplier = 0; |
|
|
noteElem.duration = Math.round((tripletDurationTotal - tripletDurationCount)*1000000)/1000000; |
|
|
} else { |
|
|
noteElem.duration = noteElem.duration * tripletMultiplier; |
|
|
noteElem.duration = Math.round(noteElem.duration*1000000)/1000000; |
|
|
tripletDurationCount += noteElem.duration; |
|
|
} |
|
|
} |
|
|
if (elem.rest) noteElem.rest = elem.rest; |
|
|
if (elem.decoration) noteElem.decoration = elem.decoration.slice(0); |
|
|
if (elem.pitches) noteElem.pitches = parseCommon.cloneArray(elem.pitches); |
|
|
if (elem.gracenotes) noteElem.gracenotes = parseCommon.cloneArray(elem.gracenotes); |
|
|
if (elem.chord) noteElem.chord = parseCommon.cloneArray(elem.chord); |
|
|
|
|
|
voices[voiceNumber].push(noteElem); |
|
|
if (elem.style === "rhythm") { |
|
|
rhythmHeadThisBar = true; |
|
|
chordVoiceOffThisBar(voices) |
|
|
} |
|
|
noteEventsInBar++; |
|
|
durationCounter[voiceNumber] += noteElem.duration; |
|
|
} |
|
|
break; |
|
|
case "key": |
|
|
case "keySignature": |
|
|
addKey(voices[voiceNumber], elem); |
|
|
break; |
|
|
case "meter": |
|
|
addMeter(voices[voiceNumber], elem); |
|
|
break; |
|
|
case "clef": |
|
|
if (elem.transpose) |
|
|
voices[voiceNumber].push({ el_type: 'transpose', transpose: elem.transpose }); |
|
|
if (elem.type) { |
|
|
if (elem.type.indexOf("-8") >= 0) |
|
|
voices[voiceNumber].push({ el_type: 'transpose', transpose: -12 }); |
|
|
else if (elem.type.indexOf("+8") >= 0) |
|
|
voices[voiceNumber].push({ el_type: 'transpose', transpose: 12 }); |
|
|
} |
|
|
break; |
|
|
case "tempo": |
|
|
qpm = interpretTempo(elem, abctune.getBeatLength()); |
|
|
voices[voiceNumber].push({ el_type: 'tempo', qpm: qpm, timing: durationCounter[voiceNumber] }); |
|
|
tempoChanges[''+durationCounter[voiceNumber]] = { el_type: 'tempo', qpm: qpm, timing: durationCounter[voiceNumber] }; |
|
|
break; |
|
|
case "bar": |
|
|
if (noteEventsInBar > 0) |
|
|
voices[voiceNumber].push({ el_type: 'bar' }); |
|
|
setDynamics(elem); |
|
|
noteEventsInBar = 0; |
|
|
|
|
|
|
|
|
var endRepeat = (elem.type === "bar_right_repeat" || elem.type === "bar_dbl_repeat"); |
|
|
var startEnding = (elem.startEnding === '1'); |
|
|
var startRepeat = (elem.type === "bar_left_repeat" || elem.type === "bar_dbl_repeat" || elem.type === "bar_right_repeat"); |
|
|
if (endRepeat) { |
|
|
var s = startRepeatPlaceholder[voiceNumber]; |
|
|
if (!s) s = 0; |
|
|
var e = skipEndingPlaceholder[voiceNumber]; |
|
|
if (!e) e = voices[voiceNumber].length; |
|
|
|
|
|
for (var z = s; z < e; z++) { |
|
|
var item = Object.assign({},voices[voiceNumber][z]); |
|
|
if (item.pitches) |
|
|
item.pitches = parseCommon.cloneArray(item.pitches); |
|
|
voices[voiceNumber].push(item); |
|
|
} |
|
|
|
|
|
skipEndingPlaceholder[voiceNumber] = undefined; |
|
|
startRepeatPlaceholder[voiceNumber] = undefined; |
|
|
} |
|
|
if (startEnding) |
|
|
skipEndingPlaceholder[voiceNumber] = voices[voiceNumber].length; |
|
|
if (startRepeat) |
|
|
startRepeatPlaceholder[voiceNumber] = voices[voiceNumber].length; |
|
|
rhythmHeadThisBar = false; |
|
|
break; |
|
|
case 'style': |
|
|
style[voiceNumber] = elem.head; |
|
|
break; |
|
|
case 'timeSignature': |
|
|
voices[voiceNumber].push(interpretMeter(elem)); |
|
|
break; |
|
|
case 'part': |
|
|
|
|
|
break; |
|
|
case 'stem': |
|
|
case 'scale': |
|
|
case 'break': |
|
|
case 'font': |
|
|
|
|
|
break; |
|
|
case 'midi': |
|
|
|
|
|
var drumChange = false; |
|
|
switch (elem.cmd) { |
|
|
case "drumon": drumOn = true; drumChange = true; break; |
|
|
case "drumoff": drumOn = false; drumChange = true; break; |
|
|
case "drum": drumPattern = elem.params; drumChange = true; break; |
|
|
case "drumbars": drumBars = elem.params[0]; drumChange = true; break; |
|
|
case "drummap": |
|
|
|
|
|
break; |
|
|
case "channel": |
|
|
|
|
|
if (elem.params[0] === 10) |
|
|
voices[voiceNumber].push({ el_type: 'instrument', program: PERCUSSION_PROGRAM }); |
|
|
break; |
|
|
case "program": |
|
|
addIfDifferent(voices[voiceNumber], { el_type: 'instrument', program: elem.params[0] }); |
|
|
channelExplicitlySet = true; |
|
|
break; |
|
|
case "transpose": |
|
|
voices[voiceNumber].push({ el_type: 'transpose', transpose: elem.params[0] }); |
|
|
break; |
|
|
case "gchordoff": |
|
|
voices[voiceNumber].push({ el_type: 'gchordOn', tacet: true }); |
|
|
break; |
|
|
case "gchordon": |
|
|
voices[voiceNumber].push({ el_type: 'gchordOn', tacet: false }); |
|
|
break; |
|
|
case "beat": |
|
|
voices[voiceNumber].push({ el_type: 'beat', beats: elem.params }); |
|
|
break; |
|
|
case "nobeataccents": |
|
|
voices[voiceNumber].push({ el_type: 'beataccents', value: false }); |
|
|
break; |
|
|
case "beataccents": |
|
|
voices[voiceNumber].push({ el_type: 'beataccents', value: true }); |
|
|
break; |
|
|
case "vol": |
|
|
case "volinc": |
|
|
voices[voiceNumber].push({ el_type: elem.cmd, volume: elem.params[0] }); |
|
|
break; |
|
|
case "swing": |
|
|
case "gchord": |
|
|
case "bassvol": |
|
|
case "chordvol": |
|
|
voices[voiceNumber].push({ el_type: elem.cmd, param: elem.params[0] }); |
|
|
break; |
|
|
|
|
|
case "bassprog": |
|
|
case "chordprog": |
|
|
voices[voiceNumber].push({ |
|
|
el_type: elem.cmd, |
|
|
value: elem.params[0], |
|
|
octaveShift: elem.params[1] |
|
|
}); |
|
|
break; |
|
|
|
|
|
|
|
|
case "gchordbars": |
|
|
voices[voiceNumber].push({ |
|
|
el_type: elem.cmd, |
|
|
param: elem.params[0] |
|
|
}); |
|
|
break; |
|
|
default: |
|
|
console.log("MIDI seq: midi cmd not handled: ", elem.cmd, elem); |
|
|
} |
|
|
if (drumChange) { |
|
|
voices[0].push({el_type: 'drum', params: { pattern: drumPattern, bars: drumBars, intro: drumIntro, on: drumOn}}); |
|
|
startingDrumSet = true; |
|
|
} |
|
|
break; |
|
|
default: |
|
|
console.log("MIDI: element type " + elem.el_type + " not handled."); |
|
|
} |
|
|
} |
|
|
voiceNumber++; |
|
|
if (!durationCounter[voiceNumber]) |
|
|
durationCounter[voiceNumber] = 0; |
|
|
} |
|
|
} |
|
|
|
|
|
function setDynamics(elem) { |
|
|
var volumes = { |
|
|
'pppp': [15, 10, 5, 1], |
|
|
'ppp': [30, 20, 10, 1], |
|
|
'pp': [45, 35, 20, 1], |
|
|
'p': [60, 50, 35, 1], |
|
|
'mp': [75, 65, 50, 1], |
|
|
'mf': [90, 80, 65, 1], |
|
|
'f': [105, 95, 80, 1], |
|
|
'ff': [120, 110, 95, 1], |
|
|
'fff': [127, 125, 110, 1], |
|
|
'ffff': [127, 125, 110, 1] |
|
|
}; |
|
|
|
|
|
var dynamicType; |
|
|
if (elem.decoration) { |
|
|
if (elem.decoration.indexOf('pppp') >= 0) |
|
|
dynamicType = 'pppp'; |
|
|
else if (elem.decoration.indexOf('ppp') >= 0) |
|
|
dynamicType = 'ppp'; |
|
|
else if (elem.decoration.indexOf('pp') >= 0) |
|
|
dynamicType = 'pp'; |
|
|
else if (elem.decoration.indexOf('p') >= 0) |
|
|
dynamicType = 'p'; |
|
|
else if (elem.decoration.indexOf('mp') >= 0) |
|
|
dynamicType = 'mp'; |
|
|
else if (elem.decoration.indexOf('mf') >= 0) |
|
|
dynamicType = 'mf'; |
|
|
else if (elem.decoration.indexOf('f') >= 0) |
|
|
dynamicType = 'f'; |
|
|
else if (elem.decoration.indexOf('ff') >= 0) |
|
|
dynamicType = 'ff'; |
|
|
else if (elem.decoration.indexOf('fff') >= 0) |
|
|
dynamicType = 'fff'; |
|
|
else if (elem.decoration.indexOf('ffff') >= 0) |
|
|
dynamicType = 'ffff'; |
|
|
|
|
|
if (dynamicType) { |
|
|
currentVolume = volumes[dynamicType].slice(0); |
|
|
voices[voiceNumber].push({ el_type: 'beat', beats: currentVolume.slice(0) }); |
|
|
inCrescendo[k] = false; |
|
|
inDiminuendo[k] = false; |
|
|
} |
|
|
|
|
|
if (elem.decoration.indexOf("crescendo(") >= 0) { |
|
|
var n = numNotesToDecoration(voice, v, "crescendo)"); |
|
|
var top = Math.min(127, currentVolume[0] + crescendoSize); |
|
|
var endDec = endingVolume(voice, v+n+1, Object.keys(volumes)); |
|
|
if (endDec) |
|
|
top = volumes[endDec][0]; |
|
|
if (n > 0) |
|
|
inCrescendo[k] = Math.floor((top - currentVolume[0]) / n); |
|
|
else |
|
|
inCrescendo[k] = false; |
|
|
inDiminuendo[k] = false; |
|
|
} else if (elem.decoration.indexOf("crescendo)") >= 0) { |
|
|
inCrescendo[k] = false; |
|
|
} else if (elem.decoration.indexOf("diminuendo(") >= 0) { |
|
|
var n2 = numNotesToDecoration(voice, v, "diminuendo)"); |
|
|
var bottom = Math.max(15, currentVolume[0] - crescendoSize); |
|
|
var endDec2 = endingVolume(voice, v+n2+1, Object.keys(volumes)); |
|
|
if (endDec2) |
|
|
bottom = volumes[endDec2][0]; |
|
|
inCrescendo[k] = false; |
|
|
if (n2 > 0) |
|
|
inDiminuendo[k] = Math.floor((bottom - currentVolume[0]) / n2); |
|
|
else |
|
|
inDiminuendo[k] = false; |
|
|
} else if (elem.decoration.indexOf("diminuendo)") >= 0) { |
|
|
inDiminuendo[k] = false; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
insertTempoChanges(voices, tempoChanges); |
|
|
|
|
|
if (drumIntro) { |
|
|
var pickups = abctune.getPickupLength(); |
|
|
|
|
|
for (var vv = 0; vv < voices.length; vv++) { |
|
|
var insertPoint = 0; |
|
|
while (voices[vv][insertPoint].el_type !== "note" && voices[vv].length > insertPoint) |
|
|
insertPoint++; |
|
|
if (voices[vv].length > insertPoint) { |
|
|
for (var w = 0; w < drumIntro; w++) { |
|
|
|
|
|
if (pickups === 0 || w < drumIntro-1) { |
|
|
voices[vv].splice(insertPoint, 0, |
|
|
{el_type: "note", rest: {type: "rest"}, duration: measureLength}, |
|
|
{ el_type: "bar" } |
|
|
); |
|
|
insertPoint += 2 |
|
|
} else { |
|
|
voices[vv].splice(insertPoint++, 0, {el_type: "note", rest: {type: "rest"}, duration: measureLength-pickups}); |
|
|
} |
|
|
} |
|
|
if (drumOffAfterIntro) { |
|
|
drumOn = false |
|
|
voices[vv].splice(insertPoint++, 0, {el_type: 'drum', params: { pattern: drumPattern, bars: drumBars, intro: drumIntro, on: drumOn}}); |
|
|
drumOffAfterIntro = false |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
if (voices.length > 0 && voices[0].length > 0) { |
|
|
voices[0][0].pickupLength = abctune.getPickupLength(); |
|
|
} |
|
|
return voices; |
|
|
}; |
|
|
|
|
|
function numNotesToDecoration(voice, start, decoration) { |
|
|
var counter = 0; |
|
|
for (var i = start+1; i < voice.length; i++) { |
|
|
if (voice[i].el_type === "note") |
|
|
counter++; |
|
|
if (voice[i].decoration && voice[i].decoration.indexOf(decoration) >= 0) |
|
|
return counter; |
|
|
} |
|
|
return counter; |
|
|
} |
|
|
function endingVolume(voice, start, volumeDecorations) { |
|
|
var end = Math.min(voice.length, start + 3); |
|
|
for (var i = start; i < end; i++) { |
|
|
if (voice[i].el_type === "note") { |
|
|
if (voice[i].decoration) { |
|
|
for (var j = 0; j < voice[i].decoration.length; j++) { |
|
|
if (volumeDecorations.indexOf(voice[i].decoration[j]) >= 0) |
|
|
return voice[i].decoration[j]; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
return null; |
|
|
} |
|
|
|
|
|
function insertTempoChanges(voices, tempoChanges) { |
|
|
if (!tempoChanges || tempoChanges.length === 0) |
|
|
return; |
|
|
var changePositions = Object.keys(tempoChanges); |
|
|
for (var i = 0; i < voices.length; i++) { |
|
|
var voice = voices[i]; |
|
|
var lastTempo = tempoChanges['0'] ? tempoChanges['0'].qpm : 0; |
|
|
for (var j = 0; j < voice.length; j++) { |
|
|
var el = voice[j]; |
|
|
if (el.el_type === "tempo") |
|
|
lastTempo = el.qpm; |
|
|
if (changePositions.indexOf(''+el.timing) >= 0 && lastTempo !== tempoChanges[''+el.timing].qpm) { |
|
|
lastTempo = tempoChanges[''+el.timing].qpm; |
|
|
if (el.el_type === "tempo") { |
|
|
el.qpm = tempoChanges[''+el.timing].qpm; |
|
|
j++; |
|
|
} else { |
|
|
|
|
|
voices[i].splice(j, 0, {el_type: "tempo", qpm: tempoChanges[''+el.timing].qpm, timing: el.timing}); |
|
|
j +=2; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
function chordVoiceOffThisBar(voices) { |
|
|
for (var i = 0; i < voices.length; i++) { |
|
|
var voice = voices[i]; |
|
|
var j = voice.length-1; |
|
|
while (j >= 0 && voice[j].el_type !== 'bar') { |
|
|
voice[j].noChordVoice = true; |
|
|
j--; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
function getTrackTitle(staff, voiceNumber) { |
|
|
if (!staff || staff.length <= voiceNumber || !staff[voiceNumber].title) |
|
|
return undefined; |
|
|
return staff[voiceNumber].title.join(" "); |
|
|
} |
|
|
|
|
|
function interpretTempo(element, beatLength) { |
|
|
var duration = 1/4; |
|
|
if (element.duration) { |
|
|
duration = element.duration[0]; |
|
|
} |
|
|
var bpm = 60; |
|
|
if (element.bpm) { |
|
|
bpm = element.bpm; |
|
|
} |
|
|
|
|
|
return duration * bpm / beatLength; |
|
|
} |
|
|
|
|
|
function interpretMeter(element) { |
|
|
var meter; |
|
|
switch (element.type) { |
|
|
case "common_time": |
|
|
meter = { el_type: 'meter', num: 4, den: 4 }; |
|
|
break; |
|
|
case "cut_time": |
|
|
meter = { el_type: 'meter', num: 2, den: 2 }; |
|
|
break; |
|
|
case "specified": |
|
|
|
|
|
meter = { el_type: 'meter', num: element.value[0].num, den: element.value[0].den }; |
|
|
break; |
|
|
default: |
|
|
|
|
|
meter = { el_type: 'meter' }; |
|
|
} |
|
|
measureLength = meter.num/meter.den; |
|
|
return meter; |
|
|
} |
|
|
|
|
|
function removeNaturals(accidentals) { |
|
|
var acc = []; |
|
|
for (var i = 0; i < accidentals.length; i++) { |
|
|
if (accidentals[i].acc !== "natural") |
|
|
acc.push(accidentals[i]) |
|
|
} |
|
|
return acc; |
|
|
} |
|
|
function addKey(arr, key) { |
|
|
var newKey; |
|
|
if (key.root === 'HP') |
|
|
newKey = {el_type: 'key', accidentals: [{acc: 'natural', note: 'g'}, {acc: 'sharp', note: 'f'}, {acc: 'sharp', note: 'c'}]}; |
|
|
else |
|
|
newKey = {el_type: 'key', accidentals: removeNaturals(key.accidentals) }; |
|
|
addIfDifferent(arr, newKey); |
|
|
} |
|
|
function addMeter(arr, meter) { |
|
|
var newMeter = interpretMeter(meter); |
|
|
addIfDifferent(arr, newMeter); |
|
|
} |
|
|
function addIfDifferent(arr, item) { |
|
|
for (var i = arr.length-1; i >= 0; i--) { |
|
|
if (arr[i].el_type === item.el_type) { |
|
|
if (JSON.stringify(arr[i]) !== JSON.stringify(item)) |
|
|
arr.push(item); |
|
|
return; |
|
|
} |
|
|
} |
|
|
arr.push(item); |
|
|
} |
|
|
|
|
|
})(); |
|
|
|
|
|
module.exports = sequence; |
|
|
|