added per-channel low-pass filter
sounds better, but still clicks and pops a lot. i'll need a volume envelope lowpass filter, i guess.
This commit is contained in:
parent
15a848d797
commit
7de75f6977
2 changed files with 74 additions and 23 deletions
22
LICENSE
Normal file
22
LICENSE
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Andy Sloane <andy@a1k0n.net>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
69
xm.js
69
xm.js
|
|
@ -56,6 +56,28 @@ function GetEnvelope(env, ticks) {
|
|||
return y0;
|
||||
}
|
||||
|
||||
// Return 2-pole Butterworth lowpass filter coefficients for
|
||||
// center frequncy f_c (relative to sampling frequency)
|
||||
function FilterCoeffs(f_c) {
|
||||
// if (f_c > 0.5) { // we can't lowpass above the nyquist frequency...
|
||||
// return [1, 0, 0];
|
||||
// }
|
||||
var wct = Math.sqrt(2) * Math.PI * f_c;
|
||||
var e = Math.exp(-wct);
|
||||
var c = e * Math.cos(wct);
|
||||
var s = e * Math.sin(wct);
|
||||
var gain = (1 - 2*c + c*c + s*s) / 2;
|
||||
return [gain, 2*c, -c*c - s*s];
|
||||
}
|
||||
|
||||
function Filter(x, coef, state) {
|
||||
var y = coef[0] * (x + state[0]) + coef[1]*state[1] + coef[2]*state[2];
|
||||
state[0] = x;
|
||||
state[2] = state[1];
|
||||
state[1] = y;
|
||||
return y;
|
||||
}
|
||||
|
||||
var cur_songpos = -1, cur_pat = -1, cur_row = 64, cur_ticksamp = 0;
|
||||
var cur_tick = 6;
|
||||
function next_row(f_smp) {
|
||||
|
|
@ -107,6 +129,7 @@ function next_row(f_smp) {
|
|||
channelinfo[i].off = 0;
|
||||
channelinfo[i].release = 0;
|
||||
channelinfo[i].envtick = 0;
|
||||
channelinfo[i].filter = FilterCoeffs(channelinfo[i].doff / 2);
|
||||
// console.log("channel", i, r[i][0], channelinfo[i]);
|
||||
// if there's an instrument and a note, set the volume
|
||||
if (r[i][0] != -1) {
|
||||
|
|
@ -140,21 +163,22 @@ function next_tick(f_smp) {
|
|||
next_row(f_smp);
|
||||
}
|
||||
for (var j = 0; j < nchan; j++) {
|
||||
var inst = channelinfo[j].inst;
|
||||
var ch = channelinfo[j];
|
||||
var inst = ch.inst;
|
||||
if (inst === undefined) continue;
|
||||
if (inst.env_vol !== undefined) {
|
||||
channelinfo[j].env_vol = GetEnvelope(inst.env_vol, channelinfo[j].envtick);
|
||||
ch.env_vol = GetEnvelope(inst.env_vol, ch.envtick);
|
||||
if (inst.env_vol_type & 2) { // sustain loop?
|
||||
// if we're sustaining a note, leave env_vol alone
|
||||
if (channelinfo[j].release == 0) {
|
||||
if (channelinfo[j].envtick >= inst.env_vol[inst.env_vol_sustain*2]) {
|
||||
if (ch.release == 0) {
|
||||
if (ch.envtick >= inst.env_vol[inst.env_vol_sustain*2]) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
channelinfo[j].envtick++;
|
||||
ch.envtick++;
|
||||
} else {
|
||||
channelinfo[j].env_vol = 64;
|
||||
ch.env_vol = 64;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -176,52 +200,55 @@ function audio_cb(e) {
|
|||
}
|
||||
var tickduration = Math.min(buflen, ticklen - cur_ticksamp);
|
||||
for (var j = 0; j < nchan; j++) {
|
||||
var inst = channelinfo[j].inst;
|
||||
var ch = channelinfo[j];
|
||||
var inst = ch.inst;
|
||||
if (inst === undefined) continue;
|
||||
var samp = inst.sampledata;
|
||||
var sample_end = inst.len;
|
||||
var looplen = 0;
|
||||
var loop = false;
|
||||
if (inst.type & 3) {
|
||||
if ((inst.type & 3) == 1) { // todo: support pingpong
|
||||
loop = true;
|
||||
looplen = inst.looplen;
|
||||
sample_end = looplen + inst.loop;
|
||||
}
|
||||
var samplen = inst.len;
|
||||
var env_vol = channelinfo[j].env_vol / 64.0;
|
||||
var volL = env_vol * channelinfo[j].volL / 64.0;
|
||||
var volR = env_vol * channelinfo[j].volR / 64.0;
|
||||
var env_vol = ch.env_vol / 64.0;
|
||||
var volL = env_vol * ch.volL / 64.0;
|
||||
var volR = env_vol * ch.volR / 64.0;
|
||||
if (volL < 0) volL = 0;
|
||||
if (volR < 0) volR = 0;
|
||||
if (volR == 0 && volL == 0)
|
||||
continue;
|
||||
var k = channelinfo[j].off;
|
||||
var dk = channelinfo[j].doff;
|
||||
// console.log(j, offset, channelinfo[j]);
|
||||
var k = ch.off;
|
||||
var dk = ch.doff;
|
||||
// console.log(j, offset, ch);
|
||||
for (var i = offset; i < offset+tickduration; i++) {
|
||||
var kk = k|0;
|
||||
var si = samp[kk];
|
||||
/*
|
||||
// bilinear filtering
|
||||
var sj = si;
|
||||
if (kk < sample_end-1)
|
||||
sj = samp[kk+1];
|
||||
var t = k - kk;
|
||||
si = t * sj + (1 - t) * si;
|
||||
dataL[i] += volL * si;
|
||||
dataR[i] += volR * si;
|
||||
*/
|
||||
dataL[i] += Filter(volL * si, ch.filter, ch.filterstate[0]);
|
||||
dataR[i] += Filter(volR * si, ch.filter, ch.filterstate[1]);
|
||||
k += dk;
|
||||
if (k >= sample_end) { // TODO: implement pingpong looping
|
||||
if (loop) {
|
||||
k -= looplen;
|
||||
} else {
|
||||
// kill sample
|
||||
channelinfo[j].inst = undefined;
|
||||
ch.inst = undefined;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
channelinfo[j].off = k;
|
||||
channelinfo[j].doff = dk;
|
||||
ch.off = k;
|
||||
ch.doff = dk;
|
||||
}
|
||||
offset += tickduration;
|
||||
cur_ticksamp += tickduration;
|
||||
|
|
@ -269,7 +296,9 @@ function playXM(arrayBuf) {
|
|||
tempo = dv.getUint16(0x4c, true);
|
||||
bpm = dv.getUint16(0x4e, true);
|
||||
for (var i = 0; i < nchan; i++) {
|
||||
channelinfo.push({})
|
||||
channelinfo.push({
|
||||
filterstate: [new Float32Array(3), new Float32Array(3)]
|
||||
})
|
||||
}
|
||||
console.log("header len " + hlen);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue