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