update 4-operator mode enable logic

This commit is contained in:
wbcbz7 2024-09-02 13:55:54 +07:00
parent a6b1293d37
commit d4e88aebea
7 changed files with 215 additions and 99 deletions

View file

@ -169,7 +169,7 @@ int synth_render(int16_t* buffer, uint32_t num_samples) {
samples_to_render -= opmctx.delay_count; samples_to_render -= opmctx.delay_count;
// parse VGM stream // parse VGM stream
opmplay_tick(&opmctx.opm, &opl3); opmplay_tick(&opmctx.opm);
opmctx.delay_count = (44100.0 / ((double)0x1234DD / opmctx.opm.header.frame_rate)); opmctx.delay_count = (44100.0 / ((double)0x1234DD / opmctx.opm.header.frame_rate));
} }
} }
@ -251,7 +251,7 @@ int main(int argc, char* argv[])
int rtn; 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); printf("unable to init OPMPlay (error = %d)\n", rtn);
return 1; return 1;
} }

View file

@ -34,9 +34,9 @@ struct opm_header_t {
uint16_t frame_rate; // [hz] = 0x1234dd/frame_rate uint16_t frame_rate; // [hz] = 0x1234dd/frame_rate
uint8_t callstack_depth; // reserved, 0 at this moment uint8_t callstack_depth; // reserved, 0 at this moment
uint8_t channels; // must be either 9 or 18! 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: // 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_SET_FRAME_RATE = 0xFB, // word rate (as in opm_header_t::frame_rate)
OPM_STREAM_LOOP = 0xFA, // set loop point here 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 OPM_KEY_TRIGGER = 0xF0, // set key on/off + optionally end of frame
// delay commands // delay commands
@ -68,6 +69,9 @@ enum {
OPM_CTRL_REG_SET = 0x80, // 80..BF - set control registers OPM_CTRL_REG_SET = 0x80, // 80..BF - set control registers
// flags // flags
OPM_4OP_OFF = (0 << 0),
OPM_4OP_ON = (1 << 0),
OPM_KEY_OFF = (0 << 0), OPM_KEY_OFF = (0 << 0),
OPM_KEY_ON = (1 << 0), OPM_KEY_ON = (1 << 0),
OPM_KEY_END_OF_FRAME = (1 << 1), OPM_KEY_END_OF_FRAME = (1 << 1),

View file

@ -29,10 +29,11 @@ static uint32_t opmplay_file_seek(opmplay_io_t* io, uint32_t pos) {
// ------------------------- // -------------------------
int opmplay_init(opmplay_context_t* ctx) { int opmplay_init(opmplay_context_t* ctx, opl3_chip* chip) {
if (ctx == NULL) return OPMPLAY_ERR_NULLPTR; if ((ctx == NULL) || (chip == NULL)) return OPMPLAY_ERR_NULLPTR;
opmplay_memset(ctx, 0, sizeof(opmplay_context_t)); opmplay_memset(ctx, 0, sizeof(opmplay_context_t));
ctx->chip = chip;
return OPMPLAY_ERR_OK; return OPMPLAY_ERR_OK;
} }
@ -139,9 +140,35 @@ int opmplay_loop(opmplay_context_t* ctx) {
} }
int opmplay_rewind(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++) { for (int ch = 0; ch < ctx->header.channels + 1; ch++) {
ctx->channels[ch].stream.loop = ctx->channels[ch].stream.data; ctx->channels[ch].stream.loop = ctx->channels[ch].stream.data;
} }
ctx->_4op = 0;
opmplay_loop(ctx); opmplay_loop(ctx);
@ -178,7 +205,7 @@ static uint32_t opmplay_set_delay(uint8_t** data) {
return delay; return delay;
} }
// translation tables (TODO: OPL3) // translation tables
static int opmplay_opregs_channel_offset[] = { static int opmplay_opregs_channel_offset[] = {
0x000, 0x001, 0x002, 0x008, 0x009, 0x00A, 0x010, 0x011, 0x012, 0x000, 0x001, 0x002, 0x008, 0x009, 0x00A, 0x010, 0x011, 0x012,
0x100, 0x101, 0x102, 0x108, 0x109, 0x10A, 0x110, 0x111, 0x112, 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, 0x000, 0x001, 0x002, 0x003, 0x004, 0x005, 0x006, 0x007, 0x008,
0x100, 0x101, 0x102, 0x103, 0x104, 0x105, 0x106, 0x107, 0x108, 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 ch = 0;
int rtn = OPMPLAY_ERR_OK; int rtn = OPMPLAY_ERR_OK;
uint32_t newdelay = 0; 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 // 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; opmplay_channel_context_t* chctx = ctx->channels + ch + 1;
uint8_t* data = chctx->stream.ptr; if (data == NULL) continue; uint8_t* data = chctx->stream.ptr; if (data == NULL) continue;
bool isRun = true; bool isRun = true;
@ -270,32 +243,40 @@ int opmplay_tick(opmplay_context_t* ctx, opl3_chip* opl3) {
int mask = *data; int mask = *data;
int op = (mask & OPM_CMD00_SELECT_OPERATOR ? 3 : 0) + opmplay_opregs_channel_offset[ch]; int op = (mask & OPM_CMD00_SELECT_OPERATOR ? 3 : 0) + opmplay_opregs_channel_offset[ch];
data++; data++;
if (mask & OPM_CMD00_SET_MULT) OPL3_WriteRegBuffered(opl3, 0x20 + op, *data++); if (mask & OPM_CMD00_SET_MULT) OPL3_WriteRegBuffered(ctx->chip, 0x20 + op, *data++);
if (mask & OPM_CMD00_SET_TL) OPL3_WriteRegBuffered(opl3, 0x40 + op, *data++); if (mask & OPM_CMD00_SET_TL) OPL3_WriteRegBuffered(ctx->chip, 0x40 + op, *data++);
if (mask & OPM_CMD00_SET_AD) OPL3_WriteRegBuffered(opl3, 0x60 + op, *data++); if (mask & OPM_CMD00_SET_AD) OPL3_WriteRegBuffered(ctx->chip, 0x60 + op, *data++);
if (mask & OPM_CMD00_SET_SR) OPL3_WriteRegBuffered(opl3, 0x80 + op, *data++); if (mask & OPM_CMD00_SET_SR) OPL3_WriteRegBuffered(ctx->chip, 0x80 + op, *data++);
if (mask & OPM_CMD00_SET_WAVEFORM) OPL3_WriteRegBuffered(opl3, 0xE0 + op, *data++); if (mask & OPM_CMD00_SET_WAVEFORM) OPL3_WriteRegBuffered(ctx->chip, 0xE0 + op, *data++);
if (mask & OPM_CMD00_END_OF_FRAME) isRun = false; if (mask & OPM_CMD00_END_OF_FRAME) isRun = false;
continue; continue;
} }
if ((*(data) & 0xC0) == OPM_SET_FREQ_FB_VOL) { if ((*(data) & 0xC0) == OPM_SET_FREQ_FB_VOL) {
int mask = *data; int mask = *data;
data++; data++;
if (mask & OPM_CMD80_SET_TL0) OPL3_WriteRegBuffered(opl3, 0x40 + opmplay_opregs_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(opl3, 0x43 + 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(opl3, 0xC0 + opmplay_regs_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(opl3, 0xA0 + 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(opl3, 0xB0 + 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; if (mask & OPM_CMD80_END_OF_FRAME) isRun = false;
continue; continue;
} }
if ((*data & 0xFC) == OPM_KEY_TRIGGER) { if ((*data & 0xFC) == OPM_KEY_TRIGGER) {
if (*data & OPM_KEY_ON) chctx->block |= (1 << 5); else chctx->block &= ~(1 << 5); 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; if (*data & OPM_KEY_END_OF_FRAME) isRun = false;
data++; data++;
continue; 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) { if ((*data & 0xF0) == OPM_STREAM_BACKREF) {
// back reference, nested call :) // back reference, nested call :)
int distance = ((*(data + 0) & 0x0F) << 8) | (*(data + 1)); 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++; ctx->pos.frame++;
return rtn; return rtn;

View file

@ -110,11 +110,17 @@ struct opmplay_context_t {
uint32_t looped; uint32_t looped;
} pos; } pos;
// internal registers
uint8_t _4op; // used to track 4op mode changes
// opl chip context
opl3_chip* chip;
}; };
// init context // init context
int opmplay_init(opmplay_context_t* ctx); int opmplay_init(opmplay_context_t* ctx, opl3_chip *chip);
// free context // free context
int opmplay_free(opmplay_context_t* ctx); 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); int opmplay_rewind(opmplay_context_t* ctx);
// play one tick, render changes to opl3 device // 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 #ifdef __cplusplus
} }

View file

@ -39,6 +39,8 @@ enum {
OPM_REG_104 = (1 << 18), OPM_REG_104 = (1 << 18),
OPM_REG_BD = (1 << 19), OPM_REG_BD = (1 << 19),
OPM_4OP = (1 << 20),
// opm_serialize_channel_stream() tweaks // opm_serialize_channel_stream() tweaks
OPM_TL0 = (1 << 8), OPM_TL0 = (1 << 8),
OPM_TL1 = (1 << 9), OPM_TL1 = (1 << 9),
@ -59,6 +61,7 @@ struct opm_frame_record {
int block; int block;
int feedback; int feedback;
int key; int key;
int _4op;
// control stream only // control stream only
int keyperc; int keyperc;

View file

@ -48,6 +48,7 @@ enum {
OPM_STREAM_SET_FRAME_RATE = 0xFB, // word rate (as in opm_header_t::frame_rate) OPM_STREAM_SET_FRAME_RATE = 0xFB, // word rate (as in opm_header_t::frame_rate)
OPM_STREAM_LOOP = 0xFA, // set loop point here 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 OPM_KEY_TRIGGER = 0xF0, // set key on/off + optionally end of frame
// delay commands // delay commands
@ -68,6 +69,9 @@ enum {
OPM_CTRL_REG_SET = 0x80, // 80..BF - set control registers OPM_CTRL_REG_SET = 0x80, // 80..BF - set control registers
// flags // flags
OPM_4OP_OFF = (0 << 0),
OPM_4OP_ON = (1 << 0),
OPM_KEY_OFF = (0 << 0), OPM_KEY_OFF = (0 << 0),
OPM_KEY_ON = (1 << 0), OPM_KEY_ON = (1 << 0),
OPM_KEY_END_OF_FRAME = (1 << 1), OPM_KEY_END_OF_FRAME = (1 << 1),

View file

@ -147,6 +147,7 @@ int opm_requantize(opm_convert_context_t* ctx) {
// per-channel structures // per-channel structures
std::vector<opm_channel_rawdata_t> oplchans(1 + ctx->max_channels); // TODO std::vector<opm_channel_rawdata_t> oplchans(1 + ctx->max_channels); // TODO
uint32_t currentFrame = 0; uint32_t delay_rate, delay_acc = 0; 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; auto it = ctx->vgm.vgmfile.begin() + ctx->vgm.start; bool loopFrame = false;
while (it < ctx->vgm.vgmfile.begin() + ctx->vgm.end) { 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(reg & ~0xF);
oplchans[channel+chip_chidx].data.push_back(data); oplchans[channel+chip_chidx].data.push_back(data);
break; break;
break;
default: 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: global_reg:
oplchans[0].frame = currentFrame; oplchans[0].frame = currentFrame;
oplchans[0].data.push_back(reg); 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; defrec.reg_104 = data; defrec.flags |= OPM_REG_104;
// mark channel pairs as used depending on register value // mark channel pairs as used depending on register value
if ((ctx->chip_type == OPM_FLAG_CHIP_OPL3) && (ctx->max_channels >= 18)) { 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 << 0)) ctx->ch[ 0+1].used = ctx->ch[ 3+1].used = true;
if (data & (1 << 1)) ctx->ch[ 1].used = ctx->ch[ 4].used = true; if (data & (1 << 1)) ctx->ch[ 1+1].used = ctx->ch[ 4+1].used = true;
if (data & (1 << 2)) ctx->ch[ 2].used = ctx->ch[ 5].used = true; if (data & (1 << 2)) ctx->ch[ 2+1].used = ctx->ch[ 5+1].used = true;
if (data & (1 << 3)) ctx->ch[ 9].used = ctx->ch[12].used = true; if (data & (1 << 3)) ctx->ch[ 9+1].used = ctx->ch[12+1].used = true;
if (data & (1 << 4)) ctx->ch[10].used = ctx->ch[13].used = true; if (data & (1 << 4)) ctx->ch[10+1].used = ctx->ch[13+1].used = true;
if (data & (1 << 5)) ctx->ch[11].used = ctx->ch[14].used = true; if (data & (1 << 5)) ctx->ch[11+1].used = ctx->ch[14+1].used = true;
} }
break; break;
} }
case OPM_REG_105: defrec.reg_105 = data; defrec.flags |= OPM_REG_105; break; case OPM_REG_105: defrec.reg_105 = data; defrec.flags |= OPM_REG_105; break;
case OPM_REG_BD: case OPM_REG_BD:
// test low 6 bits // test low 6 bits
defrec.reg_BD = data; defrec.flags |= OPM_REG_BD; break; defrec.reg_BD = data; defrec.flags |= OPM_REG_BD;
if (((data ^ defrec.reg_BD) & 0x3F) != 0) { if (((data) & 0x3F) != 0) {
// percussion key on/off - serialize records! // percussion key on/off - serialize records!
chanrec.records.push_back(defrec); defrec.flags = 0; chanrec.records.push_back(defrec); defrec.flags = 0;
if (data & (1 << 5)) { if (data & (1 << 5)) {
// percussion mode is used - mark channels 6-8 as used // percussion mode is used - mark channels 6-8 as used
ctx->percussion_mode = true; 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; 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); defrec.key = data & (1 << 5);
if ((oldblock ^ data) == (1 << 5)) defrec.flags |= OPM_KEY; if ((oldblock ^ data) == (1 << 5)) defrec.flags |= OPM_KEY;
// serialize! // serialize!
chanrec.records.push_back(defrec); defrec.flags = 0; chanrec.records.push_back(defrec); defrec.flags = 0;
if (defrec.key != 0) { if (defrec.key != 0) {
// key on triggered - mark channel as used // key on triggered - mark channel as used
ctx->ch[ch].used = true; 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; 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: default:
break; break;
} }
@ -573,6 +601,7 @@ int opm_serialize_channel_stream(opm_convert_context_t* ctx, int ch) {
int tl_relocated; int tl_relocated;
int freq_fb; int freq_fb;
bool key_only; bool key_only;
bool _4op;
int last; int last;
} mask; } 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.freq_fb = (e.flags & (OPM_BLOCK | OPM_FNUM | OPM_FEEDBACK)) | mask.tl_relocated;
mask.last |= ((mask.freq_fb ? 4 : 0) << 0); 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.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) if (i < s.records.size() - 1)
mask.last |= (1 << 31); // prevent non-last events from emitting eof token 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); if (mask.freq_fb & OPM_BLOCK) s.rawdata.push_back(e.block);
} }
mask.last >>= 1; 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) { if (mask.key_only) {
s.rawdata.push_back( s.rawdata.push_back(
OPM_KEY_TRIGGER | OPM_KEY_TRIGGER |