diff --git a/test/all.js b/test/all.js index a21d2bd..c0a6053 100644 --- a/test/all.js +++ b/test/all.js @@ -4,7 +4,6 @@ require('../xm.js'); require('../xmeffects.js'); var XMPlayer = window.XMPlayer; -tests = {}; testdata = require('./testdata.js'); // test TODO: @@ -12,7 +11,7 @@ testdata = require('./testdata.js'); // - unit test for envelopes // // using assert passed to the test function that just logs failures -tests['test XM startup'] = function(assert) { +exports['test XM startup'] = function(assert) { testdata.resetXMData(); XMPlayer.nextTick(); assert.equal(XMPlayer.cur_songpos, 0, 'advance to initial song position'); @@ -21,7 +20,7 @@ tests['test XM startup'] = function(assert) { assert.equal(XMPlayer.cur_row, 1, 'advance to row 1'); }; -require('./instrument.js'); -require('./effects.js'); +exports['test instruments'] = require('./instrument.js'); +exports['test effects'] = require('./effects.js'); -if (module == require.main) require('test').run(tests); +if (module == require.main) require('test').run(exports); diff --git a/test/effects.js b/test/effects.js index 2993de8..247fec6 100644 --- a/test/effects.js +++ b/test/effects.js @@ -7,7 +7,7 @@ var XMPlayer = window.XMPlayer; // - porta+vol 5xy // - etc etc -tests['test 0xy arpeggio'] = function(assert) { +exports['test 0xy arpeggio'] = function(assert) { var xm = testdata.resetXMData(); // [pat][row][channel] xm.patterns[0][0][0] = [48, 1, -1, 0, 0x4f]; // C-4 1 -- 04f @@ -26,7 +26,7 @@ tests['test 0xy arpeggio'] = function(assert) { assert.equal(ch.period, 1152 - 16*0, 'row 1 tick 0 period 0'); }; -tests['test 1xx slide up'] = function(assert) { +exports['test 1xx slide up'] = function(assert) { var xm = testdata.resetXMData(); // [pat][row][channel] xm.patterns[0][0][0] = [48, 1, -1, 1, 0x01]; // C-4 1 -- 101 @@ -45,7 +45,7 @@ tests['test 1xx slide up'] = function(assert) { assert.equal(ch.period, 1152 - 3, 'row 1 tick 1 period -3'); }; -tests['test 2xx slide down'] = function(assert) { +exports['test 2xx slide down'] = function(assert) { var xm = testdata.resetXMData(); // [pat][row][channel] xm.patterns[0][0][0] = [48, 1, -1, 2, 0x01]; // C-4 1 -- 201 @@ -55,16 +55,16 @@ tests['test 2xx slide down'] = function(assert) { var ch = xm.channelinfo[0]; assert.equal(ch.period, 1152, 'row 0 tick 0 period 0'); XMPlayer.nextTick(); - assert.equal(ch.period, 1152 + 1, 'row 0 tick 1 period -1'); + assert.equal(ch.period, 1152 + 1, 'row 0 tick 1 period +1'); XMPlayer.nextTick(); - assert.equal(ch.period, 1152 + 2, 'row 0 tick 2 period -2'); + assert.equal(ch.period, 1152 + 2, 'row 0 tick 2 period +2'); XMPlayer.nextTick(); - assert.equal(ch.period, 1152 + 2, 'row 1 tick 0 period -2'); + assert.equal(ch.period, 1152 + 2, 'row 1 tick 0 period +2'); XMPlayer.nextTick(); - assert.equal(ch.period, 1152 + 3, 'row 1 tick 1 period -3'); + assert.equal(ch.period, 1152 + 3, 'row 1 tick 1 period +3'); }; -tests['test 3xx portamento'] = function(assert) { +exports['test 3xx portamento'] = function(assert) { var xm = testdata.resetXMData(); // [pat][row][channel] xm.patterns[0][0][0] = [48, 1, -1, 0, 0x00]; // C-4 1 -- 000 @@ -83,3 +83,72 @@ tests['test 3xx portamento'] = function(assert) { assert.equal(ch.period, 1152 - 16, 'row 1 tick 2 period -16'); }; +exports['test 4xy vibrato'] = function(assert) { + var xm = testdata.resetXMData(); + // vibrato 4xy: speed x, depth y + // full cycle is 64/speed + // [pat][row][channel] + xm.patterns[0] = [ + [[48, 1, -1, 4, 0x81]], // C-4 1 -- 481 + [[-1, -1, -1, 4, 0x02]], // --- -- -- 402 + [[-1, -1, -1, 4, 0x10]], // --- -- -- 410 + [[-1, -1, -1, 4, 0x00]], // --- -- -- 400 + [[-1, -1, -1, 0, 0x00]], // --- -- -- 000 - no vibrato + [[-1, -1, -1, 4, 0x00]], // --- -- -- 400 - resume vibrato @ pos 0 + ]; + // I should really be testing ch.doff directly + XMPlayer.xm.tempo = 3; + var ch = xm.channelinfo[0]; + XMPlayer.nextTick(); // row 0 tick 0 + var p0 = ch.doff; + assert.equal(ch.periodoffset, 0, 'row 0 tick 0 periodoffset=0'); + XMPlayer.nextTick(); // row 0 tick 1 + // compute logical period p from actual play frequency + var p = 16*12 * Math.log(p0 / ch.doff) / Math.log(2); + assert.equal(p.toFixed(3), "0.707", 'row 0 tick 1 period +0.707'); + XMPlayer.nextTick(); // row 0 tick 2 + p = -16*12 * Math.log(ch.doff / p0) / Math.log(2); + assert.equal(p.toFixed(3), "1.000", 'row 0 tick 2 period +1.000'); + XMPlayer.nextTick(); // row 1 tick 0 + p = -16*12 * Math.log(ch.doff / p0) / Math.log(2); + assert.equal(p.toFixed(3), "1.414", 'row 1 tick 0 period +1.414'); + XMPlayer.nextTick(); // row 1 tick 1 + p = -16*12 * Math.log(ch.doff / p0) / Math.log(2); + assert.equal(p.toFixed(3), "0.000", 'row 1 tick 1 period +0'); + XMPlayer.nextTick(); // row 1 tick 2 + p = -16*12 * Math.log(ch.doff / p0) / Math.log(2); + assert.equal(p.toFixed(3), "-1.414", 'row 1 tick 2 period -1.414'); + XMPlayer.nextTick(); // row 2 tick 0 + p = -16*12 * Math.log(ch.doff / p0) / Math.log(2); + assert.equal(p.toFixed(3), "-2.000", 'row 2 tick 0 period -2.000'); + XMPlayer.nextTick(); // row 2 tick 1 + p = -16*12 * Math.log(ch.doff / p0) / Math.log(2); + assert.equal(p.toFixed(3), "-1.990", 'row 2 tick 1 period -1.990'); + XMPlayer.nextTick(); // row 2 tick 2 + p = -16*12 * Math.log(ch.doff / p0) / Math.log(2); + assert.equal(p.toFixed(3), "-1.962", 'row 2 tick 2 period -1.962'); + XMPlayer.nextTick(); // row 3 tick 0 + p = -16*12 * Math.log(ch.doff / p0) / Math.log(2); + assert.equal(p.toFixed(3), "-1.914", 'row 3 tick 0 period -1.914'); + XMPlayer.nextTick(); // row 3 tick 1 + p = -16*12 * Math.log(ch.doff / p0) / Math.log(2); + assert.equal(p.toFixed(3), "-1.848", 'row 3 tick 1 period -1.848'); + XMPlayer.nextTick(); // row 3 tick 2 + p = -16*12 * Math.log(ch.doff / p0) / Math.log(2); + assert.equal(p.toFixed(3), "-1.764", 'row 3 tick 2 period -1.764'); + XMPlayer.nextTick(); // row 4 tick 0 + p = -16*12 * Math.log(ch.doff / p0) / Math.log(2); + assert.equal(p.toFixed(3), "0.000", 'row 4 tick 0 period 0 - no vibrato'); + XMPlayer.nextTick(); // row 4 tick 1 + XMPlayer.nextTick(); // row 4 tick 2 + // I actually don't know whether vibrato is supposed to reset when the effect + // goes away or whether it should resume. Resuming is simpler to implement so + // that's what I'm assuming here... + XMPlayer.nextTick(); // row 5 tick 0 + p = -16*12 * Math.log(ch.doff / p0) / Math.log(2); + assert.equal(p.toFixed(3), "-1.663", 'row 5 tick 0 period -1.663 - vibrato resume'); + XMPlayer.nextTick(); // row 5 tick 1 + p = -16*12 * Math.log(ch.doff / p0) / Math.log(2); + assert.equal(p.toFixed(3), "-1.546", 'row 5 tick 1 period -1.546'); +}; + diff --git a/test/instrument.js b/test/instrument.js index 929ee7e..a7d399b 100644 --- a/test/instrument.js +++ b/test/instrument.js @@ -4,7 +4,7 @@ var XMPlayer = window.XMPlayer; // - test volume envelope (sustain, release) // - test panning envelope -tests['test note on'] = function(assert) { +exports['test note on'] = function(assert) { var xm = testdata.resetXMData(); // [pat][row][channel] xm.patterns[0][0][0] = [48, 1, 0x10 + 0x33, 0, 0]; // C-4 1 33 000 @@ -17,7 +17,7 @@ tests['test note on'] = function(assert) { assert.equal(ch.off, 0, 'trigger offset'); }; -tests['test instrument trigger'] = function(assert) { +exports['test instrument trigger'] = function(assert) { var xm = testdata.resetXMData(); // [pat][row][channel] xm.patterns[0][0][0] = [48, 1, 0x10 + 0x33, 0, 0]; // C-4 1 33 000 @@ -37,7 +37,7 @@ tests['test instrument trigger'] = function(assert) { assert.equal(ch.off, 0, 'set offset=0'); }; -tests['test note trigger'] = function(assert) { +exports['test note trigger'] = function(assert) { var xm = testdata.resetXMData(); // [pat][row][channel] xm.patterns[0][0][0] = [48, 1, 0x10 + 0x33, 0, 0]; // C-4 1 33 000 diff --git a/test/testdata.js b/test/testdata.js index ed04dec..0ca0568 100644 --- a/test/testdata.js +++ b/test/testdata.js @@ -22,6 +22,7 @@ exports.resetXMData = function() { mute: 0, volE: 0, panE: 0, retrig: 0, + vibratopos: 0, vibratodepth: 1, vibratospeed: 1, }); diff --git a/xm.js b/xm.js index 5b28d9f..ff31034 100644 --- a/xm.js +++ b/xm.js @@ -226,10 +226,9 @@ function nextRow() { if (ch.effect != 9) ch.off = 0; ch.release = 0; ch.envtick = 0; - ch.vibratopos = 0; ch.env_vol = new EnvelopeFollower(inst.env_vol); ch.env_pan = new EnvelopeFollower(inst.env_pan); - if (ch.note != undefined) { + if (ch.note) { ch.period = periodForNote(ch, ch.note); } } @@ -287,25 +286,31 @@ EnvelopeFollower.prototype.Tick = function(release) { function nextTick() { player.cur_tick++; + var j, ch; + for (j = 0; j < player.xm.nchan; j++) { + ch = player.xm.channelinfo[j]; + ch.periodoffset = 0; + } if (player.cur_tick >= player.xm.tempo) { player.cur_tick = 0; nextRow(); } - for (var j = 0; j < player.xm.nchan; j++) { - var ch = player.xm.channelinfo[j]; + for (j = 0; j < player.xm.nchan; j++) { + ch = player.xm.channelinfo[j]; var inst = ch.inst; - ch.periodoffset = 0; - if (player.cur_tick != 0) { + if (player.cur_tick !== 0) { if(ch.voleffectfn) ch.voleffectfn(ch); if(ch.effectfn) ch.effectfn(ch); } if (isNaN(ch.period)) { - console.log(prettify_notedata(player.xm.patterns[player.cur_pat][player.cur_row-1][j]), + console.log(prettify_notedata( + player.xm.patterns[player.cur_pat][player.cur_row-1][j]), "set channel", j, "period to NaN"); } - if (inst == undefined) continue; - if (ch.env_vol == undefined) { - console.log(prettify_notedata(player.xm.patterns[player.cur_pat][player.cur_row-1][j]), + if (inst === undefined) continue; + if (ch.env_vol === undefined) { + console.log(prettify_notedata( + player.xm.patterns[player.cur_pat][player.cur_row-1][j]), "set channel", j, "env_vol to undefined, but note is playing"); continue; } @@ -655,6 +660,7 @@ function load(arrayBuf) { mute: 0, volE: 0, panE: 0, retrig: 0, + vibratopos: 0, vibratodepth: 1, vibratospeed: 1, }); diff --git a/xmeffects.js b/xmeffects.js index ef429fa..5832d3d 100644 --- a/xmeffects.js +++ b/xmeffects.js @@ -61,13 +61,14 @@ function eff_t0_4(ch, data) { // vibrato if (data >> 4) { ch.vibratospeed = data >> 4; } - eff_t1_4(ch, data); + eff_t1_4(ch); } function eff_t1_4(ch) { // vibrato ch.periodoffset = Math.sin(ch.vibratopos * Math.PI / 32) * ch.vibratodepth; if (isNaN(ch.periodoffset)) { - console.log("vibrato periodoffset NaN?", ch.vibratopos, ch.vibratodepth); + console.log("vibrato periodoffset NaN?", + ch.vibratopos, ch.vibratospeed, ch.vibratodepth); ch.periodoffset = 0; } ch.vibratopos += ch.vibratospeed;