implement AY/SSG player part

This commit is contained in:
wbcbz7 2025-08-13 18:49:14 +07:00
parent 4a3d35e7ec
commit ad6deb23d2
5 changed files with 156 additions and 51 deletions

View file

@ -509,6 +509,24 @@ int main(int argc, char* argv[])
yy++;
if ((ch % 3) == 2) {
tprintf(0, yy, "AY%d: [%02X %02X] [%02X %02X] [%02X %02X] %02X %02X [%02X %02X %02X] [%02X %02X] <- %02X",
cc,
opn_reg_view[cc][0],
opn_reg_view[cc][1],
opn_reg_view[cc][2],
opn_reg_view[cc][3],
opn_reg_view[cc][4],
opn_reg_view[cc][5],
opn_reg_view[cc][6],
opn_reg_view[cc][7],
opn_reg_view[cc][8],
opn_reg_view[cc][9],
opn_reg_view[cc][10],
opn_reg_view[cc][11],
opn_reg_view[cc][12],
opn_reg_view[cc][13]
);
yy++;
tprintf(0, yy, "EC%d: [%02X %02X %02X %02X %02X %02X %02X %02X]",
cc,
opn_reg_view[cc][0xAD],
@ -521,6 +539,7 @@ int main(int argc, char* argv[])
opn_reg_view[cc][0xA2]
);
yy++;
}
}
}

View file

@ -206,6 +206,81 @@ extern "C" {
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) {
@ -225,34 +300,25 @@ const uint8_t* opmplay_parse_fm_control_stream(opmplay_context_t* ctx, opmplay_c
// OP4 freq is handled by FM channel 3 stream
int mask = *data;
data++;
if (mask & OPM_CTRL_EXTCH3_OP1_HIGH) opn_write_reg(chip_index, 0xAD, *data++);
if (mask & OPM_CTRL_EXTCH3_OP1_LOW) opn_write_reg(chip_index, 0xA9, *data++);
if (mask & OPM_CTRL_EXTCH3_OP2_HIGH) opn_write_reg(chip_index, 0xAC, *data++);
if (mask & OPM_CTRL_EXTCH3_OP2_LOW) opn_write_reg(chip_index, 0xA8, *data++);
if (mask & OPM_CTRL_EXTCH3_OP3_HIGH) opn_write_reg(chip_index, 0xAE, *data++);
if (mask & OPM_CTRL_EXTCH3_OP3_LOW) opn_write_reg(chip_index, 0xAA, *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;
}
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?
doNextOp = true;
goto done;
}
if ((*data) == OPM_STREAM_END) {
// rewind to start
chctx->stream.reload = -1;
endOfFrame = true;
doNextOp = true;
goto done;
}
done:
return data;
}
@ -287,8 +353,11 @@ const uint8_t* opmplay_parse_fm_channel_stream(opmplay_context_t* ctx, opmplay_c
// frequency/feedpack
int mask = *data;
data++;
if (mask & OPM_FM_CMD80_REGA4) opn_write_reg(chip_index, 0xA4 + ch, *data++);
if (mask & OPM_FM_CMD80_REGA0) opn_write_reg(chip_index, 0xA0 + ch, *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;
@ -303,25 +372,6 @@ const uint8_t* opmplay_parse_fm_channel_stream(opmplay_context_t* ctx, opmplay_c
doNextOp = true;
goto done;
}
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?
doNextOp = true;
goto done;
}
if ((*data) == OPM_STREAM_END) {
// rewind to start
chctx->stream.reload = -1;
endOfFrame = true;
doNextOp = true;
goto done;
}
done:
return data;
}
@ -343,6 +393,17 @@ const int opmplay_parse_stream(opmplay_context_t* ctx, opmplay_channel_context_t
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
@ -356,6 +417,11 @@ const int opmplay_parse_stream(opmplay_context_t* ctx, opmplay_channel_context_t
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;
@ -404,12 +470,25 @@ int opmplay_tick(opmplay_context_t* ctx) {
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++;

View file

@ -88,6 +88,9 @@ struct opmplay_channel_context_t {
const uint8_t* ptr;
const uint8_t* loop; // if active
} stream;
// private stuff
uint8_t block; // cached block register
};
struct opmplay_context_t {
@ -103,6 +106,10 @@ struct opmplay_context_t {
uint16_t frame_looped;
uint32_t samples;
} pos;
// private stuff
uint8_t ssg_r7[2]; // cached SSG mask register
uint8_t extch3_block[2][3]; // cached extch3 block
};

View file

@ -150,7 +150,7 @@ enum {
OPM_AYTONE_CMD00_PERIOD_LOW = (1 << 5),
OPM_AYTONE_CMD00_EOF = (1 << 6),
OPM_AYTONE_CMD80_PERIOD_HIGH = (0xF << 0),
OPM_AYTONE_CMD80_PERIOD_HIGH_MASK = (0xF << 0),
OPM_AYTONE_CMD80_PERIOD_LOW = (1 << 4),
OPM_AYTONE_CMD80_EOF = (1 << 5),

View file

@ -208,7 +208,7 @@ int opm_requantize(opm_convert_context_t* ctx) {
oplchans[channel].data.push_back((data >> ch) & ((1 << 0)|(1 << 3)));
}
break;
case 5: case 11: case 12: case 13:
case 6: case 11: case 12: case 13:
// noise and envelope stuff
channel = chip_chidx + ctx->ssg_chidx + 3;
oplchans[channel].frame = currentFrame;
@ -1055,7 +1055,7 @@ int opm_serialize_ssg_tone_stream(opm_channel_t* chctx) {
OPM_AYTONE_PERIOD |
((ef == 0) && (i == s.records.size() - 1) ? OPM_AYTONE_CMD80_EOF : 0) |
(ef_cur & OPM_REC_AYTONE_PERIOD_LOW ? OPM_AYTONE_CMD80_PERIOD_LOW : 0) |
(e.aytone.period_hi & OPM_AYTONE_CMD80_PERIOD_HIGH)
(e.aytone.period_hi & OPM_AYTONE_CMD80_PERIOD_HIGH_MASK)
);
if (ef_cur & OPM_REC_AYTONE_PERIOD_LOW) s.rawdata.push_back(e.aytone.period_low);
}
@ -1076,11 +1076,11 @@ int opm_serialize_ssg_tone_stream(opm_channel_t* chctx) {
if (ef & OPM_REC_AYTONE_MASK) {
// write mask at last
ef & ~OPM_REC_AYTONE_MASK;
ef &= ~OPM_REC_AYTONE_MASK;
// write registers
s.rawdata.push_back(
OPM_AYTONE_REGS |
((ef == 0) && (i == s.records.size() - 1) ? OPM_AYTONE_CMD00_EOF : 0) |
OPM_AYTONE_MASK |
((ef == 0) && (i == s.records.size() - 1) ? OPM_AYTONE_MASK_EOF : 0) |
(e.aytone.mask & 1 ? OPM_AYTONE_MASK_TONE : 0) |
(e.aytone.mask & 8 ? OPM_AYTONE_MASK_NOISE : 0)
);