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; }