diff options
Diffstat (limited to 'audio/softsynth/mt32')
27 files changed, 1408 insertions, 528 deletions
diff --git a/audio/softsynth/mt32/AReverbModel.cpp b/audio/softsynth/mt32/AReverbModel.cpp index 4ee6c87943..595b286a31 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" - -using namespace MT32Emu; - -// Default reverb settings for modes 0-2 - -static const unsigned int NUM_ALLPASSES = 6; -static const unsigned int NUM_DELAYS = 5; -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}; +#if MT32EMU_USE_REVERBMODEL == 1 -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}; - -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}; - -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}; +#include "AReverbModel.h" -RingBuffer::RingBuffer(Bit32u newsize) { - index = 0; - size = newsize; +// 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; + +// 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[] = {10*1, 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[] = {10*1, 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[] = {10*1, 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,59 +116,66 @@ 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 + + // the previously stored value + float last = buffer[index]; - out = next(); + // prepare input + feedback + float filterIn = in + next() * feedbackFactor; - // store input - buffer[index] = in; + // store input + feedback processed by a low-pass filter + buffer[index] = filterFactor * last - filterIn; +} - // return buffer output - return out; +float CombFilter::getOutputAt(const Bit32u outIndex) const { + return buffer[(size + index - outIndex) % size]; } -AReverbModel::AReverbModel(const AReverbSettings *useSettings) : allpasses(NULL), delays(NULL), currentSettings(useSettings) { +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(); } -void AReverbModel::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 +void AReverbModel::open() { 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 +190,80 @@ 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; + level &= 7; + time &= 7; + for (Bit32u i = 0; i < NUM_COMBS; i++) { + combs[i]->setFeedbackFactor(currentSettings.decayTimes[(i << 3) + time] / 256.0f); + } + wetLevel = (level == 0 && time == 0) ? 0.0f : 0.5f * lpfAmp * currentSettings.wetLevels[level] / 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++; @@ -235,3 +271,7 @@ void AReverbModel::process(const float *inLeft, const float *inRight, float *out outRight++; } } + +} + +#endif diff --git a/audio/softsynth/mt32/AReverbModel.h b/audio/softsynth/mt32/AReverbModel.h index 3fae08c34c..be1ca4916b 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 open(); 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..2570424187 --- /dev/null +++ b/audio/softsynth/mt32/BReverbModel.cpp @@ -0,0 +1,393 @@ +/* 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() { + 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..f728d1a99c --- /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(); + 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 89eebf0d79..bf925f8419 100644 --- a/audio/softsynth/mt32/DelayReverb.cpp +++ b/audio/softsynth/mt32/DelayReverb.cpp @@ -20,15 +20,14 @@ #include "mt32emu.h" #include "DelayReverb.h" -using namespace MT32Emu; +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 // Leaving these separate in case it's useful for work on other reverb modes... -const Bit32u REVERB_TIMINGS[8][3]= { +static const Bit32u REVERB_TIMINGS[8][3]= { // {leftDelay, rightDelay, feedbackDelay} {402, 802, 801}, {626, 1250, 1249}, @@ -40,14 +39,16 @@ const Bit32u REVERB_TIMINGS[8][3]= { {8002, 16002, 16001} }; -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; + +static const Bit32u BUFFER_SIZE = 16384; DelayReverb::DelayReverb() { buf = NULL; - sampleRate = 0; setParameters(0, 0); } @@ -55,27 +56,22 @@ DelayReverb::~DelayReverb() { delete[] buf; } -void DelayReverb::open(unsigned int newSampleRate) { - if (newSampleRate != sampleRate || buf == NULL) { - sampleRate = newSampleRate; - +void DelayReverb::open() { + if (buf == NULL) { delete[] buf; - // If we ever need a speedup, set bufSize to EXP2F(ceil(log2(bufSize))) and use & instead of % to find buf indexes - bufSize = 16384 * sampleRate / 32000; - buf = new float[bufSize]; + buf = new float[BUFFER_SIZE]; recalcParameters(); // mute buffer bufIx = 0; if (buf != NULL) { - for (unsigned int i = 0; i < bufSize; i++) { + for (unsigned int i = 0; i < BUFFER_SIZE; i++) { buf[i] = 0.0f; } } } - // FIXME: IIR filter value depends on sample rate as well } void DelayReverb::close() { @@ -92,59 +88,55 @@ void DelayReverb::setParameters(Bit8u newTime, Bit8u newLevel) { void DelayReverb::recalcParameters() { // Number of samples between impulse and eventual appearance on the left channel - delayLeft = REVERB_TIMINGS[time][0] * sampleRate / 32000; + delayLeft = REVERB_TIMINGS[time][0]; // Number of samples between impulse and eventual appearance on the right channel - delayRight = REVERB_TIMINGS[time][1] * sampleRate / 32000; + delayRight = REVERB_TIMINGS[time][1]; // Number of samples between a response and that response feeding back/echoing - delayFeedback = REVERB_TIMINGS[time][2] * sampleRate / 32000; + delayFeedback = REVERB_TIMINGS[time][2]; - 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. - Bit32u bufIxPrev = (bufIx + 1) % bufSize; - Bit32u bufIxLeft = (bufIx + delayLeft) % bufSize; - Bit32u bufIxRight = (bufIx + delayRight) % bufSize; - Bit32u bufIxFeedback = (bufIx + delayFeedback) % bufSize; + Bit32u bufIxPrev = (bufIx + 1) % BUFFER_SIZE; + Bit32u bufIxLeft = (bufIx + delayLeft) % BUFFER_SIZE; + Bit32u bufIxRight = (bufIx + delayRight) % BUFFER_SIZE; + Bit32u bufIxFeedback = (bufIx + delayFeedback) % BUFFER_SIZE; // 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]; - bufIx = (bufSize + bufIx - 1) % bufSize; + bufIx = (BUFFER_SIZE + bufIx - 1) % BUFFER_SIZE; } } 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 < BUFFER_SIZE; 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..1abb49f128 100644 --- a/audio/softsynth/mt32/DelayReverb.h +++ b/audio/softsynth/mt32/DelayReverb.h @@ -25,17 +25,14 @@ private: Bit8u time; Bit8u level; - unsigned int sampleRate; - Bit32u bufSize; Bit32u bufIx; - float *buf; Bit32u delayLeft; Bit32u delayRight; Bit32u delayFeedback; - float fade; + float amp; float feedback; void recalcParameters(); @@ -43,7 +40,7 @@ private: public: DelayReverb(); ~DelayReverb(); - void open(unsigned int sampleRate); + void open(); void close(); void setParameters(Bit8u time, Bit8u level); void process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples); diff --git a/audio/softsynth/mt32/FreeverbModel.cpp b/audio/softsynth/mt32/FreeverbModel.cpp index c11fa859d8..2ed302fddc 100644 --- a/audio/softsynth/mt32/FreeverbModel.cpp +++ b/audio/softsynth/mt32/FreeverbModel.cpp @@ -20,7 +20,7 @@ #include "freeverb.h" -using namespace MT32Emu; +namespace MT32Emu { FreeverbModel::FreeverbModel(float useScaleTuning, float useFiltVal, float useWet, Bit8u useRoom, float useDamp) { freeverb = NULL; @@ -35,9 +35,7 @@ FreeverbModel::~FreeverbModel() { delete freeverb; } -void FreeverbModel::open(unsigned int /*sampleRate*/) { - // FIXME: scaleTuning must be multiplied by sample rate to 32000Hz ratio - // IIR filter values depend on sample rate as well +void FreeverbModel::open() { if (freeverb == NULL) { freeverb = new revmodel(scaleTuning); } @@ -76,3 +74,5 @@ bool FreeverbModel::isActive() const { // FIXME: Not bothering to do this properly since we'll be replacing Freeverb soon... return false; } + +} diff --git a/audio/softsynth/mt32/FreeverbModel.h b/audio/softsynth/mt32/FreeverbModel.h index 925b2dbf96..bf1102b14a 100644 --- a/audio/softsynth/mt32/FreeverbModel.h +++ b/audio/softsynth/mt32/FreeverbModel.h @@ -32,7 +32,7 @@ class FreeverbModel : public ReverbModel { public: FreeverbModel(float useScaleTuning, float useFiltVal, float useWet, Bit8u useRoom, float useDamp); ~FreeverbModel(); - void open(unsigned int sampleRate); + void open(); void close(); void setParameters(Bit8u time, Bit8u level); void process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples); diff --git a/audio/softsynth/mt32/LA32Ramp.cpp b/audio/softsynth/mt32/LA32Ramp.cpp index 9f1f01c3c2..4e4d6b4f30 100644 --- a/audio/softsynth/mt32/LA32Ramp.cpp +++ b/audio/softsynth/mt32/LA32Ramp.cpp @@ -79,11 +79,12 @@ LA32Ramp::LA32Ramp() : void LA32Ramp::startRamp(Bit8u target, Bit8u increment) { // CONFIRMED: From sample analysis, this appears to be very accurate. - // FIXME: We could use a table for this in future if (increment == 0) { largeIncrement = 0; } else { - largeIncrement = (unsigned int)(EXP2F(((increment & 0x7F) + 24) / 8.0f) + 0.125f); + // Using integer argument here, no precision loss: + // (unsigned int)(EXP2F(((increment & 0x7F) + 24) / 8.0f) + 0.125f) + largeIncrement = (unsigned int)(EXP2I(((increment & 0x7F) + 24) << 9) + 0.125f); } descending = (increment & 0x80) != 0; if (descending) { diff --git a/audio/softsynth/mt32/Part.cpp b/audio/softsynth/mt32/Part.cpp index 75912f38a8..cd385898e1 100644 --- a/audio/softsynth/mt32/Part.cpp +++ b/audio/softsynth/mt32/Part.cpp @@ -68,18 +68,16 @@ Part::Part(Synth *useSynth, unsigned int usePartNum) { activePartialCount = 0; memset(patchCache, 0, sizeof(patchCache)); for (int i = 0; i < MT32EMU_MAX_POLY; i++) { - freePolys.push_front(new Poly(this)); + freePolys.prepend(new Poly(this)); } } Part::~Part() { - while (!activePolys.empty()) { - delete activePolys.front(); - activePolys.pop_front(); + while (!activePolys.isEmpty()) { + delete activePolys.takeFirst(); } - while (!freePolys.empty()) { - delete freePolys.front(); - freePolys.pop_front(); + while (!freePolys.isEmpty()) { + delete freePolys.takeFirst(); } } @@ -209,6 +207,7 @@ void RhythmPart::setTimbre(TimbreParam * /*timbre*/) { void Part::setTimbre(TimbreParam *timbre) { *timbreTemp = *timbre; + synth->newTimbreSet(partNum, timbre->common.name); } unsigned int RhythmPart::getAbsTimbreNum() const { @@ -245,8 +244,8 @@ void Part::backupCacheToPartials(PatchCache cache[4]) { // if so then duplicate the cached data from the part to the partial so that // we can change the part's cache without affecting the partial. // We delay this until now to avoid a copy operation with every note played - for (Common::List<Poly *>::iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) { - (*polyIt)->backupCacheToPartials(cache); + for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) { + poly->backupCacheToPartials(cache); } } @@ -445,8 +444,7 @@ void Part::abortPoly(Poly *poly) { } bool Part::abortFirstPoly(unsigned int key) { - for (Common::List<Poly *>::iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) { - Poly *poly = *polyIt; + for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) { if (poly->getKey() == key) { abortPoly(poly); return true; @@ -456,8 +454,7 @@ bool Part::abortFirstPoly(unsigned int key) { } bool Part::abortFirstPoly(PolyState polyState) { - for (Common::List<Poly *>::iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) { - Poly *poly = *polyIt; + for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) { if (poly->getState() == polyState) { abortPoly(poly); return true; @@ -474,10 +471,10 @@ bool Part::abortFirstPolyPreferHeld() { } bool Part::abortFirstPoly() { - if (activePolys.empty()) { + if (activePolys.isEmpty()) { return false; } - abortPoly(activePolys.front()); + abortPoly(activePolys.getFirst()); return true; } @@ -502,17 +499,16 @@ void Part::playPoly(const PatchCache cache[4], const MemParams::RhythmTemp *rhyt return; } - if (freePolys.empty()) { + if (freePolys.isEmpty()) { synth->printDebug("%s (%s): No free poly to play key %d (velocity %d)", name, currentInstr, midiKey, velocity); return; } - Poly *poly = freePolys.front(); - freePolys.pop_front(); + Poly *poly = freePolys.takeFirst(); if (patchTemp->patch.assignMode & 1) { // Priority to data first received - activePolys.push_front(poly); + activePolys.prepend(poly); } else { - activePolys.push_back(poly); + activePolys.append(poly); } Partial *partials[4]; @@ -537,16 +533,20 @@ void Part::playPoly(const PatchCache cache[4], const MemParams::RhythmTemp *rhyt #if MT32EMU_MONITOR_PARTIALS > 1 synth->printPartialUsage(); #endif + synth->partStateChanged(partNum, true); + synth->polyStateChanged(partNum); } void Part::allNotesOff() { // The MIDI specification states - and Mok confirms - that all notes off (0x7B) // should treat the hold pedal as usual. - for (Common::List<Poly *>::iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) { - Poly *poly = *polyIt; - // FIXME: This has special handling of key 0 in NoteOff that Mok has not yet confirmed - // applies to AllNotesOff. - poly->noteOff(holdpedal); + for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) { + // FIXME: This has special handling of key 0 in NoteOff that Mok has not yet confirmed applies to AllNotesOff. + // if (poly->canSustain() || poly->getKey() == 0) { + // FIXME: The real devices are found to be ignoring non-sustaining polys while processing AllNotesOff. Need to be confirmed. + if (poly->canSustain()) { + poly->noteOff(holdpedal); + } } } @@ -554,15 +554,13 @@ void Part::allSoundOff() { // MIDI "All sound off" (0x78) should release notes immediately regardless of the hold pedal. // This controller is not actually implemented by the synths, though (according to the docs and Mok) - // we're only using this method internally. - for (Common::List<Poly *>::iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) { - Poly *poly = *polyIt; + for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) { poly->startDecay(); } } void Part::stopPedalHold() { - for (Common::List<Poly *>::iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) { - Poly *poly = *polyIt; + for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) { poly->stopPedalHold(); } } @@ -580,8 +578,7 @@ void Part::stopNote(unsigned int key) { synth->printDebug("%s (%s): stopping key %d", name, currentInstr, key); #endif - for (Common::List<Poly *>::iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) { - Poly *poly = *polyIt; + for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) { // Generally, non-sustaining instruments ignore note off. They die away eventually anyway. // Key 0 (only used by special cases on rhythm part) reacts to note off even if non-sustaining or pedal held. if (poly->getKey() == key && (poly->canSustain() || key == 0)) { @@ -602,8 +599,7 @@ unsigned int Part::getActivePartialCount() const { unsigned int Part::getActiveNonReleasingPartialCount() const { unsigned int activeNonReleasingPartialCount = 0; - for (Common::List<Poly *>::const_iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) { - Poly *poly = *polyIt; + for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) { if (poly->getState() != POLY_Releasing) { activeNonReleasingPartialCount += poly->getActivePartialCount(); } @@ -615,7 +611,103 @@ void Part::partialDeactivated(Poly *poly) { activePartialCount--; if (!poly->isActive()) { activePolys.remove(poly); - freePolys.push_front(poly); + freePolys.prepend(poly); + synth->polyStateChanged(partNum); + } + if (activePartialCount == 0) { + synth->partStateChanged(partNum, false); + } +} + +//#define POLY_LIST_DEBUG + +PolyList::PolyList() : firstPoly(NULL), lastPoly(NULL) {} + +bool PolyList::isEmpty() const { +#ifdef POLY_LIST_DEBUG + if ((firstPoly == NULL || lastPoly == NULL) && firstPoly != lastPoly) { + printf("PolyList: desynchronised firstPoly & lastPoly pointers\n"); + } +#endif + return firstPoly == NULL && lastPoly == NULL; +} + +Poly *PolyList::getFirst() const { + return firstPoly; +} + +Poly *PolyList::getLast() const { + return lastPoly; +} + +void PolyList::prepend(Poly *poly) { +#ifdef POLY_LIST_DEBUG + if (poly->getNext() != NULL) { + printf("PolyList: Non-NULL next field in a Poly being prepended is ignored\n"); + } +#endif + poly->setNext(firstPoly); + firstPoly = poly; + if (lastPoly == NULL) { + lastPoly = poly; + } +} + +void PolyList::append(Poly *poly) { +#ifdef POLY_LIST_DEBUG + if (poly->getNext() != NULL) { + printf("PolyList: Non-NULL next field in a Poly being appended is ignored\n"); + } +#endif + poly->setNext(NULL); + if (lastPoly != NULL) { +#ifdef POLY_LIST_DEBUG + if (lastPoly->getNext() != NULL) { + printf("PolyList: Non-NULL next field in the lastPoly\n"); + } +#endif + lastPoly->setNext(poly); + } + lastPoly = poly; + if (firstPoly == NULL) { + firstPoly = poly; + } +} + +Poly *PolyList::takeFirst() { + Poly *oldFirst = firstPoly; + firstPoly = oldFirst->getNext(); + if (firstPoly == NULL) { +#ifdef POLY_LIST_DEBUG + if (lastPoly != oldFirst) { + printf("PolyList: firstPoly != lastPoly in a list with a single Poly\n"); + } +#endif + lastPoly = NULL; + } + oldFirst->setNext(NULL); + return oldFirst; +} + +void PolyList::remove(Poly * const polyToRemove) { + if (polyToRemove == firstPoly) { + takeFirst(); + return; + } + for (Poly *poly = firstPoly; poly != NULL; poly = poly->getNext()) { + if (poly->getNext() == polyToRemove) { + if (polyToRemove == lastPoly) { +#ifdef POLY_LIST_DEBUG + if (lastPoly->getNext() != NULL) { + printf("PolyList: Non-NULL next field in the lastPoly\n"); + } +#endif + lastPoly = poly; + } + poly->setNext(polyToRemove->getNext()); + polyToRemove->setNext(NULL); + break; + } } } diff --git a/audio/softsynth/mt32/Part.h b/audio/softsynth/mt32/Part.h index 5c59c6d61f..e5be41ff10 100644 --- a/audio/softsynth/mt32/Part.h +++ b/audio/softsynth/mt32/Part.h @@ -18,13 +18,27 @@ #ifndef MT32EMU_PART_H #define MT32EMU_PART_H -#include <common/list.h> - namespace MT32Emu { class PartialManager; class Synth; +class PolyList { +private: + Poly *firstPoly; + Poly *lastPoly; + +public: + PolyList(); + bool isEmpty() const; + Poly *getFirst() const; + Poly *getLast() const; + void prepend(Poly *poly); + void append(Poly *poly); + Poly *takeFirst(); + void remove(Poly * const poly); +}; + class Part { private: // Direct pointer to sysex-addressable memory dedicated to this part (valid for parts 1-8, NULL for rhythm) @@ -37,8 +51,8 @@ private: unsigned int activePartialCount; PatchCache patchCache[4]; - Common::List<Poly *> freePolys; - Common::List<Poly *> activePolys; + PolyList freePolys; + PolyList activePolys; void setPatch(const PatchParam *patch); unsigned int midiKeyToKey(unsigned int midiKey); diff --git a/audio/softsynth/mt32/Partial.cpp b/audio/softsynth/mt32/Partial.cpp index 03bec560b8..a4d1ab03fa 100644 --- a/audio/softsynth/mt32/Partial.cpp +++ b/audio/softsynth/mt32/Partial.cpp @@ -22,7 +22,7 @@ #include "mt32emu.h" #include "mmath.h" -using namespace MT32Emu; +namespace MT32Emu { #ifdef INACCURATE_SMOOTH_PAN // Mok wanted an option for smoother panning, and we love Mok. @@ -85,6 +85,7 @@ void Partial::deactivate() { pair->pair = NULL; } } + synth->partialStateChanged(this, tva->getPhase(), TVA_PHASE_DEAD); #if MT32EMU_MONITOR_PARTIALS > 2 synth->printDebug("[+%lu] [Partial %d] Deactivated", sampleNum, debugPartialNum); synth->printPartialUsage(sampleNum); @@ -133,6 +134,25 @@ void Partial::startPartial(const Part *part, Poly *usePoly, const PatchCache *us stereoVolume.leftVol = panVal / 7.0f; stereoVolume.rightVol = 1.0f - stereoVolume.leftVol; + // SEMI-CONFIRMED: From sample analysis: + // Found that timbres with 3 or 4 partials (i.e. one using two partial pairs) are mixed in two different ways. + // Either partial pairs are added or subtracted, it depends on how the partial pairs are allocated. + // It seems that partials are grouped into quarters and if the partial pairs are allocated in different quarters the subtraction happens. + // Though, this matters little for the majority of timbres, it becomes crucial for timbres which contain several partials that sound very close. + // In this case that timbre can sound totally different depending of the way it is mixed up. + // Most easily this effect can be displayed with the help of a special timbre consisting of several identical square wave partials (3 or 4). + // Say, it is 3-partial timbre. Just play any two notes simultaneously and the polys very probably are mixed differently. + // Moreover, the partial allocator retains the last partial assignment it did and all the subsequent notes will sound the same as the last released one. + // The situation is better with 4-partial timbres since then a whole quarter is assigned for each poly. However, if a 3-partial timbre broke the normal + // whole-quarter assignment or after some partials got aborted, even 4-partial timbres can be found sounding differently. + // This behaviour is also confirmed with two more special timbres: one with identical sawtooth partials, and one with PCM wave 02. + // For my personal taste, this behaviour rather enriches the sounding and should be emulated. + // Also, the current partial allocator model probably needs to be refined. + if (debugPartialNum & 8) { + stereoVolume.leftVol = -stereoVolume.leftVol; + stereoVolume.rightVol = -stereoVolume.rightVol; + } + if (patchCache->PCMPartial) { pcmNum = patchCache->pcm; if (synth->controlROMMap->pcmCount > 128) { @@ -149,7 +169,7 @@ void Partial::startPartial(const Part *part, Poly *usePoly, const PatchCache *us } // CONFIRMED: pulseWidthVal calculation is based on information from Mok - pulseWidthVal = (poly->getVelocity() - 64) * (patchCache->srcPartial.wg.pulseWidthVeloSensitivity - 7) + synth->tables.pulseWidth100To255[patchCache->srcPartial.wg.pulseWidth]; + pulseWidthVal = (poly->getVelocity() - 64) * (patchCache->srcPartial.wg.pulseWidthVeloSensitivity - 7) + Tables::getInstance().pulseWidth100To255[patchCache->srcPartial.wg.pulseWidth]; if (pulseWidthVal < 0) { pulseWidthVal = 0; } else if (pulseWidthVal > 255) { @@ -175,6 +195,7 @@ float Partial::getPCMSample(unsigned int position) { } unsigned long Partial::generateSamples(float *partialBuf, unsigned long length) { + const Tables &tables = Tables::getInstance(); if (!isActive() || alreadyOutputed) { return 0; } @@ -197,6 +218,9 @@ unsigned long Partial::generateSamples(float *partialBuf, unsigned long length) deactivate(); break; } + + Bit16u pitch = tvp->nextPitch(); + // SEMI-CONFIRMED: From sample analysis: // (1) Tested with a single partial playing PCM wave 77 with pitchCoarse 36 and no keyfollow, velocity follow, etc. // This gives results within +/- 2 at the output (before any DAC bitshifting) @@ -206,10 +230,17 @@ unsigned long Partial::generateSamples(float *partialBuf, unsigned long length) // positive amps, so negative still needs to be explored, as well as lower levels. // // Also still partially unconfirmed is the behaviour when ramping between levels, as well as the timing. + +#if MT32EMU_ACCURATE_WG == 1 float amp = EXP2F((32772 - ampRampVal / 2048) / -2048.0f); + float freq = EXP2F(pitch / 4096.0f - 16.0f) * SAMPLE_RATE; +#else + static const float ampFactor = EXP2F(32772 / -2048.0f); + float amp = EXP2I(ampRampVal >> 10) * ampFactor; - Bit16u pitch = tvp->nextPitch(); - float freq = synth->tables.pitchToFreq[pitch]; + static const float freqFactor = EXP2F(-16.0f) * SAMPLE_RATE; + float freq = EXP2I(pitch) * freqFactor; +#endif if (patchCache->PCMPartial) { // Render PCM waveform @@ -221,12 +252,18 @@ unsigned long Partial::generateSamples(float *partialBuf, unsigned long length) break; } Bit32u pcmAddr = pcmWave->addr; - float positionDelta = freq * 2048.0f / synth->myProp.sampleRate; + float positionDelta = freq * 2048.0f / SAMPLE_RATE; // Linear interpolation float firstSample = synth->pcmROMData[pcmAddr + intPCMPosition]; - float nextSample = getPCMSample(intPCMPosition + 1); - sample = firstSample + (nextSample - firstSample) * (pcmPosition - intPCMPosition); + // We observe that for partial structures with ring modulation the interpolation is not applied to the slave PCM partial. + // It's assumed that the multiplication circuitry intended to perform the interpolation on the slave PCM partial + // is borrowed by the ring modulation circuit (or the LA32 chip has a similar lack of resources assigned to each partial pair). + if (pair == NULL || mixType == 0 || structurePosition == 0) { + sample = firstSample + (getPCMSample(intPCMPosition + 1) - firstSample) * (pcmPosition - intPCMPosition); + } else { + sample = firstSample; + } float newPCMPosition = pcmPosition + positionDelta; if (pcmWave->loop) { @@ -247,8 +284,12 @@ unsigned long Partial::generateSamples(float *partialBuf, unsigned long length) // res corresponds to a value set in an LA32 register Bit8u res = patchCache->srcPartial.tvf.resonance + 1; - // EXP2F(1.0f - (32 - res) / 4.0f); - float resAmp = synth->tables.resAmpMax[res]; + float resAmp; + { + // resAmp = EXP2F(1.0f - (32 - res) / 4.0f); + static const float resAmpFactor = EXP2F(-7); + resAmp = EXP2I(res << 10) * resAmpFactor; + } // The cutoffModifier may not be supposed to be directly added to the cutoff - // it may for example need to be multiplied in some way. @@ -260,7 +301,7 @@ unsigned long Partial::generateSamples(float *partialBuf, unsigned long length) } // Wave length in samples - float waveLen = synth->myProp.sampleRate / freq; + float waveLen = SAMPLE_RATE / freq; // Init cosineLen float cosineLen = 0.5f * waveLen; @@ -268,7 +309,8 @@ unsigned long Partial::generateSamples(float *partialBuf, unsigned long length) #if MT32EMU_ACCURATE_WG == 1 cosineLen *= EXP2F((cutoffVal - 128.0f) / -16.0f); // found from sample analysis #else - cosineLen *= synth->tables.cutoffToCosineLen[Bit32u((cutoffVal - 128.0f) * 8.0f)]; + static const float cosineLenFactor = EXP2F(128.0f / -16.0f); + cosineLen *= EXP2I(Bit32u((256.0f - cutoffVal) * 256.0f)) * cosineLenFactor; #endif } @@ -279,23 +321,26 @@ unsigned long Partial::generateSamples(float *partialBuf, unsigned long length) relWavePos -= waveLen; } + // Ratio of positive segment to wave length float pulseLen = 0.5f; if (pulseWidthVal > 128) { - pulseLen += synth->tables.pulseLenFactor[pulseWidthVal - 128]; + // pulseLen = EXP2F((64 - pulseWidthVal) / 64); + static const float pulseLenFactor = EXP2F(-192 / 64); + pulseLen = EXP2I((256 - pulseWidthVal) << 6) * pulseLenFactor; } pulseLen *= waveLen; - float lLen = pulseLen - cosineLen; + float hLen = pulseLen - cosineLen; // Ignore pulsewidths too high for given freq - if (lLen < 0.0f) { - lLen = 0.0f; + if (hLen < 0.0f) { + hLen = 0.0f; } // Ignore pulsewidths too high for given freq and cutoff - float hLen = waveLen - lLen - 2 * cosineLen; - if (hLen < 0.0f) { - hLen = 0.0f; + float lLen = waveLen - hLen - 2 * cosineLen; + if (lLen < 0.0f) { + lLen = 0.0f; } // Correct resAmp for cutoff in range 50..66 @@ -303,7 +348,7 @@ unsigned long Partial::generateSamples(float *partialBuf, unsigned long length) #if MT32EMU_ACCURATE_WG == 1 resAmp *= sinf(FLOAT_PI * (cutoffVal - 128.0f) / 32.0f); #else - resAmp *= synth->tables.sinf10[Bit32u(64 * (cutoffVal - 128.0f))]; + resAmp *= tables.sinf10[Bit32u(64 * (cutoffVal - 128.0f))]; #endif } @@ -314,7 +359,7 @@ unsigned long Partial::generateSamples(float *partialBuf, unsigned long length) #if MT32EMU_ACCURATE_WG == 1 sample = -cosf(FLOAT_PI * relWavePos / cosineLen); #else - sample = -synth->tables.sinf10[Bit32u(2048.0f * relWavePos / cosineLen) + 1024]; + sample = -tables.sinf10[Bit32u(2048.0f * relWavePos / cosineLen) + 1024]; #endif } else @@ -328,7 +373,7 @@ unsigned long Partial::generateSamples(float *partialBuf, unsigned long length) #if MT32EMU_ACCURATE_WG == 1 sample = cosf(FLOAT_PI * (relWavePos - (cosineLen + hLen)) / cosineLen); #else - sample = synth->tables.sinf10[Bit32u(2048.0f * (relWavePos - (cosineLen + hLen)) / cosineLen) + 1024]; + sample = tables.sinf10[Bit32u(2048.0f * (relWavePos - (cosineLen + hLen)) / cosineLen) + 1024]; #endif } else { @@ -343,7 +388,8 @@ unsigned long Partial::generateSamples(float *partialBuf, unsigned long length) #if MT32EMU_ACCURATE_WG == 1 sample *= EXP2F(-0.125f * (128.0f - cutoffVal)); #else - sample *= synth->tables.cutoffToFilterAmp[Bit32u(cutoffVal * 8.0f)]; + static const float cutoffAttenuationFactor = EXP2F(-0.125f * 128.0f); + sample *= EXP2I(Bit32u(512.0f * cutoffVal)) * cutoffAttenuationFactor; #endif } else { @@ -363,11 +409,17 @@ unsigned long Partial::generateSamples(float *partialBuf, unsigned long length) #if MT32EMU_ACCURATE_WG == 1 resSample *= sinf(FLOAT_PI * relWavePos / cosineLen); #else - resSample *= synth->tables.sinf10[Bit32u(2048.0f * relWavePos / cosineLen) & 4095]; + resSample *= tables.sinf10[Bit32u(2048.0f * relWavePos / cosineLen) & 4095]; #endif // Resonance sine amp - float resAmpFade = EXP2F(-synth->tables.resAmpFadeFactor[res >> 2] * (relWavePos / cosineLen)); // seems to be exact + float resAmpFadeLog2 = -tables.resAmpFadeFactor[res >> 2] * (relWavePos / cosineLen); // seems to be exact +#if MT32EMU_ACCURATE_WG == 1 + float resAmpFade = EXP2F(resAmpFadeLog2); +#else + static const float resAmpFadeFactor = EXP2F(-30.0f); + float resAmpFade = (resAmpFadeLog2 < -30.0f) ? 0.0f : EXP2I(Bit32u((30.0f + resAmpFadeLog2) * 4096.0f)) * resAmpFadeFactor; +#endif // Now relWavePos set negative to the left from center of any cosine relWavePos = wavePos; @@ -388,7 +440,7 @@ unsigned long Partial::generateSamples(float *partialBuf, unsigned long length) #if MT32EMU_ACCURATE_WG == 1 resAmpFade *= 0.5f * (1.0f - cosf(FLOAT_PI * relWavePos / (0.5f * cosineLen))); #else - resAmpFade *= 0.5f * (1.0f + synth->tables.sinf10[Bit32s(2048.0f * relWavePos / (0.5f * cosineLen)) + 3072]); + resAmpFade *= 0.5f * (1.0f + tables.sinf10[Bit32s(2048.0f * relWavePos / (0.5f * cosineLen)) + 3072]); #endif } @@ -400,7 +452,7 @@ unsigned long Partial::generateSamples(float *partialBuf, unsigned long length) #if MT32EMU_ACCURATE_WG == 1 sample *= cosf(FLOAT_2PI * wavePos / waveLen); #else - sample *= synth->tables.sinf10[(Bit32u(4096.0f * wavePos / waveLen) & 4095) + 1024]; + sample *= tables.sinf10[(Bit32u(4096.0f * wavePos / waveLen) & 4095) + 1024]; #endif } @@ -516,10 +568,9 @@ bool Partial::produceOutput(float *leftBuf, float *rightBuf, unsigned long lengt } } if (numGenerated > pairNumGenerated) { - if (mixType == 1) { - mixBuffersRingMix(partialBuf + pairNumGenerated, NULL, numGenerated - pairNumGenerated); - } else { - mixBuffersRing(partialBuf + pairNumGenerated, NULL, numGenerated - pairNumGenerated); + if (mixType == 2) { + numGenerated = pairNumGenerated; + deactivate(); } } } @@ -555,3 +606,5 @@ void Partial::startDecayAll() { tvp->startDecay(); tvf->startDecay(); } + +} diff --git a/audio/softsynth/mt32/PartialManager.cpp b/audio/softsynth/mt32/PartialManager.cpp index 0a6be826d6..f8c2dbcd48 100644 --- a/audio/softsynth/mt32/PartialManager.cpp +++ b/audio/softsynth/mt32/PartialManager.cpp @@ -20,7 +20,7 @@ #include "mt32emu.h" #include "PartialManager.h" -using namespace MT32Emu; +namespace MT32Emu { PartialManager::PartialManager(Synth *useSynth, Part **useParts) { synth = useSynth; @@ -248,3 +248,5 @@ const Partial *PartialManager::getPartial(unsigned int partialNum) const { } return partialTable[partialNum]; } + +} diff --git a/audio/softsynth/mt32/Poly.cpp b/audio/softsynth/mt32/Poly.cpp index a2f00db73c..46e30c0f02 100644 --- a/audio/softsynth/mt32/Poly.cpp +++ b/audio/softsynth/mt32/Poly.cpp @@ -29,6 +29,7 @@ Poly::Poly(Part *usePart) { partials[i] = NULL; } state = POLY_Inactive; + next = NULL; } void Poly::reset(unsigned int newKey, unsigned int newVelocity, bool newSustain, Partial **newPartials) { @@ -58,6 +59,9 @@ bool Poly::noteOff(bool pedalHeld) { return false; } if (pedalHeld) { + if (state == POLY_Held) { + return false; + } state = POLY_Held; } else { startDecay(); @@ -171,4 +175,12 @@ void Poly::partialDeactivated(Partial *partial) { part->partialDeactivated(this); } +Poly *Poly::getNext() { + return next; +} + +void Poly::setNext(Poly *poly) { + next = poly; +} + } diff --git a/audio/softsynth/mt32/Poly.h b/audio/softsynth/mt32/Poly.h index cd15a776f5..e25b6d8993 100644 --- a/audio/softsynth/mt32/Poly.h +++ b/audio/softsynth/mt32/Poly.h @@ -41,6 +41,8 @@ private: Partial *partials[4]; + Poly *next; + public: Poly(Part *part); void reset(unsigned int key, unsigned int velocity, bool sustain, Partial **partials); @@ -60,6 +62,9 @@ public: bool isActive() const; void partialDeactivated(Partial *partial); + + Poly *getNext(); + void setNext(Poly *poly); }; } diff --git a/audio/softsynth/mt32/ROMInfo.cpp b/audio/softsynth/mt32/ROMInfo.cpp new file mode 100644 index 0000000000..514bc23496 --- /dev/null +++ b/audio/softsynth/mt32/ROMInfo.cpp @@ -0,0 +1,111 @@ +/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher + * Copyright (C) 2011 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 <cstring> +#include "ROMInfo.h" + +namespace MT32Emu { + +// Known ROMs +static const ROMInfo CTRL_MT32_V1_04 = {65536, "5a5cb5a77d7d55ee69657c2f870416daed52dea7", ROMInfo::Control, "ctrl_mt32_1_04", "MT-32 Control v1.04", ROMInfo::Full, NULL, NULL}; +static const ROMInfo CTRL_MT32_V1_05 = {65536, "e17a3a6d265bf1fa150312061134293d2b58288c", ROMInfo::Control, "ctrl_mt32_1_05", "MT-32 Control v1.05", ROMInfo::Full, NULL, NULL}; +static const ROMInfo CTRL_MT32_V1_06 = {65536, "a553481f4e2794c10cfe597fef154eef0d8257de", ROMInfo::Control, "ctrl_mt32_1_06", "MT-32 Control v1.06", ROMInfo::Full, NULL, NULL}; +static const ROMInfo CTRL_MT32_V1_07 = {65536, "b083518fffb7f66b03c23b7eb4f868e62dc5a987", ROMInfo::Control, "ctrl_mt32_1_07", "MT-32 Control v1.07", ROMInfo::Full, NULL, NULL}; +static const ROMInfo CTRL_MT32_BLUER = {65536, "7b8c2a5ddb42fd0732e2f22b3340dcf5360edf92", ROMInfo::Control, "ctrl_mt32_bluer", "MT-32 Control BlueRidge", ROMInfo::Full, NULL, NULL}; + +static const ROMInfo CTRL_CM32L_V1_00 = {65536, "73683d585cd6948cc19547942ca0e14a0319456d", ROMInfo::Control, "ctrl_cm32l_1_00", "CM-32L/LAPC-I Control v1.00", ROMInfo::Full, NULL, NULL}; +static const ROMInfo CTRL_CM32L_V1_02 = {65536, "a439fbb390da38cada95a7cbb1d6ca199cd66ef8", ROMInfo::Control, "ctrl_cm32l_1_02", "CM-32L/LAPC-I Control v1.02", ROMInfo::Full, NULL, NULL}; + +static const ROMInfo PCM_MT32 = {524288, "f6b1eebc4b2d200ec6d3d21d51325d5b48c60252", ROMInfo::PCM, "pcm_mt32", "MT-32 PCM ROM", ROMInfo::Full, NULL, NULL}; +static const ROMInfo PCM_CM32L = {1048576, "289cc298ad532b702461bfc738009d9ebe8025ea", ROMInfo::PCM, "pcm_cm32l", "CM-32L/CM-64/LAPC-I PCM ROM", ROMInfo::Full, NULL, NULL}; + +static const ROMInfo * const ROM_INFOS[] = { + &CTRL_MT32_V1_04, + &CTRL_MT32_V1_05, + &CTRL_MT32_V1_06, + &CTRL_MT32_V1_07, + &CTRL_MT32_BLUER, + &CTRL_CM32L_V1_00, + &CTRL_CM32L_V1_02, + &PCM_MT32, + &PCM_CM32L, + NULL}; + +const ROMInfo* ROMInfo::getROMInfo(Common::File *file) { + size_t fileSize = file->size(); + // We haven't added the SHA1 checksum code in ScummVM, as the file size + // suffices for our needs for now. + //const char *fileDigest = file->getSHA1(); + for (int i = 0; ROM_INFOS[i] != NULL; i++) { + const ROMInfo *romInfo = ROM_INFOS[i]; + if (fileSize == romInfo->fileSize /*&& !strcmp(fileDigest, romInfo->sha1Digest)*/) { + return romInfo; + } + } + return NULL; +} + +void ROMInfo::freeROMInfo(const ROMInfo *romInfo) { + (void) romInfo; +} + +static int getROMCount() { + int count; + for(count = 0; ROM_INFOS[count] != NULL; count++) { + } + return count; +} + +const ROMInfo** ROMInfo::getROMInfoList(unsigned int types, unsigned int pairTypes) { + const ROMInfo **romInfoList = new const ROMInfo*[getROMCount() + 1]; + const ROMInfo **currentROMInList = romInfoList; + for(int i = 0; ROM_INFOS[i] != NULL; i++) { + const ROMInfo *romInfo = ROM_INFOS[i]; + if ((types & (1 << romInfo->type)) && (pairTypes & (1 << romInfo->pairType))) { + *currentROMInList++ = romInfo; + } + } + *currentROMInList = NULL; + return romInfoList; +} + +void ROMInfo::freeROMInfoList(const ROMInfo **romInfoList) { + delete[] romInfoList; +} + +const ROMImage* ROMImage::makeROMImage(Common::File *file) { + ROMImage *romImage = new ROMImage; + romImage->file = file; + romImage->romInfo = ROMInfo::getROMInfo(romImage->file); + return romImage; +} + +void ROMImage::freeROMImage(const ROMImage *romImage) { + ROMInfo::freeROMInfo(romImage->romInfo); + delete romImage; +} + + +Common::File* ROMImage::getFile() const { + return file; +} + +const ROMInfo* ROMImage::getROMInfo() const { + return romInfo; +} + +} diff --git a/audio/softsynth/mt32/ROMInfo.h b/audio/softsynth/mt32/ROMInfo.h new file mode 100644 index 0000000000..2ffd2b72c6 --- /dev/null +++ b/audio/softsynth/mt32/ROMInfo.h @@ -0,0 +1,77 @@ +/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher + * Copyright (C) 2011 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_ROMINFO_H +#define MT32EMU_ROMINFO_H + +//#include <cstddef> +#include "common/file.h" + +namespace MT32Emu { + +// Defines vital info about ROM file to be used by synth and applications + +struct ROMInfo { +public: + size_t fileSize; + const char *sha1Digest; + enum Type {PCM, Control, Reverb} type; + const char *shortName; + const char *description; + enum PairType {Full, FirstHalf, SecondHalf, Mux0, Mux1} pairType; + ROMInfo *pairROMInfo; + void *controlROMInfo; + + // Returns a ROMInfo struct by inspecting the size and the SHA1 hash + static const ROMInfo* getROMInfo(Common::File *file); + + // Currently no-op + static void freeROMInfo(const ROMInfo *romInfo); + + // Allows retrieving a NULL-terminated list of ROMInfos for a range of types and pairTypes + // (specified by bitmasks) + // Useful for GUI/console app to output information on what ROMs it supports + static const ROMInfo** getROMInfoList(unsigned int types, unsigned int pairTypes); + + // Frees the list of ROMInfos given + static void freeROMInfoList(const ROMInfo **romInfos); +}; + +// Synth::open() is to require a full control ROMImage and a full PCM ROMImage to work + +class ROMImage { +private: + Common::File *file; + const ROMInfo *romInfo; + +public: + + // Creates a ROMImage object given a ROMInfo and a File. Keeps a reference + // to the File and ROMInfo given, which must be freed separately by the user + // after the ROMImage is freed + static const ROMImage* makeROMImage(Common::File *file); + + // Must only be done after all Synths using the ROMImage are deleted + static void freeROMImage(const ROMImage *romImage); + + Common::File *getFile() const; + const ROMInfo *getROMInfo() const; +}; + +} + +#endif diff --git a/audio/softsynth/mt32/Synth.cpp b/audio/softsynth/mt32/Synth.cpp index 7a1b5c2275..7f4ba44999 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 @@ -36,7 +38,7 @@ namespace MT32Emu { -const ControlROMMap ControlROMMaps[7] = { +static const ControlROMMap ControlROMMaps[7] = { // ID IDc IDbytes PCMmap PCMc tmbrA tmbrAO, tmbrAC tmbrB tmbrBO, tmbrBC tmbrR trC rhythm rhyC rsrv panpot prog rhyMax patMax sysMax timMax {0x4014, 22, "\000 ver1.04 14 July 87 ", 0x3000, 128, 0x8000, 0x0000, false, 0xC000, 0x4000, false, 0x3200, 30, 0x73A6, 85, 0x57C7, 0x57E2, 0x57D0, 0x5252, 0x525E, 0x526E, 0x520A}, {0x4014, 22, "\000 ver1.05 06 Aug, 87 ", 0x3000, 128, 0x8000, 0x0000, false, 0xC000, 0x4000, false, 0x3200, 30, 0x7414, 85, 0x57C7, 0x57E2, 0x57D0, 0x5252, 0x525E, 0x526E, 0x520A}, @@ -140,22 +142,36 @@ Bit8u Synth::calcSysexChecksum(const Bit8u *data, Bit32u len, Bit8u checksum) { return checksum; } -Synth::Synth() { +Synth::Synth(ReportHandler *useReportHandler) { isOpen = false; 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 (useReportHandler == NULL) { + reportHandler = new ReportHandler; + isDefaultReportHandler = true; + } else { + reportHandler = useReportHandler; + isDefaultReportHandler = false; + } + +#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); @@ -170,31 +186,49 @@ Synth::~Synth() { for (int i = 0; i < 4; i++) { delete reverbModels[i]; } + if (isDefaultReportHandler) { + delete reportHandler; + } +} + +void ReportHandler::showLCDMessage(const char *data) { + printf("WRITE-LCD: %s", data); + printf("\n"); +} + +void ReportHandler::printDebug(const char *fmt, va_list list) { + vprintf(fmt, list); + printf("\n"); } -int Synth::report(ReportType type, const void *data) { - if (myProp.report != NULL) { - return myProp.report(myProp.userData, type, data); +void Synth::partStateChanged(int partNum, bool isPartActive) { + reportHandler->onPartStateChanged(partNum, isPartActive); +} + +void Synth::polyStateChanged(int partNum) { + reportHandler->onPolyStateChanged(partNum); +} + +void Synth::partialStateChanged(const Partial * const partial, int oldPartialPhase, int newPartialPhase) { + for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + if (getPartial(i) == partial) { + reportHandler->onPartialStateChanged(i, oldPartialPhase, newPartialPhase); + break; + } } - return 0; } -unsigned int Synth::getSampleRate() const { - return myProp.sampleRate; +void Synth::newTimbreSet(int partNum, char patchName[]) { + reportHandler->onProgramChanged(partNum, patchName); } void Synth::printDebug(const char *fmt, ...) { va_list ap; va_start(ap, fmt); - if (myProp.printDebug != NULL) { - myProp.printDebug(myProp.userData, fmt, ap); - } else { #if MT32EMU_DEBUG_SAMPLESTAMPS > 0 - printf("[%u] ", renderedSampleCount); + reportHandler->printDebug("[%u] ", renderedSampleCount); #endif - vprintf(fmt, ap); - printf("\n"); - } + reportHandler->printDebug(fmt, ap); va_end(ap); } @@ -244,80 +278,60 @@ void Synth::setReverbOutputGain(float newReverbOutputGain) { reverbOutputGain = newReverbOutputGain; } -Common::File *Synth::openFile(const char *filename) { - if (myProp.openFile != NULL) { - return myProp.openFile(myProp.userData, filename); - } - char pathBuf[2048]; - if (myProp.baseDir != NULL) { - strcpy(&pathBuf[0], myProp.baseDir); - strcat(&pathBuf[0], filename); - filename = pathBuf; - } - Common::File *file = new Common::File(); - if (!file->open(filename)) { - delete file; - return NULL; - } - return file; -} - -void Synth::closeFile(Common::File *file) { - if (myProp.closeFile != NULL) { - myProp.closeFile(myProp.userData, file); - } else { - file->close(); - delete file; - } -} - -LoadResult Synth::loadControlROM(const char *filename) { - Common::File *file = openFile(filename); // ROM File - if (file == NULL) { - return LoadResult_NotFound; - } - size_t fileSize = file->size(); - if (fileSize != CONTROL_ROM_SIZE) { - printDebug("Control ROM file %s size mismatch: %i", filename, fileSize); +bool Synth::loadControlROM(const ROMImage &controlROMImage) { + if (&controlROMImage == NULL) return false; + Common::File *file = controlROMImage.getFile(); + const ROMInfo *controlROMInfo = controlROMImage.getROMInfo(); + if ((controlROMInfo == NULL) + || (controlROMInfo->type != ROMInfo::Control) + || (controlROMInfo->pairType != ROMInfo::Full)) { + return false; } +#if MT32EMU_MONITOR_INIT + printDebug("Found Control ROM: %s, %s", controlROMInfo->shortName, controlROMInfo->description); +#endif file->read(controlROMData, CONTROL_ROM_SIZE); - if (file->err()) { - closeFile(file); - return LoadResult_Unreadable; - } - closeFile(file); // Control ROM successfully loaded, now check whether it's a known type controlROMMap = NULL; for (unsigned int i = 0; i < sizeof(ControlROMMaps) / sizeof(ControlROMMaps[0]); i++) { if (memcmp(&controlROMData[ControlROMMaps[i].idPos], ControlROMMaps[i].idBytes, ControlROMMaps[i].idLen) == 0) { controlROMMap = &ControlROMMaps[i]; - return LoadResult_OK; + return true; } } - printDebug("%s does not match a known control ROM type", filename); - return LoadResult_Invalid; +#if MT32EMU_MONITOR_INIT + printDebug("Control ROM failed to load"); +#endif + return false; } -LoadResult Synth::loadPCMROM(const char *filename) { - Common::File *file = openFile(filename); // ROM File - if (file == NULL) { - return LoadResult_NotFound; +bool Synth::loadPCMROM(const ROMImage &pcmROMImage) { + if (&pcmROMImage == NULL) return false; + Common::File *file = pcmROMImage.getFile(); + const ROMInfo *pcmROMInfo = pcmROMImage.getROMInfo(); + if ((pcmROMInfo == NULL) + || (pcmROMInfo->type != ROMInfo::PCM) + || (pcmROMInfo->pairType != ROMInfo::Full)) { + return false; } +#if MT32EMU_MONITOR_INIT + printDebug("Found PCM ROM: %s, %s", pcmROMInfo->shortName, pcmROMInfo->description); +#endif size_t fileSize = file->size(); - if (fileSize < (size_t)(2 * pcmROMSize)) { - printDebug("PCM ROM file is too short (expected %d, got %d)", 2 * pcmROMSize, fileSize); - closeFile(file); - return LoadResult_Invalid; - } - if (file->err()) { - closeFile(file); - return LoadResult_Unreadable; + if (fileSize != (2 * pcmROMSize)) { +#if MT32EMU_MONITOR_INIT + printDebug("PCM ROM file has wrong size (expected %d, got %d)", 2 * pcmROMSize, fileSize); +#endif + return false; } - LoadResult rc = LoadResult_OK; - for (int i = 0; i < pcmROMSize; i++) { - Bit8u s = file->readByte(); - Bit8u c = file->readByte(); + + byte *buffer = new byte[file->size()]; + file->read(buffer, file->size()); + const byte *fileData = buffer; + for (size_t i = 0; i < pcmROMSize; i++) { + Bit8u s = *(fileData++); + Bit8u c = *(fileData++); int order[16] = {0, 9, 1, 2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15, 8}; @@ -343,16 +357,18 @@ LoadResult Synth::loadPCMROM(const char *filename) { pcmROMData[i] = lin; } - closeFile(file); - return rc; + + delete[] buffer; + + return true; } 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; - int rLenExp = (tps[i].len & 0x70) >> 4; - int rLen = 0x800 << rLenExp; + size_t rAddr = tps[i].pos * 0x800; + size_t rLenExp = (tps[i].len & 0x70) >> 4; + size_t rLen = 0x800 << rLenExp; if (rAddr + rLen > pcmROMSize) { printDebug("Control ROM error: Wave map entry %d points to invalid PCM address 0x%04X, length 0x%04X", i, rAddr, rLen); return false; @@ -414,26 +430,19 @@ bool Synth::initTimbres(Bit16u mapAddress, Bit16u offset, int count, int startTi return true; } -bool Synth::open(SynthProperties &useProp) { +bool Synth::open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage) { if (isOpen) { return false; } prerenderReadIx = prerenderWriteIx = 0; - myProp = useProp; #if MT32EMU_MONITOR_INIT printDebug("Initialising Constant Tables"); #endif - tables.init(); #if !MT32EMU_REDUCE_REVERB_MEMORY for (int i = 0; i < 4; i++) { reverbModels[i]->open(useProp.sampleRate); } #endif - if (useProp.baseDir != NULL) { - char *baseDirCopy = new char[strlen(useProp.baseDir) + 1]; - strcpy(baseDirCopy, useProp.baseDir); - myProp.baseDir = baseDirCopy; - } // This is to help detect bugs memset(&mt32ram, '?', sizeof(mt32ram)); @@ -441,12 +450,10 @@ bool Synth::open(SynthProperties &useProp) { #if MT32EMU_MONITOR_INIT printDebug("Loading Control ROM"); #endif - if (loadControlROM("CM32L_CONTROL.ROM") != LoadResult_OK) { - if (loadControlROM("MT32_CONTROL.ROM") != LoadResult_OK) { - printDebug("Init Error - Missing or invalid MT32_CONTROL.ROM"); - //report(ReportType_errorControlROM, &errno); - return false; - } + if (!loadControlROM(controlROMImage)) { + printDebug("Init Error - Missing or invalid Control ROM image"); + reportHandler->onErrorControlROM(); + return false; } initMemoryRegions(); @@ -460,12 +467,10 @@ bool Synth::open(SynthProperties &useProp) { #if MT32EMU_MONITOR_INIT printDebug("Loading PCM ROM"); #endif - if (loadPCMROM("CM32L_PCM.ROM") != LoadResult_OK) { - if (loadPCMROM("MT32_PCM.ROM") != LoadResult_OK) { - printDebug("Init Error - Missing MT32_PCM.ROM"); - //report(ReportType_errorPCMROM, &errno); - return false; - } + if (!loadPCMROM(pcmROMImage)) { + printDebug("Init Error - Missing PCM ROM image"); + reportHandler->onErrorPCMROM(); + return false; } #if MT32EMU_MONITOR_INIT @@ -594,9 +599,6 @@ void Synth::close() { parts[i] = NULL; } - delete[] myProp.baseDir; - myProp.baseDir = NULL; - delete[] pcmWaves; delete[] pcmROMData; @@ -627,6 +629,11 @@ void Synth::playMsg(Bit32u msg) { return; } playMsgOnPart(part, code, note, velocity); + + // This ensures minimum 1-sample delay between sequential MIDI events + // Without this, a sequence of NoteOn and immediately succeeding NoteOff messages is always silent + // Technically, it's also impossible to send events through the MIDI interface faster than about each millisecond + prerender(); } void Synth::playMsgOnPart(unsigned char part, unsigned char code, unsigned char note, unsigned char velocity) { @@ -1178,7 +1185,7 @@ void Synth::writeMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u le case MR_System: region->write(0, off, data, len); - report(ReportType_devReconfig, NULL); + reportHandler->onDeviceReconfig(); // FIXME: We haven't properly confirmed any of this behaviour // In particular, we tend to reset things such as reverb even if the write contained // the same parameters as were already set, which may be wrong. @@ -1216,7 +1223,7 @@ void Synth::writeMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u le #if MT32EMU_MONITOR_SYSEX > 0 printDebug("WRITE-LCD: %s", buf); #endif - report(ReportType_lcdMessage, buf); + reportHandler->showLCDMessage(buf); break; case MR_Reset: reset(); @@ -1244,9 +1251,9 @@ void Synth::refreshSystemReverbParameters() { #endif return; } - report(ReportType_newReverbMode, &mt32ram.system.reverbMode); - report(ReportType_newReverbTime, &mt32ram.system.reverbTime); - report(ReportType_newReverbLevel, &mt32ram.system.reverbLevel); + reportHandler->onNewReverbMode(mt32ram.system.reverbMode); + reportHandler->onNewReverbTime(mt32ram.system.reverbTime); + reportHandler->onNewReverbLevel(mt32ram.system.reverbLevel); ReverbModel *newReverbModel = reverbModels[mt32ram.system.reverbMode]; #if MT32EMU_REDUCE_REVERB_MEMORY @@ -1254,7 +1261,7 @@ void Synth::refreshSystemReverbParameters() { if (reverbModel != NULL) { reverbModel->close(); } - newReverbModel->open(myProp.sampleRate); + newReverbModel->open(); } #endif reverbModel = newReverbModel; @@ -1309,7 +1316,7 @@ void Synth::reset() { #if MT32EMU_MONITOR_SYSEX > 0 printDebug("RESET"); #endif - report(ReportType_devReset, NULL); + reportHandler->onDeviceReset(); partialManager->deactivateAll(); mt32ram = mt32default; for (int i = 0; i < 9; i++) { diff --git a/audio/softsynth/mt32/Synth.h b/audio/softsynth/mt32/Synth.h index ccabce7282..91375c0fc0 100644 --- a/audio/softsynth/mt32/Synth.h +++ b/audio/softsynth/mt32/Synth.h @@ -26,6 +26,7 @@ class TableInitialiser; class Partial; class PartialManager; class Part; +class ROMImage; /** * Methods for emulating the connection between the LA32 and the DAC, which involves @@ -57,71 +58,6 @@ enum DACInputMode { DACInputMode_GENERATION2 }; -enum ReportType { - // Errors - ReportType_errorControlROM = 1, - ReportType_errorPCMROM, - ReportType_errorSampleRate, - - // Progress - ReportType_progressInit, - - // HW spec - ReportType_availableSSE, - ReportType_available3DNow, - ReportType_usingSSE, - ReportType_using3DNow, - - // General info - ReportType_lcdMessage, - ReportType_devReset, - ReportType_devReconfig, - ReportType_newReverbMode, - ReportType_newReverbTime, - ReportType_newReverbLevel -}; - -enum LoadResult { - LoadResult_OK, - LoadResult_NotFound, - LoadResult_Unreadable, - LoadResult_Invalid -}; - -struct SynthProperties { - // Sample rate to use in mixing - unsigned int sampleRate; - - // Deprecated - ignored. Use Synth::setReverbEnabled() instead. - bool useReverb; - // Deprecated - ignored. Use Synth::setReverbOverridden() instead. - bool useDefaultReverb; - // Deprecated - ignored. Use Synth::playSysex*() to configure reverb instead. - unsigned char reverbType; - // Deprecated - ignored. Use Synth::playSysex*() to configure reverb instead. - unsigned char reverbTime; - // Deprecated - ignored. Use Synth::playSysex*() to configure reverb instead. - unsigned char reverbLevel; - // The name of the directory in which the ROM and data files are stored (with trailing slash/backslash) - // Not used if "openFile" is set. May be NULL in any case. - const char *baseDir; - // This is used as the first argument to all callbacks - void *userData; - // Callback for reporting various errors and information. May be NULL - int (*report)(void *userData, ReportType type, const void *reportData); - // Callback for debug messages, in vprintf() format - void (*printDebug)(void *userData, const char *fmt, va_list list); - // Callback for providing an implementation of File, opened and ready for use - // May be NULL, in which case a default implementation will be used. - Common::File *(*openFile)(void *userData, const char *filename); - // Callback for closing a File. May be NULL, in which case the File will automatically be close()d/deleted. - void (*closeFile)(void *userData, Common::File *file); -}; - -// This is the specification of the Callback routine used when calling the RecalcWaveforms -// function -typedef void (*recalcStatusCallback)(int percDone); - typedef void (*FloatToBit16sFunc)(Bit16s *target, const float *source, Bit32u len, float outputGain); const Bit8u SYSEX_MANUFACTURER_ROLAND = 0x41; @@ -179,6 +115,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; @@ -278,7 +221,7 @@ class ReverbModel { public: virtual ~ReverbModel() {} // After construction or a close(), open() will be called at least once before any other call (with the exception of close()). - virtual void open(unsigned int sampleRate) = 0; + virtual void open() = 0; // May be called multiple times without an open() in between. virtual void close() = 0; virtual void setParameters(Bit8u time, Bit8u level) = 0; @@ -286,6 +229,32 @@ public: virtual bool isActive() const = 0; }; +class ReportHandler { +friend class Synth; + +public: + virtual ~ReportHandler() {} + +protected: + + // Callback for debug messages, in vprintf() format + virtual void printDebug(const char *fmt, va_list list); + + // Callbacks for reporting various errors and information + virtual void onErrorControlROM() {} + virtual void onErrorPCMROM() {} + virtual void showLCDMessage(const char *message); + virtual void onDeviceReset() {} + virtual void onDeviceReconfig() {} + virtual void onNewReverbMode(Bit8u /* mode */) {} + virtual void onNewReverbTime(Bit8u /* time */) {} + virtual void onNewReverbLevel(Bit8u /* level */) {} + virtual void onPartStateChanged(int /* partNum */, bool /* isActive */) {} + virtual void onPolyStateChanged(int /* partNum */) {} + virtual void onPartialStateChanged(int /* partialNum */, int /* oldPartialPhase */, int /* newPartialPhase */) {} + virtual void onProgramChanged(int /* partNum */, char * /* patchName */) {} +}; + class Synth { friend class Part; friend class RhythmPart; @@ -315,13 +284,12 @@ 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 + size_t pcmROMSize; // This is in 16-bit samples, therefore half the number of bytes in the ROM Bit8s chantable[32]; Bit32u renderedSampleCount; - Tables tables; MemParams mt32ram, mt32default; @@ -337,6 +305,9 @@ private: bool isOpen; + bool isDefaultReportHandler; + ReportHandler *reportHandler; + PartialManager *partialManager; Part *parts[9]; @@ -369,8 +340,6 @@ private: int prerenderReadIx; int prerenderWriteIx; - SynthProperties myProp; - bool prerender(); void copyPrerender(Bit16s *nonReverbLeft, Bit16s *nonReverbRight, Bit16s *reverbDryLeft, Bit16s *reverbDryRight, Bit16s *reverbWetLeft, Bit16s *reverbWetRight, Bit32u pos, Bit32u len); void checkPrerender(Bit16s *nonReverbLeft, Bit16s *nonReverbRight, Bit16s *reverbDryLeft, Bit16s *reverbDryRight, Bit16s *reverbWetLeft, Bit16s *reverbWetRight, Bit32u &pos, Bit32u &len); @@ -384,8 +353,8 @@ private: void writeMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, const Bit8u *data); void readMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, Bit8u *data); - LoadResult loadControlROM(const char *filename); - LoadResult loadPCMROM(const char *filename); + bool loadControlROM(const ROMImage &controlROMImage); + bool loadPCMROM(const ROMImage &pcmROMImage); bool initPCMList(Bit16u mapAddress, Bit16u count); bool initTimbres(Bit16u mapAddress, Bit16u offset, int timbreCount, int startTimbre, bool compressed); @@ -399,24 +368,25 @@ private: void refreshSystem(); void reset(); - unsigned int getSampleRate() const; - void printPartialUsage(unsigned long sampleOffset = 0); -protected: - int report(ReportType type, const void *reportData); - Common::File *openFile(const char *filename); - void closeFile(Common::File *file); + + void partStateChanged(int partNum, bool isPartActive); + void polyStateChanged(int partNum); + void partialStateChanged(const Partial * const partial, int oldPartialPhase, int newPartialPhase); + void newTimbreSet(int partNum, char patchName[]); void printDebug(const char *fmt, ...); public: static Bit8u calcSysexChecksum(const Bit8u *data, Bit32u len, Bit8u checksum); - Synth(); + // Optionally sets callbacks for reporting various errors, information and debug messages + Synth(ReportHandler *useReportHandler = NULL); ~Synth(); // Used to initialise the MT-32. Must be called before any other function. // Returns true if initialization was sucessful, otherwise returns false. - bool open(SynthProperties &useProp); + // controlROMImage and pcmROMImage represent Control and PCM ROM images for use by synth. + bool open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage); // Closes the MT-32 and deallocates any memory used by the synthesizer void close(void); diff --git a/audio/softsynth/mt32/TVA.cpp b/audio/softsynth/mt32/TVA.cpp index f3e3f7bbc7..19a01cfc73 100644 --- a/audio/softsynth/mt32/TVA.cpp +++ b/audio/softsynth/mt32/TVA.cpp @@ -30,10 +30,13 @@ namespace MT32Emu { static Bit8u biasLevelToAmpSubtractionCoeff[13] = {255, 187, 137, 100, 74, 54, 40, 29, 21, 15, 10, 5, 0}; TVA::TVA(const Partial *usePartial, LA32Ramp *useAmpRamp) : - partial(usePartial), ampRamp(useAmpRamp), system_(&usePartial->getSynth()->mt32ram.system) { + partial(usePartial), ampRamp(useAmpRamp), system_(&usePartial->getSynth()->mt32ram.system), phase(TVA_PHASE_DEAD) { } void TVA::startRamp(Bit8u newTarget, Bit8u newIncrement, int newPhase) { + if (newPhase != phase) { + partial->getSynth()->partialStateChanged(partial, phase, newPhase); + } target = newTarget; phase = newPhase; ampRamp->startRamp(newTarget, newIncrement); @@ -43,6 +46,9 @@ void TVA::startRamp(Bit8u newTarget, Bit8u newIncrement, int newPhase) { } void TVA::end(int newPhase) { + if (newPhase != phase) { + partial->getSynth()->partialStateChanged(partial, phase, newPhase); + } phase = newPhase; playing = false; #if MT32EMU_MONITOR_TVA >= 1 @@ -154,7 +160,7 @@ void TVA::reset(const Part *newPart, const TimbreParam::PartialParam *newPartial playing = true; - Tables *tables = &partial->getSynth()->tables; + const Tables *tables = &Tables::getInstance(); int key = partial->getPoly()->getKey(); int velocity = partial->getPoly()->getVelocity(); @@ -215,7 +221,7 @@ void TVA::recalcSustain() { return; } // We're sustaining. Recalculate all the values - Tables *tables = &partial->getSynth()->tables; + const Tables *tables = &Tables::getInstance(); int newTarget = calcBasicAmp(tables, partial, system_, partialParam, patchTemp, rhythmTemp, biasAmpSubtraction, veloAmpSubtraction, part->getExpression()); newTarget += partialParam->tva.envLevel[3]; // Since we're in TVA_PHASE_SUSTAIN at this point, we know that target has been reached and an interrupt fired, so we can rely on it being the current amp. @@ -241,7 +247,7 @@ int TVA::getPhase() const { } void TVA::nextPhase() { - Tables *tables = &partial->getSynth()->tables; + const Tables *tables = &Tables::getInstance(); if (phase >= TVA_PHASE_DEAD || !playing) { partial->getSynth()->printDebug("TVA::nextPhase(): Shouldn't have got here with phase %d, playing=%s", phase, playing ? "true" : "false"); @@ -274,7 +280,7 @@ void TVA::nextPhase() { } int newTarget; - int newIncrement = 0; + int newIncrement = 0; // Initialised to please compilers int envPointIndex = phase; if (!allLevelsZeroFromNowOn) { diff --git a/audio/softsynth/mt32/TVF.cpp b/audio/softsynth/mt32/TVF.cpp index 80b592ea67..47c4917632 100644 --- a/audio/softsynth/mt32/TVF.cpp +++ b/audio/softsynth/mt32/TVF.cpp @@ -117,7 +117,7 @@ void TVF::reset(const TimbreParam::PartialParam *newPartialParam, unsigned int b unsigned int key = partial->getPoly()->getKey(); unsigned int velocity = partial->getPoly()->getVelocity(); - Tables *tables = &partial->getSynth()->tables; + const Tables *tables = &Tables::getInstance(); baseCutoff = calcBaseCutoff(newPartialParam, basePitch, key); #if MT32EMU_MONITOR_TVF >= 1 @@ -179,7 +179,7 @@ void TVF::startDecay() { } void TVF::nextPhase() { - Tables *tables = &partial->getSynth()->tables; + const Tables *tables = &Tables::getInstance(); int newPhase = phase + 1; switch (newPhase) { diff --git a/audio/softsynth/mt32/TVP.cpp b/audio/softsynth/mt32/TVP.cpp index 0b339e8d71..5dc4ca6b66 100644 --- a/audio/softsynth/mt32/TVP.cpp +++ b/audio/softsynth/mt32/TVP.cpp @@ -47,12 +47,11 @@ static Bit16u keyToPitchTable[] = { TVP::TVP(const Partial *usePartial) : partial(usePartial), system_(&usePartial->getSynth()->mt32ram.system) { - unsigned int sampleRate = usePartial->getSynth()->myProp.sampleRate; // We want to do processing 4000 times per second. FIXME: This is pretty arbitrary. - maxCounter = sampleRate / 4000; + maxCounter = SAMPLE_RATE / 4000; // The timer runs at 500kHz. We only need to bother updating it every maxCounter samples, before we do processing. // This is how much to increment it by every maxCounter samples. - processTimerIncrement = 500000 * maxCounter / sampleRate; + processTimerIncrement = 500000 * maxCounter / SAMPLE_RATE; } static Bit16s keyToPitch(unsigned int key) { @@ -171,9 +170,14 @@ void TVP::updatePitch() { if (newPitch < 0) { newPitch = 0; } + +// Note: Temporary #ifdef until we have proper "quirk" configuration +// This is about right emulation of MT-32 GEN0 quirk exploited in Colonel's Bequest timbre "Lightning" +#ifndef MT32EMU_QUIRK_PITCH_ENVELOPE_OVERFLOW_MT32 if (newPitch > 59392) { newPitch = 59392; } +#endif pitch = (Bit16u)newPitch; // FIXME: We're doing this here because that's what the CM-32L does - we should probably move this somewhere more appropriate in future. diff --git a/audio/softsynth/mt32/Tables.cpp b/audio/softsynth/mt32/Tables.cpp index c9bd40b7a4..5353a74079 100644 --- a/audio/softsynth/mt32/Tables.cpp +++ b/audio/softsynth/mt32/Tables.cpp @@ -22,18 +22,14 @@ #include "mt32emu.h" #include "mmath.h" -using namespace MT32Emu; +namespace MT32Emu { -Tables::Tables() { - initialised = false; +const Tables &Tables::getInstance() { + static const Tables instance; + return instance; } -void Tables::init() { - if (initialised) { - return; - } - initialised = true; - +Tables::Tables() { int lf; for (lf = 0; lf <= 100; lf++) { // CONFIRMED:KG: This matches a ROM table found by Mok @@ -76,31 +72,9 @@ void Tables::init() { //synth->printDebug("%d: %d", i, pulseWidth100To255[i]); } - // Ratio of negative segment to wave length - for (int i = 0; i < 128; i++) { - // Formula determined from sample analysis. - float pt = 0.5f / 127.0f * i; - pulseLenFactor[i] = (1.241857812f - pt) * pt; // seems to be 2 ^ (5 / 16) = 1.241857812f - } - - for (int i = 0; i < 65536; i++) { - // Aka (slightly slower): EXP2F(pitchVal / 4096.0f - 16.0f) * 32000.0f - pitchToFreq[i] = EXP2F(i / 4096.0f - 1.034215715f); - } - - // found from sample analysis - for (int i = 0; i < 1024; i++) { - cutoffToCosineLen[i] = EXP2F(i / -128.0f); - } - - // found from sample analysis - for (int i = 0; i < 1024; i++) { - cutoffToFilterAmp[i] = EXP2F(-0.125f * (128.0f - i / 8.0f)); - } - - // found from sample analysis - for (int i = 0; i < 32; i++) { - resAmpMax[i] = EXP2F(1.0f - (32 - i) / 4.0f); + // The LA32 chip presumably has such a table inside as the internal computaions seem to be performed using fixed point math with 12-bit fractions + for (int i = 0; i < 4096; i++) { + exp2[i] = EXP2F(i / 4096.0f); } // found from sample analysis @@ -117,3 +91,5 @@ void Tables::init() { sinf10[i] = sin(FLOAT_PI * i / 2048.0f); } } + +} diff --git a/audio/softsynth/mt32/Tables.h b/audio/softsynth/mt32/Tables.h index a2b5ff5d56..c3e80e7e9b 100644 --- a/audio/softsynth/mt32/Tables.h +++ b/audio/softsynth/mt32/Tables.h @@ -20,14 +20,23 @@ namespace MT32Emu { +// Sample rate to use in mixing. With the progress of development, we've found way too many thing dependent. +// In order to achieve further advance in emulation accuracy, sample rate made fixed throughout the emulator. +// The output from the synth is supposed to be resampled to convert the sample rate. +const unsigned int SAMPLE_RATE = 32000; + const int MIDDLEC = 60; class Synth; class Tables { - bool initialised; +private: + Tables(); + Tables(Tables &); public: + static const Tables &getInstance(); + // Constant LUTs // CONFIRMED: This is used to convert several parameters to amp-modifying values in the TVA envelope: @@ -47,16 +56,9 @@ public: // CONFIRMED: Bit8u pulseWidth100To255[101]; - float pulseLenFactor[128]; - float pitchToFreq[65536]; - float cutoffToCosineLen[1024]; - float cutoffToFilterAmp[1024]; - float resAmpMax[32]; + float exp2[4096]; float resAmpFadeFactor[8]; float sinf10[5120]; - - Tables(); - void init(); }; } diff --git a/audio/softsynth/mt32/mmath.h b/audio/softsynth/mt32/mmath.h index 226d73e27e..25c79d57a9 100644 --- a/audio/softsynth/mt32/mmath.h +++ b/audio/softsynth/mt32/mmath.h @@ -52,6 +52,10 @@ static inline float EXP2F(float x) { #endif } +static inline float EXP2I(unsigned int i) { + return float(1 << (i >> 12)) * Tables::getInstance().exp2[i & 0x0FFF]; +} + static inline float EXP10F(float x) { return exp(FLOAT_LN_10 * x); } diff --git a/audio/softsynth/mt32/module.mk b/audio/softsynth/mt32/module.mk index 995e450076..ed6b0d33ed 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 \ @@ -9,6 +10,7 @@ MODULE_OBJS := \ Partial.o \ PartialManager.o \ Poly.o \ + ROMInfo.o \ Synth.o \ TVA.o \ TVF.o \ diff --git a/audio/softsynth/mt32/mt32emu.h b/audio/softsynth/mt32/mt32emu.h index 091819b95c..ae5f4955b1 100644 --- a/audio/softsynth/mt32/mt32emu.h +++ b/audio/softsynth/mt32/mt32emu.h @@ -59,13 +59,18 @@ #define MT32EMU_MONITOR_TVA 0 #define MT32EMU_MONITOR_TVF 0 - -// 0: Use LUTs to speedup WG -// 1: Use precise float math +// The WG algorithm involves dozens of transcendent maths, e.g. exponents and trigonometry. +// Unfortunately, the majority of systems perform such computations inefficiently, +// standard math libs and FPUs make no optimisations for single precision floats, +// and use no LUTs to speedup computing internal taylor series. Though, there're rare exceptions, +// and there's a hope it will become common soon. +// So, this is the crucial point of speed optimisations. We have now eliminated all the transcendent maths +// within the critical path and use LUTs instead. +// Besides, since the LA32 chip is assumed to use similar LUTs inside, the overall emulation accuracy should be better. +// 0: Use LUTs to speedup WG. Most common setting. You can expect about 50% performance boost. +// 1: Use precise float math. Use this setting to achieve more accurate wave generator. If your system performs better with this setting, it is really notable. :) #define MT32EMU_ACCURATE_WG 0 -#define MT32EMU_USE_EXTINT 0 - // Configuration // The maximum number of partials playing simultaneously #define MT32EMU_MAX_PARTIALS 32 @@ -77,9 +82,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 { @@ -109,6 +115,7 @@ const unsigned int MAX_PRERENDER_SAMPLES = 1024; #include "TVF.h" #include "Partial.h" #include "Part.h" +#include "ROMInfo.h" #include "Synth.h" #endif |
