#include #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; } // AY channel stream const uint8_t* opmplay_parse_ay_channel_stream(opmplay_context_t* ctx, opmplay_channel_context_t* chctx, int ch, const uint8_t* data) { if ((*(data) & 0x80) == OPM_AYTONE_REGS) { // volume and period low int mask = *data; data++; opn_write_reg(chip_index, 8 + ch, (mask & OPM_AYTONE_CMD00_VOLUME_MASK)); if (mask & OPM_AYTONE_CMD00_PERIOD_LOW) opn_write_reg(chip_index, 0 + (ch<<1), *data++); if (mask & OPM_AYTONE_CMD00_EOF) endOfFrame = true; doNextOp = true; goto done; } if ((*(data) & 0xC0) == OPM_AYTONE_PERIOD) { // period high/low int mask = *data; data++; opn_write_reg(chip_index, 1 + (ch<<1), (mask & OPM_AYTONE_CMD80_PERIOD_HIGH)); if (mask & OPM_AYTONE_CMD80_PERIOD_LOW) opn_write_reg(chip_index, 0 + (ch << 1), *data++); if (mask & OPM_AYTONE_CMD80_EOF) endOfFrame = true; doNextOp = true; goto done; } if ((*(data) & 0xF8) == OPM_AYTONE_MASK) { const static uint8_t mask_lookup[4] = {(0<<3)|(0<<0), (0<<3)|(1<<0), (1<<3)|(0<<0), (1<<3)|(1<<0)}; // mask int mask = *data; data++; ctx->ssg_r7[chip_index] &= ~(((1 << 3) | (1 << 0)) << ch); ctx->ssg_r7[chip_index] |= (mask_lookup[mask & 3] << ch); if (mask & OPM_AYTONE_MASK_EOF) endOfFrame = true; doNextOp = true; goto done; } done: return data; } // AY noise/envelope stream const uint8_t* opmplay_parse_ay_envnoise_stream(opmplay_context_t* ctx, opmplay_channel_context_t* chctx, int ch, const uint8_t* data) { if ((*(data) & 0x80) == OPM_AYENV_REGS) { // noise and period low int mask = *data; data++; opn_write_reg(chip_index, 6, (mask & OPM_AYENV_CMD00_NOISE_MASK)); if (mask & OPM_AYENV_CMD00_PERIOD_LOW) opn_write_reg(chip_index, 11, *data++); if (mask & OPM_AYENV_CMD00_EOF) endOfFrame = true; doNextOp = true; goto done; } if ((*(data) & 0xC0) == OPM_AYENV_ENVTYPE) { // envelope type (and retrig) int mask = *data; data++; opn_write_reg(chip_index, 13, (mask & OPM_AYENV_CMD80_ENV_TYPE)); if (mask & OPM_AYENV_CMD80_PERIOD_LOW) opn_write_reg(chip_index, 11, *data++); if (mask & OPM_AYENV_CMD80_EOF) endOfFrame = true; doNextOp = true; goto done; } if ((*(data) & 0xF8) == OPM_AYENV_PERIOD_FULL) { // mask int mask = *data; data++; if (mask & OPM_AYENV_CMDF0_PERIOD_LOW) opn_write_reg(chip_index, 11, *data++); if (mask & OPM_AYENV_CMDF0_PERIOD_HIGH) opn_write_reg(chip_index, 12, *data++); if (mask & OPM_AYENV_CMDF0_EOF) endOfFrame = true; doNextOp = true; goto done; } done: return data; } // 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) ctx->extch3_block[chip_index][0] = *data++; if (mask & OPM_CTRL_EXTCH3_OP1_LOW) { opn_write_reg(chip_index, 0xAD, ctx->extch3_block[chip_index][0]); opn_write_reg(chip_index, 0xA9, *data++); } if (mask & OPM_CTRL_EXTCH3_OP2_HIGH) ctx->extch3_block[chip_index][1] = *data++; if (mask & OPM_CTRL_EXTCH3_OP2_LOW) { opn_write_reg(chip_index, 0xAC, ctx->extch3_block[chip_index][1]); opn_write_reg(chip_index, 0xA8, *data++); } if (mask & OPM_CTRL_EXTCH3_OP3_HIGH) ctx->extch3_block[chip_index][2] = *data++; if (mask & OPM_CTRL_EXTCH3_OP3_LOW) { opn_write_reg(chip_index, 0xAE, ctx->extch3_block[chip_index][2]); opn_write_reg(chip_index, 0xAA, *data++); } if (mask & OPM_CTRL_EXTCH3_EOF) 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) chctx->block = *data++; if (mask & OPM_FM_CMD80_REGA0) { opn_write_reg(chip_index, 0xA4 + ch, chctx->block); 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; } 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; } 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) { // 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; case OPM_STREAM_END: // rewind to start chctx->stream.reload = -1; endOfFrame = true; 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); opmplay_parse_stream(ctx, ctx->channels + 4, 0, opmplay_parse_ay_channel_stream); opmplay_parse_stream(ctx, ctx->channels + 5, 1, opmplay_parse_ay_channel_stream); opmplay_parse_stream(ctx, ctx->channels + 6, 2, opmplay_parse_ay_channel_stream); opmplay_parse_stream(ctx, ctx->channels + 7, 0, opmplay_parse_ay_envnoise_stream); opn_write_reg(chip_index, 7, ctx->ssg_r7[chip_index]); 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); opmplay_parse_stream(ctx, ctx->channels + 12, 0, opmplay_parse_ay_channel_stream); opmplay_parse_stream(ctx, ctx->channels + 13, 1, opmplay_parse_ay_channel_stream); opmplay_parse_stream(ctx, ctx->channels + 14, 2, opmplay_parse_ay_channel_stream); opmplay_parse_stream(ctx, ctx->channels + 15, 0, opmplay_parse_ay_envnoise_stream); opn_write_reg(chip_index, 7, ctx->ssg_r7[chip_index]); // advance frame counter ctx->pos.frame++; return rtn; }