DOSBox-X
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator
src/mt32/BReverbModel.cpp
00001 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
00002  * Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
00003  *
00004  *  This program is free software: you can redistribute it and/or modify
00005  *  it under the terms of the GNU Lesser General Public License as published by
00006  *  the Free Software Foundation, either version 2.1 of the License, or
00007  *  (at your option) any later version.
00008  *
00009  *  This program is distributed in the hope that it will be useful,
00010  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00011  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012  *  GNU Lesser General Public License for more details.
00013  *
00014  *  You should have received a copy of the GNU Lesser General Public License
00015  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
00016  */
00017 
00018 #include "mt32emu.h"
00019 
00020 #if MT32EMU_USE_REVERBMODEL == 2
00021 
00022 #include "BReverbModel.h"
00023 
00024 // Analysing of state of reverb RAM address lines gives exact sizes of the buffers of filters used. This also indicates that
00025 // 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)
00026 // and followed by three parallel comb filters
00027 
00028 namespace MT32Emu {
00029 
00030 // Because LA-32 chip makes it's output available to process by the Boss chip with a significant delay,
00031 // the Boss chip puts to the buffer the LA32 dry output when it is ready and performs processing of the _previously_ latched data.
00032 // Of course, the right way would be to use a dedicated variable for this, but our reverb model is way higher level,
00033 // so we can simply increase the input buffer size.
00034 static const Bit32u PROCESS_DELAY = 1;
00035 
00036 static const Bit32u MODE_3_ADDITIONAL_DELAY = 1;
00037 static const Bit32u MODE_3_FEEDBACK_DELAY = 1;
00038 
00039 // Default reverb settings for modes 0-2. These correspond to CM-32L / LAPC-I "new" reverb settings. MT-32 reverb is a bit different.
00040 // Found by tracing reverb RAM data lines (thanks go to Lord_Nightmare & balrog).
00041 
00042 static const Bit32u MODE_0_NUMBER_OF_ALLPASSES = 3;
00043 static const Bit32u MODE_0_ALLPASSES[] = {994, 729, 78};
00044 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.
00045 static const Bit32u MODE_0_COMBS[] = {705 + PROCESS_DELAY, 2349, 2839, 3632};
00046 static const Bit32u MODE_0_OUTL[] = {2349, 141, 1960};
00047 static const Bit32u MODE_0_OUTR[] = {1174, 1570, 145};
00048 static const Bit32u MODE_0_COMB_FACTOR[] = {0xA0, 0x60, 0x60, 0x60};
00049 static const Bit32u MODE_0_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
00050                                               0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98,
00051                                               0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98,
00052                                               0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98};
00053 static const Bit32u MODE_0_DRY_AMP[] = {0xA0, 0xA0, 0xA0, 0xA0, 0xB0, 0xB0, 0xB0, 0xD0};
00054 static const Bit32u MODE_0_WET_AMP[] = {0x10, 0x30, 0x50, 0x70, 0x90, 0xC0, 0xF0, 0xF0};
00055 static const Bit32u MODE_0_LPF_AMP = 0x60;
00056 
00057 static const Bit32u MODE_1_NUMBER_OF_ALLPASSES = 3;
00058 static const Bit32u MODE_1_ALLPASSES[] = {1324, 809, 176};
00059 static const Bit32u MODE_1_NUMBER_OF_COMBS = 4; // Same as for mode 0 above
00060 static const Bit32u MODE_1_COMBS[] = {961 + PROCESS_DELAY, 2619, 3545, 4519};
00061 static const Bit32u MODE_1_OUTL[] = {2618, 1760, 4518};
00062 static const Bit32u MODE_1_OUTR[] = {1300, 3532, 2274};
00063 static const Bit32u MODE_1_COMB_FACTOR[] = {0x80, 0x60, 0x60, 0x60};
00064 static const Bit32u MODE_1_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
00065                                                                                           0x28, 0x48, 0x60, 0x70, 0x78, 0x80, 0x90, 0x98,
00066                                                                                           0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98,
00067                                                                                           0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98};
00068 static const Bit32u MODE_1_DRY_AMP[] = {0xA0, 0xA0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xE0};
00069 static const Bit32u MODE_1_WET_AMP[] = {0x10, 0x30, 0x50, 0x70, 0x90, 0xC0, 0xF0, 0xF0};
00070 static const Bit32u MODE_1_LPF_AMP = 0x60;
00071 
00072 static const Bit32u MODE_2_NUMBER_OF_ALLPASSES = 3;
00073 static const Bit32u MODE_2_ALLPASSES[] = {969, 644, 157};
00074 static const Bit32u MODE_2_NUMBER_OF_COMBS = 4; // Same as for mode 0 above
00075 static const Bit32u MODE_2_COMBS[] = {116 + PROCESS_DELAY, 2259, 2839, 3539};
00076 static const Bit32u MODE_2_OUTL[] = {2259, 718, 1769};
00077 static const Bit32u MODE_2_OUTR[] = {1136, 2128, 1};
00078 static const Bit32u MODE_2_COMB_FACTOR[] = {0, 0x20, 0x20, 0x20};
00079 static const Bit32u MODE_2_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
00080                                               0x30, 0x58, 0x78, 0x88, 0xA0, 0xB8, 0xC0, 0xD0,
00081                                               0x30, 0x58, 0x78, 0x88, 0xA0, 0xB8, 0xC0, 0xD0,
00082                                               0x30, 0x58, 0x78, 0x88, 0xA0, 0xB8, 0xC0, 0xD0};
00083 static const Bit32u MODE_2_DRY_AMP[] = {0xA0, 0xA0, 0xB0, 0xB0, 0xB0, 0xB0, 0xC0, 0xE0};
00084 static const Bit32u MODE_2_WET_AMP[] = {0x10, 0x30, 0x50, 0x70, 0x90, 0xC0, 0xF0, 0xF0};
00085 static const Bit32u MODE_2_LPF_AMP = 0x80;
00086 
00087 static const Bit32u MODE_3_NUMBER_OF_ALLPASSES = 0;
00088 static const Bit32u MODE_3_NUMBER_OF_COMBS = 1;
00089 static const Bit32u MODE_3_DELAY[] = {16000 + MODE_3_FEEDBACK_DELAY + PROCESS_DELAY + MODE_3_ADDITIONAL_DELAY};
00090 static const Bit32u MODE_3_OUTL[] = {400, 624, 960, 1488, 2256, 3472, 5280, 8000};
00091 static const Bit32u MODE_3_OUTR[] = {800, 1248, 1920, 2976, 4512, 6944, 10560, 16000};
00092 static const Bit32u MODE_3_COMB_FACTOR[] = {0x68};
00093 static const Bit32u MODE_3_COMB_FEEDBACK[] = {0x68, 0x60};
00094 static const Bit32u MODE_3_DRY_AMP[] = {0x20, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50};
00095 static const Bit32u MODE_3_WET_AMP[] = {0x18, 0x18, 0x28, 0x40, 0x60, 0x80, 0xA8, 0xF8};
00096 
00097 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};
00098 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};
00099 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};
00100 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};
00101 
00102 static const BReverbSettings * const REVERB_SETTINGS[] = {&REVERB_MODE_0_SETTINGS, &REVERB_MODE_1_SETTINGS, &REVERB_MODE_2_SETTINGS, &REVERB_MODE_3_SETTINGS};
00103 
00104 // This algorithm tries to emulate exactly Boss multiplication operation (at least this is what we see on reverb RAM data lines).
00105 // Also LA32 is suspected to use the similar one to perform PCM interpolation and ring modulation.
00106 static Bit32s weirdMul(Bit32s a, Bit8u addMask, Bit8u carryMask) {
00107         Bit8u mask = 0x80;
00108         Bit32s res = 0;
00109         for (int i = 0; i < 8; i++) {
00110                 Bit32s carry = (a < 0) && (mask & carryMask) > 0 ? a & 1 : 0;
00111                 a >>= 1;
00112                 res += (mask & addMask) > 0 ? a + carry : 0;
00113                 mask >>= 1;
00114         }
00115         return res;
00116 }
00117 
00118 RingBuffer::RingBuffer(Bit32u newsize) : size(newsize), index(0) {
00119         buffer = new Bit16s[size];
00120 }
00121 
00122 RingBuffer::~RingBuffer() {
00123         delete[] buffer;
00124         buffer = NULL;
00125 }
00126 
00127 Bit32s RingBuffer::next() {
00128         if (++index >= size) {
00129                 index = 0;
00130         }
00131         return buffer[index];
00132 }
00133 
00134 bool RingBuffer::isEmpty() const {
00135         if (buffer == NULL) return true;
00136 
00137         Bit16s *buf = buffer;
00138         for (Bit32u i = 0; i < size; i++) {
00139                 if (*buf < -8 || *buf > 8) return false;
00140                 buf++;
00141         }
00142         return true;
00143 }
00144 
00145 void RingBuffer::mute() {
00146         Bit16s *buf = buffer;
00147         for (Bit32u i = 0; i < size; i++) {
00148                 *buf++ = 0;
00149         }
00150 }
00151 
00152 AllpassFilter::AllpassFilter(const Bit32u useSize) : RingBuffer(useSize) {}
00153 
00154 Bit32s AllpassFilter::process(const Bit32s in) {
00155         // This model corresponds to the allpass filter implementation of the real CM-32L device
00156         // found from sample analysis
00157 
00158         Bit16s bufferOut = next();
00159 
00160         // store input - feedback / 2
00161         buffer[index] = in - (bufferOut >> 1);
00162 
00163         // return buffer output + feedforward / 2
00164         return bufferOut + (buffer[index] >> 1);
00165 }
00166 
00167 CombFilter::CombFilter(const Bit32u useSize, const Bit32u useFilterFactor) : RingBuffer(useSize), filterFactor(useFilterFactor) {}
00168 
00169 void CombFilter::process(const Bit32s in) {
00170         // This model corresponds to the comb filter implementation of the real CM-32L device
00171 
00172         // the previously stored value
00173         Bit32s last = buffer[index];
00174 
00175         // prepare input + feedback
00176         Bit32s filterIn = in + weirdMul(next(), feedbackFactor, 0xF0 /* Maybe 0x80 ? */);
00177 
00178         // store input + feedback processed by a low-pass filter
00179         buffer[index] = weirdMul(last, filterFactor, 0x40) - filterIn;
00180 }
00181 
00182 Bit32s CombFilter::getOutputAt(const Bit32u outIndex) const {
00183         return buffer[(size + index - outIndex) % size];
00184 }
00185 
00186 void CombFilter::setFeedbackFactor(const Bit32u useFeedbackFactor) {
00187         feedbackFactor = useFeedbackFactor;
00188 }
00189 
00190 DelayWithLowPassFilter::DelayWithLowPassFilter(const Bit32u useSize, const Bit32u useFilterFactor, const Bit32u useAmp)
00191         : CombFilter(useSize, useFilterFactor), amp(useAmp) {}
00192 
00193 void DelayWithLowPassFilter::process(const Bit32s in) {
00194         // the previously stored value
00195         Bit32s last = buffer[index];
00196 
00197         // move to the next index
00198         next();
00199 
00200         // low-pass filter process
00201         Bit32s lpfOut = weirdMul(last, filterFactor, 0xFF) + in;
00202 
00203         // store lpfOut multiplied by LPF amp factor
00204         buffer[index] = weirdMul(lpfOut, amp, 0xFF);
00205 }
00206 
00207 TapDelayCombFilter::TapDelayCombFilter(const Bit32u useSize, const Bit32u useFilterFactor) : CombFilter(useSize, useFilterFactor) {}
00208 
00209 void TapDelayCombFilter::process(const Bit32s in) {
00210         // the previously stored value
00211         Bit32s last = buffer[index];
00212 
00213         // move to the next index
00214         next();
00215 
00216         // prepare input + feedback
00217         // Actually, the size of the filter varies with the TIME parameter, the feedback sample is taken from the position just below the right output
00218         Bit32s filterIn = in + weirdMul(getOutputAt(outR + MODE_3_FEEDBACK_DELAY), feedbackFactor, 0xF0);
00219 
00220         // store input + feedback processed by a low-pass filter
00221         buffer[index] = weirdMul(last, filterFactor, 0xF0) - filterIn;
00222 }
00223 
00224 Bit32s TapDelayCombFilter::getLeftOutput() const {
00225         return getOutputAt(outL + PROCESS_DELAY + MODE_3_ADDITIONAL_DELAY);
00226 }
00227 
00228 Bit32s TapDelayCombFilter::getRightOutput() const {
00229         return getOutputAt(outR + PROCESS_DELAY + MODE_3_ADDITIONAL_DELAY);
00230 }
00231 
00232 void TapDelayCombFilter::setOutputPositions(const Bit32u useOutL, const Bit32u useOutR) {
00233         outL = useOutL;
00234         outR = useOutR;
00235 }
00236 
00237 BReverbModel::BReverbModel(const ReverbMode mode)
00238         : allpasses(NULL), combs(NULL), currentSettings(*REVERB_SETTINGS[mode]), tapDelayMode(mode == REVERB_MODE_TAP_DELAY) {}
00239 
00240 BReverbModel::~BReverbModel() {
00241         close();
00242 }
00243 
00244 void BReverbModel::open() {
00245         if (currentSettings.numberOfAllpasses > 0) {
00246                 allpasses = new AllpassFilter*[currentSettings.numberOfAllpasses];
00247                 for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) {
00248                         allpasses[i] = new AllpassFilter(currentSettings.allpassSizes[i]);
00249                 }
00250         }
00251         combs = new CombFilter*[currentSettings.numberOfCombs];
00252         if (tapDelayMode) {
00253                 *combs = new TapDelayCombFilter(*currentSettings.combSizes, *currentSettings.filterFactors);
00254         } else {
00255                 combs[0] = new DelayWithLowPassFilter(currentSettings.combSizes[0], currentSettings.filterFactors[0], currentSettings.lpfAmp);
00256                 for (Bit32u i = 1; i < currentSettings.numberOfCombs; i++) {
00257                         combs[i] = new CombFilter(currentSettings.combSizes[i], currentSettings.filterFactors[i]);
00258                 }
00259         }
00260         mute();
00261 }
00262 
00263 void BReverbModel::close() {
00264         if (allpasses != NULL) {
00265                 for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) {
00266                         if (allpasses[i] != NULL) {
00267                                 delete allpasses[i];
00268                                 allpasses[i] = NULL;
00269                         }
00270                 }
00271                 delete[] allpasses;
00272                 allpasses = NULL;
00273         }
00274         if (combs != NULL) {
00275                 for (Bit32u i = 0; i < currentSettings.numberOfCombs; i++) {
00276                         if (combs[i] != NULL) {
00277                                 delete combs[i];
00278                                 combs[i] = NULL;
00279                         }
00280                 }
00281                 delete[] combs;
00282                 combs = NULL;
00283         }
00284 }
00285 
00286 void BReverbModel::mute() {
00287         if (allpasses != NULL) {
00288                 for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) {
00289                         allpasses[i]->mute();
00290                 }
00291         }
00292         if (combs != NULL) {
00293                 for (Bit32u i = 0; i < currentSettings.numberOfCombs; i++) {
00294                         combs[i]->mute();
00295                 }
00296         }
00297 }
00298 
00299 void BReverbModel::setParameters(Bit8u time, Bit8u level) {
00300         if (combs == NULL) return;
00301         level &= 7;
00302         time &= 7;
00303         if (tapDelayMode) {
00304                 TapDelayCombFilter *comb = static_cast<TapDelayCombFilter *> (*combs);
00305                 comb->setOutputPositions(currentSettings.outLPositions[time], currentSettings.outRPositions[time & 7]);
00306                 comb->setFeedbackFactor(currentSettings.feedbackFactors[((level < 3) || (time < 6)) ? 0 : 1]);
00307         } else {
00308                 for (Bit32u i = 0; i < currentSettings.numberOfCombs; i++) {
00309                         combs[i]->setFeedbackFactor(currentSettings.feedbackFactors[(i << 3) + time]);
00310                 }
00311         }
00312         if (time == 0 && level == 0) {
00313                 dryAmp = wetLevel = 0;
00314         } else {
00315                 dryAmp = currentSettings.dryAmps[level];
00316                 wetLevel = currentSettings.wetLevels[level];
00317         }
00318 }
00319 
00320 bool BReverbModel::isActive() const {
00321         for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) {
00322                 if (!allpasses[i]->isEmpty()) return true;
00323         }
00324         for (Bit32u i = 0; i < currentSettings.numberOfCombs; i++) {
00325                 if (!combs[i]->isEmpty()) return true;
00326         }
00327         return false;
00328 }
00329 
00330 void BReverbModel::process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples) {
00331         Bit32s dry, link, outL1, outR1;
00332 
00333         for (unsigned long i = 0; i < numSamples; i++) {
00334                 if (tapDelayMode) {
00335                         dry = Bit32s(*inLeft * 8192.0f) + Bit32s(*inRight * 8192.0f);
00336                 } else {
00337                         dry = Bit32s(*inLeft * 8192.0f) / 2 + Bit32s(*inRight * 8192.0f) / 2;
00338                 }
00339 
00340                 // Looks like dryAmp doesn't change in MT-32 but it does in CM-32L / LAPC-I
00341                 dry = weirdMul(dry, dryAmp, 0xFF);
00342 
00343                 if (tapDelayMode) {
00344                         TapDelayCombFilter *comb = static_cast<TapDelayCombFilter *> (*combs);
00345                         comb->process(dry);
00346                         *outLeft = weirdMul(comb->getLeftOutput(), wetLevel, 0xFF) / 8192.0f;
00347                         *outRight = weirdMul(comb->getRightOutput(), wetLevel, 0xFF) / 8192.0f;
00348                 } else {
00349                         // Get the last stored sample before processing in order not to loose it
00350                         link = combs[0]->getOutputAt(currentSettings.combSizes[0] - 1);
00351 
00352                         // Entrance LPF. Note, comb.process() differs a bit here.
00353                         combs[0]->process(dry);
00354 
00355                         // This introduces reverb noise which actually makes output from the real Boss chip nondeterministic
00356                         link = link - 1;
00357                         link = allpasses[0]->process(link);
00358                         link = allpasses[1]->process(link);
00359                         link = allpasses[2]->process(link);
00360 
00361                         // If the output position is equal to the comb size, get it now in order not to loose it
00362                         outL1 = combs[1]->getOutputAt(currentSettings.outLPositions[0] - 1);
00363                         outL1 += outL1 >> 1;
00364 
00365                         combs[1]->process(link);
00366                         combs[2]->process(link);
00367                         combs[3]->process(link);
00368 
00369                         link = combs[2]->getOutputAt(currentSettings.outLPositions[1]);
00370                         link += link >> 1;
00371                         link += outL1;
00372                         link += combs[3]->getOutputAt(currentSettings.outLPositions[2]);
00373                         *outLeft = weirdMul(link, wetLevel, 0xFF) / 8192.0f;
00374 
00375                         outR1 = combs[1]->getOutputAt(currentSettings.outRPositions[0]);
00376                         outR1 += outR1 >> 1;
00377                         link = combs[2]->getOutputAt(currentSettings.outRPositions[1]);
00378                         link += link >> 1;
00379                         link += outR1;
00380                         link += combs[3]->getOutputAt(currentSettings.outRPositions[2]);
00381                         *outRight = weirdMul(link, wetLevel, 0xFF) / 8192.0f;
00382                 }
00383 
00384                 inLeft++;
00385                 inRight++;
00386                 outLeft++;
00387                 outRight++;
00388         }
00389 }
00390 
00391 }
00392 
00393 #endif