From a16025e39c10914b0ff8317c1abbd69f01bef80c Mon Sep 17 00:00:00 2001 From: Bruce Sutherland Date: Tue, 6 Jan 2015 22:25:51 +0900 Subject: [PATCH] DRO output from multiple plugins. Channel mapping needs testing and debugging. --- Source/DROMultiplexer.cpp | 113 ++++++++++++++++++++++++++++++++++++- Source/DROMultiplexer.h | 23 +++++++- Source/PluginProcessor.cpp | 13 ++--- Source/hiopl.cpp | 12 ++-- Source/hiopl.h | 16 +++--- 5 files changed, 151 insertions(+), 26 deletions(-) diff --git a/Source/DROMultiplexer.cpp b/Source/DROMultiplexer.cpp index 50f1c56..30bce3a 100644 --- a/Source/DROMultiplexer.cpp +++ b/Source/DROMultiplexer.cpp @@ -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) { diff --git a/Source/DROMultiplexer.h b/Source/DROMultiplexer.h index 4d1181e..7d5bed4 100644 --- a/Source/DROMultiplexer.h +++ b/Source/DROMultiplexer.h @@ -1,9 +1,12 @@ #pragma once +#include #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 channelMap; }; diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index ebaeb5f..73fda29 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -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()) { diff --git a/Source/hiopl.cpp b/Source/hiopl.cpp index 2d23dc7..aacb61a 100644 --- a/Source/hiopl.cpp +++ b/Source/hiopl.cpp @@ -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; } diff --git a/Source/hiopl.h b/Source/hiopl.h index 7e098bb..407ee2a 100644 --- a/Source/hiopl.h +++ b/Source/hiopl.h @@ -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();