fix internal structures, extend file format for OPL3 support

This commit is contained in:
wbcbz7 2024-06-08 05:32:05 +07:00
parent e4d8dfe028
commit a6b1293d37
6 changed files with 147 additions and 136 deletions

View file

@ -39,7 +39,7 @@ int opmplay_init(opmplay_context_t* ctx) {
int opmplay_free(opmplay_context_t* ctx)
{
for (int ch = 0; ch < OPMPLAY_MAX_CHANNLES; ch++) {
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;
}
@ -87,19 +87,22 @@ int opmplay_load_module(opmplay_context_t* ctx, opmplay_io_t* io) {
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) * (OPMPLAY_MAX_CHANNLES));
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) * (OPMPLAY_MAX_CHANNLES))
!= sizeof(opm_header_stream_desc_t) * (OPMPLAY_MAX_CHANNLES))
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 < OPMPLAY_MAX_CHANNLES; ch++) {
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;
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
@ -124,7 +127,7 @@ void opmplay_push_stack(opmplay_channel_context_t* chctx) {
int opmplay_loop(opmplay_context_t* ctx) {
// channel streams
for (int ch = 0; ch < OPMPLAY_MAX_CHANNLES; ch++) {
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;
@ -136,7 +139,7 @@ int opmplay_loop(opmplay_context_t* ctx) {
}
int opmplay_rewind(opmplay_context_t* ctx) {
for (int ch = 0; ch < OPMPLAY_MAX_CHANNLES; ch++) {
for (int ch = 0; ch < ctx->header.channels + 1; ch++) {
ctx->channels[ch].stream.loop = ctx->channels[ch].stream.data;
}
@ -177,7 +180,12 @@ static uint32_t opmplay_set_delay(uint8_t** data) {
// translation tables (TODO: OPL3)
static int opmplay_opregs_channel_offset[] = {
0, 1, 2, 8, 9, 0xA, 0x10, 0x11, 0x12
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,
};
int opmplay_tick(opmplay_context_t* ctx, opl3_chip* opl3) {
@ -251,9 +259,9 @@ int opmplay_tick(opmplay_context_t* ctx, opl3_chip* opl3) {
}
// parse channel streams
for (int ch = 0; ch < OPMPLAY_MAX_CHANNLES - 1; ch++) {
for (int ch = 0; ch < ctx->header.channels; ch++) {
opmplay_channel_context_t* chctx = ctx->channels + ch + 1;
uint8_t* data = chctx->stream.ptr;
uint8_t* data = chctx->stream.ptr; if (data == NULL) continue;
bool isRun = true;
if (--chctx->stream.delay == 0) {
while (isRun) {
@ -275,15 +283,15 @@ int opmplay_tick(opmplay_context_t* ctx, opl3_chip* opl3) {
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 + ch, *data++);
if (mask & OPM_CMD80_SET_FREQ) OPL3_WriteRegBuffered(opl3, 0xA0 + ch, *data++);
if (mask & OPM_CMD80_SET_KEYBLOCK) { chctx->block = *data; OPL3_WriteRegBuffered(opl3, 0xB0 + 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_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 + ch, chctx->block);
OPL3_WriteRegBuffered(opl3, 0xB0 + opmplay_regs_channel_offset[ch], chctx->block);
if (*data & OPM_KEY_END_OF_FRAME) isRun = false;
data++;
continue;

View file

@ -26,7 +26,7 @@ extern "C" {
// general enums
enum {
OPMPLAY_MAX_CHANNLES = 9+1,
OPMPLAY_MAX_CHANNLES = 18+1,
OPMPLAY_MAX_STACK_DEPTH = 4,
};
@ -99,7 +99,7 @@ struct opmplay_context_t {
opm_header_t header;
// channel context
opmplay_channel_context_t channels[1+9];
opmplay_channel_context_t channels[OPMPLAY_MAX_CHANNLES];
// position data
struct {

View file

@ -109,17 +109,17 @@ static match_info_t find_match(opm_convert_context_t* ctx,
}
uint32_t opm_compress_channel(opm_convert_context_t* ctx, int ch, int min_backref_length) {
ctx->opmpacked[ch].clear();
auto& src_ch = ctx->opmrecords[ch];
ctx->ch[ch].packed.clear();
auto& src_ch = ctx->ch[ch].records;
int pos = 0; uint32_t total_bytes = 0;
while (pos != src_ch.size()) {
// find match
auto match = find_match(ctx, src_ch, ctx->opmpacked[ch], pos, MAX_BACKREF_DISTANCE_FRAMES, min_backref_length);
auto match = find_match(ctx, src_ch, ctx->ch[ch].packed, pos, MAX_BACKREF_DISTANCE_FRAMES, min_backref_length);
if (match.logic_frames == 0) {
// copy one frame, recalculate byte stamp!
opm_channel_record_t rec = src_ch[pos];
rec.byte_stamp = total_bytes;
ctx->opmpacked[ch].push_back(src_ch[pos]);
ctx->ch[ch].packed.push_back(src_ch[pos]);
total_bytes += src_ch[pos].rawdata.size();
pos++;
}
@ -135,19 +135,18 @@ uint32_t opm_compress_channel(opm_convert_context_t* ctx, int ch, int min_backre
rec.rawdata.push_back(OPM_STREAM_BACKREF);
rec.rawdata.push_back(0);
rec.rawdata.push_back(rec.frames_to_play);
ctx->opmpacked[ch].push_back(rec);
ctx->ch[ch].packed.push_back(rec);
pos += match.total_frames; // skip matched data
total_bytes += rec.rawdata.size();
}
}
if (ctx->flags.verbosity >= 2) {
printf("ch %d min backref %d - %d records, %d bytes\n", ch, min_backref_length, ctx->opmpacked[ch].size(), total_bytes);
printf("ch %d min backref %d - %d records, %d bytes\n", ch, min_backref_length, ctx->ch[ch].packed.size(), total_bytes);
}
return total_bytes;
}
void opm_compress(opm_convert_context_t* ctx) {
ctx->opmpacked.resize(ctx->opmrecords.size());
printf("compressing"); fflush(stdout);
// messy backref bruteforce stuff
@ -164,8 +163,7 @@ void opm_compress(opm_convert_context_t* ctx) {
break;
};
int ch = 0;
for (auto& src_ch : ctx->opmrecords) {
for (int ch = 0; ch < ctx->max_channels+1; ch++) {
if (ctx->flags.verbosity >= 2) {
printf("\n");
}
@ -187,20 +185,19 @@ void opm_compress(opm_convert_context_t* ctx) {
// calculate byte offsets for each frame
std::vector<uint32_t> ch_bytepos;
uint32_t bytepos_cur = 0;
for (int f = 0; f < ctx->opmpacked[ch].size(); f++) {
for (int f = 0; f < ctx->ch[ch].packed.size(); f++) {
ch_bytepos.push_back(bytepos_cur);
bytepos_cur += ctx->opmpacked[ch][f].rawdata.size();
bytepos_cur += ctx->ch[ch].packed[f].rawdata.size();
}
// resolve back references
for (int f = 0; f < ctx->opmpacked[ch].size(); f++) {
for (int f = 0; f < ctx->ch[ch].packed.size(); f++) {
// fixup back reference (if any)
if (ctx->opmpacked[ch][f].flags & OPM_CHAN_BACKREF) {
uint32_t backref_dist = ch_bytepos[f] - ch_bytepos[f - ctx->opmpacked[ch][f].distance_frames];
ctx->opmpacked[ch][f].rawdata[0] = OPM_STREAM_BACKREF | (backref_dist >> 8);
ctx->opmpacked[ch][f].rawdata[1] = (backref_dist & 0xFF);
if (ctx->ch[ch].packed[f].flags & OPM_CHAN_BACKREF) {
uint32_t backref_dist = ch_bytepos[f] - ch_bytepos[f - ctx->ch[ch].packed[f].distance_frames];
ctx->ch[ch].packed[f].rawdata[0] = OPM_STREAM_BACKREF | (backref_dist >> 8);
ctx->ch[ch].packed[f].rawdata[1] = (backref_dist & 0xFF);
}
}
ch++;
#ifndef ULTRA_DEBUG
printf("."); fflush(stdout);
#endif

View file

@ -119,6 +119,9 @@ struct opm_channel_rawdata_t {
};
struct opm_channel_t {
// non-empty (i.e key on present)
bool used;
// OPL register stream (parsed from VGM)
std::vector<opm_channel_rawdata_t> opl;
@ -126,10 +129,10 @@ struct opm_channel_t {
std::vector<opm_channel_record_t> records;
// packed records
std::vector<opm_channel_record_t> records_packed;
std::vector<opm_channel_record_t> packed;
// raw stream bytes
std::vector<uint8_t> stream_bytes;
std::vector<uint8_t> rawstream;
// stream descriptor
opm_header_stream_desc_t streamdesc;
@ -141,6 +144,8 @@ struct opm_convert_context_t {
// chip type
uint32_t chip_type; // OPM_FLAG_CHIP_*
uint32_t max_channels;
bool percussion_mode;
// conversion flags
struct {
@ -156,6 +161,10 @@ struct opm_convert_context_t {
// total frames
uint32_t total_frames;
// -----------------------------------
// channel struct
std::vector<opm_channel_t> ch;
// raw per-channel OPL data
std::vector<std::vector<opm_channel_rawdata_t>> oplchan;
std::vector<std::vector<uint8_t>> oplchan_out;

View file

@ -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:

View file

@ -144,10 +144,8 @@ int chanParamsToChan[16] = {
// ------------------------
// 2nd step - extract register writes from VGM, requantize to new frames
int opm_requantize(opm_convert_context_t* ctx) {
ctx->oplchan.resize(1 + 18);
// per-channel structures
std::vector<opm_channel_rawdata_t> oplchans(1 + 18); // TODO
std::vector<opm_channel_rawdata_t> oplchans(1 + ctx->max_channels); // TODO
uint32_t currentFrame = 0; uint32_t delay_rate, delay_acc = 0;
auto it = ctx->vgm.vgmfile.begin() + ctx->vgm.start; bool loopFrame = false;
@ -157,13 +155,18 @@ int opm_requantize(opm_convert_context_t* ctx) {
if (it == ctx->vgm.vgmfile.begin() + ctx->vgm.loop_pos) {
loopFrame = true;
for (int i = 0; i < 9 + 1; i++) {
for (int i = 0; i < ctx->max_channels + 1; i++) {
oplchans[i].frame = currentFrame;
}
}
// first, do OPL cases
int chip_idx = 0, chip_chidx = 0;
switch ((VGM_Stream_Opcode)*it) {
case VGM_Stream_Opcode::YMF262_PORT1_WRITE:
if (ctx->chip_type != OPM_FLAG_CHIP_OPL3) break;
chip_idx = 1; chip_chidx = 9;
[[fallthrough]]
case VGM_Stream_Opcode::YMF262_PORT0_WRITE:
case VGM_Stream_Opcode::YM3812_WRITE: {
// get register and data
@ -186,9 +189,9 @@ int opm_requantize(opm_convert_context_t* ctx) {
// translate slot
slot = slotParamsToIndex[reg & 0x1F];
// push data
oplchans[channel].frame = currentFrame;
oplchans[channel].data.push_back((reg & ~0x1F) + slot);
oplchans[channel].data.push_back(data);
oplchans[channel+chip_chidx].frame = currentFrame;
oplchans[channel+chip_chidx].data.push_back((reg & ~0x1F) + slot);
oplchans[channel+chip_chidx].data.push_back(data);
break;
case 0xB0:
if (reg == 0xBD) goto global_reg; // special case
@ -197,11 +200,12 @@ int opm_requantize(opm_convert_context_t* ctx) {
case 0xC0:
channel = chanParamsToChan[reg & 0xF]+1;
if (channel == -1) break;
oplchans[channel].frame = currentFrame;
oplchans[channel].data.push_back(reg & ~0xF);
oplchans[channel].data.push_back(data);
oplchans[channel+chip_chidx].frame = currentFrame;
oplchans[channel+chip_chidx].data.push_back(reg & ~0xF);
oplchans[channel+chip_chidx].data.push_back(data);
break;
default:
if ((chip_idx == 1) && (reg == 0x04 || reg == 0x05)) break;
global_reg:
oplchans[0].frame = currentFrame;
oplchans[0].data.push_back(reg);
@ -251,7 +255,7 @@ int opm_requantize(opm_convert_context_t* ctx) {
delay_acc = 0;
for (int ch = 0; ch < oplchans.size(); ch++) if (oplchans[ch].frame != -1) {
oplchans[ch].loop = loopFrame;
ctx->oplchan[ch].push_back(oplchans[ch]);
ctx->ch[ch].opl.push_back(oplchans[ch]);
oplchans[ch].data.clear();
oplchans[ch].frame = -1;
}
@ -267,15 +271,15 @@ int opm_requantize(opm_convert_context_t* ctx) {
}
// save total count of frames
if (ctx->oplchan[0].back().frame == currentFrame) {
ctx->oplchan[0].back().data.push_back(OPM_STREAM_END);
if (ctx->ch[0].opl.back().frame == currentFrame) {
ctx->ch[0].opl.back().data.push_back(OPM_STREAM_END);
}
else {
opm_channel_rawdata_t endmarker;
endmarker.frame = currentFrame;
endmarker.loop = false;
endmarker.data.push_back(OPM_STREAM_END);
ctx->oplchan[0].push_back(endmarker);
ctx->ch[0].opl.push_back(endmarker);
}
ctx->total_frames = currentFrame;
@ -296,48 +300,17 @@ int opm_write_file(opm_convert_context_t* ctx) {
memset(&ctx->opmfile.header, 0, sizeof(ctx->opmfile.header));
memcpy(ctx->opmfile.header.magic, "OPM\x1A", 4);
ctx->opmfile.header.version.v = OPM_FORMAT_VERSION;
ctx->opmfile.header.flags = ctx->chip_type;
ctx->opmfile.header.flags = (ctx->chip_type) | (ctx->percussion_mode ? OPM_FLAG_PERCUSSION_MODE : 0);
ctx->opmfile.header.callstack_depth = ctx->flags.max_stack_depth;
ctx->opmfile.header.frame_rate = ((double)0x1234DD / (double)(44100 / ctx->delay));
struct opm_write_file_info_t {
uint32_t pos;
uint32_t padding_pre;
uint32_t size;
};
std::vector<opm_write_file_info_t> writeinfo(9 + 1);
ctx->opmfile.streamdesc.resize(9 + 1);
#if 0
// calculate offsets
auto fsize = round_to_para(sizeof(ctx->opmfile.header) + (9 + 1) * sizeof(opm_header_stream_desc_t));
for (int i = 0; i < 9 + 1; i++) {
writeinfo[i].pos = fsize.pos;
writeinfo[i].padding_pre = fsize.pad;
writeinfo[i].size = ctx->oplchan_out[i].size();
ctx->opmfile.streamdesc[i].ptr = (fsize.pos >> 4);
ctx->opmfile.streamdesc[i].size = ctx->oplchan_out[i].size();
fsize = round_to_para(fsize.pos + ctx->oplchan_out[i].size());
ctx->opmfile.header.channels = ctx->max_channels;
ctx->opmfile.streamdesc.resize(ctx->max_channels + 1);
for (int ch = 0; ch < ctx->max_channels; ch++) {
if (ctx->ch[ch + 1].used) ctx->opmfile.header.channel_mask |= (1 << ch);
}
// dump to OPM file
FILE* f = fopen(ctx->opmfile.filename.c_str(), "wb");
if (!f) return 1;
// write header
fwrite(&ctx->opmfile.header, sizeof(opm_header_t), 1, f);
fwrite(ctx->opmfile.streamdesc.data(), sizeof(decltype(ctx->opmfile.streamdesc)::value_type), ctx->opmfile.streamdesc.size(), f);
// write (aligned) channel streams
for (int i = 0; i < 1 + 9; i++) {
if (writeinfo[i].padding_pre > 0) for (int c = 0; c < writeinfo[i].padding_pre; c++) fputc(0, f); // dirty!!!
fwrite(ctx->oplchan_out[i].data(), sizeof(uint8_t), ctx->oplchan_out[i].size(), f);
}
fclose(f);
#else
for (int i = 0; i < 9 + 1; i++) {
ctx->opmfile.streamdesc[i].size = ctx->oplchan_out[i].size();
for (int ch = 0; ch < ctx->max_channels + 1; ch++) {
ctx->opmfile.streamdesc[ch].size = ctx->ch[ch].rawstream.size();
}
// dump to OPM file
FILE* f = fopen(ctx->opmfile.filename.c_str(), "wb");
@ -347,11 +320,11 @@ int opm_write_file(opm_convert_context_t* ctx) {
fwrite(&ctx->opmfile.header, sizeof(opm_header_t), 1, f);
fwrite(ctx->opmfile.streamdesc.data(), sizeof(decltype(ctx->opmfile.streamdesc)::value_type), ctx->opmfile.streamdesc.size(), f);
// write channel streams
for (int i = 0; i < 1 + 9; i++) {
fwrite(ctx->oplchan_out[i].data(), sizeof(uint8_t), ctx->oplchan_out[i].size(), f);
for (int ch = 0; ch < 1 + ctx->max_channels; ch++) {
fwrite(ctx->ch[ch].rawstream.data(), sizeof(uint8_t), ctx->ch[ch].rawstream.size(), f);
}
fclose(f);
#endif
return 0;
}
@ -361,8 +334,8 @@ std::map<int, int> opm_reg_to_ctrl = {
{0x1, OPM_REG_01},
{0x8, OPM_REG_08},
{0xBD, OPM_REG_BD},
{0x104, OPM_REG_104},
{0x105, OPM_REG_105},
{0x04, OPM_REG_104}, // !!!!
{0x05, OPM_REG_105}, // !!!!
};
int opm_group_control_stream(opm_convert_context_t* ctx) {
@ -370,17 +343,17 @@ int opm_group_control_stream(opm_convert_context_t* ctx) {
opm_frame_record defrec;
memset(&defrec, -1, sizeof(defrec));
for (int f = 0; f < ctx->oplchan[0].size(); f++) {
for (int f = 0; f < ctx->ch[0].opl.size(); f++) {
opm_channel_record_t chanrec;
chanrec.frame = ctx->oplchan[0][f].frame;
chanrec.frame_dist = f >= ctx->oplchan[0].size() - 1 ? 0 : ctx->oplchan[0][f + 1].frame - ctx->oplchan[0][f].frame;
chanrec.frame = ctx->ch[0].opl[f].frame;
chanrec.frame_dist = f >= ctx->ch[0].opl.size() - 1 ? 0 : ctx->ch[0].opl[f + 1].frame - ctx->ch[0].opl[f].frame;
chanrec.flags = 0;
if (ctx->oplchan[0][f].loop) chanrec.flags |= OPM_CHAN_LOOP_POINT;
if (ctx->ch[0].opl[f].loop) chanrec.flags |= OPM_CHAN_LOOP_POINT;
defrec.flags = 0;
// parse register writes
bool isFrame = true; auto it = ctx->oplchan[0][f].data.begin();
while (isFrame && (it < ctx->oplchan[0][f].data.end())) {
bool isFrame = true; auto it = ctx->ch[0].opl[f].data.begin();
while (isFrame && (it < ctx->ch[0].opl[f].data.end())) {
// get reg:data pair
int reg = *it++; if (reg == OPM_STREAM_END) break;
@ -392,7 +365,19 @@ int opm_group_control_stream(opm_convert_context_t* ctx) {
switch (idx->second) {
case OPM_REG_01: defrec.reg_01 = data; defrec.flags |= OPM_REG_01; break;
case OPM_REG_08: defrec.reg_08 = data; defrec.flags |= OPM_REG_08; break;
case OPM_REG_104: defrec.reg_104 = data; defrec.flags |= OPM_REG_104; break;
case OPM_REG_104: {
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;
}
break;
}
case OPM_REG_105: defrec.reg_105 = data; defrec.flags |= OPM_REG_105; break;
case OPM_REG_BD:
// test low 6 bits
@ -400,6 +385,11 @@ int opm_group_control_stream(opm_convert_context_t* ctx) {
if (((data ^ defrec.reg_BD) & 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;
}
}
break;
default: break;
@ -409,28 +399,28 @@ int opm_group_control_stream(opm_convert_context_t* ctx) {
if (defrec.flags) {
chanrec.records.push_back(defrec); defrec.flags = 0;
}
ctx->opmrecords[0].push_back(chanrec);
ctx->ch[0].records.push_back(chanrec);
}
return 0;
}
int opm_group_channel_stream(opm_convert_context_t* ctx, int ch) {
int opm_group_channel_stream(opm_convert_context_t *ctx, opm_channel_t* chctx, int ch) {
// default (startup) frame record
opm_frame_record defrec;
memset(&defrec, -1, sizeof(defrec));
for (int f = 0; f < ctx->oplchan[ch].size(); f++) {
for (int f = 0; f < chctx->opl.size(); f++) {
opm_channel_record_t chanrec;
chanrec.frame = ctx->oplchan[ch][f].frame;
chanrec.frame_dist = f >= ctx->oplchan[ch].size()-1 ? 0 : ctx->oplchan[ch][f+1].frame - ctx->oplchan[ch][f].frame;
chanrec.frame = chctx->opl[f].frame;
chanrec.frame_dist = f >= chctx->opl.size()-1 ? 0 : chctx->opl[f+1].frame - chctx->opl[f].frame;
chanrec.flags = 0;
if (ctx->oplchan[ch][f].loop) chanrec.flags |= OPM_CHAN_LOOP_POINT;
if (chctx->opl[f].loop) chanrec.flags |= OPM_CHAN_LOOP_POINT;
defrec.flags = 0;
// parse register writes
bool isFrame = true; auto it = ctx->oplchan[ch][f].data.begin();
while (isFrame && (it < ctx->oplchan[ch][f].data.end())) {
bool isFrame = true; auto it = chctx->opl[f].data.begin();
while (isFrame && (it < chctx->opl[f].data.end())) {
// get reg:data pair
int reg = *it++; if (reg == OPM_STREAM_END) break;
@ -472,7 +462,11 @@ int opm_group_channel_stream(opm_convert_context_t* ctx, int ch) {
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;
}
}
break;
}
@ -483,7 +477,7 @@ int opm_group_channel_stream(opm_convert_context_t* ctx, int ch) {
if (defrec.flags != 0) {
chanrec.records.push_back(defrec); defrec.flags = 0;
}
ctx->opmrecords[ch].push_back(chanrec);
ctx->ch[ch].records.push_back(chanrec);
}
return 0;
@ -493,11 +487,9 @@ int opm_group_channel_stream(opm_convert_context_t* ctx, int ch) {
// 3rd step - group events by registers written
// key changes are acting as "fence" (the entire stream is flushed in this case)
int opm_group_registers(opm_convert_context_t* ctx) {
ctx->opmrecords.resize(18 + 1);
opm_group_control_stream(ctx);
for (int ch = 0; ch < 9; ch++) {
opm_group_channel_stream(ctx, ch+1);
for (int ch = 0; ch < ctx->max_channels; ch++) {
opm_group_channel_stream(ctx, ctx->ch.data() + ch + 1, ch + 1);
}
return 0;
@ -529,7 +521,7 @@ void opm_set_delay(std::vector<uint8_t>& vec, uint64_t delay) {
int opm_serialize_control_stream(opm_convert_context_t* ctx) {
int old_delay = -1;
for (auto& s : ctx->opmrecords[0]) {
for (auto& s : ctx->ch[0].records) {
s.rawdata.clear();
// set loop point
@ -561,7 +553,7 @@ int opm_serialize_control_stream(opm_convert_context_t* ctx) {
int opm_serialize_channel_stream(opm_convert_context_t* ctx, int ch) {
int old_delay = -1;
uint32_t byte_stamp = 0;
for (auto& s : ctx->opmrecords[ch]) {
for (auto& s : ctx->ch[ch].records) {
s.rawdata.clear();
// set loop point
@ -659,7 +651,7 @@ int opm_serialize_channel_stream(opm_convert_context_t* ctx, int ch) {
// 4th step - generate byte stream
int opm_serialize_stream(opm_convert_context_t* ctx) {
opm_serialize_control_stream(ctx);
for (int ch = 0; ch < 9; ch++) {
for (int ch = 0; ch < ctx->max_channels; ch++) {
opm_serialize_channel_stream(ctx, ch + 1);
}
return 0;
@ -668,14 +660,12 @@ int opm_serialize_stream(opm_convert_context_t* ctx) {
// --------------------------
// 5th step - not compress yet :) concatenate byte streams of all frames
int opm_concat_streams(opm_convert_context_t* ctx) {
ctx->oplchan_out.resize(1 + 9); // TODO: FIXME for OPL3 support!!
for (int ch = 0; ch < (1 + 9); ch++) {
//for (auto& f : ctx->opmrecords[ch]) {
for (auto& f : ctx->opmpacked[ch]) {
for (int ch = 0; ch < (1 + ctx->max_channels); ch++) {
for (auto& f : ctx->ch[ch].packed) {
// copy raw register data
ctx->oplchan_out[ch].insert(ctx->oplchan_out[ch].end(), f.rawdata.begin(), f.rawdata.end());
ctx->ch[ch].rawstream.insert(ctx->ch[ch].rawstream.end(), f.rawdata.begin(), f.rawdata.end());
}
ctx->oplchan_out[ch].push_back(OPM_STREAM_END);
ctx->ch[ch].rawstream.push_back(OPM_STREAM_END);
}
return 0;
@ -683,8 +673,8 @@ int opm_concat_streams(opm_convert_context_t* ctx) {
int opm_dump_backref(opm_convert_context_t* ctx, FILE* f, int ch, int pos, int frames, int current_frame) {
if (ctx->opmpacked[ch].size() > 0) do {
auto& a = ctx->opmpacked[ch][pos];
if (ctx->ch[ch].packed.size() > 0) do {
auto& a = ctx->ch[ch].packed[pos];
if (a.flags & OPM_CHAN_BACKREF) {
current_frame = opm_dump_backref(ctx, f, ch, pos - a.distance_frames, a.frames_to_play, current_frame);
}
@ -695,7 +685,7 @@ int opm_dump_backref(opm_convert_context_t* ctx, FILE* f, int ch, int pos, int f
current_frame += a.frame_dist;
}
pos++;
} while ((--frames != 0) && (pos < ctx->opmpacked[ch].size()));
} while ((--frames != 0) && (pos < ctx->ch[ch].packed.size()));
return current_frame;
}
@ -704,9 +694,9 @@ int opm_dump_backref(opm_convert_context_t* ctx, FILE* f, int ch, int pos, int f
int opm_dump_events(opm_convert_context_t* ctx) {
FILE* f = fopen(ctx->logname.c_str(), "w");
fprintf(f, "total %d frames\n", ctx->total_frames);
for (int ch = 0; ch < (ctx->opmrecords.size()); ch++) {
for (int ch = 0; ch < (ctx->max_channels+1); ch++) {
fprintf(f, "---channel %d\n", ch - 1);
for (auto& a : ctx->opmrecords[ch]) {
for (auto& a : ctx->ch[ch].records) {
fprintf(f, "frame %07d, dist %d: data ", a.frame, a.frame_dist);
for (auto& d : a.rawdata) fprintf(f, "%02X ", d);
fprintf(f, "\n");
@ -717,9 +707,9 @@ int opm_dump_events(opm_convert_context_t* ctx) {
f = fopen((ctx->logname + ".packed.log").c_str(), "w");
if (ctx->flags.compress_level > 0) {
fprintf(f, "total %d frames\n", ctx->total_frames);
for (int ch = 0; ch < (ctx->opmpacked.size()); ch++) {
for (int ch = 0; ch < (ctx->max_channels+1); ch++) {
fprintf(f, "---channel %d\n", ch - 1);
if (ctx->opmpacked[ch].size() > 0) for (auto& a : ctx->opmpacked[ch]) {
if (ctx->ch[ch].packed.size() > 0) for (auto& a : ctx->ch[ch].packed) {
fprintf(f, "frame %07d, dist %d: data ", a.frame, a.frame_dist);
for (auto& d : a.rawdata) fprintf(f, "%02X ", d);
fprintf(f, "\n");
@ -732,7 +722,7 @@ int opm_dump_events(opm_convert_context_t* ctx) {
f = fopen((ctx->logname + ".unpacked.log").c_str(), "w");
if (ctx->flags.compress_level > 0) {
fprintf(f, "total %d frames\n", ctx->total_frames);
for (int ch = 0; ch < (ctx->opmpacked.size()); ch++) {
for (int ch = 0; ch < (ctx->max_channels+1); ch++) {
fprintf(f, "---channel %d\n", ch - 1);
opm_dump_backref(ctx, f, ch, 0, -1, 0);
}
@ -757,6 +747,8 @@ int main(int argc, char* argv[]) {
ctx->flags.max_stack_depth = 1;
ctx->flags.verbosity = 1;
ctx->percussion_mode = false;
if (argc < 2) {
printf("usage: vgm2opm input.vgm [-cx] [-dx] [-vx]\n");
printf("-c[x]: enable compression, [x] - mode (default - 1):\n");
@ -822,10 +814,12 @@ int main(int argc, char* argv[]) {
if (ctx->vgm.header->YMF262_Clock != 0) {
ctx->chip_type = OPM_FLAG_CHIP_OPL3;
oplClockRate = ctx->vgm.header->YMF262_Clock;
ctx->max_channels = 18;
}
else if (ctx->vgm.header->YM3812_Clock != 0) {
ctx->chip_type = OPM_FLAG_CHIP_OPL2;
oplClockRate = ctx->vgm.header->YM3812_Clock;
ctx->max_channels = 9;
}
else {
printf("OPL2/3 data not found!\n");
@ -838,6 +832,9 @@ int main(int argc, char* argv[]) {
oplClockRate
);
// allocate channels
ctx->ch.resize(ctx->max_channels + 1);
// set estimation parameters
ctx->estimate.max_delay_base = (44100.0 / ((double)0x1234DD / 65536));
ctx->estimate.max_delay_freq = 400.0;