moe-bius/vgmplay/lxmplay/opmplay.cpp

430 lines
16 KiB
C++

#include <stdint.h>
#include "opmplay.h"
#include "opmfile.h"
// -------------------------
// file I/O procedures
static uint32_t opmplay_mem_read(opmplay_io_t* io, void* dst, uint32_t size) {
if ((io == NULL) || dst == NULL) return OPMPLAY_ERR_NULLPTR;
if ((size + io->offset) > io->size) return OPMPLAY_ERR_IO;
opmplay_memcpy(dst, (uint8_t*)io->buf + io->offset, io->size);
io->offset += io->size;
return size;
}
static uint32_t opmplay_mem_seek(opmplay_io_t* io, uint32_t pos) {
if (io == NULL) return OPMPLAY_ERR_NULLPTR;
if (pos > io->size) return OPMPLAY_ERR_IO;
io->offset = pos;
return 0;
}
#ifdef OPMPLAY_ENABLE_STDIO
static uint32_t opmplay_file_read(opmplay_io_t* io, void* dst, uint32_t size) {
return fread(dst, 1, size, io->io);
}
static uint32_t opmplay_file_seek(opmplay_io_t* io, uint32_t pos) {
return fseek(io->io, pos, SEEK_SET);
}
#endif
// -------------------------
int opmplay_init(opmplay_context_t* ctx, opl3_chip* chip) {
if ((ctx == NULL) || (chip == NULL)) return OPMPLAY_ERR_NULLPTR;
opmplay_memset(ctx, 0, sizeof(opmplay_context_t));
ctx->chip = chip;
return OPMPLAY_ERR_OK;
}
int opmplay_free(opmplay_context_t* ctx)
{
for (int ch = 0; ch < ctx->header.channels+1; ch++) {
if (ctx->channels[ch].stream.data != NULL) {
opmplay_memfree(ctx->channels[ch].stream.data); ctx->channels[ch].stream.data = NULL;
}
}
return OPMPLAY_ERR_OK;
}
// load file header
int opmplay_load_header(opmplay_context_t* ctx, opmplay_io_t* io) {
if ((ctx == NULL) || (io == NULL)) return OPMPLAY_ERR_NULLPTR;
// init file I/O handlers
switch (io->type) {
#ifdef OPMPLAY_ENABLE_STDIO
case OPMPLAY_IO_FILE:
io->read = &opmplay_file_read;
io->seek = &opmplay_file_seek;
break;
#endif
case OPMPLAY_IO_MEMORY:
io->read = &opmplay_mem_read;
io->seek = &opmplay_mem_seek;
break;
case OPMPLAY_IO_USER:
// use user-provided I/O functions
if ((io->read == NULL) || (io->seek == NULL)) return OPMPLAY_ERR_BAD_PARAMETER;
break;
default:
return OPMPLAY_ERR_BAD_PARAMETER;
}
// read header
if (io->read(io, &ctx->header, sizeof(ctx->header)) != sizeof(ctx->header)) return OPMPLAY_ERR_IO;
// and validate it
if ((opmplay_memcmp(ctx->header.magic, "OPM\x1A", sizeof(ctx->header.magic))) || (ctx->header.version.v != OPM_FORMAT_VERSION))
return OPMPLAY_ERR_BAD_FILE_STRUCTURE;
// done for now, waiting for opmplay_load_module :)
return OPMPLAY_ERR_OK;
}
int opmplay_load_module(opmplay_context_t* ctx, opmplay_io_t* io) {
if ((ctx == NULL) || (io == NULL)) return OPMPLAY_ERR_NULLPTR;
uint32_t filepos = sizeof(opm_header_t);
// allocate and copy stream data
opm_header_stream_desc_t* streamdesc = (opm_header_stream_desc_t*)opmplay_alloc(sizeof(opm_header_stream_desc_t) * (ctx->header.channels + 1));
if (streamdesc == NULL) return OPMPLAY_ERR_NULLPTR;
if (io->seek(io, filepos)) return OPMPLAY_ERR_IO;
if (io->read(io, streamdesc, sizeof(opm_header_stream_desc_t) * (ctx->header.channels + 1))
!= sizeof(opm_header_stream_desc_t) * (ctx->header.channels + 1))
return OPMPLAY_ERR_IO;
// allocate and copy channel streams
for (int ch = 0; ch < ctx->header.channels + 1; ch++) {
if (streamdesc[ch].size > 0) {
ctx->channels[ch].stream.data = (uint8_t*)opmplay_alloc(sizeof(uint8_t*) * (streamdesc[ch].size));
if (ctx->channels[ch].stream.data == NULL) return OPMPLAY_ERR_MEMALLOC;
if (io->read(io, ctx->channels[ch].stream.data, streamdesc[ch].size) != streamdesc[ch].size) return OPMPLAY_ERR_IO;
ctx->channels[ch].stream.delay = 1;
}
else ctx->channels[ch].stream.data = NULL;
}
// rewind to start
opmplay_rewind(ctx);
// done :)
return OPMPLAY_ERR_OK;
}
void opmplay_pop_stack(opmplay_channel_context_t* chctx) {
opmplay_channel_stack_t* st = chctx->stack + (--chctx->stack_pos);
chctx->stream.ptr = st->ptr;
chctx->stream.samples_to_play = st->frames_to_play;
}
void opmplay_push_stack(opmplay_channel_context_t* chctx) {
opmplay_channel_stack_t* st = chctx->stack + chctx->stack_pos;
st->ptr = chctx->stream.ptr;
st->frames_to_play = chctx->stream.samples_to_play;
chctx->stack_pos++;
}
int opmplay_loop(opmplay_context_t* ctx) {
// channel streams
for (int ch = 0; ch < ctx->header.channels + 1; ch++) {
// init stack
ctx->channels[ch].stack_pos = 0;
ctx->channels[ch].stream.samples_to_play = -1;
ctx->channels[ch].stream.ptr = ctx->channels[ch].stream.loop;
ctx->channels[ch].stream.delay = ctx->channels[ch].stream.reload = 1;
}
return OPMPLAY_ERR_OK;
}
int opmplay_rewind(opmplay_context_t* ctx) {
// set chip mode
switch (ctx->header.flags & OPM_FLAG_CHIP_TYPE) {
case OPM_FLAG_CHIP_OPL3:
// set OPL3 mode
OPL3_WriteRegBuffered(ctx->chip, 0x105, 0x01);
// disable 4-op
OPL3_WriteRegBuffered(ctx->chip, 0x104, 0x00);
// send key off for every channel
for (int ch = 0; ch < 9; ch++) {
OPL3_WriteRegBuffered(ctx->chip, 0x0B0 + ch, 0x00);
OPL3_WriteRegBuffered(ctx->chip, 0x1B0 + ch, 0x00);
}
break;
case OPM_FLAG_CHIP_OPL2:
// enable waveforms
OPL3_WriteRegBuffered(ctx->chip, 0x001, 0x20);
// send key off for every channel
for (int ch = 0; ch < 9; ch++) {
OPL3_WriteRegBuffered(ctx->chip, 0x0B0 + ch, 0x00);
}
break;
default: return OPMPLAY_ERR_DEVICE;
}
for (int ch = 0; ch < ctx->header.channels + 1; ch++) {
ctx->channels[ch].stream.loop = ctx->channels[ch].stream.data;
}
ctx->_4op = 0;
opmplay_loop(ctx);
return OPMPLAY_ERR_OK;
}
// get and parse delay
static uint32_t opmplay_set_delay(uint8_t** data) {
uint32_t delay = 0;
if (**data == OPM_STREAM_DELAY_INT32) {
delay = (
(*(*data + 1) << 0) |
(*(*data + 2) << 8) |
(*(*data + 3) << 16) |
(*(*data + 4) << 24)
);
*data += 5;
}
else if (**data == OPM_STREAM_DELAY_INT16) {
delay = (
(*(*data + 1) << 0) |
(*(*data + 2) << 8)
);
*data += 3;
}
else if ((**data & 0xF0) == OPM_STREAM_DELAY_INT12) {
delay = ((**data & 0x0F) << 8) | (*(*data + 1));
*data += 2;
}
else if ((**data & 0xF0) == OPM_STERAM_DELAY_SHORT) {
delay = (**data & 0xF) + 1;
(*data)++;
}
return delay;
}
// translation tables
static int opmplay_opregs_channel_offset[] = {
0x000, 0x001, 0x002, 0x008, 0x009, 0x00A, 0x010, 0x011, 0x012,
0x100, 0x101, 0x102, 0x108, 0x109, 0x10A, 0x110, 0x111, 0x112,
};
static int opmplay_regs_channel_offset[] = {
0x000, 0x001, 0x002, 0x003, 0x004, 0x005, 0x006, 0x007, 0x008,
0x100, 0x101, 0x102, 0x103, 0x104, 0x105, 0x106, 0x107, 0x108,
};
static int opmplay_4op_bit_offset[] = {
1<<0, 1<<1, 1<<2, 0, 0, 0, 0, 0, 0,
1<<3, 1<<4, 1<<5, 0, 0, 0, 0, 0, 0,
};
static int opmplay_ch_parsing_order[] = {
3, 0, 4, 1, 5, 2, 6, 7, 8,
12, 9, 13, 10, 14, 11, 15, 16, 17
};
int opmplay_tick(opmplay_context_t* ctx) {
int ch = 0;
int rtn = OPMPLAY_ERR_OK;
uint32_t newdelay = 0;
// parse channel streams
for (int i = 0; i < ctx->header.channels; i++) {
int ch = opmplay_ch_parsing_order[i]; // mangle channel order to make 4op mode work
opmplay_channel_context_t* chctx = ctx->channels + ch + 1;
uint8_t* data = chctx->stream.ptr; if (data == NULL) continue;
bool isRun = true;
if (--chctx->stream.delay == 0) {
while (isRun) {
// get streams
if ((*(data) & 0x80) == OPM_SET_OPERATOR) {
int mask = *data;
int op = (mask & OPM_CMD00_SELECT_OPERATOR ? 3 : 0) + opmplay_opregs_channel_offset[ch];
data++;
if (mask & OPM_CMD00_SET_MULT) OPL3_WriteRegBuffered(ctx->chip, 0x20 + op, *data++);
if (mask & OPM_CMD00_SET_TL) OPL3_WriteRegBuffered(ctx->chip, 0x40 + op, *data++);
if (mask & OPM_CMD00_SET_AD) OPL3_WriteRegBuffered(ctx->chip, 0x60 + op, *data++);
if (mask & OPM_CMD00_SET_SR) OPL3_WriteRegBuffered(ctx->chip, 0x80 + op, *data++);
if (mask & OPM_CMD00_SET_WAVEFORM) OPL3_WriteRegBuffered(ctx->chip, 0xE0 + op, *data++);
if (mask & OPM_CMD00_END_OF_FRAME) isRun = false;
continue;
}
if ((*(data) & 0xC0) == OPM_SET_FREQ_FB_VOL) {
int mask = *data;
data++;
if (mask & OPM_CMD80_SET_TL0) OPL3_WriteRegBuffered(ctx->chip, 0x40 + opmplay_opregs_channel_offset[ch], *data++);
if (mask & OPM_CMD80_SET_TL1) OPL3_WriteRegBuffered(ctx->chip, 0x43 + opmplay_opregs_channel_offset[ch], *data++);
if (mask & OPM_CMD80_SET_FEEDBACK) OPL3_WriteRegBuffered(ctx->chip, 0xC0 + opmplay_regs_channel_offset[ch], *data++);
if (mask & OPM_CMD80_SET_FREQ) OPL3_WriteRegBuffered(ctx->chip, 0xA0 + opmplay_regs_channel_offset[ch], *data++);
if (mask & OPM_CMD80_SET_KEYBLOCK) { chctx->block = *data; OPL3_WriteRegBuffered(ctx->chip, 0xB0 + opmplay_regs_channel_offset[ch], *data++); };
if (mask & OPM_CMD80_END_OF_FRAME) isRun = false;
continue;
}
if ((*data & 0xFC) == OPM_KEY_TRIGGER) {
if (*data & OPM_KEY_ON) chctx->block |= (1 << 5); else chctx->block &= ~(1 << 5);
OPL3_WriteRegBuffered(ctx->chip, 0xB0 + opmplay_regs_channel_offset[ch], chctx->block);
if (*data & OPM_KEY_END_OF_FRAME) isRun = false;
data++;
continue;
}
if ((*data & 0xFE) == OPM_4OP_TRIGGER) {
if (opmplay_4op_bit_offset[ch] != 0) {
if (*data & OPM_4OP_ON) ctx->_4op |= opmplay_4op_bit_offset[ch]; else ctx->_4op &= ~opmplay_4op_bit_offset[ch];
OPL3_WriteRegBuffered(ctx->chip, 0x104, ctx->_4op);
}
data++;
continue;
}
if ((*data & 0xF0) == OPM_STREAM_BACKREF) {
// back reference, nested call :)
int distance = ((*(data + 0) & 0x0F) << 8) | (*(data + 1));
int frames_to_play = *(data + 2);
chctx->stream.ptr = data + 3;
opmplay_push_stack(chctx);
data -= distance;
chctx->stream.samples_to_play = frames_to_play; // hack?
continue;
}
// check for common stuff
switch (*data) {
case OPM_STREAM_END:
// end of current stream, delay forever
chctx->stream.reload = -1;
isRun = false;
break;
// just an NOP, break
case OPM_STREAM_NEW_ORDER:
case OPM_STREAM_NOP:
data++;
break;
case OPM_STREAM_LOOP:
// save loop point
chctx->stream.loop = data;
data++;
break;
case OPM_STREAM_END_FRAME:
// end of frame - special case here
data++;
isRun = false;
break;
default:
// test for delay
newdelay = opmplay_set_delay(&data);
if (newdelay > 0) {
chctx->stream.reload = newdelay;
}
else
{
printf("unknonw token %02x!\n", *data);
return OPMPLAY_ERR_BAD_FILE_STRUCTURE;
}
}
}
chctx->stream.delay = chctx->stream.reload;
// decrement samples to play counter
if (--chctx->stream.samples_to_play == 0) {
// pop context from the stack
do opmplay_pop_stack(chctx); while (--chctx->stream.samples_to_play == 0);
}
else {
// save data pointer
chctx->stream.ptr = data;
}
}
}
// process control stream
{
opmplay_channel_context_t* chctx = ctx->channels + 0;
uint8_t* data = chctx->stream.ptr;
bool isRun = true;
if (--chctx->stream.delay == 0) {
while (isRun) {
if ((*(data) & 0xE0) == OPM_CTRL_REG_SET) {
int mask = *data;
data++;
if (mask & OPM_CTRL_SET_REG01) OPL3_WriteRegBuffered(ctx->chip, 0x001, *data++);
if (mask & OPM_CTRL_SET_REG08) OPL3_WriteRegBuffered(ctx->chip, 0x008, *data++);
if (mask & OPM_CTRL_SET_REG105) OPL3_WriteRegBuffered(ctx->chip, 0x105, *data++);
if (mask & OPM_CTRL_SET_REG104) OPL3_WriteRegBuffered(ctx->chip, 0x104, *data++);
if (mask & OPM_CTRL_SET_REGBD) OPL3_WriteRegBuffered(ctx->chip, 0x0BD, *data++);
continue;
}
if ((*data & 0xF0) == OPM_STREAM_BACKREF) {
// back reference, nested call :)
int distance = ((*(data + 0) & 0x0F) << 8) | (*(data + 1));
int frames_to_play = *(data + 2);
chctx->stream.ptr = data + 3;
opmplay_push_stack(chctx);
data -= distance;
chctx->stream.samples_to_play = frames_to_play; // hack?
continue;
}
// check for common stuff
switch (*data) {
// end of stream - rewind everything
case OPM_STREAM_END:
opmplay_loop(ctx);
isRun = false;
return OPMPLAY_ERR_END_OF_STREAM;
// just an NOP, break
case OPM_STREAM_NEW_ORDER:
case OPM_STREAM_NOP:
data++;
break;
case OPM_STREAM_LOOP:
// save loop point
ctx->pos.looped = ctx->pos.frame;
chctx->stream.loop = data;
data++;
break;
// set new frame rate
case OPM_STREAM_SET_FRAME_RATE:
ctx->header.frame_rate = *(uint16_t*)(data + 1); data += 3;
break;
case OPM_STREAM_END_FRAME:
// end of frame - special case here
data++;
isRun = false;
break;
default:
// test for delay
newdelay = opmplay_set_delay(&data);
if (newdelay > 0) {
chctx->stream.reload = newdelay;
}
else
{
printf("unknonw token %02x!\n", *data);
return OPMPLAY_ERR_BAD_FILE_STRUCTURE;
}
}
}
chctx->stream.delay = chctx->stream.reload;
// decrement samples to play counter
if (--chctx->stream.samples_to_play == 0) {
// pop context from the stack
do opmplay_pop_stack(chctx); while (--chctx->stream.samples_to_play == 0);
}
else {
// save data pointer
chctx->stream.ptr = data;
}
}
}
ctx->pos.frame++;
return rtn;
}