fixed a bunch of playback-stopping bugs

This commit is contained in:
Andy Sloane 2015-10-29 14:56:17 -07:00
commit 92c6bddcc6
2 changed files with 55 additions and 25 deletions

View file

@ -7,6 +7,8 @@
code: <a href="http://github.com/a1k0n/jsxm/">github.com/a1k0n/jsxm</a>
tune: my dirty old kamel - <a href="http://zalza.bandcamp.com">Zalza</a>
todo:
- fix bugs!
- envelope loops - are they right or not now?
- multi-sample instruments
- missing XM effects (in rough order of priority):
- Rxy - retrigger

78
xm.js
View file

@ -21,16 +21,16 @@ function prettify_volume(num) {
}
function prettify_effect(t, p) {
t = t.toString(16);
if (t >= 10) t = String.fromCharCode(55 + t);
if (p < 16) p = '0' + p.toString(16);
else p = p.toString(16);
return t + p
}
function prettify_notedata(note, inst, vol, efftype, effparam) {
return (prettify_note(note) + " " + prettify_number(inst) + " "
+ prettify_volume(vol) + " "
+ prettify_effect(efftype, effparam));
function prettify_notedata(data) {
return (prettify_note(data[0]) + " " + prettify_number(data[1]) + " "
+ prettify_volume(data[2]) + " "
+ prettify_effect(data[3], data[4]));
}
function getstring(dv, offset, len) {
@ -68,6 +68,10 @@ popfilter_alpha = 0.9837;
function UpdateChannelPeriod(ch, period) {
var freq = 8363 * Math.pow(2, (1152.0 - period) / 192.0);
if (isNaN(freq)) {
console.log("invalid period!", period);
return;
}
ch.doff = freq / f_smp;
ch.filter = FilterCoeffs(ch.doff / 2);
}
@ -94,7 +98,8 @@ function next_row() {
for (var i = 0; i < r.length; i++) {
var ch = channelinfo[i];
ch.update = false;
pretty_row.push(prettify_notedata(r[i][0], r[i][1], r[i][2], r[i][3], r[i][4]));
pretty_row.push(prettify_notedata(r[i]));
var triggernote = false;
// instrument trigger
if (r[i][1] != -1) {
var inst = instruments[r[i][1] - 1];
@ -107,12 +112,11 @@ function next_row() {
// console.log("invalid inst", r[i][1], instruments.length);
}
}
var triggernote = false;
// note trigger
if (r[i][0] != -1) {
if (r[i][0] == 96) {
// release note, FIXME once envelopes are implemented
ch.release = 1;
triggernote = false;
} else {
// assume linear frequency table (flags header & 1 == 1)
// is this true in kamel.xm?
@ -128,7 +132,6 @@ function next_row() {
}
}
if (r[i][2] != -1) { // volume column
// FIXME: panning
var v = r[i][2];
if (v < 0x10) {
console.log("channel", i, "invalid volume", v.toString(16));
@ -162,13 +165,19 @@ function next_row() {
ch.periodtarget = PeriodForNote(ch, ch.note);
}
triggernote = false;
if (ch.release && inst != undefined) {
// reset envelopes if note was released but leave offset/pitch/etc
// alone
ch.envtick = 0;
ch.release = 0;
ch.env_vol = new EnvelopeFollower(inst.env_vol, inst.fadeout);
ch.env_pan = new EnvelopeFollower(inst.env_pan, 0);
if (inst != undefined) {
if (ch.env_vol == undefined) {
// note wasn't already playing; we basically have to ignore the
// portamento and just trigger
triggernote = true;
} else if (ch.release) {
// reset envelopes if note was released but leave offset/pitch/etc
// alone
ch.envtick = 0;
ch.release = 0;
ch.env_vol = new EnvelopeFollower(inst.env_vol, inst.fadeout);
ch.env_pan = new EnvelopeFollower(inst.env_pan, 0);
}
}
}
@ -192,8 +201,8 @@ function Envelope(points, type, sustain, loopstart, loopend) {
this.points = points;
this.type = type;
this.sustain = sustain;
this.loopstart = loopstart;
this.loopend = loopend;
this.loopstart = points[loopstart*2];
this.loopend = points[loopend*2];
}
Envelope.prototype.Get = function(ticks) {
@ -231,7 +240,7 @@ EnvelopeFollower.prototype.Tick = function(release) {
this.tick++;
if (this.env.type & 4) { // envelope loop?
if (!release &&
this.tick > this.env.loopend) {
this.tick >= this.env.loopend) {
this.tick -= this.env.loopend - this.env.loopstart;
}
}
@ -251,7 +260,16 @@ function next_tick() {
if (cur_tick != 0 && ch.effectfn) {
ch.effectfn(ch);
}
if (inst === undefined) continue;
if (isNaN(ch.period)) {
console.log(prettify_notedata(patterns[cur_pat][cur_row-1][j]),
"set channel", j, "period to NaN");
}
if (inst == undefined) continue;
if (ch.env_vol == undefined) {
console.log(prettify_notedata(patterns[cur_pat][cur_row-1][j]),
"set channel", j, "env_vol to undefined, but note is playing");
continue;
}
ch.volE = ch.env_vol.Tick(ch.release);
ch.panE = ch.env_pan.Tick(ch.release);
UpdateChannelPeriod(ch, ch.period + ch.periodoffset);
@ -333,7 +351,12 @@ function MixChannelIntoBuf(ch, start, end, dataL, dataR) {
// we can mix up to this many bytes before running into a sample end/loop
var i = start;
var failsafe = 100;
while (i < end) {
if (failsafe-- == 0) {
console.log("failsafe in mixing loop!", k, sample_end, loopstart, looplen, dk);
break;
}
if (k >= sample_end) { // TODO: implement pingpong looping
if (loop) {
k = loopstart + (k - loopstart) % looplen;
@ -344,11 +367,11 @@ function MixChannelIntoBuf(ch, start, end, dataL, dataR) {
return Vrms + MixSilenceIntoBuf(ch, i, end, dataL, dataR);
}
}
var next_event = Math.min(end, i + (sample_end - k) / dk);
var next_event = Math.max(1, Math.min(end, i + (sample_end - k) / dk));
// this is the inner loop of the player
// unrolled 8x
for (; i + 8 < next_event; i+=8) {
for (; i + 7 < next_event; i+=8) {
var s = samp[k|0];
var y = f0 * (s + fs0) + f1*fs1 + f2*fs2;
fs2 = fs1; fs1 = y; fs0 = s;
@ -433,7 +456,6 @@ function MixChannelIntoBuf(ch, start, end, dataL, dataR) {
}
}
ch.off = k;
ch.doff = dk;
ch.filterstate[0] = fs0;
ch.filterstate[1] = fs1;
ch.filterstate[2] = fs2;
@ -662,6 +684,10 @@ function eff_t1_3(ch) { // portamento
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);
ch.periodoffset = 0;
}
ch.vibratopos += ch.vibratospeed;
ch.vibratopos &= 63;
}
@ -789,6 +815,7 @@ function playXM(arrayBuf) {
filterstate: new Float32Array(3),
vol: 0,
pan: 128,
period: 1920 - 48*16,
vL: 0, vR: 0, // left right volume envelope followers (changes per sample)
vLprev: 0, vRprev: 0,
mute: 0,
@ -849,8 +876,9 @@ function playXM(arrayBuf) {
efftype = dv.getUint8(idx); idx++;
effparam = dv.getUint8(idx); idx++;
}
pretty_row.push(prettify_notedata(note, inst, vol, efftype, effparam));
row.push([note, inst, vol, efftype, effparam]);
var notedata = [note, inst, vol, efftype, effparam];
pretty_row.push(prettify_notedata(notedata));
row.push(notedata);
}
pattern.push(row);
}