330 lines
9 KiB
C++
330 lines
9 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 "wavehead.h"
|
|
#include "opmplay.h"
|
|
#include "opl3.h"
|
|
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#define WIN32_EXTRA_LEAN
|
|
#define NOMINMAX
|
|
#include <Windows.h>
|
|
|
|
const uint32_t SAMPLE_RATE = 44100;
|
|
enum {
|
|
CHANNELS = 2,
|
|
FRAMES_PER_BUFFER = 512,
|
|
};
|
|
|
|
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;
|
|
};
|
|
|
|
opm_context_t opmctx;
|
|
opl3_chip opl3;
|
|
|
|
// ------------------
|
|
|
|
// 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 = 80;
|
|
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);
|
|
}
|
|
|
|
// -------------------
|
|
// opl3 synth render
|
|
int synth_render(int16_t* buffer, uint32_t num_samples) {
|
|
int samples_to_render = num_samples;
|
|
|
|
while (samples_to_render > 0) {
|
|
if (samples_to_render < opmctx.delay_count) {
|
|
OPL3_GenerateStream(&opl3, buffer, samples_to_render);
|
|
opmctx.delay_count -= samples_to_render;
|
|
break;
|
|
}
|
|
else {
|
|
// calculate new delay
|
|
OPL3_GenerateStream(&opl3, buffer, opmctx.delay_count);
|
|
buffer += CHANNELS * opmctx.delay_count;
|
|
samples_to_render -= opmctx.delay_count;
|
|
|
|
// parse VGM stream
|
|
opmplay_tick(&opmctx.opm);
|
|
opmctx.delay_count = (44100.0 / ((double)0x1234DD / opmctx.opm.header.frame_rate));
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int pa_init() {
|
|
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);
|
|
|
|
OPL3_Reset(&opl3, SAMPLE_RATE);
|
|
|
|
if (!render_to_wave) pa_init();
|
|
console_open();
|
|
|
|
FILE* f = fopen(argv[1], "rb");
|
|
if (f == NULL) {
|
|
printf("error: unable to open file!\n");
|
|
return 1;
|
|
}
|
|
|
|
opmctx.io.type = OPMPLAY_IO_FILE;
|
|
opmctx.io.io = f;
|
|
|
|
int rtn;
|
|
|
|
if ((rtn = opmplay_init(&opmctx.opm, &opl3)) != 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 = 0;
|
|
|
|
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);
|
|
}
|
|
|
|
// update console
|
|
memset(console.buffer, 0, sizeof(CHAR_INFO) * console.bufsize.X * console.bufsize.Y);
|
|
|
|
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;
|
|
}
|