From d4e88aebea9a4e132ab7fb3f96df2e2ca37f840e Mon Sep 17 00:00:00 2001 From: wbcbz7 Date: Mon, 2 Sep 2024 13:55:54 +0700 Subject: [PATCH] update 4-operator mode enable logic --- oplplay/lxmplay/main.cpp | 4 +- oplplay/lxmplay/opmfile.h | 8 +- oplplay/lxmplay/opmplay.cpp | 223 +++++++++++++-------- oplplay/lxmplay/opmplay.h | 11 +- vgm2opl_next/vgm2opl_next/opmctx.h | 3 + vgm2opl_next/vgm2opl_next/opmfile.h | 4 + vgm2opl_next/vgm2opl_next/vgm2opl_next.cpp | 61 ++++-- 7 files changed, 215 insertions(+), 99 deletions(-) diff --git a/oplplay/lxmplay/main.cpp b/oplplay/lxmplay/main.cpp index a6a48a3..73e94a9 100644 --- a/oplplay/lxmplay/main.cpp +++ b/oplplay/lxmplay/main.cpp @@ -169,7 +169,7 @@ int synth_render(int16_t* buffer, uint32_t num_samples) { samples_to_render -= opmctx.delay_count; // parse VGM stream - opmplay_tick(&opmctx.opm, &opl3); + opmplay_tick(&opmctx.opm); opmctx.delay_count = (44100.0 / ((double)0x1234DD / opmctx.opm.header.frame_rate)); } } @@ -251,7 +251,7 @@ int main(int argc, char* argv[]) int rtn; - if ((rtn = opmplay_init(&opmctx.opm)) != OPMPLAY_ERR_OK) { + if ((rtn = opmplay_init(&opmctx.opm, &opl3)) != OPMPLAY_ERR_OK) { printf("unable to init OPMPlay (error = %d)\n", rtn); return 1; } diff --git a/oplplay/lxmplay/opmfile.h b/oplplay/lxmplay/opmfile.h index a965721..c025d25 100644 --- a/oplplay/lxmplay/opmfile.h +++ b/oplplay/lxmplay/opmfile.h @@ -34,9 +34,9 @@ struct opm_header_t { uint16_t frame_rate; // [hz] = 0x1234dd/frame_rate uint8_t callstack_depth; // reserved, 0 at this moment uint8_t channels; // must be either 9 or 18! - uint32_t channel_mask; // used channel mask, LSB-ch 0 + uint32_t channel_mask; // used channel mask, LSB = ch 0 - // opm_header_stream_desc_t stream[18 + 1]; // first is control stream + // opm_header_stream_desc_t stream[opm_header_t::channels + 1]; // first is control stream }; // OPM v0 stream data: @@ -48,6 +48,7 @@ enum { OPM_STREAM_SET_FRAME_RATE = 0xFB, // word rate (as in opm_header_t::frame_rate) OPM_STREAM_LOOP = 0xFA, // set loop point here + OPM_4OP_TRIGGER = 0xF4, // 4-op mode on/off OPM_KEY_TRIGGER = 0xF0, // set key on/off + optionally end of frame // delay commands @@ -68,6 +69,9 @@ enum { OPM_CTRL_REG_SET = 0x80, // 80..BF - set control registers // flags + OPM_4OP_OFF = (0 << 0), + OPM_4OP_ON = (1 << 0), + OPM_KEY_OFF = (0 << 0), OPM_KEY_ON = (1 << 0), OPM_KEY_END_OF_FRAME = (1 << 1), diff --git a/oplplay/lxmplay/opmplay.cpp b/oplplay/lxmplay/opmplay.cpp index 5ab01a4..594f340 100644 --- a/oplplay/lxmplay/opmplay.cpp +++ b/oplplay/lxmplay/opmplay.cpp @@ -29,10 +29,11 @@ static uint32_t opmplay_file_seek(opmplay_io_t* io, uint32_t pos) { // ------------------------- -int opmplay_init(opmplay_context_t* ctx) { - if (ctx == NULL) return OPMPLAY_ERR_NULLPTR; +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; } @@ -139,9 +140,35 @@ int opmplay_loop(opmplay_context_t* ctx) { } 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); @@ -178,7 +205,7 @@ static uint32_t opmplay_set_delay(uint8_t** data) { return delay; } -// translation tables (TODO: OPL3) +// 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, @@ -187,79 +214,25 @@ 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, opl3_chip* opl3) { +int opmplay_tick(opmplay_context_t* ctx) { 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 < ctx->header.channels; ch++) { + 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; @@ -270,32 +243,40 @@ int opmplay_tick(opmplay_context_t* ctx, opl3_chip* opl3) { 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(opl3, 0x20 + op, *data++); - if (mask & OPM_CMD00_SET_TL) OPL3_WriteRegBuffered(opl3, 0x40 + op, *data++); - if (mask & OPM_CMD00_SET_AD) OPL3_WriteRegBuffered(opl3, 0x60 + op, *data++); - if (mask & OPM_CMD00_SET_SR) OPL3_WriteRegBuffered(opl3, 0x80 + op, *data++); - if (mask & OPM_CMD00_SET_WAVEFORM) OPL3_WriteRegBuffered(opl3, 0xE0 + op, *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(opl3, 0x40 + opmplay_opregs_channel_offset[ch], *data++); - if (mask & OPM_CMD80_SET_TL1) OPL3_WriteRegBuffered(opl3, 0x43 + opmplay_opregs_channel_offset[ch], *data++); - if (mask & OPM_CMD80_SET_FEEDBACK) OPL3_WriteRegBuffered(opl3, 0xC0 + opmplay_regs_channel_offset[ch], *data++); - if (mask & OPM_CMD80_SET_FREQ) OPL3_WriteRegBuffered(opl3, 0xA0 + opmplay_regs_channel_offset[ch], *data++); - if (mask & OPM_CMD80_SET_KEYBLOCK) { chctx->block = *data; OPL3_WriteRegBuffered(opl3, 0xB0 + opmplay_regs_channel_offset[ch], *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(opl3, 0xB0 + opmplay_regs_channel_offset[ch], chctx->block); + 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)); @@ -357,6 +338,88 @@ int opmplay_tick(opmplay_context_t* ctx, opl3_chip* opl3) { } } + // 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; diff --git a/oplplay/lxmplay/opmplay.h b/oplplay/lxmplay/opmplay.h index 77450a5..f9dbd47 100644 --- a/oplplay/lxmplay/opmplay.h +++ b/oplplay/lxmplay/opmplay.h @@ -110,11 +110,17 @@ struct opmplay_context_t { uint32_t looped; } pos; + // internal registers + uint8_t _4op; // used to track 4op mode changes + + // opl chip context + opl3_chip* chip; + }; // init context -int opmplay_init(opmplay_context_t* ctx); +int opmplay_init(opmplay_context_t* ctx, opl3_chip *chip); // free context int opmplay_free(opmplay_context_t* ctx); @@ -129,8 +135,7 @@ int opmplay_load_module(opmplay_context_t* ctx, opmplay_io_t* io); int opmplay_rewind(opmplay_context_t* ctx); // play one tick, render changes to opl3 device -int opmplay_tick(opmplay_context_t* ctx, opl3_chip *opl3); - +int opmplay_tick(opmplay_context_t* ctx); #ifdef __cplusplus } diff --git a/vgm2opl_next/vgm2opl_next/opmctx.h b/vgm2opl_next/vgm2opl_next/opmctx.h index b0ccf3e..c9c3232 100644 --- a/vgm2opl_next/vgm2opl_next/opmctx.h +++ b/vgm2opl_next/vgm2opl_next/opmctx.h @@ -39,6 +39,8 @@ enum { OPM_REG_104 = (1 << 18), OPM_REG_BD = (1 << 19), + OPM_4OP = (1 << 20), + // opm_serialize_channel_stream() tweaks OPM_TL0 = (1 << 8), OPM_TL1 = (1 << 9), @@ -59,6 +61,7 @@ struct opm_frame_record { int block; int feedback; int key; + int _4op; // control stream only int keyperc; diff --git a/vgm2opl_next/vgm2opl_next/opmfile.h b/vgm2opl_next/vgm2opl_next/opmfile.h index e31dc5d..c025d25 100644 --- a/vgm2opl_next/vgm2opl_next/opmfile.h +++ b/vgm2opl_next/vgm2opl_next/opmfile.h @@ -48,6 +48,7 @@ enum { OPM_STREAM_SET_FRAME_RATE = 0xFB, // word rate (as in opm_header_t::frame_rate) OPM_STREAM_LOOP = 0xFA, // set loop point here + OPM_4OP_TRIGGER = 0xF4, // 4-op mode on/off OPM_KEY_TRIGGER = 0xF0, // set key on/off + optionally end of frame // delay commands @@ -68,6 +69,9 @@ enum { OPM_CTRL_REG_SET = 0x80, // 80..BF - set control registers // flags + OPM_4OP_OFF = (0 << 0), + OPM_4OP_ON = (1 << 0), + OPM_KEY_OFF = (0 << 0), OPM_KEY_ON = (1 << 0), OPM_KEY_END_OF_FRAME = (1 << 1), diff --git a/vgm2opl_next/vgm2opl_next/vgm2opl_next.cpp b/vgm2opl_next/vgm2opl_next/vgm2opl_next.cpp index 5db0075..b32787b 100644 --- a/vgm2opl_next/vgm2opl_next/vgm2opl_next.cpp +++ b/vgm2opl_next/vgm2opl_next/vgm2opl_next.cpp @@ -147,6 +147,7 @@ int opm_requantize(opm_convert_context_t* ctx) { // per-channel structures std::vector oplchans(1 + ctx->max_channels); // TODO uint32_t currentFrame = 0; uint32_t delay_rate, delay_acc = 0; + int reg_104_old = 0; // KLUDGE! auto it = ctx->vgm.vgmfile.begin() + ctx->vgm.start; bool loopFrame = false; while (it < ctx->vgm.vgmfile.begin() + ctx->vgm.end) { @@ -204,8 +205,28 @@ int opm_requantize(opm_convert_context_t* ctx) { oplchans[channel+chip_chidx].data.push_back(reg & ~0xF); oplchans[channel+chip_chidx].data.push_back(data); break; + break; default: - if ((chip_idx == 1) && (reg == 0x04 || reg == 0x05)) break; + if ((chip_idx == 1) && (reg == 0x04)) { + // process special case with 4op register (0x104) + if (reg_104_old != data) { + static int reg104_to_ch[] = {0+1, 1+1, 2+1, 9+1, 10+1, 11+1}; + int mask = reg_104_old ^ data; + for (int i = 0; i < 6; i++) { + if (mask & (1 << i)) { + // push data + oplchans[channel + chip_chidx].frame = currentFrame; + oplchans[reg104_to_ch[i]].data.push_back(0x04); + oplchans[reg104_to_ch[i]].data.push_back(data & (1 << i)); + // mark channels as used + ctx->ch[reg104_to_ch[i]].used = ctx->ch[reg104_to_ch[i]+3].used = true; + } + } + reg_104_old = data; + } + break; + } + if ((chip_idx == 0) && (reg == 0x04 || reg == 0x05)) break; global_reg: oplchans[0].frame = currentFrame; oplchans[0].data.push_back(reg); @@ -369,26 +390,26 @@ int opm_group_control_stream(opm_convert_context_t* ctx) { defrec.reg_104 = data; defrec.flags |= OPM_REG_104; // mark channel pairs as used depending on register value if ((ctx->chip_type == OPM_FLAG_CHIP_OPL3) && (ctx->max_channels >= 18)) { - if (data & (1 << 0)) ctx->ch[ 0].used = ctx->ch[ 3].used = true; - if (data & (1 << 1)) ctx->ch[ 1].used = ctx->ch[ 4].used = true; - if (data & (1 << 2)) ctx->ch[ 2].used = ctx->ch[ 5].used = true; - if (data & (1 << 3)) ctx->ch[ 9].used = ctx->ch[12].used = true; - if (data & (1 << 4)) ctx->ch[10].used = ctx->ch[13].used = true; - if (data & (1 << 5)) ctx->ch[11].used = ctx->ch[14].used = true; + if (data & (1 << 0)) ctx->ch[ 0+1].used = ctx->ch[ 3+1].used = true; + if (data & (1 << 1)) ctx->ch[ 1+1].used = ctx->ch[ 4+1].used = true; + if (data & (1 << 2)) ctx->ch[ 2+1].used = ctx->ch[ 5+1].used = true; + if (data & (1 << 3)) ctx->ch[ 9+1].used = ctx->ch[12+1].used = true; + if (data & (1 << 4)) ctx->ch[10+1].used = ctx->ch[13+1].used = true; + if (data & (1 << 5)) ctx->ch[11+1].used = ctx->ch[14+1].used = true; } break; } case OPM_REG_105: defrec.reg_105 = data; defrec.flags |= OPM_REG_105; break; case OPM_REG_BD: // test low 6 bits - defrec.reg_BD = data; defrec.flags |= OPM_REG_BD; break; - if (((data ^ defrec.reg_BD) & 0x3F) != 0) { + defrec.reg_BD = data; defrec.flags |= OPM_REG_BD; + if (((data) & 0x3F) != 0) { // percussion key on/off - serialize records! chanrec.records.push_back(defrec); defrec.flags = 0; if (data & (1 << 5)) { // percussion mode is used - mark channels 6-8 as used ctx->percussion_mode = true; - ctx->ch[6].used = ctx->ch[7].used = ctx->ch[8].used = true; + ctx->ch[6+1].used = ctx->ch[7+1].used = ctx->ch[8+1].used = true; } } break; @@ -462,7 +483,7 @@ int opm_group_channel_stream(opm_convert_context_t *ctx, opm_channel_t* chctx, i defrec.key = data & (1 << 5); if ((oldblock ^ data) == (1 << 5)) defrec.flags |= OPM_KEY; // serialize! - chanrec.records.push_back(defrec); defrec.flags = 0; + chanrec.records.push_back(defrec); defrec.flags = 0; if (defrec.key != 0) { // key on triggered - mark channel as used ctx->ch[ch].used = true; @@ -470,6 +491,13 @@ int opm_group_channel_stream(opm_convert_context_t *ctx, opm_channel_t* chctx, i } break; } + case 0x04: + if (ctx->chip_type != OPM_FLAG_CHIP_OPL3) break; + defrec.flags |= OPM_4OP; + defrec._4op = data; + // serialize! + chanrec.records.push_back(defrec); defrec.flags = 0; + break; default: break; } @@ -573,6 +601,7 @@ int opm_serialize_channel_stream(opm_convert_context_t* ctx, int ch) { int tl_relocated; int freq_fb; bool key_only; + bool _4op; int last; } mask; @@ -589,8 +618,10 @@ int opm_serialize_channel_stream(opm_convert_context_t* ctx, int ch) { } mask.freq_fb = (e.flags & (OPM_BLOCK | OPM_FNUM | OPM_FEEDBACK)) | mask.tl_relocated; mask.last |= ((mask.freq_fb ? 4 : 0) << 0); + mask._4op = (e.flags & OPM_4OP); + mask.last |= ((mask._4op ? 8 : 0) << 0); mask.key_only = ((e.flags & (OPM_BLOCK | OPM_FNUM | OPM_FEEDBACK | OPM_KEY)) | (mask.tl_relocated)) == (OPM_BLOCK | OPM_KEY); - mask.last |= ((mask.key_only ? 8 : 0) << 0); + mask.last |= ((mask.key_only ? 16 : 0) << 0); if (i < s.records.size() - 1) mask.last |= (1 << 31); // prevent non-last events from emitting eof token @@ -628,6 +659,12 @@ int opm_serialize_channel_stream(opm_convert_context_t* ctx, int ch) { if (mask.freq_fb & OPM_BLOCK) s.rawdata.push_back(e.block); } mask.last >>= 1; + if (mask._4op) { + s.rawdata.push_back( + OPM_4OP_TRIGGER | (e._4op ? OPM_4OP_ON : OPM_4OP_OFF) + ); + } + mask.last >>= 1; if (mask.key_only) { s.rawdata.push_back( OPM_KEY_TRIGGER |