|
|
|
|
|
|
|
|
var parseCommon = require('../parse/abc_common'); |
|
|
var spacing = require('../write/helpers/spacing'); |
|
|
var sequence = require('../synth/abc_midi_sequencer'); |
|
|
var flatten = require('../synth/abc_midi_flattener'); |
|
|
var delineTune = require("./deline-tune"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var Tune = function() { |
|
|
this.reset = function () { |
|
|
this.version = "1.1.0"; |
|
|
this.media = "screen"; |
|
|
this.metaText = {}; |
|
|
this.metaTextInfo = {}; |
|
|
this.formatting = {}; |
|
|
this.lines = []; |
|
|
this.staffNum = 0; |
|
|
this.voiceNum = 0; |
|
|
this.lineNum = 0; |
|
|
this.runningFonts = {}; |
|
|
delete this.visualTranspose; |
|
|
}; |
|
|
this.reset(); |
|
|
|
|
|
function copy(dest, src, prop, attrs) { |
|
|
for (var i = 0; i < attrs.length; i++) |
|
|
dest[prop][attrs[i]] = src[prop][attrs[i]]; |
|
|
} |
|
|
|
|
|
this.copyTopInfo = function(src) { |
|
|
var attrs = ['tempo', 'title', 'header', 'rhythm', 'origin', 'composer', 'author', 'partOrder']; |
|
|
copy(this, src, "metaText", attrs); |
|
|
copy(this, src, "metaTextInfo", attrs); |
|
|
}; |
|
|
|
|
|
this.copyBottomInfo = function(src) { |
|
|
var attrs = ['unalignedWords', |
|
|
'book', |
|
|
'source', |
|
|
'discography', |
|
|
'notes', |
|
|
'transcription', |
|
|
'history', |
|
|
'abc-copyright', |
|
|
'abc-creator', |
|
|
'abc-edited-by', |
|
|
'footer'] |
|
|
copy(this, src, "metaText", attrs); |
|
|
copy(this, src, "metaTextInfo", attrs); |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.getBeatLength = function() { |
|
|
|
|
|
|
|
|
var meter = this.getMeterFraction(); |
|
|
var multiplier = 1; |
|
|
if (meter.num === 6 || meter.num === 9 || meter.num === 12) |
|
|
multiplier = 3; |
|
|
else if (meter.num === 3 && meter.den === 8) |
|
|
multiplier = 3; |
|
|
|
|
|
return multiplier / meter.den; |
|
|
}; |
|
|
|
|
|
function computePickupLength(lines, barLength) { |
|
|
var pickupLength = 0; |
|
|
for (var i = 0; i < lines.length; i++) { |
|
|
if (lines[i].staff) { |
|
|
for (var j = 0; j < lines[i].staff.length; j++) { |
|
|
for (var v = 0; v < lines[i].staff[j].voices.length; v++) { |
|
|
var voice = lines[i].staff[j].voices[v]; |
|
|
var tripletMultiplier = 1; |
|
|
for (var el = 0; el < voice.length; el++) { |
|
|
var isSpacer = voice[el].rest && voice[el].rest.type === "spacer"; |
|
|
if (voice[el].startTriplet) |
|
|
tripletMultiplier = voice[el].tripletMultiplier; |
|
|
if (voice[el].duration && !isSpacer && voice[el].el_type !== "tempo") |
|
|
pickupLength += voice[el].duration * tripletMultiplier; |
|
|
if (voice[el].endTriplet) |
|
|
tripletMultiplier = 1; |
|
|
if (pickupLength >= barLength) |
|
|
pickupLength -= barLength; |
|
|
if (voice[el].el_type === 'bar') |
|
|
return pickupLength; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
return pickupLength; |
|
|
} |
|
|
this.getPickupLength = function() { |
|
|
var barLength = this.getBarLength(); |
|
|
var pickupLength = computePickupLength(this.lines, barLength); |
|
|
|
|
|
|
|
|
|
|
|
return (pickupLength < 1e-8 || barLength-pickupLength < 1e-8) ? 0 : pickupLength; |
|
|
}; |
|
|
|
|
|
this.getBarLength = function() { |
|
|
var meter = this.getMeterFraction(); |
|
|
return meter.num / meter.den; |
|
|
}; |
|
|
|
|
|
this.getTotalTime = function() { |
|
|
return this.totalTime; |
|
|
}; |
|
|
|
|
|
this.getTotalBeats = function() { |
|
|
return this.totalBeats; |
|
|
}; |
|
|
|
|
|
this.millisecondsPerMeasure = function(bpmOverride) { |
|
|
var bpm; |
|
|
if (bpmOverride) { |
|
|
bpm = bpmOverride; |
|
|
} else { |
|
|
var tempo = this.metaText ? this.metaText.tempo : null; |
|
|
bpm = this.getBpm(tempo); |
|
|
} |
|
|
if (bpm <= 0) |
|
|
bpm = 1; |
|
|
|
|
|
var beatsPerMeasure = this.getBeatsPerMeasure(); |
|
|
|
|
|
var minutesPerMeasure = beatsPerMeasure / bpm; |
|
|
return minutesPerMeasure * 60000; |
|
|
}; |
|
|
|
|
|
this.getBeatsPerMeasure = function() { |
|
|
var beatLen = this.getBeatLength(); |
|
|
var barLen = this.getBarLength(); |
|
|
return barLen / beatLen; |
|
|
}; |
|
|
|
|
|
this.getMeter = function() { |
|
|
for (var i = 0; i < this.lines.length; i++) { |
|
|
var line = this.lines[i]; |
|
|
if (line.staff) { |
|
|
for (var j = 0; j < line.staff.length; j++) { |
|
|
var meter = line.staff[j].meter; |
|
|
if (meter) { |
|
|
return meter; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
return { type: "common_time" }; |
|
|
}; |
|
|
|
|
|
this.getMeterFraction = function() { |
|
|
var meter = this.getMeter(); |
|
|
var num = 4; |
|
|
var den = 4; |
|
|
if (meter) { |
|
|
if (meter.type === 'specified') { |
|
|
num = parseInt(meter.value[0].num, 10); |
|
|
den = parseInt(meter.value[0].den,10); |
|
|
} else if (meter.type === 'cut_time') { |
|
|
num = 2; |
|
|
den = 2; |
|
|
} else if (meter.type === 'common_time') { |
|
|
num = 4; |
|
|
den = 4; |
|
|
} |
|
|
} |
|
|
this.meter = { num: num, den: den }; |
|
|
return this.meter; |
|
|
}; |
|
|
|
|
|
this.getKeySignature = function() { |
|
|
for (var i = 0; i < this.lines.length; i++) { |
|
|
var line = this.lines[i]; |
|
|
if (line.staff) { |
|
|
for (var j = 0; j < line.staff.length; j++) { |
|
|
if (line.staff[j].key) |
|
|
return line.staff[j].key; |
|
|
} |
|
|
} |
|
|
} |
|
|
return { }; |
|
|
}; |
|
|
|
|
|
this.getElementFromChar = function(char) { |
|
|
for (var i = 0; i < this.lines.length; i++) { |
|
|
var line = this.lines[i]; |
|
|
if (line.staff) { |
|
|
for (var j = 0; j < line.staff.length; j++) { |
|
|
var staff = line.staff[j]; |
|
|
for (var k = 0; k < staff.voices.length; k++) { |
|
|
var voice = staff.voices[k]; |
|
|
for (var ii = 0; ii < voice.length; ii++) { |
|
|
var elem = voice[ii]; |
|
|
if (elem.startChar && elem.endChar && |
|
|
elem.startChar <= char && elem.endChar > char) |
|
|
return elem; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
return null; |
|
|
}; |
|
|
|
|
|
function addVerticalInfo(timingEvents) { |
|
|
|
|
|
var lastBarTop; |
|
|
var lastBarBottom; |
|
|
var lastEventTop; |
|
|
var lastEventBottom; |
|
|
for (var e = timingEvents.length - 1; e >= 0; e--) { |
|
|
var ev = timingEvents[e]; |
|
|
if (ev.type === 'bar') { |
|
|
ev.top = lastEventTop; |
|
|
ev.nextTop = lastBarTop; |
|
|
lastBarTop = lastEventTop; |
|
|
|
|
|
ev.bottom = lastEventBottom; |
|
|
ev.nextBottom = lastBarBottom; |
|
|
lastBarBottom = lastEventBottom; |
|
|
} else if (ev.type === 'event') { |
|
|
lastEventTop = ev.top; |
|
|
lastEventBottom = ev.top + ev.height; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
function makeSortedArray(hash) { |
|
|
var arr = []; |
|
|
for (var k in hash) { |
|
|
if (hash.hasOwnProperty(k)) |
|
|
arr.push(hash[k]); |
|
|
} |
|
|
arr = arr.sort(function (a, b) { |
|
|
var diff = a.milliseconds - b.milliseconds; |
|
|
|
|
|
if (diff !== 0) { |
|
|
return diff; |
|
|
} |
|
|
else { |
|
|
return a.type === "bar" ? -1 : 1; |
|
|
} |
|
|
}); |
|
|
return arr; |
|
|
} |
|
|
|
|
|
this.addElementToEvents = function(eventHash, element, voiceTimeMilliseconds, top, height, line, measureNumber, timeDivider, isTiedState, nextIsBar) { |
|
|
if (element.hint) |
|
|
return { isTiedState: undefined, duration: 0 }; |
|
|
var realDuration = element.durationClass ? element.durationClass : element.duration; |
|
|
if (element.abcelem.rest && element.abcelem.rest.type === "spacer") |
|
|
realDuration = 0; |
|
|
if (realDuration > 0) { |
|
|
var es = []; |
|
|
|
|
|
for (var i = 0; i < element.elemset.length; i++) { |
|
|
if (element.elemset[i] !== null) |
|
|
es.push(element.elemset[i]); |
|
|
} |
|
|
var isTiedToNext = element.startTie; |
|
|
if (isTiedState !== undefined) { |
|
|
eventHash["event" + isTiedState].elements.push(es); |
|
|
if (nextIsBar) { |
|
|
if (!eventHash["event" + voiceTimeMilliseconds]) { |
|
|
eventHash["event" + voiceTimeMilliseconds] = { |
|
|
type: "event", |
|
|
milliseconds: voiceTimeMilliseconds, |
|
|
line: line, |
|
|
measureNumber: measureNumber, |
|
|
top: top, |
|
|
height: height, |
|
|
left: null, |
|
|
width: 0, |
|
|
elements: [], |
|
|
startChar: null, |
|
|
endChar: null, |
|
|
startCharArray: [], |
|
|
endCharArray: [] |
|
|
}; |
|
|
} |
|
|
eventHash["event" + voiceTimeMilliseconds].measureStart = true; |
|
|
nextIsBar = false; |
|
|
} |
|
|
if (!isTiedToNext) |
|
|
isTiedState = undefined; |
|
|
} else { |
|
|
|
|
|
if (!eventHash["event" + voiceTimeMilliseconds]) { |
|
|
eventHash["event" + voiceTimeMilliseconds] = { |
|
|
type: "event", |
|
|
milliseconds: voiceTimeMilliseconds, |
|
|
line: line, |
|
|
measureNumber: measureNumber, |
|
|
top: top, |
|
|
height: height, |
|
|
left: element.x, |
|
|
width: element.w, |
|
|
elements: [es], |
|
|
startChar: element.abcelem.startChar, |
|
|
endChar: element.abcelem.endChar, |
|
|
startCharArray: [element.abcelem.startChar], |
|
|
endCharArray: [element.abcelem.endChar], |
|
|
midiPitches: element.abcelem.midiPitches ? parseCommon.cloneArray(element.abcelem.midiPitches) : [] |
|
|
}; |
|
|
if (element.abcelem.midiGraceNotePitches) |
|
|
eventHash["event" + voiceTimeMilliseconds].midiGraceNotePitches = parseCommon.cloneArray(element.abcelem.midiGraceNotePitches); |
|
|
} else { |
|
|
|
|
|
if (eventHash["event" + voiceTimeMilliseconds].left) |
|
|
eventHash["event" + voiceTimeMilliseconds].left = Math.min(eventHash["event" + voiceTimeMilliseconds].left, element.x); |
|
|
else |
|
|
eventHash["event" + voiceTimeMilliseconds].left = element.x; |
|
|
eventHash["event" + voiceTimeMilliseconds].elements.push(es); |
|
|
eventHash["event" + voiceTimeMilliseconds].startCharArray.push(element.abcelem.startChar); |
|
|
eventHash["event" + voiceTimeMilliseconds].endCharArray.push(element.abcelem.endChar); |
|
|
if (eventHash["event" + voiceTimeMilliseconds].startChar === null) |
|
|
eventHash["event" + voiceTimeMilliseconds].startChar =element.abcelem.startChar; |
|
|
if (eventHash["event" + voiceTimeMilliseconds].endChar === null) |
|
|
eventHash["event" + voiceTimeMilliseconds].endChar =element.abcelem.endChar; |
|
|
if (element.abcelem.midiPitches && element.abcelem.midiPitches.length) { |
|
|
if (!eventHash["event" + voiceTimeMilliseconds].midiPitches) |
|
|
eventHash["event" + voiceTimeMilliseconds].midiPitches = []; |
|
|
for (var i = 0; i < element.abcelem.midiPitches.length; i++) |
|
|
eventHash["event" + voiceTimeMilliseconds].midiPitches.push(element.abcelem.midiPitches[i]); |
|
|
} |
|
|
if (element.abcelem.midiGraceNotePitches && element.abcelem.midiGraceNotePitches.length) { |
|
|
if (!eventHash["event" + voiceTimeMilliseconds].midiGraceNotePitches) |
|
|
eventHash["event" + voiceTimeMilliseconds].midiGraceNotePitches = []; |
|
|
for (var j = 0; j < element.abcelem.midiGraceNotePitches.length; j++) |
|
|
eventHash["event" + voiceTimeMilliseconds].midiGraceNotePitches.push(element.abcelem.midiGraceNotePitches[j]); |
|
|
} |
|
|
} |
|
|
if (nextIsBar) { |
|
|
eventHash["event" + voiceTimeMilliseconds].measureStart = true; |
|
|
nextIsBar = false; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
} |
|
|
return { isTiedState: isTiedState, duration: realDuration / timeDivider, nextIsBar: nextIsBar || element.type === 'bar' }; |
|
|
}; |
|
|
|
|
|
this.makeVoicesArray = function() { |
|
|
|
|
|
var voicesArr = []; |
|
|
var measureNumber = []; |
|
|
var tempos = {}; |
|
|
for (var line = 0; line < this.engraver.staffgroups.length; line++) { |
|
|
var group = this.engraver.staffgroups[line]; |
|
|
if (group && group.staffs && group.staffs.length > 0) { |
|
|
var firstStaff = group.staffs[0]; |
|
|
var middleC = firstStaff.absoluteY; |
|
|
var top = middleC - firstStaff.top * spacing.STEP; |
|
|
var lastStaff = group.staffs[group.staffs.length - 1]; |
|
|
middleC = lastStaff.absoluteY; |
|
|
var bottom = middleC - lastStaff.bottom * spacing.STEP; |
|
|
var height = bottom - top; |
|
|
|
|
|
var voices = group.voices; |
|
|
for (var v = 0; v < voices.length; v++) { |
|
|
if (voices[v].staff && voices[v].staff.isTabStaff) |
|
|
continue; |
|
|
var noteFound = false; |
|
|
if (!voicesArr[v]) |
|
|
voicesArr[v] = []; |
|
|
if (measureNumber[v] === undefined) |
|
|
measureNumber[v] = 0; |
|
|
var elements = voices[v].children; |
|
|
for (var elem = 0; elem < elements.length; elem++) { |
|
|
if (elements[elem].type === "tempo") |
|
|
tempos[measureNumber[v]] = this.getBpm(elements[elem].abcelem); |
|
|
voicesArr[v].push({top: top, height: height, line: group.line, measureNumber: measureNumber[v], elem: elements[elem]}); |
|
|
if (elements[elem].type === 'bar' && noteFound) |
|
|
measureNumber[v]++; |
|
|
if (elements[elem].type === 'note' || elements[elem].type === 'rest') |
|
|
noteFound = true; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
this.tempoLocations = tempos; |
|
|
return voicesArr; |
|
|
}; |
|
|
|
|
|
this.setupEvents = function(startingDelay, timeDivider, startingBpm, warp) { |
|
|
if (!warp) warp = 1; |
|
|
var timingEvents = []; |
|
|
|
|
|
var eventHash = {}; |
|
|
|
|
|
|
|
|
var time = startingDelay; |
|
|
var isTiedState; |
|
|
var nextIsBar = true; |
|
|
var voices = this.makeVoicesArray(); |
|
|
var maxVoiceTimeMilliseconds = 0; |
|
|
for (var v = 0; v < voices.length; v++) { |
|
|
var voiceTime = time; |
|
|
var voiceTimeMilliseconds = Math.round(voiceTime * 1000); |
|
|
var startingRepeatElem = 0; |
|
|
var endingRepeatElem = -1; |
|
|
var elements = voices[v]; |
|
|
var bpm = startingBpm; |
|
|
timeDivider = this.getBeatLength() * bpm / 60; |
|
|
var tempoDone = -1; |
|
|
for (var elem = 0; elem < elements.length; elem++) { |
|
|
var thisMeasure = elements[elem].measureNumber; |
|
|
if (tempoDone !== thisMeasure && this.tempoLocations[thisMeasure]) { |
|
|
bpm = this.tempoLocations[thisMeasure]; |
|
|
timeDivider = warp * this.getBeatLength() * bpm / 60; |
|
|
tempoDone = thisMeasure; |
|
|
} |
|
|
var element = elements[elem].elem; |
|
|
var ret = this.addElementToEvents(eventHash, element, voiceTimeMilliseconds, elements[elem].top, elements[elem].height, elements[elem].line, elements[elem].measureNumber, timeDivider, isTiedState, nextIsBar); |
|
|
isTiedState = ret.isTiedState; |
|
|
nextIsBar = ret.nextIsBar; |
|
|
voiceTime += ret.duration; |
|
|
var lastHash; |
|
|
if (element.duration > 0 && eventHash["event" + voiceTimeMilliseconds]) |
|
|
lastHash = "event" + voiceTimeMilliseconds; |
|
|
voiceTimeMilliseconds = Math.round(voiceTime * 1000); |
|
|
if (element.type === 'bar') { |
|
|
var barType = element.abcelem.type; |
|
|
var endRepeat = (barType === "bar_right_repeat" || barType === "bar_dbl_repeat"); |
|
|
var startEnding = (element.abcelem.startEnding === '1'); |
|
|
var startRepeat = (barType === "bar_left_repeat" || barType === "bar_dbl_repeat" || barType === "bar_right_repeat"); |
|
|
if (endRepeat) { |
|
|
|
|
|
if (elem > 0) { |
|
|
eventHash[lastHash].endX = element.x; |
|
|
} |
|
|
|
|
|
if (endingRepeatElem === -1) |
|
|
endingRepeatElem = elem; |
|
|
var lastVoiceTimeMilliseconds = 0; |
|
|
tempoDone = -1; |
|
|
for (var el2 = startingRepeatElem; el2 < endingRepeatElem; el2++) { |
|
|
thisMeasure = elements[el2].measureNumber; |
|
|
if (tempoDone !== thisMeasure && this.tempoLocations[thisMeasure]) { |
|
|
bpm = this.tempoLocations[thisMeasure]; |
|
|
timeDivider = warp * this.getBeatLength() * bpm / 60; |
|
|
tempoDone = thisMeasure; |
|
|
} |
|
|
var element2 = elements[el2].elem; |
|
|
ret = this.addElementToEvents(eventHash, element2, voiceTimeMilliseconds, elements[el2].top, elements[el2].height, elements[el2].line, elements[el2].measureNumber, timeDivider, isTiedState, nextIsBar); |
|
|
isTiedState = ret.isTiedState; |
|
|
nextIsBar = ret.nextIsBar; |
|
|
voiceTime += ret.duration; |
|
|
lastVoiceTimeMilliseconds = voiceTimeMilliseconds; |
|
|
voiceTimeMilliseconds = Math.round(voiceTime * 1000); |
|
|
} |
|
|
if (eventHash["event" + lastVoiceTimeMilliseconds]) |
|
|
eventHash["event" + lastVoiceTimeMilliseconds].endX = elements[endingRepeatElem].elem.x; |
|
|
nextIsBar = true; |
|
|
endingRepeatElem = -1; |
|
|
} |
|
|
if (startEnding) |
|
|
endingRepeatElem = elem; |
|
|
if (startRepeat) |
|
|
startingRepeatElem = elem; |
|
|
} |
|
|
} |
|
|
maxVoiceTimeMilliseconds = Math.max(maxVoiceTimeMilliseconds, voiceTimeMilliseconds) |
|
|
} |
|
|
|
|
|
timingEvents = makeSortedArray(eventHash); |
|
|
addVerticalInfo(timingEvents); |
|
|
addEndPoints(this.lines, timingEvents) |
|
|
timingEvents.push({ type: "end", milliseconds: maxVoiceTimeMilliseconds }); |
|
|
this.addUsefulCallbackInfo(timingEvents, bpm*warp); |
|
|
return timingEvents; |
|
|
}; |
|
|
|
|
|
this.addUsefulCallbackInfo = function(timingEvents, bpm) { |
|
|
var millisecondsPerMeasure = this.millisecondsPerMeasure(bpm); |
|
|
for (var i = 0; i < timingEvents.length; i++) { |
|
|
var ev = timingEvents[i]; |
|
|
ev.millisecondsPerMeasure = millisecondsPerMeasure; |
|
|
} |
|
|
}; |
|
|
|
|
|
function skipTies(elements, index) { |
|
|
while (index < elements.length && elements[index].left === null) |
|
|
index++; |
|
|
return elements[index]; |
|
|
} |
|
|
function addEndPoints(lines, elements) { |
|
|
if (elements.length < 1) |
|
|
return; |
|
|
for (var i = 0; i < elements.length-1; i++) { |
|
|
var el = elements[i]; |
|
|
var next = skipTies(elements, i+1); |
|
|
if (el.left !== null) { |
|
|
|
|
|
var endX = (next && el.top === next.top) ? next.left : lines[el.line].staffGroup.w; |
|
|
|
|
|
|
|
|
|
|
|
if (el.endX !== undefined) { |
|
|
if (endX > el.left) |
|
|
el.endX = Math.min(el.endX, endX); |
|
|
} else |
|
|
el.endX = endX; |
|
|
} |
|
|
} |
|
|
var lastEl = elements[elements.length-1]; |
|
|
lastEl.endX = lines[lastEl.line].staffGroup.w; |
|
|
} |
|
|
|
|
|
this.getBpm = function(tempo) { |
|
|
var bpm; |
|
|
if (!tempo) |
|
|
tempo = this.metaText ? this.metaText.tempo : null; |
|
|
if (tempo) { |
|
|
bpm = tempo.bpm; |
|
|
var beatLength = this.getBeatLength(); |
|
|
var statedBeatLength = tempo.duration && tempo.duration.length > 0 ? tempo.duration[0] : beatLength; |
|
|
bpm = bpm * statedBeatLength / beatLength; |
|
|
} |
|
|
if (!bpm) { |
|
|
bpm = 180; |
|
|
|
|
|
var meter = this.getMeterFraction(); |
|
|
if (meter && meter.num !== 3 && (meter.num % 3 === 0)) { |
|
|
bpm = 120; |
|
|
} |
|
|
} |
|
|
return bpm; |
|
|
}; |
|
|
|
|
|
this.setTiming = function (bpm, measuresOfDelay) { |
|
|
measuresOfDelay = measuresOfDelay || 0; |
|
|
if (!this.engraver || !this.engraver.staffgroups) { |
|
|
console.log("setTiming cannot be called before the tune is drawn."); |
|
|
this.noteTimings = []; |
|
|
return this.noteTimings; |
|
|
} |
|
|
|
|
|
var tempo = this.metaText ? this.metaText.tempo : null; |
|
|
var naturalBpm = this.getBpm(tempo); |
|
|
var warp = 1; |
|
|
if (bpm) { |
|
|
if (tempo) |
|
|
warp = bpm / naturalBpm; |
|
|
} else |
|
|
bpm = naturalBpm; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var beatLength = this.getBeatLength(); |
|
|
var beatsPerSecond = bpm / 60; |
|
|
|
|
|
var measureLength = this.getBarLength(); |
|
|
|
|
|
var startingDelay = measureLength / beatLength * measuresOfDelay / beatsPerSecond; |
|
|
if (startingDelay) |
|
|
startingDelay -= this.getPickupLength() / beatLength / beatsPerSecond; |
|
|
var timeDivider = beatLength * beatsPerSecond; |
|
|
|
|
|
this.noteTimings = this.setupEvents(startingDelay, timeDivider, bpm, warp); |
|
|
if (this.noteTimings.length > 0) { |
|
|
this.totalTime = this.noteTimings[this.noteTimings.length - 1].milliseconds / 1000; |
|
|
this.totalBeats = this.totalTime * beatsPerSecond; |
|
|
} else { |
|
|
this.totalTime = undefined; |
|
|
this.totalBeats = undefined; |
|
|
} |
|
|
return this.noteTimings; |
|
|
}; |
|
|
|
|
|
this.setUpAudio = function(options) { |
|
|
if (!options) options = {}; |
|
|
var seq = sequence(this, options); |
|
|
return flatten(seq, options, this.formatting.percmap, this.formatting.midi); |
|
|
}; |
|
|
this.deline = function(options) { |
|
|
return delineTune(this.lines, options); |
|
|
} |
|
|
this.findSelectableElement = function(target) { |
|
|
if (this.engraver && this.engraver.selectables) |
|
|
return this.engraver.findSelectableElement(target) |
|
|
return null |
|
|
} |
|
|
this.getSelectableArray = function() { |
|
|
if (this.engraver && this.engraver.selectables) |
|
|
return this.engraver.selectables |
|
|
return [] |
|
|
} |
|
|
}; |
|
|
|
|
|
module.exports = Tune; |
|
|
|