moe-bius/opmplay/lxmplay/opmplay.cpp

422 lines
14 KiB
C++

#include <stdint.h>
#include "opmplay.h"
#include "opmfile.h"
// very hacky extern lmao
void opn_write_reg(int chip, int reg, int data);
// -------------------------
// 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) {
if ((ctx == NULL)) return OPMPLAY_ERR_NULLPTR;
opmplay_memset(ctx, 0, sizeof(opmplay_context_t));
return OPMPLAY_ERR_OK;
}
int opmplay_free(opmplay_context_t* ctx)
{
for (int ch = 0; ch < ctx->header.streams; ch++) {
if (ctx->channels[ch].stream.data != NULL) {
opmplay_memfree((uint8_t*)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.streams));
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.streams))
!= sizeof(opm_header_stream_desc_t) * (ctx->header.streams))
return OPMPLAY_ERR_IO;
// allocate and copy channel streams
for (int ch = 0; ch < ctx->header.streams; ch++) {
if (streamdesc[ch].size > 0) {
ctx->channels[ch].stream.data = (const 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, (uint8_t*)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.streams; 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_OPN_DUAL:
for (int chip = 0; chip < 2; chip++) {
for (int r = 0; r < 0xF0; r++) {
//opn_write_reg(chip, r, 0);
}
// setup prescaler
opn_write_reg(chip, 0x2F, 0);
opn_write_reg(chip, 0x2D, 0);
}
break;
default:
return OPMPLAY_ERR_DEVICE;
}
for (int ch = 0; ch < ctx->header.streams; ch++) {
ctx->channels[ch].stream.loop = ctx->channels[ch].stream.data;
}
opmplay_loop(ctx);
return OPMPLAY_ERR_OK;
}
// get and parse delay
static uint32_t opmplay_set_delay(const 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;
}
// ------------------------------------------
// stream parsing procedures
// bruuuuuuuhhhhhhhhhhhh
extern "C" {
int chip_index;
bool endOfFrame;
bool doNextOp;
}
// FM control stream
const uint8_t* opmplay_parse_fm_control_stream(opmplay_context_t* ctx, opmplay_channel_context_t* chctx, int ch, const uint8_t* data) {
if ((*(data) & 0xE0) == OPM_CTRL_TIMER_CSM) {
// CSM/timer stuff
int mask = *data;
data++;
if (mask & OPM_CTRL_CMD80_REG25) opn_write_reg(chip_index, 0x25, *data++);
if (mask & OPM_CTRL_CMD80_REG24) opn_write_reg(chip_index, 0x24, *data++);
if (mask & OPM_CTRL_CMD80_REG27) opn_write_reg(chip_index, 0x27, *data++);
if (mask & OPM_CTRL_CMD80_REG22) opn_write_reg(chip_index, 0x22, *data++);
if (mask & OPM_CTRL_CMD80_EOF) endOfFrame = true;
doNextOp = true;
goto done;
}
if ((*(data) & 0x80) == OPM_CTRL_EXTCH3) {
// channel 3 ext mode
// OP4 freq is handled by FM channel 3 stream
int mask = *data;
data++;
if (mask & OPM_CTRL_EXTCH3_OP1_HIGH) opn_write_reg(chip_index, 0xAD, *data++);
if (mask & OPM_CTRL_EXTCH3_OP1_LOW) opn_write_reg(chip_index, 0xA9, *data++);
if (mask & OPM_CTRL_EXTCH3_OP2_HIGH) opn_write_reg(chip_index, 0xAC, *data++);
if (mask & OPM_CTRL_EXTCH3_OP2_LOW) opn_write_reg(chip_index, 0xA8, *data++);
if (mask & OPM_CTRL_EXTCH3_OP3_HIGH) opn_write_reg(chip_index, 0xAE, *data++);
if (mask & OPM_CTRL_EXTCH3_OP3_LOW) opn_write_reg(chip_index, 0xAA, *data++);
if (mask & OPM_CTRL_EXTCH3_EOF) endOfFrame = true;
doNextOp = true;
goto done;
}
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?
doNextOp = true;
goto done;
}
if ((*data) == OPM_STREAM_END) {
// rewind to start
chctx->stream.reload = -1;
endOfFrame = true;
doNextOp = true;
goto done;
}
done:
return data;
}
// FM channel stream
const uint8_t* opmplay_parse_fm_channel_stream(opmplay_context_t* ctx, opmplay_channel_context_t* chctx, int ch, const uint8_t* data) {
if ((*(data) & 0xC0) == OPM_FM_ADSR) {
// ADSR
int mask = *data;
int regbase = (ch & 3) + ((mask & OPM_FM_CMD00_OP_MASK) >> 2);
data++;
if (mask & OPM_FM_CMD00_REG50) opn_write_reg(chip_index, 0x50 + regbase, *data++);
if (mask & OPM_FM_CMD00_REG60) opn_write_reg(chip_index, 0x60 + regbase, *data++);
if (mask & OPM_FM_CMD00_REG70) opn_write_reg(chip_index, 0x70 + regbase, *data++);
if (mask & OPM_FM_CMD00_REG80) opn_write_reg(chip_index, 0x80 + regbase, *data++);
doNextOp = true;
goto done;
}
if ((*(data) & 0xC0) == OPM_FM_MUL_TL_EG) {
// MULT/TL/SSG-EG
int mask = *data;
int regbase = (ch & 3) + ((mask & OPM_FM_CMD40_OP_MASK) >> 2);
data++;
if (mask & OPM_FM_CMD40_REG30) opn_write_reg(chip_index, 0x30 + regbase, *data++);
if (mask & OPM_FM_CMD40_REG40) opn_write_reg(chip_index, 0x40 + regbase, *data++);
if (mask & OPM_FM_CMD40_REG90) opn_write_reg(chip_index, 0x90 + regbase, *data++);
if (mask & OPM_FM_CMD40_EOF) endOfFrame = true;
doNextOp = true;
goto done;
}
if ((*(data) & 0xE0) == OPM_FM_FREQ_FB_PAN) {
// frequency/feedpack
int mask = *data;
data++;
if (mask & OPM_FM_CMD80_REGA4) opn_write_reg(chip_index, 0xA4 + ch, *data++);
if (mask & OPM_FM_CMD80_REGA0) opn_write_reg(chip_index, 0xA0 + ch, *data++);
if (mask & OPM_FM_CMD80_REGB0) opn_write_reg(chip_index, 0xB0 + ch, *data++);
if (mask & OPM_FM_CMD80_REGB4) opn_write_reg(chip_index, 0xB4 + ch, *data++);
if (mask & OPM_FM_CMD80_EOF) endOfFrame = true;
doNextOp = true;
goto done;
}
if ((*(data) & 0xE0) == OPM_FM_KEY) {
// frequency/feedpack
int mask = *data; data++;
opn_write_reg(chip_index, 0x28, ((mask & OPM_FM_CMDA0_OP_MASK) << 4) + ch);
if (mask & OPM_FM_CMDA0_EOF) endOfFrame = true;
doNextOp = true;
goto done;
}
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?
doNextOp = true;
goto done;
}
if ((*data) == OPM_STREAM_END) {
// rewind to start
chctx->stream.reload = -1;
endOfFrame = true;
doNextOp = true;
goto done;
}
done:
return data;
}
// parse one individual stream
const int opmplay_parse_stream(opmplay_context_t* ctx, opmplay_channel_context_t* chctx, int ch, const uint8_t* (*proc)(opmplay_context_t* ctx, opmplay_channel_context_t* chctx, int ch, const uint8_t* data)) {
uint16_t newdelay;
const uint8_t* data = chctx->stream.ptr;
endOfFrame = false;
if (--chctx->stream.delay == 0) {
do {
doNextOp = false;
data = proc(ctx, chctx, ch, data);
if (doNextOp) {
doNextOp = false;
continue;
}
// check for common stuff
switch (*data) {
// just an NOP, break
case OPM_STREAM_NEW_ORDER:
case OPM_STREAM_NOP:
data++;
break;
case OPM_STREAM_LOOP:
// save loop point
ctx->pos.frame_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++;
endOfFrame = true;
break;
default:
// test for delay
newdelay = opmplay_set_delay(&data);
if (newdelay > 0) {
chctx->stream.reload = newdelay;
}
else
{
printf("unknown token %02x!\n", *data);
return OPMPLAY_ERR_BAD_FILE_STRUCTURE;
}
}
} while (!endOfFrame);
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;
}
}
return OPMPLAY_ERR_OK;
}
int opmplay_tick(opmplay_context_t* ctx) {
int ch = 0;
int rtn = OPMPLAY_ERR_OK;
// unroll this shit =)
chip_index = 0;
opmplay_parse_stream(ctx, ctx->channels + 0, 0, opmplay_parse_fm_control_stream);
opmplay_parse_stream(ctx, ctx->channels + 1, 0, opmplay_parse_fm_channel_stream);
opmplay_parse_stream(ctx, ctx->channels + 2, 1, opmplay_parse_fm_channel_stream);
opmplay_parse_stream(ctx, ctx->channels + 3, 2, opmplay_parse_fm_channel_stream);
chip_index = 1;
opmplay_parse_stream(ctx, ctx->channels + 8, 0, opmplay_parse_fm_control_stream);
opmplay_parse_stream(ctx, ctx->channels + 9, 0, opmplay_parse_fm_channel_stream);
opmplay_parse_stream(ctx, ctx->channels + 10, 1, opmplay_parse_fm_channel_stream);
opmplay_parse_stream(ctx, ctx->channels + 11, 2, opmplay_parse_fm_channel_stream);
// advance frame counter
ctx->pos.frame++;
return rtn;
}