2021-01-01 20:44:05 (UTC-03:00)
Marcel Rodrigues <marcelgmr@gmail.com>
first commit minimal core with sawtooth and sine waves no front-end no back-end
diff --git a/qms.c b/qms.c new file mode 100644 index 0000000..735e4e6 --- /dev/null +++ b/qms.c @@ -0,0 +1,122 @@ +#include "qms.h" + +typedef struct TrackState { + int pac; /* < NPACS */ + int vol; /* 0-127 */ + int pan; /* (-64)-(+63) */ +} TrackState; + +typedef struct VoiceState { + unsigned int phase_acc; /* fixed point with NEXP.NEXP precision */ + unsigned int phase_step; /* fixed point with NEXP.NEXP precision */ + int velocity; /* 0-127 */ + /* int note_age; */ +} VoiceState; + +static int16_t wavetables[NPACS][N]; +static TrackState tracks[NTRACKS]; +static VoiceState voices[NTRACKS][NVOICES]; + +/* integer frequencies of MIDI notes 0-11 multiplied by N */ +/* to be used as fixed point with NEXP.NEXP precision */ +/* python: [int(440 * 2**((m-69)/12) * N +0.5) for m in range(12)] */ +static int freq[12] = + {16744, 17740, 18795, 19912, 21096, 22351, + 23680, 25088, 26580, 28160, 29834, 31609}; + +/* 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); +} + +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; +} + +void +qms_setvolume(int track, int midivol) +{ + tracks[track].vol = midivol; +} + +void +qms_setpan(int track, int midipan) +{ + tracks[track].pan = midipan - 64; +} + +static int +midipitch2step(int m) +{ + int o, n; /* m = o * 12 + n */ + for (n = m, o = 0; n >= 12; n -= 12, o++) ; + return (freq[n] << (o+NEXP)) / R; +} + +void +qms_setnote(int track, int voice, int velocity, int midipitch) +{ + voices[track][voice].velocity = velocity; + voices[track][voice].phase_step = midipitch2step(midipitch); +} + +void +qms_advance(int nsamples) +{ + int32_t left, right; + int32_t amp; + int16_t *pac; + int lvol, rvol; /* 0 - 2^14 */ + TrackState *track; + VoiceState *voice; + int ti, vi; + for (; nsamples; nsamples--) { + left = right = 0; + for (ti = 0; ti < NTRACKS; ti++) { + track = &tracks[ti]; + lvol = (63 - track->pan) * track->vol; + rvol = (63 + track->pan) * track->vol; + pac = wavetables[track->pac]; + for (vi = 0; vi < NVOICES; vi++) { + voice = &voices[ti][vi]; + amp = pac[voice->phase_acc >> NEXP] * voice->velocity >> 7; + left += amp * lvol >> 14; + right += amp * rvol >> 14; + voice->phase_acc += voice->phase_step; + voice->phase_acc &= (1 << (NEXP << 1)) - 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); + } +} diff --git a/qms.h b/qms.h new file mode 100644 index 0000000..daa4885 --- /dev/null +++ b/qms.h @@ -0,0 +1,21 @@ +#include <stdint.h> + +#define NTRACKS 16 +#define NVOICES 8 + +#define NPACS 2 + +/* N = 2^NEXP */ +#define NEXP 11 +/* wavetable size in samples */ +#define N (1 << NEXP) +/* sample rate in samples per second*/ +#define R 44100 + +void qms_init(); +void qms_setpac(int track, int pac); +void qms_setvolume(int track, int midivol); +void qms_setpan(int track, int midipan); +void qms_setnote(int track, int voice, int velocity, int midipitch); +void qms_advance(int nsamples); +void qms_putsample(int16_t left, int16_t right);