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
|
||||
};
|
||||
|
||||
// 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) {
|
||||
off[0] = (Bit8u)(val);
|
||||
off[1] = (Bit8u)(val >> 8);
|
||||
|
@ -32,26 +70,96 @@ INLINE void host_writed(Bit8u *off, Bit32u val) {
|
|||
|
||||
DROMultiplexer::DROMultiplexer()
|
||||
{
|
||||
for (int i = 0; i < MELODIC_CHANNELS; ++i) {
|
||||
channels[i].opl = NULL;
|
||||
channels[i].ch = -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
DROMultiplexer::~DROMultiplexer()
|
||||
{
|
||||
}
|
||||
|
||||
DROMultiplexer* DROMultiplexer::GetMaster() {
|
||||
return DROMultiplexer::master;
|
||||
}
|
||||
|
||||
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) {
|
||||
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::InitCaptureVariables() {
|
||||
captureHandle = NULL;
|
||||
captureLengthBytes = 0;
|
||||
|
@ -107,6 +215,7 @@ void DROMultiplexer::StopCapture() {
|
|||
fclose(captureHandle);
|
||||
}
|
||||
InitCaptureVariables();
|
||||
DROMultiplexer::master = NULL;
|
||||
}
|
||||
|
||||
void DROMultiplexer::_CaptureDelay(Bit16u delayMs) {
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
#pragma once
|
||||
#include <map>
|
||||
#include "hiopl.h"
|
||||
|
||||
class DROMultiplexer
|
||||
{
|
||||
public:
|
||||
static const int MELODIC_CHANNELS = 15;
|
||||
|
||||
DROMultiplexer();
|
||||
~DROMultiplexer();
|
||||
|
||||
|
@ -16,18 +19,34 @@ public:
|
|||
bool IsAnotherInstanceRecording();
|
||||
void StartCapture(const char* filepath, Hiopl* opl);
|
||||
void StopCapture();
|
||||
static DROMultiplexer* GetMaster();
|
||||
|
||||
//private:
|
||||
private:
|
||||
void _CaptureDelay(Bit16u delayMs);
|
||||
void _CaptureRegWriteWithDelay(Bit32u reg, Bit8u value);
|
||||
void _CaptureRegWrite(Bit32u reg, Bit8u value);
|
||||
void _CaptureOpl3Enable();
|
||||
|
||||
int _FindFreeChannel();
|
||||
static DROMultiplexer* master;
|
||||
|
||||
FILE* captureHandle;
|
||||
Bit64s captureStart;
|
||||
Bit64s lastWrite;
|
||||
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";
|
||||
|
||||
static DROMultiplexer* plexer = NULL;
|
||||
void regWriteCallback(Bit32u reg, Bit8u val) {
|
||||
if (NULL != plexer) plexer->_CaptureRegWriteWithDelay(reg, val);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JuceOplvstiAudioProcessor::JuceOplvstiAudioProcessor()
|
||||
: i_program(-1)
|
||||
|
@ -21,7 +16,6 @@ JuceOplvstiAudioProcessor::JuceOplvstiAudioProcessor()
|
|||
Opl->SetSampleRate(44100);
|
||||
Opl->EnableWaveformControl();
|
||||
dro = new DROMultiplexer();
|
||||
plexer = dro;
|
||||
|
||||
recordingFile = NULL;
|
||||
|
||||
|
@ -159,7 +153,6 @@ bool JuceOplvstiAudioProcessor::isAnyInstanceRecording() {
|
|||
void JuceOplvstiAudioProcessor::startRecording(File *outputFile) {
|
||||
recordingFile = outputFile;
|
||||
dro->StartCapture(outputFile->getFullPathName().toUTF8(), Opl);
|
||||
Opl->regWriteCallback = ®WriteCallback;
|
||||
}
|
||||
|
||||
void JuceOplvstiAudioProcessor::stopRecording() {
|
||||
|
@ -776,6 +769,9 @@ void JuceOplvstiAudioProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuf
|
|||
Opl->KeyOn(ch, noteHz);
|
||||
active_notes[ch] = n;
|
||||
applyPitchBend();
|
||||
if (isAnyInstanceRecording()) {
|
||||
dro->GetMaster()->TwoOpMelodicNoteOn(Opl, ch);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (midi_message.isNoteOff()) {
|
||||
|
@ -804,6 +800,9 @@ void JuceOplvstiAudioProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuf
|
|||
Opl->KeyOff(ch);
|
||||
active_notes[ch] = NO_NOTE;
|
||||
}
|
||||
if (isAnyInstanceRecording()) {
|
||||
dro->GetMaster()->TwoOpMelodicNoteOff(Opl, ch);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (midi_message.isPitchWheel()) {
|
||||
|
|
|
@ -76,8 +76,6 @@ void Hiopl::_WriteReg(Bit32u reg, Bit8u value, Bit8u mask) {
|
|||
zdoom->WriteReg(reg, value);
|
||||
//}
|
||||
regCache[reg] = value;
|
||||
//_CaptureRegWriteWithDelay(reg, value);
|
||||
if (NULL != regWriteCallback) regWriteCallback(reg, value);
|
||||
}
|
||||
|
||||
Bit8u Hiopl::_ReadReg(Bit32u reg) {
|
||||
|
@ -172,7 +170,6 @@ 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) {
|
||||
|
@ -193,14 +190,15 @@ void Hiopl::KeyOff(int ch) {
|
|||
_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) {
|
||||
unsigned int fnum, block;
|
||||
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) {
|
||||
frqHz *= 1.122461363636364f;
|
||||
}
|
||||
|
|
|
@ -25,12 +25,8 @@ 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);
|
||||
|
@ -62,10 +58,16 @@ class Hiopl {
|
|||
void SetEnvelopeDecay(int ch, int osc, int t);
|
||||
void SetEnvelopeSustain(int ch, int osc, int level);
|
||||
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 KeyOn(int ch, float frqHz);
|
||||
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 _WriteReg(Bit32u reg, Bit8u value, Bit8u mask=0x0);
|
||||
void _ClearRegBits(Bit32u reg, Bit8u mask);
|
||||
|
@ -75,12 +77,10 @@ class Hiopl {
|
|||
~Hiopl();
|
||||
private:
|
||||
Emulator emulator;
|
||||
Adlib::Handler *adlib;
|
||||
DBOPL::Handler *adlib;
|
||||
OPLEmul *zdoom;
|
||||
Bit8u regCache[256];
|
||||
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 _ClearRegisters();
|
||||
|
||||
|
|
Loading…
Reference in a new issue