diff --git a/JuceOPLVSTi.jucer b/JuceOPLVSTi.jucer index 8b56992..5ec1808 100644 --- a/JuceOPLVSTi.jucer +++ b/JuceOPLVSTi.jucer @@ -11,6 +11,11 @@ buildVST3="0" buildRTAS="0" buildAAX="0" includeBinaryInAppConfig="1"> + + + - + diff --git a/Source/DROMultiplexer.cpp b/Source/DROMultiplexer.cpp new file mode 100644 index 0000000..50f1c56 --- /dev/null +++ b/Source/DROMultiplexer.cpp @@ -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; +} diff --git a/Source/DROMultiplexer.h b/Source/DROMultiplexer.h new file mode 100644 index 0000000..4d1181e --- /dev/null +++ b/Source/DROMultiplexer.h @@ -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; + +}; + diff --git a/Source/EnumFloatParameter.cpp b/Source/EnumFloatParameter.cpp index 4b23a3e..4ca9870 100644 --- a/Source/EnumFloatParameter.cpp +++ b/Source/EnumFloatParameter.cpp @@ -14,7 +14,7 @@ EnumFloatParameter::~EnumFloatParameter(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()) i = values.size() - 1; return i; diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 67c7b55..ebaeb5f 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -6,6 +6,11 @@ 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() : i_program(-1) @@ -15,6 +20,8 @@ JuceOplvstiAudioProcessor::JuceOplvstiAudioProcessor() Opl = new Hiopl(44100); // 1 second at 44100 Opl->SetSampleRate(44100); Opl->EnableWaveformControl(); + dro = new DROMultiplexer(); + plexer = dro; recordingFile = NULL; @@ -146,16 +153,17 @@ bool JuceOplvstiAudioProcessor::isThisInstanceRecording() { } bool JuceOplvstiAudioProcessor::isAnyInstanceRecording() { - return Opl->IsAnInstanceRecording(); + return dro->IsAnInstanceRecording(); } void JuceOplvstiAudioProcessor::startRecording(File *outputFile) { recordingFile = outputFile; - Opl->StartCapture(outputFile->getFullPathName().toUTF8()); + dro->StartCapture(outputFile->getFullPathName().toUTF8(), Opl); + Opl->regWriteCallback = ®WriteCallback; } void JuceOplvstiAudioProcessor::stopRecording() { - Opl->StopCapture(); + dro->StopCapture(); recordingFile = NULL; } @@ -446,6 +454,8 @@ JuceOplvstiAudioProcessor::~JuceOplvstiAudioProcessor() { for (unsigned int i=0; i < params.size(); ++i) delete params[i]; + //delete Opl; + //delete dro; } //============================================================================== @@ -703,6 +713,8 @@ void JuceOplvstiAudioProcessor::releaseResources() // spare memory, etc. } +static const Drum DRUM_INDEX[] = { BDRUM, SNARE, TOM, CYMBAL, HIHAT }; + void JuceOplvstiAudioProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages) { buffer.clear(0, 0, buffer.getNumSamples()); @@ -719,12 +731,11 @@ void JuceOplvstiAudioProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuf float noteHz = (float)MidiMessage::getMidiNoteInHertz(n); int ch; - if (perc > 0) { - static const Drum drumIndex[] = { BDRUM, SNARE, TOM, CYMBAL, HIHAT }; + if (perc > 0) { for (int i = 1; i <= Hiopl::CHANNELS; i++) { Opl->SetFrequency(i, noteHz, false); } - Opl->HitPercussion(drumIndex[perc - 1]); + Opl->HitPercussion(DRUM_INDEX[perc - 1]); } else { if (!available_channels.empty()) { @@ -766,7 +777,6 @@ void JuceOplvstiAudioProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuf active_notes[ch] = n; applyPitchBend(); } - } else if (midi_message.isNoteOff()) { if (perc > 0) { diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index 5c48366..2e8e6b2 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -13,6 +13,7 @@ #include #include "../JuceLibraryCode/JuceHeader.h" #include "hiopl.h" +#include "DROMultiplexer.h" #include "FloatParameter.h" @@ -87,6 +88,7 @@ public: private: Hiopl *Opl; + DROMultiplexer *dro; std::vector params; std::map paramIdxByName; std::map> programs; diff --git a/Source/dbopl.cpp b/Source/dbopl.cpp index a35e054..9566516 100644 --- a/Source/dbopl.cpp +++ b/Source/dbopl.cpp @@ -529,6 +529,9 @@ void Operator::WriteE0( const Chip* chip, Bit8u val ) { INLINE void Operator::SetState( Bit8u s ) { state = s; volHandler = VolumeHandlerTable[ s ]; + if (OFF == s) { + // TODO: callback + } } INLINE bool Operator::Silent() const { diff --git a/Source/hiopl.cpp b/Source/hiopl.cpp index 53ea0bb..2d23dc7 100644 --- a/Source/hiopl.cpp +++ b/Source/hiopl.cpp @@ -5,20 +5,8 @@ // 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) { - InitCaptureVariables(); + //InitCaptureVariables(); adlib = new DBOPL::Handler(); zdoom = JavaOPLCreate(false); @@ -88,7 +76,12 @@ void Hiopl::_WriteReg(Bit32u reg, Bit8u value, Bit8u mask) { zdoom->WriteReg(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) { @@ -179,6 +172,7 @@ void Hiopl::SetModulatorFeedback(int ch, int level) { void Hiopl::SetPercussionMode(bool enable) { _WriteReg(0xbd, enable ? 0x20 : 0x0, 0x20); + // TODO: handle capture mode.. channels reduced from 18 -> 15 } void Hiopl::HitPercussion(Drum drum) { @@ -199,6 +193,10 @@ void Hiopl::KeyOff(int ch) { _ClearRegBits(0xb0+offset, 0x20); } +//void Hiopl::EnvelopeOffCallback(int ch) { +// +//} + void Hiopl::SetFrequency(int ch, float frqHz, bool keyOn) { unsigned int fnum, block; int offset = this->_GetOffset(ch); @@ -250,145 +248,6 @@ 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) { - 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() { }; @@ -406,3 +265,14 @@ int Hiopl::_GetOffset(int ch) { assert(_CheckParams(ch)); 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 ³ +ÀÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ +*/ diff --git a/Source/hiopl.h b/Source/hiopl.h index bfe6677..7e098bb 100644 --- a/Source/hiopl.h +++ b/Source/hiopl.h @@ -1,3 +1,4 @@ +#pragma once #include #include "adlib.h" @@ -24,12 +25,15 @@ enum Drum BDRUM=0x10, SNARE=0x8, TOM=0x4, CYMBAL=0x2, HIHAT=0x1 }; +typedef void(*RegWriteCallback_t)(Bit32u reg, Bit8u val); + class Hiopl { public: + RegWriteCallback_t regWriteCallback = NULL; static const int CHANNELS = 9; static const int OSCILLATORS = 2; - Hiopl(int buflen, Emulator emulator=ZDOOM); + Hiopl(int buflen, Emulator emulator = ZDOOM); void SetEmulator(Emulator emulator); void SetPercussionMode(bool enable); void HitPercussion(Drum drum); @@ -65,12 +69,8 @@ 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 InitCaptureVariables(); - bool IsAnInstanceRecording(); - bool IsAnotherInstanceRecording(); - void StartCapture(const char* filepath); - void StopCapture(); + // Read the last value written to the specified register address (cached) + Bit8u _ReadReg(Bit32u reg); ~Hiopl(); private: @@ -83,16 +83,8 @@ 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; - static Hiopl* master; - FILE* captureHandle; - Bit64s captureStart; - Bit64s lastWrite; - Bit32u captureLengthBytes; }; diff --git a/Source/tests.cpp b/Source/tests.cpp new file mode 100644 index 0000000..3991be1 --- /dev/null +++ b/Source/tests.cpp @@ -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; + diff --git a/readme.md b/readme.md index a9e04f3..58b3775 100644 --- a/readme.md +++ b/readme.md @@ -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. - 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? ## 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.