Work in progress - refactoring for multiple instance DRO recording, bug fix.
This commit is contained in:
parent
ecfc83df47
commit
45d0d9549c
11 changed files with 281 additions and 178 deletions
|
@ -11,6 +11,11 @@
|
|||
buildVST3="0" buildRTAS="0" buildAAX="0" includeBinaryInAppConfig="1">
|
||||
<MAINGROUP id="TOefyq" name="JuceOPLVSTi">
|
||||
<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="TQyCKv" name="zdopl.h" compile="0" resource="0" file="Source/zdopl.h"/>
|
||||
<FILE id="LVSdHL" name="InstrumentLoader.h" compile="0" resource="0"
|
||||
|
@ -69,7 +74,8 @@
|
|||
</MODULES>
|
||||
<JUCEOPTIONS JUCE_QUICKTIME="disabled"/>
|
||||
<EXPORTFORMATS>
|
||||
<VS2013 targetFolder="Builds/VisualStudio2013" vstFolder="C:\code\audio\vstsdk2.4">
|
||||
<VS2013 targetFolder="Builds/VisualStudio2013" vstFolder="C:\code\audio\vstsdk2.4"
|
||||
externalLibraries="">
|
||||
<CONFIGURATIONS>
|
||||
<CONFIGURATION name="Debug" winWarningLevel="4" generateManifest="1" winArchitecture="32-bit"
|
||||
isDebug="1" optimisation="1" targetName="JuceOPLVSTi"/>
|
||||
|
|
157
Source/DROMultiplexer.cpp
Normal file
157
Source/DROMultiplexer.cpp
Normal 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
33
Source/DROMultiplexer.h
Normal 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;
|
||||
|
||||
};
|
||||
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include <deque>
|
||||
#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<FloatParameter*> params;
|
||||
std::map<String, int> paramIdxByName;
|
||||
std::map<String, std::vector<float>> programs;
|
||||
|
|
|
@ -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 {
|
||||
|
|
176
Source/hiopl.cpp
176
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 ³
|
||||
ÀÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
|
||||
*/
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#pragma once
|
||||
#include <map>
|
||||
|
||||
#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<int, int> _op1offset;
|
||||
std::map<int, int> _op2offset;
|
||||
|
||||
static Hiopl* master;
|
||||
FILE* captureHandle;
|
||||
Bit64s captureStart;
|
||||
Bit64s lastWrite;
|
||||
Bit32u captureLengthBytes;
|
||||
};
|
||||
|
|
26
Source/tests.cpp
Normal file
26
Source/tests.cpp
Normal 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;
|
||||
|
|
@ -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.
|
||||
|
|
Loading…
Reference in a new issue