diff options
Diffstat (limited to 'audio')
29 files changed, 730 insertions, 1742 deletions
diff --git a/audio/softsynth/mt32/AReverbModel.cpp b/audio/softsynth/mt32/AReverbModel.cpp deleted file mode 100644 index 1d63832157..0000000000 --- a/audio/softsynth/mt32/AReverbModel.cpp +++ /dev/null @@ -1,277 +0,0 @@ -/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011, 2012, 2013 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 == 1 - -#include "AReverbModel.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; - -// 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; -} - -float RingBuffer::next() { - if (++index >= size) { - index = 0; - } - return buffer[index]; -} - -bool RingBuffer::isEmpty() const { - if (buffer == NULL) return true; - - float *buf = buffer; - float max = 0.001f; - for (Bit32u i = 0; i < size; i++) { - if ((*buf < -max) || (*buf > max)) return false; - buf++; - } - return true; -} - -void RingBuffer::mute() { - float *buf = buffer; - for (Bit32u i = 0; i < size; i++) { - *buf++ = 0; - } -} - -AllpassFilter::AllpassFilter(const Bit32u useSize) : RingBuffer(useSize) {} - -float AllpassFilter::process(const float in) { - // This model corresponds to the allpass filter implementation of the real CM-32L device - // found from sample analysis - - const float bufferOut = next(); - - // store input - feedback / 2 - buffer[index] = in - 0.5f * bufferOut; - - // return buffer output + feedforward / 2 - return bufferOut + 0.5f * buffer[index]; -} - -CombFilter::CombFilter(const Bit32u useSize) : RingBuffer(useSize) {} - -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]; - - // prepare input + feedback - float filterIn = in + next() * feedbackFactor; - - // store input + feedback processed by a low-pass filter - buffer[index] = filterFactor * last - filterIn; -} - -float CombFilter::getOutputAt(const Bit32u outIndex) const { - return buffer[(size + index - outIndex) % size]; -} - -void CombFilter::setFeedbackFactor(const float useFeedbackFactor) { - feedbackFactor = useFeedbackFactor; -} - -void CombFilter::setFilterFactor(const float useFilterFactor) { - filterFactor = useFilterFactor; -} - -AReverbModel::AReverbModel(const ReverbMode mode) : allpasses(NULL), combs(NULL), currentSettings(*REVERB_SETTINGS[mode]) {} - -AReverbModel::~AReverbModel() { - close(); -} - -void AReverbModel::open() { - allpasses = new AllpassFilter*[NUM_ALLPASSES]; - for (Bit32u i = 0; i < NUM_ALLPASSES; i++) { - allpasses[i] = new AllpassFilter(currentSettings.allpassSizes[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(); -} - -void AReverbModel::close() { - if (allpasses != NULL) { - for (Bit32u i = 0; i < NUM_ALLPASSES; i++) { - if (allpasses[i] != NULL) { - delete allpasses[i]; - allpasses[i] = NULL; - } - } - delete[] allpasses; - allpasses = NULL; - } - if (combs != NULL) { - for (Bit32u i = 0; i < NUM_COMBS; i++) { - if (combs[i] != NULL) { - delete combs[i]; - combs[i] = 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_COMBS; i++) { - combs[i]->mute(); - } -} - -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 - 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 { - for (Bit32u i = 0; i < NUM_ALLPASSES; i++) { - if (!allpasses[i]->isEmpty()) return true; - } - for (Bit32u i = 0; i < NUM_COMBS; i++) { - if (!combs[i]->isEmpty()) return true; - } - return false; -} - -void AReverbModel::process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples) { - float dry, link, outL1; - - for (unsigned long i = 0; i < numSamples; i++) { - dry = wetLevel * (*inLeft + *inRight); - - // Get the last stored sample before processing in order not to loose it - link = combs[0]->getOutputAt(currentSettings.combSizes[0] - 1); - - combs[0]->process(-dry); - - 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 = 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++; - outLeft++; - outRight++; - } -} - -} - -#endif diff --git a/audio/softsynth/mt32/AReverbModel.h b/audio/softsynth/mt32/AReverbModel.h deleted file mode 100644 index c992478907..0000000000 --- a/audio/softsynth/mt32/AReverbModel.h +++ /dev/null @@ -1,87 +0,0 @@ -/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011, 2012, 2013 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_A_REVERB_MODEL_H -#define MT32EMU_A_REVERB_MODEL_H - -namespace MT32Emu { - -struct AReverbSettings { - 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; - const Bit32u size; - Bit32u index; - -public: - RingBuffer(const Bit32u size); - virtual ~RingBuffer(); - float next(); - bool isEmpty() const; - void mute(); -}; - -class AllpassFilter : public RingBuffer { -public: - AllpassFilter(const Bit32u size); - float process(const float in); -}; - -class CombFilter : public RingBuffer { - float feedbackFactor; - float filterFactor; - -public: - 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; - CombFilter **combs; - - const AReverbSettings ¤tSettings; - float lpfAmp; - float wetLevel; - void mute(); - -public: - AReverbModel(const ReverbMode mode); - ~AReverbModel(); - 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/BReverbModel.cpp b/audio/softsynth/mt32/BReverbModel.cpp index cc0219b741..c16f7f17da 100644 --- a/audio/softsynth/mt32/BReverbModel.cpp +++ b/audio/softsynth/mt32/BReverbModel.cpp @@ -15,10 +15,8 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ +//#include <memory.h> #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 @@ -62,9 +60,9 @@ 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}; + 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; @@ -103,7 +101,11 @@ static const BReverbSettings * const REVERB_SETTINGS[] = {&REVERB_MODE_0_SETTING // 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) { +static Sample weirdMul(Sample a, Bit8u addMask, Bit8u carryMask) { + (void)carryMask; +#if MT32EMU_USE_FLOAT_SAMPLES + return a * addMask / 256.0f; +#elif MT32EMU_BOSS_REVERB_PRECISE_MODE Bit8u mask = 0x80; Bit32s res = 0; for (int i = 0; i < 8; i++) { @@ -113,10 +115,13 @@ static Bit32s weirdMul(Bit32s a, Bit8u addMask, Bit8u carryMask) { mask >>= 1; } return res; +#else + return Sample(((Bit32s)a * addMask) >> 8); +#endif } RingBuffer::RingBuffer(Bit32u newsize) : size(newsize), index(0) { - buffer = new Bit16s[size]; + buffer = new Sample[size]; } RingBuffer::~RingBuffer() { @@ -124,7 +129,7 @@ RingBuffer::~RingBuffer() { buffer = NULL; } -Bit32s RingBuffer::next() { +Sample RingBuffer::next() { if (++index >= size) { index = 0; } @@ -134,52 +139,69 @@ Bit32s RingBuffer::next() { bool RingBuffer::isEmpty() const { if (buffer == NULL) return true; - Bit16s *buf = buffer; +#if MT32EMU_USE_FLOAT_SAMPLES + Sample max = 0.001f; +#else + Sample max = 8; +#endif + Sample *buf = buffer; for (Bit32u i = 0; i < size; i++) { - if (*buf < -8 || *buf > 8) return false; + if (*buf < -max || *buf > max) return false; buf++; } return true; } void RingBuffer::mute() { - Bit16s *buf = buffer; +#if MT32EMU_USE_FLOAT_SAMPLES + Sample *buf = buffer; for (Bit32u i = 0; i < size; i++) { *buf++ = 0; } +#else + memset(buffer, 0, size * sizeof(Sample)); +#endif } AllpassFilter::AllpassFilter(const Bit32u useSize) : RingBuffer(useSize) {} -Bit32s AllpassFilter::process(const Bit32s in) { +Sample AllpassFilter::process(const Sample in) { // This model corresponds to the allpass filter implementation of the real CM-32L device // found from sample analysis - Bit16s bufferOut = next(); + const Sample bufferOut = next(); +#if MT32EMU_USE_FLOAT_SAMPLES + // store input - feedback / 2 + buffer[index] = in - 0.5f * bufferOut; + + // return buffer output + feedforward / 2 + return bufferOut + 0.5f * buffer[index]; +#else // store input - feedback / 2 buffer[index] = in - (bufferOut >> 1); // return buffer output + feedforward / 2 return bufferOut + (buffer[index] >> 1); +#endif } CombFilter::CombFilter(const Bit32u useSize, const Bit32u useFilterFactor) : RingBuffer(useSize), filterFactor(useFilterFactor) {} -void CombFilter::process(const Bit32s in) { +void CombFilter::process(const Sample in) { // This model corresponds to the comb filter implementation of the real CM-32L device // the previously stored value - Bit32s last = buffer[index]; + const Sample last = buffer[index]; // prepare input + feedback - Bit32s filterIn = in + weirdMul(next(), feedbackFactor, 0xF0 /* Maybe 0x80 ? */); + const Sample 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 { +Sample CombFilter::getOutputAt(const Bit32u outIndex) const { return buffer[(size + index - outIndex) % size]; } @@ -190,15 +212,15 @@ void CombFilter::setFeedbackFactor(const Bit32u useFeedbackFactor) { DelayWithLowPassFilter::DelayWithLowPassFilter(const Bit32u useSize, const Bit32u useFilterFactor, const Bit32u useAmp) : CombFilter(useSize, useFilterFactor), amp(useAmp) {} -void DelayWithLowPassFilter::process(const Bit32s in) { +void DelayWithLowPassFilter::process(const Sample in) { // the previously stored value - Bit32s last = buffer[index]; + const Sample last = buffer[index]; // move to the next index next(); // low-pass filter process - Bit32s lpfOut = weirdMul(last, filterFactor, 0xFF) + in; + Sample lpfOut = weirdMul(last, filterFactor, 0xFF) + in; // store lpfOut multiplied by LPF amp factor buffer[index] = weirdMul(lpfOut, amp, 0xFF); @@ -206,26 +228,26 @@ void DelayWithLowPassFilter::process(const Bit32s in) { TapDelayCombFilter::TapDelayCombFilter(const Bit32u useSize, const Bit32u useFilterFactor) : CombFilter(useSize, useFilterFactor) {} -void TapDelayCombFilter::process(const Bit32s in) { +void TapDelayCombFilter::process(const Sample in) { // the previously stored value - Bit32s last = buffer[index]; + const Sample 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); + const Sample 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 { +Sample TapDelayCombFilter::getLeftOutput() const { return getOutputAt(outL + PROCESS_DELAY + MODE_3_ADDITIONAL_DELAY); } -Bit32s TapDelayCombFilter::getRightOutput() const { +Sample TapDelayCombFilter::getRightOutput() const { return getOutputAt(outR + PROCESS_DELAY + MODE_3_ADDITIONAL_DELAY); } @@ -327,14 +349,14 @@ bool BReverbModel::isActive() const { return false; } -void BReverbModel::process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples) { - Bit32s dry, link, outL1, outR1; +void BReverbModel::process(const Sample *inLeft, const Sample *inRight, Sample *outLeft, Sample *outRight, unsigned long numSamples) { + Sample dry; - for (unsigned long i = 0; i < numSamples; i++) { + while (numSamples > 0) { if (tapDelayMode) { - dry = Bit32s(*inLeft * 8192.0f) + Bit32s(*inRight * 8192.0f); + dry = *inLeft + *inRight; } else { - dry = Bit32s(*inLeft * 8192.0f) / 2 + Bit32s(*inRight * 8192.0f) / 2; + dry = *inLeft / 2 + *inRight / 2; } // Looks like dryAmp doesn't change in MT-32 but it does in CM-32L / LAPC-I @@ -343,44 +365,53 @@ void BReverbModel::process(const float *inLeft, const float *inRight, float *out 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; + *outLeft = weirdMul(comb->getLeftOutput(), wetLevel, 0xFF); + *outRight = weirdMul(comb->getRightOutput(), wetLevel, 0xFF); } else { - // Get the last stored sample before processing in order not to loose it - link = combs[0]->getOutputAt(currentSettings.combSizes[0] - 1); + // If the output position is equal to the comb size, get it now in order not to loose it + Sample link = combs[0]->getOutputAt(currentSettings.combSizes[0] - 1); // Entrance LPF. Note, comb.process() differs a bit here. combs[0]->process(dry); +#if !MT32EMU_USE_FLOAT_SAMPLES // This introduces reverb noise which actually makes output from the real Boss chip nondeterministic link = link - 1; +#endif 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; + Sample outL1 = combs[1]->getOutputAt(currentSettings.outLPositions[0] - 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; + Sample outL2 = combs[2]->getOutputAt(currentSettings.outLPositions[1]); + Sample outL3 = combs[3]->getOutputAt(currentSettings.outLPositions[2]); + Sample outR1 = combs[1]->getOutputAt(currentSettings.outRPositions[0]); + Sample outR2 = combs[2]->getOutputAt(currentSettings.outRPositions[1]); + Sample outR3 = combs[3]->getOutputAt(currentSettings.outRPositions[2]); + +#if MT32EMU_USE_FLOAT_SAMPLES + *outLeft = 1.5f * (outL1 + outL2) + outL3; + *outRight = 1.5f * (outR1 + outR2) + outR3; +#else + outL1 += outL1 >> 1; + outL2 += outL2 >> 1; + *outLeft = outL1 + outL2 + outL3; - 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; + outR2 += outR2 >> 1; + *outRight = outR1 + outR2 + outR3; +#endif + *outLeft = weirdMul(*outLeft, wetLevel, 0xFF); + *outRight = weirdMul(*outRight, wetLevel, 0xFF); } + numSamples--; inLeft++; inRight++; outLeft++; @@ -389,5 +420,3 @@ void BReverbModel::process(const float *inLeft, const float *inRight, float *out } } - -#endif diff --git a/audio/softsynth/mt32/BReverbModel.h b/audio/softsynth/mt32/BReverbModel.h index d6fcb73c13..7cf431db0d 100644 --- a/audio/softsynth/mt32/BReverbModel.h +++ b/audio/softsynth/mt32/BReverbModel.h @@ -36,14 +36,14 @@ struct BReverbSettings { class RingBuffer { protected: - Bit16s *buffer; + Sample *buffer; const Bit32u size; Bit32u index; public: RingBuffer(const Bit32u size); virtual ~RingBuffer(); - Bit32s next(); + Sample next(); bool isEmpty() const; void mute(); }; @@ -51,7 +51,7 @@ public: class AllpassFilter : public RingBuffer { public: AllpassFilter(const Bit32u size); - Bit32s process(const Bit32s in); + Sample process(const Sample in); }; class CombFilter : public RingBuffer { @@ -61,8 +61,8 @@ protected: 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; + virtual void process(const Sample in); + Sample getOutputAt(const Bit32u outIndex) const; void setFeedbackFactor(const Bit32u useFeedbackFactor); }; @@ -71,7 +71,7 @@ class DelayWithLowPassFilter : public CombFilter { public: DelayWithLowPassFilter(const Bit32u useSize, const Bit32u useFilterFactor, const Bit32u useAmp); - void process(const Bit32s in); + void process(const Sample in); void setFeedbackFactor(const Bit32u) {} }; @@ -81,13 +81,13 @@ class TapDelayCombFilter : public CombFilter { public: TapDelayCombFilter(const Bit32u useSize, const Bit32u useFilterFactor); - void process(const Bit32s in); - Bit32s getLeftOutput() const; - Bit32s getRightOutput() const; + void process(const Sample in); + Sample getLeftOutput() const; + Sample getRightOutput() const; void setOutputPositions(const Bit32u useOutL, const Bit32u useOutR); }; -class BReverbModel : public ReverbModel { +class BReverbModel { AllpassFilter **allpasses; CombFilter **combs; @@ -100,10 +100,12 @@ class BReverbModel : public ReverbModel { public: BReverbModel(const ReverbMode mode); ~BReverbModel(); + // After construction or a close(), open() must be called at least once before any other call (with the exception of close()). void open(); + // May be called multiple times without an open() in between. void close(); void setParameters(Bit8u time, Bit8u level); - void process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples); + void process(const Sample *inLeft, const Sample *inRight, Sample *outLeft, Sample *outRight, unsigned long numSamples); bool isActive() const; }; diff --git a/audio/softsynth/mt32/DelayReverb.cpp b/audio/softsynth/mt32/DelayReverb.cpp deleted file mode 100644 index d80c98acbc..0000000000 --- a/audio/softsynth/mt32/DelayReverb.cpp +++ /dev/null @@ -1,142 +0,0 @@ -/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011, 2012, 2013 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 <cmath> -//#include <cstring> -#include "mt32emu.h" -#include "DelayReverb.h" - -namespace MT32Emu { - -// 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... -static const Bit32u REVERB_TIMINGS[8][3]= { - // {leftDelay, rightDelay, feedbackDelay} - {402, 802, 801}, - {626, 1250, 1249}, - {962, 1922, 1921}, - {1490, 2978, 2977}, - {2258, 4514, 4513}, - {3474, 6946, 6945}, - {5282, 10562, 10561}, - {8002, 16002, 16001} -}; - -// 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; - setParameters(0, 0); -} - -DelayReverb::~DelayReverb() { - delete[] buf; -} - -void DelayReverb::open() { - if (buf == NULL) { - delete[] buf; - - buf = new float[BUFFER_SIZE]; - - recalcParameters(); - - // mute buffer - bufIx = 0; - if (buf != NULL) { - for (unsigned int i = 0; i < BUFFER_SIZE; i++) { - buf[i] = 0.0f; - } - } - } -} - -void DelayReverb::close() { - delete[] buf; - buf = NULL; -} - -// This method will always trigger a flush of the buffer -void DelayReverb::setParameters(Bit8u newTime, Bit8u newLevel) { - time = newTime; - level = newLevel; - recalcParameters(); -} - -void DelayReverb::recalcParameters() { - // Number of samples between impulse and eventual appearance on the left channel - delayLeft = REVERB_TIMINGS[time][0]; - // Number of samples between impulse and eventual appearance on the right channel - delayRight = REVERB_TIMINGS[time][1]; - // Number of samples between a response and that response feeding back/echoing - delayFeedback = REVERB_TIMINGS[time][2]; - - if (level < 3 || time < 6) { - feedback = REVERB_FEEDBACK / 256.0f; - } else { - feedback = REVERB_FEEDBACK67 / 256.0f; - } - - // 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; - - 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) % 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 lpfIn = amp * (inLeft[sampleIx] + inRight[sampleIx]) + feedback * buf[bufIxFeedback]; - - // Single-pole IIR filter found on real devices - buf[bufIx] = buf[bufIxPrev] * LPF_VALUE - lpfIn; - - outLeft[sampleIx] = buf[bufIxLeft]; - outRight[sampleIx] = buf[bufIxRight]; - - bufIx = (BUFFER_SIZE + bufIx - 1) % BUFFER_SIZE; - } -} - -bool DelayReverb::isActive() const { - 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 deleted file mode 100644 index c8003832b5..0000000000 --- a/audio/softsynth/mt32/DelayReverb.h +++ /dev/null @@ -1,50 +0,0 @@ -/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011, 2012, 2013 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_DELAYREVERB_H -#define MT32EMU_DELAYREVERB_H - -namespace MT32Emu { - -class DelayReverb : public ReverbModel { -private: - Bit8u time; - Bit8u level; - - Bit32u bufIx; - float *buf; - - Bit32u delayLeft; - Bit32u delayRight; - Bit32u delayFeedback; - - float amp; - float feedback; - - void recalcParameters(); - -public: - DelayReverb(); - ~DelayReverb(); - 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/FreeverbModel.cpp b/audio/softsynth/mt32/FreeverbModel.cpp deleted file mode 100644 index bd9c70b6f4..0000000000 --- a/audio/softsynth/mt32/FreeverbModel.cpp +++ /dev/null @@ -1,78 +0,0 @@ -/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011, 2012, 2013 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" -#include "FreeverbModel.h" - -#include "freeverb.h" - -namespace MT32Emu { - -FreeverbModel::FreeverbModel(float useScaleTuning, float useFiltVal, float useWet, Bit8u useRoom, float useDamp) { - freeverb = NULL; - scaleTuning = useScaleTuning; - filtVal = useFiltVal; - wet = useWet; - room = useRoom; - damp = useDamp; -} - -FreeverbModel::~FreeverbModel() { - delete freeverb; -} - -void FreeverbModel::open() { - if (freeverb == NULL) { - freeverb = new revmodel(scaleTuning); - } - freeverb->mute(); - - // entrance Lowpass filter factor - freeverb->setfiltval(filtVal); - - // decay speed of high frequencies in the wet signal - freeverb->setdamp(damp); -} - -void FreeverbModel::close() { - delete freeverb; - freeverb = NULL; -} - -void FreeverbModel::process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples) { - freeverb->process(inLeft, inRight, outLeft, outRight, numSamples); -} - -void FreeverbModel::setParameters(Bit8u time, Bit8u level) { - // wet signal level - // FIXME: need to implement some sort of reverb level ramping - freeverb->setwet((float)level / 7.0f * wet); - - // wet signal decay speed - static float roomTable[] = { - 0.25f, 0.37f, 0.54f, 0.71f, 0.78f, 0.86f, 0.93f, 1.00f, - -1.00f, -0.50f, 0.00f, 0.30f, 0.51f, 0.64f, 0.77f, 0.90f, - 0.50f, 0.57f, 0.70f, 0.77f, 0.85f, 0.93f, 0.96f, 1.01f}; - freeverb->setroomsize(roomTable[8 * room + time]); -} - -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 deleted file mode 100644 index 5ea11f1f40..0000000000 --- a/audio/softsynth/mt32/FreeverbModel.h +++ /dev/null @@ -1,44 +0,0 @@ -/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011, 2012, 2013 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_FREEVERB_MODEL_H -#define MT32EMU_FREEVERB_MODEL_H - -class revmodel; - -namespace MT32Emu { - -class FreeverbModel : public ReverbModel { - revmodel *freeverb; - float scaleTuning; - float filtVal; - float wet; - Bit8u room; - float damp; -public: - FreeverbModel(float useScaleTuning, float useFiltVal, float useWet, Bit8u useRoom, float useDamp); - ~FreeverbModel(); - 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/LegacyWaveGenerator.cpp b/audio/softsynth/mt32/LA32FloatWaveGenerator.cpp index 35ca975018..486942b75c 100644 --- a/audio/softsynth/mt32/LegacyWaveGenerator.cpp +++ b/audio/softsynth/mt32/LA32FloatWaveGenerator.cpp @@ -18,9 +18,7 @@ //#include <cmath> #include "mt32emu.h" #include "mmath.h" -#include "LegacyWaveGenerator.h" - -#if MT32EMU_ACCURATE_WG == 1 +#include "LA32FloatWaveGenerator.h" namespace MT32Emu { @@ -317,7 +315,7 @@ void LA32PartialPair::generateNextSample(const PairType useMaster, const Bit32u } } -Bit16s LA32PartialPair::nextOutSample() { +float LA32PartialPair::nextOutSample() { float outputSample; if (ringModulated) { float ringModulatedSample = masterOutputSample * slaveOutputSample; @@ -325,7 +323,7 @@ Bit16s LA32PartialPair::nextOutSample() { } else { outputSample = masterOutputSample + slaveOutputSample; } - return Bit16s(outputSample * 8192.0f); + return outputSample; } void LA32PartialPair::deactivate(const PairType useMaster) { @@ -343,5 +341,3 @@ bool LA32PartialPair::isActive(const PairType useMaster) const { } } - -#endif // #if MT32EMU_ACCURATE_WG == 1 diff --git a/audio/softsynth/mt32/LegacyWaveGenerator.h b/audio/softsynth/mt32/LA32FloatWaveGenerator.h index 81c1b9c713..9046160083 100644 --- a/audio/softsynth/mt32/LegacyWaveGenerator.h +++ b/audio/softsynth/mt32/LA32FloatWaveGenerator.h @@ -15,8 +15,6 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#if MT32EMU_ACCURATE_WG == 1 - #ifndef MT32EMU_LA32_WAVE_GENERATOR_H #define MT32EMU_LA32_WAVE_GENERATOR_H @@ -130,7 +128,7 @@ public: void generateNextSample(const PairType master, const Bit32u amp, const Bit16u pitch, const Bit32u cutoff); // Perform mixing / ring modulation and return the result - Bit16s nextOutSample(); + float nextOutSample(); // Deactivate the WG engine void deactivate(const PairType master); @@ -142,5 +140,3 @@ public: } // namespace MT32Emu #endif // #ifndef MT32EMU_LA32_WAVE_GENERATOR_H - -#endif // #if MT32EMU_ACCURATE_WG == 1 diff --git a/audio/softsynth/mt32/LA32WaveGenerator.cpp b/audio/softsynth/mt32/LA32WaveGenerator.cpp index 80650699fb..9ffc2ca32e 100644 --- a/audio/softsynth/mt32/LA32WaveGenerator.cpp +++ b/audio/softsynth/mt32/LA32WaveGenerator.cpp @@ -20,7 +20,9 @@ #include "mmath.h" #include "LA32WaveGenerator.h" -#if MT32EMU_ACCURATE_WG == 0 +#if MT32EMU_USE_FLOAT_SAMPLES +#include "LA32FloatWaveGenerator.cpp" +#else namespace MT32Emu { @@ -129,7 +131,8 @@ void LA32WaveGenerator::advancePosition() { computePositions(highLinearLength, lowLinearLength, resonanceWaveLengthFactor); // resonancePhase computation hack - *(int*)&resonancePhase = ((resonanceSinePosition >> 18) + (phase > POSITIVE_FALLING_SINE_SEGMENT ? 2 : 0)) & 3; + int *resonancePhaseAlias = (int *)&resonancePhase; + *resonancePhaseAlias = ((resonanceSinePosition >> 18) + (phase > POSITIVE_FALLING_SINE_SEGMENT ? 2 : 0)) & 3; } void LA32WaveGenerator::generateNextSquareWaveLogSample() { @@ -415,4 +418,4 @@ bool LA32PartialPair::isActive(const PairType useMaster) const { } -#endif // #if MT32EMU_ACCURATE_WG == 0 +#endif // #if MT32EMU_USE_FLOAT_SAMPLES diff --git a/audio/softsynth/mt32/LA32WaveGenerator.h b/audio/softsynth/mt32/LA32WaveGenerator.h index 37a4aead85..4bc04c78d3 100644 --- a/audio/softsynth/mt32/LA32WaveGenerator.h +++ b/audio/softsynth/mt32/LA32WaveGenerator.h @@ -15,7 +15,9 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#if MT32EMU_ACCURATE_WG == 0 +#if MT32EMU_USE_FLOAT_SAMPLES +#include "LA32FloatWaveGenerator.h" +#else #ifndef MT32EMU_LA32_WAVE_GENERATOR_H #define MT32EMU_LA32_WAVE_GENERATOR_H @@ -243,4 +245,4 @@ public: #endif // #ifndef MT32EMU_LA32_WAVE_GENERATOR_H -#endif // #if MT32EMU_ACCURATE_WG == 0 +#endif // #if MT32EMU_USE_FLOAT_SAMPLES diff --git a/audio/softsynth/mt32/Part.cpp b/audio/softsynth/mt32/Part.cpp index 88404316eb..8a0daf3b38 100644 --- a/audio/softsynth/mt32/Part.cpp +++ b/audio/softsynth/mt32/Part.cpp @@ -67,18 +67,12 @@ Part::Part(Synth *useSynth, unsigned int usePartNum) { pitchBend = 0; activePartialCount = 0; memset(patchCache, 0, sizeof(patchCache)); - for (int i = 0; i < MT32EMU_MAX_POLY; i++) { - freePolys.prepend(new Poly(this)); - } } Part::~Part() { while (!activePolys.isEmpty()) { delete activePolys.takeFirst(); } - while (!freePolys.isEmpty()) { - delete freePolys.takeFirst(); - } } void Part::setDataEntryMSB(unsigned char midiDataEntryMSB) { @@ -431,23 +425,10 @@ void Part::noteOn(unsigned int midiKey, unsigned int velocity) { playPoly(patchCache, NULL, midiKey, key, velocity); } -void Part::abortPoly(Poly *poly) { - if (poly->startAbort()) { - while (poly->isActive()) { - if (!synth->prerender()) { - synth->printDebug("%s (%s): Ran out of prerender space to abort poly gracefully", name, currentInstr); - poly->terminate(); - break; - } - } - } -} - bool Part::abortFirstPoly(unsigned int key) { for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) { if (poly->getKey() == key) { - abortPoly(poly); - return true; + return poly->startAbort(); } } return false; @@ -456,8 +437,7 @@ bool Part::abortFirstPoly(unsigned int key) { bool Part::abortFirstPoly(PolyState polyState) { for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) { if (poly->getState() == polyState) { - abortPoly(poly); - return true; + return poly->startAbort(); } } return false; @@ -474,8 +454,7 @@ bool Part::abortFirstPoly() { if (activePolys.isEmpty()) { return false; } - abortPoly(activePolys.getFirst()); - return true; + return activePolys.getFirst()->startAbort(); } void Part::playPoly(const PatchCache cache[4], const MemParams::RhythmTemp *rhythmTemp, unsigned int midiKey, unsigned int key, unsigned int velocity) { @@ -489,6 +468,7 @@ void Part::playPoly(const PatchCache cache[4], const MemParams::RhythmTemp *rhyt if ((patchTemp->patch.assignMode & 2) == 0) { // Single-assign mode abortFirstPoly(key); + if (synth->isAbortingPoly()) return; } if (!synth->partialManager->freePartials(needPartials, partNum)) { @@ -498,12 +478,13 @@ void Part::playPoly(const PatchCache cache[4], const MemParams::RhythmTemp *rhyt #endif return; } + if (synth->isAbortingPoly()) return; - if (freePolys.isEmpty()) { + Poly *poly = synth->partialManager->assignPolyToPart(this); + if (poly == NULL) { synth->printDebug("%s (%s): No free poly to play key %d (velocity %d)", name, currentInstr, midiKey, velocity); return; } - Poly *poly = freePolys.takeFirst(); if (patchTemp->patch.assignMode & 1) { // Priority to data first received activePolys.prepend(poly); @@ -596,6 +577,10 @@ unsigned int Part::getActivePartialCount() const { return activePartialCount; } +const Poly *Part::getFirstActivePoly() const { + return activePolys.getFirst(); +} + unsigned int Part::getActiveNonReleasingPartialCount() const { unsigned int activeNonReleasingPartialCount = 0; for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) { @@ -606,11 +591,15 @@ unsigned int Part::getActiveNonReleasingPartialCount() const { return activeNonReleasingPartialCount; } +Synth *Part::getSynth() const { + return synth; +} + void Part::partialDeactivated(Poly *poly) { activePartialCount--; if (!poly->isActive()) { activePolys.remove(poly); - freePolys.prepend(poly); + synth->partialManager->polyFreed(poly); synth->polyStateChanged(partNum); } } diff --git a/audio/softsynth/mt32/Part.h b/audio/softsynth/mt32/Part.h index b6585880fe..2aeeaf5bd7 100644 --- a/audio/softsynth/mt32/Part.h +++ b/audio/softsynth/mt32/Part.h @@ -51,13 +51,11 @@ private: unsigned int activePartialCount; PatchCache patchCache[4]; - PolyList freePolys; PolyList activePolys; void setPatch(const PatchParam *patch); unsigned int midiKeyToKey(unsigned int midiKey); - void abortPoly(Poly *poly); bool abortFirstPoly(unsigned int key); protected: @@ -110,8 +108,10 @@ public: virtual void setTimbre(TimbreParam *timbre); virtual unsigned int getAbsTimbreNum() const; const char *getCurrentInstr() const; + const Poly *getFirstActivePoly() const; unsigned int getActivePartialCount() const; unsigned int getActiveNonReleasingPartialCount() const; + Synth *getSynth() const; const MemParams::PatchTemp *getPatchTemp() const; diff --git a/audio/softsynth/mt32/Partial.cpp b/audio/softsynth/mt32/Partial.cpp index b80a028515..c7848f02d8 100644 --- a/audio/softsynth/mt32/Partial.cpp +++ b/audio/softsynth/mt32/Partial.cpp @@ -131,6 +131,7 @@ void Partial::startPartial(const Part *part, Poly *usePoly, const PatchCache *us } // FIXME: Sample analysis suggests that the use of panVal is linear, but there are some some quirks that still need to be resolved. + // FIXME: I suppose this should be panVal / 8 and undoubtly integer, clarify ASAP stereoVolume.leftVol = panVal / 7.0f; stereoVolume.rightVol = 1.0f - stereoVolume.leftVol; @@ -198,9 +199,6 @@ void Partial::startPartial(const Part *part, Poly *usePoly, const PatchCache *us if (!hasRingModulatingSlave()) { la32Pair.deactivate(LA32PartialPair::SLAVE); } - // Temporary integration hack - stereoVolume.leftVol /= 8192.0f; - stereoVolume.rightVol /= 8192.0f; } Bit32u Partial::getAmpValue() { @@ -232,7 +230,7 @@ Bit32u Partial::getCutoffValue() { return (tvf->getBaseCutoff() << 18) + cutoffModifierRampVal; } -unsigned long Partial::generateSamples(Bit16s *partialBuf, unsigned long length) { +unsigned long Partial::generateSamples(Sample *partialBuf, unsigned long length) { if (!isActive() || alreadyOutputed) { return 0; } @@ -258,7 +256,7 @@ unsigned long Partial::generateSamples(Bit16s *partialBuf, unsigned long length) } } } - *partialBuf++ = la32Pair.nextOutSample(); + *(partialBuf++) = la32Pair.nextOutSample(); } unsigned long renderedSamples = sampleNum; sampleNum = 0; @@ -288,7 +286,18 @@ Synth *Partial::getSynth() const { return synth; } -bool Partial::produceOutput(float *leftBuf, float *rightBuf, unsigned long length) { +TVA *Partial::getTVA() const { + return tva; +} + +void Partial::backupCache(const PatchCache &cache) { + if (patchCache == &cache) { + cachebackup = cache; + patchCache = &cachebackup; + } +} + +bool Partial::produceOutput(Sample *leftBuf, Sample *rightBuf, unsigned long length) { if (!isActive() || alreadyOutputed || isRingModulatingSlave()) { return false; } @@ -296,14 +305,18 @@ bool Partial::produceOutput(float *leftBuf, float *rightBuf, unsigned long lengt synth->printDebug("[Partial %d] *** ERROR: poly is NULL at Partial::produceOutput()!", debugPartialNum); return false; } - unsigned long numGenerated = generateSamples(myBuffer, length); + Sample buffer[MAX_SAMPLES_PER_RUN]; + unsigned long numGenerated = generateSamples(buffer, length); for (unsigned int i = 0; i < numGenerated; i++) { - *leftBuf++ = myBuffer[i] * stereoVolume.leftVol; - *rightBuf++ = myBuffer[i] * stereoVolume.rightVol; - } - for (; numGenerated < length; numGenerated++) { - *leftBuf++ = 0.0f; - *rightBuf++ = 0.0f; +#if MT32EMU_USE_FLOAT_SAMPLES + *(leftBuf++) += buffer[i] * stereoVolume.leftVol; + *(rightBuf++) += buffer[i] * stereoVolume.rightVol; +#else + *leftBuf = Synth::clipBit16s((Bit32s)*leftBuf + Bit32s(buffer[i] * stereoVolume.leftVol)); + *rightBuf = Synth::clipBit16s((Bit32s)*rightBuf + Bit32s(buffer[i] * stereoVolume.rightVol)); + leftBuf++; + rightBuf++; +#endif } return true; } diff --git a/audio/softsynth/mt32/Partial.h b/audio/softsynth/mt32/Partial.h index 21b1bfe376..358cb9d2d9 100644 --- a/audio/softsynth/mt32/Partial.h +++ b/audio/softsynth/mt32/Partial.h @@ -44,8 +44,6 @@ private: int structurePosition; // 0 or 1 of a structure pair StereoVolume stereoVolume; - Bit16s myBuffer[MAX_SAMPLES_PER_RUN]; - // Only used for PCM partials int pcmNum; // FIXME: Give this a better name (e.g. pcmWaveInfo) @@ -56,6 +54,11 @@ private: int pulseWidthVal; Poly *poly; + Partial *pair; + + TVA *tva; + TVP *tvp; + TVF *tvf; LA32Ramp ampRamp; LA32Ramp cutoffModifierRamp; @@ -63,18 +66,13 @@ private: // TODO: This should be owned by PartialPair LA32PartialPair la32Pair; + const PatchCache *patchCache; + PatchCache cachebackup; + Bit32u getAmpValue(); Bit32u getCutoffValue(); public: - const PatchCache *patchCache; - TVA *tva; - TVP *tvp; - TVF *tvf; - - PatchCache cachebackup; - - Partial *pair; bool alreadyOutputed; Partial(Synth *synth, int debugPartialNum); @@ -97,14 +95,17 @@ public: bool isPCM() const; const ControlROMPCMStruct *getControlROMPCMStruct() const; Synth *getSynth() const; + TVA *getTVA() const; + + void backupCache(const PatchCache &cache); // Returns true only if data written to buffer // This function (unlike the one below it) returns processed stereo samples // made from combining this single partial with its pair, if it has one. - bool produceOutput(float *leftBuf, float *rightBuf, unsigned long length); + bool produceOutput(Sample *leftBuf, Sample *rightBuf, unsigned long length); // This function writes mono sample output to the provided buffer, and returns the number of samples written - unsigned long generateSamples(Bit16s *partialBuf, unsigned long length); + unsigned long generateSamples(Sample *partialBuf, unsigned long length); }; } diff --git a/audio/softsynth/mt32/PartialManager.cpp b/audio/softsynth/mt32/PartialManager.cpp index 436e7a353e..905b5b8cf3 100644 --- a/audio/softsynth/mt32/PartialManager.cpp +++ b/audio/softsynth/mt32/PartialManager.cpp @@ -25,19 +25,26 @@ namespace MT32Emu { PartialManager::PartialManager(Synth *useSynth, Part **useParts) { synth = useSynth; parts = useParts; - for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + partialTable = new Partial *[synth->getPartialCount()]; + freePolys = new Poly *[synth->getPartialCount()]; + firstFreePolyIndex = 0; + for (unsigned int i = 0; i < synth->getPartialCount(); i++) { partialTable[i] = new Partial(synth, i); + freePolys[i] = new Poly(); } } PartialManager::~PartialManager(void) { - for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + for (unsigned int i = 0; i < synth->getPartialCount(); i++) { delete partialTable[i]; + if (freePolys[i] != NULL) delete freePolys[i]; } + delete[] partialTable; + delete[] freePolys; } void PartialManager::clearAlreadyOutputed() { - for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + for (unsigned int i = 0; i < synth->getPartialCount(); i++) { partialTable[i]->alreadyOutputed = false; } } @@ -46,12 +53,12 @@ bool PartialManager::shouldReverb(int i) { return partialTable[i]->shouldReverb(); } -bool PartialManager::produceOutput(int i, float *leftBuf, float *rightBuf, Bit32u bufferLength) { +bool PartialManager::produceOutput(int i, Sample *leftBuf, Sample *rightBuf, Bit32u bufferLength) { return partialTable[i]->produceOutput(leftBuf, rightBuf, bufferLength); } void PartialManager::deactivateAll() { - for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + for (unsigned int i = 0; i < synth->getPartialCount(); i++) { partialTable[i]->deactivate(); } } @@ -69,7 +76,7 @@ Partial *PartialManager::allocPartial(int partNum) { Partial *outPartial = NULL; // Get the first inactive partial - for (int partialNum = 0; partialNum < MT32EMU_MAX_PARTIALS; partialNum++) { + for (unsigned int partialNum = 0; partialNum < synth->getPartialCount(); partialNum++) { if (!partialTable[partialNum]->isActive()) { outPartial = partialTable[partialNum]; break; @@ -83,7 +90,7 @@ Partial *PartialManager::allocPartial(int partNum) { unsigned int PartialManager::getFreePartialCount(void) { int count = 0; - for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + for (unsigned int i = 0; i < synth->getPartialCount(); i++) { if (!partialTable[i]->isActive()) { count++; } @@ -94,7 +101,7 @@ unsigned int PartialManager::getFreePartialCount(void) { // This function is solely used to gather data for debug output at the moment. void PartialManager::getPerPartPartialUsage(unsigned int perPartPartialUsage[9]) { memset(perPartPartialUsage, 0, 9 * sizeof(unsigned int)); - for (unsigned int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + for (unsigned int i = 0; i < synth->getPartialCount(); i++) { if (partialTable[i]->isActive()) { perPartPartialUsage[partialTable[i]->getOwnerPart()]++; } @@ -189,7 +196,7 @@ bool PartialManager::freePartials(unsigned int needed, int partNum) { break; } #endif - if (getFreePartialCount() >= needed) { + if (synth->isAbortingPoly() || getFreePartialCount() >= needed) { return true; } } @@ -206,7 +213,7 @@ bool PartialManager::freePartials(unsigned int needed, int partNum) { if (!abortFirstPolyPreferHeldWhereReserveExceeded(partNum)) { break; } - if (getFreePartialCount() >= needed) { + if (synth->isAbortingPoly() || getFreePartialCount() >= needed) { return true; } } @@ -222,7 +229,7 @@ bool PartialManager::freePartials(unsigned int needed, int partNum) { if (!abortFirstPolyPreferHeldWhereReserveExceeded(-1)) { break; } - if (getFreePartialCount() >= needed) { + if (synth->isAbortingPoly() || getFreePartialCount() >= needed) { return true; } } @@ -233,7 +240,7 @@ bool PartialManager::freePartials(unsigned int needed, int partNum) { if (!parts[partNum]->abortFirstPolyPreferHeld()) { break; } - if (getFreePartialCount() >= needed) { + if (synth->isAbortingPoly() || getFreePartialCount() >= needed) { return true; } } @@ -243,10 +250,39 @@ bool PartialManager::freePartials(unsigned int needed, int partNum) { } const Partial *PartialManager::getPartial(unsigned int partialNum) const { - if (partialNum > MT32EMU_MAX_PARTIALS - 1) { + if (partialNum > synth->getPartialCount() - 1) { return NULL; } return partialTable[partialNum]; } +Poly *PartialManager::assignPolyToPart(Part *part) { + if (firstFreePolyIndex < synth->getPartialCount()) { + Poly *poly = freePolys[firstFreePolyIndex]; + freePolys[firstFreePolyIndex] = NULL; + firstFreePolyIndex++; + poly->setPart(part); + return poly; + } + return NULL; +} + +void PartialManager::polyFreed(Poly *poly) { + if (0 == firstFreePolyIndex) { + synth->printDebug("Cannot return freed poly, currently active polys:\n"); + for (Bit32u partNum = 0; partNum < 9; partNum++) { + const Poly *activePoly = synth->getPart(partNum)->getFirstActivePoly(); + Bit32u polyCount = 0; + while (activePoly != NULL) { + activePoly->getNext(); + polyCount++; + } + synth->printDebug("Part: %i, active poly count: %i\n", partNum, polyCount); + } + } + poly->setPart(NULL); + firstFreePolyIndex--; + freePolys[firstFreePolyIndex] = poly; +} + } diff --git a/audio/softsynth/mt32/PartialManager.h b/audio/softsynth/mt32/PartialManager.h index a1c9266ea1..229b6e8121 100644 --- a/audio/softsynth/mt32/PartialManager.h +++ b/audio/softsynth/mt32/PartialManager.h @@ -24,17 +24,17 @@ class Synth; class PartialManager { private: - Synth *synth; // Only used for sending debug output + Synth *synth; Part **parts; - - Partial *partialTable[MT32EMU_MAX_PARTIALS]; + Poly **freePolys; + Partial **partialTable; Bit8u numReservedPartialsForPart[9]; + Bit32u firstFreePolyIndex; bool abortFirstReleasingPolyWhereReserveExceeded(int minPart); bool abortFirstPolyPreferHeldWhereReserveExceeded(int minPart); public: - PartialManager(Synth *synth, Part **parts); ~PartialManager(); Partial *allocPartial(int partNum); @@ -43,10 +43,12 @@ public: bool freePartials(unsigned int needed, int partNum); unsigned int setReserve(Bit8u *rset); void deactivateAll(); - bool produceOutput(int i, float *leftBuf, float *rightBuf, Bit32u bufferLength); + bool produceOutput(int i, Sample *leftBuf, Sample *rightBuf, Bit32u bufferLength); bool shouldReverb(int i); void clearAlreadyOutputed(); const Partial *getPartial(unsigned int partialNum) const; + Poly *assignPolyToPart(Part *part); + void polyFreed(Poly *poly); }; } diff --git a/audio/softsynth/mt32/Poly.cpp b/audio/softsynth/mt32/Poly.cpp index 46574f8967..1554881270 100644 --- a/audio/softsynth/mt32/Poly.cpp +++ b/audio/softsynth/mt32/Poly.cpp @@ -19,8 +19,8 @@ namespace MT32Emu { -Poly::Poly(Part *usePart) { - part = usePart; +Poly::Poly() { + part = NULL; key = 255; velocity = 255; sustain = false; @@ -32,10 +32,21 @@ Poly::Poly(Part *usePart) { next = NULL; } +void Poly::setPart(Part *usePart) { + part = usePart; +} + void Poly::reset(unsigned int newKey, unsigned int newVelocity, bool newSustain, Partial **newPartials) { if (isActive()) { - // FIXME: Throw out some big ugly debug output with a lot of exclamation marks - we should never get here - terminate(); + // This should never happen + part->getSynth()->printDebug("Resetting active poly. Active partial count: %i\n", activePartialCount); + for (int i = 0; i < 4; i++) { + if (partials[i] != NULL && partials[i]->isActive()) { + partials[i]->deactivate(); + activePartialCount--; + } + } + state = POLY_Inactive; } key = newKey; @@ -92,41 +103,24 @@ bool Poly::startDecay() { } bool Poly::startAbort() { - if (state == POLY_Inactive) { + if (state == POLY_Inactive || part->getSynth()->isAbortingPoly()) { return false; } for (int t = 0; t < 4; t++) { Partial *partial = partials[t]; if (partial != NULL) { partial->startAbort(); + part->getSynth()->abortingPoly = this; } } return true; } -void Poly::terminate() { - if (state == POLY_Inactive) { - return; - } - for (int t = 0; t < 4; t++) { - Partial *partial = partials[t]; - if (partial != NULL) { - partial->deactivate(); - } - } - if (state != POLY_Inactive) { - // FIXME: Throw out lots of debug output - this should never happen - // (Deactivating the partials above should've made them each call partialDeactivated(), ultimately changing the state to POLY_Inactive) - state = POLY_Inactive; - } -} - void Poly::backupCacheToPartials(PatchCache cache[4]) { for (int partialNum = 0; partialNum < 4; partialNum++) { Partial *partial = partials[partialNum]; - if (partial != NULL && partial->patchCache == &cache[partialNum]) { - partial->cachebackup = cache[partialNum]; - partial->patchCache = &partial->cachebackup; + if (partial != NULL) { + partial->backupCache(cache[partialNum]); } } } @@ -171,11 +165,14 @@ void Poly::partialDeactivated(Partial *partial) { } if (activePartialCount == 0) { state = POLY_Inactive; + if (part->getSynth()->abortingPoly == this) { + part->getSynth()->abortingPoly = NULL; + } } part->partialDeactivated(this); } -Poly *Poly::getNext() { +Poly *Poly::getNext() const { return next; } diff --git a/audio/softsynth/mt32/Poly.h b/audio/softsynth/mt32/Poly.h index 068cf73d35..33abc35fdf 100644 --- a/audio/softsynth/mt32/Poly.h +++ b/audio/softsynth/mt32/Poly.h @@ -44,13 +44,13 @@ private: Poly *next; public: - Poly(Part *part); + Poly(); + void setPart(Part *usePart); void reset(unsigned int key, unsigned int velocity, bool sustain, Partial **partials); bool noteOff(bool pedalHeld); bool stopPedalHold(); bool startDecay(); bool startAbort(); - void terminate(); void backupCacheToPartials(PatchCache cache[4]); @@ -63,7 +63,7 @@ public: void partialDeactivated(Partial *partial); - Poly *getNext(); + Poly *getNext() const; void setNext(Poly *poly); }; diff --git a/audio/softsynth/mt32/Structures.h b/audio/softsynth/mt32/Structures.h index 43d2d1f226..421e427fc0 100644 --- a/audio/softsynth/mt32/Structures.h +++ b/audio/softsynth/mt32/Structures.h @@ -38,6 +38,12 @@ typedef signed short int Bit16s; typedef unsigned char Bit8u; typedef signed char Bit8s; +#if MT32EMU_USE_FLOAT_SAMPLES +typedef float Sample; +#else +typedef Bit16s Sample; +#endif + // The following structures represent the MT-32's memory // Since sysex allows this memory to be written to in blocks of bytes, // we keep this packed so that we can copy data into the various diff --git a/audio/softsynth/mt32/Synth.cpp b/audio/softsynth/mt32/Synth.cpp index 1e1be06bc9..b76dc58b5f 100644 --- a/audio/softsynth/mt32/Synth.cpp +++ b/audio/softsynth/mt32/Synth.cpp @@ -26,15 +26,7 @@ #include "mt32emu.h" #include "mmath.h" #include "PartialManager.h" - -#if MT32EMU_USE_REVERBMODEL == 1 -#include "AReverbModel.h" -#elif MT32EMU_USE_REVERBMODEL == 2 #include "BReverbModel.h" -#else -#include "FreeverbModel.h" -#endif -#include "DelayReverb.h" namespace MT32Emu { @@ -50,84 +42,22 @@ static const ControlROMMap ControlROMMaps[7] = { // (Note that all but CM-32L ROM actually have 86 entries for rhythmTemp) }; -static inline Bit16s *streamOffset(Bit16s *stream, Bit32u pos) { - return stream == NULL ? NULL : stream + pos; -} +static inline void muteStream(Sample *stream, Bit32u len) { + if (stream == NULL) return; -static inline void clearIfNonNull(Bit16s *stream, Bit32u len) { - if (stream != NULL) { - memset(stream, 0, len * sizeof(Bit16s)); - } -} - -static inline void mix(float *target, const float *stream, Bit32u len) { - while (len--) { - *target += *stream; - stream++; - target++; - } -} - -static inline void clearFloats(float *leftBuf, float *rightBuf, Bit32u len) { +#if MT32EMU_USE_FLOAT_SAMPLES // FIXME: Use memset() where compatibility is guaranteed (if this turns out to be a win) while (len--) { - *leftBuf++ = 0.0f; - *rightBuf++ = 0.0f; - } -} - -static inline Bit16s clipBit16s(Bit32s a) { - // Clamp values above 32767 to 32767, and values below -32768 to -32768 - if ((a + 32768) & ~65535) { - return (a >> 31) ^ 32767; - } - return a; -} - -static void floatToBit16s_nice(Bit16s *target, const float *source, Bit32u len, float outputGain) { - float gain = outputGain * 16384.0f; - while (len--) { - // Since we're not shooting for accuracy here, don't worry about the rounding mode. - *target = clipBit16s((Bit32s)(*source * gain)); - source++; - target++; - } -} - -static void floatToBit16s_pure(Bit16s *target, const float *source, Bit32u len, float /*outputGain*/) { - while (len--) { - *target = clipBit16s((Bit32s)floor(*source * 8192.0f)); - source++; - target++; - } -} - -static void floatToBit16s_reverb(Bit16s *target, const float *source, Bit32u len, float outputGain) { - float gain = outputGain * 8192.0f; - while (len--) { - *target = clipBit16s((Bit32s)floor(*source * gain)); - source++; - target++; - } -} - -static void floatToBit16s_generation1(Bit16s *target, const float *source, Bit32u len, float outputGain) { - float gain = outputGain * 8192.0f; - while (len--) { - *target = clipBit16s((Bit32s)floor(*source * gain)); - *target = (*target & 0x8000) | ((*target << 1) & 0x7FFE); - source++; - target++; + *stream++ = 0.0f; } +#else + memset(stream, 0, len * sizeof(Sample)); +#endif } -static void floatToBit16s_generation2(Bit16s *target, const float *source, Bit32u len, float outputGain) { - float gain = outputGain * 8192.0f; - while (len--) { - *target = clipBit16s((Bit32s)floor(*source * gain)); - *target = (*target & 0x8000) | ((*target << 1) & 0x7FFE) | ((*target >> 14) & 0x0001); - source++; - target++; +static inline void advanceStreamPosition(Sample *&stream, Bit32u posDelta) { + if (stream != NULL) { + stream += posDelta; } } @@ -155,28 +85,19 @@ Synth::Synth(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[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 reverbModel = NULL; setDACInputMode(DACInputMode_NICE); + setMIDIDelayMode(MIDIDelayMode_DELAY_SHORT_MESSAGES_ONLY); setOutputGain(1.0f); - setReverbOutputGain(0.68f); + setReverbOutputGain(1.0f); partialManager = NULL; + midiQueue = NULL; + lastReceivedMIDIEventTimestamp = 0; memset(parts, 0, sizeof(parts)); renderedSampleCount = 0; } @@ -197,8 +118,8 @@ void ReportHandler::showLCDMessage(const char *data) { } void ReportHandler::printDebug(const char *fmt, va_list list) { - vprintf(fmt, list); - printf("\n"); + vprintf(fmt, list); + printf("\n"); } void Synth::polyStateChanged(int partNum) { @@ -236,35 +157,37 @@ bool Synth::isReverbOverridden() const { } void Synth::setDACInputMode(DACInputMode mode) { - switch(mode) { - case DACInputMode_GENERATION1: - la32FloatToBit16sFunc = floatToBit16s_generation1; - reverbFloatToBit16sFunc = floatToBit16s_reverb; - break; - case DACInputMode_GENERATION2: - la32FloatToBit16sFunc = floatToBit16s_generation2; - reverbFloatToBit16sFunc = floatToBit16s_reverb; - break; - case DACInputMode_PURE: - la32FloatToBit16sFunc = floatToBit16s_pure; - reverbFloatToBit16sFunc = floatToBit16s_pure; - break; - case DACInputMode_NICE: - default: - la32FloatToBit16sFunc = floatToBit16s_nice; - reverbFloatToBit16sFunc = floatToBit16s_reverb; - break; - } + dacInputMode = mode; +} + +DACInputMode Synth::getDACInputMode() const { + return dacInputMode; +} + +void Synth::setMIDIDelayMode(MIDIDelayMode mode) { + midiDelayMode = mode; +} + +MIDIDelayMode Synth::getMIDIDelayMode() const { + return midiDelayMode; } void Synth::setOutputGain(float newOutputGain) { outputGain = newOutputGain; } +float Synth::getOutputGain() const { + return outputGain; +} + void Synth::setReverbOutputGain(float newReverbOutputGain) { reverbOutputGain = newReverbOutputGain; } +float Synth::getReverbOutputGain() const { + return reverbOutputGain; +} + bool Synth::loadControlROM(const ROMImage &controlROMImage) { if (&controlROMImage == NULL) return false; Common::File *file = controlROMImage.getFile(); @@ -343,9 +266,9 @@ bool Synth::loadPCMROM(const ROMImage &pcmROMImage) { bool Synth::initPCMList(Bit16u mapAddress, Bit16u count) { ControlROMPCMStruct *tps = (ControlROMPCMStruct *)&controlROMData[mapAddress]; for (int i = 0; i < count; i++) { - size_t rAddr = tps[i].pos * 0x800; - size_t rLenExp = (tps[i].len & 0x70) >> 4; - size_t rLen = 0x800 << rLenExp; + Bit32u rAddr = tps[i].pos * 0x800; + Bit32u rLenExp = (tps[i].len & 0x70) >> 4; + Bit32u 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; @@ -407,17 +330,18 @@ bool Synth::initTimbres(Bit16u mapAddress, Bit16u offset, int count, int startTi return true; } -bool Synth::open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage) { +bool Synth::open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage, unsigned int usePartialCount) { if (isOpen) { return false; } - prerenderReadIx = prerenderWriteIx = 0; + partialCount = usePartialCount; + abortingPoly = NULL; #if MT32EMU_MONITOR_INIT printDebug("Initialising Constant Tables"); #endif #if !MT32EMU_REDUCE_REVERB_MEMORY - for (int i = 0; i < 4; i++) { - reverbModels[i]->open(useProp.sampleRate); + for (int i = REVERB_MODE_ROOM; i <= REVERB_MODE_TAP_DELAY; i++) { + reverbModels[i]->open(); } #endif @@ -554,6 +478,8 @@ bool Synth::open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage) { // For resetting mt32 mid-execution mt32default = mt32ram; + midiQueue = new MidiEventQueue(); + isOpen = true; isEnabled = false; @@ -568,6 +494,9 @@ void Synth::close() { return; } + delete midiQueue; + midiQueue = NULL; + delete partialManager; partialManager = NULL; @@ -588,12 +517,78 @@ void Synth::close() { isOpen = false; } -void Synth::playMsg(Bit32u msg) { +void Synth::flushMIDIQueue() { + if (midiQueue != NULL) { + for (;;) { + const MidiEvent *midiEvent = midiQueue->peekMidiEvent(); + if (midiEvent == NULL) break; + if (midiEvent->sysexData == NULL) { + playMsgNow(midiEvent->shortMessageData); + } else { + playSysexNow(midiEvent->sysexData, midiEvent->sysexLength); + } + midiQueue->dropMidiEvent(); + } + lastReceivedMIDIEventTimestamp = renderedSampleCount; + } +} + +void Synth::setMIDIEventQueueSize(Bit32u useSize) { + if (midiQueue != NULL) { + flushMIDIQueue(); + delete midiQueue; + midiQueue = new MidiEventQueue(useSize); + } +} + +Bit32u Synth::getShortMessageLength(Bit32u msg) { + if ((msg & 0xF0) == 0xF0) return 1; + // NOTE: This calculation isn't quite correct + // as it doesn't consider the running status byte + return ((msg & 0xE0) == 0xC0) ? 2 : 3; +} + +Bit32u Synth::addMIDIInterfaceDelay(Bit32u len, Bit32u timestamp) { + Bit32u transferTime = Bit32u((double)len * MIDI_DATA_TRANSFER_RATE); + // Dealing with wrapping + if (Bit32s(timestamp - lastReceivedMIDIEventTimestamp) < 0) { + timestamp = lastReceivedMIDIEventTimestamp; + } + timestamp += transferTime; + lastReceivedMIDIEventTimestamp = timestamp; + return timestamp; +} + +bool Synth::playMsg(Bit32u msg) { + return playMsg(msg, renderedSampleCount); +} + +bool Synth::playMsg(Bit32u msg, Bit32u timestamp) { + if (midiQueue == NULL) return false; + if (midiDelayMode != MIDIDelayMode_IMMEDIATE) { + timestamp = addMIDIInterfaceDelay(getShortMessageLength(msg), timestamp); + } + return midiQueue->pushShortMessage(msg, timestamp); +} + +bool Synth::playSysex(const Bit8u *sysex, Bit32u len) { + return playSysex(sysex, len, renderedSampleCount); +} + +bool Synth::playSysex(const Bit8u *sysex, Bit32u len, Bit32u timestamp) { + if (midiQueue == NULL) return false; + if (midiDelayMode == MIDIDelayMode_DELAY_ALL) { + timestamp = addMIDIInterfaceDelay(len, timestamp); + } + return midiQueue->pushSysex(sysex, len, timestamp); +} + +void Synth::playMsgNow(Bit32u msg) { // FIXME: Implement active sensing unsigned char code = (unsigned char)((msg & 0x0000F0) >> 4); unsigned char chan = (unsigned char)(msg & 0x00000F); - unsigned char note = (unsigned char)((msg & 0x00FF00) >> 8); - unsigned char velocity = (unsigned char)((msg & 0xFF0000) >> 16); + unsigned char note = (unsigned char)((msg & 0x007F00) >> 8); + unsigned char velocity = (unsigned char)((msg & 0x7F0000) >> 16); isEnabled = true; //printDebug("Playing chan %d, code 0x%01x note: 0x%02x", chan, code, note); @@ -606,11 +601,6 @@ 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) { @@ -692,7 +682,7 @@ void Synth::playMsgOnPart(unsigned char part, unsigned char code, unsigned char #if MT32EMU_MONITOR_MIDI > 0 printDebug("Unknown MIDI Control code: 0x%02x - vel 0x%02x", note, velocity); #endif - break; + return; } break; @@ -709,13 +699,12 @@ void Synth::playMsgOnPart(unsigned char part, unsigned char code, unsigned char #if MT32EMU_MONITOR_MIDI > 0 printDebug("Unknown Midi code: 0x%01x - %02x - %02x", code, note, velocity); #endif - break; + return; } - - //midiOutShortMsg(m_out, msg); + reportHandler->onMIDIMessagePlayed(); } -void Synth::playSysex(const Bit8u *sysex, Bit32u len) { +void Synth::playSysexNow(const Bit8u *sysex, Bit32u len) { if (len < 2) { printDebug("playSysex: Message is too short for sysex (%d bytes)", len); } @@ -810,6 +799,7 @@ void Synth::readSysex(unsigned char /*device*/, const Bit8u * /*sysex*/, Bit32u } void Synth::writeSysex(unsigned char device, const Bit8u *sysex, Bit32u len) { + reportHandler->onMIDIMessagePlayed(); Bit32u addr = (sysex[0] << 16) | (sysex[1] << 8) | (sysex[2]); addr = MT32EMU_MEMADDR(addr); sysex += 3; @@ -1232,7 +1222,7 @@ void Synth::refreshSystemReverbParameters() { reportHandler->onNewReverbTime(mt32ram.system.reverbTime); reportHandler->onNewReverbLevel(mt32ram.system.reverbLevel); - ReverbModel *newReverbModel = reverbModels[mt32ram.system.reverbMode]; + BReverbModel *newReverbModel = reverbModels[mt32ram.system.reverbMode]; #if MT32EMU_REDUCE_REVERB_MEMORY if (reverbModel != newReverbModel) { if (reverbModel != NULL) { @@ -1308,179 +1298,226 @@ void Synth::reset() { isEnabled = false; } -void Synth::render(Bit16s *stream, Bit32u len) { - if (!isEnabled) { - memset(stream, 0, len * sizeof(Bit16s) * 2); - return; +MidiEvent::~MidiEvent() { + if (sysexData != NULL) { + delete[] sysexData; } - while (len > 0) { - Bit32u thisLen = len > MAX_SAMPLES_PER_RUN ? MAX_SAMPLES_PER_RUN : len; - renderStreams(tmpNonReverbLeft, tmpNonReverbRight, tmpReverbDryLeft, tmpReverbDryRight, tmpReverbWetLeft, tmpReverbWetRight, thisLen); - for (Bit32u i = 0; i < thisLen; i++) { - stream[0] = clipBit16s((Bit32s)tmpNonReverbLeft[i] + (Bit32s)tmpReverbDryLeft[i] + (Bit32s)tmpReverbWetLeft[i]); - stream[1] = clipBit16s((Bit32s)tmpNonReverbRight[i] + (Bit32s)tmpReverbDryRight[i] + (Bit32s)tmpReverbWetRight[i]); - stream += 2; - } - len -= thisLen; +} + +void MidiEvent::setShortMessage(Bit32u useShortMessageData, Bit32u useTimestamp) { + if (sysexData != NULL) { + delete[] sysexData; } + shortMessageData = useShortMessageData; + timestamp = useTimestamp; + sysexData = NULL; + sysexLength = 0; } -bool Synth::prerender() { - int newPrerenderWriteIx = (prerenderWriteIx + 1) % MAX_PRERENDER_SAMPLES; - if (newPrerenderWriteIx == prerenderReadIx) { - // The prerender buffer is full - return false; +void MidiEvent::setSysex(const Bit8u *useSysexData, Bit32u useSysexLength, Bit32u useTimestamp) { + if (sysexData != NULL) { + delete[] sysexData; } - doRenderStreams( - prerenderNonReverbLeft + prerenderWriteIx, - prerenderNonReverbRight + prerenderWriteIx, - prerenderReverbDryLeft + prerenderWriteIx, - prerenderReverbDryRight + prerenderWriteIx, - prerenderReverbWetLeft + prerenderWriteIx, - prerenderReverbWetRight + prerenderWriteIx, - 1); - prerenderWriteIx = newPrerenderWriteIx; + shortMessageData = 0; + timestamp = useTimestamp; + sysexLength = useSysexLength; + Bit8u *dstSysexData = new Bit8u[sysexLength]; + sysexData = dstSysexData; + memcpy(dstSysexData, useSysexData, sysexLength); +} + +MidiEventQueue::MidiEventQueue(Bit32u useRingBufferSize) : ringBufferSize(useRingBufferSize) { + ringBuffer = new MidiEvent[ringBufferSize]; + memset(ringBuffer, 0, ringBufferSize * sizeof(MidiEvent)); + reset(); +} + +MidiEventQueue::~MidiEventQueue() { + delete[] ringBuffer; +} + +void MidiEventQueue::reset() { + startPosition = 0; + endPosition = 0; +} + +bool MidiEventQueue::pushShortMessage(Bit32u shortMessageData, Bit32u timestamp) { + unsigned int newEndPosition = (endPosition + 1) % ringBufferSize; + // Is ring buffer full? + if (startPosition == newEndPosition) return false; + ringBuffer[endPosition].setShortMessage(shortMessageData, timestamp); + endPosition = newEndPosition; return true; } -static inline void maybeCopy(Bit16s *out, Bit32u outPos, Bit16s *in, Bit32u inPos, Bit32u len) { - if (out == NULL) { - return; - } - memcpy(out + outPos, in + inPos, len * sizeof(Bit16s)); +bool MidiEventQueue::pushSysex(const Bit8u *sysexData, Bit32u sysexLength, Bit32u timestamp) { + unsigned int newEndPosition = (endPosition + 1) % ringBufferSize; + // Is ring buffer full? + if (startPosition == newEndPosition) return false; + ringBuffer[endPosition].setSysex(sysexData, sysexLength, timestamp); + endPosition = newEndPosition; + return true; } -void Synth::copyPrerender(Bit16s *nonReverbLeft, Bit16s *nonReverbRight, Bit16s *reverbDryLeft, Bit16s *reverbDryRight, Bit16s *reverbWetLeft, Bit16s *reverbWetRight, Bit32u pos, Bit32u len) { - maybeCopy(nonReverbLeft, pos, prerenderNonReverbLeft, prerenderReadIx, len); - maybeCopy(nonReverbRight, pos, prerenderNonReverbRight, prerenderReadIx, len); - maybeCopy(reverbDryLeft, pos, prerenderReverbDryLeft, prerenderReadIx, len); - maybeCopy(reverbDryRight, pos, prerenderReverbDryRight, prerenderReadIx, len); - maybeCopy(reverbWetLeft, pos, prerenderReverbWetLeft, prerenderReadIx, len); - maybeCopy(reverbWetRight, pos, prerenderReverbWetRight, prerenderReadIx, len); +const MidiEvent *MidiEventQueue::peekMidiEvent() { + return (startPosition == endPosition) ? NULL : &ringBuffer[startPosition]; } -void Synth::checkPrerender(Bit16s *nonReverbLeft, Bit16s *nonReverbRight, Bit16s *reverbDryLeft, Bit16s *reverbDryRight, Bit16s *reverbWetLeft, Bit16s *reverbWetRight, Bit32u &pos, Bit32u &len) { - if (prerenderReadIx > prerenderWriteIx) { - // There's data in the prerender buffer, and the write index has wrapped. - Bit32u prerenderCopyLen = MAX_PRERENDER_SAMPLES - prerenderReadIx; - if (prerenderCopyLen > len) { - prerenderCopyLen = len; - } - copyPrerender(nonReverbLeft, nonReverbRight, reverbDryLeft, reverbDryRight, reverbWetLeft, reverbWetRight, pos, prerenderCopyLen); - len -= prerenderCopyLen; - pos += prerenderCopyLen; - prerenderReadIx = (prerenderReadIx + prerenderCopyLen) % MAX_PRERENDER_SAMPLES; - } - if (prerenderReadIx < prerenderWriteIx) { - // There's data in the prerender buffer, and the write index is ahead of the read index. - Bit32u prerenderCopyLen = prerenderWriteIx - prerenderReadIx; - if (prerenderCopyLen > len) { - prerenderCopyLen = len; - } - copyPrerender(nonReverbLeft, nonReverbRight, reverbDryLeft, reverbDryRight, reverbWetLeft, reverbWetRight, pos, prerenderCopyLen); - len -= prerenderCopyLen; - pos += prerenderCopyLen; - prerenderReadIx += prerenderCopyLen; - } - if (prerenderReadIx == prerenderWriteIx) { - // If the ring buffer's empty, reset it to start at 0 to minimise wrapping, - // which requires two writes instead of one. - prerenderReadIx = prerenderWriteIx = 0; - } -} - -void Synth::renderStreams(Bit16s *nonReverbLeft, Bit16s *nonReverbRight, Bit16s *reverbDryLeft, Bit16s *reverbDryRight, Bit16s *reverbWetLeft, Bit16s *reverbWetRight, Bit32u len) { - if (!isEnabled) { - clearIfNonNull(nonReverbLeft, len); - clearIfNonNull(nonReverbRight, len); - clearIfNonNull(reverbDryLeft, len); - clearIfNonNull(reverbDryRight, len); - clearIfNonNull(reverbWetLeft, len); - clearIfNonNull(reverbWetRight, len); - return; +void MidiEventQueue::dropMidiEvent() { + // Is ring buffer empty? + if (startPosition != endPosition) { + startPosition = (startPosition + 1) % ringBufferSize; } - Bit32u pos = 0; +} - // First, check for data in the prerender buffer and spit that out before generating anything new. - // Note that the prerender buffer is rarely used - see comments elsewhere for details. - checkPrerender(nonReverbLeft, nonReverbRight, reverbDryLeft, reverbDryRight, reverbWetLeft, reverbWetRight, pos, len); +void Synth::render(Sample *stream, Bit32u len) { + Sample tmpNonReverbLeft[MAX_SAMPLES_PER_RUN]; + Sample tmpNonReverbRight[MAX_SAMPLES_PER_RUN]; + Sample tmpReverbDryLeft[MAX_SAMPLES_PER_RUN]; + Sample tmpReverbDryRight[MAX_SAMPLES_PER_RUN]; + Sample tmpReverbWetLeft[MAX_SAMPLES_PER_RUN]; + Sample tmpReverbWetRight[MAX_SAMPLES_PER_RUN]; while (len > 0) { Bit32u thisLen = len > MAX_SAMPLES_PER_RUN ? MAX_SAMPLES_PER_RUN : len; - doRenderStreams( - streamOffset(nonReverbLeft, pos), - streamOffset(nonReverbRight, pos), - streamOffset(reverbDryLeft, pos), - streamOffset(reverbDryRight, pos), - streamOffset(reverbWetLeft, pos), - streamOffset(reverbWetRight, pos), - thisLen); + renderStreams(tmpNonReverbLeft, tmpNonReverbRight, tmpReverbDryLeft, tmpReverbDryRight, tmpReverbWetLeft, tmpReverbWetRight, thisLen); + for (Bit32u i = 0; i < thisLen; i++) { +#if MT32EMU_USE_FLOAT_SAMPLES + *(stream++) = tmpNonReverbLeft[i] + tmpReverbDryLeft[i] + tmpReverbWetLeft[i]; + *(stream++) = tmpNonReverbRight[i] + tmpReverbDryRight[i] + tmpReverbWetRight[i]; +#else + *(stream++) = clipBit16s((Bit32s)tmpNonReverbLeft[i] + (Bit32s)tmpReverbDryLeft[i] + (Bit32s)tmpReverbWetLeft[i]); + *(stream++) = clipBit16s((Bit32s)tmpNonReverbRight[i] + (Bit32s)tmpReverbDryRight[i] + (Bit32s)tmpReverbWetRight[i]); +#endif + } len -= thisLen; - pos += thisLen; } } -// FIXME: Using more temporary buffers than we need to -void Synth::doRenderStreams(Bit16s *nonReverbLeft, Bit16s *nonReverbRight, Bit16s *reverbDryLeft, Bit16s *reverbDryRight, Bit16s *reverbWetLeft, Bit16s *reverbWetRight, Bit32u len) { - clearFloats(&tmpBufMixLeft[0], &tmpBufMixRight[0], len); - if (!reverbEnabled) { - for (unsigned int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { - if (partialManager->produceOutput(i, &tmpBufPartialLeft[0], &tmpBufPartialRight[0], len)) { - mix(&tmpBufMixLeft[0], &tmpBufPartialLeft[0], len); - mix(&tmpBufMixRight[0], &tmpBufPartialRight[0], len); - } - } - if (nonReverbLeft != NULL) { - la32FloatToBit16sFunc(nonReverbLeft, &tmpBufMixLeft[0], len, outputGain); - } - if (nonReverbRight != NULL) { - la32FloatToBit16sFunc(nonReverbRight, &tmpBufMixRight[0], len, outputGain); - } - clearIfNonNull(reverbDryLeft, len); - clearIfNonNull(reverbDryRight, len); - clearIfNonNull(reverbWetLeft, len); - clearIfNonNull(reverbWetRight, len); - } else { - for (unsigned int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { - if (!partialManager->shouldReverb(i)) { - if (partialManager->produceOutput(i, &tmpBufPartialLeft[0], &tmpBufPartialRight[0], len)) { - mix(&tmpBufMixLeft[0], &tmpBufPartialLeft[0], len); - mix(&tmpBufMixRight[0], &tmpBufPartialRight[0], len); +void Synth::renderStreams(Sample *nonReverbLeft, Sample *nonReverbRight, Sample *reverbDryLeft, Sample *reverbDryRight, Sample *reverbWetLeft, Sample *reverbWetRight, Bit32u len) { + while (len > 0) { + // We need to ensure zero-duration notes will play so add minimum 1-sample delay. + Bit32u thisLen = 1; + if (!isAbortingPoly()) { + const MidiEvent *nextEvent = midiQueue->peekMidiEvent(); + Bit32s samplesToNextEvent = (nextEvent != NULL) ? Bit32s(nextEvent->timestamp - renderedSampleCount) : MAX_SAMPLES_PER_RUN; + if (samplesToNextEvent > 0) { + thisLen = len > MAX_SAMPLES_PER_RUN ? MAX_SAMPLES_PER_RUN : len; + if (thisLen > (Bit32u)samplesToNextEvent) { + thisLen = samplesToNextEvent; + } + } else { + if (nextEvent->sysexData == NULL) { + playMsgNow(nextEvent->shortMessageData); + // If a poly is aborting we don't drop the event from the queue. + // Instead, we'll return to it again when the abortion is done. + if (!isAbortingPoly()) { + midiQueue->dropMidiEvent(); + } + } else { + playSysexNow(nextEvent->sysexData, nextEvent->sysexLength); + midiQueue->dropMidiEvent(); } } } - if (nonReverbLeft != NULL) { - la32FloatToBit16sFunc(nonReverbLeft, &tmpBufMixLeft[0], len, outputGain); + doRenderStreams(nonReverbLeft, nonReverbRight, reverbDryLeft, reverbDryRight, reverbWetLeft, reverbWetRight, thisLen); + advanceStreamPosition(nonReverbLeft, thisLen); + advanceStreamPosition(nonReverbRight, thisLen); + advanceStreamPosition(reverbDryLeft, thisLen); + advanceStreamPosition(reverbDryRight, thisLen); + advanceStreamPosition(reverbWetLeft, thisLen); + advanceStreamPosition(reverbWetRight, thisLen); + len -= thisLen; + } +} + +void Synth::convertSamplesToOutput(Sample *target, const Sample *source, Bit32u len, bool reverb) { + if (target == NULL) return; + + if (dacInputMode == DACInputMode_PURE) { + memcpy(target, source, len * sizeof(Sample)); + return; + } + +#if MT32EMU_USE_FLOAT_SAMPLES + float gain = reverb ? reverbOutputGain * CM32L_REVERB_TO_LA32_ANALOG_OUTPUT_GAIN_FACTOR : 2.0f * outputGain; + while (len--) { + *(target++) = *(source++) * gain; + } +#else + float gain = reverb ? reverbOutputGain * CM32L_REVERB_TO_LA32_ANALOG_OUTPUT_GAIN_FACTOR : outputGain; + if (!reverb) { + switch (dacInputMode) { + case DACInputMode_NICE: + // Since we're not shooting for accuracy here, don't worry about the rounding mode. + gain *= 2.0f; + break; + case DACInputMode_GENERATION1: + while (len--) { + *target = clipBit16s(Bit32s(*source * gain)); + *target = (*target & 0x8000) | ((*target << 1) & 0x7FFE); + source++; + target++; + } + return; + case DACInputMode_GENERATION2: + while (len--) { + *target = clipBit16s(Bit32s(*source * gain)); + *target = (*target & 0x8000) | ((*target << 1) & 0x7FFE) | ((*target >> 14) & 0x0001); + source++; + target++; + } + return; + default: + break; } - if (nonReverbRight != NULL) { - la32FloatToBit16sFunc(nonReverbRight, &tmpBufMixRight[0], len, outputGain); + } + while (len--) { + *(target++) = clipBit16s(Bit32s(*(source++) * gain)); + } +#endif +} + +void Synth::doRenderStreams(Sample *nonReverbLeft, Sample *nonReverbRight, Sample *reverbDryLeft, Sample *reverbDryRight, Sample *reverbWetLeft, Sample *reverbWetRight, Bit32u len) { + if (isEnabled) { + Sample tmpBufMixLeft[MAX_SAMPLES_PER_RUN], tmpBufMixRight[MAX_SAMPLES_PER_RUN]; + muteStream(tmpBufMixLeft, len); + muteStream(tmpBufMixRight, len); + for (unsigned int i = 0; i < getPartialCount(); i++) { + if (!reverbEnabled || !partialManager->shouldReverb(i)) { + partialManager->produceOutput(i, tmpBufMixLeft, tmpBufMixRight, len); + } } + convertSamplesToOutput(nonReverbLeft, tmpBufMixLeft, len, false); + convertSamplesToOutput(nonReverbRight, tmpBufMixRight, len, false); + } else { + muteStream(nonReverbLeft, len); + muteStream(nonReverbRight, len); + } - clearFloats(&tmpBufMixLeft[0], &tmpBufMixRight[0], len); - for (unsigned int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + if (isEnabled && reverbEnabled) { + Sample tmpBufMixLeft[MAX_SAMPLES_PER_RUN], tmpBufMixRight[MAX_SAMPLES_PER_RUN]; + muteStream(tmpBufMixLeft, len); + muteStream(tmpBufMixRight, len); + for (unsigned int i = 0; i < getPartialCount(); i++) { if (partialManager->shouldReverb(i)) { - if (partialManager->produceOutput(i, &tmpBufPartialLeft[0], &tmpBufPartialRight[0], len)) { - mix(&tmpBufMixLeft[0], &tmpBufPartialLeft[0], len); - mix(&tmpBufMixRight[0], &tmpBufPartialRight[0], len); - } + partialManager->produceOutput(i, tmpBufMixLeft, tmpBufMixRight, len); } } - if (reverbDryLeft != NULL) { - la32FloatToBit16sFunc(reverbDryLeft, &tmpBufMixLeft[0], len, outputGain); - } - if (reverbDryRight != NULL) { - la32FloatToBit16sFunc(reverbDryRight, &tmpBufMixRight[0], len, outputGain); - } + convertSamplesToOutput(reverbDryLeft, tmpBufMixLeft, len, false); + convertSamplesToOutput(reverbDryRight, tmpBufMixRight, len, false); - // FIXME: Note that on the real devices, reverb input and output are signed linear 16-bit (well, kinda, there's some fudging) PCM, not float. - reverbModel->process(&tmpBufMixLeft[0], &tmpBufMixRight[0], &tmpBufReverbOutLeft[0], &tmpBufReverbOutRight[0], len); - if (reverbWetLeft != NULL) { - reverbFloatToBit16sFunc(reverbWetLeft, &tmpBufReverbOutLeft[0], len, reverbOutputGain); - } - if (reverbWetRight != NULL) { - reverbFloatToBit16sFunc(reverbWetRight, &tmpBufReverbOutRight[0], len, reverbOutputGain); - } + Sample tmpBufReverbOutLeft[MAX_SAMPLES_PER_RUN], tmpBufReverbOutRight[MAX_SAMPLES_PER_RUN]; + reverbModel->process(tmpBufMixLeft, tmpBufMixRight, tmpBufReverbOutLeft, tmpBufReverbOutRight, len); + convertSamplesToOutput(reverbWetLeft, tmpBufReverbOutLeft, len, true); + convertSamplesToOutput(reverbWetRight, tmpBufReverbOutRight, len, true); + } else { + muteStream(reverbDryLeft, len); + muteStream(reverbDryRight, len); + muteStream(reverbWetLeft, len); + muteStream(reverbWetRight, len); } + partialManager->clearAlreadyOutputed(); renderedSampleCount += len; } @@ -1489,19 +1526,14 @@ void Synth::printPartialUsage(unsigned long sampleOffset) { unsigned int partialUsage[9]; partialManager->getPerPartPartialUsage(partialUsage); if (sampleOffset > 0) { - printDebug("[+%lu] Partial Usage: 1:%02d 2:%02d 3:%02d 4:%02d 5:%02d 6:%02d 7:%02d 8:%02d R: %02d TOTAL: %02d", sampleOffset, partialUsage[0], partialUsage[1], partialUsage[2], partialUsage[3], partialUsage[4], partialUsage[5], partialUsage[6], partialUsage[7], partialUsage[8], MT32EMU_MAX_PARTIALS - partialManager->getFreePartialCount()); + printDebug("[+%lu] Partial Usage: 1:%02d 2:%02d 3:%02d 4:%02d 5:%02d 6:%02d 7:%02d 8:%02d R: %02d TOTAL: %02d", sampleOffset, partialUsage[0], partialUsage[1], partialUsage[2], partialUsage[3], partialUsage[4], partialUsage[5], partialUsage[6], partialUsage[7], partialUsage[8], getPartialCount() - partialManager->getFreePartialCount()); } else { - printDebug("Partial Usage: 1:%02d 2:%02d 3:%02d 4:%02d 5:%02d 6:%02d 7:%02d 8:%02d R: %02d TOTAL: %02d", partialUsage[0], partialUsage[1], partialUsage[2], partialUsage[3], partialUsage[4], partialUsage[5], partialUsage[6], partialUsage[7], partialUsage[8], MT32EMU_MAX_PARTIALS - partialManager->getFreePartialCount()); + printDebug("Partial Usage: 1:%02d 2:%02d 3:%02d 4:%02d 5:%02d 6:%02d 7:%02d 8:%02d R: %02d TOTAL: %02d", partialUsage[0], partialUsage[1], partialUsage[2], partialUsage[3], partialUsage[4], partialUsage[5], partialUsage[6], partialUsage[7], partialUsage[8], getPartialCount() - partialManager->getFreePartialCount()); } } bool Synth::hasActivePartials() const { - if (prerenderReadIx != prerenderWriteIx) { - // Data in the prerender buffer means that the current isActive() states are "in the future". - // It also means that partials are definitely active at this render point. - return true; - } - for (int partialNum = 0; partialNum < MT32EMU_MAX_PARTIALS; partialNum++) { + for (unsigned int partialNum = 0; partialNum < getPartialCount(); partialNum++) { if (partialManager->getPartial(partialNum)->isActive()) { return true; } @@ -1509,6 +1541,10 @@ bool Synth::hasActivePartials() const { return false; } +bool Synth::isAbortingPoly() const { + return abortingPoly != NULL; +} + bool Synth::isActive() const { if (hasActivePartials()) { return true; @@ -1523,6 +1559,10 @@ const Partial *Synth::getPartial(unsigned int partialNum) const { return partialManager->getPartial(partialNum); } +unsigned int Synth::getPartialCount() const { + return partialCount; +} + const Part *Synth::getPart(unsigned int partNum) const { if (partNum > 8) { return NULL; diff --git a/audio/softsynth/mt32/Synth.h b/audio/softsynth/mt32/Synth.h index b85e7ae507..783d6e2747 100644 --- a/audio/softsynth/mt32/Synth.h +++ b/audio/softsynth/mt32/Synth.h @@ -27,6 +27,7 @@ class Partial; class PartialManager; class Part; class ROMImage; +class BReverbModel; /** * Methods for emulating the connection between the LA32 and the DAC, which involves @@ -44,6 +45,7 @@ enum DACInputMode { // * Much less likely to overdrive than any other mode. // * Half the volume of any of the other modes, meaning its volume relative to the reverb // output when mixed together directly will sound wrong. + // * Output gain is ignored for both LA32 and reverb output. // * Perfect for developers while debugging :) DACInputMode_PURE, @@ -58,7 +60,17 @@ enum DACInputMode { DACInputMode_GENERATION2 }; -typedef void (*FloatToBit16sFunc)(Bit16s *target, const float *source, Bit32u len, float outputGain); +enum MIDIDelayMode { + // Process incoming MIDI events immediately. + MIDIDelayMode_IMMEDIATE, + + // Delay incoming short MIDI messages as if they where transferred via a MIDI cable to a real hardware unit and immediate sysex processing. + // This ensures more accurate timing of simultaneous NoteOn messages. + MIDIDelayMode_DELAY_SHORT_MESSAGES_ONLY, + + // Delay all incoming MIDI events as if they where transferred via a MIDI cable to a real hardware unit. + MIDIDelayMode_DELAY_ALL +}; const Bit8u SYSEX_MANUFACTURER_ROLAND = 0x41; @@ -217,18 +229,6 @@ public: ResetMemoryRegion(Synth *useSynth) : MemoryRegion(useSynth, NULL, NULL, MR_Reset, MT32EMU_MEMADDR(0x7F0000), 0x3FFF, 1) {} }; -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() = 0; - // May be called multiple times without an open() in between. - virtual void close() = 0; - virtual void setParameters(Bit8u time, Bit8u level) = 0; - virtual void process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples) = 0; - virtual bool isActive() const = 0; -}; - class ReportHandler { friend class Synth; @@ -244,22 +244,63 @@ protected: virtual void onErrorControlROM() {} virtual void onErrorPCMROM() {} virtual void showLCDMessage(const char *message); + virtual void onMIDIMessagePlayed() {} 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 /* hasActiveNonReleasingPolys */) {} virtual void onPolyStateChanged(int /* partNum */) {} - virtual void onPartialStateChanged(int /* partialNum */, int /* oldPartialPhase */, int /* newPartialPhase */) {} virtual void onProgramChanged(int /* partNum */, int /* bankNum */, const char * /* patchName */) {} }; +/** + * Used to safely store timestamped MIDI events in a local queue. + */ +struct MidiEvent { + Bit32u shortMessageData; + const Bit8u *sysexData; + Bit32u sysexLength; + Bit32u timestamp; + + ~MidiEvent(); + void setShortMessage(Bit32u shortMessageData, Bit32u timestamp); + void setSysex(const Bit8u *sysexData, Bit32u sysexLength, Bit32u timestamp); +}; + +/** + * Simple queue implementation using a ring buffer to store incoming MIDI event before the synth actually processes it. + * It is intended to: + * - get rid of prerenderer while retaining graceful partial abortion + * - add fair emulation of the MIDI interface delays + * - extend the synth interface with the default implementation of a typical rendering loop. + * THREAD SAFETY: + * It is safe to use either in a single thread environment or when there are only two threads - one performs only reading + * and one performs only writing. More complicated usage requires external synchronisation. + */ +class MidiEventQueue { +private: + MidiEvent *ringBuffer; + Bit32u ringBufferSize; + volatile Bit32u startPosition; + volatile Bit32u endPosition; + +public: + MidiEventQueue(Bit32u ringBufferSize = DEFAULT_MIDI_EVENT_QUEUE_SIZE); + ~MidiEventQueue(); + void reset(); + bool pushShortMessage(Bit32u shortMessageData, Bit32u timestamp); + bool pushSysex(const Bit8u *sysexData, Bit32u sysexLength, Bit32u timestamp); + const MidiEvent *peekMidiEvent(); + void dropMidiEvent(); +}; + class Synth { friend class Part; friend class RhythmPart; friend class Poly; friend class Partial; +friend class PartialManager; friend class Tables; friend class MemoryRegion; friend class TVA; @@ -286,20 +327,22 @@ private: Bit16s *pcmROMData; size_t pcmROMSize; // This is in 16-bit samples, therefore half the number of bytes in the ROM - Bit8s chantable[32]; - - Bit32u renderedSampleCount; + unsigned int partialCount; + Bit8s chantable[32]; // FIXME: Need explanation why 32 is set, obviously it should be 16 + MidiEventQueue *midiQueue; + volatile Bit32u lastReceivedMIDIEventTimestamp; + volatile Bit32u renderedSampleCount; MemParams mt32ram, mt32default; - ReverbModel *reverbModels[4]; - ReverbModel *reverbModel; + BReverbModel *reverbModels[4]; + BReverbModel *reverbModel; bool reverbEnabled; bool reverbOverridden; - FloatToBit16sFunc la32FloatToBit16sFunc; - FloatToBit16sFunc reverbFloatToBit16sFunc; + MIDIDelayMode midiDelayMode; + DACInputMode dacInputMode; float outputGain; float reverbOutputGain; @@ -311,41 +354,18 @@ private: PartialManager *partialManager; Part *parts[9]; - // FIXME: We can reorganise things so that we don't need all these separate tmpBuf, tmp and prerender buffers. - // This should be rationalised when things have stabilised a bit (if prerender buffers don't die in the mean time). - - float tmpBufPartialLeft[MAX_SAMPLES_PER_RUN]; - float tmpBufPartialRight[MAX_SAMPLES_PER_RUN]; - float tmpBufMixLeft[MAX_SAMPLES_PER_RUN]; - float tmpBufMixRight[MAX_SAMPLES_PER_RUN]; - float tmpBufReverbOutLeft[MAX_SAMPLES_PER_RUN]; - float tmpBufReverbOutRight[MAX_SAMPLES_PER_RUN]; - - Bit16s tmpNonReverbLeft[MAX_SAMPLES_PER_RUN]; - Bit16s tmpNonReverbRight[MAX_SAMPLES_PER_RUN]; - Bit16s tmpReverbDryLeft[MAX_SAMPLES_PER_RUN]; - Bit16s tmpReverbDryRight[MAX_SAMPLES_PER_RUN]; - Bit16s tmpReverbWetLeft[MAX_SAMPLES_PER_RUN]; - Bit16s tmpReverbWetRight[MAX_SAMPLES_PER_RUN]; - - // These ring buffers are only used to simulate delays present on the real device. - // In particular, when a partial needs to be aborted to free it up for use by a new Poly, + // When a partial needs to be aborted to free it up for use by a new Poly, // the controller will busy-loop waiting for the sound to finish. - Bit16s prerenderNonReverbLeft[MAX_PRERENDER_SAMPLES]; - Bit16s prerenderNonReverbRight[MAX_PRERENDER_SAMPLES]; - Bit16s prerenderReverbDryLeft[MAX_PRERENDER_SAMPLES]; - Bit16s prerenderReverbDryRight[MAX_PRERENDER_SAMPLES]; - Bit16s prerenderReverbWetLeft[MAX_PRERENDER_SAMPLES]; - Bit16s prerenderReverbWetRight[MAX_PRERENDER_SAMPLES]; - int prerenderReadIx; - int prerenderWriteIx; - - 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); - void doRenderStreams(Bit16s *nonReverbLeft, Bit16s *nonReverbRight, Bit16s *reverbDryLeft, Bit16s *reverbDryRight, Bit16s *reverbWetLeft, Bit16s *reverbWetRight, Bit32u len); - - void playAddressedSysex(unsigned char channel, const Bit8u *sysex, Bit32u len); + // We emulate this by delaying new MIDI events processing until abortion finishes. + Poly *abortingPoly; + + Bit32u getShortMessageLength(Bit32u msg); + Bit32u addMIDIInterfaceDelay(Bit32u len, Bit32u timestamp); + + void convertSamplesToOutput(Sample *target, const Sample *source, Bit32u len, bool reverb); + bool isAbortingPoly() const; + void doRenderStreams(Sample *nonReverbLeft, Sample *nonReverbRight, Sample *reverbDryLeft, Sample *reverbDryRight, Sample *reverbWetLeft, Sample *reverbWetRight, Bit32u len); + void readSysex(unsigned char channel, const Bit8u *sysex, Bit32u len) const; void initMemoryRegions(); void deleteMemoryRegions(); @@ -375,6 +395,14 @@ private: void printDebug(const char *fmt, ...); public: + static inline Bit16s clipBit16s(Bit32s sample) { + // Clamp values above 32767 to 32767, and values below -32768 to -32768 + if ((sample + 32768) & ~65535) { + return (sample >> 31) ^ 32767; + } + return (Bit16s)sample; + } + static Bit8u calcSysexChecksum(const Bit8u *data, Bit32u len, Bit8u checksum); // Optionally sets callbacks for reporting various errors, information and debug messages @@ -384,18 +412,44 @@ public: // Used to initialise the MT-32. Must be called before any other function. // Returns true if initialization was sucessful, otherwise returns false. // controlROMImage and pcmROMImage represent Control and PCM ROM images for use by synth. - bool open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage); + // usePartialCount sets the maximum number of partials playing simultaneously for this session. + bool open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage, unsigned int usePartialCount = DEFAULT_MAX_PARTIALS); // Closes the MT-32 and deallocates any memory used by the synthesizer void close(void); - // Sends a 4-byte MIDI message to the MT-32 for immediate playback - void playMsg(Bit32u msg); + // All the enqueued events are processed by the synth immediately. + void flushMIDIQueue(); + + // Sets size of the internal MIDI event queue. + // The queue is flushed before reallocation. + void setMIDIEventQueueSize(Bit32u); + + // Enqueues a MIDI event for subsequent playback. + // The minimum delay involves the delay introduced while the event is transferred via MIDI interface + // and emulation of the MCU busy-loop while it frees partials for use by a new Poly. + // Calls from multiple threads must be synchronised, although, + // no synchronisation is required with the rendering thread. + + // The MIDI event will be processed not before the specified timestamp. + // The timestamp is measured as the global rendered sample count since the synth was created. + bool playMsg(Bit32u msg, Bit32u timestamp); + bool playSysex(const Bit8u *sysex, Bit32u len, Bit32u timestamp); + // The MIDI event will be processed ASAP. + bool playMsg(Bit32u msg); + bool playSysex(const Bit8u *sysex, Bit32u len); + + // WARNING: + // The methods below don't ensure minimum 1-sample delay between sequential MIDI events, + // and a sequence of NoteOn and immediately succeeding NoteOff messages is always silent. + + // Sends a 4-byte MIDI message to the MT-32 for immediate playback. + void playMsgNow(Bit32u msg); void playMsgOnPart(unsigned char part, unsigned char code, unsigned char note, unsigned char velocity); // Sends a string of Sysex commands to the MT-32 for immediate interpretation // The length is in bytes - void playSysex(const Bit8u *sysex, Bit32u len); + void playSysexNow(const Bit8u *sysex, Bit32u len); void playSysexWithoutFraming(const Bit8u *sysex, Bit32u len); void playSysexWithoutHeader(unsigned char device, unsigned char command, const Bit8u *sysex, Bit32u len); void writeSysex(unsigned char channel, const Bit8u *sysex, Bit32u len); @@ -405,20 +459,31 @@ public: void setReverbOverridden(bool reverbOverridden); bool isReverbOverridden() const; void setDACInputMode(DACInputMode mode); + DACInputMode getDACInputMode() const; + void setMIDIDelayMode(MIDIDelayMode mode); + MIDIDelayMode getMIDIDelayMode() const; // Sets output gain factor. Applied to all output samples and unrelated with the synth's Master volume. + // Ignored in DACInputMode_PURE void setOutputGain(float); + float getOutputGain() const; // Sets output gain factor for the reverb wet output. setOutputGain() doesn't change reverb output gain. + // Note: We're currently emulate CM-32L/CM-64 reverb quite accurately and the reverb output level closely + // corresponds to the level of digital capture. Although, according to the CM-64 PCB schematic, + // there is a difference in the reverb analogue circuit, and the resulting output gain is 0.68 + // of that for LA32 analogue output. This factor is applied to the reverb output gain. + // Ignored in DACInputMode_PURE void setReverbOutputGain(float); + float getReverbOutputGain() const; // Renders samples to the specified output stream. // The length is in frames, not bytes (in 16-bit stereo, // one frame is 4 bytes). - void render(Bit16s *stream, Bit32u len); + void render(Sample *stream, Bit32u len); // Renders samples to the specified output streams (any or all of which may be NULL). - void renderStreams(Bit16s *nonReverbLeft, Bit16s *nonReverbRight, Bit16s *reverbDryLeft, Bit16s *reverbDryRight, Bit16s *reverbWetLeft, Bit16s *reverbWetRight, Bit32u len); + void renderStreams(Sample *nonReverbLeft, Sample *nonReverbRight, Sample *reverbDryLeft, Sample *reverbDryRight, Sample *reverbWetLeft, Sample *reverbWetRight, Bit32u len); // Returns true when there is at least one active partial, otherwise false. bool hasActivePartials() const; @@ -428,6 +493,9 @@ public: const Partial *getPartial(unsigned int partialNum) const; + // Returns the maximum number of partials playing simultaneously. + unsigned int getPartialCount() const; + void readMemory(Bit32u addr, Bit32u len, Bit8u *data); // partNum should be 0..7 for Part 1..8, or 8 for Rhythm diff --git a/audio/softsynth/mt32/TVP.cpp b/audio/softsynth/mt32/TVP.cpp index c3e64c18d0..8f68245753 100644 --- a/audio/softsynth/mt32/TVP.cpp +++ b/audio/softsynth/mt32/TVP.cpp @@ -181,7 +181,7 @@ void TVP::updatePitch() { 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. - partial->tva->recalcSustain(); + partial->getTVA()->recalcSustain(); } void TVP::targetPitchOffsetReached() { diff --git a/audio/softsynth/mt32/Tables.h b/audio/softsynth/mt32/Tables.h index 8b4580df0e..bfb80e121e 100644 --- a/audio/softsynth/mt32/Tables.h +++ b/audio/softsynth/mt32/Tables.h @@ -25,6 +25,11 @@ namespace MT32Emu { // The output from the synth is supposed to be resampled to convert the sample rate. const unsigned int SAMPLE_RATE = 32000; +// MIDI interface data transfer rate in samples. Used to simulate the transfer delay. +const double MIDI_DATA_TRANSFER_RATE = (double)SAMPLE_RATE / 31250.0 * 8.0; + +const float CM32L_REVERB_TO_LA32_ANALOG_OUTPUT_GAIN_FACTOR = 0.68f; + const int MIDDLEC = 60; class Synth; diff --git a/audio/softsynth/mt32/freeverb.cpp b/audio/softsynth/mt32/freeverb.cpp deleted file mode 100644 index 181b878596..0000000000 --- a/audio/softsynth/mt32/freeverb.cpp +++ /dev/null @@ -1,324 +0,0 @@ -// Allpass filter implementation -// -// Written by Jezar at Dreampoint, June 2000 -// http://www.dreampoint.co.uk -// This code is public domain - -#include "freeverb.h" - -allpass::allpass() -{ - bufidx = 0; -} - -void allpass::setbuffer(float *buf, int size) -{ - buffer = buf; - bufsize = size; -} - -void allpass::mute() -{ - for (int i=0; i<bufsize; i++) - buffer[i]=0; -} - -void allpass::setfeedback(float val) -{ - feedback = val; -} - -float allpass::getfeedback() -{ - return feedback; -} - -void allpass::deletebuffer() -{ - delete[] buffer; - buffer = 0; -} -// Comb filter implementation -// -// Written by Jezar at Dreampoint, June 2000 -// http://www.dreampoint.co.uk -// This code is public domain - -comb::comb() -{ - filterstore = 0; - bufidx = 0; -} - -void comb::setbuffer(float *buf, int size) -{ - buffer = buf; - bufsize = size; -} - -void comb::mute() -{ - for (int i=0; i<bufsize; i++) - buffer[i]=0; -} - -void comb::setdamp(float val) -{ - damp1 = val; - damp2 = 1-val; -} - -float comb::getdamp() -{ - return damp1; -} - -void comb::setfeedback(float val) -{ - feedback = val; -} - -float comb::getfeedback() -{ - return feedback; -} - -void comb::deletebuffer() -{ - delete[] buffer; - buffer = 0; -} -// Reverb model implementation -// -// Written by Jezar at Dreampoint, June 2000 -// Modifications by Jerome Fisher, 2009, 2011 -// http://www.dreampoint.co.uk -// This code is public domain - -revmodel::revmodel(float scaletuning) -{ - int i; - int bufsize; - - // Allocate buffers for the components - for (i = 0; i < numcombs; i++) { - bufsize = int(scaletuning * combtuning[i]); - combL[i].setbuffer(new float[bufsize], bufsize); - bufsize += int(scaletuning * stereospread); - combR[i].setbuffer(new float[bufsize], bufsize); - } - for (i = 0; i < numallpasses; i++) { - bufsize = int(scaletuning * allpasstuning[i]); - allpassL[i].setbuffer(new float[bufsize], bufsize); - allpassL[i].setfeedback(0.5f); - bufsize += int(scaletuning * stereospread); - allpassR[i].setbuffer(new float[bufsize], bufsize); - allpassR[i].setfeedback(0.5f); - } - - // Set default values - dry = initialdry; - wet = initialwet*scalewet; - damp = initialdamp*scaledamp; - roomsize = (initialroom*scaleroom) + offsetroom; - width = initialwidth; - mode = initialmode; - update(); - - // Buffer will be full of rubbish - so we MUST mute them - mute(); -} - -revmodel::~revmodel() -{ - int i; - - for (i = 0; i < numcombs; i++) { - combL[i].deletebuffer(); - combR[i].deletebuffer(); - } - for (i = 0; i < numallpasses; i++) { - allpassL[i].deletebuffer(); - allpassR[i].deletebuffer(); - } -} - -void revmodel::mute() -{ - int i; - - if (getmode() >= freezemode) - return; - - for (i=0;i<numcombs;i++) - { - combL[i].mute(); - combR[i].mute(); - } - for (i=0;i<numallpasses;i++) - { - allpassL[i].mute(); - allpassR[i].mute(); - } - - // Init LPF history - filtprev1 = 0; - filtprev2 = 0; -} - -void revmodel::process(const float *inputL, const float *inputR, float *outputL, float *outputR, long numsamples) -{ - float outL,outR,input; - - while (numsamples-- > 0) - { - int i; - - outL = outR = 0; - input = (*inputL + *inputR) * gain; - - // Implementation of 2-stage IIR single-pole low-pass filter - // found at the entrance of reverb processing on real devices - filtprev1 += (input - filtprev1) * filtval; - filtprev2 += (filtprev1 - filtprev2) * filtval; - input = filtprev2; - - int s = -1; - // Accumulate comb filters in parallel - for (i=0; i<numcombs; i++) - { - outL += s * combL[i].process(input); - outR += s * combR[i].process(input); - s = -s; - } - - // Feed through allpasses in series - for (i=0; i<numallpasses; i++) - { - outL = allpassL[i].process(outL); - outR = allpassR[i].process(outR); - } - - // Calculate output REPLACING anything already there - *outputL = outL*wet1 + outR*wet2; - *outputR = outR*wet1 + outL*wet2; - - inputL++; - inputR++; - outputL++; - outputR++; - } -} - -void revmodel::update() -{ -// Recalculate internal values after parameter change - - int i; - - wet1 = wet*(width/2 + 0.5f); - wet2 = wet*((1-width)/2); - - if (mode >= freezemode) - { - roomsize1 = 1; - damp1 = 0; - gain = muted; - } - else - { - roomsize1 = roomsize; - damp1 = damp; - gain = fixedgain; - } - - for (i=0; i<numcombs; i++) - { - combL[i].setfeedback(roomsize1); - combR[i].setfeedback(roomsize1); - } - - for (i=0; i<numcombs; i++) - { - combL[i].setdamp(damp1); - combR[i].setdamp(damp1); - } -} - -// The following get/set functions are not inlined, because -// speed is never an issue when calling them, and also -// because as you develop the reverb model, you may -// wish to take dynamic action when they are called. - -void revmodel::setroomsize(float value) -{ - roomsize = (value*scaleroom) + offsetroom; - update(); -} - -float revmodel::getroomsize() -{ - return (roomsize-offsetroom)/scaleroom; -} - -void revmodel::setdamp(float value) -{ - damp = value*scaledamp; - update(); -} - -float revmodel::getdamp() -{ - return damp/scaledamp; -} - -void revmodel::setwet(float value) -{ - wet = value*scalewet; - update(); -} - -float revmodel::getwet() -{ - return wet/scalewet; -} - -void revmodel::setdry(float value) -{ - dry = value*scaledry; -} - -float revmodel::getdry() -{ - return dry/scaledry; -} - -void revmodel::setwidth(float value) -{ - width = value; - update(); -} - -float revmodel::getwidth() -{ - return width; -} - -void revmodel::setmode(float value) -{ - mode = value; - update(); -} - -float revmodel::getmode() -{ - if (mode >= freezemode) - return 1; - else - return 0; -} - -void revmodel::setfiltval(float value) -{ - filtval = value; -} diff --git a/audio/softsynth/mt32/freeverb.h b/audio/softsynth/mt32/freeverb.h deleted file mode 100644 index ae4d48169e..0000000000 --- a/audio/softsynth/mt32/freeverb.h +++ /dev/null @@ -1,189 +0,0 @@ -#ifndef _freeverb_ -#define _freeverb_ - -// Reverb model tuning values -// -// Written by Jezar at Dreampoint, June 2000 -// http://www.dreampoint.co.uk -// This code is public domain - -const int numcombs = 8; -const int numallpasses = 4; -const float muted = 0; -const float fixedgain = 0.015f; -const float scalewet = 3; -const float scaledry = 2; -const float scaledamp = 0.4f; -const float scaleroom = 0.28f; -const float offsetroom = 0.7f; -const float initialroom = 0.5f; -const float initialdamp = 0.5f; -const float initialwet = 1/scalewet; -const float initialdry = 0; -const float initialwidth = 1; -const float initialmode = 0; -const float freezemode = 0.5f; -const int stereospread = 23; - -const int combtuning[] = {1116, 1188, 1277, 1356, 1422, 1491, 1557, 1617}; -const int allpasstuning[] = {556, 441, 341, 225}; - -// Macro for killing denormalled numbers -// -// Written by Jezar at Dreampoint, June 2000 -// http://www.dreampoint.co.uk -// Based on IS_DENORMAL macro by Jon Watte -// This code is public domain - -static inline float undenormalise(float x) { - union { - float f; - unsigned int i; - } u; - u.f = x; - if ((u.i & 0x7f800000) == 0) { - return 0.0f; - } - return x; -} - -// Allpass filter declaration -// -// Written by Jezar at Dreampoint, June 2000 -// http://www.dreampoint.co.uk -// This code is public domain - -class allpass -{ -public: - allpass(); - void setbuffer(float *buf, int size); - void deletebuffer(); - inline float process(float inp); - void mute(); - void setfeedback(float val); - float getfeedback(); -// private: - float feedback; - float *buffer; - int bufsize; - int bufidx; -}; - - -// Big to inline - but crucial for speed - -inline float allpass::process(float input) -{ - float output; - float bufout; - - bufout = undenormalise(buffer[bufidx]); - - output = -input + bufout; - buffer[bufidx] = input + (bufout*feedback); - - if (++bufidx>=bufsize) bufidx = 0; - - return output; -} - -// Comb filter class declaration -// -// Written by Jezar at Dreampoint, June 2000 -// http://www.dreampoint.co.uk -// This code is public domain - -class comb -{ -public: - comb(); - void setbuffer(float *buf, int size); - void deletebuffer(); - inline float process(float inp); - void mute(); - void setdamp(float val); - float getdamp(); - void setfeedback(float val); - float getfeedback(); -private: - float feedback; - float filterstore; - float damp1; - float damp2; - float *buffer; - int bufsize; - int bufidx; -}; - - -// Big to inline - but crucial for speed - -inline float comb::process(float input) -{ - float output; - - output = undenormalise(buffer[bufidx]); - - filterstore = undenormalise((output*damp2) + (filterstore*damp1)); - - buffer[bufidx] = input + (filterstore*feedback); - - if (++bufidx>=bufsize) bufidx = 0; - - return output; -} - -// Reverb model declaration -// -// Written by Jezar at Dreampoint, June 2000 -// Modifications by Jerome Fisher, 2009 -// http://www.dreampoint.co.uk -// This code is public domain - -class revmodel -{ -public: - revmodel(float scaletuning); - ~revmodel(); - void mute(); - void process(const float *inputL, const float *inputR, float *outputL, float *outputR, long numsamples); - void setroomsize(float value); - float getroomsize(); - void setdamp(float value); - float getdamp(); - void setwet(float value); - float getwet(); - void setdry(float value); - float getdry(); - void setwidth(float value); - float getwidth(); - void setmode(float value); - float getmode(); - void setfiltval(float value); -private: - void update(); -private: - float gain; - float roomsize,roomsize1; - float damp,damp1; - float wet,wet1,wet2; - float dry; - float width; - float mode; - - // LPF stuff - float filtval; - float filtprev1; - float filtprev2; - - // Comb filters - comb combL[numcombs]; - comb combR[numcombs]; - - // Allpass filters - allpass allpassL[numallpasses]; - allpass allpassR[numallpasses]; -}; - -#endif//_freeverb_ diff --git a/audio/softsynth/mt32/module.mk b/audio/softsynth/mt32/module.mk index e7afdfd2b4..1c8aa125ab 100644 --- a/audio/softsynth/mt32/module.mk +++ b/audio/softsynth/mt32/module.mk @@ -1,24 +1,19 @@ MODULE := audio/softsynth/mt32 MODULE_OBJS := \ - AReverbModel.o \ BReverbModel.o \ - DelayReverb.o \ - FreeverbModel.o \ LA32Ramp.o \ LA32WaveGenerator.o \ - LegacyWaveGenerator.o \ Part.o \ Partial.o \ PartialManager.o \ Poly.o \ ROMInfo.o \ Synth.o \ + Tables.o \ TVA.o \ TVF.o \ - TVP.o \ - Tables.o \ - freeverb.o + TVP.o # Include common rules include $(srcdir)/rules.mk diff --git a/audio/softsynth/mt32/mt32emu.h b/audio/softsynth/mt32/mt32emu.h index 971a0886d5..ab963886ac 100644 --- a/audio/softsynth/mt32/mt32emu.h +++ b/audio/softsynth/mt32/mt32emu.h @@ -60,27 +60,24 @@ #define MT32EMU_MONITOR_TVF 0 // Configuration -// The maximum number of partials playing simultaneously -#define MT32EMU_MAX_PARTIALS 32 -// The maximum number of notes playing simultaneously per part. -// No point making it more than MT32EMU_MAX_PARTIALS, since each note needs at least one partial. -#define MT32EMU_MAX_POLY 32 // If non-zero, deletes reverb buffers that are not in use to save memory. // 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 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 +// 0: Maximum speed at the cost of a bit lower emulation accuracy. +// 1: Maximum achievable emulation accuracy. +#define MT32EMU_BOSS_REVERB_PRECISE_MODE 0 -// 0: Use refined wave generator based on logarithmic fixed-point computations and LUTs -// 1: Use legacy accurate wave generator based on float computations -#define MT32EMU_ACCURATE_WG 0 +// 0: Use 16-bit signed samples and refined wave generator based on logarithmic fixed-point computations and LUTs. Maximum emulation accuracy and speed. +// 1: Use float samples in the wave generator and renderer. Maximum output quality and minimum noise. +#define MT32EMU_USE_FLOAT_SAMPLES 0 namespace MT32Emu { +// The default value for the maximum number of partials playing simultaneously. +const unsigned int DEFAULT_MAX_PARTIALS = 32; + // The higher this number, the more memory will be used, but the more samples can be processed in one run - // various parts of sample generation can be processed more efficiently in a single run. // A run's maximum length is that given to Synth::render(), so giving a value here higher than render() is ever @@ -90,11 +87,14 @@ namespace MT32Emu // This value must be >= 1. const unsigned int MAX_SAMPLES_PER_RUN = 4096; -// This determines the amount of memory available for simulating delays. -// If set too low, partials aborted to allow other partials to play will not end gracefully, but will terminate -// abruptly and potentially cause a pop/crackle in the audio output. -// This value must be >= 1. -const unsigned int MAX_PRERENDER_SAMPLES = 1024; +// The default size of the internal MIDI event queue. +// It holds the incoming MIDI events before the rendering engine actually processes them. +// The main goal is to fairly emulate the real hardware behaviour which obviously +// uses an internal MIDI event queue to gather incoming data as well as the delays +// introduced by transferring data via the MIDI interface. +// This also facilitates building of an external rendering loop +// as the queue stores timestamped MIDI events. +const unsigned int DEFAULT_MIDI_EVENT_QUEUE_SIZE = 1024; } #include "Structures.h" @@ -103,7 +103,6 @@ const unsigned int MAX_PRERENDER_SAMPLES = 1024; #include "Poly.h" #include "LA32Ramp.h" #include "LA32WaveGenerator.h" -#include "LegacyWaveGenerator.h" #include "TVA.h" #include "TVP.h" #include "TVF.h" |