diff options
Diffstat (limited to 'audio/softsynth')
-rw-r--r-- | audio/softsynth/mt32/AReverbModel.cpp | 252 | ||||
-rw-r--r-- | audio/softsynth/mt32/AReverbModel.h | 57 | ||||
-rw-r--r-- | audio/softsynth/mt32/BReverbModel.cpp | 395 | ||||
-rw-r--r-- | audio/softsynth/mt32/BReverbModel.h | 112 | ||||
-rw-r--r-- | audio/softsynth/mt32/DelayReverb.cpp | 47 | ||||
-rw-r--r-- | audio/softsynth/mt32/DelayReverb.h | 2 | ||||
-rw-r--r-- | audio/softsynth/mt32/Synth.cpp | 30 | ||||
-rw-r--r-- | audio/softsynth/mt32/Synth.h | 9 | ||||
-rw-r--r-- | audio/softsynth/mt32/module.mk | 1 | ||||
-rw-r--r-- | audio/softsynth/mt32/mt32emu.h | 7 |
10 files changed, 735 insertions, 177 deletions
diff --git a/audio/softsynth/mt32/AReverbModel.cpp b/audio/softsynth/mt32/AReverbModel.cpp index 151f6c2c81..ec24394e71 100644 --- a/audio/softsynth/mt32/AReverbModel.cpp +++ b/audio/softsynth/mt32/AReverbModel.cpp @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011, 2012 Dean Beeler, Jerome Fisher, Sergey V. Mikayev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -16,64 +16,97 @@ */ #include "mt32emu.h" -#include "AReverbModel.h" - -namespace MT32Emu { - -// Default reverb settings for modes 0-2 -static const unsigned int NUM_ALLPASSES = 6; -static const unsigned int NUM_DELAYS = 5; +#if MT32EMU_USE_REVERBMODEL == 1 -static const Bit32u MODE_0_ALLPASSES[] = {729, 78, 394, 994, 1250, 1889}; -static const Bit32u MODE_0_DELAYS[] = {846, 4, 1819, 778, 346}; -static const float MODE_0_TIMES[] = {0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.9f}; -static const float MODE_0_LEVELS[] = {0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 1.01575f}; - -static const Bit32u MODE_1_ALLPASSES[] = {176, 809, 1324, 1258}; -static const Bit32u MODE_1_DELAYS[] = {2262, 124, 974, 2516, 356}; -static const float MODE_1_TIMES[] = {0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.95f}; -static const float MODE_1_LEVELS[] = {0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 1.01575f}; +#include "AReverbModel.h" -static const Bit32u MODE_2_ALLPASSES[] = {78, 729, 994, 389}; -static const Bit32u MODE_2_DELAYS[] = {846, 4, 1819, 778, 346}; -static const float MODE_2_TIMES[] = {0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f}; -static const float MODE_2_LEVELS[] = {0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f}; +// Analysing of state of reverb RAM address lines gives exact sizes of the buffers of filters used. This also indicates that +// the reverb model implemented in the real devices consists of three series allpass filters preceded by a non-feedback comb (or a delay with a LPF) +// and followed by three parallel comb filters -const AReverbSettings AReverbModel::REVERB_MODE_0_SETTINGS = {MODE_0_ALLPASSES, MODE_0_DELAYS, MODE_0_TIMES, MODE_0_LEVELS, 0.687770909f, 0.5f, 0.5f}; -const AReverbSettings AReverbModel::REVERB_MODE_1_SETTINGS = {MODE_1_ALLPASSES, MODE_1_DELAYS, MODE_1_TIMES, MODE_1_LEVELS, 0.712025098f, 0.375f, 0.625f}; -const AReverbSettings AReverbModel::REVERB_MODE_2_SETTINGS = {MODE_2_ALLPASSES, MODE_2_DELAYS, MODE_2_TIMES, MODE_2_LEVELS, 0.939522749f, 0.0f, 0.0f}; +namespace MT32Emu { -RingBuffer::RingBuffer(Bit32u newsize) { - index = 0; - size = newsize; +// Because LA-32 chip makes it's output available to process by the Boss chip with a significant delay, +// the Boss chip puts to the buffer the LA32 dry output when it is ready and performs processing of the _previously_ latched data. +// Of course, the right way would be to use a dedicated variable for this, but our reverb model is way higher level, +// so we can simply increase the input buffer size. +static const Bit32u PROCESS_DELAY = 1; + +// Default reverb settings for modes 0-2. These correspond to CM-32L / LAPC-I "new" reverb settings. MT-32 reverb is a bit different. +// Found by tracing reverb RAM data lines (thanks go to Lord_Nightmare & balrog). + +static const Bit32u NUM_ALLPASSES = 3; +static const Bit32u NUM_COMBS = 4; // Well, actually there are 3 comb filters, but the entrance LPF + delay can be perfectly processed via a comb here. + +static const Bit32u MODE_0_ALLPASSES[] = {994, 729, 78}; +static const Bit32u MODE_0_COMBS[] = {705 + PROCESS_DELAY, 2349, 2839, 3632}; +static const Bit32u MODE_0_OUTL[] = {2349, 141, 1960}; +static const Bit32u MODE_0_OUTR[] = {1174, 1570, 145}; +static const Bit32u MODE_0_COMB_FACTOR[] = {0x3C, 0x60, 0x60, 0x60}; +static const Bit32u MODE_0_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98, + 0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98, + 0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98}; +static const Bit32u MODE_0_LEVELS[] = {0, 10*3, 10*5, 10*7, 11*9, 11*12, 11*15, 13*15}; +static const Bit32u MODE_0_LPF_AMP = 6; + +static const Bit32u MODE_1_ALLPASSES[] = {1324, 809, 176}; +static const Bit32u MODE_1_COMBS[] = {961 + PROCESS_DELAY, 2619, 3545, 4519}; +static const Bit32u MODE_1_OUTL[] = {2618, 1760, 4518}; +static const Bit32u MODE_1_OUTR[] = {1300, 3532, 2274}; +static const Bit32u MODE_1_COMB_FACTOR[] = {0x30, 0x60, 0x60, 0x60}; +static const Bit32u MODE_1_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x28, 0x48, 0x60, 0x70, 0x78, 0x80, 0x90, 0x98, + 0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98, + 0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98}; +static const Bit32u MODE_1_LEVELS[] = {0, 10*3, 11*5, 11*7, 11*9, 11*12, 11*15, 14*15}; +static const Bit32u MODE_1_LPF_AMP = 6; + +static const Bit32u MODE_2_ALLPASSES[] = {969, 644, 157}; +static const Bit32u MODE_2_COMBS[] = {116 + PROCESS_DELAY, 2259, 2839, 3539}; +static const Bit32u MODE_2_OUTL[] = {2259, 718, 1769}; +static const Bit32u MODE_2_OUTR[] = {1136, 2128, 1}; +static const Bit32u MODE_2_COMB_FACTOR[] = {0, 0x20, 0x20, 0x20}; +static const Bit32u MODE_2_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x30, 0x58, 0x78, 0x88, 0xA0, 0xB8, 0xC0, 0xD0, + 0x30, 0x58, 0x78, 0x88, 0xA0, 0xB8, 0xC0, 0xD0, + 0x30, 0x58, 0x78, 0x88, 0xA0, 0xB8, 0xC0, 0xD0}; +static const Bit32u MODE_2_LEVELS[] = {0, 10*3, 11*5, 11*7, 11*9, 11*12, 12*15, 14*15}; +static const Bit32u MODE_2_LPF_AMP = 8; + +static const AReverbSettings REVERB_MODE_0_SETTINGS = {MODE_0_ALLPASSES, MODE_0_COMBS, MODE_0_OUTL, MODE_0_OUTR, MODE_0_COMB_FACTOR, MODE_0_COMB_FEEDBACK, MODE_0_LEVELS, MODE_0_LPF_AMP}; +static const AReverbSettings REVERB_MODE_1_SETTINGS = {MODE_1_ALLPASSES, MODE_1_COMBS, MODE_1_OUTL, MODE_1_OUTR, MODE_1_COMB_FACTOR, MODE_1_COMB_FEEDBACK, MODE_1_LEVELS, MODE_1_LPF_AMP}; +static const AReverbSettings REVERB_MODE_2_SETTINGS = {MODE_2_ALLPASSES, MODE_2_COMBS, MODE_2_OUTL, MODE_2_OUTR, MODE_2_COMB_FACTOR, MODE_2_COMB_FEEDBACK, MODE_2_LEVELS, MODE_2_LPF_AMP}; + +static const AReverbSettings * const REVERB_SETTINGS[] = {&REVERB_MODE_0_SETTINGS, &REVERB_MODE_1_SETTINGS, &REVERB_MODE_2_SETTINGS, &REVERB_MODE_0_SETTINGS}; + +RingBuffer::RingBuffer(const Bit32u newsize) : size(newsize), index(0) { buffer = new float[size]; } RingBuffer::~RingBuffer() { delete[] buffer; buffer = NULL; - size = 0; } float RingBuffer::next() { - index++; - if (index >= size) { + if (++index >= size) { index = 0; } return buffer[index]; } -bool RingBuffer::isEmpty() { +bool RingBuffer::isEmpty() const { if (buffer == NULL) return true; float *buf = buffer; - float total = 0; + float max = 0.001f; for (Bit32u i = 0; i < size; i++) { - total += (*buf < 0 ? -*buf : *buf); + if ((*buf < -max) || (*buf > max)) return false; buf++; } - return ((total / size) < .0002 ? true : false); + return true; } void RingBuffer::mute() { @@ -83,44 +116,51 @@ void RingBuffer::mute() { } } -AllpassFilter::AllpassFilter(Bit32u useSize) : RingBuffer(useSize) { -} - -Delay::Delay(Bit32u useSize) : RingBuffer(useSize) { -} +AllpassFilter::AllpassFilter(const Bit32u useSize) : RingBuffer(useSize) {} -float AllpassFilter::process(float in) { - // This model corresponds to the allpass filter implementation in the real CM-32L device +float AllpassFilter::process(const float in) { + // This model corresponds to the allpass filter implementation of the real CM-32L device // found from sample analysis - float out; - - out = next(); + const float bufferOut = next(); // store input - feedback / 2 - buffer[index] = in - 0.5f * out; + buffer[index] = in - 0.5f * bufferOut; // return buffer output + feedforward / 2 - return out + 0.5f * buffer[index]; + return bufferOut + 0.5f * buffer[index]; } -float Delay::process(float in) { - // Implements a very simple delay +CombFilter::CombFilter(const Bit32u useSize) : RingBuffer(useSize) {} - float out; +void CombFilter::process(const float in) { + // This model corresponds to the comb filter implementation of the real CM-32L device + // found from sample analysis - out = next(); + // the previously stored value + float last = buffer[index]; - // store input - buffer[index] = in; + // prepare input + feedback + float filterIn = in + next() * feedbackFactor; - // return buffer output - return out; + // store input + feedback processed by a low-pass filter + buffer[index] = filterFactor * last - filterIn; } -AReverbModel::AReverbModel(const AReverbSettings *useSettings) : allpasses(NULL), delays(NULL), currentSettings(useSettings) { +float CombFilter::getOutputAt(const Bit32u outIndex) const { + return buffer[(size + index - outIndex) % size]; } +void CombFilter::setFeedbackFactor(const float useFeedbackFactor) { + feedbackFactor = useFeedbackFactor; +} + +void CombFilter::setFilterFactor(const float useFilterFactor) { + filterFactor = useFilterFactor; +} + +AReverbModel::AReverbModel(const ReverbMode mode) : allpasses(NULL), combs(NULL), currentSettings(*REVERB_SETTINGS[mode]) {} + AReverbModel::~AReverbModel() { close(); } @@ -130,12 +170,14 @@ void AReverbModel::open(unsigned int /*sampleRate*/) { // IIR filter values depend on sample rate as well allpasses = new AllpassFilter*[NUM_ALLPASSES]; for (Bit32u i = 0; i < NUM_ALLPASSES; i++) { - allpasses[i] = new AllpassFilter(currentSettings->allpassSizes[i]); + allpasses[i] = new AllpassFilter(currentSettings.allpassSizes[i]); } - delays = new Delay*[NUM_DELAYS]; - for (Bit32u i = 0; i < NUM_DELAYS; i++) { - delays[i] = new Delay(currentSettings->delaySizes[i]); + combs = new CombFilter*[NUM_COMBS]; + for (Bit32u i = 0; i < NUM_COMBS; i++) { + combs[i] = new CombFilter(currentSettings.combSizes[i]); + combs[i]->setFilterFactor(currentSettings.filterFactor[i] / 256.0f); } + lpfAmp = currentSettings.lpfAmp / 16.0f; mute(); } @@ -150,84 +192,78 @@ void AReverbModel::close() { delete[] allpasses; allpasses = NULL; } - if (delays != NULL) { - for (Bit32u i = 0; i < NUM_DELAYS; i++) { - if (delays[i] != NULL) { - delete delays[i]; - delays[i] = NULL; + if (combs != NULL) { + for (Bit32u i = 0; i < NUM_COMBS; i++) { + if (combs[i] != NULL) { + delete combs[i]; + combs[i] = NULL; } } - delete[] delays; - delays = NULL; + delete[] combs; + combs = NULL; } } void AReverbModel::mute() { + if (allpasses == NULL || combs == NULL) return; for (Bit32u i = 0; i < NUM_ALLPASSES; i++) { allpasses[i]->mute(); } - for (Bit32u i = 0; i < NUM_DELAYS; i++) { - delays[i]->mute(); + for (Bit32u i = 0; i < NUM_COMBS; i++) { + combs[i]->mute(); } - filterhist1 = 0; - filterhist2 = 0; - combhist = 0; } void AReverbModel::setParameters(Bit8u time, Bit8u level) { // FIXME: wetLevel definitely needs ramping when changed // Although, most games don't set reverb level during MIDI playback - decayTime = currentSettings->decayTimes[time]; - wetLevel = currentSettings->wetLevels[level]; + if (combs == NULL) return; + for (Bit32u i = 0; i < NUM_COMBS; i++) { + combs[i]->setFeedbackFactor(currentSettings.decayTimes[(i << 3) + (time & 7)] / 256.0f); + } + wetLevel = 0.5f * lpfAmp * currentSettings.wetLevels[(level & 7)] / 256.0f; } bool AReverbModel::isActive() const { - bool bActive = false; for (Bit32u i = 0; i < NUM_ALLPASSES; i++) { - bActive |= !allpasses[i]->isEmpty(); + if (!allpasses[i]->isEmpty()) return true; } - for (Bit32u i = 0; i < NUM_DELAYS; i++) { - bActive |= !delays[i]->isEmpty(); + for (Bit32u i = 0; i < NUM_COMBS; i++) { + if (!combs[i]->isEmpty()) return true; } - return bActive; + return false; } void AReverbModel::process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples) { -// Three series allpass filters followed by a delay, fourth allpass filter and another delay - float dry, link, outL1, outL2, outR1, outR2; + float dry, link, outL1; for (unsigned long i = 0; i < numSamples; i++) { - dry = *inLeft + *inRight; + dry = wetLevel * (*inLeft + *inRight); - // Implementation of 2-stage IIR single-pole low-pass filter - // found at the entrance of reverb processing on real devices - filterhist1 += (dry - filterhist1) * currentSettings->filtVal; - filterhist2 += (filterhist1 - filterhist2) * currentSettings->filtVal; + // Get the last stored sample before processing in order not to loose it + link = combs[0]->getOutputAt(currentSettings.combSizes[0] - 1); - link = allpasses[0]->process(-filterhist2); - link = allpasses[1]->process(link); + combs[0]->process(-dry); - // this implements a comb filter cross-linked with the fourth allpass filter - link += combhist * decayTime; + link = allpasses[0]->process(link); + link = allpasses[1]->process(link); link = allpasses[2]->process(link); - link = delays[0]->process(link); - outL1 = link; - link = allpasses[3]->process(link); - link = delays[1]->process(link); - outR1 = link; - link = allpasses[4]->process(link); - link = delays[2]->process(link); - outL2 = link; - link = allpasses[5]->process(link); - link = delays[3]->process(link); - outR2 = link; - link = delays[4]->process(link); - - // comb filter end point - combhist = combhist * currentSettings->damp1 + link * currentSettings->damp2; - - *outLeft = (outL1 + outL2) * wetLevel; - *outRight = (outR1 + outR2) * wetLevel; + + // If the output position is equal to the comb size, get it now in order not to loose it + outL1 = 1.5f * combs[1]->getOutputAt(currentSettings.outLPositions[0] - 1); + + combs[1]->process(link); + combs[2]->process(link); + combs[3]->process(link); + + link = outL1 + 1.5f * combs[2]->getOutputAt(currentSettings.outLPositions[1]); + link += combs[3]->getOutputAt(currentSettings.outLPositions[2]); + *outLeft = link; + + link = 1.5f * combs[1]->getOutputAt(currentSettings.outRPositions[0]); + link += 1.5f * combs[2]->getOutputAt(currentSettings.outRPositions[1]); + link += combs[3]->getOutputAt(currentSettings.outRPositions[2]); + *outRight = link; inLeft++; inRight++; @@ -237,3 +273,5 @@ void AReverbModel::process(const float *inLeft, const float *inRight, float *out } } + +#endif diff --git a/audio/softsynth/mt32/AReverbModel.h b/audio/softsynth/mt32/AReverbModel.h index 3fae08c34c..d70e9ee14f 100644 --- a/audio/softsynth/mt32/AReverbModel.h +++ b/audio/softsynth/mt32/AReverbModel.h @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011, 2012 Dean Beeler, Jerome Fisher, Sergey V. Mikayev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -21,66 +21,67 @@ namespace MT32Emu { struct AReverbSettings { - const Bit32u *allpassSizes; - const Bit32u *delaySizes; - const float *decayTimes; - const float *wetLevels; - float filtVal; - float damp1; - float damp2; + const Bit32u * const allpassSizes; + const Bit32u * const combSizes; + const Bit32u * const outLPositions; + const Bit32u * const outRPositions; + const Bit32u * const filterFactor; + const Bit32u * const decayTimes; + const Bit32u * const wetLevels; + const Bit32u lpfAmp; }; class RingBuffer { protected: float *buffer; - Bit32u size; + const Bit32u size; Bit32u index; + public: - RingBuffer(Bit32u size); + RingBuffer(const Bit32u size); virtual ~RingBuffer(); float next(); - bool isEmpty(); + bool isEmpty() const; void mute(); }; class AllpassFilter : public RingBuffer { public: - AllpassFilter(Bit32u size); - float process(float in); + AllpassFilter(const Bit32u size); + float process(const float in); }; -class Delay : public RingBuffer { +class CombFilter : public RingBuffer { + float feedbackFactor; + float filterFactor; + public: - Delay(Bit32u size); - float process(float in); + CombFilter(const Bit32u size); + void process(const float in); + float getOutputAt(const Bit32u outIndex) const; + void setFeedbackFactor(const float useFeedbackFactor); + void setFilterFactor(const float useFilterFactor); }; class AReverbModel : public ReverbModel { AllpassFilter **allpasses; - Delay **delays; + CombFilter **combs; - const AReverbSettings *currentSettings; - float decayTime; + const AReverbSettings ¤tSettings; + float lpfAmp; float wetLevel; - float filterhist1, filterhist2; - float combhist; void mute(); + public: - AReverbModel(const AReverbSettings *newSettings); + AReverbModel(const ReverbMode mode); ~AReverbModel(); void open(unsigned int sampleRate); void close(); void setParameters(Bit8u time, Bit8u level); void process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples); bool isActive() const; - - static const AReverbSettings REVERB_MODE_0_SETTINGS; - static const AReverbSettings REVERB_MODE_1_SETTINGS; - static const AReverbSettings REVERB_MODE_2_SETTINGS; }; -// Default reverb settings for modes 0-2 - } #endif diff --git a/audio/softsynth/mt32/BReverbModel.cpp b/audio/softsynth/mt32/BReverbModel.cpp new file mode 100644 index 0000000000..b5f4467c20 --- /dev/null +++ b/audio/softsynth/mt32/BReverbModel.cpp @@ -0,0 +1,395 @@ +/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher + * Copyright (C) 2011, 2012 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "mt32emu.h" + +#if MT32EMU_USE_REVERBMODEL == 2 + +#include "BReverbModel.h" + +// Analysing of state of reverb RAM address lines gives exact sizes of the buffers of filters used. This also indicates that +// the reverb model implemented in the real devices consists of three series allpass filters preceded by a non-feedback comb (or a delay with a LPF) +// and followed by three parallel comb filters + +namespace MT32Emu { + +// Because LA-32 chip makes it's output available to process by the Boss chip with a significant delay, +// the Boss chip puts to the buffer the LA32 dry output when it is ready and performs processing of the _previously_ latched data. +// Of course, the right way would be to use a dedicated variable for this, but our reverb model is way higher level, +// so we can simply increase the input buffer size. +static const Bit32u PROCESS_DELAY = 1; + +static const Bit32u MODE_3_ADDITIONAL_DELAY = 1; +static const Bit32u MODE_3_FEEDBACK_DELAY = 1; + +// Default reverb settings for modes 0-2. These correspond to CM-32L / LAPC-I "new" reverb settings. MT-32 reverb is a bit different. +// Found by tracing reverb RAM data lines (thanks go to Lord_Nightmare & balrog). + +static const Bit32u MODE_0_NUMBER_OF_ALLPASSES = 3; +static const Bit32u MODE_0_ALLPASSES[] = {994, 729, 78}; +static const Bit32u MODE_0_NUMBER_OF_COMBS = 4; // Well, actually there are 3 comb filters, but the entrance LPF + delay can be processed via a hacked comb. +static const Bit32u MODE_0_COMBS[] = {705 + PROCESS_DELAY, 2349, 2839, 3632}; +static const Bit32u MODE_0_OUTL[] = {2349, 141, 1960}; +static const Bit32u MODE_0_OUTR[] = {1174, 1570, 145}; +static const Bit32u MODE_0_COMB_FACTOR[] = {0xA0, 0x60, 0x60, 0x60}; +static const Bit32u MODE_0_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98, + 0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98, + 0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98}; +static const Bit32u MODE_0_DRY_AMP[] = {0xA0, 0xA0, 0xA0, 0xA0, 0xB0, 0xB0, 0xB0, 0xD0}; +static const Bit32u MODE_0_WET_AMP[] = {0x10, 0x30, 0x50, 0x70, 0x90, 0xC0, 0xF0, 0xF0}; +static const Bit32u MODE_0_LPF_AMP = 0x60; + +static const Bit32u MODE_1_NUMBER_OF_ALLPASSES = 3; +static const Bit32u MODE_1_ALLPASSES[] = {1324, 809, 176}; +static const Bit32u MODE_1_NUMBER_OF_COMBS = 4; // Same as for mode 0 above +static const Bit32u MODE_1_COMBS[] = {961 + PROCESS_DELAY, 2619, 3545, 4519}; +static const Bit32u MODE_1_OUTL[] = {2618, 1760, 4518}; +static const Bit32u MODE_1_OUTR[] = {1300, 3532, 2274}; +static const Bit32u MODE_1_COMB_FACTOR[] = {0x80, 0x60, 0x60, 0x60}; +static const Bit32u MODE_1_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x28, 0x48, 0x60, 0x70, 0x78, 0x80, 0x90, 0x98, + 0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98, + 0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98}; +static const Bit32u MODE_1_DRY_AMP[] = {0xA0, 0xA0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xE0}; +static const Bit32u MODE_1_WET_AMP[] = {0x10, 0x30, 0x50, 0x70, 0x90, 0xC0, 0xF0, 0xF0}; +static const Bit32u MODE_1_LPF_AMP = 0x60; + +static const Bit32u MODE_2_NUMBER_OF_ALLPASSES = 3; +static const Bit32u MODE_2_ALLPASSES[] = {969, 644, 157}; +static const Bit32u MODE_2_NUMBER_OF_COMBS = 4; // Same as for mode 0 above +static const Bit32u MODE_2_COMBS[] = {116 + PROCESS_DELAY, 2259, 2839, 3539}; +static const Bit32u MODE_2_OUTL[] = {2259, 718, 1769}; +static const Bit32u MODE_2_OUTR[] = {1136, 2128, 1}; +static const Bit32u MODE_2_COMB_FACTOR[] = {0, 0x20, 0x20, 0x20}; +static const Bit32u MODE_2_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x30, 0x58, 0x78, 0x88, 0xA0, 0xB8, 0xC0, 0xD0, + 0x30, 0x58, 0x78, 0x88, 0xA0, 0xB8, 0xC0, 0xD0, + 0x30, 0x58, 0x78, 0x88, 0xA0, 0xB8, 0xC0, 0xD0}; +static const Bit32u MODE_2_DRY_AMP[] = {0xA0, 0xA0, 0xB0, 0xB0, 0xB0, 0xB0, 0xC0, 0xE0}; +static const Bit32u MODE_2_WET_AMP[] = {0x10, 0x30, 0x50, 0x70, 0x90, 0xC0, 0xF0, 0xF0}; +static const Bit32u MODE_2_LPF_AMP = 0x80; + +static const Bit32u MODE_3_NUMBER_OF_ALLPASSES = 0; +static const Bit32u MODE_3_NUMBER_OF_COMBS = 1; +static const Bit32u MODE_3_DELAY[] = {16000 + MODE_3_FEEDBACK_DELAY + PROCESS_DELAY + MODE_3_ADDITIONAL_DELAY}; +static const Bit32u MODE_3_OUTL[] = {400, 624, 960, 1488, 2256, 3472, 5280, 8000}; +static const Bit32u MODE_3_OUTR[] = {800, 1248, 1920, 2976, 4512, 6944, 10560, 16000}; +static const Bit32u MODE_3_COMB_FACTOR[] = {0x68}; +static const Bit32u MODE_3_COMB_FEEDBACK[] = {0x68, 0x60}; +static const Bit32u MODE_3_DRY_AMP[] = {0x20, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50}; +static const Bit32u MODE_3_WET_AMP[] = {0x18, 0x18, 0x28, 0x40, 0x60, 0x80, 0xA8, 0xF8}; + +static const BReverbSettings REVERB_MODE_0_SETTINGS = {MODE_0_NUMBER_OF_ALLPASSES, MODE_0_ALLPASSES, MODE_0_NUMBER_OF_COMBS, MODE_0_COMBS, MODE_0_OUTL, MODE_0_OUTR, MODE_0_COMB_FACTOR, MODE_0_COMB_FEEDBACK, MODE_0_DRY_AMP, MODE_0_WET_AMP, MODE_0_LPF_AMP}; +static const BReverbSettings REVERB_MODE_1_SETTINGS = {MODE_1_NUMBER_OF_ALLPASSES, MODE_1_ALLPASSES, MODE_1_NUMBER_OF_COMBS, MODE_1_COMBS, MODE_1_OUTL, MODE_1_OUTR, MODE_1_COMB_FACTOR, MODE_1_COMB_FEEDBACK, MODE_1_DRY_AMP, MODE_1_WET_AMP, MODE_1_LPF_AMP}; +static const BReverbSettings REVERB_MODE_2_SETTINGS = {MODE_2_NUMBER_OF_ALLPASSES, MODE_2_ALLPASSES, MODE_2_NUMBER_OF_COMBS, MODE_2_COMBS, MODE_2_OUTL, MODE_2_OUTR, MODE_2_COMB_FACTOR, MODE_2_COMB_FEEDBACK, MODE_2_DRY_AMP, MODE_2_WET_AMP, MODE_2_LPF_AMP}; +static const BReverbSettings REVERB_MODE_3_SETTINGS = {MODE_3_NUMBER_OF_ALLPASSES, NULL, MODE_3_NUMBER_OF_COMBS, MODE_3_DELAY, MODE_3_OUTL, MODE_3_OUTR, MODE_3_COMB_FACTOR, MODE_3_COMB_FEEDBACK, MODE_3_DRY_AMP, MODE_3_WET_AMP, 0}; + +static const BReverbSettings * const REVERB_SETTINGS[] = {&REVERB_MODE_0_SETTINGS, &REVERB_MODE_1_SETTINGS, &REVERB_MODE_2_SETTINGS, &REVERB_MODE_3_SETTINGS}; + +// This algorithm tries to emulate exactly Boss multiplication operation (at least this is what we see on reverb RAM data lines). +// Also LA32 is suspected to use the similar one to perform PCM interpolation and ring modulation. +static Bit32s weirdMul(Bit32s a, Bit8u addMask, Bit8u carryMask) { + Bit8u mask = 0x80; + Bit32s res = 0; + for (int i = 0; i < 8; i++) { + Bit32s carry = (a < 0) && (mask & carryMask) > 0 ? a & 1 : 0; + a >>= 1; + res += (mask & addMask) > 0 ? a + carry : 0; + mask >>= 1; + } + return res; +} + +RingBuffer::RingBuffer(Bit32u newsize) : size(newsize), index(0) { + buffer = new Bit16s[size]; +} + +RingBuffer::~RingBuffer() { + delete[] buffer; + buffer = NULL; +} + +Bit32s RingBuffer::next() { + if (++index >= size) { + index = 0; + } + return buffer[index]; +} + +bool RingBuffer::isEmpty() const { + if (buffer == NULL) return true; + + Bit16s *buf = buffer; + for (Bit32u i = 0; i < size; i++) { + if (*buf < -8 || *buf > 8) return false; + buf++; + } + return true; +} + +void RingBuffer::mute() { + Bit16s *buf = buffer; + for (Bit32u i = 0; i < size; i++) { + *buf++ = 0; + } +} + +AllpassFilter::AllpassFilter(const Bit32u useSize) : RingBuffer(useSize) {} + +Bit32s AllpassFilter::process(const Bit32s in) { + // This model corresponds to the allpass filter implementation of the real CM-32L device + // found from sample analysis + + Bit16s bufferOut = next(); + + // store input - feedback / 2 + buffer[index] = in - (bufferOut >> 1); + + // return buffer output + feedforward / 2 + return bufferOut + (buffer[index] >> 1); +} + +CombFilter::CombFilter(const Bit32u useSize, const Bit32u useFilterFactor) : RingBuffer(useSize), filterFactor(useFilterFactor) {} + +void CombFilter::process(const Bit32s in) { + // This model corresponds to the comb filter implementation of the real CM-32L device + + // the previously stored value + Bit32s last = buffer[index]; + + // prepare input + feedback + Bit32s filterIn = in + weirdMul(next(), feedbackFactor, 0xF0 /* Maybe 0x80 ? */); + + // store input + feedback processed by a low-pass filter + buffer[index] = weirdMul(last, filterFactor, 0x40) - filterIn; +} + +Bit32s CombFilter::getOutputAt(const Bit32u outIndex) const { + return buffer[(size + index - outIndex) % size]; +} + +void CombFilter::setFeedbackFactor(const Bit32u useFeedbackFactor) { + feedbackFactor = useFeedbackFactor; +} + +DelayWithLowPassFilter::DelayWithLowPassFilter(const Bit32u useSize, const Bit32u useFilterFactor, const Bit32u useAmp) + : CombFilter(useSize, useFilterFactor), amp(useAmp) {} + +void DelayWithLowPassFilter::process(const Bit32s in) { + // the previously stored value + Bit32s last = buffer[index]; + + // move to the next index + next(); + + // low-pass filter process + Bit32s lpfOut = weirdMul(last, filterFactor, 0xFF) + in; + + // store lpfOut multiplied by LPF amp factor + buffer[index] = weirdMul(lpfOut, amp, 0xFF); +} + +TapDelayCombFilter::TapDelayCombFilter(const Bit32u useSize, const Bit32u useFilterFactor) : CombFilter(useSize, useFilterFactor) {} + +void TapDelayCombFilter::process(const Bit32s in) { + // the previously stored value + Bit32s last = buffer[index]; + + // move to the next index + next(); + + // prepare input + feedback + // Actually, the size of the filter varies with the TIME parameter, the feedback sample is taken from the position just below the right output + Bit32s filterIn = in + weirdMul(getOutputAt(outR + MODE_3_FEEDBACK_DELAY), feedbackFactor, 0xF0); + + // store input + feedback processed by a low-pass filter + buffer[index] = weirdMul(last, filterFactor, 0xF0) - filterIn; +} + +Bit32s TapDelayCombFilter::getLeftOutput() const { + return getOutputAt(outL + PROCESS_DELAY + MODE_3_ADDITIONAL_DELAY); +} + +Bit32s TapDelayCombFilter::getRightOutput() const { + return getOutputAt(outR + PROCESS_DELAY + MODE_3_ADDITIONAL_DELAY); +} + +void TapDelayCombFilter::setOutputPositions(const Bit32u useOutL, const Bit32u useOutR) { + outL = useOutL; + outR = useOutR; +} + +BReverbModel::BReverbModel(const ReverbMode mode) + : allpasses(NULL), combs(NULL), currentSettings(*REVERB_SETTINGS[mode]), tapDelayMode(mode == REVERB_MODE_TAP_DELAY) {} + +BReverbModel::~BReverbModel() { + close(); +} + +void BReverbModel::open(unsigned int /*sampleRate*/) { + // FIXME: filter sizes must be multiplied by sample rate to 32000Hz ratio + // IIR filter values depend on sample rate as well + if (currentSettings.numberOfAllpasses > 0) { + allpasses = new AllpassFilter*[currentSettings.numberOfAllpasses]; + for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) { + allpasses[i] = new AllpassFilter(currentSettings.allpassSizes[i]); + } + } + combs = new CombFilter*[currentSettings.numberOfCombs]; + if (tapDelayMode) { + *combs = new TapDelayCombFilter(*currentSettings.combSizes, *currentSettings.filterFactors); + } else { + combs[0] = new DelayWithLowPassFilter(currentSettings.combSizes[0], currentSettings.filterFactors[0], currentSettings.lpfAmp); + for (Bit32u i = 1; i < currentSettings.numberOfCombs; i++) { + combs[i] = new CombFilter(currentSettings.combSizes[i], currentSettings.filterFactors[i]); + } + } + mute(); +} + +void BReverbModel::close() { + if (allpasses != NULL) { + for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) { + if (allpasses[i] != NULL) { + delete allpasses[i]; + allpasses[i] = NULL; + } + } + delete[] allpasses; + allpasses = NULL; + } + if (combs != NULL) { + for (Bit32u i = 0; i < currentSettings.numberOfCombs; i++) { + if (combs[i] != NULL) { + delete combs[i]; + combs[i] = NULL; + } + } + delete[] combs; + combs = NULL; + } +} + +void BReverbModel::mute() { + if (allpasses != NULL) { + for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) { + allpasses[i]->mute(); + } + } + if (combs != NULL) { + for (Bit32u i = 0; i < currentSettings.numberOfCombs; i++) { + combs[i]->mute(); + } + } +} + +void BReverbModel::setParameters(Bit8u time, Bit8u level) { + if (combs == NULL) return; + level &= 7; + time &= 7; + if (tapDelayMode) { + TapDelayCombFilter *comb = static_cast<TapDelayCombFilter *> (*combs); + comb->setOutputPositions(currentSettings.outLPositions[time], currentSettings.outRPositions[time & 7]); + comb->setFeedbackFactor(currentSettings.feedbackFactors[((level < 3) || (time < 6)) ? 0 : 1]); + } else { + for (Bit32u i = 0; i < currentSettings.numberOfCombs; i++) { + combs[i]->setFeedbackFactor(currentSettings.feedbackFactors[(i << 3) + time]); + } + } + if (time == 0 && level == 0) { + dryAmp = wetLevel = 0; + } else { + dryAmp = currentSettings.dryAmps[level]; + wetLevel = currentSettings.wetLevels[level]; + } +} + +bool BReverbModel::isActive() const { + for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) { + if (!allpasses[i]->isEmpty()) return true; + } + for (Bit32u i = 0; i < currentSettings.numberOfCombs; i++) { + if (!combs[i]->isEmpty()) return true; + } + return false; +} + +void BReverbModel::process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples) { + Bit32s dry, link, outL1, outR1; + + for (unsigned long i = 0; i < numSamples; i++) { + if (tapDelayMode) { + dry = Bit32s(*inLeft * 8192.0f) + Bit32s(*inRight * 8192.0f); + } else { + dry = Bit32s(*inLeft * 8192.0f) / 2 + Bit32s(*inRight * 8192.0f) / 2; + } + + // Looks like dryAmp doesn't change in MT-32 but it does in CM-32L / LAPC-I + dry = weirdMul(dry, dryAmp, 0xFF); + + if (tapDelayMode) { + TapDelayCombFilter *comb = static_cast<TapDelayCombFilter *> (*combs); + comb->process(dry); + *outLeft = weirdMul(comb->getLeftOutput(), wetLevel, 0xFF) / 8192.0f; + *outRight = weirdMul(comb->getRightOutput(), wetLevel, 0xFF) / 8192.0f; + } else { + // Get the last stored sample before processing in order not to loose it + link = combs[0]->getOutputAt(currentSettings.combSizes[0] - 1); + + // Entrance LPF. Note, comb.process() differs a bit here. + combs[0]->process(dry); + + // This introduces reverb noise which actually makes output from the real Boss chip nondeterministic + link = link - 1; + link = allpasses[0]->process(link); + link = allpasses[1]->process(link); + link = allpasses[2]->process(link); + + // If the output position is equal to the comb size, get it now in order not to loose it + outL1 = combs[1]->getOutputAt(currentSettings.outLPositions[0] - 1); + outL1 += outL1 >> 1; + + combs[1]->process(link); + combs[2]->process(link); + combs[3]->process(link); + + link = combs[2]->getOutputAt(currentSettings.outLPositions[1]); + link += link >> 1; + link += outL1; + link += combs[3]->getOutputAt(currentSettings.outLPositions[2]); + *outLeft = weirdMul(link, wetLevel, 0xFF) / 8192.0f; + + outR1 = combs[1]->getOutputAt(currentSettings.outRPositions[0]); + outR1 += outR1 >> 1; + link = combs[2]->getOutputAt(currentSettings.outRPositions[1]); + link += link >> 1; + link += outR1; + link += combs[3]->getOutputAt(currentSettings.outRPositions[2]); + *outRight = weirdMul(link, wetLevel, 0xFF) / 8192.0f; + } + + inLeft++; + inRight++; + outLeft++; + outRight++; + } +} + +} + +#endif diff --git a/audio/softsynth/mt32/BReverbModel.h b/audio/softsynth/mt32/BReverbModel.h new file mode 100644 index 0000000000..7096ae04ae --- /dev/null +++ b/audio/softsynth/mt32/BReverbModel.h @@ -0,0 +1,112 @@ +/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher + * Copyright (C) 2011, 2012 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef MT32EMU_B_REVERB_MODEL_H +#define MT32EMU_B_REVERB_MODEL_H + +namespace MT32Emu { + +struct BReverbSettings { + const Bit32u numberOfAllpasses; + const Bit32u * const allpassSizes; + const Bit32u numberOfCombs; + const Bit32u * const combSizes; + const Bit32u * const outLPositions; + const Bit32u * const outRPositions; + const Bit32u * const filterFactors; + const Bit32u * const feedbackFactors; + const Bit32u * const dryAmps; + const Bit32u * const wetLevels; + const Bit32u lpfAmp; +}; + +class RingBuffer { +protected: + Bit16s *buffer; + const Bit32u size; + Bit32u index; + +public: + RingBuffer(const Bit32u size); + virtual ~RingBuffer(); + Bit32s next(); + bool isEmpty() const; + void mute(); +}; + +class AllpassFilter : public RingBuffer { +public: + AllpassFilter(const Bit32u size); + Bit32s process(const Bit32s in); +}; + +class CombFilter : public RingBuffer { +protected: + const Bit32u filterFactor; + Bit32u feedbackFactor; + +public: + CombFilter(const Bit32u size, const Bit32u useFilterFactor); + virtual void process(const Bit32s in); // Actually, no need to make it virtual, but for sure + Bit32s getOutputAt(const Bit32u outIndex) const; + void setFeedbackFactor(const Bit32u useFeedbackFactor); +}; + +class DelayWithLowPassFilter : public CombFilter { + Bit32u amp; + +public: + DelayWithLowPassFilter(const Bit32u useSize, const Bit32u useFilterFactor, const Bit32u useAmp); + void process(const Bit32s in); + void setFeedbackFactor(const Bit32u) {} +}; + +class TapDelayCombFilter : public CombFilter { + Bit32u outL; + Bit32u outR; + +public: + TapDelayCombFilter(const Bit32u useSize, const Bit32u useFilterFactor); + void process(const Bit32s in); + Bit32s getLeftOutput() const; + Bit32s getRightOutput() const; + void setOutputPositions(const Bit32u useOutL, const Bit32u useOutR); +}; + +class BReverbModel : public ReverbModel { + AllpassFilter **allpasses; + CombFilter **combs; + + const BReverbSettings ¤tSettings; + const bool tapDelayMode; + Bit32u dryAmp; + Bit32u wetLevel; + void mute(); + +public: + BReverbModel(const ReverbMode mode); + ~BReverbModel(); + void open(unsigned int sampleRate); + void close(); + void setParameters(Bit8u time, Bit8u level); + void process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples); + bool isActive() const; +}; + +} + +#endif diff --git a/audio/softsynth/mt32/DelayReverb.cpp b/audio/softsynth/mt32/DelayReverb.cpp index 23d25a596e..77521b4a01 100644 --- a/audio/softsynth/mt32/DelayReverb.cpp +++ b/audio/softsynth/mt32/DelayReverb.cpp @@ -22,7 +22,7 @@ namespace MT32Emu { -// CONFIRMED: The values below are found via analysis of digital samples. Checked with all time and level combinations. +// CONFIRMED: The values below are found via analysis of digital samples and tracing reverb RAM address / data lines. Checked with all time and level combinations. // Obviously: // rightDelay = (leftDelay - 2) * 2 + 2 // echoDelay = rightDelay - 1 @@ -39,10 +39,11 @@ static const Bit32u REVERB_TIMINGS[8][3]= { {8002, 16002, 16001} }; -static const float REVERB_FADE[8] = {0.0f, -0.049400051f, -0.08220577f, -0.131861118f, -0.197344907f, -0.262956344f, -0.345162114f, -0.509508615f}; -const float REVERB_FEEDBACK67 = -0.629960524947437f; // = -EXP2F(-2 / 3) -const float REVERB_FEEDBACK = -0.682034520443118f; // = -EXP2F(-53 / 96) -const float LPF_VALUE = 0.594603558f; // = EXP2F(-0.75f) +// Reverb amp is found as dryAmp * wetAmp +static const Bit32u REVERB_AMP[8] = {0x20*0x18, 0x50*0x18, 0x50*0x28, 0x50*0x40, 0x50*0x60, 0x50*0x80, 0x50*0xA8, 0x50*0xF8}; +static const Bit32u REVERB_FEEDBACK67 = 0x60; +static const Bit32u REVERB_FEEDBACK = 0x68; +static const float LPF_VALUE = 0x68 / 256.0f; DelayReverb::DelayReverb() { buf = NULL; @@ -97,20 +98,18 @@ void DelayReverb::recalcParameters() { // Number of samples between a response and that response feeding back/echoing delayFeedback = REVERB_TIMINGS[time][2] * sampleRate / 32000; - if (time < 6) { - feedback = REVERB_FEEDBACK; + if (level < 3 || time < 6) { + feedback = REVERB_FEEDBACK / 256.0f; } else { - feedback = REVERB_FEEDBACK67; + feedback = REVERB_FEEDBACK67 / 256.0f; } - // Fading speed, i.e. amplitude ratio of neighbor responses - fade = REVERB_FADE[level]; + // Overall output amp + amp = (level == 0 && time == 0) ? 0.0f : REVERB_AMP[level] / 65536.0f; } void DelayReverb::process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples) { - if (buf == NULL) { - return; - } + if (buf == NULL) return; for (unsigned int sampleIx = 0; sampleIx < numSamples; sampleIx++) { // The ring buffer write index moves backwards; reads are all done with positive offsets. @@ -120,10 +119,10 @@ void DelayReverb::process(const float *inLeft, const float *inRight, float *outL Bit32u bufIxFeedback = (bufIx + delayFeedback) % bufSize; // Attenuated input samples and feedback response are directly added to the current ring buffer location - float sample = fade * (inLeft[sampleIx] + inRight[sampleIx]) + feedback * buf[bufIxFeedback]; + float lpfIn = amp * (inLeft[sampleIx] + inRight[sampleIx]) + feedback * buf[bufIxFeedback]; // Single-pole IIR filter found on real devices - buf[bufIx] = buf[bufIxPrev] + (sample - buf[bufIxPrev]) * LPF_VALUE; + buf[bufIx] = buf[bufIxPrev] * LPF_VALUE - lpfIn; outLeft[sampleIx] = buf[bufIxLeft]; outRight[sampleIx] = buf[bufIxRight]; @@ -133,17 +132,13 @@ void DelayReverb::process(const float *inLeft, const float *inRight, float *outL } bool DelayReverb::isActive() const { - // Quick hack: Return true iff all samples in the left buffer are the same and - // all samples in the right buffers are the same (within the sample output threshold). - if (buf == NULL) { - return false; - } - float last = buf[0] * 8192.0f; - for (unsigned int i = 1; i < bufSize; i++) { - float s = (buf[i] * 8192.0f); - if (fabs(s - last) > 1.0f) { - return true; - } + if (buf == NULL) return false; + + float *b = buf; + float max = 0.001f; + for (Bit32u i = 0; i < bufSize; i++) { + if ((*b < -max) || (*b > max)) return true; + b++; } return false; } diff --git a/audio/softsynth/mt32/DelayReverb.h b/audio/softsynth/mt32/DelayReverb.h index 7c030fb839..e967ddc49c 100644 --- a/audio/softsynth/mt32/DelayReverb.h +++ b/audio/softsynth/mt32/DelayReverb.h @@ -35,7 +35,7 @@ private: Bit32u delayRight; Bit32u delayFeedback; - float fade; + float amp; float feedback; void recalcParameters(); diff --git a/audio/softsynth/mt32/Synth.cpp b/audio/softsynth/mt32/Synth.cpp index f37d76cd85..9b5518b207 100644 --- a/audio/softsynth/mt32/Synth.cpp +++ b/audio/softsynth/mt32/Synth.cpp @@ -27,8 +27,10 @@ #include "mmath.h" #include "PartialManager.h" -#if MT32EMU_USE_AREVERBMODEL == 1 +#if MT32EMU_USE_REVERBMODEL == 1 #include "AReverbModel.h" +#elif MT32EMU_USE_REVERBMODEL == 2 +#include "BReverbModel.h" #else #include "FreeverbModel.h" #endif @@ -145,17 +147,23 @@ Synth::Synth() { reverbEnabled = true; reverbOverridden = false; -#if MT32EMU_USE_AREVERBMODEL == 1 - reverbModels[0] = new AReverbModel(&AReverbModel::REVERB_MODE_0_SETTINGS); - reverbModels[1] = new AReverbModel(&AReverbModel::REVERB_MODE_1_SETTINGS); - reverbModels[2] = new AReverbModel(&AReverbModel::REVERB_MODE_2_SETTINGS); +#if MT32EMU_USE_REVERBMODEL == 1 + reverbModels[REVERB_MODE_ROOM] = new AReverbModel(REVERB_MODE_ROOM); + reverbModels[REVERB_MODE_HALL] = new AReverbModel(REVERB_MODE_HALL); + reverbModels[REVERB_MODE_PLATE] = new AReverbModel(REVERB_MODE_PLATE); + reverbModels[REVERB_MODE_TAP_DELAY] = new DelayReverb(); +#elif MT32EMU_USE_REVERBMODEL == 2 + reverbModels[REVERB_MODE_ROOM] = new BReverbModel(REVERB_MODE_ROOM); + reverbModels[REVERB_MODE_HALL] = new BReverbModel(REVERB_MODE_HALL); + reverbModels[REVERB_MODE_PLATE] = new BReverbModel(REVERB_MODE_PLATE); + reverbModels[REVERB_MODE_TAP_DELAY] = new BReverbModel(REVERB_MODE_TAP_DELAY); #else - reverbModels[0] = new FreeverbModel(0.76f, 0.687770909f, 0.63f, 0, 0.5f); - reverbModels[1] = new FreeverbModel(2.0f, 0.712025098f, 0.86f, 1, 0.5f); - reverbModels[2] = new FreeverbModel(0.4f, 0.939522749f, 0.38f, 2, 0.05f); + reverbModels[REVERB_MODE_ROOM] = new FreeverbModel(0.76f, 0.687770909f, 0.63f, 0, 0.5f); + reverbModels[REVERB_MODE_HALL] = new FreeverbModel(2.0f, 0.712025098f, 0.86f, 1, 0.5f); + reverbModels[REVERB_MODE_PLATE] = new FreeverbModel(0.4f, 0.939522749f, 0.38f, 2, 0.05f); + reverbModels[REVERB_MODE_TAP_DELAY] = new DelayReverb(); #endif - reverbModels[3] = new DelayReverb(); reverbModel = NULL; setDACInputMode(DACInputMode_NICE); setOutputGain(1.0f); @@ -315,7 +323,7 @@ LoadResult Synth::loadPCMROM(const char *filename) { return LoadResult_Unreadable; } LoadResult rc = LoadResult_OK; - for (int i = 0; i < pcmROMSize; i++) { + for (unsigned int i = 0; i < pcmROMSize; i++) { Bit8u s = file->readByte(); Bit8u c = file->readByte(); @@ -350,7 +358,7 @@ LoadResult Synth::loadPCMROM(const char *filename) { bool Synth::initPCMList(Bit16u mapAddress, Bit16u count) { ControlROMPCMStruct *tps = (ControlROMPCMStruct *)&controlROMData[mapAddress]; for (int i = 0; i < count; i++) { - int rAddr = tps[i].pos * 0x800; + unsigned int rAddr = tps[i].pos * 0x800; int rLenExp = (tps[i].len & 0x70) >> 4; int rLen = 0x800 << rLenExp; if (rAddr + rLen > pcmROMSize) { diff --git a/audio/softsynth/mt32/Synth.h b/audio/softsynth/mt32/Synth.h index a30df62911..2534b7af35 100644 --- a/audio/softsynth/mt32/Synth.h +++ b/audio/softsynth/mt32/Synth.h @@ -179,6 +179,13 @@ enum MemoryRegionType { MR_PatchTemp, MR_RhythmTemp, MR_TimbreTemp, MR_Patches, MR_Timbres, MR_System, MR_Display, MR_Reset }; +enum ReverbMode { + REVERB_MODE_ROOM, + REVERB_MODE_HALL, + REVERB_MODE_PLATE, + REVERB_MODE_TAP_DELAY +}; + class MemoryRegion { private: Synth *synth; @@ -315,7 +322,7 @@ private: const ControlROMMap *controlROMMap; Bit8u controlROMData[CONTROL_ROM_SIZE]; float *pcmROMData; - int pcmROMSize; // This is in 16-bit samples, therefore half the number of bytes in the ROM + unsigned int pcmROMSize; // This is in 16-bit samples, therefore half the number of bytes in the ROM Bit8s chantable[32]; diff --git a/audio/softsynth/mt32/module.mk b/audio/softsynth/mt32/module.mk index 995e450076..c0ee363c04 100644 --- a/audio/softsynth/mt32/module.mk +++ b/audio/softsynth/mt32/module.mk @@ -2,6 +2,7 @@ MODULE := audio/softsynth/mt32 MODULE_OBJS := \ AReverbModel.o \ + BReverbModel.o \ DelayReverb.o \ FreeverbModel.o \ LA32Ramp.o \ diff --git a/audio/softsynth/mt32/mt32emu.h b/audio/softsynth/mt32/mt32emu.h index f10bc1faab..a5a72b01ca 100644 --- a/audio/softsynth/mt32/mt32emu.h +++ b/audio/softsynth/mt32/mt32emu.h @@ -84,9 +84,10 @@ // If zero, keeps reverb buffers for all modes around all the time to avoid allocating/freeing in the critical path. #define MT32EMU_REDUCE_REVERB_MEMORY 1 -// 0: Use standard Freeverb -// 1: Use AReverb (currently not properly tuned) -#define MT32EMU_USE_AREVERBMODEL 0 +// 0: Use legacy Freeverb +// 1: Use Accurate Reverb model aka AReverb +// 2: Use Bit-perfect Boss Reverb model aka BReverb (for developers, not much practical use) +#define MT32EMU_USE_REVERBMODEL 1 namespace MT32Emu { |