bare minimum 2xOPN (w/o SSG) done
This commit is contained in:
parent
ae9302f049
commit
db1f29e227
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -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" />
|
|
@ -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>
|
|
@ -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();
|
||||
|
|
@ -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
421
opmplay/lxmplay/opmplay.cpp
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
@ -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);
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue