bare minimum 2xOPN (w/o SSG) done

This commit is contained in:
wbcbz7 2025-08-12 20:59:39 +07:00
parent ae9302f049
commit db1f29e227
59 changed files with 627 additions and 611 deletions

View file

@ -1,429 +0,0 @@
#include <stdint.h>
#include "opmplay.h"
#include "opmfile.h"
// -------------------------
// file I/O procedures
static uint32_t opmplay_mem_read(opmplay_io_t* io, void* dst, uint32_t size) {
if ((io == NULL) || dst == NULL) return OPMPLAY_ERR_NULLPTR;
if ((size + io->offset) > io->size) return OPMPLAY_ERR_IO;
opmplay_memcpy(dst, (uint8_t*)io->buf + io->offset, io->size);
io->offset += io->size;
return size;
}
static uint32_t opmplay_mem_seek(opmplay_io_t* io, uint32_t pos) {
if (io == NULL) return OPMPLAY_ERR_NULLPTR;
if (pos > io->size) return OPMPLAY_ERR_IO;
io->offset = pos;
return 0;
}
#ifdef OPMPLAY_ENABLE_STDIO
static uint32_t opmplay_file_read(opmplay_io_t* io, void* dst, uint32_t size) {
return fread(dst, 1, size, io->io);
}
static uint32_t opmplay_file_seek(opmplay_io_t* io, uint32_t pos) {
return fseek(io->io, pos, SEEK_SET);
}
#endif
// -------------------------
int opmplay_init(opmplay_context_t* ctx, opl3_chip* chip) {
if ((ctx == NULL) || (chip == NULL)) return OPMPLAY_ERR_NULLPTR;
opmplay_memset(ctx, 0, sizeof(opmplay_context_t));
ctx->chip = chip;
return OPMPLAY_ERR_OK;
}
int opmplay_free(opmplay_context_t* ctx)
{
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;
}
}
return OPMPLAY_ERR_OK;
}
// load file header
int opmplay_load_header(opmplay_context_t* ctx, opmplay_io_t* io) {
if ((ctx == NULL) || (io == NULL)) return OPMPLAY_ERR_NULLPTR;
// init file I/O handlers
switch (io->type) {
#ifdef OPMPLAY_ENABLE_STDIO
case OPMPLAY_IO_FILE:
io->read = &opmplay_file_read;
io->seek = &opmplay_file_seek;
break;
#endif
case OPMPLAY_IO_MEMORY:
io->read = &opmplay_mem_read;
io->seek = &opmplay_mem_seek;
break;
case OPMPLAY_IO_USER:
// use user-provided I/O functions
if ((io->read == NULL) || (io->seek == NULL)) return OPMPLAY_ERR_BAD_PARAMETER;
break;
default:
return OPMPLAY_ERR_BAD_PARAMETER;
}
// read header
if (io->read(io, &ctx->header, sizeof(ctx->header)) != sizeof(ctx->header)) return OPMPLAY_ERR_IO;
// and validate it
if ((opmplay_memcmp(ctx->header.magic, "OPM\x1A", sizeof(ctx->header.magic))) || (ctx->header.version.v != OPM_FORMAT_VERSION))
return OPMPLAY_ERR_BAD_FILE_STRUCTURE;
// done for now, waiting for opmplay_load_module :)
return OPMPLAY_ERR_OK;
}
int opmplay_load_module(opmplay_context_t* ctx, opmplay_io_t* io) {
if ((ctx == NULL) || (io == NULL)) return OPMPLAY_ERR_NULLPTR;
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) * (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) * (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 < 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
opmplay_rewind(ctx);
// done :)
return OPMPLAY_ERR_OK;
}
void opmplay_pop_stack(opmplay_channel_context_t* chctx) {
opmplay_channel_stack_t* st = chctx->stack + (--chctx->stack_pos);
chctx->stream.ptr = st->ptr;
chctx->stream.samples_to_play = st->frames_to_play;
}
void opmplay_push_stack(opmplay_channel_context_t* chctx) {
opmplay_channel_stack_t* st = chctx->stack + chctx->stack_pos;
st->ptr = chctx->stream.ptr;
st->frames_to_play = chctx->stream.samples_to_play;
chctx->stack_pos++;
}
int opmplay_loop(opmplay_context_t* ctx) {
// channel streams
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;
ctx->channels[ch].stream.ptr = ctx->channels[ch].stream.loop;
ctx->channels[ch].stream.delay = ctx->channels[ch].stream.reload = 1;
}
return OPMPLAY_ERR_OK;
}
int opmplay_rewind(opmplay_context_t* ctx) {
// set chip mode
switch (ctx->header.flags & OPM_FLAG_CHIP_TYPE) {
case OPM_FLAG_CHIP_OPL3:
// set OPL3 mode
OPL3_WriteRegBuffered(ctx->chip, 0x105, 0x01);
// disable 4-op
OPL3_WriteRegBuffered(ctx->chip, 0x104, 0x00);
// send key off for every channel
for (int ch = 0; ch < 9; ch++) {
OPL3_WriteRegBuffered(ctx->chip, 0x0B0 + ch, 0x00);
OPL3_WriteRegBuffered(ctx->chip, 0x1B0 + ch, 0x00);
}
break;
case OPM_FLAG_CHIP_OPL2:
// enable waveforms
OPL3_WriteRegBuffered(ctx->chip, 0x001, 0x20);
// send key off for every channel
for (int ch = 0; ch < 9; ch++) {
OPL3_WriteRegBuffered(ctx->chip, 0x0B0 + ch, 0x00);
}
break;
default: return OPMPLAY_ERR_DEVICE;
}
for (int ch = 0; ch < ctx->header.channels + 1; ch++) {
ctx->channels[ch].stream.loop = ctx->channels[ch].stream.data;
}
ctx->_4op = 0;
opmplay_loop(ctx);
return OPMPLAY_ERR_OK;
}
// get and parse delay
static uint32_t opmplay_set_delay(uint8_t** data) {
uint32_t delay = 0;
if (**data == OPM_STREAM_DELAY_INT32) {
delay = (
(*(*data + 1) << 0) |
(*(*data + 2) << 8) |
(*(*data + 3) << 16) |
(*(*data + 4) << 24)
);
*data += 5;
}
else if (**data == OPM_STREAM_DELAY_INT16) {
delay = (
(*(*data + 1) << 0) |
(*(*data + 2) << 8)
);
*data += 3;
}
else if ((**data & 0xF0) == OPM_STREAM_DELAY_INT12) {
delay = ((**data & 0x0F) << 8) | (*(*data + 1));
*data += 2;
}
else if ((**data & 0xF0) == OPM_STERAM_DELAY_SHORT) {
delay = (**data & 0xF) + 1;
(*data)++;
}
return delay;
}
// translation tables
static int opmplay_opregs_channel_offset[] = {
0x000, 0x001, 0x002, 0x008, 0x009, 0x00A, 0x010, 0x011, 0x012,
0x100, 0x101, 0x102, 0x108, 0x109, 0x10A, 0x110, 0x111, 0x112,
};
static int opmplay_regs_channel_offset[] = {
0x000, 0x001, 0x002, 0x003, 0x004, 0x005, 0x006, 0x007, 0x008,
0x100, 0x101, 0x102, 0x103, 0x104, 0x105, 0x106, 0x107, 0x108,
};
static int opmplay_4op_bit_offset[] = {
1<<0, 1<<1, 1<<2, 0, 0, 0, 0, 0, 0,
1<<3, 1<<4, 1<<5, 0, 0, 0, 0, 0, 0,
};
static int opmplay_ch_parsing_order[] = {
3, 0, 4, 1, 5, 2, 6, 7, 8,
12, 9, 13, 10, 14, 11, 15, 16, 17
};
int opmplay_tick(opmplay_context_t* ctx) {
int ch = 0;
int rtn = OPMPLAY_ERR_OK;
uint32_t newdelay = 0;
// parse channel streams
for (int i = 0; i < ctx->header.channels; i++) {
int ch = opmplay_ch_parsing_order[i]; // mangle channel order to make 4op mode work
opmplay_channel_context_t* chctx = ctx->channels + ch + 1;
uint8_t* data = chctx->stream.ptr; if (data == NULL) continue;
bool isRun = true;
if (--chctx->stream.delay == 0) {
while (isRun) {
// get streams
if ((*(data) & 0x80) == OPM_SET_OPERATOR) {
int mask = *data;
int op = (mask & OPM_CMD00_SELECT_OPERATOR ? 3 : 0) + opmplay_opregs_channel_offset[ch];
data++;
if (mask & OPM_CMD00_SET_MULT) OPL3_WriteRegBuffered(ctx->chip, 0x20 + op, *data++);
if (mask & OPM_CMD00_SET_TL) OPL3_WriteRegBuffered(ctx->chip, 0x40 + op, *data++);
if (mask & OPM_CMD00_SET_AD) OPL3_WriteRegBuffered(ctx->chip, 0x60 + op, *data++);
if (mask & OPM_CMD00_SET_SR) OPL3_WriteRegBuffered(ctx->chip, 0x80 + op, *data++);
if (mask & OPM_CMD00_SET_WAVEFORM) OPL3_WriteRegBuffered(ctx->chip, 0xE0 + op, *data++);
if (mask & OPM_CMD00_END_OF_FRAME) isRun = false;
continue;
}
if ((*(data) & 0xC0) == OPM_SET_FREQ_FB_VOL) {
int mask = *data;
data++;
if (mask & OPM_CMD80_SET_TL0) OPL3_WriteRegBuffered(ctx->chip, 0x40 + opmplay_opregs_channel_offset[ch], *data++);
if (mask & OPM_CMD80_SET_TL1) OPL3_WriteRegBuffered(ctx->chip, 0x43 + opmplay_opregs_channel_offset[ch], *data++);
if (mask & OPM_CMD80_SET_FEEDBACK) OPL3_WriteRegBuffered(ctx->chip, 0xC0 + opmplay_regs_channel_offset[ch], *data++);
if (mask & OPM_CMD80_SET_FREQ) OPL3_WriteRegBuffered(ctx->chip, 0xA0 + opmplay_regs_channel_offset[ch], *data++);
if (mask & OPM_CMD80_SET_KEYBLOCK) { chctx->block = *data; OPL3_WriteRegBuffered(ctx->chip, 0xB0 + opmplay_regs_channel_offset[ch], *data++); };
if (mask & OPM_CMD80_END_OF_FRAME) isRun = false;
continue;
}
if ((*data & 0xFC) == OPM_KEY_TRIGGER) {
if (*data & OPM_KEY_ON) chctx->block |= (1 << 5); else chctx->block &= ~(1 << 5);
OPL3_WriteRegBuffered(ctx->chip, 0xB0 + opmplay_regs_channel_offset[ch], chctx->block);
if (*data & OPM_KEY_END_OF_FRAME) isRun = false;
data++;
continue;
}
if ((*data & 0xFE) == OPM_4OP_TRIGGER) {
if (opmplay_4op_bit_offset[ch] != 0) {
if (*data & OPM_4OP_ON) ctx->_4op |= opmplay_4op_bit_offset[ch]; else ctx->_4op &= ~opmplay_4op_bit_offset[ch];
OPL3_WriteRegBuffered(ctx->chip, 0x104, ctx->_4op);
}
data++;
continue;
}
if ((*data & 0xF0) == OPM_STREAM_BACKREF) {
// back reference, nested call :)
int distance = ((*(data + 0) & 0x0F) << 8) | (*(data + 1));
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) {
case OPM_STREAM_END:
// end of current stream, delay forever
chctx->stream.reload = -1;
isRun = false;
break;
// just an NOP, break
case OPM_STREAM_NEW_ORDER:
case OPM_STREAM_NOP:
data++;
break;
case OPM_STREAM_LOOP:
// save loop point
chctx->stream.loop = data;
data++;
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;
}
}
}
// process control stream
{
opmplay_channel_context_t* chctx = ctx->channels + 0;
uint8_t* data = chctx->stream.ptr;
bool isRun = true;
if (--chctx->stream.delay == 0) {
while (isRun) {
if ((*(data) & 0xE0) == OPM_CTRL_REG_SET) {
int mask = *data;
data++;
if (mask & OPM_CTRL_SET_REG01) OPL3_WriteRegBuffered(ctx->chip, 0x001, *data++);
if (mask & OPM_CTRL_SET_REG08) OPL3_WriteRegBuffered(ctx->chip, 0x008, *data++);
if (mask & OPM_CTRL_SET_REG105) OPL3_WriteRegBuffered(ctx->chip, 0x105, *data++);
if (mask & OPM_CTRL_SET_REG104) OPL3_WriteRegBuffered(ctx->chip, 0x104, *data++);
if (mask & OPM_CTRL_SET_REGBD) OPL3_WriteRegBuffered(ctx->chip, 0x0BD, *data++);
continue;
}
if ((*data & 0xF0) == OPM_STREAM_BACKREF) {
// back reference, nested call :)
int distance = ((*(data + 0) & 0x0F) << 8) | (*(data + 1));
int frames_to_play = *(data + 2);
chctx->stream.ptr = data + 3;
opmplay_push_stack(chctx);
data -= distance;
chctx->stream.samples_to_play = frames_to_play; // hack?
continue;
}
// check for common stuff
switch (*data) {
// end of stream - rewind everything
case OPM_STREAM_END:
opmplay_loop(ctx);
isRun = false;
return OPMPLAY_ERR_END_OF_STREAM;
// just an NOP, break
case OPM_STREAM_NEW_ORDER:
case OPM_STREAM_NOP:
data++;
break;
case OPM_STREAM_LOOP:
// save loop point
ctx->pos.looped = ctx->pos.frame;
chctx->stream.loop = data;
data++;
break;
// set new frame rate
case OPM_STREAM_SET_FRAME_RATE:
ctx->header.frame_rate = *(uint16_t*)(data + 1); data += 3;
break;
case OPM_STREAM_END_FRAME:
// end of frame - special case here
data++;
isRun = false;
break;
default:
// test for delay
newdelay = opmplay_set_delay(&data);
if (newdelay > 0) {
chctx->stream.reload = newdelay;
}
else
{
printf("unknonw token %02x!\n", *data);
return OPMPLAY_ERR_BAD_FILE_STRUCTURE;
}
}
}
chctx->stream.delay = chctx->stream.reload;
// decrement samples to play counter
if (--chctx->stream.samples_to_play == 0) {
// pop context from the stack
do opmplay_pop_stack(chctx); while (--chctx->stream.samples_to_play == 0);
}
else {
// save data pointer
chctx->stream.ptr = data;
}
}
}
ctx->pos.frame++;
return rtn;
}

View file

@ -146,6 +146,7 @@
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="main.cpp" />
<ClCompile Include="opmplay.cpp" />
<ClCompile Include="ymfm\src\ymfm_adpcm.cpp" />
<ClCompile Include="ymfm\src\ymfm_misc.cpp" />
<ClCompile Include="ymfm\src\ymfm_opn.cpp" />
@ -156,6 +157,7 @@
<ClInclude Include="include\portaudio.h" />
<ClInclude Include="include\vgm.h" />
<ClInclude Include="opmfile.h" />
<ClInclude Include="opmplay.h" />
<ClInclude Include="wavehead.h" />
<ClInclude Include="ymfm\src\ymfm.h" />
<ClInclude Include="ymfm\src\ymfm_opn.h" />

View file

@ -39,6 +39,9 @@
<ClCompile Include="ymfm\src\ymfm_adpcm.cpp">
<Filter>Исходные файлы\ymfm</Filter>
</ClCompile>
<ClCompile Include="opmplay.cpp">
<Filter>Исходные файлы</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="wavehead.h">
@ -59,5 +62,8 @@
<ClInclude Include="ymfm\src\ymfm.h">
<Filter>Файлы заголовков\ymfm</Filter>
</ClInclude>
<ClInclude Include="opmplay.h">
<Filter>Файлы заголовков</Filter>
</ClInclude>
</ItemGroup>
</Project>

View file

@ -46,6 +46,7 @@ struct opm_context_t {
// delay count relative to sample rate
int32_t delay_count;
int32_t delay_count_reload;
};
struct vgm_context_t {
@ -83,7 +84,7 @@ private:
uint64_t clock;
struct reg_entry_t {
uint64_t clock;
int chip, reg, data;
int reg, data;
};
std::queue<reg_entry_t> reg_queue;
ymfm::ym2203* chip;
@ -99,7 +100,6 @@ public:
void add(int chip, int reg, int data, uint64_t delay) {
reg_entry_t r;
r.clock = clock + write_delay;
r.chip = chip;
r.reg = reg;
r.data = data;
reg_queue.push(r);
@ -123,6 +123,16 @@ ymfm::ym2203 *opnachip[2];
opnx_register_queue_t opna_regqueue[2];
ymfm::ym2203::output_data opna_out[MAX_FRAMES_PER_BUFFER][2];
// hack af
uint8_t opn_reg_view[2][256];
// generic output routine
void opn_write_reg(int chip, int reg, int data) {
if (chip >= 2) return;
opn_reg_view[chip][reg] = data;
opna_regqueue[chip].add(0, reg, data, 4);
}
// ------------------
// draw plain string
@ -190,7 +200,7 @@ int console_open() {
}
// resize
console.bufsize.X = 80;
console.bufsize.X = 132;
console.bufsize.Y = 40;
SetConsoleScreenBufferSize(console.hScreenBuffer, console.bufsize);
@ -228,82 +238,6 @@ void console_done() {
SetConsoleActiveScreenBuffer(console.hStdout);
}
// ----------------------------
int vgm_parse_frame() {
auto it = vgmctx.vgmfile_it;
while (it < vgmctx.vgmfile.begin() + vgmctx.end) {
// flush delays
vgmctx.delay_count = 0;
// first, do OPL cases
switch ((VGM_Stream_Opcode)*it) {
case VGM_Stream_Opcode::YM2203_WRITE: {
// get register and data
int reg = *(it + 1), data = *(it + 2);
opna_regqueue[0].add(0, reg, data, 4);
//opnachip->write(2, reg);
//opnachip->write(3, data);
break;
}
case VGM_Stream_Opcode::YM2203_CHIP2_WRITE: {
// get register and data
int reg = *(it + 1), data = *(it + 2);
opna_regqueue[1].add(0, reg, data, 4);
//opnachip->write(0, reg);
//opnachip->write(1, data);
break;
}
default: break;
}
// then parse everything else
if (((*it >= 0x30) && (*it <= 0x3F)) || (*it == 0x4F) || (*it == 0x50)) it += 2; else
if (((*it >= 0x40) && (*it <= 0x4E)) || ((*it >= 0x51) && (*it <= 0x5F)) ||
((*it >= 0xA0) && (*it <= 0xAF)) || ((*it >= 0xB0) && (*it <= 0xBF))) it += 3; else
if (((*it >= 0xC0) && (*it <= 0xCF)) || ((*it >= 0xD0) && (*it <= 0xDF))) it += 4; else
if (((*it >= 0x70) && (*it <= 0x7F))) {
// accumulate short delays
vgmctx.delay_count += (*it) - 0x70; it++;
} else
switch ((VGM_Stream_Opcode)*it) {
case VGM_Stream_Opcode::DELAY_LONG:
vgmctx.delay_count = (uint32_t)((*(it + 1)) | (*(it + 2) << 8));
it += 3;
break;
case VGM_Stream_Opcode::DELAY_60HZ:
vgmctx.delay_count = 735U;
it += 1;
break;
case VGM_Stream_Opcode::DELAY_50HZ:
vgmctx.delay_count = 882U;
it += 1;
break;
case VGM_Stream_Opcode::END_OF_DATA:
it = vgmctx.vgmfile.begin() + vgmctx.end; // hack but should work
break;
case VGM_Stream_Opcode::DATA_BLOCK:
// skip data block
{
uint32_t datasize = (*(it + 3)) | (*(it + 4) << 8) | (*(it + 5) << 16) | (*(it + 6) << 24);
it += datasize + 7;
}
break;
default:
printf("unknown opcode %02X at offset %X!\n", *it, it - vgmctx.vgmfile.begin());
return 1;
break;
}
if (vgmctx.delay_count > 0) {
vgmctx.delay_count *= vgmctx.rescaler;
break;
}
}
// save current pointer
vgmctx.vgmfile_it = it;
return 0;
}
// -------------------
// synth render
int synth_render(int16_t* buffer, uint32_t num_samples) {
@ -312,37 +246,37 @@ int synth_render(int16_t* buffer, uint32_t num_samples) {
memset(buffer, 0, sizeof(int16_t) * 2 * num_samples);
while (samples_to_render > 0) {
if (samples_to_render < vgmctx.delay_count) {
if (samples_to_render < opmctx.delay_count) {
for (int i = 0; i < samples_to_render; i++) {
for (int chip = 0; chip < 2; chip++) {
opna_regqueue[chip].pop_clock();
opnachip[chip]->generate(opna_out[chip] + i, 1);
opna_out[chip][i].clamp16();
buffer[2*i+0] += 0.5*(0.8 * opna_out[chip][i].data[0] + 0.2 * (0.33*opna_out[chip][i].data[1] + 0.33*opna_out[chip][i].data[2] + 0.33*opna_out[chip][i].data[3])); // mix FM and SSG
buffer[2*i+1] += 0.5*(0.8 * opna_out[chip][i].data[0] + 0.2 * (0.33*opna_out[chip][i].data[1] + 0.33*opna_out[chip][i].data[2] + 0.33*opna_out[chip][i].data[3]));
buffer[2*i+0] += 0.5*(0.5 * opna_out[chip][i].data[0] + 0.5 * (1.0*opna_out[chip][i].data[1] + 0.5*opna_out[chip][i].data[2] + 0.0*opna_out[chip][i].data[3])); // mix FM and SSG
buffer[2*i+1] += 0.5*(0.5 * opna_out[chip][i].data[0] + 0.5 * (0.0*opna_out[chip][i].data[1] + 0.5*opna_out[chip][i].data[2] + 1.0*opna_out[chip][i].data[3]));
}
}
vgmctx.delay_count -= samples_to_render;
opmctx.delay_count -= samples_to_render;
buffer += 2 * samples_to_render;
break;
}
else {
// calculate new delay
for (int i = 0; i < vgmctx.delay_count; i++) {
for (int i = 0; i < opmctx.delay_count; i++) {
for (int chip = 0; chip < 2; chip++) {
opna_regqueue[chip].pop_clock();
opnachip[chip]->generate(opna_out[chip] + i, 1);
opna_out[chip][i].clamp16();
buffer[2*i+0] += 0.5*(0.8 * opna_out[chip][i].data[0] + 0.2 * (0.33*opna_out[chip][i].data[1] + 0.33*opna_out[chip][i].data[2] + 0.33*opna_out[chip][i].data[3])); // mix FM and SSG
buffer[2*i+1] += 0.5*(0.8 * opna_out[chip][i].data[0] + 0.2 * (0.33*opna_out[chip][i].data[1] + 0.33*opna_out[chip][i].data[2] + 0.33*opna_out[chip][i].data[3]));
buffer[2*i+0] += 0.5*(0.5 * opna_out[chip][i].data[0] + 0.5 * (1.0*opna_out[chip][i].data[1] + 0.5*opna_out[chip][i].data[2] + 0.0*opna_out[chip][i].data[3])); // mix FM and SSG
buffer[2*i+1] += 0.5*(0.5 * opna_out[chip][i].data[0] + 0.5 * (0.0*opna_out[chip][i].data[1] + 0.5*opna_out[chip][i].data[2] + 1.0*opna_out[chip][i].data[3]));
}
}
samples_to_render -= vgmctx.delay_count;
buffer += 2 * vgmctx.delay_count;
samples_to_render -= opmctx.delay_count;
buffer += 2 * opmctx.delay_count;
// parse VGM stream
vgm_parse_frame();
opmplay_tick(&opmctx.opm);
opmctx.delay_count = opmctx.delay_count_reload;
}
}
@ -437,13 +371,13 @@ int main(int argc, char* argv[])
return 1;
}
#if 0
#if 1
opmctx.io.type = OPMPLAY_IO_FILE;
opmctx.io.io = f;
int rtn;
if ((rtn = opmplay_init(&opmctx.opm, &opl3)) != OPMPLAY_ERR_OK) {
if ((rtn = opmplay_init(&opmctx.opm)) != OPMPLAY_ERR_OK) {
printf("unable to init OPMPlay (error = %d)\n", rtn);
return 1;
}
@ -455,7 +389,7 @@ int main(int argc, char* argv[])
printf("unable to load OPM module (error = %d)\n", rtn);
return 1;
};
opmctx.delay_count = 0;
opmctx.delay_count_reload = opmctx.delay_count = ((double)sample_rate / 50.0);
#else
// open VGM file, ready to parse
std::ifstream infile(argv[1], std::ios::in | std::ios::binary);
@ -507,6 +441,53 @@ int main(int argc, char* argv[])
memset(console.buffer, 0, sizeof(CHAR_INFO) * console.bufsize.X * console.bufsize.Y);
tprintf(0, 0, "frame = %d", ff_pos);
{
int yy = 2;
for (int ch = 0; ch < 6; ch++) {
int cc = ch / 3;
int co = ch % 3;
tprintf(0, yy, "FM%d: [%02X %02X %02X %02X %02X %02X %02X] [%02X %02X %02X %02X %02X %02X %02X] [%02X %02X %02X %02X %02X %02X %02X] [%02X %02X %02X %02X %02X %02X %02X] - %02X %02X %02X",
ch,
opn_reg_view[cc][0x30 + co],
opn_reg_view[cc][0x40 + co],
opn_reg_view[cc][0x50 + co],
opn_reg_view[cc][0x60 + co],
opn_reg_view[cc][0x70 + co],
opn_reg_view[cc][0x80 + co],
opn_reg_view[cc][0x90 + co],
opn_reg_view[cc][0x34 + co],
opn_reg_view[cc][0x44 + co],
opn_reg_view[cc][0x54 + co],
opn_reg_view[cc][0x64 + co],
opn_reg_view[cc][0x74 + co],
opn_reg_view[cc][0x84 + co],
opn_reg_view[cc][0x94 + co],
opn_reg_view[cc][0x38 + co],
opn_reg_view[cc][0x48 + co],
opn_reg_view[cc][0x58 + co],
opn_reg_view[cc][0x68 + co],
opn_reg_view[cc][0x78 + co],
opn_reg_view[cc][0x88 + co],
opn_reg_view[cc][0x98 + co],
opn_reg_view[cc][0x3C + co],
opn_reg_view[cc][0x4C + co],
opn_reg_view[cc][0x5C + co],
opn_reg_view[cc][0x6C + co],
opn_reg_view[cc][0x7C + co],
opn_reg_view[cc][0x8C + co],
opn_reg_view[cc][0x9C + co],
opn_reg_view[cc][0xA0 + co],
opn_reg_view[cc][0xA4 + co],
opn_reg_view[cc][0xB0 + co]
);
yy++;
}
}
console_update();

View file

@ -40,7 +40,7 @@ struct opm_header_t {
uint32_t clock_rate; // hz, integer
uint16_t frame_rate; // hz, 8.8 fixedpoint
uint16_t flags; // see above
uint8_t callstack_depth; // reserved, 0 at this moment
uint8_t callstack_depth;
uint8_t streams; // including control streams
uint32_t stream_mask; // used channel mask, LSB = ch 0
@ -72,18 +72,18 @@ enum {
OPM_CTRL_TIMER_CSM = 0x80, // 80..9F - set timer/CSM/LFO frequency
OPM_CTRL_RHYTHM = 0xA0, // A0..BF - rhythm control
OPM_CTRL_CMD80_REG24 = (1 << 0),
OPM_CTRL_CMD80_REG25 = (1 << 1),
OPM_CTRL_CMD80_REG25 = (1 << 0),
OPM_CTRL_CMD80_REG24 = (1 << 1),
OPM_CTRL_CMD80_REG27 = (1 << 2),
OPM_CTRL_CMD80_REG22 = (1 << 3),
OPM_CTRL_CMD80_EOF = (1 << 4),
OPM_CTRL_EXTCH3_OP1_LOW = (1 << 0),
OPM_CTRL_EXTCH3_OP1_HIGH = (1 << 1),
OPM_CTRL_EXTCH3_OP2_LOW = (1 << 2),
OPM_CTRL_EXTCH3_OP2_HIGH = (1 << 3),
OPM_CTRL_EXTCH3_OP3_LOW = (1 << 4),
OPM_CTRL_EXTCH3_OP3_HIGH = (1 << 5),
OPM_CTRL_EXTCH3_OP1_HIGH = (1 << 0),
OPM_CTRL_EXTCH3_OP1_LOW = (1 << 1),
OPM_CTRL_EXTCH3_OP2_HIGH = (1 << 2),
OPM_CTRL_EXTCH3_OP2_LOW = (1 << 3),
OPM_CTRL_EXTCH3_OP3_HIGH = (1 << 4),
OPM_CTRL_EXTCH3_OP3_LOW = (1 << 5),
OPM_CTRL_EXTCH3_EOF = (1 << 6),
OPM_CTRL_CMDA0_REG_MASK = (0x0F << 0),
@ -111,8 +111,8 @@ enum {
OPM_FM_CMD40_OP_SHIFT = OPM_FM_CMD00_OP_SHIFT,
OPM_FM_CMD40_OP_MASK = OPM_FM_CMD00_OP_MASK,
OPM_FM_CMD80_REGA0 = (1 << 0),
OPM_FM_CMD80_REGA4 = (1 << 1),
OPM_FM_CMD80_REGA4 = (1 << 0),
OPM_FM_CMD80_REGA0 = (1 << 1),
OPM_FM_CMD80_REGB0 = (1 << 2),
OPM_FM_CMD80_REGB4 = (1 << 3),
OPM_FM_CMD80_EOF = (1 << 4),
@ -122,6 +122,24 @@ enum {
OPM_FM_CMDA0_EOF = (1 << 4),
};
// OPNA rhythm channel stream
enum {
OPN_RHYTHM_KEY = 0x00, // 00..7F - key on
OPN_RHYTHM_REGS1 = 0x80, // 80..9F - write reg set 1
OPN_RHYTHM_REGS2 = 0xA0, // A0..BF - write reg set 2
OPN_RHYTHM_KEY_EOF = (1 << 6),
OPN_RHYTHM_CMD80_REG10 = (1 << 0),
OPN_RHYTHM_CMD80_REG11 = (1 << 1),
OPN_RHYTHM_CMD80_REG18 = (1 << 2),
OPN_RHYTHM_CMD80_REG19 = (1 << 3),
OPN_RHYTHM_CMDA0_REG1A = (1 << 0),
OPN_RHYTHM_CMDA0_REG1B = (1 << 1),
OPN_RHYTHM_CMDA0_REG1C = (1 << 2),
OPN_RHYTHM_CMDA0_REG1D = (1 << 3),
OPN_RHYTHM_REGS_EOF = (1 << 4),
};
// OPN SSG tone stream commands (shared with AY chip type)
enum {
OPM_AYTONE_REGS = 0x00, // 00..7F - set volume and period low

421
opmplay/lxmplay/opmplay.cpp Normal file
View file

@ -0,0 +1,421 @@
#include <stdint.h>
#include "opmplay.h"
#include "opmfile.h"
// very hacky extern lmao
void opn_write_reg(int chip, int reg, int data);
// -------------------------
// file I/O procedures
static uint32_t opmplay_mem_read(opmplay_io_t* io, void* dst, uint32_t size) {
if ((io == NULL) || dst == NULL) return OPMPLAY_ERR_NULLPTR;
if ((size + io->offset) > io->size) return OPMPLAY_ERR_IO;
opmplay_memcpy(dst, (uint8_t*)io->buf + io->offset, io->size);
io->offset += io->size;
return size;
}
static uint32_t opmplay_mem_seek(opmplay_io_t* io, uint32_t pos) {
if (io == NULL) return OPMPLAY_ERR_NULLPTR;
if (pos > io->size) return OPMPLAY_ERR_IO;
io->offset = pos;
return 0;
}
#ifdef OPMPLAY_ENABLE_STDIO
static uint32_t opmplay_file_read(opmplay_io_t* io, void* dst, uint32_t size) {
return fread(dst, 1, size, io->io);
}
static uint32_t opmplay_file_seek(opmplay_io_t* io, uint32_t pos) {
return fseek(io->io, pos, SEEK_SET);
}
#endif
// -------------------------
int opmplay_init(opmplay_context_t* ctx) {
if ((ctx == NULL)) return OPMPLAY_ERR_NULLPTR;
opmplay_memset(ctx, 0, sizeof(opmplay_context_t));
return OPMPLAY_ERR_OK;
}
int opmplay_free(opmplay_context_t* ctx)
{
for (int ch = 0; ch < ctx->header.streams; ch++) {
if (ctx->channels[ch].stream.data != NULL) {
opmplay_memfree((uint8_t*)ctx->channels[ch].stream.data); ctx->channels[ch].stream.data = NULL;
}
}
return OPMPLAY_ERR_OK;
}
// load file header
int opmplay_load_header(opmplay_context_t* ctx, opmplay_io_t* io) {
if ((ctx == NULL) || (io == NULL)) return OPMPLAY_ERR_NULLPTR;
// init file I/O handlers
switch (io->type) {
#ifdef OPMPLAY_ENABLE_STDIO
case OPMPLAY_IO_FILE:
io->read = &opmplay_file_read;
io->seek = &opmplay_file_seek;
break;
#endif
case OPMPLAY_IO_MEMORY:
io->read = &opmplay_mem_read;
io->seek = &opmplay_mem_seek;
break;
case OPMPLAY_IO_USER:
// use user-provided I/O functions
if ((io->read == NULL) || (io->seek == NULL)) return OPMPLAY_ERR_BAD_PARAMETER;
break;
default:
return OPMPLAY_ERR_BAD_PARAMETER;
}
// read header
if (io->read(io, &ctx->header, sizeof(ctx->header)) != sizeof(ctx->header)) return OPMPLAY_ERR_IO;
// and validate it
if ((opmplay_memcmp(ctx->header.magic, "OPM\x1A", sizeof(ctx->header.magic))) || (ctx->header.version.v != OPM_FORMAT_VERSION))
return OPMPLAY_ERR_BAD_FILE_STRUCTURE;
// done for now, waiting for opmplay_load_module :)
return OPMPLAY_ERR_OK;
}
int opmplay_load_module(opmplay_context_t* ctx, opmplay_io_t* io) {
if ((ctx == NULL) || (io == NULL)) return OPMPLAY_ERR_NULLPTR;
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) * (ctx->header.streams));
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) * (ctx->header.streams))
!= sizeof(opm_header_stream_desc_t) * (ctx->header.streams))
return OPMPLAY_ERR_IO;
// allocate and copy channel streams
for (int ch = 0; ch < ctx->header.streams; ch++) {
if (streamdesc[ch].size > 0) {
ctx->channels[ch].stream.data = (const 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, (uint8_t*)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
opmplay_rewind(ctx);
// done :)
return OPMPLAY_ERR_OK;
}
void opmplay_pop_stack(opmplay_channel_context_t* chctx) {
opmplay_channel_stack_t* st = chctx->stack + (--chctx->stack_pos);
chctx->stream.ptr = st->ptr;
chctx->stream.samples_to_play = st->frames_to_play;
}
void opmplay_push_stack(opmplay_channel_context_t* chctx) {
opmplay_channel_stack_t* st = chctx->stack + chctx->stack_pos;
st->ptr = chctx->stream.ptr;
st->frames_to_play = chctx->stream.samples_to_play;
chctx->stack_pos++;
}
int opmplay_loop(opmplay_context_t* ctx) {
// channel streams
for (int ch = 0; ch < ctx->header.streams; ch++) {
// init stack
ctx->channels[ch].stack_pos = 0;
ctx->channels[ch].stream.samples_to_play = -1;
ctx->channels[ch].stream.ptr = ctx->channels[ch].stream.loop;
ctx->channels[ch].stream.delay = ctx->channels[ch].stream.reload = 1;
}
return OPMPLAY_ERR_OK;
}
int opmplay_rewind(opmplay_context_t* ctx) {
// set chip mode
switch (ctx->header.flags & OPM_FLAG_CHIP_TYPE) {
case OPM_FLAG_CHIP_OPN_DUAL:
for (int chip = 0; chip < 2; chip++) {
for (int r = 0; r < 0xF0; r++) {
opn_write_reg(chip, r, 0);
}
// setup prescaler
opn_write_reg(chip, 0x2F, 0);
opn_write_reg(chip, 0x2D, 0);
}
break;
default:
return OPMPLAY_ERR_DEVICE;
}
for (int ch = 0; ch < ctx->header.streams; ch++) {
ctx->channels[ch].stream.loop = ctx->channels[ch].stream.data;
}
opmplay_loop(ctx);
return OPMPLAY_ERR_OK;
}
// get and parse delay
static uint32_t opmplay_set_delay(const uint8_t** data) {
uint32_t delay = 0;
if (**data == OPM_STREAM_DELAY_INT32) {
delay = (
(*(*data + 1) << 0) |
(*(*data + 2) << 8) |
(*(*data + 3) << 16) |
(*(*data + 4) << 24)
);
*data += 5;
}
else if (**data == OPM_STREAM_DELAY_INT16) {
delay = (
(*(*data + 1) << 0) |
(*(*data + 2) << 8)
);
*data += 3;
}
else if ((**data & 0xF0) == OPM_STREAM_DELAY_INT12) {
delay = ((**data & 0x0F) << 8) | (*(*data + 1));
*data += 2;
}
else if ((**data & 0xF0) == OPM_STERAM_DELAY_SHORT) {
delay = (**data & 0xF) + 1;
(*data)++;
}
return delay;
}
// ------------------------------------------
// stream parsing procedures
// bruuuuuuuhhhhhhhhhhhh
extern "C" {
uint8_t chip_index;
bool endOfFrame;
bool doNextOp;
}
// 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) {
// CSM/timer stuff
int mask = *data;
data++;
if (mask & OPM_CTRL_CMD80_REG25) opn_write_reg(chip_index, 0x24, *data++);
if (mask & OPM_CTRL_CMD80_REG24) opn_write_reg(chip_index, 0x24, *data++);
if (mask & OPM_CTRL_CMD80_REG27) opn_write_reg(chip_index, 0x27, *data++);
if (mask & OPM_CTRL_CMD80_REG22) opn_write_reg(chip_index, 0x22, *data++);
if (mask & OPM_CTRL_CMD80_EOF) endOfFrame = true;
doNextOp = true;
goto done;
}
if ((*(data) & 0x80) == OPM_CTRL_EXTCH3) {
// channel 3 ext mode
// 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_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;
}
// FM channel stream
const uint8_t* opmplay_parse_fm_channel_stream(opmplay_context_t* ctx, opmplay_channel_context_t* chctx, int ch, const uint8_t* data) {
if ((*(data) & 0xC0) == OPM_FM_ADSR) {
// ADSR
int mask = *data;
int regbase = (ch & 3) + ((mask & OPM_FM_CMD00_OP_MASK) >> 2);
data++;
if (mask & OPM_FM_CMD00_REG50) opn_write_reg(chip_index, 0x50 + regbase, *data++);
if (mask & OPM_FM_CMD00_REG60) opn_write_reg(chip_index, 0x60 + regbase, *data++);
if (mask & OPM_FM_CMD00_REG70) opn_write_reg(chip_index, 0x70 + regbase, *data++);
if (mask & OPM_FM_CMD00_REG80) opn_write_reg(chip_index, 0x80 + regbase, *data++);
doNextOp = true;
goto done;
}
if ((*(data) & 0xC0) == OPM_FM_MUL_TL_EG) {
// MULT/TL/SSG-EG
int mask = *data;
int regbase = (ch & 3) + ((mask & OPM_FM_CMD40_OP_MASK) >> 2);
data++;
if (mask & OPM_FM_CMD40_REG30) opn_write_reg(chip_index, 0x30 + regbase, *data++);
if (mask & OPM_FM_CMD40_REG40) opn_write_reg(chip_index, 0x40 + regbase, *data++);
if (mask & OPM_FM_CMD40_REG90) opn_write_reg(chip_index, 0x90 + regbase, *data++);
if (mask & OPM_FM_CMD40_EOF) endOfFrame = true;
doNextOp = true;
goto done;
}
if ((*(data) & 0xE0) == OPM_FM_FREQ_FB_PAN) {
// 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_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;
doNextOp = true;
goto done;
}
if ((*(data) & 0xE0) == OPM_FM_KEY) {
// frequency/feedpack
int mask = *data; data++;
opn_write_reg(chip_index, 0x28, ((mask & OPM_FM_CMDA0_OP_MASK) << 4) + ch);
if (mask & OPM_FM_CMDA0_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;
}
// parse one individual stream
const int opmplay_parse_stream(opmplay_context_t* ctx, opmplay_channel_context_t* chctx, int ch, const uint8_t* (*proc)(opmplay_context_t* ctx, opmplay_channel_context_t* chctx, int ch, const uint8_t* data)) {
uint16_t newdelay;
const uint8_t* data = chctx->stream.ptr;
endOfFrame = false;
if (--chctx->stream.delay == 0) {
do {
doNextOp = false;
data = proc(ctx, chctx, ch, data);
if (doNextOp) {
doNextOp = false;
continue;
}
// check for common stuff
switch (*data) {
// just an NOP, break
case OPM_STREAM_NEW_ORDER:
case OPM_STREAM_NOP:
data++;
break;
case OPM_STREAM_LOOP:
// save loop point
ctx->pos.frame_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++;
endOfFrame = true;
break;
default:
// test for delay
newdelay = opmplay_set_delay(&data);
if (newdelay > 0) {
chctx->stream.reload = newdelay;
}
else
{
printf("unknown token %02x!\n", *data);
return OPMPLAY_ERR_BAD_FILE_STRUCTURE;
}
}
} while (!endOfFrame);
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;
}
}
return OPMPLAY_ERR_OK;
}
int opmplay_tick(opmplay_context_t* ctx) {
int ch = 0;
int rtn = OPMPLAY_ERR_OK;
// unroll this shit =)
chip_index = 0;
opmplay_parse_stream(ctx, ctx->channels + 0, 0, opmplay_parse_fm_control_stream);
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);
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);
// advance frame counter
ctx->pos.frame++;
return rtn;
}

View file

@ -1,6 +1,5 @@
#pragma once
#include <stdint.h>
//#include "opl3.h"
#include "opmfile.h"
// OPMPlay setup defines
@ -26,7 +25,7 @@ extern "C" {
// general enums
enum {
OPMPLAY_MAX_CHANNLES = 18+1,
OPMPLAY_MAX_CHANNLES = 16,
OPMPLAY_MAX_STACK_DEPTH = 4,
};
@ -70,28 +69,25 @@ struct opmplay_io_t {
};
struct opmplay_channel_stack_t {
uint8_t* ptr;
uint32_t frames_to_play;
const uint8_t* ptr;
uint16_t frames_to_play;
};
struct opmplay_channel_context_t {
// stack
opmplay_channel_stack_t stack[OPMPLAY_MAX_STACK_DEPTH];
uint32_t stack_pos;
uint16_t stack_pos;
// stream data
struct {
uint32_t samples_to_play;
uint32_t delay;
uint32_t reload;
uint16_t samples_to_play;
uint16_t delay;
uint16_t reload;
uint8_t* data;
uint8_t* ptr;
uint8_t* loop; // if active
const uint8_t* data;
const uint8_t* ptr;
const uint8_t* loop; // if active
} stream;
// internal registers
uint8_t block; // used to track key on/off changes
};
struct opmplay_context_t {
@ -103,24 +99,15 @@ struct opmplay_context_t {
// position data
struct {
uint32_t order;
uint32_t frame;
uint16_t frame;
uint16_t frame_looped;
uint32_t samples;
uint32_t looped;
} pos;
// internal registers
uint8_t _4op; // used to track 4op mode changes
// opl chip context
//opl3_chip* chip;
};
// init context
int opmplay_init(opmplay_context_t* ctx);// , opl3_chip* chip);
int opmplay_init(opmplay_context_t* ctx);
// free context
int opmplay_free(opmplay_context_t* ctx);

View file

@ -159,6 +159,7 @@ void opm_compress(opm_convert_context_t* ctx) {
case 1: backref_len.min = backref_len.max = 4; break; // fixed
case 0:
default:
backref_len.min = backref_len.max = 4;
ctx->flags.max_stack_depth = 0;
break;
};

View file

@ -225,7 +225,9 @@ struct opm_convert_context_t {
// -----------------------------------
struct {
std::string name_prefix;
std::string filename;
std::string foldername;
opm_header_t header;
std::vector<opm_header_stream_desc_t> streamdesc;
} opmfile;

View file

@ -40,7 +40,7 @@ struct opm_header_t {
uint32_t clock_rate; // hz, integer
uint16_t frame_rate; // hz, 8.8 fixedpoint
uint16_t flags; // see above
uint8_t callstack_depth; // reserved, 0 at this moment
uint8_t callstack_depth;
uint8_t streams; // including control streams
uint32_t stream_mask; // used channel mask, LSB = ch 0
@ -72,18 +72,18 @@ enum {
OPM_CTRL_TIMER_CSM = 0x80, // 80..9F - set timer/CSM/LFO frequency
OPM_CTRL_RHYTHM = 0xA0, // A0..BF - rhythm control
OPM_CTRL_CMD80_REG24 = (1 << 0),
OPM_CTRL_CMD80_REG25 = (1 << 1),
OPM_CTRL_CMD80_REG25 = (1 << 0),
OPM_CTRL_CMD80_REG24 = (1 << 1),
OPM_CTRL_CMD80_REG27 = (1 << 2),
OPM_CTRL_CMD80_REG22 = (1 << 3),
OPM_CTRL_CMD80_EOF = (1 << 4),
OPM_CTRL_EXTCH3_OP1_LOW = (1 << 0),
OPM_CTRL_EXTCH3_OP1_HIGH = (1 << 1),
OPM_CTRL_EXTCH3_OP2_LOW = (1 << 2),
OPM_CTRL_EXTCH3_OP2_HIGH = (1 << 3),
OPM_CTRL_EXTCH3_OP3_LOW = (1 << 4),
OPM_CTRL_EXTCH3_OP3_HIGH = (1 << 5),
OPM_CTRL_EXTCH3_OP1_HIGH = (1 << 0),
OPM_CTRL_EXTCH3_OP1_LOW = (1 << 1),
OPM_CTRL_EXTCH3_OP2_HIGH = (1 << 2),
OPM_CTRL_EXTCH3_OP2_LOW = (1 << 3),
OPM_CTRL_EXTCH3_OP3_HIGH = (1 << 4),
OPM_CTRL_EXTCH3_OP3_LOW = (1 << 5),
OPM_CTRL_EXTCH3_EOF = (1 << 6),
OPM_CTRL_CMDA0_REG_MASK = (0x0F << 0),
@ -111,8 +111,8 @@ enum {
OPM_FM_CMD40_OP_SHIFT = OPM_FM_CMD00_OP_SHIFT,
OPM_FM_CMD40_OP_MASK = OPM_FM_CMD00_OP_MASK,
OPM_FM_CMD80_REGA0 = (1 << 0),
OPM_FM_CMD80_REGA4 = (1 << 1),
OPM_FM_CMD80_REGA4 = (1 << 0),
OPM_FM_CMD80_REGA0 = (1 << 1),
OPM_FM_CMD80_REGB0 = (1 << 2),
OPM_FM_CMD80_REGB4 = (1 << 3),
OPM_FM_CMD80_EOF = (1 << 4),

View file

@ -5,6 +5,7 @@
#include <fstream>
#include <algorithm>
#include <math.h>
#include <direct.h>
#include "cmdline.h"
#include "opmfile.h"
@ -162,7 +163,7 @@ 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 < ctx->max_channels + 1; i++) {
for (int i = 0; i < ctx->max_channels; i++) {
oplchans[i].frame = currentFrame;
}
}
@ -170,12 +171,10 @@ int opm_requantize(opm_convert_context_t* ctx) {
// first, do OPNx cases
int chip_idx = 0, chip_chidx = 0;
switch ((VGM_Stream_Opcode)*it) {
case VGM_Stream_Opcode::YM2608_PORT1_WRITE:
case VGM_Stream_Opcode::YM2203_CHIP2_WRITE:
if (ctx->chip_type < OPM_FLAG_CHIP_OPN_DUAL) break;
chip_idx = 1; chip_chidx = ctx->dual_chidx;
[[fallthrough]]
case VGM_Stream_Opcode::YM2608_PORT0_WRITE:
case VGM_Stream_Opcode::YM2203_WRITE: {
// get register and data
int reg = *(it + 1), data = *(it + 2);
@ -187,7 +186,7 @@ int opm_requantize(opm_convert_context_t* ctx) {
if (reg < 6) {
// tone period
channel = reg >> 1;
channel + chip_chidx + ctx->ssg_chidx;
channel += chip_chidx + ctx->ssg_chidx;
oplchans[channel].frame = currentFrame;
oplchans[channel].data.push_back(reg & 1);
oplchans[channel].data.push_back(data);
@ -195,7 +194,7 @@ int opm_requantize(opm_convert_context_t* ctx) {
else if ((reg >= 8) && (reg <= 10)) {
// volume
channel = (reg - 8);
channel = chip_chidx + ctx->ssg_chidx;
channel += chip_chidx + ctx->ssg_chidx;
oplchans[channel].frame = currentFrame;
oplchans[channel].data.push_back(8);
oplchans[channel].data.push_back(data);
@ -223,12 +222,6 @@ int opm_requantize(opm_convert_context_t* ctx) {
// rhythm registers
case 0x10:
if ((ctx->chip_type != OPM_FLAG_CHIP_OPNA) || (chip_idx == 1)) break; // only OPNA has rhythm unit
// add to rhythm stream
channel = chip_chidx + ctx->rhy_chidx;
oplchans[channel].frame = currentFrame;
oplchans[channel].data.push_back(reg);
oplchans[channel].data.push_back(data);
break;
// FM registers
case 0x20:
if ((ctx->chip_type != OPM_FLAG_CHIP_OPN_DUAL) && (chip_idx == 1)) break; // all OPN chips have only one 0x20-0x2F reg instance
@ -241,8 +234,8 @@ int opm_requantize(opm_convert_context_t* ctx) {
}
else {
// 0x28 - key on/off
int chmask = (ctx->chip_type < OPM_FLAG_CHIP_OPNA) ? 3 : 7;
channel = chip_chidx + ctx->fm_chidx + (data & 3);
int chmask = 3;
channel = chip_chidx + ctx->fm_chidx + (data & chmask);
oplchans[channel].frame = currentFrame;
oplchans[channel].data.push_back(reg);
oplchans[channel].data.push_back(data & ~chmask); // normalize to ch0
@ -267,7 +260,8 @@ int opm_requantize(opm_convert_context_t* ctx) {
if (reg >= 0xA8) {
// ext ch3 frequency, put to control stream
if ((ctx->chip_type != OPM_FLAG_CHIP_OPN_DUAL) && (chip_idx == 1)) break; // all OPN chips have only one ext.ch3!
auto& chch = oplchans[chip_chidx + ctx->ctrl_chidx];
channel = chip_chidx + ctx->ctrl_chidx;
oplchans[channel].frame = currentFrame;
oplchans[channel].data.push_back(reg);
oplchans[channel].data.push_back(data);
break;
@ -283,9 +277,10 @@ int opm_requantize(opm_convert_context_t* ctx) {
break;
default:
global_reg:
oplchans[0].frame = currentFrame;
oplchans[0].data.push_back(reg);
oplchans[0].data.push_back(data);
channel = chip_chidx + ctx->fm_chidx;
oplchans[channel].frame = currentFrame;
oplchans[channel].data.push_back(reg);
oplchans[channel].data.push_back(data);
break;
}
}
@ -386,7 +381,7 @@ int opm_write_file(opm_convert_context_t* ctx) {
ctx->opmfile.header.callstack_depth = ctx->flags.max_stack_depth;
ctx->opmfile.header.frame_rate = ((double)(44100 / ctx->delay) * 256.0);
ctx->opmfile.header.streams = ctx->max_channels;
ctx->opmfile.streamdesc.resize(ctx->max_channels + 1);
ctx->opmfile.streamdesc.resize(ctx->max_channels);
for (int ch = 0; ch < ctx->max_channels; ch++) {
if (ctx->ch[ch].used) ctx->opmfile.header.stream_mask |= (1 << ch);
}
@ -407,6 +402,42 @@ int opm_write_file(opm_convert_context_t* ctx) {
}
fclose(f);
// now save ALL streams
_mkdir(ctx->opmfile.foldername.c_str());
{
auto fname = ctx->opmfile.foldername + "/" + ctx->opmfile.name_prefix + "_header.bin";
FILE* hf = fopen(fname.c_str(), "wb");
if (hf) {
fwrite(&ctx->opmfile.header, sizeof(opm_header_t), 1, hf);
fclose(hf);
}
for (int ch = 0; ch < ctx->max_channels; ch++) {
auto& chctx = ctx->ch[ch];
auto fname = ctx->opmfile.foldername + "/" + ctx->opmfile.name_prefix + "_ch" + std::to_string(ch) + ".bin";
FILE* sf = fopen(fname.c_str(), "wb");
if (sf) {
fwrite(chctx.rawstream.data(), sizeof(decltype(chctx.rawstream)::value_type), chctx.rawstream.size(), sf);
fclose(sf);
}
}
}
// export config file
{
auto fname = ctx->opmfile.foldername + "/" + "music.inc";
FILE* cf = fopen(fname.c_str(), "w");
fprintf(cf, " ; autogenerated, edit with care!\n");
fprintf(cf, " ifndef MUSIC_INC\n");
fprintf(cf, " define MUSIC_INC\n\n");
fprintf(cf, "TICK_RATE equ %d\n", (int)(44100.0 / ctx->delay));
fprintf(cf, "TOTAL_FRAMES equ %d\n", ctx->total_frames);
fprintf(cf, "TOTAL_CHANNELS equ %d\n", ctx->max_channels);
fprintf(cf, "FILEPATH equ \"%s\"\n", (ctx->opmfile.name_prefix + '/' + ctx->opmfile.name_prefix).c_str());
fprintf(cf, " endif\n");
fclose(cf);
}
return 0;
}
@ -649,7 +680,7 @@ int opm_group_registers(opm_convert_context_t* ctx) {
// ah fuck do it for each chip type separately
if (ctx->chip_type <= OPM_FLAG_CHIP_OPN_DUAL) {
int dch = 0;
for (int chip = 0; chip < ctx->chip_type == OPM_FLAG_CHIP_OPN_DUAL ? 2 : 1; chip++) {
for (int chip = 0; chip < (ctx->chip_type == OPM_FLAG_CHIP_OPN_DUAL ? 2 : 1); chip++) {
opm_group_control_stream(ctx, dch + ctx->ch.data() + ctx->ctrl_chidx, 0);
for (int fmch = 0; fmch < 3; fmch++) {
opm_group_fm_stream(ctx, dch + ctx->ch.data() + ctx->fm_chidx + fmch, fmch);
@ -741,13 +772,13 @@ int opm_serialize_control_stream(opm_channel_t* chctx) {
s.rawdata.push_back(
OPM_CTRL_TIMER_CSM |
((cmdmask == 0) && (i == s.records.size() - 1) ? OPM_CTRL_CMD80_EOF : 0) |
(ef & OPM_REC_REG24 ? OPM_CTRL_CMD80_REG24 : 0) |
(ef & OPM_REC_REG25 ? OPM_CTRL_CMD80_REG25 : 0) |
(ef & OPM_REC_REG24 ? OPM_CTRL_CMD80_REG24 : 0) |
(ef & OPM_REC_REG27 ? OPM_CTRL_CMD80_REG27 : 0) |
(ef & OPM_REC_REG22 ? OPM_CTRL_CMD80_REG22 : 0)
);
if (ef & OPM_REC_REG24) s.rawdata.push_back(e.reg24);
if (ef & OPM_REC_REG25) s.rawdata.push_back(e.reg25);
if (ef & OPM_REC_REG24) s.rawdata.push_back(e.reg24);
if (ef & OPM_REC_REG27) s.rawdata.push_back(e.reg27);
if (ef & OPM_REC_REG22) s.rawdata.push_back(e.reg22);
}
@ -764,12 +795,12 @@ int opm_serialize_control_stream(opm_channel_t* chctx) {
(ef & OPM_REC_EXTCH3_OP3_LOW ? OPM_CTRL_EXTCH3_OP3_LOW : 0) |
(ef & OPM_REC_EXTCH3_OP3_HIGH ? OPM_CTRL_EXTCH3_OP3_HIGH : 0)
);
if (ef & OPM_REC_EXTCH3_OP1_LOW) s.rawdata.push_back(e.extch3.freq[0][0]);
if (ef & OPM_REC_EXTCH3_OP1_HIGH) s.rawdata.push_back(e.extch3.freq[1][0]);
if (ef & OPM_REC_EXTCH3_OP2_LOW) s.rawdata.push_back(e.extch3.freq[0][1]);
if (ef & OPM_REC_EXTCH3_OP1_LOW) s.rawdata.push_back(e.extch3.freq[0][0]);
if (ef & OPM_REC_EXTCH3_OP2_HIGH) s.rawdata.push_back(e.extch3.freq[1][1]);
if (ef & OPM_REC_EXTCH3_OP3_LOW) s.rawdata.push_back(e.extch3.freq[0][2]);
if (ef & OPM_REC_EXTCH3_OP2_LOW) s.rawdata.push_back(e.extch3.freq[0][1]);
if (ef & OPM_REC_EXTCH3_OP3_HIGH) s.rawdata.push_back(e.extch3.freq[1][2]);
if (ef & OPM_REC_EXTCH3_OP3_LOW) s.rawdata.push_back(e.extch3.freq[0][2]);
}
}
}
@ -956,13 +987,13 @@ int opm_serialize_fm_stream(opm_channel_t* chctx) {
s.rawdata.push_back(
OPM_FM_FREQ_FB_PAN |
((cmdmask == 0) && (i == s.records.size() - 1) ? OPM_FM_CMD80_EOF : 0) |
(ef & OPM_REC_REGA0 ? OPM_FM_CMD80_REGA0 : 0) |
(ef & OPM_REC_REGA4 ? OPM_FM_CMD80_REGA4 : 0) |
(ef & OPM_REC_REGA0 ? OPM_FM_CMD80_REGA0 : 0) |
(ef & OPM_REC_REGB0 ? OPM_FM_CMD80_REGB0 : 0) |
(ef & OPM_REC_REGB4 ? OPM_FM_CMD80_REGB4 : 0)
);
if (ef & OPM_REC_REGA0) s.rawdata.push_back(e.fnum);
if (ef & OPM_REC_REGA4) s.rawdata.push_back(e.block);
if (ef & OPM_REC_REGA0) s.rawdata.push_back(e.fnum);
if (ef & OPM_REC_REGB0) s.rawdata.push_back(e.fb);
if (ef & OPM_REC_REGB4) s.rawdata.push_back(e.pan);
}
@ -1127,7 +1158,7 @@ int opm_serialize_stream(opm_convert_context_t* ctx) {
// ah fuck do it for each chip type separately
if (ctx->chip_type <= OPM_FLAG_CHIP_OPN_DUAL) {
int dch = 0;
for (int chip = 0; chip < ctx->chip_type == OPM_FLAG_CHIP_OPN_DUAL ? 2 : 1; chip++) {
for (int chip = 0; chip < (ctx->chip_type == OPM_FLAG_CHIP_OPN_DUAL ? 2 : 1); chip++) {
opm_serialize_control_stream(dch + ctx->ch.data() + ctx->ctrl_chidx);
for (int fmch = 0; fmch < 3; fmch++) {
opm_serialize_fm_stream(dch + ctx->ch.data() + ctx->fm_chidx + fmch);
@ -1271,10 +1302,14 @@ int main(int argc, char* argv[]) {
// file names
std::string infile_str = argv[1];
std::string infile_str_raw = infile_str.substr(0, infile_str.find_last_of('.'));
std::string outfile_str = infile_str.substr(0, infile_str.find(".vgm")) + ".opm";
std::string csvfile_str = infile_str.substr(0, infile_str.find(".vgm")) + ".csv";
std::string logfile_str = infile_str.substr(0, infile_str.find(".vgm")) + ".log";
ctx->opmfile.filename = outfile_str;
ctx->opmfile.foldername = infile_str_raw;
ctx->opmfile.name_prefix = infile_str_raw.substr(infile_str_raw.find_last_of("/\\") + 1);
ctx->logname = logfile_str;
// good old stdio :)
@ -1328,21 +1363,13 @@ int main(int argc, char* argv[]) {
else {
ctx->chip_type = OPM_FLAG_CHIP_OPN;
ctx->max_channels = 1+3+4;
ctx->ctrl_chidx = 0;
ctx->ssg_chidx = 3;
ctx->fm_chidx = 1;
ctx->rhy_chidx = 0; // not available!
ctx->dual_chidx = 0;
}
}
else if (ctx->vgm.header->YM2608_Clock != 0) {
ctx->chip_type = OPM_FLAG_CHIP_OPNA;
oplClockRate = ctx->vgm.header->YM2608_Clock & ((1 << 30) - 1);
ctx->max_channels = 1+6+4+1;
ctx->ctrl_chidx = 0;
ctx->fm_chidx = 1;
ctx->ssg_chidx = ctx->fm_chidx+6;
ctx->rhy_chidx = ctx->ssg_chidx + 4;
ctx->dual_chidx = ctx->fm_chidx+3; // for channels 4-6
}
else {
printf("OPN data not found!\n");
return 1;

View file

@ -318,8 +318,8 @@ int synth_render(int16_t* buffer, uint32_t num_samples) {
opna_regqueue[chip].pop_clock();
opnachip[chip]->generate(opna_out[chip] + i, 1);
opna_out[chip][i].clamp16();
buffer[2*i+0] += 0.5*(0.8 * opna_out[chip][i].data[0] + 0.2 * (0.33*opna_out[chip][i].data[1] + 0.33*opna_out[chip][i].data[2] + 0.33*opna_out[chip][i].data[3])); // mix FM and SSG
buffer[2*i+1] += 0.5*(0.8 * opna_out[chip][i].data[0] + 0.2 * (0.33*opna_out[chip][i].data[1] + 0.33*opna_out[chip][i].data[2] + 0.33*opna_out[chip][i].data[3]));
buffer[2*i+0] += 0.5*(0.5 * opna_out[chip][i].data[0] + 0.5 * (1.0*opna_out[chip][i].data[1] + 0.5*opna_out[chip][i].data[2] + 0.0*opna_out[chip][i].data[3])); // mix FM and SSG
buffer[2*i+1] += 0.5*(0.5 * opna_out[chip][i].data[0] + 0.5 * (0.0*opna_out[chip][i].data[1] + 0.5*opna_out[chip][i].data[2] + 1.0*opna_out[chip][i].data[3]));
}
}
@ -334,8 +334,8 @@ int synth_render(int16_t* buffer, uint32_t num_samples) {
opna_regqueue[chip].pop_clock();
opnachip[chip]->generate(opna_out[chip] + i, 1);
opna_out[chip][i].clamp16();
buffer[2*i+0] += 0.5*(0.8 * opna_out[chip][i].data[0] + 0.2 * (0.33*opna_out[chip][i].data[1] + 0.33*opna_out[chip][i].data[2] + 0.33*opna_out[chip][i].data[3])); // mix FM and SSG
buffer[2*i+1] += 0.5*(0.8 * opna_out[chip][i].data[0] + 0.2 * (0.33*opna_out[chip][i].data[1] + 0.33*opna_out[chip][i].data[2] + 0.33*opna_out[chip][i].data[3]));
buffer[2*i+0] += 0.5*(0.5 * opna_out[chip][i].data[0] + 0.5 * (1.0*opna_out[chip][i].data[1] + 0.5*opna_out[chip][i].data[2] + 0.0*opna_out[chip][i].data[3])); // mix FM and SSG
buffer[2*i+1] += 0.5*(0.5 * opna_out[chip][i].data[0] + 0.5 * (0.0*opna_out[chip][i].data[1] + 0.5*opna_out[chip][i].data[2] + 1.0*opna_out[chip][i].data[3]));
}
}
samples_to_render -= vgmctx.delay_count;