From 00992c1e68444a8123ffc89a971751cecf7287ed Mon Sep 17 00:00:00 2001 From: Filippos Karapetis Date: Wed, 21 Aug 2013 03:41:30 +0300 Subject: MT-32: Sync with the latest changes in munt --- audio/softsynth/mt32/AReverbModel.cpp | 277 ----------- audio/softsynth/mt32/AReverbModel.h | 87 ---- audio/softsynth/mt32/BReverbModel.cpp | 129 +++-- audio/softsynth/mt32/BReverbModel.h | 24 +- audio/softsynth/mt32/DelayReverb.cpp | 142 ------ audio/softsynth/mt32/DelayReverb.h | 50 -- audio/softsynth/mt32/FreeverbModel.cpp | 78 --- audio/softsynth/mt32/FreeverbModel.h | 44 -- audio/softsynth/mt32/LA32FloatWaveGenerator.cpp | 343 +++++++++++++ audio/softsynth/mt32/LA32FloatWaveGenerator.h | 142 ++++++ audio/softsynth/mt32/LA32WaveGenerator.cpp | 9 +- audio/softsynth/mt32/LA32WaveGenerator.h | 6 +- audio/softsynth/mt32/LegacyWaveGenerator.cpp | 347 -------------- audio/softsynth/mt32/LegacyWaveGenerator.h | 146 ------ audio/softsynth/mt32/Part.cpp | 43 +- audio/softsynth/mt32/Part.h | 4 +- audio/softsynth/mt32/Partial.cpp | 39 +- audio/softsynth/mt32/Partial.h | 25 +- audio/softsynth/mt32/PartialManager.cpp | 62 ++- audio/softsynth/mt32/PartialManager.h | 12 +- audio/softsynth/mt32/Poly.cpp | 49 +- audio/softsynth/mt32/Poly.h | 6 +- audio/softsynth/mt32/Structures.h | 6 + audio/softsynth/mt32/Synth.cpp | 608 +++++++++++++----------- audio/softsynth/mt32/Synth.h | 192 +++++--- audio/softsynth/mt32/TVP.cpp | 2 +- audio/softsynth/mt32/Tables.h | 5 + audio/softsynth/mt32/freeverb.cpp | 324 ------------- audio/softsynth/mt32/freeverb.h | 189 -------- audio/softsynth/mt32/module.mk | 9 +- audio/softsynth/mt32/mt32emu.h | 35 +- 31 files changed, 1211 insertions(+), 2223 deletions(-) delete mode 100644 audio/softsynth/mt32/AReverbModel.cpp delete mode 100644 audio/softsynth/mt32/AReverbModel.h delete mode 100644 audio/softsynth/mt32/DelayReverb.cpp delete mode 100644 audio/softsynth/mt32/DelayReverb.h delete mode 100644 audio/softsynth/mt32/FreeverbModel.cpp delete mode 100644 audio/softsynth/mt32/FreeverbModel.h create mode 100644 audio/softsynth/mt32/LA32FloatWaveGenerator.cpp create mode 100644 audio/softsynth/mt32/LA32FloatWaveGenerator.h delete mode 100644 audio/softsynth/mt32/LegacyWaveGenerator.cpp delete mode 100644 audio/softsynth/mt32/LegacyWaveGenerator.h delete mode 100644 audio/softsynth/mt32/freeverb.cpp delete mode 100644 audio/softsynth/mt32/freeverb.h (limited to 'audio/softsynth') 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 . - */ - -#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 . - */ - -#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 . */ +//#include #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 (*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 . - */ - -//#include -//#include -#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 . - */ - -#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 . - */ - -#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 . - */ - -#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/LA32FloatWaveGenerator.cpp b/audio/softsynth/mt32/LA32FloatWaveGenerator.cpp new file mode 100644 index 0000000000..486942b75c --- /dev/null +++ b/audio/softsynth/mt32/LA32FloatWaveGenerator.cpp @@ -0,0 +1,343 @@ +/* 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 . + */ + +//#include +#include "mt32emu.h" +#include "mmath.h" +#include "LA32FloatWaveGenerator.h" + +namespace MT32Emu { + +static const float MIDDLE_CUTOFF_VALUE = 128.0f; +static const float RESONANCE_DECAY_THRESHOLD_CUTOFF_VALUE = 144.0f; +static const float MAX_CUTOFF_VALUE = 240.0f; + +float LA32WaveGenerator::getPCMSample(unsigned int position) { + if (position >= pcmWaveLength) { + if (!pcmWaveLooped) { + return 0; + } + position = position % pcmWaveLength; + } + Bit16s pcmSample = pcmWaveAddress[position]; + float sampleValue = EXP2F(((pcmSample & 32767) - 32787.0f) / 2048.0f); + return ((pcmSample & 32768) == 0) ? sampleValue : -sampleValue; +} + +void LA32WaveGenerator::initSynth(const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance) { + this->sawtoothWaveform = sawtoothWaveform; + this->pulseWidth = pulseWidth; + this->resonance = resonance; + + wavePos = 0.0f; + lastFreq = 0.0f; + + pcmWaveAddress = NULL; + active = true; +} + +void LA32WaveGenerator::initPCM(const Bit16s * const pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped, const bool pcmWaveInterpolated) { + this->pcmWaveAddress = pcmWaveAddress; + this->pcmWaveLength = pcmWaveLength; + this->pcmWaveLooped = pcmWaveLooped; + this->pcmWaveInterpolated = pcmWaveInterpolated; + + pcmPosition = 0.0f; + active = true; +} + +float LA32WaveGenerator::generateNextSample(const Bit32u ampVal, const Bit16u pitch, const Bit32u cutoffRampVal) { + if (!active) { + return 0.0f; + } + + this->amp = amp; + this->pitch = pitch; + + float sample = 0.0f; + + // SEMI-CONFIRMED: From sample analysis: + // (1) Tested with a single partial playing PCM wave 77 with pitchCoarse 36 and no keyfollow, velocity follow, etc. + // This gives results within +/- 2 at the output (before any DAC bitshifting) + // when sustaining at levels 156 - 255 with no modifiers. + // (2) Tested with a special square wave partial (internal capture ID tva5) at TVA envelope levels 155-255. + // This gives deltas between -1 and 0 compared to the real output. Note that this special partial only produces + // positive amps, so negative still needs to be explored, as well as lower levels. + // + // Also still partially unconfirmed is the behaviour when ramping between levels, as well as the timing. + + float amp = EXP2F(ampVal / -1024.0f / 4096.0f); + float freq = EXP2F(pitch / 4096.0f - 16.0f) * SAMPLE_RATE; + + if (isPCMWave()) { + // Render PCM waveform + int len = pcmWaveLength; + int intPCMPosition = (int)pcmPosition; + if (intPCMPosition >= len && !pcmWaveLooped) { + // We're now past the end of a non-looping PCM waveform so it's time to die. + deactivate(); + return 0.0f; + } + float positionDelta = freq * 2048.0f / SAMPLE_RATE; + + // Linear interpolation + float firstSample = getPCMSample(intPCMPosition); + // We observe that for partial structures with ring modulation the interpolation is not applied to the slave PCM partial. + // It's assumed that the multiplication circuitry intended to perform the interpolation on the slave PCM partial + // is borrowed by the ring modulation circuit (or the LA32 chip has a similar lack of resources assigned to each partial pair). + if (pcmWaveInterpolated) { + sample = firstSample + (getPCMSample(intPCMPosition + 1) - firstSample) * (pcmPosition - intPCMPosition); + } else { + sample = firstSample; + } + + float newPCMPosition = pcmPosition + positionDelta; + if (pcmWaveLooped) { + newPCMPosition = fmod(newPCMPosition, (float)pcmWaveLength); + } + pcmPosition = newPCMPosition; + } else { + // Render synthesised waveform + wavePos *= lastFreq / freq; + lastFreq = freq; + + float resAmp = EXP2F(1.0f - (32 - resonance) / 4.0f); + { + //static const float resAmpFactor = EXP2F(-7); + //resAmp = EXP2I(resonance << 10) * resAmpFactor; + } + + // The cutoffModifier may not be supposed to be directly added to the cutoff - + // it may for example need to be multiplied in some way. + // The 240 cutoffVal limit was determined via sample analysis (internal Munt capture IDs: glop3, glop4). + // More research is needed to be sure that this is correct, however. + float cutoffVal = cutoffRampVal / 262144.0f; + if (cutoffVal > MAX_CUTOFF_VALUE) { + cutoffVal = MAX_CUTOFF_VALUE; + } + + // Wave length in samples + float waveLen = SAMPLE_RATE / freq; + + // Init cosineLen + float cosineLen = 0.5f * waveLen; + if (cutoffVal > MIDDLE_CUTOFF_VALUE) { + cosineLen *= EXP2F((cutoffVal - MIDDLE_CUTOFF_VALUE) / -16.0f); // found from sample analysis + } + + // Start playing in center of first cosine segment + // relWavePos is shifted by a half of cosineLen + float relWavePos = wavePos + 0.5f * cosineLen; + if (relWavePos > waveLen) { + relWavePos -= waveLen; + } + + // Ratio of positive segment to wave length + float pulseLen = 0.5f; + if (pulseWidth > 128) { + pulseLen = EXP2F((64 - pulseWidth) / 64.0f); + //static const float pulseLenFactor = EXP2F(-192 / 64); + //pulseLen = EXP2I((256 - pulseWidthVal) << 6) * pulseLenFactor; + } + pulseLen *= waveLen; + + float hLen = pulseLen - cosineLen; + + // Ignore pulsewidths too high for given freq + if (hLen < 0.0f) { + hLen = 0.0f; + } + + // Ignore pulsewidths too high for given freq and cutoff + float lLen = waveLen - hLen - 2 * cosineLen; + if (lLen < 0.0f) { + lLen = 0.0f; + } + + // Correct resAmp for cutoff in range 50..66 + if ((cutoffVal >= 128.0f) && (cutoffVal < 144.0f)) { + resAmp *= sin(FLOAT_PI * (cutoffVal - 128.0f) / 32.0f); + } + + // Produce filtered square wave with 2 cosine waves on slopes + + // 1st cosine segment + if (relWavePos < cosineLen) { + sample = -cos(FLOAT_PI * relWavePos / cosineLen); + } else + + // high linear segment + if (relWavePos < (cosineLen + hLen)) { + sample = 1.f; + } else + + // 2nd cosine segment + if (relWavePos < (2 * cosineLen + hLen)) { + sample = cos(FLOAT_PI * (relWavePos - (cosineLen + hLen)) / cosineLen); + } else { + + // low linear segment + sample = -1.f; + } + + if (cutoffVal < 128.0f) { + + // Attenuate samples below cutoff 50 + // Found by sample analysis + sample *= EXP2F(-0.125f * (128.0f - cutoffVal)); + } else { + + // Add resonance sine. Effective for cutoff > 50 only + float resSample = 1.0f; + + // Resonance decay speed factor + float resAmpDecayFactor = Tables::getInstance().resAmpDecayFactor[resonance >> 2]; + + // Now relWavePos counts from the middle of first cosine + relWavePos = wavePos; + + // negative segments + if (!(relWavePos < (cosineLen + hLen))) { + resSample = -resSample; + relWavePos -= cosineLen + hLen; + + // From the digital captures, the decaying speed of the resonance sine is found a bit different for the positive and the negative segments + resAmpDecayFactor += 0.25f; + } + + // Resonance sine WG + resSample *= sin(FLOAT_PI * relWavePos / cosineLen); + + // Resonance sine amp + float resAmpFadeLog2 = -0.125f * resAmpDecayFactor * (relWavePos / cosineLen); // seems to be exact + float resAmpFade = EXP2F(resAmpFadeLog2); + + // Now relWavePos set negative to the left from center of any cosine + relWavePos = wavePos; + + // negative segment + if (!(wavePos < (waveLen - 0.5f * cosineLen))) { + relWavePos -= waveLen; + } else + + // positive segment + if (!(wavePos < (hLen + 0.5f * cosineLen))) { + relWavePos -= cosineLen + hLen; + } + + // To ensure the output wave has no breaks, two different windows are appied to the beginning and the ending of the resonance sine segment + if (relWavePos < 0.5f * cosineLen) { + float syncSine = sin(FLOAT_PI * relWavePos / cosineLen); + if (relWavePos < 0.0f) { + // The window is synchronous square sine here + resAmpFade *= syncSine * syncSine; + } else { + // The window is synchronous sine here + resAmpFade *= syncSine; + } + } + + sample += resSample * resAmp * resAmpFade; + } + + // sawtooth waves + if (sawtoothWaveform) { + sample *= cos(FLOAT_2PI * wavePos / waveLen); + } + + wavePos++; + + // wavePos isn't supposed to be > waveLen + if (wavePos > waveLen) { + wavePos -= waveLen; + } + } + + // Multiply sample with current TVA value + sample *= amp; + return sample; +} + +void LA32WaveGenerator::deactivate() { + active = false; +} + +bool LA32WaveGenerator::isActive() const { + return active; +} + +bool LA32WaveGenerator::isPCMWave() const { + return pcmWaveAddress != NULL; +} + +void LA32PartialPair::init(const bool ringModulated, const bool mixed) { + this->ringModulated = ringModulated; + this->mixed = mixed; + masterOutputSample = 0.0f; + slaveOutputSample = 0.0f; +} + +void LA32PartialPair::initSynth(const PairType useMaster, const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance) { + if (useMaster == MASTER) { + master.initSynth(sawtoothWaveform, pulseWidth, resonance); + } else { + slave.initSynth(sawtoothWaveform, pulseWidth, resonance); + } +} + +void LA32PartialPair::initPCM(const PairType useMaster, const Bit16s *pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped) { + if (useMaster == MASTER) { + master.initPCM(pcmWaveAddress, pcmWaveLength, pcmWaveLooped, true); + } else { + slave.initPCM(pcmWaveAddress, pcmWaveLength, pcmWaveLooped, !ringModulated); + } +} + +void LA32PartialPair::generateNextSample(const PairType useMaster, const Bit32u amp, const Bit16u pitch, const Bit32u cutoff) { + if (useMaster == MASTER) { + masterOutputSample = master.generateNextSample(amp, pitch, cutoff); + } else { + slaveOutputSample = slave.generateNextSample(amp, pitch, cutoff); + } +} + +float LA32PartialPair::nextOutSample() { + float outputSample; + if (ringModulated) { + float ringModulatedSample = masterOutputSample * slaveOutputSample; + outputSample = mixed ? masterOutputSample + ringModulatedSample : ringModulatedSample; + } else { + outputSample = masterOutputSample + slaveOutputSample; + } + return outputSample; +} + +void LA32PartialPair::deactivate(const PairType useMaster) { + if (useMaster == MASTER) { + master.deactivate(); + masterOutputSample = 0.0f; + } else { + slave.deactivate(); + slaveOutputSample = 0.0f; + } +} + +bool LA32PartialPair::isActive(const PairType useMaster) const { + return useMaster == MASTER ? master.isActive() : slave.isActive(); +} + +} diff --git a/audio/softsynth/mt32/LA32FloatWaveGenerator.h b/audio/softsynth/mt32/LA32FloatWaveGenerator.h new file mode 100644 index 0000000000..9046160083 --- /dev/null +++ b/audio/softsynth/mt32/LA32FloatWaveGenerator.h @@ -0,0 +1,142 @@ +/* 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 . + */ + +#ifndef MT32EMU_LA32_WAVE_GENERATOR_H +#define MT32EMU_LA32_WAVE_GENERATOR_H + +namespace MT32Emu { + +/** + * LA32WaveGenerator is aimed to represent the exact model of LA32 wave generator. + * The output square wave is created by adding high / low linear segments in-between + * the rising and falling cosine segments. Basically, it’s very similar to the phase distortion synthesis. + * Behaviour of a true resonance filter is emulated by adding decaying sine wave. + * The beginning and the ending of the resonant sine is multiplied by a cosine window. + * To synthesise sawtooth waves, the resulting square wave is multiplied by synchronous cosine wave. + */ +class LA32WaveGenerator { + //*************************************************************************** + // The local copy of partial parameters below + //*************************************************************************** + + bool active; + + // True means the resulting square wave is to be multiplied by the synchronous cosine + bool sawtoothWaveform; + + // Logarithmic amp of the wave generator + Bit32u amp; + + // Logarithmic frequency of the resulting wave + Bit16u pitch; + + // Values in range [1..31] + // Value 1 correspong to the minimum resonance + Bit8u resonance; + + // Processed value in range [0..255] + // Values in range [0..128] have no effect and the resulting wave remains symmetrical + // Value 255 corresponds to the maximum possible asymmetric of the resulting wave + Bit8u pulseWidth; + + // Composed of the base cutoff in range [78..178] left-shifted by 18 bits and the TVF modifier + Bit32u cutoffVal; + + // Logarithmic PCM sample start address + const Bit16s *pcmWaveAddress; + + // Logarithmic PCM sample length + Bit32u pcmWaveLength; + + // true for looped logarithmic PCM samples + bool pcmWaveLooped; + + // false for slave PCM partials in the structures with the ring modulation + bool pcmWaveInterpolated; + + //*************************************************************************** + // Internal variables below + //*************************************************************************** + + float wavePos; + float lastFreq; + float pcmPosition; + + float getPCMSample(unsigned int position); + +public: + // Initialise the WG engine for generation of synth partial samples and set up the invariant parameters + void initSynth(const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance); + + // Initialise the WG engine for generation of PCM partial samples and set up the invariant parameters + void initPCM(const Bit16s * const pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped, const bool pcmWaveInterpolated); + + // Update parameters with respect to TVP, TVA and TVF, and generate next sample + float generateNextSample(const Bit32u amp, const Bit16u pitch, const Bit32u cutoff); + + // Deactivate the WG engine + void deactivate(); + + // Return active state of the WG engine + bool isActive() const; + + // Return true if the WG engine generates PCM wave samples + bool isPCMWave() const; +}; + +// LA32PartialPair contains a structure of two partials being mixed / ring modulated +class LA32PartialPair { + LA32WaveGenerator master; + LA32WaveGenerator slave; + bool ringModulated; + bool mixed; + float masterOutputSample; + float slaveOutputSample; + +public: + enum PairType { + MASTER, + SLAVE + }; + + // ringModulated should be set to false for the structures with mixing or stereo output + // ringModulated should be set to true for the structures with ring modulation + // mixed is used for the structures with ring modulation and indicates whether the master partial output is mixed to the ring modulator output + void init(const bool ringModulated, const bool mixed); + + // Initialise the WG engine for generation of synth partial samples and set up the invariant parameters + void initSynth(const PairType master, const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance); + + // Initialise the WG engine for generation of PCM partial samples and set up the invariant parameters + void initPCM(const PairType master, const Bit16s * const pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped); + + // Update parameters with respect to TVP, TVA and TVF, and generate next sample + void generateNextSample(const PairType master, const Bit32u amp, const Bit16u pitch, const Bit32u cutoff); + + // Perform mixing / ring modulation and return the result + float nextOutSample(); + + // Deactivate the WG engine + void deactivate(const PairType master); + + // Return active state of the WG engine + bool isActive(const PairType master) const; +}; + +} // namespace MT32Emu + +#endif // #ifndef MT32EMU_LA32_WAVE_GENERATOR_H 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 . */ -#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/LegacyWaveGenerator.cpp b/audio/softsynth/mt32/LegacyWaveGenerator.cpp deleted file mode 100644 index 35ca975018..0000000000 --- a/audio/softsynth/mt32/LegacyWaveGenerator.cpp +++ /dev/null @@ -1,347 +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 . - */ - -//#include -#include "mt32emu.h" -#include "mmath.h" -#include "LegacyWaveGenerator.h" - -#if MT32EMU_ACCURATE_WG == 1 - -namespace MT32Emu { - -static const float MIDDLE_CUTOFF_VALUE = 128.0f; -static const float RESONANCE_DECAY_THRESHOLD_CUTOFF_VALUE = 144.0f; -static const float MAX_CUTOFF_VALUE = 240.0f; - -float LA32WaveGenerator::getPCMSample(unsigned int position) { - if (position >= pcmWaveLength) { - if (!pcmWaveLooped) { - return 0; - } - position = position % pcmWaveLength; - } - Bit16s pcmSample = pcmWaveAddress[position]; - float sampleValue = EXP2F(((pcmSample & 32767) - 32787.0f) / 2048.0f); - return ((pcmSample & 32768) == 0) ? sampleValue : -sampleValue; -} - -void LA32WaveGenerator::initSynth(const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance) { - this->sawtoothWaveform = sawtoothWaveform; - this->pulseWidth = pulseWidth; - this->resonance = resonance; - - wavePos = 0.0f; - lastFreq = 0.0f; - - pcmWaveAddress = NULL; - active = true; -} - -void LA32WaveGenerator::initPCM(const Bit16s * const pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped, const bool pcmWaveInterpolated) { - this->pcmWaveAddress = pcmWaveAddress; - this->pcmWaveLength = pcmWaveLength; - this->pcmWaveLooped = pcmWaveLooped; - this->pcmWaveInterpolated = pcmWaveInterpolated; - - pcmPosition = 0.0f; - active = true; -} - -float LA32WaveGenerator::generateNextSample(const Bit32u ampVal, const Bit16u pitch, const Bit32u cutoffRampVal) { - if (!active) { - return 0.0f; - } - - this->amp = amp; - this->pitch = pitch; - - float sample = 0.0f; - - // SEMI-CONFIRMED: From sample analysis: - // (1) Tested with a single partial playing PCM wave 77 with pitchCoarse 36 and no keyfollow, velocity follow, etc. - // This gives results within +/- 2 at the output (before any DAC bitshifting) - // when sustaining at levels 156 - 255 with no modifiers. - // (2) Tested with a special square wave partial (internal capture ID tva5) at TVA envelope levels 155-255. - // This gives deltas between -1 and 0 compared to the real output. Note that this special partial only produces - // positive amps, so negative still needs to be explored, as well as lower levels. - // - // Also still partially unconfirmed is the behaviour when ramping between levels, as well as the timing. - - float amp = EXP2F(ampVal / -1024.0f / 4096.0f); - float freq = EXP2F(pitch / 4096.0f - 16.0f) * SAMPLE_RATE; - - if (isPCMWave()) { - // Render PCM waveform - int len = pcmWaveLength; - int intPCMPosition = (int)pcmPosition; - if (intPCMPosition >= len && !pcmWaveLooped) { - // We're now past the end of a non-looping PCM waveform so it's time to die. - deactivate(); - return 0.0f; - } - float positionDelta = freq * 2048.0f / SAMPLE_RATE; - - // Linear interpolation - float firstSample = getPCMSample(intPCMPosition); - // We observe that for partial structures with ring modulation the interpolation is not applied to the slave PCM partial. - // It's assumed that the multiplication circuitry intended to perform the interpolation on the slave PCM partial - // is borrowed by the ring modulation circuit (or the LA32 chip has a similar lack of resources assigned to each partial pair). - if (pcmWaveInterpolated) { - sample = firstSample + (getPCMSample(intPCMPosition + 1) - firstSample) * (pcmPosition - intPCMPosition); - } else { - sample = firstSample; - } - - float newPCMPosition = pcmPosition + positionDelta; - if (pcmWaveLooped) { - newPCMPosition = fmod(newPCMPosition, (float)pcmWaveLength); - } - pcmPosition = newPCMPosition; - } else { - // Render synthesised waveform - wavePos *= lastFreq / freq; - lastFreq = freq; - - float resAmp = EXP2F(1.0f - (32 - resonance) / 4.0f); - { - //static const float resAmpFactor = EXP2F(-7); - //resAmp = EXP2I(resonance << 10) * resAmpFactor; - } - - // The cutoffModifier may not be supposed to be directly added to the cutoff - - // it may for example need to be multiplied in some way. - // The 240 cutoffVal limit was determined via sample analysis (internal Munt capture IDs: glop3, glop4). - // More research is needed to be sure that this is correct, however. - float cutoffVal = cutoffRampVal / 262144.0f; - if (cutoffVal > MAX_CUTOFF_VALUE) { - cutoffVal = MAX_CUTOFF_VALUE; - } - - // Wave length in samples - float waveLen = SAMPLE_RATE / freq; - - // Init cosineLen - float cosineLen = 0.5f * waveLen; - if (cutoffVal > MIDDLE_CUTOFF_VALUE) { - cosineLen *= EXP2F((cutoffVal - MIDDLE_CUTOFF_VALUE) / -16.0f); // found from sample analysis - } - - // Start playing in center of first cosine segment - // relWavePos is shifted by a half of cosineLen - float relWavePos = wavePos + 0.5f * cosineLen; - if (relWavePos > waveLen) { - relWavePos -= waveLen; - } - - // Ratio of positive segment to wave length - float pulseLen = 0.5f; - if (pulseWidth > 128) { - pulseLen = EXP2F((64 - pulseWidth) / 64.0f); - //static const float pulseLenFactor = EXP2F(-192 / 64); - //pulseLen = EXP2I((256 - pulseWidthVal) << 6) * pulseLenFactor; - } - pulseLen *= waveLen; - - float hLen = pulseLen - cosineLen; - - // Ignore pulsewidths too high for given freq - if (hLen < 0.0f) { - hLen = 0.0f; - } - - // Ignore pulsewidths too high for given freq and cutoff - float lLen = waveLen - hLen - 2 * cosineLen; - if (lLen < 0.0f) { - lLen = 0.0f; - } - - // Correct resAmp for cutoff in range 50..66 - if ((cutoffVal >= 128.0f) && (cutoffVal < 144.0f)) { - resAmp *= sin(FLOAT_PI * (cutoffVal - 128.0f) / 32.0f); - } - - // Produce filtered square wave with 2 cosine waves on slopes - - // 1st cosine segment - if (relWavePos < cosineLen) { - sample = -cos(FLOAT_PI * relWavePos / cosineLen); - } else - - // high linear segment - if (relWavePos < (cosineLen + hLen)) { - sample = 1.f; - } else - - // 2nd cosine segment - if (relWavePos < (2 * cosineLen + hLen)) { - sample = cos(FLOAT_PI * (relWavePos - (cosineLen + hLen)) / cosineLen); - } else { - - // low linear segment - sample = -1.f; - } - - if (cutoffVal < 128.0f) { - - // Attenuate samples below cutoff 50 - // Found by sample analysis - sample *= EXP2F(-0.125f * (128.0f - cutoffVal)); - } else { - - // Add resonance sine. Effective for cutoff > 50 only - float resSample = 1.0f; - - // Resonance decay speed factor - float resAmpDecayFactor = Tables::getInstance().resAmpDecayFactor[resonance >> 2]; - - // Now relWavePos counts from the middle of first cosine - relWavePos = wavePos; - - // negative segments - if (!(relWavePos < (cosineLen + hLen))) { - resSample = -resSample; - relWavePos -= cosineLen + hLen; - - // From the digital captures, the decaying speed of the resonance sine is found a bit different for the positive and the negative segments - resAmpDecayFactor += 0.25f; - } - - // Resonance sine WG - resSample *= sin(FLOAT_PI * relWavePos / cosineLen); - - // Resonance sine amp - float resAmpFadeLog2 = -0.125f * resAmpDecayFactor * (relWavePos / cosineLen); // seems to be exact - float resAmpFade = EXP2F(resAmpFadeLog2); - - // Now relWavePos set negative to the left from center of any cosine - relWavePos = wavePos; - - // negative segment - if (!(wavePos < (waveLen - 0.5f * cosineLen))) { - relWavePos -= waveLen; - } else - - // positive segment - if (!(wavePos < (hLen + 0.5f * cosineLen))) { - relWavePos -= cosineLen + hLen; - } - - // To ensure the output wave has no breaks, two different windows are appied to the beginning and the ending of the resonance sine segment - if (relWavePos < 0.5f * cosineLen) { - float syncSine = sin(FLOAT_PI * relWavePos / cosineLen); - if (relWavePos < 0.0f) { - // The window is synchronous square sine here - resAmpFade *= syncSine * syncSine; - } else { - // The window is synchronous sine here - resAmpFade *= syncSine; - } - } - - sample += resSample * resAmp * resAmpFade; - } - - // sawtooth waves - if (sawtoothWaveform) { - sample *= cos(FLOAT_2PI * wavePos / waveLen); - } - - wavePos++; - - // wavePos isn't supposed to be > waveLen - if (wavePos > waveLen) { - wavePos -= waveLen; - } - } - - // Multiply sample with current TVA value - sample *= amp; - return sample; -} - -void LA32WaveGenerator::deactivate() { - active = false; -} - -bool LA32WaveGenerator::isActive() const { - return active; -} - -bool LA32WaveGenerator::isPCMWave() const { - return pcmWaveAddress != NULL; -} - -void LA32PartialPair::init(const bool ringModulated, const bool mixed) { - this->ringModulated = ringModulated; - this->mixed = mixed; - masterOutputSample = 0.0f; - slaveOutputSample = 0.0f; -} - -void LA32PartialPair::initSynth(const PairType useMaster, const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance) { - if (useMaster == MASTER) { - master.initSynth(sawtoothWaveform, pulseWidth, resonance); - } else { - slave.initSynth(sawtoothWaveform, pulseWidth, resonance); - } -} - -void LA32PartialPair::initPCM(const PairType useMaster, const Bit16s *pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped) { - if (useMaster == MASTER) { - master.initPCM(pcmWaveAddress, pcmWaveLength, pcmWaveLooped, true); - } else { - slave.initPCM(pcmWaveAddress, pcmWaveLength, pcmWaveLooped, !ringModulated); - } -} - -void LA32PartialPair::generateNextSample(const PairType useMaster, const Bit32u amp, const Bit16u pitch, const Bit32u cutoff) { - if (useMaster == MASTER) { - masterOutputSample = master.generateNextSample(amp, pitch, cutoff); - } else { - slaveOutputSample = slave.generateNextSample(amp, pitch, cutoff); - } -} - -Bit16s LA32PartialPair::nextOutSample() { - float outputSample; - if (ringModulated) { - float ringModulatedSample = masterOutputSample * slaveOutputSample; - outputSample = mixed ? masterOutputSample + ringModulatedSample : ringModulatedSample; - } else { - outputSample = masterOutputSample + slaveOutputSample; - } - return Bit16s(outputSample * 8192.0f); -} - -void LA32PartialPair::deactivate(const PairType useMaster) { - if (useMaster == MASTER) { - master.deactivate(); - masterOutputSample = 0.0f; - } else { - slave.deactivate(); - slaveOutputSample = 0.0f; - } -} - -bool LA32PartialPair::isActive(const PairType useMaster) const { - return useMaster == MASTER ? master.isActive() : slave.isActive(); -} - -} - -#endif // #if MT32EMU_ACCURATE_WG == 1 diff --git a/audio/softsynth/mt32/LegacyWaveGenerator.h b/audio/softsynth/mt32/LegacyWaveGenerator.h deleted file mode 100644 index 81c1b9c713..0000000000 --- a/audio/softsynth/mt32/LegacyWaveGenerator.h +++ /dev/null @@ -1,146 +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 . - */ - -#if MT32EMU_ACCURATE_WG == 1 - -#ifndef MT32EMU_LA32_WAVE_GENERATOR_H -#define MT32EMU_LA32_WAVE_GENERATOR_H - -namespace MT32Emu { - -/** - * LA32WaveGenerator is aimed to represent the exact model of LA32 wave generator. - * The output square wave is created by adding high / low linear segments in-between - * the rising and falling cosine segments. Basically, it’s very similar to the phase distortion synthesis. - * Behaviour of a true resonance filter is emulated by adding decaying sine wave. - * The beginning and the ending of the resonant sine is multiplied by a cosine window. - * To synthesise sawtooth waves, the resulting square wave is multiplied by synchronous cosine wave. - */ -class LA32WaveGenerator { - //*************************************************************************** - // The local copy of partial parameters below - //*************************************************************************** - - bool active; - - // True means the resulting square wave is to be multiplied by the synchronous cosine - bool sawtoothWaveform; - - // Logarithmic amp of the wave generator - Bit32u amp; - - // Logarithmic frequency of the resulting wave - Bit16u pitch; - - // Values in range [1..31] - // Value 1 correspong to the minimum resonance - Bit8u resonance; - - // Processed value in range [0..255] - // Values in range [0..128] have no effect and the resulting wave remains symmetrical - // Value 255 corresponds to the maximum possible asymmetric of the resulting wave - Bit8u pulseWidth; - - // Composed of the base cutoff in range [78..178] left-shifted by 18 bits and the TVF modifier - Bit32u cutoffVal; - - // Logarithmic PCM sample start address - const Bit16s *pcmWaveAddress; - - // Logarithmic PCM sample length - Bit32u pcmWaveLength; - - // true for looped logarithmic PCM samples - bool pcmWaveLooped; - - // false for slave PCM partials in the structures with the ring modulation - bool pcmWaveInterpolated; - - //*************************************************************************** - // Internal variables below - //*************************************************************************** - - float wavePos; - float lastFreq; - float pcmPosition; - - float getPCMSample(unsigned int position); - -public: - // Initialise the WG engine for generation of synth partial samples and set up the invariant parameters - void initSynth(const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance); - - // Initialise the WG engine for generation of PCM partial samples and set up the invariant parameters - void initPCM(const Bit16s * const pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped, const bool pcmWaveInterpolated); - - // Update parameters with respect to TVP, TVA and TVF, and generate next sample - float generateNextSample(const Bit32u amp, const Bit16u pitch, const Bit32u cutoff); - - // Deactivate the WG engine - void deactivate(); - - // Return active state of the WG engine - bool isActive() const; - - // Return true if the WG engine generates PCM wave samples - bool isPCMWave() const; -}; - -// LA32PartialPair contains a structure of two partials being mixed / ring modulated -class LA32PartialPair { - LA32WaveGenerator master; - LA32WaveGenerator slave; - bool ringModulated; - bool mixed; - float masterOutputSample; - float slaveOutputSample; - -public: - enum PairType { - MASTER, - SLAVE - }; - - // ringModulated should be set to false for the structures with mixing or stereo output - // ringModulated should be set to true for the structures with ring modulation - // mixed is used for the structures with ring modulation and indicates whether the master partial output is mixed to the ring modulator output - void init(const bool ringModulated, const bool mixed); - - // Initialise the WG engine for generation of synth partial samples and set up the invariant parameters - void initSynth(const PairType master, const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance); - - // Initialise the WG engine for generation of PCM partial samples and set up the invariant parameters - void initPCM(const PairType master, const Bit16s * const pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped); - - // Update parameters with respect to TVP, TVA and TVF, and generate next sample - void generateNextSample(const PairType master, const Bit32u amp, const Bit16u pitch, const Bit32u cutoff); - - // Perform mixing / ring modulation and return the result - Bit16s nextOutSample(); - - // Deactivate the WG engine - void deactivate(const PairType master); - - // Return active state of the WG engine - bool isActive(const PairType master) const; -}; - -} // namespace MT32Emu - -#endif // #ifndef MT32EMU_LA32_WAVE_GENERATOR_H - -#endif // #if MT32EMU_ACCURATE_WG == 1 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= freezemode) - return; - - for (i=0;i 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= freezemode) - { - roomsize1 = 1; - damp1 = 0; - gain = muted; - } - else - { - roomsize1 = roomsize; - damp1 = damp; - gain = fixedgain; - } - - for (i=0; i= 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" -- cgit v1.2.3