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> 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> tune: my dirty old kamel - <a href="http://zalza.bandcamp.com">Zalza</a>
todo: todo:
- fix bugs!
- envelope loops - are they right or not now?
- multi-sample instruments - multi-sample instruments
- missing XM effects (in rough order of priority): - missing XM effects (in rough order of priority):
- Rxy - retrigger - Rxy - retrigger

78
xm.js
View file

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