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">
|
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
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 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;
|
||||||
|
|
|
@ -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 = ®WriteCallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
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());
|
||||||
|
@ -719,12 +731,11 @@ void JuceOplvstiAudioProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuf
|
||||||
float noteHz = (float)MidiMessage::getMidiNoteInHertz(n);
|
float noteHz = (float)MidiMessage::getMidiNoteInHertz(n);
|
||||||
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) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
176
Source/hiopl.cpp
176
Source/hiopl.cpp
|
@ -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 ³
|
||||||
|
ÀÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
|
||||||
|
*/
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
#pragma once
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
#include "adlib.h"
|
#include "adlib.h"
|
||||||
|
@ -24,12 +25,15 @@ 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);
|
||||||
void SetEmulator(Emulator emulator);
|
void SetEmulator(Emulator emulator);
|
||||||
void SetPercussionMode(bool enable);
|
void SetPercussionMode(bool enable);
|
||||||
void HitPercussion(Drum drum);
|
void HitPercussion(Drum drum);
|
||||||
|
@ -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
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.
|
- 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.
|
||||||
|
|
Loading…
Reference in a new issue