#include "qms.h"
typedef struct TrackState {
int pac; /* < NPACS */
int atk; /* attack duration in samples */
int vol; /* 127-0 default (zero) means maximum */
int pan; /* (-64)-(+63) default (zero) means center */
} TrackState;
typedef struct VoiceState {
unsigned int phase_acc; /* fixed point with NEXP.16 precision */
unsigned int phase_step; /* fixed point with NEXP.16 precision */
int velocity; /* 0x00-0x7F */
int pitch; /* 0x00-0x7F */
int wheel; /* S7.8 default (zero) means center */
int note_age; /* for amplitude envelope */
/* current computed amplitude (for smoothing) - left & right */
int lamp, ramp;
} VoiceState;
static int16_t wavetables[NPACS][N];
static TrackState tracks[NTRACKS];
static VoiceState voices[NTRACKS][NVOICES];
static int32_t windows[NTRACKS][NVOICES][K][2];
/* sine approximation using Bhaskara I's formula
* input is in [0;2048]
* output is in [-INT16_MAX;INT16_MAX] */
static int16_t
sinb1(unsigned int i)
{
uint32_t c, n, d;
int16_t s = 1 - (i >> 10 << 1);
i &= 1023;
c = i*(46080 - 45*i);
n = 360*c;
d = 162005 - (90*c >> 15);
return s * (int16_t) (n/d);
}
/* approximation of exp(x)-1 using power series
* both input and output are U0.32 */
uint32_t
fxp_expm1(uint32_t x)
{
uint32_t e, t;
int i;
e = 0;
t = x;
for (i = 2; t; i++) {
e += t;
t = (t>>13) * (x>>19) / i;
}
return e;
}
void
qms_init()
{
unsigned int i;
int32_t tmp;
for (i = 0; i < N; i++) {
/* sawtooth */
tmp = (i * INT16_MAX >> (NEXP-1)) - INT16_MAX;
wavetables[0][i] = (int16_t) tmp;
}
for (i = 0; i < N; i++) {
/* sine */
wavetables[1][i] = sinb1(i);
}
}
void
qms_setpac(int track, int pac)
{
tracks[track].pac = pac;
tracks[track].atk = 900;
}
void
qms_setvol(int track, int midivol)
{
tracks[track].vol = 127 - midivol;
}
void
qms_setpan(int track, int midipan)
{
tracks[track].pan = midipan - 64;
}
static unsigned int
midipitch2step(int m, int w)
{
unsigned int o, n; /* m = o * 12 + n */
int neg_wheel;
uint32_t nwc;
uint32_t c = 0x0EC98200; /* ln(2^(1/12)) in U0.32 */
neg_wheel = w & 0x8000;
if (neg_wheel) {
w = 0x10000 - w;
m -= w >> 8;
} else {
m += w >> 8;
}
w &= 0xFF;
for (n = m+3, o = 0; n >= 12; n -= 12, o++) ;
/* the following takes advantage of assumed constants:
* R = 44100 = 440 * 2205 / 22
* N = 2048 = 2^11 */
if (neg_wheel)
nwc = ((n<<8)-w)*(c>>8);
else
nwc = ((n<<8)+w)*(c>>8);
return ((fxp_expm1(nwc)>>5) * 22 / (2205<<6) + (22<<21) / 2205) << o;
}
void
qms_setvelocity(int track, int voice, int velocity)
{
voices[track][voice].velocity = velocity;
}
void
qms_setnote(int track, int voice, int midipitch)
{
int wheel = voices[track][voice].wheel;
voices[track][voice].phase_step = midipitch2step(midipitch, wheel);
voices[track][voice].pitch = midipitch;
voices[track][voice].note_age = 0;
}
void
qms_setwheel(int track, int voice, int wheel)
{
int midipitch = voices[track][voice].pitch;
voices[track][voice].phase_step = midipitch2step(midipitch, wheel);
voices[track][voice].wheel = wheel;
}
void
qms_advance(unsigned int sample_i, unsigned int nsamples)
{
int32_t left, right;
int32_t wave_sample;
int16_t *pac;
int32_t tlamp, tramp; /* 0 - 2^14 */
int32_t vamp; /* 0 - 2^14 */
int32_t vlamp, vramp; /* 0 - 2^28 */
int32_t *win_addr;
TrackState *track;
VoiceState *voice;
int ti, vi;
for (; nsamples; nsamples--, sample_i++) {
left = right = 0;
for (ti = 0; ti < NTRACKS; ti++) {
track = &tracks[ti];
tlamp = (63 - track->pan) * (127 - track->vol);
tramp = (63 + track->pan) * (127 - track->vol);
pac = wavetables[track->pac];
for (vi = 0; vi < NVOICES; vi++) {
voice = &voices[ti][vi];
win_addr = windows[ti][vi][sample_i & (K-1)];
vamp = voice->velocity;
if (voice->note_age++ < track->atk)
vamp = vamp * (voice->note_age * 0x7F / track->atk);
else
vamp = vamp * 0x7F;
vlamp = tlamp * vamp;
vramp = tramp * vamp;
voice->lamp += vlamp - win_addr[0];
win_addr[0] = vlamp;
voice->ramp += vramp - win_addr[1];
win_addr[1] = vramp;
wave_sample = pac[voice->phase_acc >> 16];
//~ left += wave_sample * (vlamp >> 12) >> 16;
//~ right += wave_sample * (vramp >> 12) >> 16;
left += wave_sample * (voice->lamp >> 16) >> 16;
right += wave_sample * (voice->ramp >> 16) >> 16;
voice->phase_acc += voice->phase_step;
voice->phase_acc &= (1 << (NEXP + 16)) - 1;
}
}
/* saturate before casting down to 16-bit to avoid bad behavior on overflow */
left = left < INT16_MIN ? INT16_MIN : ( left > INT16_MAX ? INT16_MAX : left);
right = right < INT16_MIN ? INT16_MIN : (right > INT16_MAX ? INT16_MAX : right);
qms_putsample((int16_t) left, (int16_t) right);
}
}
int
qms_runevent(Event *ev)
{
EvType ev_type;
unsigned int track, voice, arg;
int end = 0;
track = ev->event >> 28;
voice = ev->event >> 24 & 7;
ev_type = ev->event >> 16 & 0xFF;
arg = ev->event & 0xFFFF;
switch (ev_type) {
case END:
end = 1;
break;
case PAC:
qms_setpac(track, arg);
break;
case VOL:
qms_setvol(track, arg);
break;
case PAN:
qms_setpan(track, arg);
break;
case VEL:
qms_setvelocity(track, voice, arg);
break;
case PITCH:
qms_setnote(track, voice, arg);
break;
case WHEEL:
qms_setwheel(track, voice, arg);
}
return end;
}
void
qms_runevents(Event *evs, unsigned int nevs)
{
uint32_t total_samples = 0;
for (; nevs--; evs++) {
qms_advance(total_samples, evs->offset - total_samples);
total_samples = evs->offset;
if (qms_runevent(evs))
break;
}
}
void
qms_load(Seeker *seeker, Event *evs, unsigned int nevs)
{
seeker->evs = evs;
seeker->nevs = nevs;
seeker->ev_i = 0;
seeker->smp_i = 0;
seeker->dur = 15;
}
void
qms_seek(Seeker *seeker, unsigned int nsamples)
{
for (seeker->ev_i = 0; seeker->ev_i < seeker->nevs; seeker->ev_i++) {
if (seeker->evs[seeker->ev_i].offset > nsamples)
break;
qms_runevent(&seeker->evs[seeker->ev_i]);
}
seeker->smp_i = nsamples;
}
#define stretch(nsmp) ((nsmp) * (seeker->dur+1) >> 4)
int
qms_play(Seeker *seeker, unsigned int nsamples)
{
unsigned int nadv;
while (seeker->smp_i + nsamples >= seeker->evs[seeker->ev_i].offset) {
nadv = seeker->evs[seeker->ev_i].offset - seeker->smp_i;
qms_advance(stretch(seeker->smp_i), stretch(nadv));
qms_runevent(&seeker->evs[seeker->ev_i]);
seeker->ev_i++;
if (seeker->ev_i == seeker->nevs)
return 1;
nsamples -= nadv;
seeker->smp_i += nadv;
}
qms_advance(stretch(seeker->smp_i), stretch(nsamples));
seeker->smp_i += nsamples;
return 0;
}