2
0
Fork 0

Work in progress - refactoring for multiple instance DRO recording, bug fix.

This commit is contained in:
Bruce Sutherland 2015-01-06 12:13:44 +09:00
parent ecfc83df47
commit 45d0d9549c
11 changed files with 281 additions and 178 deletions

View file

@ -11,6 +11,11 @@
buildVST3="0" buildRTAS="0" buildAAX="0" includeBinaryInAppConfig="1"> buildVST3="0" buildRTAS="0" buildAAX="0" includeBinaryInAppConfig="1">
<MAINGROUP id="TOefyq" name="JuceOPLVSTi"> <MAINGROUP id="TOefyq" name="JuceOPLVSTi">
<GROUP id="{DCA755EB-7953-0641-E719-95C7850E5B2B}" name="Source"> <GROUP id="{DCA755EB-7953-0641-E719-95C7850E5B2B}" name="Source">
<FILE id="o0sULY" name="DROMultiplexer.cpp" compile="1" resource="0"
file="Source/DROMultiplexer.cpp"/>
<FILE id="cULQN7" name="DROMultiplexer.h" compile="0" resource="0"
file="Source/DROMultiplexer.h"/>
<FILE id="KRj0DZ" name="tests.cpp" compile="1" resource="0" file="Source/tests.cpp"/>
<FILE id="hjHmNq" name="zdopl.cpp" compile="1" resource="0" file="Source/zdopl.cpp"/> <FILE id="hjHmNq" name="zdopl.cpp" compile="1" resource="0" file="Source/zdopl.cpp"/>
<FILE id="TQyCKv" name="zdopl.h" compile="0" resource="0" file="Source/zdopl.h"/> <FILE id="TQyCKv" name="zdopl.h" compile="0" resource="0" file="Source/zdopl.h"/>
<FILE id="LVSdHL" name="InstrumentLoader.h" compile="0" resource="0" <FILE id="LVSdHL" name="InstrumentLoader.h" compile="0" resource="0"
@ -69,7 +74,8 @@
</MODULES> </MODULES>
<JUCEOPTIONS JUCE_QUICKTIME="disabled"/> <JUCEOPTIONS JUCE_QUICKTIME="disabled"/>
<EXPORTFORMATS> <EXPORTFORMATS>
<VS2013 targetFolder="Builds/VisualStudio2013" vstFolder="C:\code\audio\vstsdk2.4"> <VS2013 targetFolder="Builds/VisualStudio2013" vstFolder="C:\code\audio\vstsdk2.4"
externalLibraries="">
<CONFIGURATIONS> <CONFIGURATIONS>
<CONFIGURATION name="Debug" winWarningLevel="4" generateManifest="1" winArchitecture="32-bit" <CONFIGURATION name="Debug" winWarningLevel="4" generateManifest="1" winArchitecture="32-bit"
isDebug="1" optimisation="1" targetName="JuceOPLVSTi"/> isDebug="1" optimisation="1" targetName="JuceOPLVSTi"/>

157
Source/DROMultiplexer.cpp Normal file
View file

@ -0,0 +1,157 @@
#include "DROMultiplexer.h"
#include "JuceHeader.h"
// Used by the first recording instance to claim master status
// TODO: develop the logic for recording data from other (non-master) plugins
DROMultiplexer* DROMultiplexer::master = NULL;
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
};
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);
};
DROMultiplexer::DROMultiplexer()
{
}
DROMultiplexer::~DROMultiplexer()
{
}
void DROMultiplexer::TwoOpMelodicNoteOn(Hiopl* opl, int ch) {
}
void DROMultiplexer::TwoOpMelodicNoteOff(Hiopl* opl, int ch) {
}
void DROMultiplexer::PercussionHit(Hiopl* opl) {
}
void DROMultiplexer::InitCaptureVariables() {
captureHandle = NULL;
captureLengthBytes = 0;
lastWrite = -1;
captureStart = -1;
}
void DROMultiplexer::StartCapture(const char* filepath, Hiopl *opl) {
DROMultiplexer::master = this;
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);
}
_CaptureOpl3Enable();
for (Bit8u i = 0x20; i <= 0x35; i++) {
_CaptureRegWrite(i, opl->_ReadReg(i));
}
for (Bit8u i = 0x40; i <= 0x55; i++) {
_CaptureRegWrite(i, opl->_ReadReg(i));
}
for (Bit8u i = 0x60; i <= 0x75; i++) {
_CaptureRegWrite(i, opl->_ReadReg(i));
}
for (Bit8u i = 0x80; i <= 0x95; i++) {
_CaptureRegWrite(i, opl->_ReadReg(i));
}
_CaptureRegWrite(0xbd, opl->_ReadReg(0xbd));
for (Bit8u i = 0xc0; i <= 0xc8; i++) {
_CaptureRegWrite(i, opl->_ReadReg(i) | 0x30); // enable L + R channels
}
for (Bit8u i = 0xe0; i <= 0xf5; i++) {
_CaptureRegWrite(i, opl->_ReadReg(i));
}
}
void DROMultiplexer::StopCapture() {
if (NULL != captureHandle) {
Bit16u finalDelay = (Bit16u)(Time::currentTimeMillis() - lastWrite);
_CaptureDelay(finalDelay);
Bit32u lengthMilliseconds = (Bit32u)(finalDelay + 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);
}
InitCaptureVariables();
}
void DROMultiplexer::_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 DROMultiplexer::_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 DROMultiplexer::_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 DROMultiplexer::_CaptureOpl3Enable() {
fwrite(dro_opl3_enable, 1, 4, captureHandle);
captureLengthBytes += 4;
}
bool DROMultiplexer::IsAnInstanceRecording() {
return NULL != DROMultiplexer::master;
}
bool DROMultiplexer::IsAnotherInstanceRecording() {
return this->IsAnInstanceRecording() && this != DROMultiplexer::master;
}

33
Source/DROMultiplexer.h Normal file
View file

@ -0,0 +1,33 @@
#pragma once
#include "hiopl.h"
class DROMultiplexer
{
public:
DROMultiplexer();
~DROMultiplexer();
void TwoOpMelodicNoteOn(Hiopl* opl, int ch);
void TwoOpMelodicNoteOff(Hiopl* opl, int ch);
void PercussionHit(Hiopl* opl);
void InitCaptureVariables();
bool IsAnInstanceRecording();
bool IsAnotherInstanceRecording();
void StartCapture(const char* filepath, Hiopl* opl);
void StopCapture();
//private:
void _CaptureDelay(Bit16u delayMs);
void _CaptureRegWriteWithDelay(Bit32u reg, Bit8u value);
void _CaptureRegWrite(Bit32u reg, Bit8u value);
void _CaptureOpl3Enable();
static DROMultiplexer* master;
FILE* captureHandle;
Bit64s captureStart;
Bit64s lastWrite;
Bit32u captureLengthBytes;
};

View file

@ -14,7 +14,7 @@ EnumFloatParameter::~EnumFloatParameter(void)
int EnumFloatParameter::getParameterIndex(void) int EnumFloatParameter::getParameterIndex(void)
{ {
int i = (int)(this->value * values.size()); int i = (int)(this->value * values.size() + 0.5f);
if (i >= values.size()) if (i >= values.size())
i = values.size() - 1; i = values.size() - 1;
return i; return i;

View file

@ -6,6 +6,11 @@
const char *JuceOplvstiAudioProcessor::PROGRAM_INDEX = "Program Index"; const char *JuceOplvstiAudioProcessor::PROGRAM_INDEX = "Program Index";
static DROMultiplexer* plexer = NULL;
void regWriteCallback(Bit32u reg, Bit8u val) {
if (NULL != plexer) plexer->_CaptureRegWriteWithDelay(reg, val);
}
//============================================================================== //==============================================================================
JuceOplvstiAudioProcessor::JuceOplvstiAudioProcessor() JuceOplvstiAudioProcessor::JuceOplvstiAudioProcessor()
: i_program(-1) : i_program(-1)
@ -15,6 +20,8 @@ JuceOplvstiAudioProcessor::JuceOplvstiAudioProcessor()
Opl = new Hiopl(44100); // 1 second at 44100 Opl = new Hiopl(44100); // 1 second at 44100
Opl->SetSampleRate(44100); Opl->SetSampleRate(44100);
Opl->EnableWaveformControl(); Opl->EnableWaveformControl();
dro = new DROMultiplexer();
plexer = dro;
recordingFile = NULL; recordingFile = NULL;
@ -146,16 +153,17 @@ bool JuceOplvstiAudioProcessor::isThisInstanceRecording() {
} }
bool JuceOplvstiAudioProcessor::isAnyInstanceRecording() { bool JuceOplvstiAudioProcessor::isAnyInstanceRecording() {
return Opl->IsAnInstanceRecording(); return dro->IsAnInstanceRecording();
} }
void JuceOplvstiAudioProcessor::startRecording(File *outputFile) { void JuceOplvstiAudioProcessor::startRecording(File *outputFile) {
recordingFile = outputFile; recordingFile = outputFile;
Opl->StartCapture(outputFile->getFullPathName().toUTF8()); dro->StartCapture(outputFile->getFullPathName().toUTF8(), Opl);
Opl->regWriteCallback = &regWriteCallback;
} }
void JuceOplvstiAudioProcessor::stopRecording() { void JuceOplvstiAudioProcessor::stopRecording() {
Opl->StopCapture(); dro->StopCapture();
recordingFile = NULL; recordingFile = NULL;
} }
@ -446,6 +454,8 @@ JuceOplvstiAudioProcessor::~JuceOplvstiAudioProcessor()
{ {
for (unsigned int i=0; i < params.size(); ++i) for (unsigned int i=0; i < params.size(); ++i)
delete params[i]; delete params[i];
//delete Opl;
//delete dro;
} }
//============================================================================== //==============================================================================
@ -703,6 +713,8 @@ void JuceOplvstiAudioProcessor::releaseResources()
// spare memory, etc. // spare memory, etc.
} }
static const Drum DRUM_INDEX[] = { BDRUM, SNARE, TOM, CYMBAL, HIHAT };
void JuceOplvstiAudioProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages) void JuceOplvstiAudioProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages)
{ {
buffer.clear(0, 0, buffer.getNumSamples()); buffer.clear(0, 0, buffer.getNumSamples());
@ -720,11 +732,10 @@ void JuceOplvstiAudioProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuf
int ch; int ch;
if (perc > 0) { if (perc > 0) {
static const Drum drumIndex[] = { BDRUM, SNARE, TOM, CYMBAL, HIHAT };
for (int i = 1; i <= Hiopl::CHANNELS; i++) { for (int i = 1; i <= Hiopl::CHANNELS; i++) {
Opl->SetFrequency(i, noteHz, false); Opl->SetFrequency(i, noteHz, false);
} }
Opl->HitPercussion(drumIndex[perc - 1]); Opl->HitPercussion(DRUM_INDEX[perc - 1]);
} else { } else {
if (!available_channels.empty()) if (!available_channels.empty())
{ {
@ -766,7 +777,6 @@ void JuceOplvstiAudioProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuf
active_notes[ch] = n; active_notes[ch] = n;
applyPitchBend(); applyPitchBend();
} }
} }
else if (midi_message.isNoteOff()) { else if (midi_message.isNoteOff()) {
if (perc > 0) { if (perc > 0) {

View file

@ -13,6 +13,7 @@
#include <deque> #include <deque>
#include "../JuceLibraryCode/JuceHeader.h" #include "../JuceLibraryCode/JuceHeader.h"
#include "hiopl.h" #include "hiopl.h"
#include "DROMultiplexer.h"
#include "FloatParameter.h" #include "FloatParameter.h"
@ -87,6 +88,7 @@ public:
private: private:
Hiopl *Opl; Hiopl *Opl;
DROMultiplexer *dro;
std::vector<FloatParameter*> params; std::vector<FloatParameter*> params;
std::map<String, int> paramIdxByName; std::map<String, int> paramIdxByName;
std::map<String, std::vector<float>> programs; std::map<String, std::vector<float>> programs;

View file

@ -529,6 +529,9 @@ void Operator::WriteE0( const Chip* chip, Bit8u val ) {
INLINE void Operator::SetState( Bit8u s ) { INLINE void Operator::SetState( Bit8u s ) {
state = s; state = s;
volHandler = VolumeHandlerTable[ s ]; volHandler = VolumeHandlerTable[ s ];
if (OFF == s) {
// TODO: callback
}
} }
INLINE bool Operator::Silent() const { INLINE bool Operator::Silent() const {

View file

@ -5,20 +5,8 @@
// A wrapper around the DOSBox and ZDoom OPL emulators. // A wrapper around the DOSBox and ZDoom OPL emulators.
// Used by the first recording instance to claim master status
// TODO: develop the logic for recording data from other (non-master) plugins
Hiopl* Hiopl::master = NULL;
bool Hiopl::IsAnInstanceRecording() {
return NULL != Hiopl::master;
}
bool Hiopl::IsAnotherInstanceRecording() {
return this->IsAnInstanceRecording() && this != Hiopl::master;
}
Hiopl::Hiopl(int buflen, Emulator emulator) { Hiopl::Hiopl(int buflen, Emulator emulator) {
InitCaptureVariables(); //InitCaptureVariables();
adlib = new DBOPL::Handler(); adlib = new DBOPL::Handler();
zdoom = JavaOPLCreate(false); zdoom = JavaOPLCreate(false);
@ -88,7 +76,12 @@ void Hiopl::_WriteReg(Bit32u reg, Bit8u value, Bit8u mask) {
zdoom->WriteReg(reg, value); zdoom->WriteReg(reg, value);
//} //}
regCache[reg] = value; regCache[reg] = value;
_CaptureRegWriteWithDelay(reg, value); //_CaptureRegWriteWithDelay(reg, value);
if (NULL != regWriteCallback) regWriteCallback(reg, value);
}
Bit8u Hiopl::_ReadReg(Bit32u reg) {
return regCache[reg];
} }
void Hiopl::_ClearRegBits(Bit32u reg, Bit8u mask) { void Hiopl::_ClearRegBits(Bit32u reg, Bit8u mask) {
@ -179,6 +172,7 @@ void Hiopl::SetModulatorFeedback(int ch, int level) {
void Hiopl::SetPercussionMode(bool enable) { void Hiopl::SetPercussionMode(bool enable) {
_WriteReg(0xbd, enable ? 0x20 : 0x0, 0x20); _WriteReg(0xbd, enable ? 0x20 : 0x0, 0x20);
// TODO: handle capture mode.. channels reduced from 18 -> 15
} }
void Hiopl::HitPercussion(Drum drum) { void Hiopl::HitPercussion(Drum drum) {
@ -199,6 +193,10 @@ void Hiopl::KeyOff(int ch) {
_ClearRegBits(0xb0+offset, 0x20); _ClearRegBits(0xb0+offset, 0x20);
} }
//void Hiopl::EnvelopeOffCallback(int ch) {
//
//}
void Hiopl::SetFrequency(int ch, float frqHz, bool keyOn) { void Hiopl::SetFrequency(int ch, float frqHz, bool keyOn) {
unsigned int fnum, block; unsigned int fnum, block;
int offset = this->_GetOffset(ch); int offset = this->_GetOffset(ch);
@ -250,145 +248,6 @@ void Hiopl::_milliHertzToFnum(unsigned int milliHertz,
return; 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) {
Hiopl::master = this;
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::InitCaptureVariables() {
captureHandle = NULL;
captureLengthBytes = 0;
lastWrite = -1;
captureStart = -1;
}
void Hiopl::StopCapture() {
if (NULL != captureHandle) {
Bit16u finalDelay = (Bit16u)(Time::currentTimeMillis() - lastWrite);
_CaptureDelay(finalDelay);
Bit32u lengthMilliseconds = (Bit32u)(finalDelay + 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);
}
InitCaptureVariables();
}
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() { Hiopl::~Hiopl() {
}; };
@ -406,3 +265,14 @@ int Hiopl::_GetOffset(int ch) {
assert(_CheckParams(ch)); assert(_CheckParams(ch));
return ch - 1; return ch - 1;
} }
/*
2. Two-operator Melodic and Percussion Mode
ÚÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
³ Channel ³ 0 1 2 3 4 5 BD SD TT CY HH 9 10 11 12 13 14 15 16 17 ³
ÃÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´
³ Operator 1 ³ 0 1 2 6 7 8 12 16 14 17 13 18 19 20 24 25 26 30 31 32 ³
³ Operator 2 ³ 3 4 5 9 10 11 15 21 22 23 27 28 29 33 34 35 ³
ÀÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
*/

View file

@ -1,3 +1,4 @@
#pragma once
#include <map> #include <map>
#include "adlib.h" #include "adlib.h"
@ -24,9 +25,12 @@ enum Drum
BDRUM=0x10, SNARE=0x8, TOM=0x4, CYMBAL=0x2, HIHAT=0x1 BDRUM=0x10, SNARE=0x8, TOM=0x4, CYMBAL=0x2, HIHAT=0x1
}; };
typedef void(*RegWriteCallback_t)(Bit32u reg, Bit8u val);
class Hiopl { class Hiopl {
public: public:
RegWriteCallback_t regWriteCallback = NULL;
static const int CHANNELS = 9; static const int CHANNELS = 9;
static const int OSCILLATORS = 2; static const int OSCILLATORS = 2;
Hiopl(int buflen, Emulator emulator = ZDOOM); Hiopl(int buflen, Emulator emulator = ZDOOM);
@ -65,12 +69,8 @@ class Hiopl {
void SetFrequency(int ch, float frqHz, bool keyOn=false); void SetFrequency(int ch, float frqHz, bool keyOn=false);
void _WriteReg(Bit32u reg, Bit8u value, Bit8u mask=0x0); void _WriteReg(Bit32u reg, Bit8u value, Bit8u mask=0x0);
void _ClearRegBits(Bit32u reg, Bit8u mask); void _ClearRegBits(Bit32u reg, Bit8u mask);
// Read the last value written to the specified register address (cached)
void InitCaptureVariables(); Bit8u _ReadReg(Bit32u reg);
bool IsAnInstanceRecording();
bool IsAnotherInstanceRecording();
void StartCapture(const char* filepath);
void StopCapture();
~Hiopl(); ~Hiopl();
private: private:
@ -83,16 +83,8 @@ class Hiopl {
int _GetOffset(int ch); int _GetOffset(int ch);
void _milliHertzToFnum(unsigned int milliHertz, unsigned int *fnum, unsigned int *block, unsigned int conversionFactor=49716); void _milliHertzToFnum(unsigned int milliHertz, unsigned int *fnum, unsigned int *block, unsigned int conversionFactor=49716);
void _ClearRegisters(); void _ClearRegisters();
void _CaptureDelay(Bit16u delayMs);
void _CaptureRegWriteWithDelay(Bit32u reg, Bit8u value);
void _CaptureRegWrite(Bit32u reg, Bit8u value);
void _CaptureOpl3Enable();
std::map<int, int> _op1offset; std::map<int, int> _op1offset;
std::map<int, int> _op2offset; std::map<int, int> _op2offset;
static Hiopl* master;
FILE* captureHandle;
Bit64s captureStart;
Bit64s lastWrite;
Bit32u captureLengthBytes;
}; };

26
Source/tests.cpp Normal file
View file

@ -0,0 +1,26 @@
#include "../JuceLibraryCode/JuceHeader.h"
#include "EnumFloatParameter.h"
class EnumFloatParameterTest : public UnitTest
{
public:
EnumFloatParameterTest() : UnitTest("EnumFloatParameterTest") {}
void runTest()
{
beginTest("Test enumerated parameters");
const String percussion[] = { "Off", "Bass drum", "Snare", "Tom", "Cymbal", "Hi-hat" };
int n = sizeof(percussion) / sizeof(String);
EnumFloatParameter* efp = new EnumFloatParameter("Percussion Mode", StringArray(percussion, n));
expect(true);
for (int i = 0; i < n; i++) {
efp->setParameterIndex(i);
expect(i == efp->getParameterIndex());
}
}
};
static EnumFloatParameterTest enumTest;

View file

@ -35,6 +35,10 @@ Percussion mode is now supported! This mode is not very well documented, even in
- Cymbal: Uses carrier settings. Half-sine recommended. - Cymbal: Uses carrier settings. Half-sine recommended.
- Hi-hat: Uses modulator settings. Half-sine recommended. - Hi-hat: Uses modulator settings. Half-sine recommended.
Also, a link to some much more detailed notes on percussion mode based on experimentation with real hardware!
http://midibox.org/forums/topic/18625-opl3-percussion-mode-map/
## How did you create the instrument programs? ## ## How did you create the instrument programs? ##
To figure out the parameters used by the original games, I just added a printf to the DOSBox OPL emulator, compiled DOSBox, ran the games, and captured their output as raw register writes with timestamps. To figure out the parameters used by the original games, I just added a printf to the DOSBox OPL emulator, compiled DOSBox, ran the games, and captured their output as raw register writes with timestamps.