#define _CRT_SECURE_NO_WARNINGS #include #include #include #include #include #include #include #include #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 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 vgmfile; std::vector::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_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(infile), std::istream_iterator()); // get header vgmctx.header = reinterpret_cast(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 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, "AY%d: [%02X %02X] [%02X %02X] [%02X %02X] %02X %02X [%02X %02X %02X] [%02X %02X] <- %02X", cc, opn_reg_view[cc][0], opn_reg_view[cc][1], opn_reg_view[cc][2], opn_reg_view[cc][3], opn_reg_view[cc][4], opn_reg_view[cc][5], opn_reg_view[cc][6], opn_reg_view[cc][7], opn_reg_view[cc][8], opn_reg_view[cc][9], opn_reg_view[cc][10], opn_reg_view[cc][11], opn_reg_view[cc][12], opn_reg_view[cc][13] ); yy++; 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; }