309 lines
7.1 KiB
C
309 lines
7.1 KiB
C
|
|
typedef struct {
|
|
const char *devname;
|
|
uint samplerate;
|
|
uint frames;
|
|
uint bufsize;
|
|
uint width;
|
|
} snd_devconfig;
|
|
|
|
enum snd_cmd {
|
|
SND_CMD_STOP,
|
|
SND_CMD_FLUSH,
|
|
SND_CMD_QUERY,
|
|
SND_CMD_RESET,
|
|
SND_CMD_MKOPL,
|
|
SND_CMD_VOICES,
|
|
// SND_CMD_BANK,
|
|
SND_CMD_PAUSE,
|
|
SND_CMD_LOAD,
|
|
SND_CMD_OFF,
|
|
SND_CMD_DEBUG,
|
|
SND_CMD_WAVECAP,
|
|
SND_CMD_VOLUME
|
|
};
|
|
|
|
enum snd_stat {
|
|
SND_STAT_RUN,
|
|
SND_STAT_STOPPED
|
|
};
|
|
|
|
ulong snd_process() {
|
|
ulong elapsed = 1000ULL;
|
|
if(!sgt.paused) {
|
|
if(sgt.mid.track && (sgt.playing ^ 1) && (sgt.nowait || !(sgt.chip) || !OPL3_Playing(sgt.chip))) {
|
|
// flush ?
|
|
if(sgt.mid.track) {
|
|
free(sgt.mid.track);
|
|
sgt.mid.track = NULL;
|
|
}
|
|
if(sgt.chip) {
|
|
OPL3_Reset(sgt.chip);
|
|
bank_reset(sgt.chip, sgt.bank);
|
|
}
|
|
sgt.mid_tick = 0;
|
|
sgt.mid_time = 0ULL;
|
|
sgt.stopped = 1;
|
|
}
|
|
else if(sgt.mid.track && sgt.playing && mid_tick(&sgt.mid, sgt.bank, sgt.chip)) {
|
|
elapsed = ((ulong)sgt.mid.ticktime) * 1000ULL;
|
|
sgt.mid_tick += 1;
|
|
sgt.mid_time += ((ulong)sgt.mid.ticktime);
|
|
}
|
|
else if(sgt.playing) {
|
|
sgt.playing = 0;
|
|
if(sgt.bank)
|
|
bank_alloff(sgt.bank);
|
|
}
|
|
}
|
|
// sampling ...
|
|
return elapsed;
|
|
}
|
|
|
|
void snd_generate(short *sample) {
|
|
int mix[2];
|
|
if(!sgt.paused && sgt.chip)
|
|
OPL3_GenerateResampled(sgt.chip, &sample[SND_VOL_MUSIC*2]);
|
|
else
|
|
sample[SND_VOL_MUSIC*2+0] = sample[SND_VOL_MUSIC*2+1] = 0;
|
|
// sampling ...
|
|
sample[SND_VOL_SFX*2+0] = sample[SND_VOL_SFX*2+1] = 0;
|
|
sample[SND_VOL_GUI*2+0] = sample[SND_VOL_GUI*2+1] = 0;
|
|
mix[0] = mix[1] = 0;
|
|
for(int z = 1; z < SND_VOLUMES; z++) {
|
|
mix[0] += (short)(int)(((int)sample[z*2+0] * (int)sgt.volumes[z]) / 32767);
|
|
mix[1] += (short)(int)(((int)sample[z*2+1] * (int)sgt.volumes[z]) / 32767);
|
|
}
|
|
sample[0] = (short)(int)((mix[0] * (int)sgt.volumes[0]) / 32767);
|
|
sample[1] = (short)(int)((mix[1] * (int)sgt.volumes[0]) / 32767);
|
|
}
|
|
|
|
void snd_dispatch(void *buf, short op, short addr, uint param, uint rate) {
|
|
switch(op) {
|
|
case SND_CMD_PAUSE:
|
|
sgt.paused = (byte)param;
|
|
break;
|
|
case SND_CMD_LOAD:
|
|
// mid_cmlog();
|
|
sgt.playing = 0;
|
|
if(sgt.mid.track) {
|
|
free(sgt.mid.track);
|
|
sgt.mid.track = NULL;
|
|
}
|
|
if(buf && sgt.chip) {
|
|
sgt.mid.tracks = param & 0xffff;
|
|
sgt.mid.tpqn = (param >> 16) & 0x7fff;
|
|
mid_settempo(&sgt.mid, MID_DEFTEMPO);
|
|
sgt.mid.track = (mid_track *)buf;
|
|
buf += (sizeof(mid_track) * sgt.mid.tracks);
|
|
for(int z = 0; z < sgt.mid.tracks; z++) {
|
|
sgt.mid.track[z].buffer = buf;
|
|
buf += sgt.mid.track[z].size;
|
|
}
|
|
sgt.playing = 1;
|
|
sgt.nowait = (byte)(param >> 31);
|
|
}
|
|
else if(buf) {
|
|
free(buf);
|
|
}
|
|
case SND_CMD_RESET:
|
|
if(sgt.chip) {
|
|
OPL3_Reset(sgt.chip);
|
|
bank_reset(sgt.chip, sgt.bank);
|
|
}
|
|
sgt.mid_tick = 0;
|
|
sgt.mid_time = 0ULL;
|
|
break;
|
|
case SND_CMD_OFF:
|
|
if(sgt.mid.track)
|
|
bank_alloff(sgt.bank);
|
|
break;
|
|
case SND_CMD_MKOPL:
|
|
if(sgt.chip) {
|
|
free(sgt.bank);
|
|
free(sgt.chip);
|
|
}
|
|
sgt.chip = param ? OPL3_Alloc(rate, param & 0xff) : NULL;
|
|
sgt.bank = param ? bank_alloc(sgt.chip, sgt.ibank, (param >> 16) & 1, (param >> 17) & 1, (param >> 8) & 0xff) : NULL;
|
|
sgt.stopped = param ? 1 : 0;
|
|
break;
|
|
case SND_CMD_VOICES:
|
|
if(buf) {
|
|
memcpy(sgt.ibank, buf, sizeof(bank_instr) * 256);
|
|
free(buf);
|
|
}
|
|
// else {
|
|
// memcpy(sgt.ibank, snd_banks[addr], sizeof(bank_instr) * 256);
|
|
// }
|
|
break;
|
|
// case SND_CMD_BANK:
|
|
// sgt.instr = addr >= 31 ? sgt.ibank : (bank_instr *)snd_banks[addr];
|
|
// break;
|
|
case SND_CMD_DEBUG:
|
|
sgt.log_debug = (byte)param;
|
|
break;
|
|
case SND_CMD_WAVECAP:
|
|
if(sgt.capture)
|
|
wav_close(&sgt.wav);
|
|
sgt.capture = buf && wav_open(&sgt.wav, (char *)buf, rate, 2, 2);
|
|
if(buf)
|
|
free(buf);
|
|
break;
|
|
case SND_CMD_VOLUME:
|
|
sgt.volumes[addr] = (ushort)param;
|
|
// snd_logd("V %d = %d", addr, param);
|
|
break;
|
|
}
|
|
}
|
|
|
|
uint snd_status(short addr, uint param) {
|
|
switch(addr) {
|
|
case SND_STAT_STOPPED:
|
|
param = sgt.stopped;
|
|
sgt.stopped = 0;
|
|
return param;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void *snd_loop(void *arg) {
|
|
snd_devconfig *cfg = (snd_devconfig*)arg;
|
|
aud_handle handle;
|
|
uint pop = 0;
|
|
short sample[2 * SND_VOLUMES];
|
|
uint rate = strcmp(cfg->devname, "none") ? aud_open_dev(&handle, cfg->devname, cfg->samplerate, 2, (byte)cfg->width, cfg->frames, cfg->bufsize) : 0;
|
|
ulong sampletime = rate ? (1000000000ULL / (ulong)rate) : 100000000000000000ULL;
|
|
byte running = 1;
|
|
ulong elapsed = 0;
|
|
ulong samples = 0;
|
|
sample[0] = sample[1] = 0;
|
|
while(running) {
|
|
pthread_mutex_lock(&sgt.lock);
|
|
while(sgt.cmd_queue) {
|
|
snd_cmd_t *cmd = &sgt.cmds[pop];
|
|
switch(cmd->command) {
|
|
case SND_CMD_STOP:
|
|
running = 0;
|
|
break;
|
|
case SND_CMD_FLUSH:
|
|
|
|
elapsed = 0;
|
|
if(rate)
|
|
aud_flush(&handle);
|
|
break;
|
|
case SND_CMD_QUERY:
|
|
switch(cmd->address) {
|
|
case SND_STAT_RUN:
|
|
sgt.query = rate;
|
|
break;
|
|
default:
|
|
sgt.query = snd_status(cmd->address, cmd->param);
|
|
break;
|
|
}
|
|
sgt.waiting = 0;
|
|
break;
|
|
default:
|
|
snd_dispatch(cmd->buffer, cmd->command, cmd->address, cmd->param, rate);
|
|
break;
|
|
}
|
|
if(++pop == SND_QUEUE)
|
|
pop = 0;
|
|
sgt.cmd_queue -= 1;
|
|
}
|
|
pthread_mutex_unlock(&sgt.lock);
|
|
if(sgt.interrupt)
|
|
sgt.interrupt = 0;
|
|
else
|
|
elapsed += snd_process();
|
|
// if((elapsed < sampletime) || !rate)
|
|
// usleep(1000);
|
|
while((elapsed >= sampletime) && !sgt.interrupt) {
|
|
snd_generate(sample);
|
|
if(rate)
|
|
aud_push(&handle, sample);
|
|
if(sgt.capture)
|
|
wav_write16(&sgt.wav, sample, 1);
|
|
samples++;
|
|
elapsed -= sampletime;
|
|
}
|
|
}
|
|
if(rate) {
|
|
aud_flush(&handle);
|
|
aud_close(&handle);
|
|
}
|
|
if(sgt.mid.track)
|
|
free(sgt.mid.track);
|
|
if(sgt.chip) {
|
|
free(sgt.bank);
|
|
free(sgt.chip);
|
|
}
|
|
sgt.query = rate;
|
|
sgt.waiting = 1;
|
|
}
|
|
|
|
void snd_instruct(const void *buf, uint size, short op, short addr, uint param) {
|
|
if(sgt.disabled)
|
|
return;
|
|
sgt.interrupt = 1;
|
|
while(sgt.cmd_queue == SND_QUEUE) {
|
|
;
|
|
}
|
|
pthread_mutex_lock(&sgt.lock);
|
|
sgt.interrupt = 1;
|
|
snd_cmd_t *cmd = &sgt.cmds[sgt.cmd_push];
|
|
cmd->command = op;
|
|
cmd->address = addr;
|
|
cmd->param = param;
|
|
if(buf) {
|
|
cmd->buffer = malloc(size);
|
|
memcpy(cmd->buffer, buf, size);
|
|
}
|
|
else {
|
|
cmd->buffer = NULL;
|
|
}
|
|
if(++(sgt.cmd_push) == SND_QUEUE)
|
|
sgt.cmd_push = 0;
|
|
sgt.cmd_queue += 1;
|
|
pthread_mutex_unlock(&sgt.lock);
|
|
}
|
|
|
|
uint snd_query(short addr, uint param) {
|
|
if(sgt.disabled || sgt.waiting)
|
|
return 0;
|
|
sgt.waiting = 1;
|
|
snd_instruct(NULL, 0, SND_CMD_QUERY, addr, param);
|
|
while(sgt.waiting) {
|
|
;
|
|
}
|
|
return sgt.query;
|
|
}
|
|
|
|
uint snd_init(const char *devname, uint samplerate, uint width, uint frames, uint bufsize, byte disabled) {
|
|
if(disabled) {
|
|
sgt.disabled = 1;
|
|
return 0;
|
|
}
|
|
snd_devconfig snd_cfg;
|
|
snd_cfg.devname = devname;
|
|
snd_cfg.samplerate = samplerate;
|
|
snd_cfg.width = ((width == 32) ? 32 : 16) / 8;
|
|
snd_cfg.frames = frames;
|
|
snd_cfg.bufsize = bufsize;
|
|
pthread_mutex_init(&sgt.lock, NULL);
|
|
pthread_mutex_init(&sgt.log_lock, NULL);
|
|
pthread_create(&sgt.thread, NULL, snd_loop, &snd_cfg);
|
|
return snd_query(SND_STAT_RUN, 0);
|
|
}
|
|
|
|
uint snd_end() {
|
|
if(sgt.disabled)
|
|
return 0;
|
|
snd_instruct(NULL, 0, SND_CMD_STOP, 0, 0);
|
|
while(sgt.waiting ^ 1) {
|
|
;
|
|
}
|
|
pthread_mutex_destroy(&sgt.lock);
|
|
pthread_mutex_destroy(&sgt.log_lock);
|
|
return sgt.query;
|
|
}
|