aboutsummaryrefslogtreecommitdiff
path: root/audio/softsynth
diff options
context:
space:
mode:
Diffstat (limited to 'audio/softsynth')
-rw-r--r--audio/softsynth/mt32/AReverbModel.cpp252
-rw-r--r--audio/softsynth/mt32/AReverbModel.h57
-rw-r--r--audio/softsynth/mt32/BReverbModel.cpp395
-rw-r--r--audio/softsynth/mt32/BReverbModel.h112
-rw-r--r--audio/softsynth/mt32/DelayReverb.cpp47
-rw-r--r--audio/softsynth/mt32/DelayReverb.h2
-rw-r--r--audio/softsynth/mt32/Synth.cpp30
-rw-r--r--audio/softsynth/mt32/Synth.h9
-rw-r--r--audio/softsynth/mt32/module.mk1
-rw-r--r--audio/softsynth/mt32/mt32emu.h7
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 &currentSettings;
+ 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 &currentSettings;
+ 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
{