|
|
describe("Timing", function() { |
|
|
var abcRepeatedSections = 'X:1\n' + |
|
|
'L:1/4\n' + |
|
|
'M:3/4\n' + |
|
|
'Q:1/4=60\n' + |
|
|
'K:Bb\n' + |
|
|
'CDE|:FG[Ab]|1 Bcd:|2 efg|]\n'; |
|
|
|
|
|
var expectedRepeatedSections = [ |
|
|
{ ms: 0, pitches: [60] }, |
|
|
{ ms: 1000, pitches: [62] }, |
|
|
{ ms: 2000, pitches: [63] }, |
|
|
{ bar: true }, |
|
|
{ ms: [3000,9000], pitches: [65] }, |
|
|
{ ms: [4000,10000], pitches: [67] }, |
|
|
{ ms: [5000,11000], pitches: [69, 82] }, |
|
|
{ bar: true }, |
|
|
{ ms: 6000, pitches: [70] }, |
|
|
{ ms: 7000, pitches: [72] }, |
|
|
{ ms: 8000, pitches: [74] }, |
|
|
{ bar: true }, |
|
|
{ ms: 12000, pitches: [75] }, |
|
|
{ ms: 13000, pitches: [77] }, |
|
|
{ ms: 14000, pitches: [79] }, |
|
|
{ bar: true }, |
|
|
]; |
|
|
|
|
|
var abcElevenEight = 'X:1\n' + |
|
|
'%%score { 1 | 2 }\n' + |
|
|
'L:1/8\n' + |
|
|
'Q:1/4=140\n' + |
|
|
'M:11/8\n' + |
|
|
'K:C\n' + |
|
|
'V:1 treble nm=\"Piano\"\n' + |
|
|
'V:2 bass\n' + |
|
|
'V:1\n' + |
|
|
'A,B,CD EFD E2 E2 |\n' + |
|
|
'V:2\n' + |
|
|
'A,,2 .[E,C,]2 E,,2 .[E,C,] A,,2 .[E,C,]2 |\n'; |
|
|
|
|
|
var expectedElevenEight = [ |
|
|
{ ms: 0, pitches: [57] }, |
|
|
{ ms: 214.28571428571428, pitches: [59] }, |
|
|
{ ms: 428.57142857142856, pitches: [60] }, |
|
|
{ ms: 642.8571428571429, pitches: [62] }, |
|
|
{ ms: 857.1428571428571, pitches: [64] }, |
|
|
{ ms: 1071.4285714285713, pitches: [65] }, |
|
|
{ ms: 1285.7142857142858, pitches: [62] }, |
|
|
{ ms: 1500, pitches: [64] }, |
|
|
{ ms: 1928.5714285714284, pitches: [64] }, |
|
|
{ bar: true }, |
|
|
]; |
|
|
|
|
|
var abcTempoChange = 'X:1\n' + |
|
|
'L:1/4\n' + |
|
|
'M:4/4\n' + |
|
|
'K:F\n' + |
|
|
'[Q:1/4=129.0476605]CDEF |[Q:1/4=127]GABc | [Q:1/4=131] CDEF |[Q:1/4=130] GABc |[Q:1/4=127]CDEF |\n' ; |
|
|
|
|
|
var abcWarp = 'X:1\n' + |
|
|
'L:1/4\n' + |
|
|
'Q:1/4=60\n' + |
|
|
'M:4/4\n' + |
|
|
'K:F\n' + |
|
|
'CD E>F | (3GAB Ac :|\n' ; |
|
|
|
|
|
var warpTests = [ |
|
|
{ bpm: 30, measuresOfDelay: 0 }, |
|
|
{ bpm: 30, measuresOfDelay: 1 }, |
|
|
{ bpm: 60, measuresOfDelay: 0 }, |
|
|
{ bpm: 60, measuresOfDelay: 1 }, |
|
|
{ bpm: 90, measuresOfDelay: 0 }, |
|
|
{ bpm: 90, measuresOfDelay: 2 }, |
|
|
]; |
|
|
|
|
|
var warpMs = [ |
|
|
{ millisecondsPerMeasure: 8000, ms: [0,2000,4000,7000,8000,9333,10667,12000,14000,16000,18000,20000,23000,24000,25333,26667,28000,30000,32000]}, |
|
|
{ millisecondsPerMeasure: 8000, ms: [8000,10000,12000,15000,16000,17333,18667,20000,22000,24000,26000,28000,31000,32000,33333,34667,36000,38000,40000]}, |
|
|
{ millisecondsPerMeasure: 4000, ms: [0,1000,2000,3500,4000,4667,5333,6000,7000,8000,9000,10000,11500,12000,12667,13333,14000,15000,16000]}, |
|
|
{ millisecondsPerMeasure: 4000, ms: [4000,5000,6000,7500,8000,8667,9333,10000,11000,12000,13000,14000,15500,16000,16667,17333,18000,19000,20000]}, |
|
|
{ millisecondsPerMeasure: 2666.666666666667, ms: [0,667,1333,2333,2667,3111,3556,4000,4667,5333,6000,6667,7667,8000,8444,8889,9333,10000,10667]}, |
|
|
{ millisecondsPerMeasure: 2666.666666666667, ms: [5333,6000,6667,7667,8000,8444,8889,9333,10000,10667,11333,12000,13000,13333,13778,14222,14667,15333,16000]}, |
|
|
]; |
|
|
|
|
|
var abcWarpNoQ = 'X:1\n' + |
|
|
'L:1/4\n' + |
|
|
'M:4/4\n' + |
|
|
'K:F\n' + |
|
|
'CD E>F | (3GAB Ac :|\n' ; |
|
|
|
|
|
var warpTestsNoQ = [ |
|
|
{ bpm: 30, measuresOfDelay: 0 }, |
|
|
{ bpm: 30, measuresOfDelay: 1 }, |
|
|
{ bpm: 60, measuresOfDelay: 0 }, |
|
|
{ bpm: 60, measuresOfDelay: 1 }, |
|
|
{ bpm: 90, measuresOfDelay: 0 }, |
|
|
{ bpm: 90, measuresOfDelay: 2 }, |
|
|
]; |
|
|
|
|
|
var warpMsNoQ = [ |
|
|
{ millisecondsPerMeasure: 8000, ms: [0,2000,4000,7000,8000,9333,10667,12000,14000,16000,18000,20000,23000,24000,25333,26667,28000,30000,32000]}, |
|
|
{ millisecondsPerMeasure: 8000, ms: [8000,10000,12000,15000,16000,17333,18667,20000,22000,24000,26000,28000,31000,32000,33333,34667,36000,38000,40000]}, |
|
|
{ millisecondsPerMeasure: 4000, ms: [0,1000,2000,3500,4000,4667,5333,6000,7000,8000,9000,10000,11500,12000,12667,13333,14000,15000,16000]}, |
|
|
{ millisecondsPerMeasure: 4000, ms: [4000,5000,6000,7500,8000,8667,9333,10000,11000,12000,13000,14000,15500,16000,16667,17333,18000,19000,20000]}, |
|
|
{ millisecondsPerMeasure: 2666.666666666667, ms: [0,667,1333,2333,2667,3111,3556,4000,4667,5333,6000,6667,7667,8000,8444,8889,9333,10000,10667]}, |
|
|
{ millisecondsPerMeasure: 2666.666666666667, ms: [5333,6000,6667,7667,8000,8444,8889,9333,10000,10667,11333,12000,13000,13333,13778,14222,14667,15333,16000]}, |
|
|
]; |
|
|
|
|
|
var abcSubTitleCrash = 'X:1\n' + |
|
|
'T:subtitle-crash\n' + |
|
|
'L:1/4\n' + |
|
|
'K:C\n' + |
|
|
'cdef|\n' + |
|
|
'T:subtitle\n' + |
|
|
'fabg|\n'; |
|
|
|
|
|
var abcRepeatAtStartCrash = 'X:1\n' + |
|
|
'T:repeat-at-start-of-line-crash\n' + |
|
|
'K:C\n' + |
|
|
'E8|\n' + |
|
|
'|1 D8 :|2 C8\n'; |
|
|
|
|
|
var abcSkipTiesCrash = 'X:1 \n' + |
|
|
'T:skip-ties-crash\n' + |
|
|
'M:4/4\n' + |
|
|
'K:C\n' + |
|
|
'E8- | E8 |]\n'; |
|
|
|
|
|
var abcTieRepeatCrash = 'X:1\n' + |
|
|
'T:tie-repeat-crash\n' + |
|
|
'L:1/8\n' + |
|
|
'Q:1/4=100\n' + |
|
|
'M:4/4\n' + |
|
|
'K:G\n' + |
|
|
'G3 F- F4 :|\n'; |
|
|
|
|
|
var abcBeatCallback = "X:1\n" + |
|
|
"M:2/4\n" + |
|
|
"L:1/16\n" + |
|
|
"K:C clef=bass\n" + |
|
|
"!f!C,2D,2 E,4|G,6 A,2|G,4 E,4|]\n" |
|
|
|
|
|
var expectedBeatCallback = [ |
|
|
{beat: 0, left: 'NONE' }, |
|
|
{beat: 1, left: 'NONE' }, |
|
|
{beat: 2, left: 'NONE' }, |
|
|
{beat: 3, left: 'NONE' }, |
|
|
{beat: 4, left: 70 }, |
|
|
{beat: 5, left: 165 }, |
|
|
{beat: 6, left: 240 }, |
|
|
{beat: 4, left: 70 }, |
|
|
{beat: 5, left: 165 }, |
|
|
{beat: 6, left: 240 }, |
|
|
{beat: 7, left: 290 }, |
|
|
{beat: 8, left: 375 }, |
|
|
{beat: 9, left: 440 }, |
|
|
{beat: 10, left: 'NONE'}, |
|
|
] |
|
|
|
|
|
var abcTieOverLineBreak = 'X:1\n' + |
|
|
'%%stretchlast 1\n' + |
|
|
'M:4/4\n' + |
|
|
'K:C\n' + |
|
|
'C4 D4-|\n' + |
|
|
'D4 F2-F2 |\n'; |
|
|
|
|
|
var expectedTieOverLineBreak = [ |
|
|
{beat: 0, top: 23, left: 70 }, |
|
|
{beat: 0.5, top: 23, left: 105 }, |
|
|
{beat: 1, top: 23, left: 150 }, |
|
|
{beat: 1.5, top: 23, left: 195 }, |
|
|
{beat: 2, top: 23, left: 240 }, |
|
|
{beat: 2.5, top: 23, left: 275 }, |
|
|
{beat: 3, top: 23, left: 325 }, |
|
|
{beat: 3.5, top: 23, left: 370 }, |
|
|
{beat: 4, top: 115, left: 50 }, |
|
|
{beat: 4.5, top: 115, left: 85 }, |
|
|
{beat: 5, top: 115, left: 135 }, |
|
|
{beat: 5.5, top: 115, left: 185 }, |
|
|
{beat: 6, top: 115, left: 230 }, |
|
|
{beat: 6.5, top: 115, left: 270 }, |
|
|
{beat: 7, top: 115, left: 315 }, |
|
|
{beat: 7.5, top: 115, left: 365 }, |
|
|
{beat: 8, top: 'NONE', left: 'NONE' }, |
|
|
] |
|
|
|
|
|
|
|
|
|
|
|
it("of repeated sections", function() { |
|
|
doTimingTest(abcRepeatedSections, expectedRepeatedSections); |
|
|
}); |
|
|
|
|
|
it("repeated sections callback", function() { |
|
|
doClickTest2(abcRepeatedSections, expectedRepeatedSections); |
|
|
}); |
|
|
|
|
|
it("of 11/8", function() { |
|
|
doTimingTest(abcElevenEight, expectedElevenEight); |
|
|
}); |
|
|
|
|
|
it("tempo change2 animation", function() { |
|
|
doAnimationTest(abcTempoChange); |
|
|
}); |
|
|
|
|
|
it("subtitle crash", function() { |
|
|
doCreationTest(abcSubTitleCrash); |
|
|
}); |
|
|
|
|
|
it("repeat at start crash", function() { |
|
|
doCreationTest(abcRepeatAtStartCrash); |
|
|
}); |
|
|
|
|
|
it("skip ties crash", function() { |
|
|
doCreationTest(abcSkipTiesCrash); |
|
|
}); |
|
|
|
|
|
it("tie repeat crash", function() { |
|
|
doCreationTest(abcTieRepeatCrash); |
|
|
}); |
|
|
|
|
|
it("warp", function() { |
|
|
for (var i = 0; i < warpTests.length; i++) { |
|
|
doWarpTest(abcWarp, warpTests[i], warpMs[i]); |
|
|
} |
|
|
}); |
|
|
|
|
|
it("warp no q", function() { |
|
|
for (var i = 0; i < warpTestsNoQ.length; i++) { |
|
|
doWarpTest(abcWarpNoQ, warpTestsNoQ[i], warpMsNoQ[i]); |
|
|
} |
|
|
}); |
|
|
|
|
|
it("beat-callback", function() { |
|
|
return doBeatCallbackTest(abcBeatCallback, expectedBeatCallback) |
|
|
}); |
|
|
|
|
|
it("tieOverLineBreak", function() { |
|
|
return doBeatCallbackTestTies(abcTieOverLineBreak, expectedTieOverLineBreak) |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
function doWarpTest(abc, warps, warpMs) { |
|
|
var visualObj = abcjs.renderAbc("paper", abc); |
|
|
|
|
|
visualObj[0].setTiming(warps.bpm, warps.measuresOfDelay); |
|
|
var ms = []; |
|
|
for (var i = 0; i < visualObj[0].noteTimings.length; i++) |
|
|
ms.push(visualObj[0].noteTimings[i].milliseconds); |
|
|
|
|
|
|
|
|
chai.assert.equal(visualObj[0].noteTimings[0].millisecondsPerMeasure, warpMs.millisecondsPerMeasure, 'millisecondsPerMeasure for test { bpm:' + warps.bpm +', measuresOfDelay:' + warps.measuresOfDelay + ' }'); |
|
|
var msg = 'millisecs for test { bpm:' + warps.bpm +', measuresOfDelay:' + warps.measuresOfDelay + ' }\n'; |
|
|
msg += "rcv: " + JSON.stringify(ms) + "\nexp: " + JSON.stringify(warpMs.ms); |
|
|
chai.assert.deepStrictEqual(ms, warpMs.ms, msg); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function doAnimationTest(abc) { |
|
|
var visualObj = abcjs.renderAbc("paper", abc); |
|
|
visualObj[0].setUpAudio(); |
|
|
visualObj[0].setTiming(); |
|
|
var ms = visualObj[0].millisecondsPerMeasure(); |
|
|
|
|
|
for (var i = 0; i < visualObj[0].noteTimings.length; i++) { |
|
|
var ev = visualObj[0].noteTimings[i]; |
|
|
|
|
|
if (ev.midiPitches) { |
|
|
var start = Math.round(ev.midiPitches[0].start * ms); |
|
|
chai.assert.equal(ev.milliseconds, start, 'timing for element ' + i); |
|
|
} |
|
|
} |
|
|
} |
|
|
var trans = function(nt, i, ms) { console.log(nt[i].milliseconds, nt[i].midiPitches[0].start, ms, nt[i].midiPitches[0].start* ms) } |
|
|
|
|
|
|
|
|
|
|
|
function doClickTest2(abc, expected) { |
|
|
var visualObj = abcjs.renderAbc("paper", abc); |
|
|
visualObj[0].setUpAudio(); |
|
|
var selectables = visualObj[0].engraver.selectables; |
|
|
|
|
|
var index = 0; |
|
|
for (var i = 0; i < expected.length; i++) { |
|
|
if (!expected[i].bar) { |
|
|
listener(selectables[index].absEl.abcelem, expected[i]); |
|
|
index++; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function doCreationTest(abc) { |
|
|
var visualObj = abcjs.renderAbc("paper", abc); |
|
|
visualObj[0].setUpAudio(); |
|
|
visualObj[0].setTiming(); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function doTimingTest(abc, expected) { |
|
|
var visualObj = abcjs.renderAbc("paper", abc); |
|
|
visualObj[0].setUpAudio(); |
|
|
var voice = visualObj[0].lines[0].staff[0].voices[0]; |
|
|
|
|
|
chai.assert.equal(voice.length,expected.length, 'num items'); |
|
|
for (var i = 0; i < voice.length; i++) { |
|
|
if (voice[i].el_type === 'bar' && expected[i].bar) |
|
|
continue; |
|
|
|
|
|
chai.assert.deepStrictEqual(voice[i].currentTrackMilliseconds, expected[i].ms, "millisecs for " + i); |
|
|
chai.assert.equal(voice[i].midiPitches.length,expected[i].pitches.length, 'num pitches for ' + i); |
|
|
for (var j = 0; j < voice[i].midiPitches.length; j++) { |
|
|
chai.assert.equal(voice[i].midiPitches[j].pitch,expected[i].pitches[j], 'pitch for ' + i + ' ' + j); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
function listener(abcElem, expected) { |
|
|
var pitches = []; |
|
|
for (var i = 0; i < abcElem.midiPitches.length; i++) |
|
|
chai.assert.equal(abcElem.midiPitches[i].pitch,expected.pitches[i]); |
|
|
chai.assert.deepStrictEqual(abcElem.currentTrackMilliseconds,expected.ms); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function doBeatCallbackTestTies(abc, expected) { |
|
|
var visualObj = abcjs.renderAbc("paper", abc, { staffwidth: 400, stretchlast: true}) |
|
|
var timing = new abcjs.TimingCallbacks(visualObj[0], { |
|
|
beatCallback: beatCallback, |
|
|
beatSubdivisions: 2, |
|
|
qpm: 480, |
|
|
}) |
|
|
console.log(visualObj[0].noteTimings) |
|
|
console.log(timing) |
|
|
|
|
|
for (var k = 0; k < timing.noteTimings.length; k++) { |
|
|
var t = timing.noteTimings[k] |
|
|
console.log(k, t.milliseconds, t.line, t.left ) |
|
|
} |
|
|
var actual = [] |
|
|
function beatCallback(beat,total,totalTime,position) { |
|
|
var left = position.left === undefined ? 'NONE' : Math.round(position.left/5)*5 |
|
|
var top = position.top === undefined ? 'NONE' : Math.round(position.top) |
|
|
actual.push({beat: beat, top: top, left: left}) |
|
|
} |
|
|
|
|
|
var svg = document.querySelector('#paper svg') |
|
|
timing.start() |
|
|
return sleep(1950).then(function () { |
|
|
var msg = [] |
|
|
for (var i = 0; i < Math.min(actual.length, expected.length); i++) { |
|
|
var err = JSON.stringify(actual[i]) !== JSON.stringify(expected[i]) ? 'XXXX' : '' |
|
|
msg.push(JSON.stringify(actual[i]) + ' = ' + JSON.stringify(expected[i]) + ' ' + err) |
|
|
if (actual[i].left !== 'NONE' && actual[i].top !== 'NONE') |
|
|
createLine(actual[i].left, actual[i].top, actual[i].left, actual[i].top+20, "stroke-width:2;stroke:blue", svg) |
|
|
console.log(actual[i]) |
|
|
} |
|
|
msg = "\n" + msg.join("\n") + "\n" |
|
|
chai.assert.deepStrictEqual(actual,expected, msg); |
|
|
return Promise.resolve(); |
|
|
}) |
|
|
} |
|
|
|
|
|
const svgNS = "http://www.w3.org/2000/svg"; |
|
|
|
|
|
function createLine(x, y, x2, y2, style, parent) { |
|
|
const line = document.createElementNS(svgNS, "line"); |
|
|
line.setAttribute("x1", ''+x); |
|
|
line.setAttribute("y1", ''+y); |
|
|
line.setAttribute("x2", ''+x2); |
|
|
line.setAttribute("y2", ''+y2); |
|
|
if (style) |
|
|
line.setAttribute("style", style); |
|
|
parent.appendChild(line); |
|
|
} |
|
|
|
|
|
function doBeatCallbackTest(abc, expected) { |
|
|
var visualObj = abcjs.renderAbc("paper", abc, { staffwidth: 500, stretchlast: true}) |
|
|
var timing = new abcjs.TimingCallbacks(visualObj[0], { |
|
|
beatCallback: beatCallback, |
|
|
beatSubdivisions: 1, |
|
|
extraMeasuresAtBeginning: 2, |
|
|
qpm: 400, |
|
|
}) |
|
|
var actual = [] |
|
|
function beatCallback(beat,total,totalTime,position) { |
|
|
var left = position.left === undefined ? 'NONE' : Math.round(position.left/5)*5 |
|
|
actual.push({beat: beat, left: left}) |
|
|
} |
|
|
|
|
|
timing.start() |
|
|
return sleep(900).then(function () { |
|
|
timing.setProgress(0.6, "seconds") |
|
|
return sleep(900).then(function () { |
|
|
var msg = [] |
|
|
for (var i = 0; i < Math.min(actual.length, expected.length); i++) { |
|
|
var err = JSON.stringify(actual[i]) !== JSON.stringify(expected[i]) ? 'XXXX' : '' |
|
|
msg.push(JSON.stringify(actual[i]) + ' = ' + JSON.stringify(expected[i]) + ' ' + err) |
|
|
} |
|
|
msg = "\n" + msg.join("\n") + "\n" |
|
|
chai.assert.deepStrictEqual(actual,expected, msg); |
|
|
return Promise.resolve(); |
|
|
}) |
|
|
}) |
|
|
} |
|
|
|
|
|
function sleep(ms) { |
|
|
return new Promise(function (resolve) { |
|
|
setTimeout(resolve, ms); |
|
|
}); |
|
|
} |
|
|
|