moe-bius/oplplay/lxmplay/opmplay.cpp
2024-04-06 15:12:38 +07:00

319 lines
11 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) {
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 < OPMPLAY_MAX_CHANNLES; 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))))
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) * (OPMPLAY_MAX_CHANNLES));
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) * (OPMPLAY_MAX_CHANNLES))
!= sizeof(opm_header_stream_desc_t) * (OPMPLAY_MAX_CHANNLES))
return OPMPLAY_ERR_IO;
// allocate and copy channel streams
for (int ch = 0; ch < OPMPLAY_MAX_CHANNLES; ch++) {
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;
}
// rewind to start
opmplay_rewind(ctx);
// done :)
return OPMPLAY_ERR_OK;
}
int opmplay_loop(opmplay_context_t* ctx) {
// channel streams
for (int ch = 0; ch < OPMPLAY_MAX_CHANNLES; ch++) {
// init stack
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) {
for (int ch = 0; ch < OPMPLAY_MAX_CHANNLES; 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(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 (TODO: OPL3)
static int opmplay_opregs_channel_offset[] = {
0, 1, 2, 8, 9, 0xA, 0x10, 0x11, 0x12
};
int opmplay_tick(opmplay_context_t* ctx, opl3_chip* opl3) {
int ch = 0;
int rtn = OPMPLAY_ERR_OK;
uint32_t newdelay = 0;
// 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(opl3, 0x001, *data++);
if (mask & OPM_CTRL_SET_REG08) OPL3_WriteRegBuffered(opl3, 0x008, *data++);
if (mask & OPM_CTRL_SET_REG105) OPL3_WriteRegBuffered(opl3, 0x105, *data++);
if (mask & OPM_CTRL_SET_REG104) OPL3_WriteRegBuffered(opl3, 0x104, *data++);
if (mask & OPM_CTRL_SET_REGBD) OPL3_WriteRegBuffered(opl3, 0x0BD, *data++);
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;
chctx->stream.ptr = data;
}
}
// parse channel streams
for (int ch = 0; ch < OPMPLAY_MAX_CHANNLES - 1; ch++) {
opmplay_channel_context_t* chctx = ctx->channels + ch + 1;
uint8_t* data = chctx->stream.ptr;
bool isRun = true;
if (--chctx->stream.delay == 0) {
while (isRun) {
// get streams
if ((*(data) & 0xE0) == OPM_SET_MULT_WAVE_TL) {
int mask = *data;
int op = (mask & OPM_CMD80_SELECT_OPERATOR ? 3 : 0) + opmplay_opregs_channel_offset[ch];
data++;
if (mask & OPM_CMD80_SET_MULT) OPL3_WriteRegBuffered(opl3, 0x20+op, *data++);
if (mask & OPM_CMD80_SET_TL) OPL3_WriteRegBuffered(opl3, 0x40+op, *data++);
if (mask & OPM_CMD80_SET_WAVEFORM) OPL3_WriteRegBuffered(opl3, 0xE0+op, *data++);
continue;
}
if ((*(data) & 0xF0) == OPM_SET_ADSR) {
int mask = *data;
int op = (mask & OPM_CMDA0_SELECT_OPERATOR ? 3 : 0) + opmplay_opregs_channel_offset[ch];
data++;
if (mask & OPM_CMDA0_SET_AD) OPL3_WriteRegBuffered(opl3, 0x60 + op, *data++);
if (mask & OPM_CMDA0_SET_SR) OPL3_WriteRegBuffered(opl3, 0x80 + op, *data++);
continue;
}
if ((*(data) & 0xF0) == OPM_SET_FREQ_FB) {
int mask = *data;
data++;
if (mask & OPM_CMDB0_SET_FEEDBACK) OPL3_WriteRegBuffered(opl3, 0xC0 + ch, *data++);
if (mask & OPM_CMDB0_SET_FREQ_LOW) OPL3_WriteRegBuffered(opl3, 0xA0 + ch, *data++);
if (mask & OPM_CMDB0_SET_FREQ_HIGH) OPL3_WriteRegBuffered(opl3, 0xB0 + ch, *data++);
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;
chctx->stream.ptr = data;
}
}
return rtn;
}