DRO output from multiple plugins. Channel mapping needs testing and debugging.
This commit is contained in:
parent
45d0d9549c
commit
a16025e39c
5 changed files with 151 additions and 26 deletions
|
@ -23,6 +23,44 @@ static Bit8u dro_opl3_enable[] = {
|
||||||
0x02 // switch back to regular OPL2 registers
|
0x02 // switch back to regular OPL2 registers
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// offsets for the 15 two-operator melodic channels
|
||||||
|
// http://www.shikadi.net/moddingwiki/OPL_chip
|
||||||
|
static Bit32u OPERATOR_OFFSETS[15][2] = {
|
||||||
|
{0x000, 0x003}, // 0, 3
|
||||||
|
{0x001, 0x004}, // 1, 4
|
||||||
|
{0x002, 0x005}, // 2, 5
|
||||||
|
{0x008, 0x00b}, // 6, 9
|
||||||
|
{0x009, 0x00c}, // 7, 10
|
||||||
|
{0x00a, 0x00d}, // 8, 11
|
||||||
|
{0x100, 0x103}, // 18, 21
|
||||||
|
{0x101, 0x104}, // 19, 22
|
||||||
|
{0x102, 0x105}, // 20, 23
|
||||||
|
{0x108, 0x10b}, // 24, 27
|
||||||
|
{0x109, 0x10c}, // 25, 28
|
||||||
|
{0x10a, 0x10d}, // 26, 29
|
||||||
|
{0x110, 0x113}, // 30, 33
|
||||||
|
{0x111, 0x114}, // 31, 34
|
||||||
|
{0x112, 0x115}, // 32, 35
|
||||||
|
};
|
||||||
|
|
||||||
|
static Bit32u CHANNEL_OFFSETS[15]= {
|
||||||
|
0x0,
|
||||||
|
0x1,
|
||||||
|
0x2,
|
||||||
|
0x3,
|
||||||
|
0x4,
|
||||||
|
0x5,
|
||||||
|
0x100,
|
||||||
|
0x101,
|
||||||
|
0x102,
|
||||||
|
0x103,
|
||||||
|
0x104,
|
||||||
|
0x105,
|
||||||
|
0x106,
|
||||||
|
0x107,
|
||||||
|
0x108,
|
||||||
|
};
|
||||||
|
|
||||||
INLINE void host_writed(Bit8u *off, Bit32u val) {
|
INLINE void host_writed(Bit8u *off, Bit32u val) {
|
||||||
off[0] = (Bit8u)(val);
|
off[0] = (Bit8u)(val);
|
||||||
off[1] = (Bit8u)(val >> 8);
|
off[1] = (Bit8u)(val >> 8);
|
||||||
|
@ -32,26 +70,96 @@ INLINE void host_writed(Bit8u *off, Bit32u val) {
|
||||||
|
|
||||||
DROMultiplexer::DROMultiplexer()
|
DROMultiplexer::DROMultiplexer()
|
||||||
{
|
{
|
||||||
|
for (int i = 0; i < MELODIC_CHANNELS; ++i) {
|
||||||
|
channels[i].opl = NULL;
|
||||||
|
channels[i].ch = -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
DROMultiplexer::~DROMultiplexer()
|
DROMultiplexer::~DROMultiplexer()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DROMultiplexer* DROMultiplexer::GetMaster() {
|
||||||
|
return DROMultiplexer::master;
|
||||||
|
}
|
||||||
|
|
||||||
void DROMultiplexer::TwoOpMelodicNoteOn(Hiopl* opl, int ch) {
|
void DROMultiplexer::TwoOpMelodicNoteOn(Hiopl* opl, int ch) {
|
||||||
|
// find a free channel and mark it as used
|
||||||
|
int outCh = _FindFreeChannel();
|
||||||
|
channels[outCh].opl = opl;
|
||||||
|
channels[outCh].ch = ch;
|
||||||
|
channelMap[channels[outCh]] = outCh;
|
||||||
|
|
||||||
|
// read all instrument settings and write them all to the file
|
||||||
|
int op1Off = opl->_GetOffset(ch, 1);
|
||||||
|
int op2Off = opl->_GetOffset(ch, 2);
|
||||||
|
Bit32u inAddr;
|
||||||
|
Bit32u outAddr;
|
||||||
|
// waveform select
|
||||||
|
int base = 0xe0;
|
||||||
|
inAddr = base + op1Off;
|
||||||
|
outAddr = base + OPERATOR_OFFSETS[outCh][0];
|
||||||
|
_CaptureRegWriteWithDelay(outAddr, opl->_ReadReg(inAddr));
|
||||||
|
inAddr = base + op2Off;
|
||||||
|
outAddr = base + OPERATOR_OFFSETS[outCh][1];
|
||||||
|
_CaptureRegWrite(outAddr, opl->_ReadReg(inAddr));
|
||||||
|
// other operator settings
|
||||||
|
for (base = 0x20; base <= 0x80; base += 0x20) {
|
||||||
|
inAddr = base + op1Off;
|
||||||
|
outAddr = base + OPERATOR_OFFSETS[outCh][0];
|
||||||
|
_CaptureRegWrite(outAddr, opl->_ReadReg(inAddr));
|
||||||
|
inAddr = base + op2Off;
|
||||||
|
outAddr = base + OPERATOR_OFFSETS[outCh][1];
|
||||||
|
_CaptureRegWrite(outAddr, opl->_ReadReg(inAddr));
|
||||||
|
}
|
||||||
|
|
||||||
|
// channel wide settings
|
||||||
|
int chInOff = opl->_GetOffset(ch);
|
||||||
|
inAddr = 0xc0 + chInOff;
|
||||||
|
outAddr = 0xc0 + CHANNEL_OFFSETS[outCh];
|
||||||
|
_CaptureRegWrite(outAddr, 0x30 | opl->_ReadReg(inAddr));
|
||||||
|
// note frequency
|
||||||
|
inAddr = 0xa0 + chInOff;
|
||||||
|
outAddr = 0xa0 + CHANNEL_OFFSETS[outCh];
|
||||||
|
_CaptureRegWrite(outAddr, opl->_ReadReg(inAddr));
|
||||||
|
// note-on
|
||||||
|
inAddr = 0xb0 + chInOff;
|
||||||
|
outAddr = 0xb0 + CHANNEL_OFFSETS[outCh];
|
||||||
|
_CaptureRegWrite(outAddr, opl->_ReadReg(inAddr));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DROMultiplexer::TwoOpMelodicNoteOff(Hiopl* opl, int ch) {
|
void DROMultiplexer::TwoOpMelodicNoteOff(Hiopl* opl, int ch) {
|
||||||
|
int chOff = opl->_GetOffset(ch);
|
||||||
|
OplCh_t key;
|
||||||
|
key.opl = opl;
|
||||||
|
key.ch = ch;
|
||||||
|
|
||||||
|
int outCh = channelMap[key];
|
||||||
|
// note-off
|
||||||
|
Bit32u inAddr = 0xb0 + chOff;
|
||||||
|
Bit32u outAddr = 0xb0 + CHANNEL_OFFSETS[outCh];
|
||||||
|
_CaptureRegWriteWithDelay(outAddr, opl->_ReadReg(inAddr));
|
||||||
|
}
|
||||||
|
|
||||||
|
int DROMultiplexer::_FindFreeChannel() {
|
||||||
|
int i = 0;
|
||||||
|
while (i < MELODIC_CHANNELS) {
|
||||||
|
Hiopl* opl = channels[i].opl;
|
||||||
|
if (NULL == opl || !opl->IsActive(channels[i].ch)) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
// TODO: handle the fact that there are no free channels :(
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DROMultiplexer::PercussionHit(Hiopl* opl) {
|
void DROMultiplexer::PercussionHit(Hiopl* opl) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void DROMultiplexer::InitCaptureVariables() {
|
void DROMultiplexer::InitCaptureVariables() {
|
||||||
captureHandle = NULL;
|
captureHandle = NULL;
|
||||||
captureLengthBytes = 0;
|
captureLengthBytes = 0;
|
||||||
|
@ -107,6 +215,7 @@ void DROMultiplexer::StopCapture() {
|
||||||
fclose(captureHandle);
|
fclose(captureHandle);
|
||||||
}
|
}
|
||||||
InitCaptureVariables();
|
InitCaptureVariables();
|
||||||
|
DROMultiplexer::master = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DROMultiplexer::_CaptureDelay(Bit16u delayMs) {
|
void DROMultiplexer::_CaptureDelay(Bit16u delayMs) {
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include <map>
|
||||||
#include "hiopl.h"
|
#include "hiopl.h"
|
||||||
|
|
||||||
class DROMultiplexer
|
class DROMultiplexer
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
static const int MELODIC_CHANNELS = 15;
|
||||||
|
|
||||||
DROMultiplexer();
|
DROMultiplexer();
|
||||||
~DROMultiplexer();
|
~DROMultiplexer();
|
||||||
|
|
||||||
|
@ -16,18 +19,34 @@ public:
|
||||||
bool IsAnotherInstanceRecording();
|
bool IsAnotherInstanceRecording();
|
||||||
void StartCapture(const char* filepath, Hiopl* opl);
|
void StartCapture(const char* filepath, Hiopl* opl);
|
||||||
void StopCapture();
|
void StopCapture();
|
||||||
|
static DROMultiplexer* GetMaster();
|
||||||
|
|
||||||
//private:
|
private:
|
||||||
void _CaptureDelay(Bit16u delayMs);
|
void _CaptureDelay(Bit16u delayMs);
|
||||||
void _CaptureRegWriteWithDelay(Bit32u reg, Bit8u value);
|
void _CaptureRegWriteWithDelay(Bit32u reg, Bit8u value);
|
||||||
void _CaptureRegWrite(Bit32u reg, Bit8u value);
|
void _CaptureRegWrite(Bit32u reg, Bit8u value);
|
||||||
void _CaptureOpl3Enable();
|
void _CaptureOpl3Enable();
|
||||||
|
int _FindFreeChannel();
|
||||||
static DROMultiplexer* master;
|
static DROMultiplexer* master;
|
||||||
|
|
||||||
FILE* captureHandle;
|
FILE* captureHandle;
|
||||||
Bit64s captureStart;
|
Bit64s captureStart;
|
||||||
Bit64s lastWrite;
|
Bit64s lastWrite;
|
||||||
Bit32u captureLengthBytes;
|
Bit32u captureLengthBytes;
|
||||||
|
|
||||||
|
typedef struct oplch {
|
||||||
|
Hiopl* opl;
|
||||||
|
int ch;
|
||||||
|
bool operator<(const oplch &o) const {
|
||||||
|
return opl < o.opl ||
|
||||||
|
(opl == o.opl && ch < o.ch);
|
||||||
|
};
|
||||||
|
bool operator==(const oplch &o) const {
|
||||||
|
return opl == o.opl && ch == o.ch;
|
||||||
|
};
|
||||||
|
} OplCh_t;
|
||||||
|
|
||||||
|
OplCh_t channels[MELODIC_CHANNELS];
|
||||||
|
std::map<OplCh_t, int> channelMap;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -6,11 +6,6 @@
|
||||||
|
|
||||||
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)
|
||||||
|
@ -21,7 +16,6 @@ JuceOplvstiAudioProcessor::JuceOplvstiAudioProcessor()
|
||||||
Opl->SetSampleRate(44100);
|
Opl->SetSampleRate(44100);
|
||||||
Opl->EnableWaveformControl();
|
Opl->EnableWaveformControl();
|
||||||
dro = new DROMultiplexer();
|
dro = new DROMultiplexer();
|
||||||
plexer = dro;
|
|
||||||
|
|
||||||
recordingFile = NULL;
|
recordingFile = NULL;
|
||||||
|
|
||||||
|
@ -159,7 +153,6 @@ bool JuceOplvstiAudioProcessor::isAnyInstanceRecording() {
|
||||||
void JuceOplvstiAudioProcessor::startRecording(File *outputFile) {
|
void JuceOplvstiAudioProcessor::startRecording(File *outputFile) {
|
||||||
recordingFile = outputFile;
|
recordingFile = outputFile;
|
||||||
dro->StartCapture(outputFile->getFullPathName().toUTF8(), Opl);
|
dro->StartCapture(outputFile->getFullPathName().toUTF8(), Opl);
|
||||||
Opl->regWriteCallback = ®WriteCallback;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void JuceOplvstiAudioProcessor::stopRecording() {
|
void JuceOplvstiAudioProcessor::stopRecording() {
|
||||||
|
@ -776,6 +769,9 @@ void JuceOplvstiAudioProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuf
|
||||||
Opl->KeyOn(ch, noteHz);
|
Opl->KeyOn(ch, noteHz);
|
||||||
active_notes[ch] = n;
|
active_notes[ch] = n;
|
||||||
applyPitchBend();
|
applyPitchBend();
|
||||||
|
if (isAnyInstanceRecording()) {
|
||||||
|
dro->GetMaster()->TwoOpMelodicNoteOn(Opl, ch);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (midi_message.isNoteOff()) {
|
else if (midi_message.isNoteOff()) {
|
||||||
|
@ -804,6 +800,9 @@ void JuceOplvstiAudioProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuf
|
||||||
Opl->KeyOff(ch);
|
Opl->KeyOff(ch);
|
||||||
active_notes[ch] = NO_NOTE;
|
active_notes[ch] = NO_NOTE;
|
||||||
}
|
}
|
||||||
|
if (isAnyInstanceRecording()) {
|
||||||
|
dro->GetMaster()->TwoOpMelodicNoteOff(Opl, ch);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (midi_message.isPitchWheel()) {
|
else if (midi_message.isPitchWheel()) {
|
||||||
|
|
|
@ -76,8 +76,6 @@ 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);
|
|
||||||
if (NULL != regWriteCallback) regWriteCallback(reg, value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Bit8u Hiopl::_ReadReg(Bit32u reg) {
|
Bit8u Hiopl::_ReadReg(Bit32u reg) {
|
||||||
|
@ -172,7 +170,6 @@ 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) {
|
||||||
|
@ -193,14 +190,15 @@ void Hiopl::KeyOff(int ch) {
|
||||||
_ClearRegBits(0xb0+offset, 0x20);
|
_ClearRegBits(0xb0+offset, 0x20);
|
||||||
}
|
}
|
||||||
|
|
||||||
//void Hiopl::EnvelopeOffCallback(int ch) {
|
bool Hiopl::IsActive(int ch) {
|
||||||
//
|
// check carrier envelope state
|
||||||
//}
|
return DBOPL::Operator::State::OFF != adlib->chip.chan[ch - 1].op[1].state;
|
||||||
|
}
|
||||||
|
|
||||||
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);
|
||||||
// ZDoom emulator seems to be tuned down by two semitones for some reason.
|
// ZDoom emulator seems to be tuned down by two semitones for some reason. Sample rate difference?
|
||||||
if (ZDOOM == emulator) {
|
if (ZDOOM == emulator) {
|
||||||
frqHz *= 1.122461363636364f;
|
frqHz *= 1.122461363636364f;
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,12 +25,8 @@ 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);
|
||||||
|
@ -62,10 +58,16 @@ class Hiopl {
|
||||||
void SetEnvelopeDecay(int ch, int osc, int t);
|
void SetEnvelopeDecay(int ch, int osc, int t);
|
||||||
void SetEnvelopeSustain(int ch, int osc, int level);
|
void SetEnvelopeSustain(int ch, int osc, int level);
|
||||||
void SetEnvelopeRelease(int ch, int osc, int t);
|
void SetEnvelopeRelease(int ch, int osc, int t);
|
||||||
|
// Get register address offset for operator settings for the specified channel and operator.
|
||||||
|
int _GetOffset(int ch, int osc);
|
||||||
|
// Get register address offset for channel-wide register settings for the specified channel.
|
||||||
|
int _GetOffset(int ch);
|
||||||
|
|
||||||
void SetModulatorFeedback(int ch, int level);
|
void SetModulatorFeedback(int ch, int level);
|
||||||
void KeyOn(int ch, float frqHz);
|
void KeyOn(int ch, float frqHz);
|
||||||
void KeyOff(int ch);
|
void KeyOff(int ch);
|
||||||
|
// Return false if no note is active on the channel (ie release is complete)
|
||||||
|
bool IsActive(int ch);
|
||||||
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);
|
||||||
|
@ -75,12 +77,10 @@ class Hiopl {
|
||||||
~Hiopl();
|
~Hiopl();
|
||||||
private:
|
private:
|
||||||
Emulator emulator;
|
Emulator emulator;
|
||||||
Adlib::Handler *adlib;
|
DBOPL::Handler *adlib;
|
||||||
OPLEmul *zdoom;
|
OPLEmul *zdoom;
|
||||||
Bit8u regCache[256];
|
Bit8u regCache[256];
|
||||||
bool _CheckParams(int ch, int osc);
|
bool _CheckParams(int ch, int osc);
|
||||||
int _GetOffset(int ch, int osc);
|
|
||||||
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();
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue