moe-bius/opmplay/lxmplay/main.cpp

574 lines
18 KiB
C++

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <conio.h>
#include <stdint.h>
#include <portaudio.h>
#include <math.h>
#include <vector>
#include <fstream>
#include <queue>
#include "wavehead.h"
#include "opmplay.h"
#include "vgm.h"
#include "rss.h" // YM2608 internal ADPCM-A ROM dump
#include "ymfm/src/ymfm_opn.h"
#define WIN32_LEAN_AND_MEAN
#define WIN32_EXTRA_LEAN
#define NOMINMAX
#include <Windows.h>
enum {
CHANNELS = 2,
FRAMES_PER_BUFFER = 512,
MAX_FRAMES_PER_BUFFER = 4096,
};
//const double OPN_CLOCK_RATE = 3500000;
const double OPN_CLOCK_RATE = 3540000;
PaStreamParameters outputParameters;
PaStream* stream;
// console stuff
struct {
HANDLE hStdout, hScreenBuffer;
COORD bufcoord, bufsize;
SMALL_RECT bufDestRect;
CHAR_INFO* buffer; // the main buffer to write to
} console;
struct opm_context_t {
// full context
opmplay_context_t opm;
opmplay_io_t io;
// delay count relative to sample rate
int32_t delay_count;
int32_t delay_count_reload;
};
struct vgm_context_t {
std::vector<uint8_t> vgmfile;
std::vector<uint8_t>::iterator vgmfile_it;
VGMHeader* header;
uint32_t loop_pos;
uint32_t start, end; // offsets
// delay count
int32_t delay_count;
// rescaler for 44100hz delays
double rescaler;
};
vgm_context_t vgmctx;
opm_context_t opmctx;
class opna_interface_t : public ymfm::ymfm_interface {
protected:
int32_t timer_a_count, timer_a_reload;
public:
opna_interface_t() : timer_a_count(0), timer_a_reload(0) {};
uint8_t ymfm_external_read(ymfm::access_class type, uint32_t address) {
switch (type) {
case ymfm::ACCESS_ADPCM_A: return YM2608_ADPCM_ROM[address & 0x1FFF];
default: return 0;
}
}
void ymfm_set_timer(uint32_t tnum, int32_t duration_in_clocks) {
if (tnum == 0) {
timer_a_reload = duration_in_clocks;
}
}
void timer_advance(int32_t ticks) {
// ripped from furnace :grins:
if (timer_a_reload >= 0) {
timer_a_count -= ticks;
if (timer_a_count < 0) {
m_engine->engine_timer_expired(0);
timer_a_count += timer_a_reload;
}
}
}
};
// register write queue
class opnx_register_queue_t {
private:
uint64_t write_delay;
uint64_t clock;
struct reg_entry_t {
uint64_t clock;
int reg, data;
};
std::queue<reg_entry_t> reg_queue;
ymfm::ym2203* chip;
public:
opnx_register_queue_t() : opnx_register_queue_t(nullptr) {}
opnx_register_queue_t(ymfm::ym2203* _chip) : clock(0), write_delay(0), chip(_chip) {}
void set_chip(ymfm::ym2203* _chip) { chip = _chip; }
void reset() {
clock = 0;
write_delay = 0;
while (!reg_queue.empty()) reg_queue.pop(); // flush queue
}
void add(int chip, int reg, int data, uint64_t delay) {
reg_entry_t r;
r.clock = clock + write_delay;
r.reg = reg;
r.data = data;
reg_queue.push(r);
write_delay += delay;
}
void pop_clock() {
if (!reg_queue.empty() && (reg_queue.front().clock <= clock)) {
auto &r = reg_queue.front();
if (chip) {
chip->write_address(r.reg);
chip->write_data(r.data);
}
reg_queue.pop();
}
clock++; write_delay = 0;
}
};
opna_interface_t opna_interface[2];
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
void drawstring(const char* str, unsigned long x, unsigned long y, unsigned char attr) {
CHAR_INFO* p = (CHAR_INFO*)console.buffer + (console.bufsize.X * y) + x;
while (*str != '\0') {
p->Char.AsciiChar = *str++;
p->Attributes = attr;
p++;
}
}
// draw string with attributes
// '\0' - end, '\xFF\xaa' - set attribute byte 'aa'
void drawastring(const char* str, unsigned long x, unsigned long y) {
CHAR_INFO* p = (CHAR_INFO*)console.buffer + (console.bufsize.X * y) + x;
unsigned short attr = 0x07;
while (*str != '\0') if (*str == '\xFF') {
attr = (*++str); str++;
}
else {
p->Char.AsciiChar = *str++;
p->Attributes = attr;
p++;
}
}
// printf
int tprintf(uint32_t x, uint32_t y, const char* format, ...) {
char buffer[1024]; // large enough
va_list arglist;
va_start(arglist, format);
int rtn = vsnprintf(buffer, sizeof(buffer), format, arglist);
drawastring(buffer, x, y);
va_end(arglist);
return rtn;
};
// -------------------
int console_open() {
// Get a handle to the STDOUT screen buffer to copy from and
// create a new screen buffer to copy to.
console.hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
console.hScreenBuffer = CreateConsoleScreenBuffer(
GENERIC_READ | // read/write access
GENERIC_WRITE,
FILE_SHARE_READ |
FILE_SHARE_WRITE, // shared
NULL, // default security attributes
CONSOLE_TEXTMODE_BUFFER, // must be TEXTMODE
NULL); // reserved; must be NULL
if (console.hStdout == INVALID_HANDLE_VALUE ||
console.hScreenBuffer == INVALID_HANDLE_VALUE)
{
printf("CreateConsoleScreenBuffer failed - (%d)\n", GetLastError());
return 1;
}
// resize
console.bufsize.X = 132;
console.bufsize.Y = 40;
SetConsoleScreenBufferSize(console.hScreenBuffer, console.bufsize);
// allocate console buffer
console.buffer = new CHAR_INFO[console.bufsize.X * console.bufsize.Y];
memset(console.buffer, 0, sizeof(CHAR_INFO) * console.bufsize.X * console.bufsize.Y);
// Make the new screen buffer the active screen buffer.
if (!SetConsoleActiveScreenBuffer(console.hScreenBuffer))
{
printf("SetConsoleActiveScreenBuffer failed - (%d)\n", GetLastError());
return 1;
}
return 0;
}
void console_update() {
console.bufDestRect.Top = 0;
console.bufDestRect.Left = 0;
console.bufDestRect.Bottom = console.bufsize.Y - 1;
console.bufDestRect.Right = console.bufsize.X - 1;
console.bufcoord.X = console.bufcoord.Y = 0;
WriteConsoleOutput(
console.hScreenBuffer, // screen buffer to write to
console.buffer, // buffer to copy from
console.bufsize, // col-row size of chiBuffer
console.bufcoord, // top left src cell in chiBuffer
&console.bufDestRect); // dest. screen buffer rectangle
}
void console_done() {
SetConsoleActiveScreenBuffer(console.hStdout);
}
// -------------------
// synth render
int synth_render(int16_t* buffer, uint32_t num_samples) {
int samples_to_render = num_samples;
memset(buffer, 0, sizeof(int16_t) * 2 * num_samples);
while (samples_to_render > 0) {
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_interface[chip].timer_advance(24);
opna_out[chip][i].clamp16();
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]));
}
}
opmctx.delay_count -= samples_to_render;
buffer += 2 * samples_to_render;
break;
}
else {
// calculate new delay
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_interface[chip].timer_advance(24);
opna_out[chip][i].clamp16();
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 -= opmctx.delay_count;
buffer += 2 * opmctx.delay_count;
opmplay_tick(&opmctx.opm);
opmctx.delay_count = opmctx.delay_count_reload;
}
}
return 0;
}
int pa_init(double sample_rate) {
PaError err;
// init portaudio
err = Pa_Initialize();
if (err != paNoError) return 1;
outputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */
if (outputParameters.device == paNoDevice) {
fprintf(stderr, "Error: No default output device.\n");
return 1;
}
outputParameters.channelCount = CHANNELS;
outputParameters.sampleFormat = paInt16;
outputParameters.suggestedLatency = 0.04;
outputParameters.hostApiSpecificStreamInfo = NULL;
err = Pa_OpenStream(
&stream,
NULL, /* no input */
&outputParameters,
sample_rate,
FRAMES_PER_BUFFER,
0, /* we won't output out of range samples so don't bother clipping them */
NULL, /* no callback, use blocking API */
NULL); /* no callback, so no callback userData */
if (err != paNoError) return 1;
err = Pa_StartStream(stream);
if (err != paNoError) return 1;
return 0;
}
int pa_write(void* data, int32_t count) {
PaError err;
err = Pa_WriteStream(stream, data, count);
return 0;
}
int pa_done() {
PaError err;
err = Pa_StopStream(stream);
if (err != paNoError) return 1;
// deinit portaudio
err = Pa_CloseStream(stream);
if (err != paNoError) return 1;
Pa_Terminate();
return 0;
}
int main(int argc, char* argv[])
{
bool render_to_wave = (argc >= 3);
uint32_t sample_rate;
for (int chip = 0; chip < 2; chip++) {
opnachip[chip] = new ymfm::ym2203(opna_interface[chip]);
if (opnachip[chip] == nullptr) {
printf("error: unable to init ymfm!\n");
return 1;
}
opnachip[chip]->reset();
opnachip[chip]->set_fidelity(ymfm::OPN_FIDELITY_MIN);
sample_rate = opnachip[chip]->sample_rate(OPN_CLOCK_RATE);
vgmctx.rescaler = ((double)sample_rate / 44100.0);
printf("sample rate - %d hz\n", sample_rate);
opna_regqueue[chip].reset();
opna_regqueue[chip].set_chip(opnachip[chip]);
}
if (!render_to_wave) {
if (pa_init(sample_rate) != 0) {
printf("error: unable to init PortAudio!\n");
return 1;
}
}
FILE* f = fopen(argv[1], "rb");
if (f == NULL) {
printf("error: unable to open file!\n");
return 1;
}
#if 1
opmctx.io.type = OPMPLAY_IO_FILE;
opmctx.io.io = f;
int rtn;
if ((rtn = opmplay_init(&opmctx.opm)) != OPMPLAY_ERR_OK) {
printf("unable to init OPMPlay (error = %d)\n", rtn);
return 1;
}
if ((rtn = opmplay_load_header(&opmctx.opm, &opmctx.io)) != OPMPLAY_ERR_OK) {
printf("unable to load OPM header (error = %d)\n", rtn);
return 1;
};
if ((rtn = opmplay_load_module(&opmctx.opm, &opmctx.io)) != OPMPLAY_ERR_OK) {
printf("unable to load OPM module (error = %d)\n", rtn);
return 1;
};
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);
infile.unsetf(std::ios::skipws);
// get filesize
infile.seekg(0, std::ios::end);
uint64_t fsize = infile.tellg();
infile.seekg(0, std::ios::beg);
// read whole file
vgmctx.vgmfile.reserve(fsize);
vgmctx.vgmfile.insert(vgmctx.vgmfile.begin(), std::istream_iterator<uint8_t>(infile), std::istream_iterator<uint8_t>());
// get header
vgmctx.header = reinterpret_cast<VGMHeader*>(vgmctx.vgmfile.data());
// check header
if (memcmp(vgmctx.header->id, "Vgm\x20", sizeof(vgmctx.header->id)) != 0) {
printf("not a vaild VGM file!\n");
return 1;
}
// parse basic VGM structure
printf("VGM %d.%d file found\n", (vgmctx.header->version >> 8) & 0xFF, vgmctx.header->version & 0xFF);
if (vgmctx.header->loopOffset != 0) vgmctx.loop_pos = vgmctx.header->loopOffset + offsetof(VGMHeader, loopOffset);
vgmctx.end = vgmctx.header->eofOffset + offsetof(VGMHeader, eofOffset);
vgmctx.start = ((vgmctx.header->version < 0x150) ? 0x40 : vgmctx.header->dataOffset + offsetof(VGMHeader, dataOffset));
vgmctx.vgmfile_it = vgmctx.vgmfile.begin() + vgmctx.start;
vgmctx.delay_count = 0;
#endif
console_open();
std::vector<int16_t> wavedata;
int ff_pos = 0, ff_counter = 0;
int16_t buf[FRAMES_PER_BUFFER * CHANNELS] = { 0 };
while (1) {
int rtn = synth_render(buf, FRAMES_PER_BUFFER);
if (render_to_wave) {
wavedata.insert(wavedata.end(), buf, buf + FRAMES_PER_BUFFER * CHANNELS);
}
else {
pa_write(buf, FRAMES_PER_BUFFER);
}
ff_pos += FRAMES_PER_BUFFER;
// update console
memset(console.buffer, 0, sizeof(CHAR_INFO) * console.bufsize.X * console.bufsize.Y);
tprintf(0, 0, "frame = %d", opmctx.opm.pos.frame);
{
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++;
if ((ch % 3) == 2) {
tprintf(0, yy, "EC%d: [%02X %02X %02X %02X %02X %02X %02X %02X]",
cc,
opn_reg_view[cc][0xAD],
opn_reg_view[cc][0xA9],
opn_reg_view[cc][0xAC],
opn_reg_view[cc][0xA8],
opn_reg_view[cc][0xAE],
opn_reg_view[cc][0xAA],
opn_reg_view[cc][0xA6],
opn_reg_view[cc][0xA2]
);
yy++;
}
}
}
console_update();
if (_kbhit()) {
_getch();
break;
}
}
// write wave file
if (render_to_wave) {
// create headers
RIFF_Header riffHeader;
memcpy(&riffHeader.id, "RIFF", sizeof(riffHeader.id));
memcpy(&riffHeader.fourcc, "WAVE", sizeof(riffHeader.fourcc));
riffHeader.size = sizeof(riffHeader.fourcc) + sizeof(fmt_Header) + sizeof(chunk_Header) + (wavedata.size() * sizeof(decltype(wavedata)::value_type));
fmt_Header fmtHeader;
memcpy(&fmtHeader.id, "fmt ", sizeof(fmtHeader.id));
fmtHeader.size = sizeof(fmtHeader) - 8;
fmtHeader.wFormatTag = 1; // plain uncompressed PCM
fmtHeader.nSamplesPerSec = sample_rate;
fmtHeader.nBlockAlign = CHANNELS;
fmtHeader.nAvgBytesPerSec = sample_rate * CHANNELS;
fmtHeader.nChannels = CHANNELS;
fmtHeader.wBitsPerSample = 8;
chunk_Header dataHeader;
memcpy(&dataHeader.id, "data", sizeof(dataHeader.id));
dataHeader.size = (wavedata.size() * sizeof(decltype(wavedata)::value_type));
// write wave file
FILE* outfile = fopen("out.wav", "wb");
fwrite(&riffHeader, sizeof(riffHeader), 1, outfile);
fwrite(&fmtHeader, sizeof(fmtHeader), 1, outfile);
fwrite(&dataHeader, sizeof(dataHeader), 1, outfile);
fwrite(wavedata.data(), (wavedata.size() * sizeof(decltype(wavedata)::value_type)), 1, outfile);
fclose(outfile);
} else
pa_done();
console_done();
return 0;
}