diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index af22075..3440f12 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -142,9 +142,11 @@ bool JuceOplvstiAudioProcessor::isRecording() { void JuceOplvstiAudioProcessor::startRecording(File *outputFile) { recordingFile = outputFile; + Opl->StartCapture(outputFile->getFullPathName().toUTF8()); } void JuceOplvstiAudioProcessor::stopRecording() { + Opl->StopCapture(); recordingFile = NULL; } diff --git a/Source/drov1.cpp b/Source/drov1.cpp new file mode 100644 index 0000000..3ba1aaa --- /dev/null +++ b/Source/drov1.cpp @@ -0,0 +1,213 @@ +#include "JuceHeader.h" + +#include +#include + +#include "drov1.h" + +// http://sourceforge.net/p/dosbox/code-0/HEAD/tree/dosbox/tags/RELEASE_0_72/src/hardware/adlib.cpp#l196 + +// from mem.h +typedef Bit8u * HostPt; +#if defined(WORDS_BIGENDIAN) || !defined(C_UNALIGNED_MEMORY) + +INLINE Bit8u host_readb(HostPt off) { + return off[0]; +}; +INLINE Bit16u host_readw(HostPt off) { + return off[0] | (off[1] << 8); +}; +INLINE Bit32u host_readd(HostPt off) { + return off[0] | (off[1] << 8) | (off[2] << 16) | (off[3] << 24); +}; +INLINE void host_writeb(HostPt off,Bit8u val) { + off[0]=val; +}; +INLINE void host_writew(HostPt off,Bit16u val) { + off[0]=(Bit8u)(val); + off[1]=(Bit8u)(val >> 8); +}; +INLINE void host_writed(HostPt off,Bit32u val) { + off[0]=(Bit8u)(val); + off[1]=(Bit8u)(val >> 8); + off[2]=(Bit8u)(val >> 16); + off[3]=(Bit8u)(val >> 24); +}; + +#else + +INLINE Bit8u host_readb(HostPt off) { + return *(Bit8u *)off; +}; +INLINE Bit16u host_readw(HostPt off) { + return *(Bit16u *)off; +}; +INLINE Bit32u host_readd(HostPt off) { + return *(Bit32u *)off; +}; +INLINE void host_writeb(HostPt off,Bit8u val) { + *(Bit8u *)(off)=val; +}; +INLINE void host_writew(HostPt off,Bit16u val) { + *(Bit16u *)(off)=val; +}; +INLINE void host_writed(HostPt off,Bit32u val) { + *(Bit32u *)(off)=val; +}; + +#endif + +#define RAW_SIZE 1024 +static struct { + struct { + FILE * handle; + bool capturing; + Bit64s start; + Bit64s last; + Bit8u index; + Bit8u buffer[RAW_SIZE+8]; + Bit8u regs[2][256]; + Bit32u used; + Bit32u done; + Bit8u cmd[2]; + bool opl3; + bool dualopl2; + } raw; +} opl; + +static void OPL_RawAdd(Bitu index,Bitu val); + +static Bit8u dro_header[]={ + 'D','B','R','A', /* 0x00, Bit32u ID */ + 'W','O','P','L', /* 0x04, Bit32u ID */ + 0x0,0x00, /* 0x08, Bit16u version low */ + 0x1,0x00, /* 0x09, Bit16u version high */ + 0x0,0x0,0x0,0x0, /* 0x0c, Bit32u total milliseconds */ + 0x0,0x0,0x0,0x0, /* 0x10, Bit32u total data */ + 0x0,0x0,0x0,0x0 /* 0x14, Bit32u Type 0=opl2,1=opl3,2=dual-opl2 */ +}; +/* Commands + 0x00 Bit8u, millisecond delay+1 + 0x02 none, Use the low index/data pair + 0x03 none, Use the high index/data pair + 0x10 Bit16u, millisecond delay+1 + 0xxx Bit8u, send command and data to current index/data pair +*/ + +static void OPL_RawEmptyBuffer(void) { + fwrite(opl.raw.buffer,1,opl.raw.used,opl.raw.handle); + opl.raw.done+=opl.raw.used; + opl.raw.used=0; +} + +#define ADDBUF(_VAL_) opl.raw.buffer[opl.raw.used++]=_VAL_; +void OPL_CaptureReg(Bitu reg, Bitu val) { if (opl.raw.capturing) { + /* Check if we have yet to start */ + if (!opl.raw.handle) { + if (reg<0xb0 || reg>0xb8) return; + if (!(val&0x20)) return; + Bitu i; + Bit64s t = Time::currentTimeMillis(); + opl.raw.last=t; + opl.raw.start=t; + opl.raw.handle=fopen("c:\\temp\\cap.dro", "wb"); + if (!opl.raw.handle) { + opl.raw.capturing=false; + return; + } + memset(opl.raw.buffer,0,sizeof(opl.raw.buffer)); + fwrite(dro_header,1,sizeof(dro_header),opl.raw.handle); + /* Check the registers to add */ + for (i=0;i<256;i++) { + if (!opl.raw.regs[0][i]) continue; + if (i>=0xb0 && i<=0xb8) continue; + ADDBUF((Bit8u)i); + ADDBUF(opl.raw.regs[0][i]); + } + bool donesecond=false; + /* Check if we already have an opl3 enable bit logged */ + if (opl.raw.regs[1][5] & 1) + opl.raw.opl3 = true; + for (i=0;i<256;i++) { + if (!opl.raw.regs[1][i]) continue; + if (i>=0xb0 && i<=0xb8) continue; + if (!donesecond) { + /* Or already have dual opl2 */ + opl.raw.dualopl2 = true; + donesecond=true; + ADDBUF(0x3); + } + ADDBUF((Bit8u)i); + ADDBUF(opl.raw.regs[1][i]); + } + if (donesecond) ADDBUF(0x2); + } + /* Check if we enable opl3 or access dual opl2 mode */ + if (cmd == 5 && index && (val & 1)) { + opl.raw.opl3 = true; + } + if (index && val && cmd>=0xb0 && cmd<=0xb8) { + opl.raw.dualopl2 = true; + } + /* Check how much time has passed, Allow an extra 5 milliseconds? */ + Bit64s t = Time::currentTimeMillis(); + if (t > (opl.raw.last+5)) { + Bitu passed = (Bitu)(t - opl.raw.last); + opl.raw.last = t; + while (passed) { + passed-=1; + if (passed<256) { + ADDBUF(0x00); //8bit delay + ADDBUF((Bit8u)passed); //8bit delay + passed=0; + } else if (passed<0x10000) { + ADDBUF(0x01); //16bit delay + ADDBUF((Bit8u)(passed & 0xff)); //lo-byte + ADDBUF((Bit8u)(passed >> 8)); //hi-byte + passed=0; + } else { + ADDBUF(0x01); //16bit delay + ADDBUF(0xff); //lo-byte + ADDBUF(0xff); //hi-byte + passed-=0xffff; + } + } + } + /* Check if we're still sending to the correct index */ + if (opl.raw.index != index) { + opl.raw.index=(Bit8u)index; + ADDBUF(opl.raw.index ? 0x3 : 0x2); + } + ADDBUF(cmd); + ADDBUF((Bit8u)val); + if (opl.raw.used>=RAW_SIZE) OPL_RawEmptyBuffer(); +}} + +void OPL_ToggleCapture() { + /* Check for previously opened file */ + if (opl.raw.handle) { + OPL_RawEmptyBuffer(); + /* Fill in the header with useful information */ + host_writed(&dro_header[0x0c],(Bit32u)(opl.raw.last-opl.raw.start)); + host_writed(&dro_header[0x10],opl.raw.done); + if (opl.raw.opl3 && opl.raw.dualopl2) host_writed(&dro_header[0x14],0x1); + else if (opl.raw.dualopl2) host_writed(&dro_header[0x14],0x2); + else host_writed(&dro_header[0x14],0x0); + fseek(opl.raw.handle,0,0); + fwrite(dro_header,1,sizeof(dro_header),opl.raw.handle); + fclose(opl.raw.handle); + opl.raw.handle=0; + } + if (opl.raw.capturing) { + opl.raw.capturing=false; + //LOG_MSG("Stopping Raw OPL capturing."); + } else { + //LOG_MSG("Preparing to capture Raw OPL, will start with first note played."); + opl.raw.capturing=true; + opl.raw.index=0; + opl.raw.used=0; + opl.raw.done=0; + opl.raw.start=0; + opl.raw.last=0; + } +} \ No newline at end of file diff --git a/Source/drov1.h b/Source/drov1.h new file mode 100644 index 0000000..10fdc65 --- /dev/null +++ b/Source/drov1.h @@ -0,0 +1,5 @@ +#include "dosbox.h" + +extern void OPL_CaptureReg(Bitu index,Bitu val); + +extern void OPL_ToggleCapture(); diff --git a/Source/hiopl.cpp b/Source/hiopl.cpp index 687b276..4f4f752 100644 --- a/Source/hiopl.cpp +++ b/Source/hiopl.cpp @@ -6,6 +6,9 @@ // A wrapper around the DOSBox and ZDoom OPL emulators. Hiopl::Hiopl(int buflen, Emulator emulator) { + captureHandle = NULL; + StopCapture(); + adlib = new DBOPL::Handler(); zdoom = JavaOPLCreate(false); @@ -74,6 +77,7 @@ void Hiopl::_WriteReg(Bit32u reg, Bit8u value, Bit8u mask) { zdoom->WriteReg(reg, value); //} regCache[reg] = value; + _CaptureRegWriteWithDelay(reg, value); } void Hiopl::_ClearRegBits(Bit32u reg, Bit8u mask) { @@ -222,6 +226,142 @@ void Hiopl::_milliHertzToFnum(unsigned int milliHertz, return; } +static Bit8u dro_header[]={ + 'D','B','R','A', /* 0x00, Bit32u ID */ + 'W','O','P','L', /* 0x04, Bit32u ID */ + 0x0,0x00, /* 0x08, Bit16u version low */ + 0x1,0x00, /* 0x09, Bit16u version high */ + 0x0,0x0,0x0,0x0, /* 0x0c, Bit32u total milliseconds */ + 0x0,0x0,0x0,0x0, /* 0x10, Bit32u total data */ + 0x0,0x0,0x0,0x0 /* 0x14, Bit32u Type 0=opl2,1=opl3,2=dual-opl2 */ +}; + +static Bit8u dro_opl3_enable[]={ + 0x03, // switch to extended register bank + 0x05, // register 0x105 + 0x01, // value 0x1 + 0x02 // switch back to regular OPL2 registers +}; + +void Hiopl::StartCapture(const char* filepath) { + if (NULL != captureHandle) { + StopCapture(); + } else { + lastWrite = -1; + captureLengthBytes = 0; + captureStart = Time::currentTimeMillis(); + captureHandle = fopen(filepath, "wb"); + fwrite(dro_header, 1, sizeof(dro_header), captureHandle); + for (int i = 0; i <= 0xff; i++) { + _CaptureRegWrite(i, 0); + } + //_CaptureRegWrite(0x1, 0x20); + /* + for (int ch = 1; ch <= CHANNELS; ch++) { + int offset1 = this->_GetOffset(ch, 1); + int offset2 = this->_GetOffset(ch, 2); + int offset0 = this->_GetOffset(ch); + _CaptureRegWrite(0x20 + offset1, regCache[0x20 + offset1]); + _CaptureRegWrite(0x20 + offset2, regCache[0x20 + offset2]); + _CaptureRegWrite(0x40 + offset1, regCache[0x40 + offset1]); + _CaptureRegWrite(0x40 + offset2, regCache[0x40 + offset2]); + _CaptureRegWrite(0x60 + offset1, regCache[0x60 + offset1]); + _CaptureRegWrite(0x60 + offset2, regCache[0x60 + offset2]); + _CaptureRegWrite(0x80 + offset1, regCache[0x80 + offset1]); + _CaptureRegWrite(0x80 + offset2, regCache[0x80 + offset2]); + _CaptureRegWrite(0xe0 + offset1, regCache[0xe0 + offset1]); + _CaptureRegWrite(0xe0 + offset2, regCache[0xe0 + offset2]); + _CaptureRegWrite(0xc0 + offset0, regCache[0xc0 + offset0]); + } + */ + _CaptureOpl3Enable(); + for (Bit8u i = 0x20; i <= 0x35; i++) { + _CaptureRegWrite(i, regCache[i]); + } + for (Bit8u i = 0x40; i <= 0x55; i++) { + _CaptureRegWrite(i, regCache[i]); + } + for (Bit8u i = 0x60; i <= 0x75; i++) { + _CaptureRegWrite(i, regCache[i]); + } + for (Bit8u i = 0x80; i <= 0x95; i++) { + _CaptureRegWrite(i, regCache[i]); + } + _CaptureRegWrite(0xbd, regCache[0xbd]); + for (Bit8u i = 0xc0; i <= 0xc8; i++) { + _CaptureRegWrite(i, regCache[i] | 0x30); // enable L + R channels + } + for (Bit8u i = 0xe0; i <= 0xf5; i++) { + _CaptureRegWrite(i, regCache[i]); + } + } +} + +INLINE void host_writed(Bit8u *off, Bit32u val) { + off[0]=(Bit8u)(val); + off[1]=(Bit8u)(val >> 8); + off[2]=(Bit8u)(val >> 16); + off[3]=(Bit8u)(val >> 24); +}; + +void Hiopl::StopCapture() { + if (NULL != captureHandle) { + Bit32u lengthMilliseconds = (Bit32u)(3000 + Time::currentTimeMillis() - captureStart); + host_writed(&dro_header[0x0c], lengthMilliseconds); + host_writed(&dro_header[0x10], captureLengthBytes); + //if (opl.raw.opl3 && opl.raw.dualopl2) host_writed(&dro_header[0x14],0x1); + //else if (opl.raw.dualopl2) host_writed(&dro_header[0x14],0x2); + //else host_writed(&dro_header[0x14],0x0); + host_writed(&dro_header[0x14], 0x1); // OPL3 + fseek(captureHandle, 0, 0); + fwrite(dro_header, 1, sizeof(dro_header), captureHandle); + fclose(captureHandle); + } + captureHandle = NULL; + captureLengthBytes = 0; + lastWrite = -1; + captureStart = -1; +} + +void Hiopl::_CaptureDelay(Bit16u delayMs) { + Bit8u delay[3]; + delay[0] = 0x01; + delay[1] = delayMs & 0xff; + delay[2] = (delayMs >> 8) & 0xff; + fwrite(delay, 1, 3, captureHandle); + captureLengthBytes += 3; +} + +void Hiopl::_CaptureRegWrite(Bit32u reg, Bit8u value) { + if (reg <= 0x4) { + Bit8u escape = 0x4; + fwrite(&escape, 1, 1, captureHandle); + captureLengthBytes += 1; + } + Bit8u regAndVal[2]; + regAndVal[0] = (Bit8u)reg; + regAndVal[1] = value; + fwrite(regAndVal, 1, 2, captureHandle); + captureLengthBytes += 2; +} + +void Hiopl::_CaptureRegWriteWithDelay(Bit32u reg, Bit8u value) { + if (NULL != captureHandle) { + Bit64s t = Time::currentTimeMillis(); + if (lastWrite >= 0) { + // Delays of over 65 seconds will be truncated, but that kind of delay is a bit silly anyway.. + _CaptureDelay((Bit16u)(t - lastWrite)); + } + _CaptureRegWrite(reg, value); + lastWrite = t; + } +} + +void Hiopl::_CaptureOpl3Enable() { + fwrite(dro_opl3_enable, 1, 4, captureHandle); + captureLengthBytes += 4; +} + Hiopl::~Hiopl() { }; diff --git a/Source/hiopl.h b/Source/hiopl.h index 60e6142..f0e99b2 100644 --- a/Source/hiopl.h +++ b/Source/hiopl.h @@ -57,6 +57,10 @@ class Hiopl { void SetFrequency(int ch, float frqHz, bool keyOn=false); void _WriteReg(Bit32u reg, Bit8u value, Bit8u mask=0x0); void _ClearRegBits(Bit32u reg, Bit8u mask); + + void StartCapture(const char* filepath); + void StopCapture(); + ~Hiopl(); private: Emulator emulator; @@ -68,6 +72,15 @@ class Hiopl { int _GetOffset(int ch); void _milliHertzToFnum(unsigned int milliHertz, unsigned int *fnum, unsigned int *block, unsigned int conversionFactor=49716); void _ClearRegisters(); + void _CaptureDelay(Bit16u delayMs); + void _CaptureRegWriteWithDelay(Bit32u reg, Bit8u value); + void _CaptureRegWrite(Bit32u reg, Bit8u value); + void _CaptureOpl3Enable(); std::map _op1offset; std::map _op2offset; + + FILE* captureHandle; + Bit64s captureStart; + Bit64s lastWrite; + Bit32u captureLengthBytes; };