fixed a bunch of playback-stopping bugs
This commit is contained in:
parent
156cca80d3
commit
92c6bddcc6
2 changed files with 55 additions and 25 deletions
|
|
@ -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
78
xm.js
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue