abcjs / tests /synth /timing.test.js
KEXEL's picture
Upload 337 files
af6912c verified
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].setUpAudio();
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);
// console.log(visualObj[0].noteTimings[0].millisecondsPerMeasure)
// console.log(JSON.stringify(ms))
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();
//console.log(JSON.stringify(visualObj[0].noteTimings))
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;
//console.log(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];
//console.log(voice)
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)
//timing.noteTimings[2].left = 40
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);
});
}