fix internal structures, extend file format for OPL3 support
This commit is contained in:
parent
e4d8dfe028
commit
a6b1293d37
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue