diff options
71 files changed, 2180 insertions, 1260 deletions
diff --git a/audio/softsynth/mt32/Analog.cpp b/audio/softsynth/mt32/Analog.cpp index 31e88561c4..2901198f2c 100644 --- a/audio/softsynth/mt32/Analog.cpp +++ b/audio/softsynth/mt32/Analog.cpp @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 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 @@ -24,8 +24,6 @@ namespace MT32Emu { -#if MT32EMU_USE_FLOAT_SAMPLES - /* FIR approximation of the overall impulse response of the cascade composed of the sample & hold circuit and the low pass filter * of the MT-32 first generation. * The coefficients below are found by windowing the inverse DFT of the 1024 pin frequency response converted to the minimum phase. @@ -34,31 +32,27 @@ namespace MT32Emu { * The peak gain of the normalised cascade appears about 1.7 near 11.8 kHz. Relative error doesn't exceed 1% for the frequencies * below 12.5 kHz. In the higher frequency range, the relative error is below 8%. Peak error value is at 16 kHz. */ -static const float COARSE_LPF_TAPS_MT32[] = { +static const FloatSample COARSE_LPF_FLOAT_TAPS_MT32[] = { 1.272473681f, -0.220267785f, -0.158039905f, 0.179603785f, -0.111484097f, 0.054137498f, -0.023518029f, 0.010997169f, -0.006935698f }; // Similar approximation for new MT-32 and CM-32L/LAPC-I LPF. As the voltage controlled amplifier was introduced, LPF has unity DC gain. // The peak gain value shifted towards higher frequencies and a bit higher about 1.83 near 13 kHz. -static const float COARSE_LPF_TAPS_CM32L[] = { +static const FloatSample COARSE_LPF_FLOAT_TAPS_CM32L[] = { 1.340615635f, -0.403331694f, 0.036005517f, 0.066156844f, -0.069672532f, 0.049563806f, -0.031113416f, 0.019169774f, -0.012421368f }; -#else - -static const unsigned int COARSE_LPF_FRACTION_BITS = 14; +static const unsigned int COARSE_LPF_INT_FRACTION_BITS = 14; // Integer versions of the FIRs above multiplied by (1 << 14) and rounded. -static const SampleEx COARSE_LPF_TAPS_MT32[] = { +static const IntSampleEx COARSE_LPF_INT_TAPS_MT32[] = { 20848, -3609, -2589, 2943, -1827, 887, -385, 180, -114 }; -static const SampleEx COARSE_LPF_TAPS_CM32L[] = { +static const IntSampleEx COARSE_LPF_INT_TAPS_CM32L[] = { 21965, -6608, 590, 1084, -1142, 812, -510, 314, -204 }; -#endif - /* Combined FIR that both approximates the impulse response of the analogue circuits of sample & hold and the low pass filter * in the audible frequency range (below 20 kHz) and attenuates unwanted mirror spectra above 28 kHz as well. It is a polyphase * filter intended for resampling the signal to 48 kHz yet for applying high frequency boost. @@ -70,7 +64,7 @@ static const SampleEx COARSE_LPF_TAPS_CM32L[] = { */ // FIR version for MT-32 first generation. -static const float ACCURATE_LPF_TAPS_MT32[] = { +static const FloatSample ACCURATE_LPF_TAPS_MT32[] = { 0.003429281f, 0.025929869f, 0.096587777f, 0.228884848f, 0.372413431f, 0.412386503f, 0.263980018f, -0.014504962f, -0.237394528f, -0.257043496f, -0.103436603f, 0.063996095f, 0.124562333f, 0.083703206f, 0.013921662f, -0.033475018f, -0.046239712f, -0.029310921f, 0.00126585f, 0.021060961f, 0.017925605f, @@ -81,7 +75,7 @@ static const float ACCURATE_LPF_TAPS_MT32[] = { }; // FIR version for new MT-32 and CM-32L/LAPC-I. -static const float ACCURATE_LPF_TAPS_CM32L[] = { +static const FloatSample ACCURATE_LPF_TAPS_CM32L[] = { 0.003917452f, 0.030693861f, 0.116424199f, 0.275101674f, 0.43217361f, 0.431247894f, 0.183255659f, -0.174955671f, -0.354240244f, -0.212401714f, 0.072259178f, 0.204655344f, 0.108336211f, -0.039099027f, -0.075138174f, -0.026261906f, 0.00582663f, 0.003052193f, 0.00613657f, 0.017017951f, 0.008732535f, @@ -107,177 +101,269 @@ static const unsigned int ACCURATE_LPF_PHASE_INCREMENT_OVERSAMPLED = 1; // No do static const Bit32u ACCURATE_LPF_DELTAS_REGULAR[][ACCURATE_LPF_NUMBER_OF_PHASES] = { { 0, 0, 0 }, { 1, 1, 0 }, { 1, 2, 1 } }; static const Bit32u ACCURATE_LPF_DELTAS_OVERSAMPLED[][ACCURATE_LPF_NUMBER_OF_PHASES] = { { 0, 0, 0 }, { 1, 0, 0 }, { 1, 0, 1 } }; +template <class SampleEx> class AbstractLowPassFilter { public: - static AbstractLowPassFilter &createLowPassFilter(AnalogOutputMode mode, bool oldMT32AnalogLPF); + static AbstractLowPassFilter<SampleEx> &createLowPassFilter(const AnalogOutputMode mode, const bool oldMT32AnalogLPF); virtual ~AbstractLowPassFilter() {} - virtual SampleEx process(SampleEx sample) = 0; - virtual bool hasNextSample() const; - virtual unsigned int getOutputSampleRate() const; - virtual unsigned int estimateInSampleCount(unsigned int outSamples) const; - virtual void addPositionIncrement(unsigned int) {} + virtual SampleEx process(const SampleEx sample) = 0; + + virtual bool hasNextSample() const { + return false; + } + + virtual unsigned int getOutputSampleRate() const { + return SAMPLE_RATE; + } + + virtual unsigned int estimateInSampleCount(const unsigned int outSamples) const { + return outSamples; + } + + virtual void addPositionIncrement(const unsigned int) {} }; -class NullLowPassFilter : public AbstractLowPassFilter { +template <class SampleEx> +class NullLowPassFilter : public AbstractLowPassFilter<SampleEx> { public: - SampleEx process(SampleEx sample); + SampleEx process(const SampleEx sample) { + return sample; + } }; -class CoarseLowPassFilter : public AbstractLowPassFilter { +template <class SampleEx> +class CoarseLowPassFilter : public AbstractLowPassFilter<SampleEx> { private: - const SampleEx * const LPF_TAPS; + const SampleEx * const lpfTaps; SampleEx ringBuffer[COARSE_LPF_DELAY_LINE_LENGTH]; unsigned int ringBufferPosition; public: - CoarseLowPassFilter(bool oldMT32AnalogLPF); - SampleEx process(SampleEx sample); + static inline const SampleEx *getLPFTaps(const bool oldMT32AnalogLPF); + static inline SampleEx normaliseSample(const SampleEx sample); + + explicit CoarseLowPassFilter(const bool oldMT32AnalogLPF) : + lpfTaps(getLPFTaps(oldMT32AnalogLPF)), + ringBufferPosition(0) + { + Synth::muteSampleBuffer(ringBuffer, COARSE_LPF_DELAY_LINE_LENGTH); + } + + SampleEx process(const SampleEx inSample) { + static const unsigned int DELAY_LINE_MASK = COARSE_LPF_DELAY_LINE_LENGTH - 1; + + SampleEx sample = lpfTaps[COARSE_LPF_DELAY_LINE_LENGTH] * ringBuffer[ringBufferPosition]; + ringBuffer[ringBufferPosition] = Synth::clipSampleEx(inSample); + + for (unsigned int i = 0; i < COARSE_LPF_DELAY_LINE_LENGTH; i++) { + sample += lpfTaps[i] * ringBuffer[(i + ringBufferPosition) & DELAY_LINE_MASK]; + } + + ringBufferPosition = (ringBufferPosition - 1) & DELAY_LINE_MASK; + + return normaliseSample(sample); + } }; -class AccurateLowPassFilter : public AbstractLowPassFilter { +class AccurateLowPassFilter : public AbstractLowPassFilter<IntSampleEx>, public AbstractLowPassFilter<FloatSample> { private: - const float * const LPF_TAPS; + const FloatSample * const LPF_TAPS; const Bit32u (* const deltas)[ACCURATE_LPF_NUMBER_OF_PHASES]; const unsigned int phaseIncrement; const unsigned int outputSampleRate; - SampleEx ringBuffer[ACCURATE_LPF_DELAY_LINE_LENGTH]; + FloatSample ringBuffer[ACCURATE_LPF_DELAY_LINE_LENGTH]; unsigned int ringBufferPosition; unsigned int phase; public: - AccurateLowPassFilter(bool oldMT32AnalogLPF, bool oversample); - SampleEx process(SampleEx sample); + AccurateLowPassFilter(const bool oldMT32AnalogLPF, const bool oversample); + FloatSample process(const FloatSample sample); + IntSampleEx process(const IntSampleEx sample); bool hasNextSample() const; unsigned int getOutputSampleRate() const; - unsigned int estimateInSampleCount(unsigned int outSamples) const; - void addPositionIncrement(unsigned int positionIncrement); + unsigned int estimateInSampleCount(const unsigned int outSamples) const; + void addPositionIncrement(const unsigned int positionIncrement); }; -Analog::Analog(const AnalogOutputMode mode, const bool oldMT32AnalogLPF) : - leftChannelLPF(AbstractLowPassFilter::createLowPassFilter(mode, oldMT32AnalogLPF)), - rightChannelLPF(AbstractLowPassFilter::createLowPassFilter(mode, oldMT32AnalogLPF)), - synthGain(0), - reverbGain(0) -{} +static inline IntSampleEx normaliseSample(const IntSampleEx sample) { + return sample >> OUTPUT_GAIN_FRACTION_BITS; +} + +static inline FloatSample normaliseSample(const FloatSample sample) { + return sample; +} + +static inline float getActualReverbOutputGain(const float reverbGain, const bool mt32ReverbCompatibilityMode) { + return mt32ReverbCompatibilityMode ? reverbGain : reverbGain * CM32L_REVERB_TO_LA32_ANALOG_OUTPUT_GAIN_FACTOR; +} -Analog::~Analog() { - delete &leftChannelLPF; - delete &rightChannelLPF; +static inline IntSampleEx getIntOutputGain(const float outputGain) { + return IntSampleEx(((OUTPUT_GAIN_MULTIPLIER < outputGain) ? OUTPUT_GAIN_MULTIPLIER : outputGain) * OUTPUT_GAIN_MULTIPLIER); } -void Analog::process(Sample *outStream, const Sample *nonReverbLeft, const Sample *nonReverbRight, const Sample *reverbDryLeft, const Sample *reverbDryRight, const Sample *reverbWetLeft, const Sample *reverbWetRight, Bit32u outLength) { - if (outStream == NULL) { - leftChannelLPF.addPositionIncrement(outLength); - rightChannelLPF.addPositionIncrement(outLength); - return; +template <class SampleEx> +class AnalogImpl : public Analog { +public: + AbstractLowPassFilter<SampleEx> &leftChannelLPF; + AbstractLowPassFilter<SampleEx> &rightChannelLPF; + SampleEx synthGain; + SampleEx reverbGain; + + AnalogImpl(const AnalogOutputMode mode, const bool oldMT32AnalogLPF) : + leftChannelLPF(AbstractLowPassFilter<SampleEx>::createLowPassFilter(mode, oldMT32AnalogLPF)), + rightChannelLPF(AbstractLowPassFilter<SampleEx>::createLowPassFilter(mode, oldMT32AnalogLPF)), + synthGain(0), + reverbGain(0) + {} + + ~AnalogImpl() { + delete &leftChannelLPF; + delete &rightChannelLPF; + } + + unsigned int getOutputSampleRate() const { + return leftChannelLPF.getOutputSampleRate(); } - while (0 < (outLength--)) { - SampleEx outSampleL; - SampleEx outSampleR; + Bit32u getDACStreamsLength(const Bit32u outputLength) const { + return leftChannelLPF.estimateInSampleCount(outputLength); + } - if (leftChannelLPF.hasNextSample()) { - outSampleL = leftChannelLPF.process(0); - outSampleR = rightChannelLPF.process(0); - } else { - SampleEx inSampleL = (SampleEx(*(nonReverbLeft++)) + SampleEx(*(reverbDryLeft++))) * synthGain + SampleEx(*(reverbWetLeft++)) * reverbGain; - SampleEx inSampleR = (SampleEx(*(nonReverbRight++)) + SampleEx(*(reverbDryRight++))) * synthGain + SampleEx(*(reverbWetRight++)) * reverbGain; + void setSynthOutputGain(const float synthGain); + void setReverbOutputGain(const float reverbGain, const bool mt32ReverbCompatibilityMode); -#if !MT32EMU_USE_FLOAT_SAMPLES - inSampleL >>= OUTPUT_GAIN_FRACTION_BITS; - inSampleR >>= OUTPUT_GAIN_FRACTION_BITS; -#endif + bool process(IntSample *outStream, const IntSample *nonReverbLeft, const IntSample *nonReverbRight, const IntSample *reverbDryLeft, const IntSample *reverbDryRight, const IntSample *reverbWetLeft, const IntSample *reverbWetRight, Bit32u outLength); + bool process(FloatSample *outStream, const FloatSample *nonReverbLeft, const FloatSample *nonReverbRight, const FloatSample *reverbDryLeft, const FloatSample *reverbDryRight, const FloatSample *reverbWetLeft, const FloatSample *reverbWetRight, Bit32u outLength); - outSampleL = leftChannelLPF.process(inSampleL); - outSampleR = rightChannelLPF.process(inSampleR); + template <class Sample> + void produceOutput(Sample *outStream, const Sample *nonReverbLeft, const Sample *nonReverbRight, const Sample *reverbDryLeft, const Sample *reverbDryRight, const Sample *reverbWetLeft, const Sample *reverbWetRight, Bit32u outLength) { + if (outStream == NULL) { + leftChannelLPF.addPositionIncrement(outLength); + rightChannelLPF.addPositionIncrement(outLength); + return; } - *(outStream++) = Synth::clipSampleEx(outSampleL); - *(outStream++) = Synth::clipSampleEx(outSampleR); + while (0 < (outLength--)) { + SampleEx outSampleL; + SampleEx outSampleR; + + if (leftChannelLPF.hasNextSample()) { + outSampleL = leftChannelLPF.process(0); + outSampleR = rightChannelLPF.process(0); + } else { + SampleEx inSampleL = (SampleEx(*(nonReverbLeft++)) + SampleEx(*(reverbDryLeft++))) * synthGain + SampleEx(*(reverbWetLeft++)) * reverbGain; + SampleEx inSampleR = (SampleEx(*(nonReverbRight++)) + SampleEx(*(reverbDryRight++))) * synthGain + SampleEx(*(reverbWetRight++)) * reverbGain; + + outSampleL = leftChannelLPF.process(normaliseSample(inSampleL)); + outSampleR = rightChannelLPF.process(normaliseSample(inSampleR)); + } + + *(outStream++) = Synth::clipSampleEx(outSampleL); + *(outStream++) = Synth::clipSampleEx(outSampleR); + } + } +}; + +Analog *Analog::createAnalog(const AnalogOutputMode mode, const bool oldMT32AnalogLPF, const RendererType rendererType) { + switch (rendererType) + { + case RendererType_BIT16S: + return new AnalogImpl<IntSampleEx>(mode, oldMT32AnalogLPF); + case RendererType_FLOAT: + return new AnalogImpl<FloatSample>(mode, oldMT32AnalogLPF); } + return NULL; +} + +template<> +bool AnalogImpl<IntSampleEx>::process(IntSample *outStream, const IntSample *nonReverbLeft, const IntSample *nonReverbRight, const IntSample *reverbDryLeft, const IntSample *reverbDryRight, const IntSample *reverbWetLeft, const IntSample *reverbWetRight, Bit32u outLength) { + produceOutput(outStream, nonReverbLeft, nonReverbRight, reverbDryLeft, reverbDryRight, reverbWetLeft, reverbWetRight, outLength); + return true; +} + +template<> +bool AnalogImpl<FloatSample>::process(IntSample *, const IntSample *, const IntSample *, const IntSample *, const IntSample *, const IntSample *, const IntSample *, Bit32u) { + return false; +} + +template<> +bool AnalogImpl<IntSampleEx>::process(FloatSample *, const FloatSample *, const FloatSample *, const FloatSample *, const FloatSample *, const FloatSample *, const FloatSample *, Bit32u) { + return false; } -unsigned int Analog::getOutputSampleRate() const { - return leftChannelLPF.getOutputSampleRate(); +template<> +bool AnalogImpl<FloatSample>::process(FloatSample *outStream, const FloatSample *nonReverbLeft, const FloatSample *nonReverbRight, const FloatSample *reverbDryLeft, const FloatSample *reverbDryRight, const FloatSample *reverbWetLeft, const FloatSample *reverbWetRight, Bit32u outLength) { + produceOutput(outStream, nonReverbLeft, nonReverbRight, reverbDryLeft, reverbDryRight, reverbWetLeft, reverbWetRight, outLength); + return true; } -Bit32u Analog::getDACStreamsLength(Bit32u outputLength) const { - return leftChannelLPF.estimateInSampleCount(outputLength); +template<> +void AnalogImpl<IntSampleEx>::setSynthOutputGain(const float useSynthGain) { + synthGain = getIntOutputGain(useSynthGain); } -void Analog::setSynthOutputGain(float useSynthGain) { -#if MT32EMU_USE_FLOAT_SAMPLES +template<> +void AnalogImpl<IntSampleEx>::setReverbOutputGain(const float useReverbGain, const bool mt32ReverbCompatibilityMode) { + reverbGain = getIntOutputGain(getActualReverbOutputGain(useReverbGain, mt32ReverbCompatibilityMode)); +} + +template<> +void AnalogImpl<FloatSample>::setSynthOutputGain(const float useSynthGain) { synthGain = useSynthGain; -#else - if (OUTPUT_GAIN_MULTIPLIER < useSynthGain) useSynthGain = OUTPUT_GAIN_MULTIPLIER; - synthGain = SampleEx(useSynthGain * OUTPUT_GAIN_MULTIPLIER); -#endif } -void Analog::setReverbOutputGain(float useReverbGain, bool mt32ReverbCompatibilityMode) { - if (!mt32ReverbCompatibilityMode) useReverbGain *= CM32L_REVERB_TO_LA32_ANALOG_OUTPUT_GAIN_FACTOR; -#if MT32EMU_USE_FLOAT_SAMPLES - reverbGain = useReverbGain; -#else - if (OUTPUT_GAIN_MULTIPLIER < useReverbGain) useReverbGain = OUTPUT_GAIN_MULTIPLIER; - reverbGain = SampleEx(useReverbGain * OUTPUT_GAIN_MULTIPLIER); -#endif +template<> +void AnalogImpl<FloatSample>::setReverbOutputGain(const float useReverbGain, const bool mt32ReverbCompatibilityMode) { + reverbGain = getActualReverbOutputGain(useReverbGain, mt32ReverbCompatibilityMode); +} + +template<> +AbstractLowPassFilter<IntSampleEx> &AbstractLowPassFilter<IntSampleEx>::createLowPassFilter(AnalogOutputMode mode, bool oldMT32AnalogLPF) { + switch (mode) { + case AnalogOutputMode_COARSE: + return *new CoarseLowPassFilter<IntSampleEx>(oldMT32AnalogLPF); + case AnalogOutputMode_ACCURATE: + return *new AccurateLowPassFilter(oldMT32AnalogLPF, false); + case AnalogOutputMode_OVERSAMPLED: + return *new AccurateLowPassFilter(oldMT32AnalogLPF, true); + default: + return *new NullLowPassFilter<IntSampleEx>; + } } -AbstractLowPassFilter &AbstractLowPassFilter::createLowPassFilter(AnalogOutputMode mode, bool oldMT32AnalogLPF) { +template<> +AbstractLowPassFilter<FloatSample> &AbstractLowPassFilter<FloatSample>::createLowPassFilter(AnalogOutputMode mode, bool oldMT32AnalogLPF) { switch (mode) { case AnalogOutputMode_COARSE: - return *new CoarseLowPassFilter(oldMT32AnalogLPF); + return *new CoarseLowPassFilter<FloatSample>(oldMT32AnalogLPF); case AnalogOutputMode_ACCURATE: return *new AccurateLowPassFilter(oldMT32AnalogLPF, false); case AnalogOutputMode_OVERSAMPLED: return *new AccurateLowPassFilter(oldMT32AnalogLPF, true); default: - return *new NullLowPassFilter; + return *new NullLowPassFilter<FloatSample>; } } -bool AbstractLowPassFilter::hasNextSample() const { - return false; -} - -unsigned int AbstractLowPassFilter::getOutputSampleRate() const { - return SAMPLE_RATE; -} - -unsigned int AbstractLowPassFilter::estimateInSampleCount(unsigned int outSamples) const { - return outSamples; +template<> +const IntSampleEx *CoarseLowPassFilter<IntSampleEx>::getLPFTaps(const bool oldMT32AnalogLPF) { + return oldMT32AnalogLPF ? COARSE_LPF_INT_TAPS_MT32 : COARSE_LPF_INT_TAPS_CM32L; } -SampleEx NullLowPassFilter::process(const SampleEx inSample) { - return inSample; +template<> +const FloatSample *CoarseLowPassFilter<FloatSample>::getLPFTaps(const bool oldMT32AnalogLPF) { + return oldMT32AnalogLPF ? COARSE_LPF_FLOAT_TAPS_MT32 : COARSE_LPF_FLOAT_TAPS_CM32L; } -CoarseLowPassFilter::CoarseLowPassFilter(bool oldMT32AnalogLPF) : - LPF_TAPS(oldMT32AnalogLPF ? COARSE_LPF_TAPS_MT32 : COARSE_LPF_TAPS_CM32L), - ringBufferPosition(0) -{ - Synth::muteSampleBuffer(ringBuffer, COARSE_LPF_DELAY_LINE_LENGTH); +template<> +IntSampleEx CoarseLowPassFilter<IntSampleEx>::normaliseSample(const IntSampleEx sample) { + return sample >> COARSE_LPF_INT_FRACTION_BITS; } -SampleEx CoarseLowPassFilter::process(const SampleEx inSample) { - static const unsigned int DELAY_LINE_MASK = COARSE_LPF_DELAY_LINE_LENGTH - 1; - - SampleEx sample = LPF_TAPS[COARSE_LPF_DELAY_LINE_LENGTH] * ringBuffer[ringBufferPosition]; - ringBuffer[ringBufferPosition] = Synth::clipSampleEx(inSample); - - for (unsigned int i = 0; i < COARSE_LPF_DELAY_LINE_LENGTH; i++) { - sample += LPF_TAPS[i] * ringBuffer[(i + ringBufferPosition) & DELAY_LINE_MASK]; - } - - ringBufferPosition = (ringBufferPosition - 1) & DELAY_LINE_MASK; - -#if !MT32EMU_USE_FLOAT_SAMPLES - sample >>= COARSE_LPF_FRACTION_BITS; -#endif - +template<> +FloatSample CoarseLowPassFilter<FloatSample>::normaliseSample(const FloatSample sample) { return sample; } @@ -292,10 +378,10 @@ AccurateLowPassFilter::AccurateLowPassFilter(const bool oldMT32AnalogLPF, const Synth::muteSampleBuffer(ringBuffer, ACCURATE_LPF_DELAY_LINE_LENGTH); } -SampleEx AccurateLowPassFilter::process(const SampleEx inSample) { +FloatSample AccurateLowPassFilter::process(const FloatSample inSample) { static const unsigned int DELAY_LINE_MASK = ACCURATE_LPF_DELAY_LINE_LENGTH - 1; - float sample = (phase == 0) ? LPF_TAPS[ACCURATE_LPF_DELAY_LINE_LENGTH * ACCURATE_LPF_NUMBER_OF_PHASES] * ringBuffer[ringBufferPosition] : 0.0f; + FloatSample sample = (phase == 0) ? LPF_TAPS[ACCURATE_LPF_DELAY_LINE_LENGTH * ACCURATE_LPF_NUMBER_OF_PHASES] * ringBuffer[ringBufferPosition] : 0.0f; if (!hasNextSample()) { ringBuffer[ringBufferPosition] = inSample; } @@ -310,7 +396,11 @@ SampleEx AccurateLowPassFilter::process(const SampleEx inSample) { ringBufferPosition = (ringBufferPosition - 1) & DELAY_LINE_MASK; } - return SampleEx(ACCURATE_LPF_NUMBER_OF_PHASES * sample); + return ACCURATE_LPF_NUMBER_OF_PHASES * sample; +} + +IntSampleEx AccurateLowPassFilter::process(const IntSampleEx sample) { + return IntSampleEx(process(FloatSample(sample))); } bool AccurateLowPassFilter::hasNextSample() const { @@ -321,7 +411,7 @@ unsigned int AccurateLowPassFilter::getOutputSampleRate() const { return outputSampleRate; } -unsigned int AccurateLowPassFilter::estimateInSampleCount(unsigned int outSamples) const { +unsigned int AccurateLowPassFilter::estimateInSampleCount(const unsigned int outSamples) const { Bit32u cycleCount = outSamples / ACCURATE_LPF_NUMBER_OF_PHASES; Bit32u remainder = outSamples - cycleCount * ACCURATE_LPF_NUMBER_OF_PHASES; return cycleCount * phaseIncrement + deltas[remainder][phase]; diff --git a/audio/softsynth/mt32/Analog.h b/audio/softsynth/mt32/Analog.h index ee642f280d..3b6dcabfa3 100644 --- a/audio/softsynth/mt32/Analog.h +++ b/audio/softsynth/mt32/Analog.h @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 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 @@ -20,13 +20,11 @@ #include "globals.h" #include "internals.h" -#include "Types.h" #include "Enumerations.h" +#include "Types.h" namespace MT32Emu { -class AbstractLowPassFilter; - /* Analog class is dedicated to perform fair emulation of analogue circuitry of hardware units that is responsible * for processing output signal after the DAC. It appears that the analogue circuit labeled "LPF" on the schematic * also applies audible changes to the signal spectra. There is a significant boost of higher frequencies observed @@ -38,21 +36,16 @@ class AbstractLowPassFilter; */ class Analog { public: - Analog(const AnalogOutputMode mode, const bool oldMT32AnalogLPF); - ~Analog(); - void process(Sample *outStream, const Sample *nonReverbLeft, const Sample *nonReverbRight, const Sample *reverbDryLeft, const Sample *reverbDryRight, const Sample *reverbWetLeft, const Sample *reverbWetRight, Bit32u outLength); - unsigned int getOutputSampleRate() const; - Bit32u getDACStreamsLength(Bit32u outputLength) const; - void setSynthOutputGain(float synthGain); - void setReverbOutputGain(float reverbGain, bool mt32ReverbCompatibilityMode); - -private: - AbstractLowPassFilter &leftChannelLPF; - AbstractLowPassFilter &rightChannelLPF; - SampleEx synthGain; - SampleEx reverbGain; - - Analog(Analog &); + static Analog *createAnalog(const AnalogOutputMode mode, const bool oldMT32AnalogLPF, const RendererType rendererType); + + virtual ~Analog() {} + virtual unsigned int getOutputSampleRate() const = 0; + virtual Bit32u getDACStreamsLength(const Bit32u outputLength) const = 0; + virtual void setSynthOutputGain(const float synthGain) = 0; + virtual void setReverbOutputGain(const float reverbGain, const bool mt32ReverbCompatibilityMode) = 0; + + virtual bool process(IntSample *outStream, const IntSample *nonReverbLeft, const IntSample *nonReverbRight, const IntSample *reverbDryLeft, const IntSample *reverbDryRight, const IntSample *reverbWetLeft, const IntSample *reverbWetRight, Bit32u outLength) = 0; + virtual bool process(FloatSample *outStream, const FloatSample *nonReverbLeft, const FloatSample *nonReverbRight, const FloatSample *reverbDryLeft, const FloatSample *reverbDryRight, const FloatSample *reverbWetLeft, const FloatSample *reverbWetRight, Bit32u outLength) = 0; }; } // namespace MT32Emu diff --git a/audio/softsynth/mt32/BReverbModel.cpp b/audio/softsynth/mt32/BReverbModel.cpp index 2be1b418a2..af559a92a1 100644 --- a/audio/softsynth/mt32/BReverbModel.cpp +++ b/audio/softsynth/mt32/BReverbModel.cpp @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 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 @@ -37,9 +37,26 @@ static const Bit32u PROCESS_DELAY = 1; static const Bit32u MODE_3_ADDITIONAL_DELAY = 1; static const Bit32u MODE_3_FEEDBACK_DELAY = 1; +// Avoid denormals degrading performance, using biased input +static const FloatSample BIAS = 1e-20f; + +struct BReverbSettings { + const Bit32u numberOfAllpasses; + const Bit32u * const allpassSizes; + const Bit32u numberOfCombs; + const Bit32u * const combSizes; + const Bit32u * const outLPositions; + const Bit32u * const outRPositions; + const Bit8u * const filterFactors; + const Bit8u * const feedbackFactors; + const Bit8u * const dryAmps; + const Bit8u * const wetLevels; + const Bit8u lpfAmp; +}; + // Default reverb settings for "new" reverb model implemented in CM-32L / LAPC-I. // Found by tracing reverb RAM data lines (thanks go to Lord_Nightmare & balrog). -const BReverbSettings &BReverbModel::getCM32L_LAPCSettings(const ReverbMode mode) { +static const BReverbSettings &getCM32L_LAPCSettings(const ReverbMode mode) { static const Bit32u MODE_0_NUMBER_OF_ALLPASSES = 3; static const Bit32u MODE_0_ALLPASSES[] = {994, 729, 78}; static const Bit32u MODE_0_NUMBER_OF_COMBS = 4; // Well, actually there are 3 comb filters, but the entrance LPF + delay can be processed via a hacked comb. @@ -108,7 +125,7 @@ const BReverbSettings &BReverbModel::getCM32L_LAPCSettings(const ReverbMode mode // Default reverb settings for "old" reverb model implemented in MT-32. // Found by tracing reverb RAM data lines (thanks go to Lord_Nightmare & balrog). -const BReverbSettings &BReverbModel::getMT32Settings(const ReverbMode mode) { +static const BReverbSettings &getMT32Settings(const ReverbMode mode) { static const Bit32u MODE_0_NUMBER_OF_ALLPASSES = 3; static const Bit32u MODE_0_ALLPASSES[] = {994, 729, 78}; static const Bit32u MODE_0_NUMBER_OF_COMBS = 4; // Same as above in the new model implementation @@ -175,357 +192,471 @@ const BReverbSettings &BReverbModel::getMT32Settings(const ReverbMode mode) { return *REVERB_SETTINGS[mode]; } -// 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 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 +static inline IntSample weirdMul(IntSample sample, Bit8u addMask, Bit8u carryMask) { +#if MT32EMU_BOSS_REVERB_PRECISE_MODE + // This algorithm tries to emulate exactly Boss multiplication operation (at least this is what we see on reverb RAM data lines). Bit8u mask = 0x80; - Bit32s res = 0; + IntSampleEx res = 0; for (int i = 0; i < 8; i++) { - Bit32s carry = (a < 0) && (mask & carryMask) > 0 ? a & 1 : 0; - a >>= 1; - res += (mask & addMask) > 0 ? a + carry : 0; + IntSampleEx carry = (sample < 0) && (mask & carryMask) > 0 ? sample & 1 : 0; + sample >>= 1; + res += (mask & addMask) > 0 ? sample + carry : 0; mask >>= 1; } - return res; + return IntSample(res); #else - return Sample((Bit32s(a) * addMask) >> 8); + (void)carryMask; + return IntSample((IntSampleEx(sample) * addMask) >> 8); #endif } -RingBuffer::RingBuffer(Bit32u newsize) : size(newsize), index(0) { - buffer = new Sample[size]; +static inline FloatSample weirdMul(FloatSample sample, Bit8u addMask, Bit8u carryMask) { + (void)carryMask; + return sample * addMask / 256.0f; } -RingBuffer::~RingBuffer() { - delete[] buffer; - buffer = NULL; +static inline IntSample halveSample(IntSample sample) { + return sample >> 1; } -Sample RingBuffer::next() { - if (++index >= size) { - index = 0; - } - return buffer[index]; +static inline FloatSample halveSample(FloatSample sample) { + return 0.5f * sample; } -bool RingBuffer::isEmpty() const { - if (buffer == NULL) return true; - -#if MT32EMU_USE_FLOAT_SAMPLES - Sample max = 0.001f; +static inline IntSample quarterSample(IntSample sample) { +#if MT32EMU_BOSS_REVERB_PRECISE_MODE + return (sample >> 1) / 2; #else - Sample max = 8; + return sample >> 2; #endif - Sample *buf = buffer; - for (Bit32u i = 0; i < size; i++) { - if (*buf < -max || *buf > max) return false; - buf++; - } - return true; } -void RingBuffer::mute() { - Synth::muteSampleBuffer(buffer, size); +static inline FloatSample quarterSample(FloatSample sample) { + return 0.25f * sample; } -AllpassFilter::AllpassFilter(const Bit32u useSize) : RingBuffer(useSize) {} +static inline IntSample addDCBias(IntSample sample) { + return sample; +} -Sample AllpassFilter::process(const Sample in) { - // This model corresponds to the allpass filter implementation of the real CM-32L device - // found from sample analysis +static inline FloatSample addDCBias(FloatSample sample) { + return sample + BIAS; +} - const Sample bufferOut = next(); +static inline IntSample addAllpassNoise(IntSample sample) { +#if MT32EMU_BOSS_REVERB_PRECISE_MODE + // This introduces reverb noise which actually makes output from the real Boss chip nondeterministic + return sample - 1; +#else + return sample; +#endif +} -#if MT32EMU_USE_FLOAT_SAMPLES - // store input - feedback / 2 - buffer[index] = in - 0.5f * bufferOut; +static inline FloatSample addAllpassNoise(FloatSample sample) { + return sample; +} - // return buffer output + feedforward / 2 - return bufferOut + 0.5f * buffer[index]; +/* NOTE: + * Thanks to Mok for discovering, the adder in BOSS reverb chip is found to perform addition with saturation to avoid integer overflow. + * Analysing of the algorithm suggests that the overflow is most probable when the combs output is added below. + * So, despite this isn't actually accurate, we only add the check here for performance reasons. + */ +static inline IntSample mixCombs(IntSample out1, IntSample out2, IntSample out3) { +#if MT32EMU_BOSS_REVERB_PRECISE_MODE + return Synth::clipSampleEx(Synth::clipSampleEx(Synth::clipSampleEx(Synth::clipSampleEx(IntSampleEx(out1) + (IntSampleEx(out1) >> 1)) + IntSampleEx(out2)) + (IntSampleEx(out2) >> 1)) + IntSampleEx(out3)); #else - // store input - feedback / 2 - buffer[index] = in - (bufferOut >> 1); - - // return buffer output + feedforward / 2 - return bufferOut + (buffer[index] >> 1); + return Synth::clipSampleEx(IntSampleEx(out1) + (IntSampleEx(out1) >> 1) + IntSampleEx(out2) + (IntSampleEx(out2) >> 1) + IntSampleEx(out3)); #endif } -CombFilter::CombFilter(const Bit32u useSize, const Bit8u useFilterFactor) : RingBuffer(useSize), filterFactor(useFilterFactor) {} +static inline FloatSample mixCombs(FloatSample out1, FloatSample out2, FloatSample out3) { + return 1.5f * (out1 + out2) + out3; +} + +template <class Sample> +class RingBuffer { + static inline Sample sampleValueThreshold(); -void CombFilter::process(const Sample in) { - // This model corresponds to the comb filter implementation of the real CM-32L device +protected: + Sample *buffer; + const Bit32u size; + Bit32u index; - // the previously stored value - const Sample last = buffer[index]; +public: + RingBuffer(const Bit32u newsize) : size(newsize), index(0) { + buffer = new Sample[size]; + } - // prepare input + feedback - const Sample filterIn = in + weirdMul(next(), feedbackFactor, 0xF0); + virtual ~RingBuffer() { + delete[] buffer; + buffer = NULL; + } - // store input + feedback processed by a low-pass filter - buffer[index] = weirdMul(last, filterFactor, 0xC0) - filterIn; -} + Sample next() { + if (++index >= size) { + index = 0; + } + return buffer[index]; + } + + bool isEmpty() const { + if (buffer == NULL) return true; + + Sample *buf = buffer; + for (Bit32u i = 0; i < size; i++) { + if (*buf < -sampleValueThreshold() || *buf > sampleValueThreshold()) return false; + buf++; + } + return true; + } + + void mute() { + Synth::muteSampleBuffer(buffer, size); + } +}; -Sample CombFilter::getOutputAt(const Bit32u outIndex) const { - return buffer[(size + index - outIndex) % size]; +template<> +IntSample RingBuffer<IntSample>::sampleValueThreshold() { + return 8; } -void CombFilter::setFeedbackFactor(const Bit8u useFeedbackFactor) { - feedbackFactor = useFeedbackFactor; +template<> +FloatSample RingBuffer<FloatSample>::sampleValueThreshold() { + return 0.001f; } -DelayWithLowPassFilter::DelayWithLowPassFilter(const Bit32u useSize, const Bit8u useFilterFactor, const Bit8u useAmp) - : CombFilter(useSize, useFilterFactor), amp(useAmp) {} +template <class Sample> +class AllpassFilter : public RingBuffer<Sample> { +public: + AllpassFilter(const Bit32u useSize) : RingBuffer<Sample>(useSize) {} -void DelayWithLowPassFilter::process(const Sample in) { - // the previously stored value - const Sample last = buffer[index]; + // This model corresponds to the allpass filter implementation of the real CM-32L device + // found from sample analysis + Sample process(const Sample in) { + const Sample bufferOut = this->next(); - // move to the next index - next(); + // store input - feedback / 2 + this->buffer[this->index] = in - halveSample(bufferOut); - // low-pass filter process - Sample lpfOut = weirdMul(last, filterFactor, 0xFF) + in; + // return buffer output + feedforward / 2 + return bufferOut + halveSample(this->buffer[this->index]); + } +}; - // store lpfOut multiplied by LPF amp factor - buffer[index] = weirdMul(lpfOut, amp, 0xFF); -} +template <class Sample> +class CombFilter : public RingBuffer<Sample> { +protected: + const Bit8u filterFactor; + Bit8u feedbackFactor; -TapDelayCombFilter::TapDelayCombFilter(const Bit32u useSize, const Bit8u useFilterFactor) : CombFilter(useSize, useFilterFactor) {} +public: + CombFilter(const Bit32u useSize, const Bit8u useFilterFactor) : RingBuffer<Sample>(useSize), filterFactor(useFilterFactor) {} -void TapDelayCombFilter::process(const Sample in) { - // the previously stored value - const Sample last = buffer[index]; + // This model corresponds to the comb filter implementation of the real CM-32L device + void process(const Sample in) { - // move to the next index - next(); + // the previously stored value + const Sample last = this->buffer[this->index]; - // 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 - const Sample filterIn = in + weirdMul(getOutputAt(outR + MODE_3_FEEDBACK_DELAY), feedbackFactor, 0xF0); + // prepare input + feedback + const Sample filterIn = in + weirdMul(this->next(), feedbackFactor, 0xF0); - // store input + feedback processed by a low-pass filter - buffer[index] = weirdMul(last, filterFactor, 0xF0) - filterIn; -} + // store input + feedback processed by a low-pass filter + this->buffer[this->index] = weirdMul(last, filterFactor, 0xC0) - filterIn; + } -Sample TapDelayCombFilter::getLeftOutput() const { - return getOutputAt(outL + PROCESS_DELAY + MODE_3_ADDITIONAL_DELAY); -} + Sample getOutputAt(const Bit32u outIndex) const { + return this->buffer[(this->size + this->index - outIndex) % this->size]; + } -Sample TapDelayCombFilter::getRightOutput() const { - return getOutputAt(outR + PROCESS_DELAY + MODE_3_ADDITIONAL_DELAY); -} + void setFeedbackFactor(const Bit8u useFeedbackFactor) { + feedbackFactor = useFeedbackFactor; + } +}; -void TapDelayCombFilter::setOutputPositions(const Bit32u useOutL, const Bit32u useOutR) { - outL = useOutL; - outR = useOutR; -} +template <class Sample> +class DelayWithLowPassFilter : public CombFilter<Sample> { + Bit8u amp; -BReverbModel::BReverbModel(const ReverbMode mode, const bool mt32CompatibleModel) : - allpasses(NULL), combs(NULL), - currentSettings(mt32CompatibleModel ? getMT32Settings(mode) : getCM32L_LAPCSettings(mode)), - tapDelayMode(mode == REVERB_MODE_TAP_DELAY) {} +public: + DelayWithLowPassFilter(const Bit32u useSize, const Bit8u useFilterFactor, const Bit8u useAmp) + : CombFilter<Sample>(useSize, useFilterFactor), amp(useAmp) {} -BReverbModel::~BReverbModel() { - close(); -} + void process(const Sample in) { + // the previously stored value + const Sample last = this->buffer[this->index]; -void BReverbModel::open() { - if (currentSettings.numberOfAllpasses > 0) { - allpasses = new AllpassFilter*[currentSettings.numberOfAllpasses]; - for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) { - allpasses[i] = new AllpassFilter(currentSettings.allpassSizes[i]); - } + // move to the next index + this->next(); + + // low-pass filter process + Sample lpfOut = weirdMul(last, this->filterFactor, 0xFF) + in; + + // store lpfOut multiplied by LPF amp factor + this->buffer[this->index] = weirdMul(lpfOut, amp, 0xFF); } - combs = new CombFilter*[currentSettings.numberOfCombs]; - if (tapDelayMode) { - *combs = new TapDelayCombFilter(*currentSettings.combSizes, *currentSettings.filterFactors); - } else { - combs[0] = new DelayWithLowPassFilter(currentSettings.combSizes[0], currentSettings.filterFactors[0], currentSettings.lpfAmp); - for (Bit32u i = 1; i < currentSettings.numberOfCombs; i++) { - combs[i] = new CombFilter(currentSettings.combSizes[i], currentSettings.filterFactors[i]); - } +}; + +template <class Sample> +class TapDelayCombFilter : public CombFilter<Sample> { + Bit32u outL; + Bit32u outR; + +public: + TapDelayCombFilter(const Bit32u useSize, const Bit8u useFilterFactor) : CombFilter<Sample>(useSize, useFilterFactor) {} + + void process(const Sample in) { + // the previously stored value + const Sample last = this->buffer[this->index]; + + // move to the next index + this->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 + const Sample filterIn = in + weirdMul(this->getOutputAt(outR + MODE_3_FEEDBACK_DELAY), this->feedbackFactor, 0xF0); + + // store input + feedback processed by a low-pass filter + this->buffer[this->index] = weirdMul(last, this->filterFactor, 0xF0) - filterIn; } - mute(); -} -void BReverbModel::close() { - if (allpasses != NULL) { - for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) { - if (allpasses[i] != NULL) { - delete allpasses[i]; - allpasses[i] = NULL; + Sample getLeftOutput() const { + return this->getOutputAt(outL + PROCESS_DELAY + MODE_3_ADDITIONAL_DELAY); + } + + Sample getRightOutput() const { + return this->getOutputAt(outR + PROCESS_DELAY + MODE_3_ADDITIONAL_DELAY); + } + + void setOutputPositions(const Bit32u useOutL, const Bit32u useOutR) { + outL = useOutL; + outR = useOutR; + } +}; + +template <class Sample> +class BReverbModelImpl : public BReverbModel { +public: + AllpassFilter<Sample> **allpasses; + CombFilter<Sample> **combs; + + const BReverbSettings ¤tSettings; + const bool tapDelayMode; + Bit8u dryAmp; + Bit8u wetLevel; + + BReverbModelImpl(const ReverbMode mode, const bool mt32CompatibleModel) : + allpasses(NULL), combs(NULL), + currentSettings(mt32CompatibleModel ? getMT32Settings(mode) : getCM32L_LAPCSettings(mode)), + tapDelayMode(mode == REVERB_MODE_TAP_DELAY) + {} + + ~BReverbModelImpl() { + close(); + } + + bool isOpen() const { + return combs != NULL; + } + + void open() { + if (isOpen()) return; + if (currentSettings.numberOfAllpasses > 0) { + allpasses = new AllpassFilter<Sample>*[currentSettings.numberOfAllpasses]; + for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) { + allpasses[i] = new AllpassFilter<Sample>(currentSettings.allpassSizes[i]); } } - delete[] allpasses; - allpasses = NULL; - } - if (combs != NULL) { - for (Bit32u i = 0; i < currentSettings.numberOfCombs; i++) { - if (combs[i] != NULL) { - delete combs[i]; - combs[i] = NULL; + combs = new CombFilter<Sample>*[currentSettings.numberOfCombs]; + if (tapDelayMode) { + *combs = new TapDelayCombFilter<Sample>(*currentSettings.combSizes, *currentSettings.filterFactors); + } else { + combs[0] = new DelayWithLowPassFilter<Sample>(currentSettings.combSizes[0], currentSettings.filterFactors[0], currentSettings.lpfAmp); + for (Bit32u i = 1; i < currentSettings.numberOfCombs; i++) { + combs[i] = new CombFilter<Sample>(currentSettings.combSizes[i], currentSettings.filterFactors[i]); } } - delete[] combs; - combs = NULL; + mute(); } -} -void BReverbModel::mute() { - if (allpasses != NULL) { - for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) { - allpasses[i]->mute(); + void close() { + if (allpasses != NULL) { + for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) { + if (allpasses[i] != NULL) { + delete allpasses[i]; + allpasses[i] = NULL; + } + } + delete[] allpasses; + allpasses = NULL; } - } - if (combs != NULL) { - for (Bit32u i = 0; i < currentSettings.numberOfCombs; i++) { - combs[i]->mute(); + if (combs != NULL) { + for (Bit32u i = 0; i < currentSettings.numberOfCombs; i++) { + if (combs[i] != NULL) { + delete combs[i]; + combs[i] = NULL; + } + } + delete[] combs; + combs = NULL; } } -} -void BReverbModel::setParameters(Bit8u time, Bit8u level) { - if (combs == NULL) return; - level &= 7; - time &= 7; - if (tapDelayMode) { - TapDelayCombFilter *comb = static_cast<TapDelayCombFilter *> (*combs); - comb->setOutputPositions(currentSettings.outLPositions[time], currentSettings.outRPositions[time & 7]); - comb->setFeedbackFactor(currentSettings.feedbackFactors[((level < 3) || (time < 6)) ? 0 : 1]); - } else { - for (Bit32u i = 0; i < currentSettings.numberOfCombs; i++) { - combs[i]->setFeedbackFactor(currentSettings.feedbackFactors[(i << 3) + time]); + void mute() { + if (allpasses != NULL) { + for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) { + allpasses[i]->mute(); + } + } + if (combs != NULL) { + for (Bit32u i = 0; i < currentSettings.numberOfCombs; i++) { + combs[i]->mute(); + } } } - if (time == 0 && level == 0) { - dryAmp = wetLevel = 0; - } else { - if (tapDelayMode && ((time == 0) || (time == 1 && level == 1))) { - // Looks like MT-32 implementation has some minor quirks in this mode: - // for odd level values, the output level changes sometimes depending on the time value which doesn't seem right. - dryAmp = currentSettings.dryAmps[level + 8]; + + void setParameters(Bit8u time, Bit8u level) { + if (!isOpen()) return; + level &= 7; + time &= 7; + if (tapDelayMode) { + TapDelayCombFilter<Sample> *comb = static_cast<TapDelayCombFilter<Sample> *> (*combs); + comb->setOutputPositions(currentSettings.outLPositions[time], currentSettings.outRPositions[time & 7]); + comb->setFeedbackFactor(currentSettings.feedbackFactors[((level < 3) || (time < 6)) ? 0 : 1]); + } else { + for (Bit32u i = 1; i < currentSettings.numberOfCombs; i++) { + combs[i]->setFeedbackFactor(currentSettings.feedbackFactors[(i << 3) + time]); + } + } + if (time == 0 && level == 0) { + dryAmp = wetLevel = 0; } else { - dryAmp = currentSettings.dryAmps[level]; + if (tapDelayMode && ((time == 0) || (time == 1 && level == 1))) { + // Looks like MT-32 implementation has some minor quirks in this mode: + // for odd level values, the output level changes sometimes depending on the time value which doesn't seem right. + dryAmp = currentSettings.dryAmps[level + 8]; + } else { + dryAmp = currentSettings.dryAmps[level]; + } + wetLevel = currentSettings.wetLevels[level]; } - wetLevel = currentSettings.wetLevels[level]; } -} -bool BReverbModel::isActive() const { - if (combs == NULL) { + bool isActive() const { + if (!isOpen()) return false; + for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) { + if (!allpasses[i]->isEmpty()) return true; + } + for (Bit32u i = 0; i < currentSettings.numberOfCombs; i++) { + if (!combs[i]->isEmpty()) return true; + } return false; } - for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) { - if (!allpasses[i]->isEmpty()) return true; - } - for (Bit32u i = 0; i < currentSettings.numberOfCombs; i++) { - if (!combs[i]->isEmpty()) return true; - } - return false; -} -bool BReverbModel::isMT32Compatible(const ReverbMode mode) const { - return ¤tSettings == &getMT32Settings(mode); -} - -void BReverbModel::process(const Sample *inLeft, const Sample *inRight, Sample *outLeft, Sample *outRight, Bit32u numSamples) { - if (combs == NULL) { - Synth::muteSampleBuffer(outLeft, numSamples); - Synth::muteSampleBuffer(outRight, numSamples); - return; + bool isMT32Compatible(const ReverbMode mode) const { + return ¤tSettings == &getMT32Settings(mode); } - Sample dry; - - while ((numSamples--) > 0) { - if (tapDelayMode) { -#if MT32EMU_USE_FLOAT_SAMPLES - dry = (*(inLeft++) * 0.5f) + (*(inRight++) * 0.5f); -#else - dry = (*(inLeft++) >> 1) + (*(inRight++) >> 1); -#endif - } else { -#if MT32EMU_USE_FLOAT_SAMPLES - dry = (*(inLeft++) * 0.25f) + (*(inRight++) * 0.25f); -#elif MT32EMU_BOSS_REVERB_PRECISE_MODE - dry = (*(inLeft++) >> 1) / 2 + (*(inRight++) >> 1) / 2; -#else - dry = (*(inLeft++) >> 2) + (*(inRight++) >> 2); -#endif + template <class SampleEx> + void produceOutput(const Sample *inLeft, const Sample *inRight, Sample *outLeft, Sample *outRight, Bit32u numSamples) { + if (!isOpen()) { + Synth::muteSampleBuffer(outLeft, numSamples); + Synth::muteSampleBuffer(outRight, numSamples); + return; } - // Looks like dryAmp doesn't change in MT-32 but it does in CM-32L / LAPC-I - dry = weirdMul(dry, dryAmp, 0xFF); + while ((numSamples--) > 0) { + Sample dry; - if (tapDelayMode) { - TapDelayCombFilter *comb = static_cast<TapDelayCombFilter *> (*combs); - comb->process(dry); - if (outLeft != NULL) { - *(outLeft++) = weirdMul(comb->getLeftOutput(), wetLevel, 0xFF); - } - if (outRight != NULL) { - *(outRight++) = weirdMul(comb->getRightOutput(), wetLevel, 0xFF); + if (tapDelayMode) { + dry = halveSample(*(inLeft++)) + halveSample(*(inRight++)); + } else { + dry = quarterSample(*(inLeft++)) + quarterSample(*(inRight++)); } - } else { - // 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 - Sample outL1 = combs[1]->getOutputAt(currentSettings.outLPositions[0] - 1); - - combs[1]->process(link); - combs[2]->process(link); - combs[3]->process(link); - - if (outLeft != NULL) { - Sample outL2 = combs[2]->getOutputAt(currentSettings.outLPositions[1]); - Sample outL3 = combs[3]->getOutputAt(currentSettings.outLPositions[2]); -#if MT32EMU_USE_FLOAT_SAMPLES - Sample outSample = 1.5f * (outL1 + outL2) + outL3; -#elif MT32EMU_BOSS_REVERB_PRECISE_MODE - /* NOTE: - * Thanks to Mok for discovering, the adder in BOSS reverb chip is found to perform addition with saturation to avoid integer overflow. - * Analysing of the algorithm suggests that the overflow is most probable when the combs output is added below. - * So, despite this isn't actually accurate, we only add the check here for performance reasons. - */ - Sample outSample = Synth::clipSampleEx(Synth::clipSampleEx(Synth::clipSampleEx(Synth::clipSampleEx(SampleEx(outL1) + (SampleEx(outL1) >> 1)) + SampleEx(outL2)) + (SampleEx(outL2) >> 1)) + SampleEx(outL3)); -#else - Sample outSample = Synth::clipSampleEx(SampleEx(outL1) + (SampleEx(outL1) >> 1) + SampleEx(outL2) + (SampleEx(outL2) >> 1) + SampleEx(outL3)); -#endif - *(outLeft++) = weirdMul(outSample, wetLevel, 0xFF); - } - if (outRight != NULL) { - 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 - Sample outSample = 1.5f * (outR1 + outR2) + outR3; -#elif MT32EMU_BOSS_REVERB_PRECISE_MODE - // See the note above for the left channel output. - Sample outSample = Synth::clipSampleEx(Synth::clipSampleEx(Synth::clipSampleEx(Synth::clipSampleEx(SampleEx(outR1) + (SampleEx(outR1) >> 1)) + SampleEx(outR2)) + (SampleEx(outR2) >> 1)) + SampleEx(outR3)); -#else - Sample outSample = Synth::clipSampleEx(SampleEx(outR1) + (SampleEx(outR1) >> 1) + SampleEx(outR2) + (SampleEx(outR2) >> 1) + SampleEx(outR3)); -#endif - *(outRight++) = weirdMul(outSample, wetLevel, 0xFF); - } - } + // Looks like dryAmp doesn't change in MT-32 but it does in CM-32L / LAPC-I + dry = weirdMul(addDCBias(dry), dryAmp, 0xFF); + + if (tapDelayMode) { + TapDelayCombFilter<Sample> *comb = static_cast<TapDelayCombFilter<Sample> *>(*combs); + comb->process(dry); + if (outLeft != NULL) { + *(outLeft++) = weirdMul(comb->getLeftOutput(), wetLevel, 0xFF); + } + if (outRight != NULL) { + *(outRight++) = weirdMul(comb->getRightOutput(), wetLevel, 0xFF); + } + } else { + DelayWithLowPassFilter<Sample> * const entranceDelay = static_cast<DelayWithLowPassFilter<Sample> *>(combs[0]); + // If the output position is equal to the comb size, get it now in order not to loose it + Sample link = entranceDelay->getOutputAt(currentSettings.combSizes[0] - 1); + + // Entrance LPF. Note, comb.process() differs a bit here. + entranceDelay->process(dry); + + link = allpasses[0]->process(addAllpassNoise(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 + Sample outL1 = combs[1]->getOutputAt(currentSettings.outLPositions[0] - 1); + + combs[1]->process(link); + combs[2]->process(link); + combs[3]->process(link); + + if (outLeft != NULL) { + Sample outL2 = combs[2]->getOutputAt(currentSettings.outLPositions[1]); + Sample outL3 = combs[3]->getOutputAt(currentSettings.outLPositions[2]); + Sample outSample = mixCombs(outL1, outL2, outL3); + *(outLeft++) = weirdMul(outSample, wetLevel, 0xFF); + } + if (outRight != NULL) { + Sample outR1 = combs[1]->getOutputAt(currentSettings.outRPositions[0]); + Sample outR2 = combs[2]->getOutputAt(currentSettings.outRPositions[1]); + Sample outR3 = combs[3]->getOutputAt(currentSettings.outRPositions[2]); + Sample outSample = mixCombs(outR1, outR2, outR3); + *(outRight++) = weirdMul(outSample, wetLevel, 0xFF); + } + } // if (tapDelayMode) + } // while ((numSamples--) > 0) + } // produceOutput + + bool process(const IntSample *inLeft, const IntSample *inRight, IntSample *outLeft, IntSample *outRight, Bit32u numSamples); + bool process(const FloatSample *inLeft, const FloatSample *inRight, FloatSample *outLeft, FloatSample *outRight, Bit32u numSamples); +}; + +BReverbModel *BReverbModel::createBReverbModel(const ReverbMode mode, const bool mt32CompatibleModel, const RendererType rendererType) { + switch (rendererType) + { + case RendererType_BIT16S: + return new BReverbModelImpl<IntSample>(mode, mt32CompatibleModel); + case RendererType_FLOAT: + return new BReverbModelImpl<FloatSample>(mode, mt32CompatibleModel); } + return NULL; +} + +template <> +bool BReverbModelImpl<IntSample>::process(const IntSample *inLeft, const IntSample *inRight, IntSample *outLeft, IntSample *outRight, Bit32u numSamples) { + produceOutput<IntSampleEx>(inLeft, inRight, outLeft, outRight, numSamples); + return true; +} + +template <> +bool BReverbModelImpl<IntSample>::process(const FloatSample *, const FloatSample *, FloatSample *, FloatSample *, Bit32u) { + return false; +} + +template <> +bool BReverbModelImpl<FloatSample>::process(const IntSample *, const IntSample *, IntSample *, IntSample *, Bit32u) { + return false; +} + +template <> +bool BReverbModelImpl<FloatSample>::process(const FloatSample *inLeft, const FloatSample *inRight, FloatSample *outLeft, FloatSample *outRight, Bit32u numSamples) { + produceOutput<FloatSample>(inLeft, inRight, outLeft, outRight, numSamples); + return true; } } // namespace MT32Emu diff --git a/audio/softsynth/mt32/BReverbModel.h b/audio/softsynth/mt32/BReverbModel.h index 8cfc5da8a3..5b1d411988 100644 --- a/audio/softsynth/mt32/BReverbModel.h +++ b/audio/softsynth/mt32/BReverbModel.h @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 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 @@ -20,101 +20,27 @@ #include "globals.h" #include "internals.h" +#include "Enumerations.h" #include "Types.h" namespace MT32Emu { -struct BReverbSettings { - const Bit32u numberOfAllpasses; - const Bit32u * const allpassSizes; - const Bit32u numberOfCombs; - const Bit32u * const combSizes; - const Bit32u * const outLPositions; - const Bit32u * const outRPositions; - const Bit8u * const filterFactors; - const Bit8u * const feedbackFactors; - const Bit8u * const dryAmps; - const Bit8u * const wetLevels; - const Bit8u lpfAmp; -}; - -class RingBuffer { -protected: - Sample *buffer; - const Bit32u size; - Bit32u index; - -public: - RingBuffer(const Bit32u size); - virtual ~RingBuffer(); - Sample next(); - bool isEmpty() const; - void mute(); -}; - -class AllpassFilter : public RingBuffer { -public: - AllpassFilter(const Bit32u size); - Sample process(const Sample in); -}; - -class CombFilter : public RingBuffer { -protected: - const Bit8u filterFactor; - Bit8u feedbackFactor; - -public: - CombFilter(const Bit32u size, const Bit8u useFilterFactor); - virtual void process(const Sample in); - Sample getOutputAt(const Bit32u outIndex) const; - void setFeedbackFactor(const Bit8u useFeedbackFactor); -}; - -class DelayWithLowPassFilter : public CombFilter { - Bit8u amp; - -public: - DelayWithLowPassFilter(const Bit32u useSize, const Bit8u useFilterFactor, const Bit8u useAmp); - void process(const Sample in); - void setFeedbackFactor(const Bit8u) {} -}; - -class TapDelayCombFilter : public CombFilter { - Bit32u outL; - Bit32u outR; - -public: - TapDelayCombFilter(const Bit32u useSize, const Bit8u useFilterFactor); - void process(const Sample in); - Sample getLeftOutput() const; - Sample getRightOutput() const; - void setOutputPositions(const Bit32u useOutL, const Bit32u useOutR); -}; - class BReverbModel { - AllpassFilter **allpasses; - CombFilter **combs; - - const BReverbSettings ¤tSettings; - const bool tapDelayMode; - Bit8u dryAmp; - Bit8u wetLevel; - - static const BReverbSettings &getCM32L_LAPCSettings(const ReverbMode mode); - static const BReverbSettings &getMT32Settings(const ReverbMode mode); - public: - BReverbModel(const ReverbMode mode, const bool mt32CompatibleModel = false); - ~BReverbModel(); + static BReverbModel *createBReverbModel(const ReverbMode mode, const bool mt32CompatibleModel, const RendererType rendererType); + + virtual ~BReverbModel() {} + virtual bool isOpen() const = 0; // After construction or a close(), open() must be called at least once before any other call (with the exception of close()). - void open(); + virtual void open() = 0; // May be called multiple times without an open() in between. - void close(); - void mute(); - void setParameters(Bit8u time, Bit8u level); - void process(const Sample *inLeft, const Sample *inRight, Sample *outLeft, Sample *outRight, Bit32u numSamples); - bool isActive() const; - bool isMT32Compatible(const ReverbMode mode) const; + virtual void close() = 0; + virtual void mute() = 0; + virtual void setParameters(Bit8u time, Bit8u level) = 0; + virtual bool isActive() const = 0; + virtual bool isMT32Compatible(const ReverbMode mode) const = 0; + virtual bool process(const IntSample *inLeft, const IntSample *inRight, IntSample *outLeft, IntSample *outRight, Bit32u numSamples) = 0; + virtual bool process(const FloatSample *inLeft, const FloatSample *inRight, FloatSample *outLeft, FloatSample *outRight, Bit32u numSamples) = 0; }; } // namespace MT32Emu diff --git a/audio/softsynth/mt32/Enumerations.h b/audio/softsynth/mt32/Enumerations.h index 9b0a35d0bf..bb580ca5b1 100644 --- a/audio/softsynth/mt32/Enumerations.h +++ b/audio/softsynth/mt32/Enumerations.h @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 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 @@ -35,6 +35,12 @@ #define MT32EMU_PARTIAL_STATE_NAME mt32emu_partial_state #define MT32EMU_PARTIAL_STATE(ident) MT32EMU_PS_##ident +#define MT32EMU_SAMPLERATE_CONVERSION_QUALITY_NAME mt32emu_samplerate_conversion_quality +#define MT32EMU_SAMPLERATE_CONVERSION_QUALITY(ident) MT32EMU_SRCQ_##ident + +#define MT32EMU_RENDERER_TYPE_NAME mt32emu_renderer_type +#define MT32EMU_RENDERER_TYPE(ident) MT32EMU_RT_##ident + #else /* #ifdef MT32EMU_C_ENUMERATIONS */ #define MT32EMU_CPP_ENUMERATIONS_H @@ -51,6 +57,12 @@ #define MT32EMU_PARTIAL_STATE_NAME PartialState #define MT32EMU_PARTIAL_STATE(ident) PartialState_##ident +#define MT32EMU_SAMPLERATE_CONVERSION_QUALITY_NAME SamplerateConversionQuality +#define MT32EMU_SAMPLERATE_CONVERSION_QUALITY(ident) SamplerateConversionQuality_##ident + +#define MT32EMU_RENDERER_TYPE_NAME RendererType +#define MT32EMU_RENDERER_TYPE(ident) RendererType_##ident + namespace MT32Emu { #endif /* #ifdef MT32EMU_C_ENUMERATIONS */ @@ -73,7 +85,6 @@ enum MT32EMU_DAC_INPUT_MODE_NAME { * Nicer overdrive characteristics than the DAC hacks (it simply clips samples within range) * Much less likely to overdrive than any other mode. * Half the volume of any of the other modes. - * Output gain is ignored for both LA32 and reverb output. * Perfect for developers while debugging :) */ MT32EMU_DAC_INPUT_MODE(PURE), @@ -134,6 +145,21 @@ enum MT32EMU_PARTIAL_STATE_NAME { MT32EMU_PARTIAL_STATE(RELEASE) }; +enum MT32EMU_SAMPLERATE_CONVERSION_QUALITY_NAME { + /** Use this only when the speed is more important than the audio quality. */ + MT32EMU_SAMPLERATE_CONVERSION_QUALITY(FASTEST), + MT32EMU_SAMPLERATE_CONVERSION_QUALITY(FAST), + MT32EMU_SAMPLERATE_CONVERSION_QUALITY(GOOD), + MT32EMU_SAMPLERATE_CONVERSION_QUALITY(BEST) +}; + +enum MT32EMU_RENDERER_TYPE_NAME { + /** Use 16-bit signed samples in the renderer and the accurate wave generator model based on logarithmic fixed-point computations and LUTs. Maximum emulation accuracy and speed. */ + MT32EMU_RENDERER_TYPE(BIT16S), + /** Use float samples in the renderer and simplified wave generator model. Maximum output quality and minimum noise. */ + MT32EMU_RENDERER_TYPE(FLOAT) +}; + #ifndef MT32EMU_C_ENUMERATIONS } // namespace MT32Emu @@ -152,4 +178,10 @@ enum MT32EMU_PARTIAL_STATE_NAME { #undef MT32EMU_PARTIAL_STATE_NAME #undef MT32EMU_PARTIAL_STATE +#undef MT32EMU_SAMPLERATE_CONVERSION_QUALITY_NAME +#undef MT32EMU_SAMPLERATE_CONVERSION_QUALITY + +#undef MT32EMU_RENDERER_TYPE_NAME +#undef MT32EMU_RENDERER_TYPE + #endif /* #if (!defined MT32EMU_CPP_ENUMERATIONS_H && !defined MT32EMU_C_ENUMERATIONS) || (!defined MT32EMU_C_ENUMERATIONS_H && defined MT32EMU_C_ENUMERATIONS) */ diff --git a/audio/softsynth/mt32/File.cpp b/audio/softsynth/mt32/File.cpp index 07164823d3..a5967b4f34 100644 --- a/audio/softsynth/mt32/File.cpp +++ b/audio/softsynth/mt32/File.cpp @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 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 diff --git a/audio/softsynth/mt32/File.h b/audio/softsynth/mt32/File.h index c9a7d582b4..91a0a7fe6c 100644 --- a/audio/softsynth/mt32/File.h +++ b/audio/softsynth/mt32/File.h @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 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 diff --git a/audio/softsynth/mt32/FileStream.cpp b/audio/softsynth/mt32/FileStream.cpp index 768c5fcdab..081f41a0e5 100644 --- a/audio/softsynth/mt32/FileStream.cpp +++ b/audio/softsynth/mt32/FileStream.cpp @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 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 @@ -15,12 +15,26 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ +#ifdef MT32EMU_SHARED +#include <locale> +#endif + #include "internals.h" #include "FileStream.h" namespace MT32Emu { +static inline void configureSystemLocale() { +#ifdef MT32EMU_SHARED + static bool configured = false; + + if (configured) return; + configured = true; + std::locale::global(std::locale("")); +#endif +} + using std::ios_base; FileStream::FileStream() : ifsp(*new std::ifstream), data(NULL), size(0) @@ -70,6 +84,7 @@ const Bit8u *FileStream::getData() { } bool FileStream::open(const char *filename) { + configureSystemLocale(); ifsp.clear(); ifsp.open(filename, ios_base::in | ios_base::binary); return !ifsp.fail(); diff --git a/audio/softsynth/mt32/FileStream.h b/audio/softsynth/mt32/FileStream.h index 2de6e801ff..ea5de69527 100644 --- a/audio/softsynth/mt32/FileStream.h +++ b/audio/softsynth/mt32/FileStream.h @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 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 diff --git a/audio/softsynth/mt32/LA32FloatWaveGenerator.cpp b/audio/softsynth/mt32/LA32FloatWaveGenerator.cpp index 824204e81b..6ff4aa37b4 100644 --- a/audio/softsynth/mt32/LA32FloatWaveGenerator.cpp +++ b/audio/softsynth/mt32/LA32FloatWaveGenerator.cpp @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 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 @@ -15,11 +15,13 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef MT32EMU_LA32_WAVE_GENERATOR_CPP -#error This file should be included from LA32WaveGenerator.cpp only. -#endif +#include <cstddef> +#include "internals.h" + +#include "LA32FloatWaveGenerator.h" #include "mmath.h" +#include "Tables.h" namespace MT32Emu { @@ -27,7 +29,7 @@ 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) { +float LA32FloatWaveGenerator::getPCMSample(unsigned int position) { if (position >= pcmWaveLength) { if (!pcmWaveLooped) { return 0; @@ -39,7 +41,7 @@ float LA32WaveGenerator::getPCMSample(unsigned int position) { return ((pcmSample & 32768) == 0) ? sampleValue : -sampleValue; } -void LA32WaveGenerator::initSynth(const bool useSawtoothWaveform, const Bit8u usePulseWidth, const Bit8u useResonance) { +void LA32FloatWaveGenerator::initSynth(const bool useSawtoothWaveform, const Bit8u usePulseWidth, const Bit8u useResonance) { sawtoothWaveform = useSawtoothWaveform; pulseWidth = usePulseWidth; resonance = useResonance; @@ -51,7 +53,7 @@ void LA32WaveGenerator::initSynth(const bool useSawtoothWaveform, const Bit8u us active = true; } -void LA32WaveGenerator::initPCM(const Bit16s * const usePCMWaveAddress, const Bit32u usePCMWaveLength, const bool usePCMWaveLooped, const bool usePCMWaveInterpolated) { +void LA32FloatWaveGenerator::initPCM(const Bit16s * const usePCMWaveAddress, const Bit32u usePCMWaveLength, const bool usePCMWaveLooped, const bool usePCMWaveInterpolated) { pcmWaveAddress = usePCMWaveAddress; pcmWaveLength = usePCMWaveLength; pcmWaveLooped = usePCMWaveLooped; @@ -64,7 +66,7 @@ void LA32WaveGenerator::initPCM(const Bit16s * const usePCMWaveAddress, const Bi // ampVal - Logarithmic amp of the wave generator // pitch - Logarithmic frequency of the resulting wave // cutoffRampVal - Composed of the base cutoff in range [78..178] left-shifted by 18 bits and the TVF modifier -float LA32WaveGenerator::generateNextSample(const Bit32u ampVal, const Bit16u pitch, const Bit32u cutoffRampVal) { +float LA32FloatWaveGenerator::generateNextSample(const Bit32u ampVal, const Bit16u pitch, const Bit32u cutoffRampVal) { if (!active) { return 0.0f; } @@ -87,7 +89,7 @@ float LA32WaveGenerator::generateNextSample(const Bit32u ampVal, const Bit16u pi if (isPCMWave()) { // Render PCM waveform int len = pcmWaveLength; - int intPCMPosition = (int)pcmPosition; + 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(); @@ -108,7 +110,7 @@ float LA32WaveGenerator::generateNextSample(const Bit32u ampVal, const Bit16u pi float newPCMPosition = pcmPosition + positionDelta; if (pcmWaveLooped) { - newPCMPosition = fmod(newPCMPosition, (float)pcmWaveLength); + newPCMPosition = fmod(newPCMPosition, float(pcmWaveLength)); } pcmPosition = newPCMPosition; } else { @@ -163,15 +165,9 @@ float LA32WaveGenerator::generateNextSample(const Bit32u ampVal, const Bit16u pi 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); + if ((cutoffVal >= MIDDLE_CUTOFF_VALUE) && (cutoffVal < RESONANCE_DECAY_THRESHOLD_CUTOFF_VALUE)) { + resAmp *= sin(FLOAT_PI * (cutoffVal - MIDDLE_CUTOFF_VALUE) / 32.0f); } // Produce filtered square wave with 2 cosine waves on slopes @@ -195,11 +191,11 @@ float LA32WaveGenerator::generateNextSample(const Bit32u ampVal, const Bit16u pi sample = -1.f; } - if (cutoffVal < 128.0f) { + if (cutoffVal < MIDDLE_CUTOFF_VALUE) { // Attenuate samples below cutoff 50 // Found by sample analysis - sample *= EXP2F(-0.125f * (128.0f - cutoffVal)); + sample *= EXP2F(-0.125f * (MIDDLE_CUTOFF_VALUE - cutoffVal)); } else { // Add resonance sine. Effective for cutoff > 50 only @@ -273,26 +269,26 @@ float LA32WaveGenerator::generateNextSample(const Bit32u ampVal, const Bit16u pi return sample; } -void LA32WaveGenerator::deactivate() { +void LA32FloatWaveGenerator::deactivate() { active = false; } -bool LA32WaveGenerator::isActive() const { +bool LA32FloatWaveGenerator::isActive() const { return active; } -bool LA32WaveGenerator::isPCMWave() const { +bool LA32FloatWaveGenerator::isPCMWave() const { return pcmWaveAddress != NULL; } -void LA32PartialPair::init(const bool useRingModulated, const bool useMixed) { +void LA32FloatPartialPair::init(const bool useRingModulated, const bool useMixed) { ringModulated = useRingModulated; mixed = useMixed; masterOutputSample = 0.0f; slaveOutputSample = 0.0f; } -void LA32PartialPair::initSynth(const PairType useMaster, const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance) { +void LA32FloatPartialPair::initSynth(const PairType useMaster, const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance) { if (useMaster == MASTER) { master.initSynth(sawtoothWaveform, pulseWidth, resonance); } else { @@ -300,7 +296,7 @@ void LA32PartialPair::initSynth(const PairType useMaster, const bool sawtoothWav } } -void LA32PartialPair::initPCM(const PairType useMaster, const Bit16s *pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped) { +void LA32FloatPartialPair::initPCM(const PairType useMaster, const Bit16s *pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped) { if (useMaster == MASTER) { master.initPCM(pcmWaveAddress, pcmWaveLength, pcmWaveLooped, true); } else { @@ -308,7 +304,7 @@ void LA32PartialPair::initPCM(const PairType useMaster, const Bit16s *pcmWaveAdd } } -void LA32PartialPair::generateNextSample(const PairType useMaster, const Bit32u amp, const Bit16u pitch, const Bit32u cutoff) { +void LA32FloatPartialPair::generateNextSample(const PairType useMaster, const Bit32u amp, const Bit16u pitch, const Bit32u cutoff) { if (useMaster == MASTER) { masterOutputSample = master.generateNextSample(amp, pitch, cutoff); } else { @@ -325,9 +321,14 @@ static inline float produceDistortedSample(float sample) { return sample; } -float LA32PartialPair::nextOutSample() { +float LA32FloatPartialPair::nextOutSample() { + // Note, LA32FloatWaveGenerator produces each sample normalised in terms of a single playing partial, + // so the unity sample corresponds to the internal LA32 logarithmic fixed-point unity sample. + // However, each logarithmic sample is then unlogged to a 14-bit signed integer value, i.e. the max absolute value is 8192. + // Thus, considering that samples are further mapped to a 16-bit signed integer, + // we apply a conversion factor 0.25 to produce properly normalised float samples. if (!ringModulated) { - return masterOutputSample + slaveOutputSample; + return 0.25f * (masterOutputSample + slaveOutputSample); } /* * SEMI-CONFIRMED: Ring modulation model derived from sample analysis of specially constructed patches which exploit distortion. @@ -338,10 +339,10 @@ float LA32PartialPair::nextOutSample() { * Most probably the overflow is caused by limited precision of the multiplication circuit as the very similar distortion occurs with panning. */ float ringModulatedSample = produceDistortedSample(masterOutputSample) * produceDistortedSample(slaveOutputSample); - return mixed ? masterOutputSample + ringModulatedSample : ringModulatedSample; + return 0.25f * (mixed ? masterOutputSample + ringModulatedSample : ringModulatedSample); } -void LA32PartialPair::deactivate(const PairType useMaster) { +void LA32FloatPartialPair::deactivate(const PairType useMaster) { if (useMaster == MASTER) { master.deactivate(); masterOutputSample = 0.0f; @@ -351,7 +352,7 @@ void LA32PartialPair::deactivate(const PairType useMaster) { } } -bool LA32PartialPair::isActive(const PairType useMaster) const { +bool LA32FloatPartialPair::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 index 89b6fe479a..7e92d0a67c 100644 --- a/audio/softsynth/mt32/LA32FloatWaveGenerator.h +++ b/audio/softsynth/mt32/LA32FloatWaveGenerator.h @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 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 @@ -15,9 +15,13 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef MT32EMU_LA32_WAVE_GENERATOR_H -#error This file should be included from LA32WaveGenerator.h only. -#endif +#ifndef MT32EMU_LA32_FLOAT_WAVE_GENERATOR_H +#define MT32EMU_LA32_FLOAT_WAVE_GENERATOR_H + +#include "globals.h" +#include "internals.h" +#include "Types.h" +#include "LA32WaveGenerator.h" namespace MT32Emu { @@ -29,7 +33,7 @@ namespace MT32Emu { * 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 { +class LA32FloatWaveGenerator { //*************************************************************************** // The local copy of partial parameters below //*************************************************************************** @@ -88,23 +92,17 @@ public: // Return true if the WG engine generates PCM wave samples bool isPCMWave() const; -}; // class LA32WaveGenerator +}; // class LA32FloatWaveGenerator -// LA32PartialPair contains a structure of two partials being mixed / ring modulated -class LA32PartialPair { - LA32WaveGenerator master; - LA32WaveGenerator slave; +class LA32FloatPartialPair : public LA32PartialPair { + LA32FloatWaveGenerator master; + LA32FloatWaveGenerator 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 @@ -127,6 +125,8 @@ public: // Return active state of the WG engine bool isActive(const PairType master) const; -}; // class LA32PartialPair +}; // class LA32FloatPartialPair } // namespace MT32Emu + +#endif // #ifndef MT32EMU_LA32_FLOAT_WAVE_GENERATOR_H diff --git a/audio/softsynth/mt32/LA32Ramp.cpp b/audio/softsynth/mt32/LA32Ramp.cpp index a4da4f57a8..9dcf143fbf 100644 --- a/audio/softsynth/mt32/LA32Ramp.cpp +++ b/audio/softsynth/mt32/LA32Ramp.cpp @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 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 @@ -56,8 +56,8 @@ We haven't fully explored: namespace MT32Emu { // SEMI-CONFIRMED from sample analysis. -const int TARGET_MULT = 0x40000; -const unsigned int MAX_CURRENT = 0xFF * TARGET_MULT; +const unsigned int TARGET_SHIFTS = 18; +const unsigned int MAX_CURRENT = 0xFF << TARGET_SHIFTS; // We simulate the delay in handling "target was reached" interrupts by waiting // this many samples before setting interruptRaised. @@ -96,7 +96,7 @@ void LA32Ramp::startRamp(Bit8u target, Bit8u increment) { largeIncrement++; } - largeTarget = target * TARGET_MULT; + largeTarget = target << TARGET_SHIFTS; interruptCountdown = 0; interruptRaised = false; } @@ -152,4 +152,13 @@ void LA32Ramp::reset() { interruptRaised = false; } +// This is actually beyond the LA32 ramp interface. +// Instead of polling the current value, MCU receives an interrupt when a ramp completes. +// However, this is a simple way to work around the specific behaviour of TVA +// when in sustain phase which one normally wants to avoid. +// See TVA::recalcSustain() for details. +bool LA32Ramp::isBelowCurrent(Bit8u target) const { + return Bit32u(target << TARGET_SHIFTS) < current; +} + } // namespace MT32Emu diff --git a/audio/softsynth/mt32/LA32Ramp.h b/audio/softsynth/mt32/LA32Ramp.h index 5e4ddf720e..959a1ad372 100644 --- a/audio/softsynth/mt32/LA32Ramp.h +++ b/audio/softsynth/mt32/LA32Ramp.h @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 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 @@ -39,6 +39,7 @@ public: Bit32u nextValue(); bool checkInterrupt(); void reset(); + bool isBelowCurrent(Bit8u target) const; }; } // namespace MT32Emu diff --git a/audio/softsynth/mt32/LA32WaveGenerator.cpp b/audio/softsynth/mt32/LA32WaveGenerator.cpp index a9c425beac..f6f6928801 100644 --- a/audio/softsynth/mt32/LA32WaveGenerator.cpp +++ b/audio/softsynth/mt32/LA32WaveGenerator.cpp @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 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 @@ -22,12 +22,6 @@ #include "LA32WaveGenerator.h" #include "Tables.h" -#if MT32EMU_USE_FLOAT_SAMPLES -#define MT32EMU_LA32_WAVE_GENERATOR_CPP -#include "LA32FloatWaveGenerator.cpp" -#undef MT32EMU_LA32_WAVE_GENERATOR_CPP -#else - namespace MT32Emu { static const Bit32u SINE_SEGMENT_RELATIVE_LENGTH = 1 << 18; @@ -343,12 +337,12 @@ Bit32u LA32WaveGenerator::getPCMInterpolationFactor() const { return pcmInterpolationFactor; } -void LA32PartialPair::init(const bool useRingModulated, const bool useMixed) { +void LA32IntPartialPair::init(const bool useRingModulated, const bool useMixed) { ringModulated = useRingModulated; mixed = useMixed; } -void LA32PartialPair::initSynth(const PairType useMaster, const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance) { +void LA32IntPartialPair::initSynth(const PairType useMaster, const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance) { if (useMaster == MASTER) { master.initSynth(sawtoothWaveform, pulseWidth, resonance); } else { @@ -356,7 +350,7 @@ void LA32PartialPair::initSynth(const PairType useMaster, const bool sawtoothWav } } -void LA32PartialPair::initPCM(const PairType useMaster, const Bit16s *pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped) { +void LA32IntPartialPair::initPCM(const PairType useMaster, const Bit16s *pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped) { if (useMaster == MASTER) { master.initPCM(pcmWaveAddress, pcmWaveLength, pcmWaveLooped, true); } else { @@ -364,7 +358,7 @@ void LA32PartialPair::initPCM(const PairType useMaster, const Bit16s *pcmWaveAdd } } -void LA32PartialPair::generateNextSample(const PairType useMaster, const Bit32u amp, const Bit16u pitch, const Bit32u cutoff) { +void LA32IntPartialPair::generateNextSample(const PairType useMaster, const Bit32u amp, const Bit16u pitch, const Bit32u cutoff) { if (useMaster == MASTER) { master.generateNextSample(amp, pitch, cutoff); } else { @@ -372,7 +366,7 @@ void LA32PartialPair::generateNextSample(const PairType useMaster, const Bit32u } } -Bit16s LA32PartialPair::unlogAndMixWGOutput(const LA32WaveGenerator &wg) { +Bit16s LA32IntPartialPair::unlogAndMixWGOutput(const LA32WaveGenerator &wg) { if (!wg.isActive()) { return 0; } @@ -384,22 +378,16 @@ Bit16s LA32PartialPair::unlogAndMixWGOutput(const LA32WaveGenerator &wg) { return firstSample + secondSample; } -Bit16s LA32PartialPair::nextOutSample() { +static inline Bit16s produceDistortedSample(Bit16s sample) { + return ((sample & 0x2000) == 0) ? Bit16s(sample & 0x1fff) : Bit16s(sample | ~0x1fff); +} + +Bit16s LA32IntPartialPair::nextOutSample() { if (!ringModulated) { return unlogAndMixWGOutput(master) + unlogAndMixWGOutput(slave); } - /* - * SEMI-CONFIRMED: Ring modulation model derived from sample analysis of specially constructed patches which exploit distortion. - * LA32 ring modulator found to produce distorted output in case if the absolute value of maximal amplitude of one of the input partials exceeds 8191. - * This is easy to reproduce using synth partials with resonance values close to the maximum. It looks like an integer overflow happens in this case. - * As the distortion is strictly bound to the amplitude of the complete mixed square + resonance wave in the linear space, - * it is reasonable to assume the ring modulation is performed also in the linear space by sample multiplication. - * Most probably the overflow is caused by limited precision of the multiplication circuit as the very similar distortion occurs with panning. - */ - Bit16s nonOverdrivenMasterSample = unlogAndMixWGOutput(master); // Store master partial sample for further mixing - Bit16s masterSample = nonOverdrivenMasterSample << 2; - masterSample >>= 2; + Bit16s masterSample = unlogAndMixWGOutput(master); // Store master partial sample for further mixing /* SEMI-CONFIRMED from sample analysis: * We observe that for partial structures with ring modulation the interpolation is not applied to the slave PCM partial. @@ -407,13 +395,20 @@ Bit16s LA32PartialPair::nextOutSample() { * is borrowed by the ring modulation circuit (or the LA32 chip has a similar lack of resources assigned to each partial pair). */ Bit16s slaveSample = slave.isPCMWave() ? LA32Utilites::unlog(slave.getOutputLogSample(true)) : unlogAndMixWGOutput(slave); - slaveSample <<= 2; - slaveSample >>= 2; - Bit16s ringModulatedSample = Bit16s((Bit32s(masterSample) * Bit32s(slaveSample)) >> 13); - return mixed ? nonOverdrivenMasterSample + ringModulatedSample : ringModulatedSample; + + /* SEMI-CONFIRMED: Ring modulation model derived from sample analysis of specially constructed patches which exploit distortion. + * LA32 ring modulator found to produce distorted output in case if the absolute value of maximal amplitude of one of the input partials exceeds 8191. + * This is easy to reproduce using synth partials with resonance values close to the maximum. It looks like an integer overflow happens in this case. + * As the distortion is strictly bound to the amplitude of the complete mixed square + resonance wave in the linear space, + * it is reasonable to assume the ring modulation is performed also in the linear space by sample multiplication. + * Most probably the overflow is caused by limited precision of the multiplication circuit as the very similar distortion occurs with panning. + */ + Bit16s ringModulatedSample = Bit16s((Bit32s(produceDistortedSample(masterSample)) * Bit32s(produceDistortedSample(slaveSample))) >> 13); + + return mixed ? masterSample + ringModulatedSample : ringModulatedSample; } -void LA32PartialPair::deactivate(const PairType useMaster) { +void LA32IntPartialPair::deactivate(const PairType useMaster) { if (useMaster == MASTER) { master.deactivate(); } else { @@ -421,10 +416,8 @@ void LA32PartialPair::deactivate(const PairType useMaster) { } } -bool LA32PartialPair::isActive(const PairType useMaster) const { +bool LA32IntPartialPair::isActive(const PairType useMaster) const { return useMaster == MASTER ? master.isActive() : slave.isActive(); } } // namespace MT32Emu - -#endif // #if MT32EMU_USE_FLOAT_SAMPLES diff --git a/audio/softsynth/mt32/LA32WaveGenerator.h b/audio/softsynth/mt32/LA32WaveGenerator.h index 6a40e44c98..c206daa63f 100644 --- a/audio/softsynth/mt32/LA32WaveGenerator.h +++ b/audio/softsynth/mt32/LA32WaveGenerator.h @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 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 @@ -22,10 +22,6 @@ #include "internals.h" #include "Types.h" -#if MT32EMU_USE_FLOAT_SAMPLES -#include "LA32FloatWaveGenerator.h" -#else - namespace MT32Emu { /** @@ -208,6 +204,30 @@ public: // LA32PartialPair contains a structure of two partials being mixed / ring modulated class LA32PartialPair { +public: + enum PairType { + MASTER, + SLAVE + }; + + virtual ~LA32PartialPair() {} + + // 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 + virtual void init(const bool ringModulated, const bool mixed) = 0; + + // Initialise the WG engine for generation of synth partial samples and set up the invariant parameters + virtual void initSynth(const PairType master, const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance) = 0; + + // Initialise the WG engine for generation of PCM partial samples and set up the invariant parameters + virtual void initPCM(const PairType master, const Bit16s * const pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped) = 0; + + // Deactivate the WG engine + virtual void deactivate(const PairType master) = 0; +}; // class LA32PartialPair + +class LA32IntPartialPair : public LA32PartialPair { LA32WaveGenerator master; LA32WaveGenerator slave; bool ringModulated; @@ -216,11 +236,6 @@ class LA32PartialPair { static Bit16s unlogAndMixWGOutput(const LA32WaveGenerator &wg); 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 @@ -235,7 +250,8 @@ public: // 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 + // Perform mixing / ring modulation of WG output and return the result + // Although, LA32 applies panning itself, we assume it is applied in the mixer, not within a pair Bit16s nextOutSample(); // Deactivate the WG engine @@ -243,10 +259,8 @@ public: // Return active state of the WG engine bool isActive(const PairType master) const; -}; // class LA32PartialPair +}; // class LA32IntPartialPair } // namespace MT32Emu -#endif // #if MT32EMU_USE_FLOAT_SAMPLES - #endif // #ifndef MT32EMU_LA32_WAVE_GENERATOR_H diff --git a/audio/softsynth/mt32/MemoryRegion.h b/audio/softsynth/mt32/MemoryRegion.h index f8d7da18f8..807f147820 100644 --- a/audio/softsynth/mt32/MemoryRegion.h +++ b/audio/softsynth/mt32/MemoryRegion.h @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 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 diff --git a/audio/softsynth/mt32/MidiEventQueue.h b/audio/softsynth/mt32/MidiEventQueue.h index 1a5ff0ad33..c5174d6cc9 100644 --- a/audio/softsynth/mt32/MidiEventQueue.h +++ b/audio/softsynth/mt32/MidiEventQueue.h @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 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 diff --git a/audio/softsynth/mt32/MidiStreamParser.cpp b/audio/softsynth/mt32/MidiStreamParser.cpp index f9ead3369c..a426a20cc3 100644 --- a/audio/softsynth/mt32/MidiStreamParser.cpp +++ b/audio/softsynth/mt32/MidiStreamParser.cpp @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 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 @@ -119,7 +119,7 @@ void MidiStreamParserImpl::parseStream(const Bit8u *stream, Bit32u length) { void MidiStreamParserImpl::processShortMessage(const Bit32u message) { // Adds running status to the MIDI message if it doesn't contain one - Bit8u status = Bit8u(message); + Bit8u status = Bit8u(message & 0xFF); if (0xF8 <= status) { midiReceiver.handleSystemRealtimeMessage(status); } else if (processStatusByte(status)) { diff --git a/audio/softsynth/mt32/MidiStreamParser.h b/audio/softsynth/mt32/MidiStreamParser.h index bd31b77c89..881ec032f5 100644 --- a/audio/softsynth/mt32/MidiStreamParser.h +++ b/audio/softsynth/mt32/MidiStreamParser.h @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 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 diff --git a/audio/softsynth/mt32/Part.cpp b/audio/softsynth/mt32/Part.cpp index 9b5119ee8b..9c85ce5599 100644 --- a/audio/softsynth/mt32/Part.cpp +++ b/audio/softsynth/mt32/Part.cpp @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 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 @@ -340,10 +340,13 @@ void RhythmPart::setPan(unsigned int midiPan) { void Part::setPan(unsigned int midiPan) { // NOTE: Panning is inverted compared to GM. - // CM-32L: Divide by 8.5 - patchTemp->panpot = Bit8u((midiPan << 3) / 68); - // FIXME: MT-32: Divide by 9 - //patchTemp->panpot = Bit8u(midiPan / 9); + if (synth->controlROMFeatures->quirkPanMult) { + // MT-32: Divide by 9 + patchTemp->panpot = Bit8u(midiPan / 9); + } else { + // CM-32L: Divide by 8.5 + patchTemp->panpot = Bit8u((midiPan << 3) / 68); + } //synth->printDebug("%s (%s): Set pan to %d", name, currentInstr, panpot); } @@ -352,6 +355,10 @@ void Part::setPan(unsigned int midiPan) { * Applies key shift to a MIDI key and converts it into an internal key value in the range 12-108. */ unsigned int Part::midiKeyToKey(unsigned int midiKey) { + if (synth->controlROMFeatures->quirkKeyShift) { + // NOTE: On MT-32 GEN0, key isn't adjusted, and keyShift is applied further in TVP, unlike newer units: + return midiKey; + } int key = midiKey + patchTemp->patch.keyShift; if (key < 36) { // After keyShift is applied, key < 36, so move up by octaves diff --git a/audio/softsynth/mt32/Part.h b/audio/softsynth/mt32/Part.h index f5171589d6..a4de1060bc 100644 --- a/audio/softsynth/mt32/Part.h +++ b/audio/softsynth/mt32/Part.h @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 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 diff --git a/audio/softsynth/mt32/Partial.cpp b/audio/softsynth/mt32/Partial.cpp index 6afef364df..60c06b7be7 100644 --- a/audio/softsynth/mt32/Partial.cpp +++ b/audio/softsynth/mt32/Partial.cpp @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -21,6 +21,7 @@ #include "Partial.h" #include "Part.h" +#include "PartialManager.h" #include "Poly.h" #include "Synth.h" #include "Tables.h" @@ -33,10 +34,26 @@ namespace MT32Emu { static const Bit8u PAN_NUMERATOR_MASTER[] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7}; static const Bit8u PAN_NUMERATOR_SLAVE[] = {0, 1, 2, 3, 4, 5, 6, 7, 7, 7, 7, 7, 7, 7, 7}; -static const Bit32s PAN_FACTORS[] = {0, 18, 37, 55, 73, 91, 110, 128, 146, 165, 183, 201, 219, 238, 256}; +// We assume the pan is applied using the same 13-bit multiplier circuit that is also used for ring modulation +// because of the observed sample overflow, so the panSetting values are likely mapped in a similar way via a LUT. +// FIXME: Sample analysis suggests that the use of panSetting is linear, but there are some quirks that still need to be resolved. +static Bit32s getPANFactor(Bit32s panSetting) { + static const Bit32u PAN_FACTORS_COUNT = 15; + static Bit32s PAN_FACTORS[PAN_FACTORS_COUNT]; + static bool firstRun = true; + + if (firstRun) { + firstRun = false; + for (Bit32u i = 1; i < PAN_FACTORS_COUNT; i++) { + PAN_FACTORS[i] = Bit32s(0.5 + i * 8192.0 / double(PAN_FACTORS_COUNT - 1)); + } + } + return PAN_FACTORS[panSetting]; +} -Partial::Partial(Synth *useSynth, int useDebugPartialNum) : - synth(useSynth), debugPartialNum(useDebugPartialNum), sampleNum(0) { +Partial::Partial(Synth *useSynth, int usePartialIndex) : + synth(useSynth), partialIndex(usePartialIndex), sampleNum(0), + floatMode(useSynth->getSelectedRendererType() == RendererType_FLOAT) { // Initialisation of tva, tvp and tvf uses 'this' pointer // and thus should not be in the initializer list to avoid a compiler warning tva = new TVA(this, &Ramp); @@ -45,9 +62,20 @@ Partial::Partial(Synth *useSynth, int useDebugPartialNum) : ownerPart = -1; poly = NULL; pair = NULL; + switch (synth->getSelectedRendererType()) { + case RendererType_BIT16S: + la32Pair = new LA32IntPartialPair; + break; + case RendererType_FLOAT: + la32Pair = new LA32FloatPartialPair; + break; + default: + la32Pair = NULL; + } } Partial::~Partial() { + delete la32Pair; delete tva; delete tvp; delete tvf; @@ -55,7 +83,7 @@ Partial::~Partial() { // Only used for debugging purposes int Partial::debugGetPartialNum() const { - return debugPartialNum; + return partialIndex; } // Only used for debugging purposes @@ -85,6 +113,7 @@ void Partial::deactivate() { return; } ownerPart = -1; + synth->partialManager->partialDeactivated(partialIndex); if (poly != NULL) { poly->partialDeactivated(this); } @@ -93,9 +122,9 @@ void Partial::deactivate() { synth->printPartialUsage(sampleNum); #endif if (isRingModulatingSlave()) { - pair->la32Pair.deactivate(LA32PartialPair::SLAVE); + pair->la32Pair->deactivate(LA32PartialPair::SLAVE); } else { - la32Pair.deactivate(LA32PartialPair::MASTER); + la32Pair->deactivate(LA32PartialPair::MASTER); if (hasRingModulatingSlave()) { pair->deactivate(); pair = NULL; @@ -108,7 +137,7 @@ void Partial::deactivate() { void Partial::startPartial(const Part *part, Poly *usePoly, const PatchCache *usePatchCache, const MemParams::RhythmTemp *rhythmTemp, Partial *pairPartial) { if (usePoly == NULL || usePatchCache == NULL) { - synth->printDebug("[Partial %d] *** Error: Starting partial for owner %d, usePoly=%s, usePatchCache=%s", debugPartialNum, ownerPart, usePoly == NULL ? "*** NULL ***" : "OK", usePatchCache == NULL ? "*** NULL ***" : "OK"); + synth->printDebug("[Partial %d] *** Error: Starting partial for owner %d, usePoly=%s, usePatchCache=%s", partialIndex, ownerPart, usePoly == NULL ? "*** NULL ***" : "OK", usePatchCache == NULL ? "*** NULL ***" : "OK"); return; } patchCache = usePatchCache; @@ -137,17 +166,17 @@ void Partial::startPartial(const Part *part, Poly *usePoly, const PatchCache *us leftPanValue = synth->reversedStereoEnabled ? 14 - panSetting : panSetting; rightPanValue = 14 - leftPanValue; -#if !MT32EMU_USE_FLOAT_SAMPLES - leftPanValue = PAN_FACTORS[leftPanValue]; - rightPanValue = PAN_FACTORS[rightPanValue]; -#endif + if (!floatMode) { + leftPanValue = getPANFactor(leftPanValue); + rightPanValue = getPANFactor(rightPanValue); + } // SEMI-CONFIRMED: From sample analysis: // Found that timbres with 3 or 4 partials (i.e. one using two partial pairs) are mixed in two different ways. // Either partial pairs are added or subtracted, it depends on how the partial pairs are allocated. // It seems that partials are grouped into quarters and if the partial pairs are allocated in different quarters the subtraction happens. // Though, this matters little for the majority of timbres, it becomes crucial for timbres which contain several partials that sound very close. - // In this case that timbre can sound totally different depending of the way it is mixed up. + // In this case that timbre can sound totally different depending on the way it is mixed up. // Most easily this effect can be displayed with the help of a special timbre consisting of several identical square wave partials (3 or 4). // Say, it is 3-partial timbre. Just play any two notes simultaneously and the polys very probably are mixed differently. // Moreover, the partial allocator retains the last partial assignment it did and all the subsequent notes will sound the same as the last released one. @@ -155,8 +184,7 @@ void Partial::startPartial(const Part *part, Poly *usePoly, const PatchCache *us // whole-quarter assignment or after some partials got aborted, even 4-partial timbres can be found sounding differently. // This behaviour is also confirmed with two more special timbres: one with identical sawtooth partials, and one with PCM wave 02. // For my personal taste, this behaviour rather enriches the sounding and should be emulated. - // Also, the current partial allocator model probably needs to be refined. - if (debugPartialNum & 8) { + if (partialIndex & 4) { leftPanValue = -leftPanValue; rightPanValue = -rightPanValue; } @@ -192,11 +220,11 @@ void Partial::startPartial(const Part *part, Poly *usePoly, const PatchCache *us LA32PartialPair *useLA32Pair; if (isRingModulatingSlave()) { pairType = LA32PartialPair::SLAVE; - useLA32Pair = &pair->la32Pair; + useLA32Pair = pair->la32Pair; } else { pairType = LA32PartialPair::MASTER; - la32Pair.init(hasRingModulatingSlave(), mixType == 1); - useLA32Pair = &la32Pair; + la32Pair->init(hasRingModulatingSlave(), mixType == 1); + useLA32Pair = la32Pair; } if (isPCM()) { useLA32Pair->initPCM(pairType, &synth->pcmROMData[pcmWave->addr], pcmWave->len, pcmWave->loop); @@ -204,7 +232,7 @@ void Partial::startPartial(const Part *part, Poly *usePoly, const PatchCache *us useLA32Pair->initSynth(pairType, (patchCache->waveform & 1) != 0, pulseWidthVal, patchCache->srcPartial.tvf.resonance + 1); } if (!hasRingModulatingSlave()) { - la32Pair.deactivate(LA32PartialPair::SLAVE); + la32Pair->deactivate(LA32PartialPair::SLAVE); } } @@ -245,6 +273,10 @@ bool Partial::isRingModulatingSlave() const { return pair != NULL && structurePosition == 1 && (mixType == 1 || mixType == 2); } +bool Partial::isRingModulatingNoMix() const { + return pair != NULL && ((structurePosition == 1 && mixType == 1) || mixType == 2); +} + bool Partial::isPCM() const { return pcmWave != NULL; } @@ -271,63 +303,90 @@ void Partial::backupCache(const PatchCache &cache) { } } -bool Partial::produceOutput(Sample *leftBuf, Sample *rightBuf, Bit32u length) { +bool Partial::canProduceOutput() { if (!isActive() || alreadyOutputed || isRingModulatingSlave()) { return false; } if (poly == NULL) { - synth->printDebug("[Partial %d] *** ERROR: poly is NULL at Partial::produceOutput()!", debugPartialNum); + synth->printDebug("[Partial %d] *** ERROR: poly is NULL at Partial::produceOutput()!", partialIndex); return false; } - alreadyOutputed = true; + return true; +} - for (sampleNum = 0; sampleNum < length; sampleNum++) { - if (!tva->isPlaying() || !la32Pair.isActive(LA32PartialPair::MASTER)) { - deactivate(); - break; - } - la32Pair.generateNextSample(LA32PartialPair::MASTER, getAmpValue(), tvp->nextPitch(), getCutoffValue()); - if (hasRingModulatingSlave()) { - la32Pair.generateNextSample(LA32PartialPair::SLAVE, pair->getAmpValue(), pair->tvp->nextPitch(), pair->getCutoffValue()); - if (!pair->tva->isPlaying() || !la32Pair.isActive(LA32PartialPair::SLAVE)) { - pair->deactivate(); - if (mixType == 2) { - deactivate(); - break; - } +template <class LA32PairImpl> +bool Partial::generateNextSample(LA32PairImpl *la32PairImpl) { + if (!tva->isPlaying() || !la32PairImpl->isActive(LA32PartialPair::MASTER)) { + deactivate(); + return false; + } + la32PairImpl->generateNextSample(LA32PartialPair::MASTER, getAmpValue(), tvp->nextPitch(), getCutoffValue()); + if (hasRingModulatingSlave()) { + la32PairImpl->generateNextSample(LA32PartialPair::SLAVE, pair->getAmpValue(), pair->tvp->nextPitch(), pair->getCutoffValue()); + if (!pair->tva->isPlaying() || !la32PairImpl->isActive(LA32PartialPair::SLAVE)) { + pair->deactivate(); + if (mixType == 2) { + deactivate(); + return false; } } + } + return true; +} - // Although, LA32 applies panning itself, we assume here it is applied in the mixer, not within a pair. - // Applying the pan value in the log-space looks like a waste of unlog resources. Though, it needs clarification. - Sample sample = la32Pair.nextOutSample(); - - // FIXME: Sample analysis suggests that the use of panVal is linear, but there are some quirks that still need to be resolved. -#if MT32EMU_USE_FLOAT_SAMPLES - Sample leftOut = (sample * (float)leftPanValue) / 14.0f; - Sample rightOut = (sample * (float)rightPanValue) / 14.0f; - *(leftBuf++) += leftOut; - *(rightBuf++) += rightOut; -#else - // FIXME: Dividing by 7 (or by 14 in a Mok-friendly way) looks of course pointless. Need clarification. - // FIXME2: LA32 may produce distorted sound in case if the absolute value of maximal amplitude of the input exceeds 8191 - // when the panning value is non-zero. Most probably the distortion occurs in the same way it does with ring modulation, - // and it seems to be caused by limited precision of the common multiplication circuit. - // From analysis of this overflow, it is obvious that the right channel output is actually found - // by subtraction of the left channel output from the input. - // Though, it is unknown whether this overflow is exploited somewhere. - Sample leftOut = Sample((sample * leftPanValue) >> 8); - Sample rightOut = Sample((sample * rightPanValue) >> 8); - *leftBuf = Synth::clipSampleEx(SampleEx(*leftBuf) + SampleEx(leftOut)); - *rightBuf = Synth::clipSampleEx(SampleEx(*rightBuf) + SampleEx(rightOut)); - leftBuf++; - rightBuf++; -#endif +void Partial::produceAndMixSample(IntSample *&leftBuf, IntSample *&rightBuf, LA32IntPartialPair *la32IntPair) { + IntSampleEx sample = la32IntPair->nextOutSample(); + + // FIXME: LA32 may produce distorted sound in case if the absolute value of maximal amplitude of the input exceeds 8191 + // when the panning value is non-zero. Most probably the distortion occurs in the same way it does with ring modulation, + // and it seems to be caused by limited precision of the common multiplication circuit. + // From analysis of this overflow, it is obvious that the right channel output is actually found + // by subtraction of the left channel output from the input. + // Though, it is unknown whether this overflow is exploited somewhere. + + IntSampleEx leftOut = ((sample * leftPanValue) >> 13) + IntSampleEx(*leftBuf); + IntSampleEx rightOut = ((sample * rightPanValue) >> 13) + IntSampleEx(*rightBuf); + *(leftBuf++) = Synth::clipSampleEx(leftOut); + *(rightBuf++) = Synth::clipSampleEx(rightOut); +} + +void Partial::produceAndMixSample(FloatSample *&leftBuf, FloatSample *&rightBuf, LA32FloatPartialPair *la32FloatPair) { + FloatSample sample = la32FloatPair->nextOutSample(); + FloatSample leftOut = (sample * leftPanValue) / 14.0f; + FloatSample rightOut = (sample * rightPanValue) / 14.0f; + *(leftBuf++) += leftOut; + *(rightBuf++) += rightOut; +} + +template <class Sample, class LA32PairImpl> +bool Partial::doProduceOutput(Sample *leftBuf, Sample *rightBuf, Bit32u length, LA32PairImpl *la32PairImpl) { + if (!canProduceOutput()) return false; + alreadyOutputed = true; + + for (sampleNum = 0; sampleNum < length; sampleNum++) { + if (!generateNextSample(la32PairImpl)) break; + produceAndMixSample(leftBuf, rightBuf, la32PairImpl); } sampleNum = 0; return true; } +bool Partial::produceOutput(IntSample *leftBuf, IntSample *rightBuf, Bit32u length) { + if (floatMode) { + synth->printDebug("Partial: Invalid call to produceOutput()! Renderer = %d\n", synth->getSelectedRendererType()); + return false; + } + return doProduceOutput(leftBuf, rightBuf, length, static_cast<LA32IntPartialPair *>(la32Pair)); +} + +bool Partial::produceOutput(FloatSample *leftBuf, FloatSample *rightBuf, Bit32u length) { + if (!floatMode) { + synth->printDebug("Partial: Invalid call to produceOutput()! Renderer = %d\n", synth->getSelectedRendererType()); + return false; + } + return doProduceOutput(leftBuf, rightBuf, length, static_cast<LA32FloatPartialPair *>(la32Pair)); +} + bool Partial::shouldReverb() { if (!isActive()) { return false; diff --git a/audio/softsynth/mt32/Partial.h b/audio/softsynth/mt32/Partial.h index 187665de51..e297710f78 100644 --- a/audio/softsynth/mt32/Partial.h +++ b/audio/softsynth/mt32/Partial.h @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 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 @@ -24,6 +24,7 @@ #include "Structures.h" #include "LA32Ramp.h" #include "LA32WaveGenerator.h" +#include "LA32FloatWaveGenerator.h" namespace MT32Emu { @@ -39,7 +40,7 @@ struct ControlROMPCMStruct; class Partial { private: Synth *synth; - const int debugPartialNum; // Only used for debugging + const int partialIndex; // Index of this Partial in the global partial table // Number of the sample currently being rendered by produceOutput(), or 0 if no run is in progress // This is only kept available for debugging purposes. Bit32u sampleNum; @@ -72,7 +73,8 @@ private: LA32Ramp cutoffModifierRamp; // TODO: This should be owned by PartialPair - LA32PartialPair la32Pair; + LA32PartialPair *la32Pair; + const bool floatMode; const PatchCache *patchCache; PatchCache cachebackup; @@ -80,6 +82,14 @@ private: Bit32u getAmpValue(); Bit32u getCutoffValue(); + template <class Sample, class LA32PairImpl> + bool doProduceOutput(Sample *leftBuf, Sample *rightBuf, Bit32u length, LA32PairImpl *la32PairImpl); + bool canProduceOutput(); + template <class LA32PairImpl> + bool generateNextSample(LA32PairImpl *la32PairImpl); + void produceAndMixSample(IntSample *&leftBuf, IntSample *&rightBuf, LA32IntPartialPair *la32IntPair); + void produceAndMixSample(FloatSample *&leftBuf, FloatSample *&rightBuf, LA32FloatPartialPair *la32FloatPair); + public: bool alreadyOutputed; @@ -98,6 +108,7 @@ public: void startAbort(); void startDecayAll(); bool shouldReverb(); + bool isRingModulatingNoMix() const; bool hasRingModulatingSlave() const; bool isRingModulatingSlave() const; bool isPCM() const; @@ -108,9 +119,10 @@ public: void backupCache(const PatchCache &cache); // Returns true only if data written to buffer - // This function (unlike the one below it) returns processed stereo samples + // These functions produce processed stereo samples // made from combining this single partial with its pair, if it has one. - bool produceOutput(Sample *leftBuf, Sample *rightBuf, Bit32u length); + bool produceOutput(IntSample *leftBuf, IntSample *rightBuf, Bit32u length); + bool produceOutput(FloatSample *leftBuf, FloatSample *rightBuf, Bit32u length); }; // class Partial } // namespace MT32Emu diff --git a/audio/softsynth/mt32/PartialManager.cpp b/audio/softsynth/mt32/PartialManager.cpp index 7c702f7582..aeba5ce8e0 100644 --- a/audio/softsynth/mt32/PartialManager.cpp +++ b/audio/softsynth/mt32/PartialManager.cpp @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 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 @@ -31,11 +31,14 @@ namespace MT32Emu { PartialManager::PartialManager(Synth *useSynth, Part **useParts) { synth = useSynth; parts = useParts; - partialTable = new Partial *[synth->getPartialCount()]; + inactivePartialCount = synth->getPartialCount(); + partialTable = new Partial *[inactivePartialCount]; + inactivePartials = new int[inactivePartialCount]; freePolys = new Poly *[synth->getPartialCount()]; firstFreePolyIndex = 0; for (unsigned int i = 0; i < synth->getPartialCount(); i++) { partialTable[i] = new Partial(synth, i); + inactivePartials[i] = inactivePartialCount - i - 1; freePolys[i] = new Poly(); } } @@ -46,6 +49,7 @@ PartialManager::~PartialManager(void) { if (freePolys[i] != NULL) delete freePolys[i]; } delete[] partialTable; + delete[] inactivePartials; delete[] freePolys; } @@ -59,7 +63,11 @@ bool PartialManager::shouldReverb(int i) { return partialTable[i]->shouldReverb(); } -bool PartialManager::produceOutput(int i, Sample *leftBuf, Sample *rightBuf, Bit32u bufferLength) { +bool PartialManager::produceOutput(int i, IntSample *leftBuf, IntSample *rightBuf, Bit32u bufferLength) { + return partialTable[i]->produceOutput(leftBuf, rightBuf, bufferLength); +} + +bool PartialManager::produceOutput(int i, FloatSample *leftBuf, FloatSample *rightBuf, Bit32u bufferLength) { return partialTable[i]->produceOutput(leftBuf, rightBuf, bufferLength); } @@ -79,29 +87,21 @@ unsigned int PartialManager::setReserve(Bit8u *rset) { } Partial *PartialManager::allocPartial(int partNum) { - Partial *outPartial = NULL; - - // Get the first inactive partial - for (unsigned int partialNum = 0; partialNum < synth->getPartialCount(); partialNum++) { - if (!partialTable[partialNum]->isActive()) { - outPartial = partialTable[partialNum]; - break; - } + if (inactivePartialCount > 0) { + Partial *partial = partialTable[inactivePartials[--inactivePartialCount]]; + partial->activate(partNum); + return partial; } - if (outPartial != NULL) { - outPartial->activate(partNum); + synth->printDebug("PartialManager Error: No inactive partials to allocate for part %d, current partial state:\n", partNum); + for (Bit32u i = 0; i < synth->getPartialCount(); i++) { + const Partial *partial = partialTable[i]; + synth->printDebug("[Partial %d]: activation=%d, owner part=%d\n", i, partial->isActive(), partial->getOwnerPart()); } - return outPartial; + return NULL; } -unsigned int PartialManager::getFreePartialCount(void) { - int count = 0; - for (unsigned int i = 0; i < synth->getPartialCount(); i++) { - if (!partialTable[i]->isActive()) { - count++; - } - } - return count; +unsigned int PartialManager::getFreePartialCount() { + return inactivePartialCount; } // This function is solely used to gather data for debug output at the moment. @@ -275,7 +275,7 @@ Poly *PartialManager::assignPolyToPart(Part *part) { void PartialManager::polyFreed(Poly *poly) { if (0 == firstFreePolyIndex) { - synth->printDebug("Cannot return freed poly, currently active polys:\n"); + synth->printDebug("PartialManager Error: 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; @@ -285,10 +285,23 @@ void PartialManager::polyFreed(Poly *poly) { } synth->printDebug("Part: %i, active poly count: %i\n", partNum, polyCount); } + } else { + firstFreePolyIndex--; + freePolys[firstFreePolyIndex] = poly; } poly->setPart(NULL); - firstFreePolyIndex--; - freePolys[firstFreePolyIndex] = poly; +} + +void PartialManager::partialDeactivated(int partialIndex) { + if (inactivePartialCount < synth->getPartialCount()) { + inactivePartials[inactivePartialCount++] = partialIndex; + return; + } + synth->printDebug("PartialManager Error: Cannot return deactivated partial %d, current partial state:\n", partialIndex); + for (Bit32u i = 0; i < synth->getPartialCount(); i++) { + const Partial *partial = partialTable[i]; + synth->printDebug("[Partial %d]: activation=%d, owner part=%d\n", i, partial->isActive(), partial->getOwnerPart()); + } } } // namespace MT32Emu diff --git a/audio/softsynth/mt32/PartialManager.h b/audio/softsynth/mt32/PartialManager.h index b2908a5c21..deded8f150 100644 --- a/audio/softsynth/mt32/PartialManager.h +++ b/audio/softsynth/mt32/PartialManager.h @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 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 @@ -37,6 +37,8 @@ private: Partial **partialTable; Bit8u numReservedPartialsForPart[9]; Bit32u firstFreePolyIndex; + int *inactivePartials; // Holds indices of inactive Partials in the Partial table + Bit32u inactivePartialCount; bool abortFirstReleasingPolyWhereReserveExceeded(int minPart); bool abortFirstPolyPreferHeldWhereReserveExceeded(int minPart); @@ -45,17 +47,19 @@ public: PartialManager(Synth *synth, Part **parts); ~PartialManager(); Partial *allocPartial(int partNum); - unsigned int getFreePartialCount(void); + unsigned int getFreePartialCount(); void getPerPartPartialUsage(unsigned int perPartPartialUsage[9]); bool freePartials(unsigned int needed, int partNum); unsigned int setReserve(Bit8u *rset); void deactivateAll(); - bool produceOutput(int i, Sample *leftBuf, Sample *rightBuf, Bit32u bufferLength); + bool produceOutput(int i, IntSample *leftBuf, IntSample *rightBuf, Bit32u bufferLength); + bool produceOutput(int i, FloatSample *leftBuf, FloatSample *rightBuf, Bit32u bufferLength); bool shouldReverb(int i); void clearAlreadyOutputed(); const Partial *getPartial(unsigned int partialNum) const; Poly *assignPolyToPart(Part *part); void polyFreed(Poly *poly); + void partialDeactivated(int partialIndex); }; // class PartialManager } // namespace MT32Emu diff --git a/audio/softsynth/mt32/Poly.cpp b/audio/softsynth/mt32/Poly.cpp index 9a3948cf11..44b8d24460 100644 --- a/audio/softsynth/mt32/Poly.cpp +++ b/audio/softsynth/mt32/Poly.cpp @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 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 diff --git a/audio/softsynth/mt32/Poly.h b/audio/softsynth/mt32/Poly.h index 4b283231c8..b2d4eceaf9 100644 --- a/audio/softsynth/mt32/Poly.h +++ b/audio/softsynth/mt32/Poly.h @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 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 diff --git a/audio/softsynth/mt32/ROMInfo.cpp b/audio/softsynth/mt32/ROMInfo.cpp index ce78e693aa..8c813a4e69 100644 --- a/audio/softsynth/mt32/ROMInfo.cpp +++ b/audio/softsynth/mt32/ROMInfo.cpp @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 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 @@ -31,6 +31,7 @@ static const ROMInfo *getKnownROMInfoFromList(Bit32u index) { static const ROMInfo CTRL_MT32_V1_07 = {65536, "b083518fffb7f66b03c23b7eb4f868e62dc5a987", ROMInfo::Control, "ctrl_mt32_1_07", "MT-32 Control v1.07", ROMInfo::Full, NULL}; static const ROMInfo CTRL_MT32_BLUER = {65536, "7b8c2a5ddb42fd0732e2f22b3340dcf5360edf92", ROMInfo::Control, "ctrl_mt32_bluer", "MT-32 Control BlueRidge", ROMInfo::Full, NULL}; + static const ROMInfo CTRL_MT32_V2_04 = {131072, "2c16432b6c73dd2a3947cba950a0f4c19d6180eb", ROMInfo::Control, "ctrl_mt32_2_04", "MT-32 Control v2.04", ROMInfo::Full, NULL}; static const ROMInfo CTRL_CM32L_V1_00 = {65536, "73683d585cd6948cc19547942ca0e14a0319456d", ROMInfo::Control, "ctrl_cm32l_1_00", "CM-32L/LAPC-I Control v1.00", ROMInfo::Full, NULL}; static const ROMInfo CTRL_CM32L_V1_02 = {65536, "a439fbb390da38cada95a7cbb1d6ca199cd66ef8", ROMInfo::Control, "ctrl_cm32l_1_02", "CM-32L/LAPC-I Control v1.02", ROMInfo::Full, NULL}; @@ -43,6 +44,7 @@ static const ROMInfo *getKnownROMInfoFromList(Bit32u index) { &CTRL_MT32_V1_06, &CTRL_MT32_V1_07, &CTRL_MT32_BLUER, + &CTRL_MT32_V2_04, &CTRL_CM32L_V1_00, &CTRL_CM32L_V1_02, &PCM_MT32, diff --git a/audio/softsynth/mt32/ROMInfo.h b/audio/softsynth/mt32/ROMInfo.h index 8a5ad141b6..cd4a1c5ac0 100644 --- a/audio/softsynth/mt32/ROMInfo.h +++ b/audio/softsynth/mt32/ROMInfo.h @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 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 diff --git a/audio/softsynth/mt32/SampleRateConverter.cpp b/audio/softsynth/mt32/SampleRateConverter.cpp index 70f860f395..73f7963e21 100644 --- a/audio/softsynth/mt32/SampleRateConverter.cpp +++ b/audio/softsynth/mt32/SampleRateConverter.cpp @@ -14,13 +14,15 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ +#include <cstddef> + #include "SampleRateConverter.h" #if MT32EMU_WITH_LIBSOXR_RESAMPLER #include "srchelper/SoxrAdapter.h" #elif MT32EMU_WITH_LIBSAMPLERATE_RESAMPLER #include "srchelper/SamplerateAdapter.h" -#else +#elif MT32EMU_WITH_INTERNAL_RESAMPLER #include "srchelper/InternalResampler.h" #endif @@ -28,13 +30,16 @@ using namespace MT32Emu; -static inline void *createDelegate(Synth &synth, double targetSampleRate, SampleRateConverter::Quality quality) { +static inline void *createDelegate(Synth &synth, double targetSampleRate, SamplerateConversionQuality quality) { #if MT32EMU_WITH_LIBSOXR_RESAMPLER return new SoxrAdapter(synth, targetSampleRate, quality); #elif MT32EMU_WITH_LIBSAMPLERATE_RESAMPLER return new SamplerateAdapter(synth, targetSampleRate, quality); -#else +#elif MT32EMU_WITH_INTERNAL_RESAMPLER return new InternalResampler(synth, targetSampleRate, quality); +#else + (void)synth, (void)targetSampleRate, (void)quality; + return NULL; #endif } @@ -47,34 +52,58 @@ AnalogOutputMode SampleRateConverter::getBestAnalogOutputMode(double targetSampl return AnalogOutputMode_COARSE; } -SampleRateConverter::SampleRateConverter(Synth &useSynth, double targetSampleRate, Quality useQuality) : +double SampleRateConverter::getSupportedOutputSampleRate(double desiredSampleRate) { +#if MT32EMU_WITH_LIBSOXR_RESAMPLER || MT32EMU_WITH_LIBSAMPLERATE_RESAMPLER || MT32EMU_WITH_INTERNAL_RESAMPLER + return desiredSampleRate > 0 ? desiredSampleRate : 0; +#else + (void)desiredSampleRate; + return 0; +#endif +} + +SampleRateConverter::SampleRateConverter(Synth &useSynth, double targetSampleRate, SamplerateConversionQuality useQuality) : synthInternalToTargetSampleRateRatio(SAMPLE_RATE / targetSampleRate), - srcDelegate(createDelegate(useSynth, targetSampleRate, useQuality)) + useSynthDelegate(useSynth.getStereoOutputSampleRate() == targetSampleRate), + srcDelegate(useSynthDelegate ? &useSynth : createDelegate(useSynth, targetSampleRate, useQuality)) {} SampleRateConverter::~SampleRateConverter() { + if (!useSynthDelegate) { #if MT32EMU_WITH_LIBSOXR_RESAMPLER - delete static_cast<SoxrAdapter *>(srcDelegate); + delete static_cast<SoxrAdapter *>(srcDelegate); #elif MT32EMU_WITH_LIBSAMPLERATE_RESAMPLER - delete static_cast<SamplerateAdapter *>(srcDelegate); -#else - delete static_cast<InternalResampler *>(srcDelegate); + delete static_cast<SamplerateAdapter *>(srcDelegate); +#elif MT32EMU_WITH_INTERNAL_RESAMPLER + delete static_cast<InternalResampler *>(srcDelegate); #endif + } } void SampleRateConverter::getOutputSamples(float *buffer, unsigned int length) { + if (useSynthDelegate) { + static_cast<Synth *>(srcDelegate)->render(buffer, length); + return; + } + #if MT32EMU_WITH_LIBSOXR_RESAMPLER static_cast<SoxrAdapter *>(srcDelegate)->getOutputSamples(buffer, length); #elif MT32EMU_WITH_LIBSAMPLERATE_RESAMPLER static_cast<SamplerateAdapter *>(srcDelegate)->getOutputSamples(buffer, length); -#else +#elif MT32EMU_WITH_INTERNAL_RESAMPLER static_cast<InternalResampler *>(srcDelegate)->getOutputSamples(buffer, length); +#else + Synth::muteSampleBuffer(buffer, length); #endif } void SampleRateConverter::getOutputSamples(Bit16s *outBuffer, unsigned int length) { static const unsigned int CHANNEL_COUNT = 2; + if (useSynthDelegate) { + static_cast<Synth *>(srcDelegate)->render(outBuffer, length); + return; + } + float floatBuffer[CHANNEL_COUNT * MAX_SAMPLES_PER_RUN]; while (length > 0) { const unsigned int size = MAX_SAMPLES_PER_RUN < length ? MAX_SAMPLES_PER_RUN : length; diff --git a/audio/softsynth/mt32/SampleRateConverter.h b/audio/softsynth/mt32/SampleRateConverter.h index 24bce0891b..e8950111f0 100644 --- a/audio/softsynth/mt32/SampleRateConverter.h +++ b/audio/softsynth/mt32/SampleRateConverter.h @@ -14,8 +14,8 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef SAMPLE_RATE_CONVERTER_H -#define SAMPLE_RATE_CONVERTER_H +#ifndef MT32EMU_SAMPLE_RATE_CONVERTER_H +#define MT32EMU_SAMPLE_RATE_CONVERTER_H #include "globals.h" #include "Types.h" @@ -33,21 +33,17 @@ class Synth; */ class MT32EMU_EXPORT SampleRateConverter { public: - enum Quality { - // Use this only when the speed is more important than the audio quality. - FASTEST, - FAST, - GOOD, - BEST - }; - // Returns the value of AnalogOutputMode for which the output signal may retain its full frequency spectrum // at the sample rate specified by the targetSampleRate argument. static AnalogOutputMode getBestAnalogOutputMode(double targetSampleRate); + // Returns the sample rate supported by the sample rate conversion implementation currently in effect + // that is closest to the one specified by the desiredSampleRate argument. + static double getSupportedOutputSampleRate(double desiredSampleRate); + // Creates a SampleRateConverter instance that converts output signal from the synth to the given sample rate // with the specified conversion quality. - SampleRateConverter(Synth &synth, double targetSampleRate, Quality quality); + SampleRateConverter(Synth &synth, double targetSampleRate, SamplerateConversionQuality quality); ~SampleRateConverter(); // Fills the provided output buffer with the results of the sample rate conversion. @@ -70,9 +66,10 @@ public: private: const double synthInternalToTargetSampleRateRatio; + const bool useSynthDelegate; void * const srcDelegate; }; // class SampleRateConverter } // namespace MT32Emu -#endif // SAMPLE_RATE_CONVERTER_H +#endif // MT32EMU_SAMPLE_RATE_CONVERTER_H diff --git a/audio/softsynth/mt32/Structures.h b/audio/softsynth/mt32/Structures.h index 6dcb9c86a9..d116aaeb46 100644 --- a/audio/softsynth/mt32/Structures.h +++ b/audio/softsynth/mt32/Structures.h @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 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 @@ -184,7 +184,13 @@ struct SoundGroup { #endif struct ControlROMFeatureSet { + unsigned int quirkBasePitchOverflow : 1; unsigned int quirkPitchEnvelopeOverflow : 1; + unsigned int quirkRingModulationNoMix : 1; + unsigned int quirkTVAZeroEnvLevels : 1; + unsigned int quirkPanMult : 1; + unsigned int quirkKeyShift : 1; + unsigned int quirkTVFBaseCutoffLimit : 1; // Features below don't actually depend on control ROM version, which is used to identify hardware model unsigned int defaultReverbMT32Compatible : 1; diff --git a/audio/softsynth/mt32/Synth.cpp b/audio/softsynth/mt32/Synth.cpp index 3a478b5b62..62810ba3e6 100644 --- a/audio/softsynth/mt32/Synth.cpp +++ b/audio/softsynth/mt32/Synth.cpp @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 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 @@ -32,25 +32,50 @@ #include "ROMInfo.h" #include "TVA.h" +#if MT32EMU_MONITOR_SYSEX > 0 +#include "mmath.h" +#endif + namespace MT32Emu { // MIDI interface data transfer rate in samples. Used to simulate the transfer delay. static const double MIDI_DATA_TRANSFER_RATE = double(SAMPLE_RATE) / 31250.0 * 8.0; // FIXME: there should be more specific feature sets for various MT-32 control ROM versions -static const ControlROMFeatureSet OLD_MT32_COMPATIBLE = { true, true, true }; -static const ControlROMFeatureSet CM32L_COMPATIBLE = { false, false, false }; +static const ControlROMFeatureSet OLD_MT32_COMPATIBLE = { + true, // quirkBasePitchOverflow + true, // quirkPitchEnvelopeOverflow + true, // quirkRingModulationNoMix + true, // quirkTVAZeroEnvLevels + true, // quirkPanMult + true, // quirkKeyShift + true, // quirkTVFBaseCutoffLimit + true, // defaultReverbMT32Compatible + true // oldMT32AnalogLPF +}; +static const ControlROMFeatureSet CM32L_COMPATIBLE = { + false, // quirkBasePitchOverflow + false, // quirkPitchEnvelopeOverflow + false, // quirkRingModulationNoMix + false, // quirkTVAZeroEnvLevels + false, // quirkPanMult + false, // quirkKeyShift + false, // quirkTVFBaseCutoffLimit + false, // defaultReverbMT32Compatible + false // oldMT32AnalogLPF +}; -static const ControlROMMap ControlROMMaps[7] = { +static const ControlROMMap ControlROMMaps[8] = { // ID Features PCMmap PCMc tmbrA tmbrAO, tmbrAC tmbrB tmbrBO tmbrBC tmbrR trC rhythm rhyC rsrv panpot prog rhyMax patMax sysMax timMax sndGrp sGC { "ctrl_mt32_1_04", OLD_MT32_COMPATIBLE, 0x3000, 128, 0x8000, 0x0000, false, 0xC000, 0x4000, false, 0x3200, 30, 0x73A6, 85, 0x57C7, 0x57E2, 0x57D0, 0x5252, 0x525E, 0x526E, 0x520A, 0x7064, 19 }, { "ctrl_mt32_1_05", OLD_MT32_COMPATIBLE, 0x3000, 128, 0x8000, 0x0000, false, 0xC000, 0x4000, false, 0x3200, 30, 0x7414, 85, 0x57C7, 0x57E2, 0x57D0, 0x5252, 0x525E, 0x526E, 0x520A, 0x70CA, 19 }, { "ctrl_mt32_1_06", OLD_MT32_COMPATIBLE, 0x3000, 128, 0x8000, 0x0000, false, 0xC000, 0x4000, false, 0x3200, 30, 0x7414, 85, 0x57D9, 0x57F4, 0x57E2, 0x5264, 0x5270, 0x5280, 0x521C, 0x70CA, 19 }, { "ctrl_mt32_1_07", OLD_MT32_COMPATIBLE, 0x3000, 128, 0x8000, 0x0000, false, 0xC000, 0x4000, false, 0x3200, 30, 0x73fe, 85, 0x57B1, 0x57CC, 0x57BA, 0x523C, 0x5248, 0x5258, 0x51F4, 0x70B0, 19 }, // MT-32 revision 1 {"ctrl_mt32_bluer", OLD_MT32_COMPATIBLE, 0x3000, 128, 0x8000, 0x0000, false, 0xC000, 0x4000, false, 0x3200, 30, 0x741C, 85, 0x57E5, 0x5800, 0x57EE, 0x5270, 0x527C, 0x528C, 0x5228, 0x70CE, 19 }, // MT-32 Blue Ridge mod + {"ctrl_mt32_2_04", CM32L_COMPATIBLE, 0x8100, 128, 0x8000, 0x8000, true, 0x8080, 0x8000, true, 0x8500, 30, 0x8580, 85, 0x4F5D, 0x4F78, 0x4F66, 0x4899, 0x489D, 0x48B6, 0x48CD, 0x5A58, 19 }, {"ctrl_cm32l_1_00", CM32L_COMPATIBLE, 0x8100, 256, 0x8000, 0x8000, true, 0x8080, 0x8000, true, 0x8500, 64, 0x8580, 85, 0x4F65, 0x4F80, 0x4F6E, 0x48A1, 0x48A5, 0x48BE, 0x48D5, 0x5A6C, 19 }, {"ctrl_cm32l_1_02", CM32L_COMPATIBLE, 0x8100, 256, 0x8000, 0x8000, true, 0x8080, 0x8000, true, 0x8500, 64, 0x8580, 85, 0x4F93, 0x4FAE, 0x4F9C, 0x48CB, 0x48CF, 0x48E8, 0x48FF, 0x5A96, 19 } // CM-32L - // (Note that all but CM-32L ROM actually have 86 entries for rhythmTemp) + // (Note that old MT-32 ROMs actually have 86 entries for rhythmTemp) }; static const PartialState PARTIAL_PHASE_TO_STATE[8] = { @@ -63,80 +88,120 @@ static inline PartialState getPartialState(PartialManager *partialManager, unsig return partial->isActive() ? PARTIAL_PHASE_TO_STATE[partial->getTVA()->getPhase()] : PartialState_INACTIVE; } -class SampleFormatConverter { +template <class I, class O> +static inline void convertSampleFormat(const I *inBuffer, O *outBuffer, const Bit32u len) { + if (inBuffer == NULL || outBuffer == NULL) return; + + const I *inBufferEnd = inBuffer + len; + while (inBuffer < inBufferEnd) { + *(outBuffer++) = Synth::convertSample(*(inBuffer++)); + } +} + +class Renderer { protected: -#if MT32EMU_USE_FLOAT_SAMPLES - Bit16s *outBuffer; -#else - float *outBuffer; -#endif + Synth &synth; -public: - Sample *sampleBuffer; + void printDebug(const char *msg) const { + synth.printDebug("%s", msg); + } - SampleFormatConverter(Sample *buffer) : outBuffer(NULL), sampleBuffer(buffer) {} + bool isActivated() const { + return synth.activated; + } - inline bool isConversionNeeded() { - return outBuffer != NULL; + bool isAbortingPoly() const { + return synth.isAbortingPoly(); } - inline void convert(Bit32u len) { - if (sampleBuffer == NULL) return; - if (outBuffer == NULL) { - sampleBuffer += len; - return; - } - Sample *inBuffer = sampleBuffer; - while (len--) { - *(outBuffer++) = Synth::convertSample(*(inBuffer++)); - } + Analog &getAnalog() const { + return *synth.analog; } - inline void addSilence(Bit32u len) { - if (outBuffer != NULL) { - Synth::muteSampleBuffer(outBuffer, len); - outBuffer += len; - } else if (sampleBuffer != NULL) { - Synth::muteSampleBuffer(sampleBuffer, len); - sampleBuffer += len; - } + MidiEventQueue &getMidiQueue() { + return *synth.midiQueue; } -}; -template <int BUFFER_SIZE_MULTIPLIER = 1> -class BufferedSampleFormatConverter : public SampleFormatConverter { - Sample renderingBuffer[BUFFER_SIZE_MULTIPLIER * MAX_SAMPLES_PER_RUN]; + PartialManager &getPartialManager() { + return *synth.partialManager; + } -public: -#if MT32EMU_USE_FLOAT_SAMPLES - BufferedSampleFormatConverter(Bit16s *buffer) -#else - BufferedSampleFormatConverter(float *buffer) -#endif - : SampleFormatConverter(renderingBuffer) - { - outBuffer = buffer; - if (buffer == NULL) sampleBuffer = NULL; + BReverbModel &getReverbModel() { + return *synth.reverbModel; } -}; -class Renderer { - Synth &synth; + Bit32u getRenderedSampleCount() { + return synth.renderedSampleCount; + } + + void incRenderedSampleCount(const Bit32u count) { + synth.renderedSampleCount += count; + } + +public: + Renderer(Synth &useSynth) : synth(useSynth) {} + + virtual ~Renderer() {} + + virtual void render(IntSample *stereoStream, Bit32u len) = 0; + virtual void render(FloatSample *stereoStream, Bit32u len) = 0; + virtual void renderStreams(const DACOutputStreams<IntSample> &streams, Bit32u len) = 0; + virtual void renderStreams(const DACOutputStreams<FloatSample> &streams, Bit32u len) = 0; +}; +template <class Sample> +class RendererImpl : public Renderer { // These buffers are used for building the output streams as they are found at the DAC entrance. // The output is mixed down to stereo interleaved further in the analog circuitry emulation. Sample tmpNonReverbLeft[MAX_SAMPLES_PER_RUN], tmpNonReverbRight[MAX_SAMPLES_PER_RUN]; Sample tmpReverbDryLeft[MAX_SAMPLES_PER_RUN], tmpReverbDryRight[MAX_SAMPLES_PER_RUN]; Sample tmpReverbWetLeft[MAX_SAMPLES_PER_RUN], tmpReverbWetRight[MAX_SAMPLES_PER_RUN]; -public: - Renderer(Synth &useSynth) : synth(useSynth) {} + const DACOutputStreams<Sample> tmpBuffers; + DACOutputStreams<Sample> createTmpBuffers() { + DACOutputStreams<Sample> buffers = { + tmpNonReverbLeft, tmpNonReverbRight, + tmpReverbDryLeft, tmpReverbDryRight, + tmpReverbWetLeft, tmpReverbWetRight + }; + return buffers; + } - void render(SampleFormatConverter &converter, Bit32u len); - void renderStreams(SampleFormatConverter &nonReverbLeft, SampleFormatConverter &nonReverbRight, SampleFormatConverter &reverbDryLeft, SampleFormatConverter &reverbDryRight, SampleFormatConverter &reverbWetLeft, SampleFormatConverter &reverbWetRight, Bit32u len); +public: + RendererImpl(Synth &useSynth) : + Renderer(useSynth), + tmpBuffers(createTmpBuffers()) + {} + + void render(IntSample *stereoStream, Bit32u len); + void render(FloatSample *stereoStream, Bit32u len); + void renderStreams(const DACOutputStreams<IntSample> &streams, Bit32u len); + void renderStreams(const DACOutputStreams<FloatSample> &streams, Bit32u len); + + template <class O> + void doRenderAndConvert(O *stereoStream, Bit32u len); + void doRender(Sample *stereoStream, Bit32u len); + + template <class O> + void doRenderAndConvertStreams(const DACOutputStreams<O> &streams, Bit32u len); + void doRenderStreams(const DACOutputStreams<Sample> &streams, Bit32u len); void produceLA32Output(Sample *buffer, Bit32u len); void convertSamplesToOutput(Sample *buffer, Bit32u len); - void doRenderStreams(DACOutputStreams<Sample> &streams, Bit32u len); + void produceStreams(const DACOutputStreams<Sample> &streams, Bit32u len); +}; + +class Extensions { +public: + RendererType selectedRendererType; + Bit32s masterTunePitchDelta; + bool niceAmpRamp; + + // Here we keep the reverse mapping of assigned parts per MIDI channel. + // NOTE: value above 8 means that the channel is not assigned + Bit8u chantable[16][9]; + + // This stores the index of Part in chantable that failed to play and required partial abortion. + Bit32u abortingPartIx; }; Bit32u Synth::getLibraryVersionInt() { @@ -161,7 +226,11 @@ Bit32u Synth::getStereoOutputSampleRate(AnalogOutputMode analogOutputMode) { return SAMPLE_RATES[analogOutputMode]; } -Synth::Synth(ReportHandler *useReportHandler) : mt32ram(*new MemParams), mt32default(*new MemParams), renderer(*new Renderer(*this)) { +Synth::Synth(ReportHandler *useReportHandler) : + mt32ram(*new MemParams), + mt32default(*new MemParams), + extensions(*new Extensions) +{ opened = false; reverbOverridden = false; partialCount = DEFAULT_MAX_PARTIALS; @@ -181,11 +250,14 @@ Synth::Synth(ReportHandler *useReportHandler) : mt32ram(*new MemParams), mt32def } reverbModel = NULL; analog = NULL; + renderer = NULL; setDACInputMode(DACInputMode_NICE); setMIDIDelayMode(MIDIDelayMode_DELAY_SHORT_MESSAGES_ONLY); setOutputGain(1.0f); setReverbOutputGain(1.0f); setReversedStereoEnabled(false); + setNiceAmpRampEnabled(true); + selectRendererType(RendererType_BIT16S); patchTempMemoryRegion = NULL; rhythmTempMemoryRegion = NULL; @@ -205,8 +277,6 @@ Synth::Synth(ReportHandler *useReportHandler) : mt32ram(*new MemParams), mt32def lastReceivedMIDIEventTimestamp = 0; memset(parts, 0, sizeof(parts)); renderedSampleCount = 0; - - reserved = NULL; } Synth::~Synth() { @@ -216,7 +286,7 @@ Synth::~Synth() { } delete &mt32ram; delete &mt32default; - delete &renderer; + delete &extensions; } void ReportHandler::showLCDMessage(const char *data) { @@ -309,12 +379,6 @@ bool Synth::isDefaultReverbMT32Compatible() const { } void Synth::setDACInputMode(DACInputMode mode) { -#if MT32EMU_USE_FLOAT_SAMPLES - // We aren't emulating these in float mode, so better to inform the invoker - if ((mode == DACInputMode_GENERATION1) || (mode == DACInputMode_GENERATION2)) { - mode = DACInputMode_NICE; - } -#endif dacInputMode = mode; } @@ -358,6 +422,14 @@ bool Synth::isReversedStereoEnabled() const { return reversedStereoEnabled; } +void Synth::setNiceAmpRampEnabled(bool enabled) { + extensions.niceAmpRamp = enabled; +} + +bool Synth::isNiceAmpRampEnabled() const { + return extensions.niceAmpRamp; +} + bool Synth::loadControlROM(const ROMImage &controlROMImage) { File *file = controlROMImage.getFile(); const ROMInfo *controlROMInfo = controlROMImage.getROMInfo(); @@ -500,10 +572,10 @@ bool Synth::initTimbres(Bit16u mapAddress, Bit16u offset, Bit16u count, Bit16u s } void Synth::initReverbModels(bool mt32CompatibleMode) { - reverbModels[REVERB_MODE_ROOM] = new BReverbModel(REVERB_MODE_ROOM, mt32CompatibleMode); - reverbModels[REVERB_MODE_HALL] = new BReverbModel(REVERB_MODE_HALL, mt32CompatibleMode); - reverbModels[REVERB_MODE_PLATE] = new BReverbModel(REVERB_MODE_PLATE, mt32CompatibleMode); - reverbModels[REVERB_MODE_TAP_DELAY] = new BReverbModel(REVERB_MODE_TAP_DELAY, mt32CompatibleMode); + reverbModels[REVERB_MODE_ROOM] = BReverbModel::createBReverbModel(REVERB_MODE_ROOM, mt32CompatibleMode, getSelectedRendererType()); + reverbModels[REVERB_MODE_HALL] = BReverbModel::createBReverbModel(REVERB_MODE_HALL, mt32CompatibleMode, getSelectedRendererType()); + reverbModels[REVERB_MODE_PLATE] = BReverbModel::createBReverbModel(REVERB_MODE_PLATE, mt32CompatibleMode, getSelectedRendererType()); + reverbModels[REVERB_MODE_TAP_DELAY] = BReverbModel::createBReverbModel(REVERB_MODE_TAP_DELAY, mt32CompatibleMode, getSelectedRendererType()); #if !MT32EMU_REDUCE_REVERB_MEMORY for (int i = REVERB_MODE_ROOM; i <= REVERB_MODE_TAP_DELAY; i++) { reverbModels[i]->open(); @@ -529,6 +601,7 @@ bool Synth::open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage, B } partialCount = usePartialCount; abortingPoly = NULL; + extensions.abortingPartIx = 0; // This is to help detect bugs memset(&mt32ram, '?', sizeof(mt32ram)); @@ -650,6 +723,7 @@ bool Synth::open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage, B bool oldReverbOverridden = reverbOverridden; reverbOverridden = false; refreshSystem(); + resetMasterTunePitchDelta(); reverbOverridden = oldReverbOverridden; char(*writableSoundGroupNames)[9] = new char[controlROMMap->soundGroupsCount][9]; @@ -687,10 +761,33 @@ bool Synth::open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage, B midiQueue = new MidiEventQueue(); - analog = new Analog(analogOutputMode, controlROMFeatures->oldMT32AnalogLPF); + analog = Analog::createAnalog(analogOutputMode, controlROMFeatures->oldMT32AnalogLPF, getSelectedRendererType()); +#if MT32EMU_MONITOR_INIT + static const char *ANALOG_OUTPUT_MODES[] = { "Digital only", "Coarse", "Accurate", "Oversampled2x" }; + printDebug("Using Analog output mode %s", ANALOG_OUTPUT_MODES[analogOutputMode]); +#endif setOutputGain(outputGain); setReverbOutputGain(reverbOutputGain); + switch (getSelectedRendererType()) { + case RendererType_BIT16S: + renderer = new RendererImpl<IntSample>(*this); +#if MT32EMU_MONITOR_INIT + printDebug("Using integer 16-bit samples in renderer and wave generator"); +#endif + break; + case RendererType_FLOAT: + renderer = new RendererImpl<FloatSample>(*this); +#if MT32EMU_MONITOR_INIT + printDebug("Using float 32-bit samples in renderer and wave generator"); +#endif + break; + default: + printDebug("Synth: Unknown renderer type %i\n", getSelectedRendererType()); + dispose(); + return false; + } + opened = true; activated = false; @@ -706,6 +803,9 @@ void Synth::dispose() { delete midiQueue; midiQueue = NULL; + delete renderer; + renderer = NULL; + delete analog; analog = NULL; @@ -810,13 +910,17 @@ Bit32u Synth::addMIDIInterfaceDelay(Bit32u len, Bit32u timestamp) { return timestamp; } +Bit32u Synth::getInternalRenderedSampleCount() const { + return renderedSampleCount; +} + bool Synth::playMsg(Bit32u msg) { return playMsg(msg, renderedSampleCount); } bool Synth::playMsg(Bit32u msg, Bit32u timestamp) { if ((msg & 0xF8) == 0xF8) { - reportHandler->onMIDISystemRealtime(Bit8u(msg)); + reportHandler->onMIDISystemRealtime(Bit8u(msg & 0xFF)); return true; } if (midiQueue == NULL) return false; @@ -859,14 +963,24 @@ void Synth::playMsgNow(Bit32u msg) { //printDebug("Playing chan %d, code 0x%01x note: 0x%02x", chan, code, note); - Bit8u part = chantable[chan]; - if (part > 8) { + Bit8u *chanParts = extensions.chantable[chan]; + if (*chanParts > 8) { #if MT32EMU_MONITOR_MIDI > 0 printDebug("Play msg on unreg chan %d (%d): code=0x%01x, vel=%d", chan, part, code, velocity); #endif return; } - playMsgOnPart(part, code, note, velocity); + for (Bit32u i = extensions.abortingPartIx; i <= 8; i++) { + const Bit32u partNum = chanParts[i]; + if (partNum > 8) break; + playMsgOnPart(partNum, code, note, velocity); + if (isAbortingPoly()) { + extensions.abortingPartIx = i; + break; + } else if (extensions.abortingPartIx) { + extensions.abortingPartIx = 0; + } + } } void Synth::playMsgOnPart(Bit8u part, Bit8u code, Bit8u note, Bit8u velocity) { @@ -1057,7 +1171,7 @@ void Synth::playSysexWithoutHeader(Bit8u device, Bit8u command, const Bit8u *sys break; } */ - // fall through + // Fall-through case SYSEX_CMD_DT1: writeSysex(device, sysex, len); break; @@ -1067,7 +1181,7 @@ void Synth::playSysexWithoutHeader(Bit8u device, Bit8u command, const Bit8u *sys // FIXME: We should send SYSEX_CMD_RJC in this case break; } - // fall through + // Fall-through case SYSEX_CMD_RQ1: readSysex(device, sysex, len); break; @@ -1097,45 +1211,59 @@ void Synth::writeSysex(Bit8u device, const Bit8u *sysex, Bit32u len) { printDebug("WRITE-CHANNEL: Channel %d temp area 0x%06x", device, MT32EMU_SYSEXMEMADDR(addr)); #endif if (/*addr >= MT32EMU_MEMADDR(0x000000) && */addr < MT32EMU_MEMADDR(0x010000)) { - int offset; - if (chantable[device] > 8) { + addr += MT32EMU_MEMADDR(0x030000); + Bit8u *chanParts = extensions.chantable[device]; + if (*chanParts > 8) { #if MT32EMU_MONITOR_SYSEX > 0 printDebug(" (Channel not mapped to a part... 0 offset)"); #endif - offset = 0; - } else if (chantable[device] == 8) { + } else { + for (Bit32u partIx = 0; partIx <= 8; partIx++) { + if (chanParts[partIx] > 8) break; + int offset; + if (chanParts[partIx] == 8) { #if MT32EMU_MONITOR_SYSEX > 0 - printDebug(" (Channel mapped to rhythm... 0 offset)"); + printDebug(" (Channel mapped to rhythm... 0 offset)"); #endif - offset = 0; - } else { - offset = chantable[device] * sizeof(MemParams::PatchTemp); + offset = 0; + } else { + offset = chanParts[partIx] * sizeof(MemParams::PatchTemp); #if MT32EMU_MONITOR_SYSEX > 0 - printDebug(" (Setting extra offset to %d)", offset); + printDebug(" (Setting extra offset to %d)", offset); #endif + } + writeSysexGlobal(addr + offset, sysex, len); + } + return; } - addr += MT32EMU_MEMADDR(0x030000) + offset; } else if (/*addr >= MT32EMU_MEMADDR(0x010000) && */ addr < MT32EMU_MEMADDR(0x020000)) { addr += MT32EMU_MEMADDR(0x030110) - MT32EMU_MEMADDR(0x010000); } else if (/*addr >= MT32EMU_MEMADDR(0x020000) && */ addr < MT32EMU_MEMADDR(0x030000)) { - int offset; - if (chantable[device] > 8) { + addr += MT32EMU_MEMADDR(0x040000) - MT32EMU_MEMADDR(0x020000); + Bit8u *chanParts = extensions.chantable[device]; + if (*chanParts > 8) { #if MT32EMU_MONITOR_SYSEX > 0 printDebug(" (Channel not mapped to a part... 0 offset)"); #endif - offset = 0; - } else if (chantable[device] == 8) { + } else { + for (Bit32u partIx = 0; partIx <= 8; partIx++) { + if (chanParts[partIx] > 8) break; + int offset; + if (chanParts[partIx] == 8) { #if MT32EMU_MONITOR_SYSEX > 0 - printDebug(" (Channel mapped to rhythm... 0 offset)"); + printDebug(" (Channel mapped to rhythm... 0 offset)"); #endif - offset = 0; - } else { - offset = chantable[device] * sizeof(TimbreParam); + offset = 0; + } else { + offset = chanParts[partIx] * sizeof(TimbreParam); #if MT32EMU_MONITOR_SYSEX > 0 - printDebug(" (Setting extra offset to %d)", offset); + printDebug(" (Setting extra offset to %d)", offset); #endif + } + writeSysexGlobal(addr + offset, sysex, len); + } + return; } - addr += MT32EMU_MEMADDR(0x040000) - MT32EMU_MEMADDR(0x020000) + offset; } else { #if MT32EMU_MONITOR_SYSEX > 0 printDebug(" Invalid channel"); @@ -1143,8 +1271,11 @@ void Synth::writeSysex(Bit8u device, const Bit8u *sysex, Bit32u len) { return; } } + writeSysexGlobal(addr, sysex, len); +} - // Process device-global sysex (possibly converted from channel-specific sysex above) +// Process device-global sysex (possibly converted from channel-specific sysex above) +void Synth::writeSysexGlobal(Bit32u addr, const Bit8u *sysex, Bit32u len) { for (;;) { // Find the appropriate memory region const MemoryRegion *region = findMemoryRegion(addr); @@ -1484,6 +1615,8 @@ void Synth::writeMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u le } void Synth::refreshSystemMasterTune() { + // 171 is ~half a semitone. + extensions.masterTunePitchDelta = ((mt32ram.system.masterTune - 64) * 171) >> 6; // PORTABILITY NOTE: Assumes arithmetic shift. #if MT32EMU_MONITOR_SYSEX > 0 //FIXME:KG: This is just an educated guess. // The LAPC-I documentation claims a range of 427.5Hz-452.6Hz (similar to what we have here) @@ -1543,9 +1676,10 @@ void Synth::refreshSystemReserveSettings() { } void Synth::refreshSystemChanAssign(Bit8u firstPart, Bit8u lastPart) { - memset(chantable, 0xFF, sizeof(chantable)); + memset(extensions.chantable, 0xFF, sizeof(extensions.chantable)); - // CONFIRMED: In the case of assigning a channel to multiple parts, the lower part wins. + // CONFIRMED: In the case of assigning a MIDI channel to multiple parts, + // the messages received on that MIDI channel are handled by all the parts. for (Bit32u i = 0; i <= 8; i++) { if (parts[i] != NULL && i >= firstPart && i <= lastPart) { // CONFIRMED: Decay is started for all polys, and all controllers are reset, for every part whose assignment was touched by the sysex write. @@ -1553,8 +1687,13 @@ void Synth::refreshSystemChanAssign(Bit8u firstPart, Bit8u lastPart) { parts[i]->resetAllControllers(); } Bit8u chan = mt32ram.system.chanAssign[i]; - if (chan < 16 && chantable[chan] > 8) { - chantable[chan] = Bit8u(i); + if (chan > 15) continue; + Bit8u *chanParts = extensions.chantable[chan]; + for (Bit32u j = 0; j <= 8; j++) { + if (chanParts[j] > 8) { + chanParts[j] = Bit8u(i); + break; + } } } @@ -1595,9 +1734,25 @@ void Synth::reset() { } } refreshSystem(); + resetMasterTunePitchDelta(); isActive(); } +void Synth::resetMasterTunePitchDelta() { + // This effectively resets master tune to 440.0Hz. + // Despite that the manual claims 442.0Hz is the default setting for master tune, + // it doesn't actually take effect upon a reset due to a bug in the reset routine. + // CONFIRMED: This bug is present in all supported Control ROMs. + extensions.masterTunePitchDelta = 0; +#if MT32EMU_MONITOR_SYSEX > 0 + printDebug(" Actual Master Tune reset to 440.0"); +#endif +} + +Bit32s Synth::getMasterTunePitchDelta() const { + return extensions.masterTunePitchDelta; +} + MidiEvent::~MidiEvent() { if (sysexData != NULL) { delete[] sysexData; @@ -1677,73 +1832,140 @@ bool MidiEventQueue::isEmpty() const { return startPosition == endPosition; } +void Synth::selectRendererType(RendererType newRendererType) { + extensions.selectedRendererType = newRendererType; +} + +RendererType Synth::getSelectedRendererType() const { + return extensions.selectedRendererType; +} + Bit32u Synth::getStereoOutputSampleRate() const { return (analog == NULL) ? SAMPLE_RATE : analog->getOutputSampleRate(); } -void Renderer::render(SampleFormatConverter &converter, Bit32u len) { - if (!synth.opened) { - converter.addSilence(len << 1); +template <class Sample> +void RendererImpl<Sample>::doRender(Sample *stereoStream, Bit32u len) { + if (!isActivated()) { + incRenderedSampleCount(getAnalog().getDACStreamsLength(len)); + if (!getAnalog().process(NULL, NULL, NULL, NULL, NULL, NULL, stereoStream, len)) { + printDebug("RendererImpl: Invalid call to Analog::process()!\n"); + } + Synth::muteSampleBuffer(stereoStream, len << 1); return; } - if (!synth.activated) { - synth.renderedSampleCount += synth.analog->getDACStreamsLength(len); - synth.analog->process(NULL, NULL, NULL, NULL, NULL, NULL, NULL, len); - converter.addSilence(len << 1); - return; + while (len > 0) { + // As in AnalogOutputMode_ACCURATE mode output is upsampled, MAX_SAMPLES_PER_RUN is more than enough for the temp buffers. + Bit32u thisPassLen = len > MAX_SAMPLES_PER_RUN ? MAX_SAMPLES_PER_RUN : len; + doRenderStreams(tmpBuffers, getAnalog().getDACStreamsLength(thisPassLen)); + if (!getAnalog().process(stereoStream, tmpNonReverbLeft, tmpNonReverbRight, tmpReverbDryLeft, tmpReverbDryRight, tmpReverbWetLeft, tmpReverbWetRight, thisPassLen)) { + printDebug("RendererImpl: Invalid call to Analog::process()!\n"); + Synth::muteSampleBuffer(stereoStream, len << 1); + return; + } + stereoStream += thisPassLen << 1; + len -= thisPassLen; } +} +template <class Sample> +template <class O> +void RendererImpl<Sample>::doRenderAndConvert(O *stereoStream, Bit32u len) { + Sample renderingBuffer[MAX_SAMPLES_PER_RUN << 1]; while (len > 0) { - // As in AnalogOutputMode_ACCURATE mode output is upsampled, MAX_SAMPLES_PER_RUN is more than enough for the temp buffers. Bit32u thisPassLen = len > MAX_SAMPLES_PER_RUN ? MAX_SAMPLES_PER_RUN : len; - synth.renderStreams(tmpNonReverbLeft, tmpNonReverbRight, tmpReverbDryLeft, tmpReverbDryRight, tmpReverbWetLeft, tmpReverbWetRight, synth.analog->getDACStreamsLength(thisPassLen)); - synth.analog->process(converter.sampleBuffer, tmpNonReverbLeft, tmpNonReverbRight, tmpReverbDryLeft, tmpReverbDryRight, tmpReverbWetLeft, tmpReverbWetRight, thisPassLen); - converter.convert(thisPassLen << 1); + doRender(renderingBuffer, thisPassLen); + convertSampleFormat(renderingBuffer, stereoStream, thisPassLen << 1); + stereoStream += thisPassLen << 1; len -= thisPassLen; } } +template<> +void RendererImpl<IntSample>::render(IntSample *stereoStream, Bit32u len) { + doRender(stereoStream, len); +} + +template<> +void RendererImpl<IntSample>::render(FloatSample *stereoStream, Bit32u len) { + doRenderAndConvert(stereoStream, len); +} + +template<> +void RendererImpl<FloatSample>::render(IntSample *stereoStream, Bit32u len) { + doRenderAndConvert(stereoStream, len); +} + +template<> +void RendererImpl<FloatSample>::render(FloatSample *stereoStream, Bit32u len) { + doRender(stereoStream, len); +} + +template <class S> +static inline void renderStereo(bool opened, Renderer *renderer, S *stream, Bit32u len) { + if (opened) { + renderer->render(stream, len); + } else { + Synth::muteSampleBuffer(stream, len << 1); + } +} + void Synth::render(Bit16s *stream, Bit32u len) { -#if MT32EMU_USE_FLOAT_SAMPLES - BufferedSampleFormatConverter<2> converter(stream); -#else - SampleFormatConverter converter(stream); -#endif - renderer.render(converter, len); + renderStereo(opened, renderer, stream, len); } void Synth::render(float *stream, Bit32u len) { -#if MT32EMU_USE_FLOAT_SAMPLES - SampleFormatConverter converter(stream); -#else - BufferedSampleFormatConverter<2> converter(stream); -#endif - renderer.render(converter, len); + renderStereo(opened, renderer, stream, len); } -void Renderer::renderStreams( - SampleFormatConverter &nonReverbLeft, SampleFormatConverter &nonReverbRight, - SampleFormatConverter &reverbDryLeft, SampleFormatConverter &reverbDryRight, - SampleFormatConverter &reverbWetLeft, SampleFormatConverter &reverbWetRight, - Bit32u len) -{ - if (!synth.opened) { - nonReverbLeft.addSilence(len); - nonReverbRight.addSilence(len); - reverbDryLeft.addSilence(len); - reverbDryRight.addSilence(len); - reverbWetLeft.addSilence(len); - reverbWetRight.addSilence(len); - return; +template <class Sample> +static inline void advanceStream(Sample *&stream, Bit32u len) { + if (stream != NULL) { + stream += len; } +} + +template <class Sample> +static inline void advanceStreams(DACOutputStreams<Sample> &streams, Bit32u len) { + advanceStream(streams.nonReverbLeft, len); + advanceStream(streams.nonReverbRight, len); + advanceStream(streams.reverbDryLeft, len); + advanceStream(streams.reverbDryRight, len); + advanceStream(streams.reverbWetLeft, len); + advanceStream(streams.reverbWetRight, len); +} + +template <class Sample> +static inline void muteStreams(const DACOutputStreams<Sample> &streams, Bit32u len) { + Synth::muteSampleBuffer(streams.nonReverbLeft, len); + Synth::muteSampleBuffer(streams.nonReverbRight, len); + Synth::muteSampleBuffer(streams.reverbDryLeft, len); + Synth::muteSampleBuffer(streams.reverbDryRight, len); + Synth::muteSampleBuffer(streams.reverbWetLeft, len); + Synth::muteSampleBuffer(streams.reverbWetRight, len); +} + +template <class I, class O> +static inline void convertStreamsFormat(const DACOutputStreams<I> &inStreams, const DACOutputStreams<O> &outStreams, Bit32u len) { + convertSampleFormat(inStreams.nonReverbLeft, outStreams.nonReverbLeft, len); + convertSampleFormat(inStreams.nonReverbRight, outStreams.nonReverbRight, len); + convertSampleFormat(inStreams.reverbDryLeft, outStreams.reverbDryLeft, len); + convertSampleFormat(inStreams.reverbDryRight, outStreams.reverbDryRight, len); + convertSampleFormat(inStreams.reverbWetLeft, outStreams.reverbWetLeft, len); + convertSampleFormat(inStreams.reverbWetRight, outStreams.reverbWetRight, len); +} +template <class Sample> +void RendererImpl<Sample>::doRenderStreams(const DACOutputStreams<Sample> &streams, Bit32u len) +{ + DACOutputStreams<Sample> tmpStreams = streams; while (len > 0) { // We need to ensure zero-duration notes will play so add minimum 1-sample delay. Bit32u thisLen = 1; - if (!synth.isAbortingPoly()) { - const MidiEvent *nextEvent = synth.midiQueue->peekMidiEvent(); - Bit32s samplesToNextEvent = (nextEvent != NULL) ? Bit32s(nextEvent->timestamp - synth.renderedSampleCount) : MAX_SAMPLES_PER_RUN; + if (!isAbortingPoly()) { + const MidiEvent *nextEvent = getMidiQueue().peekMidiEvent(); + Bit32s samplesToNextEvent = (nextEvent != NULL) ? Bit32s(nextEvent->timestamp - getRenderedSampleCount()) : MAX_SAMPLES_PER_RUN; if (samplesToNextEvent > 0) { thisLen = len > MAX_SAMPLES_PER_RUN ? MAX_SAMPLES_PER_RUN : len; if (thisLen > Bit32u(samplesToNextEvent)) { @@ -1754,51 +1976,94 @@ void Renderer::renderStreams( synth.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 (!synth.isAbortingPoly()) { - synth.midiQueue->dropMidiEvent(); + if (!isAbortingPoly()) { + getMidiQueue().dropMidiEvent(); } } else { synth.playSysexNow(nextEvent->sysexData, nextEvent->sysexLength); - synth.midiQueue->dropMidiEvent(); + getMidiQueue().dropMidiEvent(); } } } - DACOutputStreams<Sample> streams = { - nonReverbLeft.sampleBuffer, nonReverbRight.sampleBuffer, - reverbDryLeft.sampleBuffer, reverbDryRight.sampleBuffer, - reverbWetLeft.sampleBuffer, reverbWetRight.sampleBuffer - }; - doRenderStreams(streams, thisLen); - nonReverbLeft.convert(thisLen); - nonReverbRight.convert(thisLen); - reverbDryLeft.convert(thisLen); - reverbDryRight.convert(thisLen); - reverbWetLeft.convert(thisLen); - reverbWetRight.convert(thisLen); + produceStreams(tmpStreams, thisLen); + advanceStreams(tmpStreams, thisLen); len -= thisLen; } } +template <class Sample> +template <class O> +void RendererImpl<Sample>::doRenderAndConvertStreams(const DACOutputStreams<O> &streams, Bit32u len) { + Sample cnvNonReverbLeft[MAX_SAMPLES_PER_RUN], cnvNonReverbRight[MAX_SAMPLES_PER_RUN]; + Sample cnvReverbDryLeft[MAX_SAMPLES_PER_RUN], cnvReverbDryRight[MAX_SAMPLES_PER_RUN]; + Sample cnvReverbWetLeft[MAX_SAMPLES_PER_RUN], cnvReverbWetRight[MAX_SAMPLES_PER_RUN]; + + const DACOutputStreams<Sample> cnvStreams = { + cnvNonReverbLeft, cnvNonReverbRight, + cnvReverbDryLeft, cnvReverbDryRight, + cnvReverbWetLeft, cnvReverbWetRight + }; + + DACOutputStreams<O> tmpStreams = streams; + + while (len > 0) { + Bit32u thisPassLen = len > MAX_SAMPLES_PER_RUN ? MAX_SAMPLES_PER_RUN : len; + doRenderStreams(cnvStreams, thisPassLen); + convertStreamsFormat(cnvStreams, tmpStreams, thisPassLen); + advanceStreams(tmpStreams, thisPassLen); + len -= thisPassLen; + } +} + +template<> +void RendererImpl<IntSample>::renderStreams(const DACOutputStreams<IntSample> &streams, Bit32u len) { + doRenderStreams(streams, len); +} + +template<> +void RendererImpl<IntSample>::renderStreams(const DACOutputStreams<FloatSample> &streams, Bit32u len) { + doRenderAndConvertStreams(streams, len); +} + +template<> +void RendererImpl<FloatSample>::renderStreams(const DACOutputStreams<IntSample> &streams, Bit32u len) { + doRenderAndConvertStreams(streams, len); +} + +template<> +void RendererImpl<FloatSample>::renderStreams(const DACOutputStreams<FloatSample> &streams, Bit32u len) { + doRenderStreams(streams, len); +} + +template <class S> +static inline void renderStreams(bool opened, Renderer *renderer, const DACOutputStreams<S> &streams, Bit32u len) { + if (opened) { + renderer->renderStreams(streams, len); + } else { + muteStreams(streams, len); + } +} + +void Synth::renderStreams(const DACOutputStreams<Bit16s> &streams, Bit32u len) { + MT32Emu::renderStreams(opened, renderer, streams, len); +} + +void Synth::renderStreams(const DACOutputStreams<float> &streams, Bit32u len) { + MT32Emu::renderStreams(opened, renderer, streams, len); +} + void Synth::renderStreams( Bit16s *nonReverbLeft, Bit16s *nonReverbRight, Bit16s *reverbDryLeft, Bit16s *reverbDryRight, Bit16s *reverbWetLeft, Bit16s *reverbWetRight, Bit32u len) { -#if MT32EMU_USE_FLOAT_SAMPLES - BufferedSampleFormatConverter<> convNonReverbLeft(nonReverbLeft), convNonReverbRight(nonReverbRight); - BufferedSampleFormatConverter<> convReverbDryLeft(reverbDryLeft), convReverbDryRight(reverbDryRight); - BufferedSampleFormatConverter<> convReverbWetLeft(reverbWetLeft), convReverbWetRight(reverbWetRight); -#else - SampleFormatConverter convNonReverbLeft(nonReverbLeft), convNonReverbRight(nonReverbRight); - SampleFormatConverter convReverbDryLeft(reverbDryLeft), convReverbDryRight(reverbDryRight); - SampleFormatConverter convReverbWetLeft(reverbWetLeft), convReverbWetRight(reverbWetRight); -#endif - renderer.renderStreams( - convNonReverbLeft, convNonReverbRight, - convReverbDryLeft, convReverbDryRight, - convReverbWetLeft, convReverbWetRight, - len); + DACOutputStreams<IntSample> streams = { + nonReverbLeft, nonReverbRight, + reverbDryLeft, reverbDryRight, + reverbWetLeft, reverbWetRight + }; + renderStreams(streams, len); } void Synth::renderStreams( @@ -1807,30 +2072,19 @@ void Synth::renderStreams( float *reverbWetLeft, float *reverbWetRight, Bit32u len) { -#if MT32EMU_USE_FLOAT_SAMPLES - SampleFormatConverter convNonReverbLeft(nonReverbLeft), convNonReverbRight(nonReverbRight); - SampleFormatConverter convReverbDryLeft(reverbDryLeft), convReverbDryRight(reverbDryRight); - SampleFormatConverter convReverbWetLeft(reverbWetLeft), convReverbWetRight(reverbWetRight); -#else - BufferedSampleFormatConverter<> convNonReverbLeft(nonReverbLeft), convNonReverbRight(nonReverbRight); - BufferedSampleFormatConverter<> convReverbDryLeft(reverbDryLeft), convReverbDryRight(reverbDryRight); - BufferedSampleFormatConverter<> convReverbWetLeft(reverbWetLeft), convReverbWetRight(reverbWetRight); -#endif - renderer.renderStreams( - convNonReverbLeft, convNonReverbRight, - convReverbDryLeft, convReverbDryRight, - convReverbWetLeft, convReverbWetRight, - len); + DACOutputStreams<FloatSample> streams = { + nonReverbLeft, nonReverbRight, + reverbDryLeft, reverbDryRight, + reverbWetLeft, reverbWetRight + }; + renderStreams(streams, len); } // In GENERATION2 units, the output from LA32 goes to the Boss chip already bit-shifted. // In NICE mode, it's also better to increase volume before the reverb processing to preserve accuracy. -void Renderer::produceLA32Output(Sample *buffer, Bit32u len) { -#if MT32EMU_USE_FLOAT_SAMPLES - (void)buffer; - (void)len; -#else - switch (synth.dacInputMode) { +template <> +void RendererImpl<IntSample>::produceLA32Output(IntSample *buffer, Bit32u len) { + switch (synth.getDACInputMode()) { case DACInputMode_GENERATION2: while (len--) { *buffer = (*buffer & 0x8000) | ((*buffer << 1) & 0x7FFE) | ((*buffer >> 14) & 0x0001); @@ -1839,32 +2093,71 @@ void Renderer::produceLA32Output(Sample *buffer, Bit32u len) { break; case DACInputMode_NICE: while (len--) { - *buffer = Synth::clipSampleEx(SampleEx(*buffer) << 1); + *buffer = Synth::clipSampleEx(IntSampleEx(*buffer) << 1); ++buffer; } break; default: break; } -#endif } -void Renderer::convertSamplesToOutput(Sample *buffer, Bit32u len) { -#if MT32EMU_USE_FLOAT_SAMPLES - (void)buffer; - (void)len; -#else - if (synth.dacInputMode == DACInputMode_GENERATION1) { +template <> +void RendererImpl<IntSample>::convertSamplesToOutput(IntSample *buffer, Bit32u len) { + if (synth.getDACInputMode() == DACInputMode_GENERATION1) { while (len--) { - *buffer = Sample((*buffer & 0x8000) | ((*buffer << 1) & 0x7FFE)); + *buffer = IntSample((*buffer & 0x8000) | ((*buffer << 1) & 0x7FFE)); ++buffer; } } -#endif } -void Renderer::doRenderStreams(DACOutputStreams<Sample> &streams, Bit32u len) { - if (synth.activated) { +static inline float produceDistortedSample(float sample) { + // Here we roughly simulate the distortion caused by the DAC bit shift. + if (sample < -1.0f) { + return sample + 2.0f; + } else if (1.0f < sample) { + return sample - 2.0f; + } + return sample; +} + +template <> +void RendererImpl<FloatSample>::produceLA32Output(FloatSample *buffer, Bit32u len) { + switch (synth.getDACInputMode()) { + case DACInputMode_NICE: + // Note, we do not do any clamping for floats here to avoid introducing distortions. + // This means that the output signal may actually overshoot the unity when the volume is set too high. + // We leave it up to the consumer whether the output is to be clamped or properly normalised further on. + while (len--) { + *buffer *= 2.0f; + buffer++; + } + break; + case DACInputMode_GENERATION2: + while (len--) { + *buffer = produceDistortedSample(2.0f * *buffer); + buffer++; + } + break; + default: + break; + } +} + +template <> +void RendererImpl<FloatSample>::convertSamplesToOutput(FloatSample *buffer, Bit32u len) { + if (synth.getDACInputMode() == DACInputMode_GENERATION1) { + while (len--) { + *buffer = produceDistortedSample(2.0f * *buffer); + buffer++; + } + } +} + +template <class Sample> +void RendererImpl<Sample>::produceStreams(const DACOutputStreams<Sample> &streams, Bit32u len) { + if (isActivated()) { // Even if LA32 output isn't desired, we proceed anyway with temp buffers Sample *nonReverbLeft = streams.nonReverbLeft == NULL ? tmpNonReverbLeft : streams.nonReverbLeft; Sample *nonReverbRight = streams.nonReverbRight == NULL ? tmpNonReverbRight : streams.nonReverbRight; @@ -1877,10 +2170,10 @@ void Renderer::doRenderStreams(DACOutputStreams<Sample> &streams, Bit32u len) { Synth::muteSampleBuffer(reverbDryRight, len); for (unsigned int i = 0; i < synth.getPartialCount(); i++) { - if (synth.partialManager->shouldReverb(i)) { - synth.partialManager->produceOutput(i, reverbDryLeft, reverbDryRight, len); + if (getPartialManager().shouldReverb(i)) { + getPartialManager().produceOutput(i, reverbDryLeft, reverbDryRight, len); } else { - synth.partialManager->produceOutput(i, nonReverbLeft, nonReverbRight, len); + getPartialManager().produceOutput(i, nonReverbLeft, nonReverbRight, len); } } @@ -1888,7 +2181,9 @@ void Renderer::doRenderStreams(DACOutputStreams<Sample> &streams, Bit32u len) { produceLA32Output(reverbDryRight, len); if (synth.isReverbEnabled()) { - synth.reverbModel->process(reverbDryLeft, reverbDryRight, streams.reverbWetLeft, streams.reverbWetRight, len); + if (!getReverbModel().process(reverbDryLeft, reverbDryRight, streams.reverbWetLeft, streams.reverbWetRight, len)) { + printDebug("RendererImpl: Invalid call to BReverbModel::process()!\n"); + } if (streams.reverbWetLeft != NULL) convertSamplesToOutput(streams.reverbWetLeft, len); if (streams.reverbWetRight != NULL) convertSamplesToOutput(streams.reverbWetRight, len); } else { @@ -1908,16 +2203,11 @@ void Renderer::doRenderStreams(DACOutputStreams<Sample> &streams, Bit32u len) { if (streams.reverbDryLeft != NULL) convertSamplesToOutput(reverbDryLeft, len); if (streams.reverbDryRight != NULL) convertSamplesToOutput(reverbDryRight, len); } else { - Synth::muteSampleBuffer(streams.nonReverbLeft, len); - Synth::muteSampleBuffer(streams.nonReverbRight, len); - Synth::muteSampleBuffer(streams.reverbDryLeft, len); - Synth::muteSampleBuffer(streams.reverbDryRight, len); - Synth::muteSampleBuffer(streams.reverbWetLeft, len); - Synth::muteSampleBuffer(streams.reverbWetRight, len); + muteStreams(streams, len); } - synth.partialManager->clearAlreadyOutputed(); - synth.renderedSampleCount += len; + getPartialManager().clearAlreadyOutputed(); + incRenderedSampleCount(len); } void Synth::printPartialUsage(Bit32u sampleOffset) { diff --git a/audio/softsynth/mt32/Synth.h b/audio/softsynth/mt32/Synth.h index fe31d99f02..54417d5b44 100644 --- a/audio/softsynth/mt32/Synth.h +++ b/audio/softsynth/mt32/Synth.h @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 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 @@ -30,6 +30,7 @@ namespace MT32Emu { class Analog; class BReverbModel; +class Extensions; class MemoryRegion; class MidiEventQueue; class Part; @@ -123,6 +124,7 @@ friend class RhythmPart; friend class SamplerateAdapter; friend class SoxrAdapter; friend class TVA; +friend class TVF; friend class TVP; private: @@ -151,7 +153,7 @@ private: const char (*soundGroupNames)[9]; // Array Bit32u partialCount; - Bit8u chantable[16]; // NOTE: value above 8 means that the channel is not assigned + Bit8u nukeme[16]; // FIXME: Nuke it. For binary compatibility only. MidiEventQueue *midiQueue; volatile Bit32u lastReceivedMIDIEventTimestamp; @@ -186,16 +188,17 @@ private: Poly *abortingPoly; Analog *analog; - Renderer &renderer; + Renderer *renderer; // Binary compatibility helper. - void *reserved; + Extensions &extensions; // **************************** Implementation methods ************************** Bit32u addMIDIInterfaceDelay(Bit32u len, Bit32u timestamp); bool isAbortingPoly() const { return abortingPoly != NULL; } + void writeSysexGlobal(Bit32u addr, const Bit8u *sysex, Bit32u len); void readSysex(Bit8u channel, const Bit8u *sysex, Bit32u len) const; void initMemoryRegions(); void deleteMemoryRegions(); @@ -229,6 +232,9 @@ private: // partNum should be 0..7 for Part 1..8, or 8 for Rhythm const Part *getPart(Bit8u partNum) const; + void resetMasterTunePitchDelta(); + Bit32s getMasterTunePitchDelta() const; + public: static inline Bit16s clipSampleEx(Bit32s sampleEx) { // Clamp values above 32767 to 32767, and values below -32768 to -32768 @@ -257,11 +263,11 @@ public: } static inline Bit16s convertSample(float sample) { - return Synth::clipSampleEx(Bit32s(sample * 16384.0f)); // This multiplier takes into account the DAC bit shift + return Synth::clipSampleEx(Bit32s(sample * 32768.0f)); // This multiplier corresponds to normalised floats } static inline float convertSample(Bit16s sample) { - return float(sample) / 16384.0f; // This multiplier takes into account the DAC bit shift + return float(sample) / 32768.0f; // This multiplier corresponds to normalised floats } // Returns library version as an integer in format: 0x00MMmmpp, where: @@ -307,6 +313,10 @@ public: // Returns the actual queue size being used. MT32EMU_EXPORT Bit32u setMIDIEventQueueSize(Bit32u); + // Returns current value of the global counter of samples rendered since the synth was created (at the native sample rate 32000 Hz). + // This method helps to compute accurate timestamp of a MIDI message to use with the methods below. + MT32EMU_EXPORT Bit32u getInternalRenderedSampleCount() const; + // Enqueues a MIDI event for subsequent playback. // 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 (at the native sample rate 32000 Hz). @@ -381,7 +391,6 @@ public: // Sets output gain factor for synth output channels. Applied to all output samples and unrelated with the synth's Master volume, // it rather corresponds to the gain of the output analog circuitry of the hardware units. However, together with setReverbOutputGain() // it offers to the user a capability to control the gain of reverb and non-reverb output channels independently. - // Ignored in DACInputMode_PURE MT32EMU_EXPORT void setOutputGain(float gain); // Returns current output gain factor for synth output channels. MT32EMU_EXPORT float getOutputGain() const; @@ -394,7 +403,6 @@ public: // 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 MT32EMU_EXPORT void setReverbOutputGain(float gain); // Returns current output gain factor for reverb wet output channels. MT32EMU_EXPORT float getReverbOutputGain() const; @@ -404,6 +412,24 @@ public: // Returns whether left and right output channels are swapped. MT32EMU_EXPORT bool isReversedStereoEnabled() const; + // Allows to toggle the NiceAmpRamp mode. + // In this mode, we want to ensure that amp ramp never jumps to the target + // value and always gradually increases or decreases. It seems that real units + // do not bother to always check if a newly started ramp leads to a jump. + // We also prefer the quality improvement over the emulation accuracy, + // so this mode is enabled by default. + MT32EMU_EXPORT void setNiceAmpRampEnabled(bool enabled); + // Returns whether NiceAmpRamp mode is enabled. + MT32EMU_EXPORT bool isNiceAmpRampEnabled() const; + + // Selects new type of the wave generator and renderer to be used during subsequent calls to open(). + // By default, RendererType_BIT16S is selected. + // See RendererType for details. + MT32EMU_EXPORT void selectRendererType(RendererType); + // Returns previously selected type of the wave generator and renderer. + // See RendererType for details. + MT32EMU_EXPORT RendererType getSelectedRendererType() const; + // Returns actual sample rate used in emulation of stereo analog circuitry of hardware units. // See comment for render() below. MT32EMU_EXPORT Bit32u getStereoOutputSampleRate() const; @@ -422,14 +448,10 @@ public: // NULL may be specified in place of any or all of the stream buffers to skip it. // The length is in samples, not bytes. Uses NATIVE byte ordering. MT32EMU_EXPORT void renderStreams(Bit16s *nonReverbLeft, Bit16s *nonReverbRight, Bit16s *reverbDryLeft, Bit16s *reverbDryRight, Bit16s *reverbWetLeft, Bit16s *reverbWetRight, Bit32u len); - void renderStreams(const DACOutputStreams<Bit16s> &streams, Bit32u len) { - renderStreams(streams.nonReverbLeft, streams.nonReverbRight, streams.reverbDryLeft, streams.reverbDryRight, streams.reverbWetLeft, streams.reverbWetRight, len); - } + MT32EMU_EXPORT void renderStreams(const DACOutputStreams<Bit16s> &streams, Bit32u len); // Same as above but outputs to float streams. MT32EMU_EXPORT void renderStreams(float *nonReverbLeft, float *nonReverbRight, float *reverbDryLeft, float *reverbDryRight, float *reverbWetLeft, float *reverbWetRight, Bit32u len); - void renderStreams(const DACOutputStreams<float> &streams, Bit32u len) { - renderStreams(streams.nonReverbLeft, streams.nonReverbRight, streams.reverbDryLeft, streams.reverbDryRight, streams.reverbWetLeft, streams.reverbWetRight, len); - } + MT32EMU_EXPORT void renderStreams(const DACOutputStreams<float> &streams, Bit32u len); // Returns true when there is at least one active partial, otherwise false. MT32EMU_EXPORT bool hasActivePartials() const; diff --git a/audio/softsynth/mt32/TVA.cpp b/audio/softsynth/mt32/TVA.cpp index c20b8b6393..3f7064f9a4 100644 --- a/audio/softsynth/mt32/TVA.cpp +++ b/audio/softsynth/mt32/TVA.cpp @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 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 @@ -43,7 +43,7 @@ void TVA::startRamp(Bit8u newTarget, Bit8u newIncrement, int newPhase) { phase = newPhase; ampRamp->startRamp(newTarget, newIncrement); #if MT32EMU_MONITOR_TVA >= 1 - partial->getSynth()->printDebug("[+%lu] [Partial %d] TVA,ramp,%d,%d,%d,%d", partial->debugGetSampleNum(), partial->debugGetPartialNum(), (newIncrement & 0x80) ? -1 : 1, (newIncrement & 0x7F), newPhase); + partial->getSynth()->printDebug("[+%lu] [Partial %d] TVA,ramp,%x,%s%x,%d", partial->debugGetSampleNum(), partial->debugGetPartialNum(), newTarget, (newIncrement & 0x80) ? "-" : "+", (newIncrement & 0x7F), newPhase); #endif } @@ -99,10 +99,10 @@ static int calcVeloAmpSubtraction(Bit8u veloSensitivity, unsigned int velocity) return absVelocityMult - (velocityMult >> 8); // PORTABILITY NOTE: Assumes arithmetic shift } -static int calcBasicAmp(const Tables *tables, const Partial *partial, const MemParams::System *system, const TimbreParam::PartialParam *partialParam, const MemParams::PatchTemp *patchTemp, const MemParams::RhythmTemp *rhythmTemp, int biasAmpSubtraction, int veloAmpSubtraction, Bit8u expression) { +static int calcBasicAmp(const Tables *tables, const Partial *partial, const MemParams::System *system, const TimbreParam::PartialParam *partialParam, const MemParams::PatchTemp *patchTemp, const MemParams::RhythmTemp *rhythmTemp, int biasAmpSubtraction, int veloAmpSubtraction, Bit8u expression, bool hasRingModQuirk) { int amp = 155; - if (!partial->isRingModulatingSlave()) { + if (!(hasRingModQuirk ? partial->isRingModulatingNoMix() : partial->isRingModulatingSlave())) { amp -= tables->masterVolToAmpSubtraction[system->masterVol]; if (amp < 0) { return 0; @@ -169,7 +169,7 @@ void TVA::reset(const Part *newPart, const TimbreParam::PartialParam *newPartial biasAmpSubtraction = calcBiasAmpSubtractions(partialParam, key); veloAmpSubtraction = calcVeloAmpSubtraction(partialParam->tva.veloSensitivity, velocity); - int newTarget = calcBasicAmp(tables, partial, system, partialParam, patchTemp, newRhythmTemp, biasAmpSubtraction, veloAmpSubtraction, part->getExpression()); + int newTarget = calcBasicAmp(tables, partial, system, partialParam, patchTemp, newRhythmTemp, biasAmpSubtraction, veloAmpSubtraction, part->getExpression(), partial->getSynth()->controlROMFeatures->quirkRingModulationNoMix); int newPhase; if (partialParam->tva.envTime[0] == 0) { // Initially go to the TVA_PHASE_ATTACK target amp, and spend the next phase going from there to the TVA_PHASE_2 target amp @@ -221,18 +221,29 @@ void TVA::recalcSustain() { } // We're sustaining. Recalculate all the values const Tables *tables = &Tables::getInstance(); - int newTarget = calcBasicAmp(tables, partial, system, partialParam, patchTemp, rhythmTemp, biasAmpSubtraction, veloAmpSubtraction, part->getExpression()); + int newTarget = calcBasicAmp(tables, partial, system, partialParam, patchTemp, rhythmTemp, biasAmpSubtraction, veloAmpSubtraction, part->getExpression(), partial->getSynth()->controlROMFeatures->quirkRingModulationNoMix); newTarget += partialParam->tva.envLevel[3]; - // Since we're in TVA_PHASE_SUSTAIN at this point, we know that target has been reached and an interrupt fired, so we can rely on it being the current amp. + + // Although we're in TVA_PHASE_SUSTAIN at this point, we cannot be sure that there is no active ramp at the moment. + // In case the channel volume or the expression changes frequently, the previously started ramp may still be in progress. + // Real hardware units ignore this possibility and rely on the assumption that the target is the current amp. + // This is OK in most situations but when the ramp that is currently in progress needs to change direction + // due to a volume/expression update, this leads to a jump in the amp that is audible as an unpleasant click. + // To avoid that, we compare the newTarget with the the actual current ramp value and correct the direction if necessary. int targetDelta = newTarget - target; // Calculate an increment to get to the new amp value in a short, more or less consistent amount of time Bit8u newIncrement; - if (targetDelta >= 0) { + bool descending = targetDelta < 0; + if (!descending) { newIncrement = tables->envLogarithmicTime[Bit8u(targetDelta)] - 2; } else { newIncrement = (tables->envLogarithmicTime[Bit8u(-targetDelta)] - 2) | 0x80; } + if (part->getSynth()->isNiceAmpRampEnabled() && (descending != ampRamp->isBelowCurrent(newTarget))) { + newIncrement ^= 0x80; + } + // Configure so that once the transition's complete and nextPhase() is called, we'll just re-enter sustain phase (or decay phase, depending on parameters at the time). startRamp(newTarget, newIncrement, TVA_PHASE_SUSTAIN - 1); } @@ -260,7 +271,7 @@ void TVA::nextPhase() { } bool allLevelsZeroFromNowOn = false; - if (partialParam->tva.envLevel[3] == 0) { + if (!partial->getSynth()->controlROMFeatures->quirkTVAZeroEnvLevels && partialParam->tva.envLevel[3] == 0) { if (newPhase == TVA_PHASE_4) { allLevelsZeroFromNowOn = true; } else if (partialParam->tva.envLevel[2] == 0) { @@ -283,7 +294,7 @@ void TVA::nextPhase() { int envPointIndex = phase; if (!allLevelsZeroFromNowOn) { - newTarget = calcBasicAmp(tables, partial, system, partialParam, patchTemp, rhythmTemp, biasAmpSubtraction, veloAmpSubtraction, part->getExpression()); + newTarget = calcBasicAmp(tables, partial, system, partialParam, patchTemp, rhythmTemp, biasAmpSubtraction, veloAmpSubtraction, part->getExpression(), partial->getSynth()->controlROMFeatures->quirkRingModulationNoMix); if (newPhase == TVA_PHASE_SUSTAIN || newPhase == TVA_PHASE_RELEASE) { if (partialParam->tva.envLevel[3] == 0) { diff --git a/audio/softsynth/mt32/TVA.h b/audio/softsynth/mt32/TVA.h index f593b4e7d1..cf9296d481 100644 --- a/audio/softsynth/mt32/TVA.h +++ b/audio/softsynth/mt32/TVA.h @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 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 diff --git a/audio/softsynth/mt32/TVF.cpp b/audio/softsynth/mt32/TVF.cpp index b296c34132..7ba9c7f2e0 100644 --- a/audio/softsynth/mt32/TVF.cpp +++ b/audio/softsynth/mt32/TVF.cpp @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -21,6 +21,7 @@ #include "LA32Ramp.h" #include "Partial.h" #include "Poly.h" +#include "Synth.h" #include "Tables.h" namespace MT32Emu { @@ -52,7 +53,7 @@ enum { PHASE_DONE = 7 }; -static int calcBaseCutoff(const TimbreParam::PartialParam *partialParam, Bit32u basePitch, unsigned int key) { +static int calcBaseCutoff(const TimbreParam::PartialParam *partialParam, Bit32u basePitch, unsigned int key, bool quirkTVFBaseCutoffLimit) { // This table matches the values used by a real LAPC-I. static const Bit8s biasLevelToBiasMult[] = {85, 42, 21, 16, 10, 5, 2, 0, -2, -5, -10, -16, -21, -74, -85}; // These values represent unique options with no consistent pattern, so we have to use something like a table in any case. @@ -90,8 +91,14 @@ static int calcBaseCutoff(const TimbreParam::PartialParam *partialParam, Bit32u if (pitchDeltaThing > 0) { baseCutoff -= pitchDeltaThing; } - } else if (baseCutoff < -2048) { - baseCutoff = -2048; + } else if (quirkTVFBaseCutoffLimit) { + if (baseCutoff <= -0x400) { + baseCutoff = -400; + } + } else { + if (baseCutoff < -2048) { + baseCutoff = -2048; + } } baseCutoff += 2056; baseCutoff >>= 4; // PORTABILITY NOTE: Hmm... Depends whether it could've been below -2056, but maybe arithmetic shift assumed? @@ -110,7 +117,7 @@ void TVF::startRamp(Bit8u newTarget, Bit8u newIncrement, int newPhase) { phase = newPhase; cutoffModifierRamp->startRamp(newTarget, newIncrement); #if MT32EMU_MONITOR_TVF >= 1 - partial->getSynth()->printDebug("[+%lu] [Partial %d] TVF,ramp,%d,%d,%d,%d", partial->debugGetSampleNum(), partial->debugGetPartialNum(), newTarget, (newIncrement & 0x80) ? -1 : 1, (newIncrement & 0x7F), newPhase); + partial->getSynth()->printDebug("[+%lu] [Partial %d] TVF,ramp,%x,%s%x,%d", partial->debugGetSampleNum(), partial->debugGetPartialNum(), newTarget, (newIncrement & 0x80) ? "-" : "+", (newIncrement & 0x7F), newPhase); #endif } @@ -122,7 +129,7 @@ void TVF::reset(const TimbreParam::PartialParam *newPartialParam, unsigned int b const Tables *tables = &Tables::getInstance(); - baseCutoff = calcBaseCutoff(newPartialParam, basePitch, key); + baseCutoff = calcBaseCutoff(newPartialParam, basePitch, key, partial->getSynth()->controlROMFeatures->quirkTVFBaseCutoffLimit); #if MT32EMU_MONITOR_TVF >= 1 partial->getSynth()->printDebug("[+%lu] [Partial %d] TVF,base,%d", partial->debugGetSampleNum(), partial->debugGetPartialNum(), baseCutoff); #endif diff --git a/audio/softsynth/mt32/TVF.h b/audio/softsynth/mt32/TVF.h index 38dcef708c..e637aa5b48 100644 --- a/audio/softsynth/mt32/TVF.h +++ b/audio/softsynth/mt32/TVF.h @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 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 diff --git a/audio/softsynth/mt32/TVP.cpp b/audio/softsynth/mt32/TVP.cpp index dca0003843..a3b364048a 100644 --- a/audio/softsynth/mt32/TVP.cpp +++ b/audio/softsynth/mt32/TVP.cpp @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 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 @@ -51,13 +51,15 @@ static Bit16u keyToPitchTable[] = { 21845, 22187, 22528, 22869 }; +// We want to do processing 4000 times per second. FIXME: This is pretty arbitrary. +static const int NOMINAL_PROCESS_TIMER_PERIOD_SAMPLES = SAMPLE_RATE / 4000; + +// The timer runs at 500kHz. This is how much to increment it after 8 samples passes. +// We multiply by 8 to get rid of the fraction and deal with just integers. +static const int PROCESS_TIMER_INCREMENT_x8 = 8 * 500000 / SAMPLE_RATE; + TVP::TVP(const Partial *usePartial) : partial(usePartial), system(&usePartial->getSynth()->mt32ram.system) { - // We want to do processing 4000 times per second. FIXME: This is pretty arbitrary. - maxCounter = SAMPLE_RATE / 4000; - // The timer runs at 500kHz. We only need to bother updating it every maxCounter samples, before we do processing. - // This is how much to increment it by every maxCounter samples. - processTimerIncrement = 500000 * maxCounter / SAMPLE_RATE; } static Bit16s keyToPitch(unsigned int key) { @@ -76,13 +78,15 @@ static inline Bit32s fineToPitch(Bit8u fine) { return (fine - 50) * 4096 / 1200; // One cent per fine offset } -static Bit32u calcBasePitch(const Partial *partial, const TimbreParam::PartialParam *partialParam, const MemParams::PatchTemp *patchTemp, unsigned int key) { +static Bit32u calcBasePitch(const Partial *partial, const TimbreParam::PartialParam *partialParam, const MemParams::PatchTemp *patchTemp, unsigned int key, const ControlROMFeatureSet *controlROMFeatures) { Bit32s basePitch = keyToPitch(key); basePitch = (basePitch * pitchKeyfollowMult[partialParam->wg.pitchKeyfollow]) >> 13; // PORTABILITY NOTE: Assumes arithmetic shift basePitch += coarseToPitch(partialParam->wg.pitchCoarse); basePitch += fineToPitch(partialParam->wg.pitchFine); - // NOTE:Mok: This is done on MT-32, but not LAPC-I: - //pitch += coarseToPitch(patchTemp->patch.keyShift + 12); + if (controlROMFeatures->quirkKeyShift) { + // NOTE:Mok: This is done on MT-32, but not LAPC-I: + basePitch += coarseToPitch(patchTemp->patch.keyShift + 12); + } basePitch += fineToPitch(patchTemp->patch.fineTune); const ControlROMPCMStruct *controlROMPCMStruct = partial->getControlROMPCMStruct(); @@ -97,7 +101,12 @@ static Bit32u calcBasePitch(const Partial *partial, const TimbreParam::PartialPa basePitch += 33037; } } - if (basePitch < 0) { + + // MT-32 GEN0 does 16-bit calculations here, allowing an integer overflow. + // This quirk is observable playing the patch defined for timbre "HIT BOTTOM" in Larry 3. + if (controlROMFeatures->quirkBasePitchOverflow) { + basePitch = basePitch & 0xffff; + } else if (basePitch < 0) { basePitch = 0; } if (basePitch > 59392) { @@ -107,18 +116,22 @@ static Bit32u calcBasePitch(const Partial *partial, const TimbreParam::PartialPa } static Bit32u calcVeloMult(Bit8u veloSensitivity, unsigned int velocity) { - if (veloSensitivity == 0 || veloSensitivity > 3) { - // Note that on CM-32L/LAPC-I veloSensitivity is never > 3, since it's clipped to 3 by the max tables. + if (veloSensitivity == 0) { return 21845; // aka floor(4096 / 12 * 64), aka ~64 semitones } + unsigned int reversedVelocity = 127 - velocity; + unsigned int scaledReversedVelocity; + if (veloSensitivity > 3) { + // Note that on CM-32L/LAPC-I veloSensitivity is never > 3, since it's clipped to 3 by the max tables. + // MT-32 GEN0 has a bug here that leads to unspecified behaviour. We assume it is as follows. + scaledReversedVelocity = (reversedVelocity << 8) >> ((3 - veloSensitivity) & 0x1f); + } else { + scaledReversedVelocity = reversedVelocity << (5 + veloSensitivity); + } // When velocity is 127, the multiplier is 21845, aka ~64 semitones (regardless of veloSensitivity). // The lower the velocity, the lower the multiplier. The veloSensitivity determines the amount decreased per velocity value. - // The minimum multiplier (with velocity 0, veloSensitivity 3) is 170 (~half a semitone). - Bit32u veloMult = 32768; - veloMult -= (127 - velocity) << (5 + veloSensitivity); - veloMult *= 21845; - veloMult >>= 15; - return veloMult; + // The minimum multiplier on CM-32L/LAPC-I (with velocity 0, veloSensitivity 3) is 170 (~half a semitone). + return ((32768 - scaledReversedVelocity) * 21845) >> 15; } static Bit32s calcTargetPitchOffsetWithoutLFO(const TimbreParam::PartialParam *partialParam, int levelIndex, unsigned int velocity) { @@ -139,7 +152,7 @@ void TVP::reset(const Part *usePart, const TimbreParam::PartialParam *usePartial // FIXME: We're using a per-TVP timer instead of a system-wide one for convenience. timeElapsed = 0; - basePitch = calcBasePitch(partial, partialParam, patchTemp, key); + basePitch = calcBasePitch(partial, partialParam, patchTemp, key, partial->getSynth()->controlROMFeatures); currentPitchOffset = calcTargetPitchOffsetWithoutLFO(partialParam, 0, velocity); targetPitchOffsetWithoutLFO = currentPitchOffset; phase = 0; @@ -166,22 +179,23 @@ Bit32u TVP::getBasePitch() const { void TVP::updatePitch() { Bit32s newPitch = basePitch + currentPitchOffset; if (!partial->isPCM() || (partial->getControlROMPCMStruct()->len & 0x01) == 0) { // FIXME: Use !partial->pcmWaveEntry->unaffectedByMasterTune instead - // FIXME: masterTune recalculation doesn't really happen here, and there are various bugs not yet emulated + // FIXME: There are various bugs not yet emulated // 171 is ~half a semitone. - newPitch += ((system->masterTune - 64) * 171) >> 6; // PORTABILITY NOTE: Assumes arithmetic shift. + newPitch += partial->getSynth()->getMasterTunePitchDelta(); } if ((partialParam->wg.pitchBenderEnabled & 1) != 0) { newPitch += part->getPitchBend(); } - if (newPitch < 0) { + + // MT-32 GEN0 does 16-bit calculations here, allowing an integer overflow. + // This quirk is exploited e.g. in Colonel's Bequest timbres "Lightning" and "SwmpBackgr". + if (partial->getSynth()->controlROMFeatures->quirkPitchEnvelopeOverflow) { + newPitch = newPitch & 0xffff; + } else if (newPitch < 0) { newPitch = 0; } - - // Skipping this check seems about right emulation of MT-32 GEN0 quirk exploited in Colonel's Bequest timbre "Lightning" - if (partial->getSynth()->controlROMFeatures->quirkPitchEnvelopeOverflow == 0) { - if (newPitch > 59392) { - newPitch = 59392; - } + if (newPitch > 59392) { + newPitch = 59392; } pitch = Bit16u(newPitch); @@ -284,13 +298,19 @@ void TVP::startDecay() { } Bit16u TVP::nextPitch() { - // FIXME: Write explanation of counter and time increment + // We emulate MCU software timer using these counter and processTimerIncrement variables. + // The value of nominalProcessTimerPeriod approximates the period in samples + // between subsequent firings of the timer that normally occur. + // However, accurate emulation is quite complicated because the timer is not guaranteed to fire in time. + // This makes pitch variations on real unit non-deterministic and dependent on various factors. if (counter == 0) { - timeElapsed += processTimerIncrement; - timeElapsed = timeElapsed & 0x00FFFFFF; + timeElapsed = (timeElapsed + processTimerIncrement) & 0x00FFFFFF; + // This roughly emulates pitch deviations observed on real units when playing a single partial that uses TVP/LFO. + counter = NOMINAL_PROCESS_TIMER_PERIOD_SAMPLES + (rand() & 3); + processTimerIncrement = (PROCESS_TIMER_INCREMENT_x8 * counter) >> 3; process(); } - counter = (counter + 1) % maxCounter; + counter--; return pitch; } diff --git a/audio/softsynth/mt32/TVP.h b/audio/softsynth/mt32/TVP.h index be90f0ff08..896e8c11ab 100644 --- a/audio/softsynth/mt32/TVP.h +++ b/audio/softsynth/mt32/TVP.h @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 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 @@ -36,7 +36,6 @@ private: const TimbreParam::PartialParam *partialParam; const MemParams::PatchTemp *patchTemp; - int maxCounter; int processTimerIncrement; int counter; Bit32u timeElapsed; diff --git a/audio/softsynth/mt32/Tables.cpp b/audio/softsynth/mt32/Tables.cpp index cb3493285a..f12caa6b61 100644 --- a/audio/softsynth/mt32/Tables.cpp +++ b/audio/softsynth/mt32/Tables.cpp @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 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 diff --git a/audio/softsynth/mt32/Tables.h b/audio/softsynth/mt32/Tables.h index 249e32919a..47465097e2 100644 --- a/audio/softsynth/mt32/Tables.h +++ b/audio/softsynth/mt32/Tables.h @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 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 diff --git a/audio/softsynth/mt32/Types.h b/audio/softsynth/mt32/Types.h index f90dce19a4..f70e4795cd 100644 --- a/audio/softsynth/mt32/Types.h +++ b/audio/softsynth/mt32/Types.h @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 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 diff --git a/audio/softsynth/mt32/c_interface/c_interface.cpp b/audio/softsynth/mt32/c_interface/c_interface.cpp index 6ae252bea5..adb1cb6b26 100644 --- a/audio/softsynth/mt32/c_interface/c_interface.cpp +++ b/audio/softsynth/mt32/c_interface/c_interface.cpp @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 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 @@ -22,6 +22,7 @@ #include "../ROMInfo.h" #include "../Synth.h" #include "../MidiStreamParser.h" +#include "../SampleRateConverter.h" #include "c_types.h" #include "c_interface.h" @@ -30,11 +31,17 @@ using namespace MT32Emu; namespace MT32Emu { +struct SamplerateConversionState { + double outputSampleRate; + SamplerateConversionQuality srcQuality; + SampleRateConverter *src; +}; + static mt32emu_service_version getSynthVersionID(mt32emu_service_i) { return MT32EMU_SERVICE_VERSION_CURRENT; } -static const mt32emu_service_i_v0 SERVICE_VTABLE = { +static const mt32emu_service_i_v2 SERVICE_VTABLE = { getSynthVersionID, mt32emu_get_supported_report_handler_version, mt32emu_get_supported_midi_receiver_version, @@ -95,7 +102,17 @@ static const mt32emu_service_i_v0 SERVICE_VTABLE = { mt32emu_get_partial_states, mt32emu_get_playing_notes, mt32emu_get_patch_name, - mt32emu_read_memory + mt32emu_read_memory, + mt32emu_get_best_analog_output_mode, + mt32emu_set_stereo_output_samplerate, + mt32emu_set_samplerate_conversion_quality, + mt32emu_select_renderer_type, + mt32emu_get_selected_renderer_type, + mt32emu_convert_output_to_synth_timestamp, + mt32emu_convert_synth_to_output_timestamp, + mt32emu_get_internal_rendered_sample_count, + mt32emu_set_nice_amp_ramp_enabled, + mt32emu_is_nice_amp_ramp_enabled }; } // namespace MT32Emu @@ -108,6 +125,7 @@ struct mt32emu_data { DefaultMidiStreamParser *midiParser; Bit32u partialCount; AnalogOutputMode analogOutputMode; + SamplerateConversionState *srcState; }; // Internal C++ utility stuff @@ -303,8 +321,9 @@ static mt32emu_return_code addROMFile(mt32emu_data *data, File *file) { extern "C" { -const mt32emu_service_i mt32emu_get_service_i() { - mt32emu_service_i i = { &SERVICE_VTABLE }; +mt32emu_service_i mt32emu_get_service_i() { + mt32emu_service_i i; + i.v2 = &SERVICE_VTABLE; return i; } @@ -328,6 +347,10 @@ mt32emu_bit32u mt32emu_get_stereo_output_samplerate(const mt32emu_analog_output_ return Synth::getStereoOutputSampleRate(static_cast<AnalogOutputMode>(analog_output_mode)); } +mt32emu_analog_output_mode mt32emu_get_best_analog_output_mode(const double target_samplerate) { + return mt32emu_analog_output_mode(SampleRateConverter::getBestAnalogOutputMode(target_samplerate)); +} + mt32emu_context mt32emu_create_context(mt32emu_report_handler_i report_handler, void *instance_data) { mt32emu_data *data = new mt32emu_data; data->reportHandler = (report_handler.v0 != NULL) ? new DelegatingReportHandlerAdapter(report_handler, instance_data) : new ReportHandler; @@ -337,11 +360,23 @@ mt32emu_context mt32emu_create_context(mt32emu_report_handler_i report_handler, data->pcmROMImage = NULL; data->partialCount = DEFAULT_MAX_PARTIALS; data->analogOutputMode = AnalogOutputMode_COARSE; + + data->srcState = new SamplerateConversionState; + data->srcState->outputSampleRate = 0.0; + data->srcState->srcQuality = SamplerateConversionQuality_GOOD; + data->srcState->src = NULL; + return data; } void mt32emu_free_context(mt32emu_context data) { if (data == NULL) return; + + delete data->srcState->src; + data->srcState->src = NULL; + delete data->srcState; + data->srcState = NULL; + if (data->controlROMImage != NULL) { delete data->controlROMImage->getFile(); ROMImage::freeROMImage(data->controlROMImage); @@ -414,18 +449,39 @@ void mt32emu_set_analog_output_mode(mt32emu_context context, const mt32emu_analo context->analogOutputMode = static_cast<AnalogOutputMode>(analog_output_mode); } +void mt32emu_set_stereo_output_samplerate(mt32emu_context context, const double samplerate) { + context->srcState->outputSampleRate = SampleRateConverter::getSupportedOutputSampleRate(samplerate); +} + +void mt32emu_set_samplerate_conversion_quality(mt32emu_context context, const mt32emu_samplerate_conversion_quality quality) { + context->srcState->srcQuality = SamplerateConversionQuality(quality); +} + +void mt32emu_select_renderer_type(mt32emu_context context, const mt32emu_renderer_type renderer_type) { + context->synth->selectRendererType(static_cast<RendererType>(renderer_type)); +} + +mt32emu_renderer_type mt32emu_get_selected_renderer_type(mt32emu_context context) { + return static_cast<mt32emu_renderer_type>(context->synth->getSelectedRendererType()); +} + mt32emu_return_code mt32emu_open_synth(mt32emu_const_context context) { if ((context->controlROMImage == NULL) || (context->pcmROMImage == NULL)) { return MT32EMU_RC_MISSING_ROMS; } - if (context->synth->open(*context->controlROMImage, *context->pcmROMImage, context->partialCount, context->analogOutputMode)) { - return MT32EMU_RC_OK; + if (!context->synth->open(*context->controlROMImage, *context->pcmROMImage, context->partialCount, context->analogOutputMode)) { + return MT32EMU_RC_FAILED; } - return MT32EMU_RC_FAILED; + SamplerateConversionState &srcState = *context->srcState; + const double outputSampleRate = (0.0 < srcState.outputSampleRate) ? srcState.outputSampleRate : context->synth->getStereoOutputSampleRate(); + srcState.src = new SampleRateConverter(*context->synth, outputSampleRate, srcState.srcQuality); + return MT32EMU_RC_OK; } void mt32emu_close_synth(mt32emu_const_context context) { context->synth->close(); + delete context->srcState->src; + context->srcState->src = NULL; } mt32emu_boolean mt32emu_is_open(mt32emu_const_context context) { @@ -433,7 +489,24 @@ mt32emu_boolean mt32emu_is_open(mt32emu_const_context context) { } mt32emu_bit32u mt32emu_get_actual_stereo_output_samplerate(mt32emu_const_context context) { - return context->synth->getStereoOutputSampleRate(); + if (context->srcState->src == NULL) { + return context->synth->getStereoOutputSampleRate(); + } + return mt32emu_bit32u(0.5 + context->srcState->src->convertSynthToOutputTimestamp(SAMPLE_RATE)); +} + +mt32emu_bit32u mt32emu_convert_output_to_synth_timestamp(mt32emu_const_context context, mt32emu_bit32u output_timestamp) { + if (context->srcState->src == NULL) { + return output_timestamp; + } + return mt32emu_bit32u(0.5 + context->srcState->src->convertOutputToSynthTimestamp(output_timestamp)); +} + +mt32emu_bit32u mt32emu_convert_synth_to_output_timestamp(mt32emu_const_context context, mt32emu_bit32u synth_timestamp) { + if (context->srcState->src == NULL) { + return synth_timestamp; + } + return mt32emu_bit32u(0.5 + context->srcState->src->convertSynthToOutputTimestamp(synth_timestamp)); } void mt32emu_flush_midi_queue(mt32emu_const_context context) { @@ -449,6 +522,10 @@ void mt32emu_set_midi_receiver(mt32emu_context context, mt32emu_midi_receiver_i context->midiParser = (midi_receiver.v0 != NULL) ? new DelegatingMidiStreamParser(context, midi_receiver, instance_data) : new DefaultMidiStreamParser(*context->synth); } +mt32emu_bit32u mt32emu_get_internal_rendered_sample_count(mt32emu_const_context context) { + return context->synth->getInternalRenderedSampleCount(); +} + void mt32emu_parse_stream(mt32emu_const_context context, const mt32emu_bit8u *stream, mt32emu_bit32u length) { context->midiParser->resetTimestamp(); context->midiParser->parseStream(stream, length); @@ -573,12 +650,28 @@ mt32emu_boolean mt32emu_is_reversed_stereo_enabled(mt32emu_const_context context return context->synth->isReversedStereoEnabled() ? MT32EMU_BOOL_TRUE : MT32EMU_BOOL_FALSE; } +void mt32emu_set_nice_amp_ramp_enabled(mt32emu_const_context context, const mt32emu_boolean enabled) { + context->synth->setNiceAmpRampEnabled(enabled != MT32EMU_BOOL_FALSE); +} + +mt32emu_boolean mt32emu_is_nice_amp_ramp_enabled(mt32emu_const_context context) { + return context->synth->isNiceAmpRampEnabled() ? MT32EMU_BOOL_TRUE : MT32EMU_BOOL_FALSE; +} + void mt32emu_render_bit16s(mt32emu_const_context context, mt32emu_bit16s *stream, mt32emu_bit32u len) { - context->synth->render(stream, len); + if (context->srcState->src != NULL) { + context->srcState->src->getOutputSamples(stream, len); + } else { + context->synth->render(stream, len); + } } void mt32emu_render_float(mt32emu_const_context context, float *stream, mt32emu_bit32u len) { - context->synth->render(stream, len); + if (context->srcState->src != NULL) { + context->srcState->src->getOutputSamples(stream, len); + } else { + context->synth->render(stream, len); + } } void mt32emu_render_bit16s_streams(mt32emu_const_context context, const mt32emu_dac_output_bit16s_streams *streams, mt32emu_bit32u len) { diff --git a/audio/softsynth/mt32/c_interface/c_interface.h b/audio/softsynth/mt32/c_interface/c_interface.h index a2bdcb1254..f736400370 100644 --- a/audio/softsynth/mt32/c_interface/c_interface.h +++ b/audio/softsynth/mt32/c_interface/c_interface.h @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 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 @@ -35,7 +35,7 @@ extern "C" { /* === Interface handling === */ /** Returns mt32emu_service_i interface. */ -MT32EMU_EXPORT const mt32emu_service_i mt32emu_get_service_i(); +MT32EMU_EXPORT mt32emu_service_i mt32emu_get_service_i(void); #if MT32EMU_EXPORTS_TYPE == 2 #undef MT32EMU_EXPORT @@ -46,13 +46,13 @@ MT32EMU_EXPORT const mt32emu_service_i mt32emu_get_service_i(); * Returns the version ID of mt32emu_report_handler_i interface the library has been compiled with. * This allows a client to fall-back gracefully instead of silently not receiving expected event reports. */ -MT32EMU_EXPORT mt32emu_report_handler_version mt32emu_get_supported_report_handler_version(); +MT32EMU_EXPORT mt32emu_report_handler_version mt32emu_get_supported_report_handler_version(void); /** * Returns the version ID of mt32emu_midi_receiver_version_i interface the library has been compiled with. * This allows a client to fall-back gracefully instead of silently not receiving expected MIDI messages. */ -MT32EMU_EXPORT mt32emu_midi_receiver_version mt32emu_get_supported_midi_receiver_version(); +MT32EMU_EXPORT mt32emu_midi_receiver_version mt32emu_get_supported_midi_receiver_version(void); /** * Returns library version as an integer in format: 0x00MMmmpp, where: @@ -60,12 +60,12 @@ MT32EMU_EXPORT mt32emu_midi_receiver_version mt32emu_get_supported_midi_receiver * mm - minor version number * pp - patch number */ -MT32EMU_EXPORT mt32emu_bit32u mt32emu_get_library_version_int(); +MT32EMU_EXPORT mt32emu_bit32u mt32emu_get_library_version_int(void); /** * Returns library version as a C-string in format: "MAJOR.MINOR.PATCH". */ -MT32EMU_EXPORT const char *mt32emu_get_library_version_string(); +MT32EMU_EXPORT const char *mt32emu_get_library_version_string(void); /** * Returns output sample rate used in emulation of stereo analog circuitry of hardware units for particular analog_output_mode. @@ -73,6 +73,13 @@ MT32EMU_EXPORT const char *mt32emu_get_library_version_string(); */ MT32EMU_EXPORT mt32emu_bit32u mt32emu_get_stereo_output_samplerate(const mt32emu_analog_output_mode analog_output_mode); +/** + * Returns the value of analog_output_mode for which the output signal may retain its full frequency spectrum + * at the sample rate specified by the target_samplerate argument. + * See comment for mt32emu_analog_output_mode. + */ +MT32EMU_EXPORT mt32emu_analog_output_mode mt32emu_get_best_analog_output_mode(const double target_samplerate); + /* == Context-dependent functions == */ /** Initialises a new emulation context and installs custom report handler if non-NULL. */ @@ -105,17 +112,50 @@ MT32EMU_EXPORT void mt32emu_get_rom_info(mt32emu_const_context context, mt32emu_ /** * Allows to override the default maximum number of partials playing simultaneously within the emulation session. - * This function doesn't immediately change the state of already opened synth. Newly set vale will take effect upon next call of mt32emu_open_synth(). + * This function doesn't immediately change the state of already opened synth. Newly set value will take effect upon next call of mt32emu_open_synth(). */ MT32EMU_EXPORT void mt32emu_set_partial_count(mt32emu_context context, const mt32emu_bit32u partial_count); /** * Allows to override the default mode for emulation of analogue circuitry of the hardware units within the emulation session. - * This function doesn't immediately change the state of already opened synth. Newly set vale will take effect upon next call of mt32emu_open_synth(). + * This function doesn't immediately change the state of already opened synth. Newly set value will take effect upon next call of mt32emu_open_synth(). */ MT32EMU_EXPORT void mt32emu_set_analog_output_mode(mt32emu_context context, const mt32emu_analog_output_mode analog_output_mode); /** + * Allows to convert the synthesiser output to any desired sample rate. The samplerate conversion + * processes the completely mixed stereo output signal as it passes the analogue circuit emulation, + * so emulating the synthesiser output signal passing further through an ADC. When the samplerate + * argument is set to 0, the default output sample rate is used which depends on the current + * mode of analog circuitry emulation. See mt32emu_analog_output_mode. + * This function doesn't immediately change the state of already opened synth. + * Newly set value will take effect upon next call of mt32emu_open_synth(). + */ +MT32EMU_EXPORT void mt32emu_set_stereo_output_samplerate(mt32emu_context context, const double samplerate); + +/** + * Several samplerate conversion quality options are provided which allow to trade-off the conversion speed vs. + * the retained passband width. All the options except FASTEST guarantee full suppression of the aliasing noise + * in terms of the 16-bit integer samples. + * This function doesn't immediately change the state of already opened synth. + * Newly set value will take effect upon next call of mt32emu_open_synth(). + */ +MT32EMU_EXPORT void mt32emu_set_samplerate_conversion_quality(mt32emu_context context, const mt32emu_samplerate_conversion_quality quality); + +/** + * Selects new type of the wave generator and renderer to be used during subsequent calls to mt32emu_open_synth(). + * By default, MT32EMU_RT_BIT16S is selected. + * See mt32emu_renderer_type for details. + */ +MT32EMU_EXPORT void mt32emu_select_renderer_type(mt32emu_context context, const mt32emu_renderer_type renderer_type); + +/** + * Returns previously selected type of the wave generator and renderer. + * See mt32emu_renderer_type for details. + */ +MT32EMU_EXPORT mt32emu_renderer_type mt32emu_get_selected_renderer_type(mt32emu_context context); + +/** * Prepares the emulation context to receive MIDI messages and produce output audio data using aforehand added set of ROMs, * and optionally set the maximum partial count and the analog output mode. * Returns MT32EMU_RC_OK upon success. @@ -129,11 +169,28 @@ MT32EMU_EXPORT void mt32emu_close_synth(mt32emu_const_context context); MT32EMU_EXPORT mt32emu_boolean mt32emu_is_open(mt32emu_const_context context); /** - * Returns actual output sample rate used in emulation of stereo analog circuitry of hardware units. - * See comment for mt32emu_analog_output_mode. + * Returns actual sample rate of the fully processed output stereo signal. + * If samplerate conversion is used (i.e. when mt32emu_set_stereo_output_samplerate() has been invoked with a non-zero value), + * the returned value is the desired output samplerate rounded down to the closest integer. + * Otherwise, the output samplerate is choosen depending on the emulation mode of stereo analog circuitry of hardware units. + * See comment for mt32emu_analog_output_mode for more info. */ MT32EMU_EXPORT mt32emu_bit32u mt32emu_get_actual_stereo_output_samplerate(mt32emu_const_context context); +/** + * Returns the number of samples produced at the internal synth sample rate (32000 Hz) + * that correspond to the given number of samples at the output sample rate. + * Intended to facilitate audio time synchronisation. + */ +MT32EMU_EXPORT mt32emu_bit32u mt32emu_convert_output_to_synth_timestamp(mt32emu_const_context context, mt32emu_bit32u output_timestamp); + +/** + * Returns the number of samples produced at the output sample rate + * that correspond to the given number of samples at the internal synth sample rate (32000 Hz). + * Intended to facilitate audio time synchronisation. + */ +MT32EMU_EXPORT mt32emu_bit32u mt32emu_convert_synth_to_output_timestamp(mt32emu_const_context context, mt32emu_bit32u synth_timestamp); + /** All the enqueued events are processed by the synth immediately. */ MT32EMU_EXPORT void mt32emu_flush_midi_queue(mt32emu_const_context context); @@ -152,6 +209,12 @@ MT32EMU_EXPORT mt32emu_bit32u mt32emu_set_midi_event_queue_size(mt32emu_const_co */ MT32EMU_EXPORT void mt32emu_set_midi_receiver(mt32emu_context context, mt32emu_midi_receiver_i midi_receiver, void *instance_data); +/** + * Returns current value of the global counter of samples rendered since the synth was created (at the native sample rate 32000 Hz). + * This method helps to compute accurate timestamp of a MIDI message to use with the methods below. + */ +MT32EMU_EXPORT mt32emu_bit32u mt32emu_get_internal_rendered_sample_count(mt32emu_const_context context); + /* Enqueues a MIDI event for subsequent playback. * 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 (at the native sample rate 32000 Hz). @@ -267,7 +330,6 @@ MT32EMU_EXPORT mt32emu_midi_delay_mode mt32emu_get_midi_delay_mode(mt32emu_const * Sets output gain factor for synth output channels. Applied to all output samples and unrelated with the synth's Master volume, * it rather corresponds to the gain of the output analog circuitry of the hardware units. However, together with mt32emu_set_reverb_output_gain() * it offers to the user a capability to control the gain of reverb and non-reverb output channels independently. - * Ignored in MT32EMU_DAC_PURE mode. */ MT32EMU_EXPORT void mt32emu_set_output_gain(mt32emu_const_context context, float gain); /** Returns current output gain factor for synth output channels. */ @@ -282,7 +344,6 @@ MT32EMU_EXPORT float mt32emu_get_output_gain(mt32emu_const_context context); * 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 MT32EMU_DAC_PURE mode. */ MT32EMU_EXPORT void mt32emu_set_reverb_output_gain(mt32emu_const_context context, float gain); /** Returns current output gain factor for reverb wet output channels. */ @@ -294,10 +355,21 @@ MT32EMU_EXPORT void mt32emu_set_reversed_stereo_enabled(mt32emu_const_context co MT32EMU_EXPORT mt32emu_boolean mt32emu_is_reversed_stereo_enabled(mt32emu_const_context context); /** - * Renders samples to the specified output stream as if they were sampled at the analog stereo output. - * When mt32emu_analog_output_mode is set to ACCURATE (OVERSAMPLED), the output signal is upsampled to 48 (96) kHz in order - * to retain emulation accuracy in whole audible frequency spectra. Otherwise, native digital signal sample rate is retained. - * mt32emu_get_actual_stereo_output_samplerate() can be used to query actual sample rate of the output signal. + * Allows to toggle the NiceAmpRamp mode. + * In this mode, we want to ensure that amp ramp never jumps to the target + * value and always gradually increases or decreases. It seems that real units + * do not bother to always check if a newly started ramp leads to a jump. + * We also prefer the quality improvement over the emulation accuracy, + * so this mode is enabled by default. + */ +MT32EMU_EXPORT void mt32emu_set_nice_amp_ramp_enabled(mt32emu_const_context context, const mt32emu_boolean enabled); +/** Returns whether NiceAmpRamp mode is enabled. */ +MT32EMU_EXPORT mt32emu_boolean mt32emu_is_nice_amp_ramp_enabled(mt32emu_const_context context); + +/** + * Renders samples to the specified output stream as if they were sampled at the analog stereo output at the desired sample rate. + * If the output sample rate is not specified explicitly, the default output sample rate is used which depends on the current + * mode of analog circuitry emulation. See mt32emu_analog_output_mode. * The length is in frames, not bytes (in 16-bit stereo, one frame is 4 bytes). Uses NATIVE byte ordering. */ MT32EMU_EXPORT void mt32emu_render_bit16s(mt32emu_const_context context, mt32emu_bit16s *stream, mt32emu_bit32u len); diff --git a/audio/softsynth/mt32/c_interface/c_types.h b/audio/softsynth/mt32/c_interface/c_types.h index 3cd8744235..dada610bd4 100644 --- a/audio/softsynth/mt32/c_interface/c_types.h +++ b/audio/softsynth/mt32/c_interface/c_types.h @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 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 @@ -68,6 +68,8 @@ typedef enum mt32emu_analog_output_mode mt32emu_analog_output_mode; typedef enum mt32emu_dac_input_mode mt32emu_dac_input_mode; typedef enum mt32emu_midi_delay_mode mt32emu_midi_delay_mode; typedef enum mt32emu_partial_state mt32emu_partial_state; +typedef enum mt32emu_samplerate_conversion_quality mt32emu_samplerate_conversion_quality; +typedef enum mt32emu_renderer_type mt32emu_renderer_type; #endif /** Contains identifiers and descriptions of ROM files being used. */ @@ -117,7 +119,9 @@ typedef enum { /** Synth interface versions */ typedef enum { MT32EMU_SERVICE_VERSION_0 = 0, - MT32EMU_SERVICE_VERSION_CURRENT = MT32EMU_SERVICE_VERSION_0 + MT32EMU_SERVICE_VERSION_1 = 1, + MT32EMU_SERVICE_VERSION_2 = 2, + MT32EMU_SERVICE_VERSION_CURRENT = MT32EMU_SERVICE_VERSION_2 } mt32emu_service_version; /* === Report Handler Interface === */ @@ -164,7 +168,7 @@ typedef struct { /** * Extensible interface for handling reported events. * Union intended to view an interface of any subsequent version as any parent interface not requiring a cast. - * Elements are to be addressed using the tag of the interface version when they were introduced. + * It is caller's responsibility to check the actual interface version in runtime using the getVersionID() method. */ union mt32emu_report_handler_i { const mt32emu_report_handler_i_v0 *v0; @@ -192,7 +196,7 @@ typedef struct { /** * Extensible interface for receiving MIDI messages. * Union intended to view an interface of any subsequent version as any parent interface not requiring a cast. - * Elements are to be addressed using the tag of the interface version when they were introduced. + * It is caller's responsibility to check the actual interface version in runtime using the getVersionID() method. */ union mt32emu_midi_receiver_i { const mt32emu_midi_receiver_i_v0 *v0; @@ -209,90 +213,124 @@ typedef union mt32emu_service_i mt32emu_service_i; * to bind to mt32emu_get_service_i() function instead of binding to each function it needs to use. * See c_interface.h for parameter description. */ -typedef struct { - /** Returns the actual interface version ID */ - mt32emu_service_version (*getVersionID)(mt32emu_service_i i); - mt32emu_report_handler_version (*getSupportedReportHandlerVersionID)(); - mt32emu_midi_receiver_version (*getSupportedMIDIReceiverVersionID)(); - - mt32emu_bit32u (*getLibraryVersionInt)(); - const char *(*getLibraryVersionString)(); - - mt32emu_bit32u (*getStereoOutputSamplerate)(const mt32emu_analog_output_mode analog_output_mode); - - mt32emu_context (*createContext)(mt32emu_report_handler_i report_handler, void *instance_data); - void (*freeContext)(mt32emu_context context); - mt32emu_return_code (*addROMData)(mt32emu_context context, const mt32emu_bit8u *data, size_t data_size, const mt32emu_sha1_digest *sha1_digest); - mt32emu_return_code (*addROMFile)(mt32emu_context context, const char *filename); - void (*getROMInfo)(mt32emu_const_context context, mt32emu_rom_info *rom_info); - void (*setPartialCount)(mt32emu_context context, const mt32emu_bit32u partial_count); - void (*setAnalogOutputMode)(mt32emu_context context, const mt32emu_analog_output_mode analog_output_mode); - mt32emu_return_code (*openSynth)(mt32emu_const_context context); - void (*closeSynth)(mt32emu_const_context context); - mt32emu_boolean (*isOpen)(mt32emu_const_context context); - mt32emu_bit32u (*getActualStereoOutputSamplerate)(mt32emu_const_context context); - void (*flushMIDIQueue)(mt32emu_const_context context); - mt32emu_bit32u (*setMIDIEventQueueSize)(mt32emu_const_context context, const mt32emu_bit32u queue_size); - void (*setMIDIReceiver)(mt32emu_context context, mt32emu_midi_receiver_i midi_receiver, void *instance_data); - - void (*parseStream)(mt32emu_const_context context, const mt32emu_bit8u *stream, mt32emu_bit32u length); - void (*parseStream_At)(mt32emu_const_context context, const mt32emu_bit8u *stream, mt32emu_bit32u length, mt32emu_bit32u timestamp); - void (*playShortMessage)(mt32emu_const_context context, mt32emu_bit32u message); - void (*playShortMessageAt)(mt32emu_const_context context, mt32emu_bit32u message, mt32emu_bit32u timestamp); - mt32emu_return_code (*playMsg)(mt32emu_const_context context, mt32emu_bit32u msg); - mt32emu_return_code (*playSysex)(mt32emu_const_context context, const mt32emu_bit8u *sysex, mt32emu_bit32u len); - mt32emu_return_code (*playMsgAt)(mt32emu_const_context context, mt32emu_bit32u msg, mt32emu_bit32u timestamp); - mt32emu_return_code (*playSysexAt)(mt32emu_const_context context, const mt32emu_bit8u *sysex, mt32emu_bit32u len, mt32emu_bit32u timestamp); - - void (*playMsgNow)(mt32emu_const_context context, mt32emu_bit32u msg); - void (*playMsgOnPart)(mt32emu_const_context context, mt32emu_bit8u part, mt32emu_bit8u code, mt32emu_bit8u note, mt32emu_bit8u velocity); - void (*playSysexNow)(mt32emu_const_context context, const mt32emu_bit8u *sysex, mt32emu_bit32u len); - void (*writeSysex)(mt32emu_const_context context, mt32emu_bit8u channel, const mt32emu_bit8u *sysex, mt32emu_bit32u len); - - void (*setReverbEnabled)(mt32emu_const_context context, const mt32emu_boolean reverb_enabled); - mt32emu_boolean (*isReverbEnabled)(mt32emu_const_context context); - void (*setReverbOverridden)(mt32emu_const_context context, const mt32emu_boolean reverb_overridden); - mt32emu_boolean (*isReverbOverridden)(mt32emu_const_context context); - void (*setReverbCompatibilityMode)(mt32emu_const_context context, const mt32emu_boolean mt32_compatible_mode); - mt32emu_boolean (*isMT32ReverbCompatibilityMode)(mt32emu_const_context context); - mt32emu_boolean (*isDefaultReverbMT32Compatible)(mt32emu_const_context context); - - void (*setDACInputMode)(mt32emu_const_context context, const mt32emu_dac_input_mode mode); - mt32emu_dac_input_mode (*getDACInputMode)(mt32emu_const_context context); - - void (*setMIDIDelayMode)(mt32emu_const_context context, const mt32emu_midi_delay_mode mode); - mt32emu_midi_delay_mode (*getMIDIDelayMode)(mt32emu_const_context context); - - void (*setOutputGain)(mt32emu_const_context context, float gain); - float (*getOutputGain)(mt32emu_const_context context); - void (*setReverbOutputGain)(mt32emu_const_context context, float gain); - float (*getReverbOutputGain)(mt32emu_const_context context); - - void (*setReversedStereoEnabled)(mt32emu_const_context context, const mt32emu_boolean enabled); - mt32emu_boolean (*isReversedStereoEnabled)(mt32emu_const_context context); - - void (*renderBit16s)(mt32emu_const_context context, mt32emu_bit16s *stream, mt32emu_bit32u len); - void (*renderFloat)(mt32emu_const_context context, float *stream, mt32emu_bit32u len); - void (*renderBit16sStreams)(mt32emu_const_context context, const mt32emu_dac_output_bit16s_streams *streams, mt32emu_bit32u len); - void (*renderFloatStreams)(mt32emu_const_context context, const mt32emu_dac_output_float_streams *streams, mt32emu_bit32u len); - - mt32emu_boolean (*hasActivePartials)(mt32emu_const_context context); - mt32emu_boolean (*isActive)(mt32emu_const_context context); - mt32emu_bit32u (*getPartialCount)(mt32emu_const_context context); - mt32emu_bit32u (*getPartStates)(mt32emu_const_context context); - void (*getPartialStates)(mt32emu_const_context context, mt32emu_bit8u *partial_states); - mt32emu_bit32u (*getPlayingNotes)(mt32emu_const_context context, mt32emu_bit8u part_number, mt32emu_bit8u *keys, mt32emu_bit8u *velocities); - const char *(*getPatchName)(mt32emu_const_context context, mt32emu_bit8u part_number); +#define MT32EMU_SERVICE_I_V0 \ + /** Returns the actual interface version ID */ \ + mt32emu_service_version (*getVersionID)(mt32emu_service_i i); \ + mt32emu_report_handler_version (*getSupportedReportHandlerVersionID)(void); \ + mt32emu_midi_receiver_version (*getSupportedMIDIReceiverVersionID)(void); \ +\ + mt32emu_bit32u (*getLibraryVersionInt)(void); \ + const char *(*getLibraryVersionString)(void); \ +\ + mt32emu_bit32u (*getStereoOutputSamplerate)(const mt32emu_analog_output_mode analog_output_mode); \ +\ + mt32emu_context (*createContext)(mt32emu_report_handler_i report_handler, void *instance_data); \ + void (*freeContext)(mt32emu_context context); \ + mt32emu_return_code (*addROMData)(mt32emu_context context, const mt32emu_bit8u *data, size_t data_size, const mt32emu_sha1_digest *sha1_digest); \ + mt32emu_return_code (*addROMFile)(mt32emu_context context, const char *filename); \ + void (*getROMInfo)(mt32emu_const_context context, mt32emu_rom_info *rom_info); \ + void (*setPartialCount)(mt32emu_context context, const mt32emu_bit32u partial_count); \ + void (*setAnalogOutputMode)(mt32emu_context context, const mt32emu_analog_output_mode analog_output_mode); \ + mt32emu_return_code (*openSynth)(mt32emu_const_context context); \ + void (*closeSynth)(mt32emu_const_context context); \ + mt32emu_boolean (*isOpen)(mt32emu_const_context context); \ + mt32emu_bit32u (*getActualStereoOutputSamplerate)(mt32emu_const_context context); \ + void (*flushMIDIQueue)(mt32emu_const_context context); \ + mt32emu_bit32u (*setMIDIEventQueueSize)(mt32emu_const_context context, const mt32emu_bit32u queue_size); \ + void (*setMIDIReceiver)(mt32emu_context context, mt32emu_midi_receiver_i midi_receiver, void *instance_data); \ +\ + void (*parseStream)(mt32emu_const_context context, const mt32emu_bit8u *stream, mt32emu_bit32u length); \ + void (*parseStream_At)(mt32emu_const_context context, const mt32emu_bit8u *stream, mt32emu_bit32u length, mt32emu_bit32u timestamp); \ + void (*playShortMessage)(mt32emu_const_context context, mt32emu_bit32u message); \ + void (*playShortMessageAt)(mt32emu_const_context context, mt32emu_bit32u message, mt32emu_bit32u timestamp); \ + mt32emu_return_code (*playMsg)(mt32emu_const_context context, mt32emu_bit32u msg); \ + mt32emu_return_code (*playSysex)(mt32emu_const_context context, const mt32emu_bit8u *sysex, mt32emu_bit32u len); \ + mt32emu_return_code (*playMsgAt)(mt32emu_const_context context, mt32emu_bit32u msg, mt32emu_bit32u timestamp); \ + mt32emu_return_code (*playSysexAt)(mt32emu_const_context context, const mt32emu_bit8u *sysex, mt32emu_bit32u len, mt32emu_bit32u timestamp); \ +\ + void (*playMsgNow)(mt32emu_const_context context, mt32emu_bit32u msg); \ + void (*playMsgOnPart)(mt32emu_const_context context, mt32emu_bit8u part, mt32emu_bit8u code, mt32emu_bit8u note, mt32emu_bit8u velocity); \ + void (*playSysexNow)(mt32emu_const_context context, const mt32emu_bit8u *sysex, mt32emu_bit32u len); \ + void (*writeSysex)(mt32emu_const_context context, mt32emu_bit8u channel, const mt32emu_bit8u *sysex, mt32emu_bit32u len); \ +\ + void (*setReverbEnabled)(mt32emu_const_context context, const mt32emu_boolean reverb_enabled); \ + mt32emu_boolean (*isReverbEnabled)(mt32emu_const_context context); \ + void (*setReverbOverridden)(mt32emu_const_context context, const mt32emu_boolean reverb_overridden); \ + mt32emu_boolean (*isReverbOverridden)(mt32emu_const_context context); \ + void (*setReverbCompatibilityMode)(mt32emu_const_context context, const mt32emu_boolean mt32_compatible_mode); \ + mt32emu_boolean (*isMT32ReverbCompatibilityMode)(mt32emu_const_context context); \ + mt32emu_boolean (*isDefaultReverbMT32Compatible)(mt32emu_const_context context); \ +\ + void (*setDACInputMode)(mt32emu_const_context context, const mt32emu_dac_input_mode mode); \ + mt32emu_dac_input_mode (*getDACInputMode)(mt32emu_const_context context); \ +\ + void (*setMIDIDelayMode)(mt32emu_const_context context, const mt32emu_midi_delay_mode mode); \ + mt32emu_midi_delay_mode (*getMIDIDelayMode)(mt32emu_const_context context); \ +\ + void (*setOutputGain)(mt32emu_const_context context, float gain); \ + float (*getOutputGain)(mt32emu_const_context context); \ + void (*setReverbOutputGain)(mt32emu_const_context context, float gain); \ + float (*getReverbOutputGain)(mt32emu_const_context context); \ +\ + void (*setReversedStereoEnabled)(mt32emu_const_context context, const mt32emu_boolean enabled); \ + mt32emu_boolean (*isReversedStereoEnabled)(mt32emu_const_context context); \ +\ + void (*renderBit16s)(mt32emu_const_context context, mt32emu_bit16s *stream, mt32emu_bit32u len); \ + void (*renderFloat)(mt32emu_const_context context, float *stream, mt32emu_bit32u len); \ + void (*renderBit16sStreams)(mt32emu_const_context context, const mt32emu_dac_output_bit16s_streams *streams, mt32emu_bit32u len); \ + void (*renderFloatStreams)(mt32emu_const_context context, const mt32emu_dac_output_float_streams *streams, mt32emu_bit32u len); \ +\ + mt32emu_boolean (*hasActivePartials)(mt32emu_const_context context); \ + mt32emu_boolean (*isActive)(mt32emu_const_context context); \ + mt32emu_bit32u (*getPartialCount)(mt32emu_const_context context); \ + mt32emu_bit32u (*getPartStates)(mt32emu_const_context context); \ + void (*getPartialStates)(mt32emu_const_context context, mt32emu_bit8u *partial_states); \ + mt32emu_bit32u (*getPlayingNotes)(mt32emu_const_context context, mt32emu_bit8u part_number, mt32emu_bit8u *keys, mt32emu_bit8u *velocities); \ + const char *(*getPatchName)(mt32emu_const_context context, mt32emu_bit8u part_number); \ void (*readMemory)(mt32emu_const_context context, mt32emu_bit32u addr, mt32emu_bit32u len, mt32emu_bit8u *data); + +#define MT32EMU_SERVICE_I_V1 \ + mt32emu_analog_output_mode (*getBestAnalogOutputMode)(const double target_samplerate); \ + void (*setStereoOutputSampleRate)(mt32emu_context context, const double samplerate); \ + void (*setSamplerateConversionQuality)(mt32emu_context context, const mt32emu_samplerate_conversion_quality quality); \ + void (*selectRendererType)(mt32emu_context context, mt32emu_renderer_type renderer_type); \ + mt32emu_renderer_type (*getSelectedRendererType)(mt32emu_context context); \ + mt32emu_bit32u (*convertOutputToSynthTimestamp)(mt32emu_const_context context, mt32emu_bit32u output_timestamp); \ + mt32emu_bit32u (*convertSynthToOutputTimestamp)(mt32emu_const_context context, mt32emu_bit32u synth_timestamp); + +#define MT32EMU_SERVICE_I_V2 \ + mt32emu_bit32u (*getInternalRenderedSampleCount)(mt32emu_const_context context); \ + void (*setNiceAmpRampEnabled)(mt32emu_const_context context, const mt32emu_boolean enabled); \ + mt32emu_boolean (*isNiceAmpRampEnabled)(mt32emu_const_context context); + +typedef struct { + MT32EMU_SERVICE_I_V0 } mt32emu_service_i_v0; +typedef struct { + MT32EMU_SERVICE_I_V0 + MT32EMU_SERVICE_I_V1 +} mt32emu_service_i_v1; + +typedef struct { + MT32EMU_SERVICE_I_V0 + MT32EMU_SERVICE_I_V1 + MT32EMU_SERVICE_I_V2 +} mt32emu_service_i_v2; + /** * Extensible interface for all the library services. * Union intended to view an interface of any subsequent version as any parent interface not requiring a cast. - * Elements are to be addressed using the tag of the interface version when they were introduced. + * It is caller's responsibility to check the actual interface version in runtime using the getVersionID() method. */ union mt32emu_service_i { const mt32emu_service_i_v0 *v0; + const mt32emu_service_i_v1 *v1; + const mt32emu_service_i_v2 *v2; }; +#undef MT32EMU_SERVICE_I_V0 +#undef MT32EMU_SERVICE_I_V1 +#undef MT32EMU_SERVICE_I_V2 + #endif /* #ifndef MT32EMU_C_TYPES_H */ diff --git a/audio/softsynth/mt32/c_interface/cpp_interface.h b/audio/softsynth/mt32/c_interface/cpp_interface.h index 3e86322faa..3b02c03258 100644 --- a/audio/softsynth/mt32/c_interface/cpp_interface.h +++ b/audio/softsynth/mt32/c_interface/cpp_interface.h @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 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 @@ -28,11 +28,19 @@ #if MT32EMU_API_TYPE == 2 +extern "C" { + +/** Returns mt32emu_service_i interface. */ +mt32emu_service_i mt32emu_get_service_i(); + +} + #define mt32emu_get_supported_report_handler_version i.v0->getSupportedReportHandlerVersionID #define mt32emu_get_supported_midi_receiver_version i.v0->getSupportedMIDIReceiverVersionID #define mt32emu_get_library_version_int i.v0->getLibraryVersionInt #define mt32emu_get_library_version_string i.v0->getLibraryVersionString #define mt32emu_get_stereo_output_samplerate i.v0->getStereoOutputSamplerate +#define mt32emu_get_best_analog_output_mode iV1()->getBestAnalogOutputMode #define mt32emu_create_context i.v0->createContext #define mt32emu_free_context i.v0->freeContext #define mt32emu_add_rom_data i.v0->addROMData @@ -40,13 +48,20 @@ #define mt32emu_get_rom_info i.v0->getROMInfo #define mt32emu_set_partial_count i.v0->setPartialCount #define mt32emu_set_analog_output_mode i.v0->setAnalogOutputMode +#define mt32emu_set_stereo_output_samplerate iV1()->setStereoOutputSampleRate +#define mt32emu_set_samplerate_conversion_quality iV1()->setSamplerateConversionQuality +#define mt32emu_select_renderer_type iV1()->selectRendererType +#define mt32emu_get_selected_renderer_type iV1()->getSelectedRendererType #define mt32emu_open_synth i.v0->openSynth #define mt32emu_close_synth i.v0->closeSynth #define mt32emu_is_open i.v0->isOpen #define mt32emu_get_actual_stereo_output_samplerate i.v0->getActualStereoOutputSamplerate +#define mt32emu_convert_output_to_synth_timestamp iV1()->convertOutputToSynthTimestamp +#define mt32emu_convert_synth_to_output_timestamp iV1()->convertSynthToOutputTimestamp #define mt32emu_flush_midi_queue i.v0->flushMIDIQueue #define mt32emu_set_midi_event_queue_size i.v0->setMIDIEventQueueSize #define mt32emu_set_midi_receiver i.v0->setMIDIReceiver +#define mt32emu_get_internal_rendered_sample_count iV2()->getInternalRenderedSampleCount #define mt32emu_parse_stream i.v0->parseStream #define mt32emu_parse_stream_at i.v0->parseStream_At #define mt32emu_play_short_message i.v0->playShortMessage @@ -76,6 +91,8 @@ #define mt32emu_get_reverb_output_gain i.v0->getReverbOutputGain #define mt32emu_set_reversed_stereo_enabled i.v0->setReversedStereoEnabled #define mt32emu_is_reversed_stereo_enabled i.v0->isReversedStereoEnabled +#define mt32emu_set_nice_amp_ramp_enabled iV2()->setNiceAmpRampEnabled +#define mt32emu_is_nice_amp_ramp_enabled iV2()->isNiceAmpRampEnabled #define mt32emu_render_bit16s i.v0->renderBit16s #define mt32emu_render_float i.v0->renderFloat #define mt32emu_render_bit16s_streams i.v0->renderBit16sStreams @@ -171,6 +188,7 @@ public: const char *getLibraryVersionString() { return mt32emu_get_library_version_string(); } Bit32u getStereoOutputSamplerate(const AnalogOutputMode analog_output_mode) { return mt32emu_get_stereo_output_samplerate(static_cast<mt32emu_analog_output_mode>(analog_output_mode)); } + AnalogOutputMode getBestAnalogOutputMode(const double target_samplerate) { return static_cast<AnalogOutputMode>(mt32emu_get_best_analog_output_mode(target_samplerate)); } // Context-dependent methods @@ -183,15 +201,22 @@ public: void getROMInfo(mt32emu_rom_info *rom_info) { mt32emu_get_rom_info(c, rom_info); } void setPartialCount(const Bit32u partial_count) { mt32emu_set_partial_count(c, partial_count); } void setAnalogOutputMode(const AnalogOutputMode analog_output_mode) { mt32emu_set_analog_output_mode(c, static_cast<mt32emu_analog_output_mode>(analog_output_mode)); } + void setStereoOutputSampleRate(const double samplerate) { mt32emu_set_stereo_output_samplerate(c, samplerate); } + void setSamplerateConversionQuality(const SamplerateConversionQuality quality) { mt32emu_set_samplerate_conversion_quality(c, static_cast<mt32emu_samplerate_conversion_quality>(quality)); } + void selectRendererType(const RendererType newRendererType) { mt32emu_select_renderer_type(c, static_cast<mt32emu_renderer_type>(newRendererType)); } + RendererType getSelectedRendererType() { return static_cast<RendererType>(mt32emu_get_selected_renderer_type(c)); } mt32emu_return_code openSynth() { return mt32emu_open_synth(c); } void closeSynth() { mt32emu_close_synth(c); } bool isOpen() { return mt32emu_is_open(c) != MT32EMU_BOOL_FALSE; } Bit32u getActualStereoOutputSamplerate() { return mt32emu_get_actual_stereo_output_samplerate(c); } + Bit32u convertOutputToSynthTimestamp(Bit32u output_timestamp) { return mt32emu_convert_output_to_synth_timestamp(c, output_timestamp); } + Bit32u convertSynthToOutputTimestamp(Bit32u synth_timestamp) { return mt32emu_convert_synth_to_output_timestamp(c, synth_timestamp); } void flushMIDIQueue() { mt32emu_flush_midi_queue(c); } Bit32u setMIDIEventQueueSize(const Bit32u queue_size) { return mt32emu_set_midi_event_queue_size(c, queue_size); } void setMIDIReceiver(mt32emu_midi_receiver_i midi_receiver, void *instance_data) { mt32emu_set_midi_receiver(c, midi_receiver, instance_data); } void setMIDIReceiver(IMidiReceiver &midi_receiver) { setMIDIReceiver(CppInterfaceImpl::getMidiReceiverThunk(), &midi_receiver); } + Bit32u getInternalRenderedSampleCount() { return mt32emu_get_internal_rendered_sample_count(c); } void parseStream(const Bit8u *stream, Bit32u length) { mt32emu_parse_stream(c, stream, length); } void parseStream_At(const Bit8u *stream, Bit32u length, Bit32u timestamp) { mt32emu_parse_stream_at(c, stream, length, timestamp); } void playShortMessage(Bit32u message) { mt32emu_play_short_message(c, message); } @@ -228,6 +253,9 @@ public: void setReversedStereoEnabled(const bool enabled) { mt32emu_set_reversed_stereo_enabled(c, enabled ? MT32EMU_BOOL_TRUE : MT32EMU_BOOL_FALSE); } bool isReversedStereoEnabled() { return mt32emu_is_reversed_stereo_enabled(c) != MT32EMU_BOOL_FALSE; } + void setNiceAmpRampEnabled(const bool enabled) { mt32emu_set_nice_amp_ramp_enabled(c, enabled ? MT32EMU_BOOL_TRUE : MT32EMU_BOOL_FALSE); } + bool isNiceAmpRampEnabled() { return mt32emu_is_nice_amp_ramp_enabled(c) != MT32EMU_BOOL_FALSE; } + void renderBit16s(Bit16s *stream, Bit32u len) { mt32emu_render_bit16s(c, stream, len); } void renderFloat(float *stream, Bit32u len) { mt32emu_render_float(c, stream, len); } void renderBit16sStreams(const mt32emu_dac_output_bit16s_streams *streams, Bit32u len) { mt32emu_render_bit16s_streams(c, streams, len); } @@ -247,6 +275,11 @@ private: const mt32emu_service_i i; #endif mt32emu_context c; + +#if MT32EMU_API_TYPE == 2 + const mt32emu_service_i_v1 *iV1() { return (getVersionID() < MT32EMU_SERVICE_VERSION_1) ? NULL : i.v1; } + const mt32emu_service_i_v2 *iV2() { return (getVersionID() < MT32EMU_SERVICE_VERSION_2) ? NULL : i.v2; } +#endif }; namespace CppInterfaceImpl { @@ -256,59 +289,59 @@ static mt32emu_report_handler_version getReportHandlerVersionID(mt32emu_report_h } static void printDebug(void *instance_data, const char *fmt, va_list list) { - ((IReportHandler *)instance_data)->printDebug(fmt, list); + static_cast<IReportHandler *>(instance_data)->printDebug(fmt, list); } static void onErrorControlROM(void *instance_data) { - ((IReportHandler *)instance_data)->onErrorControlROM(); + static_cast<IReportHandler *>(instance_data)->onErrorControlROM(); } static void onErrorPCMROM(void *instance_data) { - ((IReportHandler *)instance_data)->onErrorPCMROM(); + static_cast<IReportHandler *>(instance_data)->onErrorPCMROM(); } static void showLCDMessage(void *instance_data, const char *message) { - ((IReportHandler *)instance_data)->showLCDMessage(message); + static_cast<IReportHandler *>(instance_data)->showLCDMessage(message); } static void onMIDIMessagePlayed(void *instance_data) { - ((IReportHandler *)instance_data)->onMIDIMessagePlayed(); + static_cast<IReportHandler *>(instance_data)->onMIDIMessagePlayed(); } static mt32emu_boolean onMIDIQueueOverflow(void *instance_data) { - return ((IReportHandler *)instance_data)->onMIDIQueueOverflow() ? MT32EMU_BOOL_TRUE : MT32EMU_BOOL_FALSE; + return static_cast<IReportHandler *>(instance_data)->onMIDIQueueOverflow() ? MT32EMU_BOOL_TRUE : MT32EMU_BOOL_FALSE; } static void onMIDISystemRealtime(void *instance_data, mt32emu_bit8u system_realtime) { - ((IReportHandler *)instance_data)->onMIDISystemRealtime(system_realtime); + static_cast<IReportHandler *>(instance_data)->onMIDISystemRealtime(system_realtime); } static void onDeviceReset(void *instance_data) { - ((IReportHandler *)instance_data)->onDeviceReset(); + static_cast<IReportHandler *>(instance_data)->onDeviceReset(); } static void onDeviceReconfig(void *instance_data) { - ((IReportHandler *)instance_data)->onDeviceReconfig(); + static_cast<IReportHandler *>(instance_data)->onDeviceReconfig(); } static void onNewReverbMode(void *instance_data, mt32emu_bit8u mode) { - ((IReportHandler *)instance_data)->onNewReverbMode(mode); + static_cast<IReportHandler *>(instance_data)->onNewReverbMode(mode); } static void onNewReverbTime(void *instance_data, mt32emu_bit8u time) { - ((IReportHandler *)instance_data)->onNewReverbTime(time); + static_cast<IReportHandler *>(instance_data)->onNewReverbTime(time); } static void onNewReverbLevel(void *instance_data, mt32emu_bit8u level) { - ((IReportHandler *)instance_data)->onNewReverbLevel(level); + static_cast<IReportHandler *>(instance_data)->onNewReverbLevel(level); } static void onPolyStateChanged(void *instance_data, mt32emu_bit8u part_num) { - ((IReportHandler *)instance_data)->onPolyStateChanged(part_num); + static_cast<IReportHandler *>(instance_data)->onPolyStateChanged(part_num); } static void onProgramChanged(void *instance_data, mt32emu_bit8u part_num, const char *sound_group_name, const char *patch_name) { - ((IReportHandler *)instance_data)->onProgramChanged(part_num, sound_group_name, patch_name); + static_cast<IReportHandler *>(instance_data)->onProgramChanged(part_num, sound_group_name, patch_name); } static mt32emu_report_handler_i getReportHandlerThunk() { @@ -340,15 +373,15 @@ static mt32emu_midi_receiver_version getMidiReceiverVersionID(mt32emu_midi_recei } static void handleShortMessage(void *instance_data, const mt32emu_bit32u message) { - ((IMidiReceiver *)instance_data)->handleShortMessage(message); + static_cast<IMidiReceiver *>(instance_data)->handleShortMessage(message); } static void handleSysex(void *instance_data, const mt32emu_bit8u stream[], const mt32emu_bit32u length) { - ((IMidiReceiver *)instance_data)->handleSysex(stream, length); + static_cast<IMidiReceiver *>(instance_data)->handleSysex(stream, length); } static void handleSystemRealtimeMessage(void *instance_data, const mt32emu_bit8u realtime) { - ((IMidiReceiver *)instance_data)->handleSystemRealtimeMessage(realtime); + static_cast<IMidiReceiver *>(instance_data)->handleSystemRealtimeMessage(realtime); } static mt32emu_midi_receiver_i getMidiReceiverThunk() { @@ -375,6 +408,7 @@ static mt32emu_midi_receiver_i getMidiReceiverThunk() { #undef mt32emu_get_library_version_int #undef mt32emu_get_library_version_string #undef mt32emu_get_stereo_output_samplerate +#undef mt32emu_get_best_analog_output_mode #undef mt32emu_create_context #undef mt32emu_free_context #undef mt32emu_add_rom_data @@ -382,13 +416,20 @@ static mt32emu_midi_receiver_i getMidiReceiverThunk() { #undef mt32emu_get_rom_info #undef mt32emu_set_partial_count #undef mt32emu_set_analog_output_mode +#undef mt32emu_set_stereo_output_samplerate +#undef mt32emu_set_samplerate_conversion_quality +#undef mt32emu_select_renderer_type +#undef mt32emu_get_selected_renderer_type #undef mt32emu_open_synth #undef mt32emu_close_synth #undef mt32emu_is_open #undef mt32emu_get_actual_stereo_output_samplerate +#undef mt32emu_convert_output_to_synth_timestamp +#undef mt32emu_convert_synth_to_output_timestamp #undef mt32emu_flush_midi_queue #undef mt32emu_set_midi_event_queue_size #undef mt32emu_set_midi_receiver +#undef mt32emu_get_internal_rendered_sample_count #undef mt32emu_parse_stream #undef mt32emu_parse_stream_at #undef mt32emu_play_short_message @@ -418,6 +459,8 @@ static mt32emu_midi_receiver_i getMidiReceiverThunk() { #undef mt32emu_get_reverb_output_gain #undef mt32emu_set_reversed_stereo_enabled #undef mt32emu_is_reversed_stereo_enabled +#undef mt32emu_set_nice_amp_ramp_enabled +#undef mt32emu_is_nice_amp_ramp_enabled #undef mt32emu_render_bit16s #undef mt32emu_render_float #undef mt32emu_render_bit16s_streams diff --git a/audio/softsynth/mt32/config.h b/audio/softsynth/mt32/config.h index af59f055a0..5ad650c08b 100644 --- a/audio/softsynth/mt32/config.h +++ b/audio/softsynth/mt32/config.h @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 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 @@ -18,11 +18,21 @@ #ifndef MT32EMU_CONFIG_H #define MT32EMU_CONFIG_H -#define MT32EMU_VERSION "2.0.3" +#define MT32EMU_VERSION "2.3.0" #define MT32EMU_VERSION_MAJOR 2 -#define MT32EMU_VERSION_MINOR 0 -#define MT32EMU_VERSION_PATCH 3 +#define MT32EMU_VERSION_MINOR 3 +#define MT32EMU_VERSION_PATCH 0 +/* Library Exports Configuration + * + * This reflects the API types actually provided by the library build. + * 0: The full-featured C++ API is only available in this build. The client application may ONLY use MT32EMU_API_TYPE 0. + * 1: The C-compatible API is only available. The library is built as a shared object, only C functions are exported, + * and thus the client application may NOT use MT32EMU_API_TYPE 0. + * 2: The C-compatible API is only available. The library is built as a shared object, only the factory function + * is exported, and thus the client application may ONLY use MT32EMU_API_TYPE 2. + * 3: All the available API types are provided by the library build. + */ #define MT32EMU_EXPORTS_TYPE 3 #endif diff --git a/audio/softsynth/mt32/globals.h b/audio/softsynth/mt32/globals.h index 49a5ecc250..2d984c82b6 100644 --- a/audio/softsynth/mt32/globals.h +++ b/audio/softsynth/mt32/globals.h @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 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 @@ -87,7 +87,7 @@ #define MT32EMU_MAX_STREAM_BUFFER_SIZE 32768 /* This should correspond to the MIDI buffer size used in real h/w devices. - * CM-32L control ROM seems using 1000 bytes, old MT-32 isn't confirmed by now. + * CM-32L control ROM is using 1000 bytes, and MT-32 GEN0 is using only 240 bytes (semi-confirmed by now). */ #define MT32EMU_SYSEX_BUFFER_SIZE 1000 diff --git a/audio/softsynth/mt32/internals.h b/audio/softsynth/mt32/internals.h index c64ba39212..0bae8d9f7b 100644 --- a/audio/softsynth/mt32/internals.h +++ b/audio/softsynth/mt32/internals.h @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 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 @@ -81,12 +81,6 @@ // Configuration -// 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. -#ifndef MT32EMU_USE_FLOAT_SAMPLES -#define MT32EMU_USE_FLOAT_SAMPLES 0 -#endif - // 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. #ifndef MT32EMU_REDUCE_REVERB_MEMORY @@ -101,6 +95,10 @@ namespace MT32Emu { +typedef Bit16s IntSample; +typedef Bit32s IntSampleEx; +typedef float FloatSample; + enum PolyState { POLY_Playing, POLY_Held, // This marks keys that have been released on the keyboard, but are being held by the pedal @@ -115,14 +113,6 @@ enum ReverbMode { REVERB_MODE_TAP_DELAY }; -#if MT32EMU_USE_FLOAT_SAMPLES -typedef float Sample; -typedef float SampleEx; -#else -typedef Bit16s Sample; -typedef Bit32s SampleEx; -#endif - -} +} // namespace MT32Emu #endif // #ifndef MT32EMU_INTERNALS_H diff --git a/audio/softsynth/mt32/mmath.h b/audio/softsynth/mt32/mmath.h index f233bedcbb..9a9e642ba1 100644 --- a/audio/softsynth/mt32/mmath.h +++ b/audio/softsynth/mt32/mmath.h @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 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 diff --git a/audio/softsynth/mt32/module.mk b/audio/softsynth/mt32/module.mk index 7657f5b55f..7c50f3edaa 100644 --- a/audio/softsynth/mt32/module.mk +++ b/audio/softsynth/mt32/module.mk @@ -5,6 +5,7 @@ MODULE_OBJS := \ BReverbModel.o \ File.o \ FileStream.o \ + LA32FloatWaveGenerator.o \ LA32Ramp.o \ LA32WaveGenerator.o \ MidiStreamParser.o \ @@ -19,22 +20,8 @@ MODULE_OBJS := \ TVF.o \ TVP.o \ sha1/sha1.o \ - c_interface/c_interface.o - -# SampleRateConverter.o \ -# srchelper/InternalResampler.o \ -# srchelper/SamplerateAdapter.o \ -# srchelper/SoxrAdapter.o \ -# srchelper/srctools/src/FIRResampler.o \ -# srchelper/srctools/src/IIR2xResampler.o \ -# srchelper/srctools/src/LinearResampler.o \ -# srchelper/srctools/src/ResamplerModel.o \ -# srchelper/srctools/src/SincResampler.o -# TODO: The Munt SampleRateConverter requires these additional -I options. -# This is not a very nice way of doing that, though, as it adds them globally. -# INCLUDES += -I $(srcdir)/$(MODULE)/srchelper/srctools/include -# INCLUDES += -I $(srcdir)/$(MODULE)/ - + c_interface/c_interface.o \ + SampleRateConverter.o # Include common rules include $(srcdir)/rules.mk diff --git a/audio/softsynth/mt32/mt32emu.h b/audio/softsynth/mt32/mt32emu.h index 3f3b6af344..6b93121bee 100644 --- a/audio/softsynth/mt32/mt32emu.h +++ b/audio/softsynth/mt32/mt32emu.h @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011-2017 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 @@ -43,7 +43,7 @@ #error Incompatible setting MT32EMU_API_TYPE=1 #elif MT32EMU_API_TYPE == 2 && (MT32EMU_EXPORTS_TYPE == 0) #error Incompatible setting MT32EMU_API_TYPE=2 -#elif MT32EMU_API_TYPE == 3 && (MT32EMU_EXPORTS_TYPE == 0) +#elif MT32EMU_API_TYPE == 3 && (MT32EMU_EXPORTS_TYPE == 0 || MT32EMU_EXPORTS_TYPE == 2) #error Incompatible setting MT32EMU_API_TYPE=3 #endif #else /* #ifdef MT32EMU_API_TYPE */ diff --git a/audio/softsynth/mt32/srchelper/InternalResampler.cpp b/audio/softsynth/mt32/srchelper/InternalResampler.cpp index f76c7d117e..320408459c 100644 --- a/audio/softsynth/mt32/srchelper/InternalResampler.cpp +++ b/audio/softsynth/mt32/srchelper/InternalResampler.cpp @@ -16,10 +16,10 @@ #include "InternalResampler.h" -#include <SincResampler.h> -#include <ResamplerModel.h> +#include "srctools/include/SincResampler.h" +#include "srctools/include/ResamplerModel.h" -#include "Synth.h" +#include "../Synth.h" using namespace SRCTools; @@ -37,11 +37,11 @@ public: } }; -static FloatSampleProvider &createModel(Synth &synth, SRCTools::FloatSampleProvider &synthSource, double targetSampleRate, SampleRateConverter::Quality quality) { +static FloatSampleProvider &createModel(Synth &synth, SRCTools::FloatSampleProvider &synthSource, double targetSampleRate, SamplerateConversionQuality quality) { static const double MAX_AUDIBLE_FREQUENCY = 20000.0; const double sourceSampleRate = synth.getStereoOutputSampleRate(); - if (quality != SampleRateConverter::FASTEST) { + if (quality != SamplerateConversionQuality_FASTEST) { const bool oversampledMode = synth.getStereoOutputSampleRate() == Synth::getStereoOutputSampleRate(AnalogOutputMode_OVERSAMPLED); // Oversampled input allows to bypass IIR interpolation stage and, in some cases, IIR decimation stage if (oversampledMode && (0.5 * sourceSampleRate) <= targetSampleRate) { @@ -59,7 +59,7 @@ static FloatSampleProvider &createModel(Synth &synth, SRCTools::FloatSampleProvi using namespace MT32Emu; -InternalResampler::InternalResampler(Synth &synth, double targetSampleRate, SampleRateConverter::Quality quality) : +InternalResampler::InternalResampler(Synth &synth, double targetSampleRate, SamplerateConversionQuality quality) : synthSource(*new SynthWrapper(synth)), model(createModel(synth, synthSource, targetSampleRate, quality)) {} diff --git a/audio/softsynth/mt32/srchelper/InternalResampler.h b/audio/softsynth/mt32/srchelper/InternalResampler.h index 0a5c3233c8..be54077597 100644 --- a/audio/softsynth/mt32/srchelper/InternalResampler.h +++ b/audio/softsynth/mt32/srchelper/InternalResampler.h @@ -14,12 +14,12 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef INTERNAL_RESAMPLER_H -#define INTERNAL_RESAMPLER_H +#ifndef MT32EMU_INTERNAL_RESAMPLER_H +#define MT32EMU_INTERNAL_RESAMPLER_H -#include "../SampleRateConverter.h" +#include "../Enumerations.h" -#include "FloatSampleProvider.h" +#include "srctools/include/FloatSampleProvider.h" namespace MT32Emu { @@ -27,7 +27,7 @@ class Synth; class InternalResampler { public: - InternalResampler(Synth &synth, double targetSampleRate, SampleRateConverter::Quality quality); + InternalResampler(Synth &synth, double targetSampleRate, SamplerateConversionQuality quality); ~InternalResampler(); void getOutputSamples(float *buffer, unsigned int length); @@ -39,4 +39,4 @@ private: } // namespace MT32Emu -#endif // INTERNAL_RESAMPLER_H +#endif // MT32EMU_INTERNAL_RESAMPLER_H diff --git a/audio/softsynth/mt32/srchelper/SamplerateAdapter.cpp b/audio/softsynth/mt32/srchelper/SamplerateAdapter.cpp index 33fcfa81dd..715d298720 100644 --- a/audio/softsynth/mt32/srchelper/SamplerateAdapter.cpp +++ b/audio/softsynth/mt32/srchelper/SamplerateAdapter.cpp @@ -16,7 +16,7 @@ #include "SamplerateAdapter.h" -#include "Synth.h" +#include "../Synth.h" using namespace MT32Emu; @@ -31,7 +31,7 @@ long SamplerateAdapter::getInputSamples(void *cb_data, float **data) { return length; } -SamplerateAdapter::SamplerateAdapter(Synth &useSynth, double targetSampleRate, SampleRateConverter::Quality quality) : +SamplerateAdapter::SamplerateAdapter(Synth &useSynth, double targetSampleRate, SamplerateConversionQuality quality) : synth(useSynth), inBuffer(new float[CHANNEL_COUNT * MAX_SAMPLES_PER_RUN]), inBufferSize(MAX_SAMPLES_PER_RUN), @@ -41,16 +41,16 @@ SamplerateAdapter::SamplerateAdapter(Synth &useSynth, double targetSampleRate, S int error; int conversionType; switch (quality) { - case SampleRateConverter::FASTEST: + case SamplerateConversionQuality_FASTEST: conversionType = SRC_LINEAR; break; - case SampleRateConverter::FAST: + case SamplerateConversionQuality_FAST: conversionType = SRC_SINC_FASTEST; break; - case SampleRateConverter::BEST: + case SamplerateConversionQuality_BEST: conversionType = SRC_SINC_BEST_QUALITY; break; - case SampleRateConverter::GOOD: + case SamplerateConversionQuality_GOOD: default: conversionType = SRC_SINC_MEDIUM_QUALITY; break; diff --git a/audio/softsynth/mt32/srchelper/SamplerateAdapter.h b/audio/softsynth/mt32/srchelper/SamplerateAdapter.h index aac259b50a..0991fd7713 100644 --- a/audio/softsynth/mt32/srchelper/SamplerateAdapter.h +++ b/audio/softsynth/mt32/srchelper/SamplerateAdapter.h @@ -14,18 +14,20 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef SAMPLERATE_ADAPTER_H -#define SAMPLERATE_ADAPTER_H +#ifndef MT32EMU_SAMPLERATE_ADAPTER_H +#define MT32EMU_SAMPLERATE_ADAPTER_H #include <samplerate.h> -#include "../SampleRateConverter.h" +#include "../Enumerations.h" namespace MT32Emu { +class Synth; + class SamplerateAdapter { public: - SamplerateAdapter(Synth &synth, double targetSampleRate, SampleRateConverter::Quality quality); + SamplerateAdapter(Synth &synth, double targetSampleRate, SamplerateConversionQuality quality); ~SamplerateAdapter(); void getOutputSamples(float *outBuffer, unsigned int length); @@ -43,4 +45,4 @@ private: } // namespace MT32Emu -#endif // SAMPLERATE_ADAPTER_H +#endif // MT32EMU_SAMPLERATE_ADAPTER_H diff --git a/audio/softsynth/mt32/srchelper/SoxrAdapter.cpp b/audio/softsynth/mt32/srchelper/SoxrAdapter.cpp index b13192be92..5e8dca97d6 100644 --- a/audio/softsynth/mt32/srchelper/SoxrAdapter.cpp +++ b/audio/softsynth/mt32/srchelper/SoxrAdapter.cpp @@ -16,37 +16,37 @@ #include "SoxrAdapter.h" -#include "Synth.h" +#include "../Synth.h" using namespace MT32Emu; static const unsigned int CHANNEL_COUNT = 2; size_t SoxrAdapter::getInputSamples(void *input_fn_state, soxr_in_t *data, size_t requested_len) { - unsigned int length = requested_len < 1 ? 1 : (MAX_SAMPLES_PER_RUN < requested_len ? MAX_SAMPLES_PER_RUN : requested_len); + unsigned int length = requested_len < 1 ? 1 : (MAX_SAMPLES_PER_RUN < requested_len ? MAX_SAMPLES_PER_RUN : static_cast<unsigned int>(requested_len)); SoxrAdapter *instance = static_cast<SoxrAdapter *>(input_fn_state); instance->synth.render(instance->inBuffer, length); *data = instance->inBuffer; return length; } -SoxrAdapter::SoxrAdapter(Synth &useSynth, double targetSampleRate, SampleRateConverter::Quality quality) : +SoxrAdapter::SoxrAdapter(Synth &useSynth, double targetSampleRate, SamplerateConversionQuality quality) : synth(useSynth), inBuffer(new float[CHANNEL_COUNT * MAX_SAMPLES_PER_RUN]) { soxr_io_spec_t ioSpec = soxr_io_spec(SOXR_FLOAT32_I, SOXR_FLOAT32_I); unsigned long qualityRecipe; switch (quality) { - case SampleRateConverter::FASTEST: + case SamplerateConversionQuality_FASTEST: qualityRecipe = SOXR_QQ; break; - case SampleRateConverter::FAST: + case SamplerateConversionQuality_FAST: qualityRecipe = SOXR_LQ; break; - case SampleRateConverter::GOOD: + case SamplerateConversionQuality_GOOD: qualityRecipe = SOXR_MQ; break; - case SampleRateConverter::BEST: + case SamplerateConversionQuality_BEST: default: qualityRecipe = SOXR_16_BITQ; break; diff --git a/audio/softsynth/mt32/srchelper/SoxrAdapter.h b/audio/softsynth/mt32/srchelper/SoxrAdapter.h index c764d9acfd..b97ca4da51 100644 --- a/audio/softsynth/mt32/srchelper/SoxrAdapter.h +++ b/audio/softsynth/mt32/srchelper/SoxrAdapter.h @@ -14,18 +14,20 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef SOXR_ADAPTER_H -#define SOXR_ADAPTER_H +#ifndef MT32EMU_SOXR_ADAPTER_H +#define MT32EMU_SOXR_ADAPTER_H #include <soxr.h> -#include "../SampleRateConverter.h" +#include "../Enumerations.h" namespace MT32Emu { +class Synth; + class SoxrAdapter { public: - SoxrAdapter(Synth &synth, double targetSampleRate, SampleRateConverter::Quality quality); + SoxrAdapter(Synth &synth, double targetSampleRate, SamplerateConversionQuality quality); ~SoxrAdapter(); void getOutputSamples(float *buffer, unsigned int length); @@ -40,4 +42,4 @@ private: } // namespace MT32Emu -#endif // SOXR_ADAPTER_H +#endif // MT32EMU_SOXR_ADAPTER_H diff --git a/audio/softsynth/mt32/srchelper/srctools/include/FIRResampler.h b/audio/softsynth/mt32/srchelper/srctools/include/FIRResampler.h index 2c5d69052a..7c09bf8ded 100644 --- a/audio/softsynth/mt32/srchelper/srctools/include/FIRResampler.h +++ b/audio/softsynth/mt32/srchelper/srctools/include/FIRResampler.h @@ -14,8 +14,8 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef FIR_RESAMPLER_H -#define FIR_RESAMPLER_H +#ifndef SRCTOOLS_FIR_RESAMPLER_H +#define SRCTOOLS_FIR_RESAMPLER_H #include "ResamplerStage.h" @@ -64,4 +64,4 @@ private: } // namespace SRCTools -#endif // FIR_RESAMPLER_H +#endif // SRCTOOLS_FIR_RESAMPLER_H diff --git a/audio/softsynth/mt32/srchelper/srctools/include/FloatSampleProvider.h b/audio/softsynth/mt32/srchelper/srctools/include/FloatSampleProvider.h index 03038d03ec..9820769f7f 100644 --- a/audio/softsynth/mt32/srchelper/srctools/include/FloatSampleProvider.h +++ b/audio/softsynth/mt32/srchelper/srctools/include/FloatSampleProvider.h @@ -14,8 +14,8 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef FLOAT_SAMPLE_PROVIDER_H -#define FLOAT_SAMPLE_PROVIDER_H +#ifndef SRCTOOLS_FLOAT_SAMPLE_PROVIDER_H +#define SRCTOOLS_FLOAT_SAMPLE_PROVIDER_H namespace SRCTools { @@ -24,11 +24,11 @@ typedef float FloatSample; /** Interface defines an abstract source of samples. It can either define a single channel stream or a stream with interleaved channels. */ class FloatSampleProvider { public: - virtual ~FloatSampleProvider() {}; + virtual ~FloatSampleProvider() {} virtual void getOutputSamples(FloatSample *outBuffer, unsigned int size) = 0; }; } // namespace SRCTools -#endif // FLOAT_SAMPLE_PROVIDER_H +#endif // SRCTOOLS_FLOAT_SAMPLE_PROVIDER_H diff --git a/audio/softsynth/mt32/srchelper/srctools/include/IIR2xResampler.h b/audio/softsynth/mt32/srchelper/srctools/include/IIR2xResampler.h index 23733e4049..0bfe1c4c8b 100644 --- a/audio/softsynth/mt32/srchelper/srctools/include/IIR2xResampler.h +++ b/audio/softsynth/mt32/srchelper/srctools/include/IIR2xResampler.h @@ -14,8 +14,8 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef IIR_2X_RESAMPLER_H -#define IIR_2X_RESAMPLER_H +#ifndef SRCTOOLS_IIR_2X_RESAMPLER_H +#define SRCTOOLS_IIR_2X_RESAMPLER_H #include "ResamplerStage.h" @@ -97,4 +97,4 @@ public: } // namespace SRCTools -#endif // IIR_2X_RESAMPLER_H +#endif // SRCTOOLS_IIR_2X_RESAMPLER_H diff --git a/audio/softsynth/mt32/srchelper/srctools/include/LinearResampler.h b/audio/softsynth/mt32/srchelper/srctools/include/LinearResampler.h index 1f4dd2fcbd..c81ff2a385 100644 --- a/audio/softsynth/mt32/srchelper/srctools/include/LinearResampler.h +++ b/audio/softsynth/mt32/srchelper/srctools/include/LinearResampler.h @@ -14,8 +14,8 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef LINEAR_RESAMPLER_H -#define LINEAR_RESAMPLER_H +#ifndef SRCTOOLS_LINEAR_RESAMPLER_H +#define SRCTOOLS_LINEAR_RESAMPLER_H #include "ResamplerStage.h" @@ -39,4 +39,4 @@ private: } // namespace SRCTools -#endif // LINEAR_RESAMPLER_H +#endif // SRCTOOLS_LINEAR_RESAMPLER_H diff --git a/audio/softsynth/mt32/srchelper/srctools/include/ResamplerModel.h b/audio/softsynth/mt32/srchelper/srctools/include/ResamplerModel.h index 0372605e87..f0ac237071 100644 --- a/audio/softsynth/mt32/srchelper/srctools/include/ResamplerModel.h +++ b/audio/softsynth/mt32/srchelper/srctools/include/ResamplerModel.h @@ -14,8 +14,8 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef RESAMPLER_MODEL_H -#define RESAMPLER_MODEL_H +#ifndef SRCTOOLS_RESAMPLER_MODEL_H +#define SRCTOOLS_RESAMPLER_MODEL_H #include "FloatSampleProvider.h" @@ -60,4 +60,4 @@ void freeResamplerModel(FloatSampleProvider &model, FloatSampleProvider &source) } // namespace SRCTools -#endif // RESAMPLER_MODEL_H +#endif // SRCTOOLS_RESAMPLER_MODEL_H diff --git a/audio/softsynth/mt32/srchelper/srctools/include/ResamplerStage.h b/audio/softsynth/mt32/srchelper/srctools/include/ResamplerStage.h index c0f0a0a50a..e335c0c380 100644 --- a/audio/softsynth/mt32/srchelper/srctools/include/ResamplerStage.h +++ b/audio/softsynth/mt32/srchelper/srctools/include/ResamplerStage.h @@ -14,8 +14,8 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef RESAMPLER_STAGE_H -#define RESAMPLER_STAGE_H +#ifndef SRCTOOLS_RESAMPLER_STAGE_H +#define SRCTOOLS_RESAMPLER_STAGE_H #include "FloatSampleProvider.h" @@ -24,7 +24,7 @@ namespace SRCTools { /** Interface defines an abstract source of samples. It can either define a single channel stream or a stream with interleaved channels. */ class ResamplerStage { public: - virtual ~ResamplerStage() {}; + virtual ~ResamplerStage() {} /** Returns a lower estimation of required number of input samples to produce the specified number of output samples. */ virtual unsigned int estimateInLength(const unsigned int outLength) const = 0; @@ -35,4 +35,4 @@ public: } // namespace SRCTools -#endif // RESAMPLER_STAGE_H +#endif // SRCTOOLS_RESAMPLER_STAGE_H diff --git a/audio/softsynth/mt32/srchelper/srctools/include/SincResampler.h b/audio/softsynth/mt32/srchelper/srctools/include/SincResampler.h index ea3f03b112..1551a1eda8 100644 --- a/audio/softsynth/mt32/srchelper/srctools/include/SincResampler.h +++ b/audio/softsynth/mt32/srchelper/srctools/include/SincResampler.h @@ -14,8 +14,8 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef SINC_RESAMPLER_H -#define SINC_RESAMPLER_H +#ifndef SRCTOOLS_SINC_RESAMPLER_H +#define SRCTOOLS_SINC_RESAMPLER_H #include "FIRResampler.h" @@ -43,4 +43,4 @@ namespace SincResampler { } // namespace SRCTools -#endif // SINC_RESAMPLER_H +#endif // SRCTOOLS_SINC_RESAMPLER_H diff --git a/audio/softsynth/mt32/srchelper/srctools/src/FIRResampler.cpp b/audio/softsynth/mt32/srchelper/srctools/src/FIRResampler.cpp index 2cded0c3d7..15d95c5fc6 100644 --- a/audio/softsynth/mt32/srchelper/srctools/src/FIRResampler.cpp +++ b/audio/softsynth/mt32/srchelper/srctools/src/FIRResampler.cpp @@ -17,7 +17,7 @@ #include <cmath> #include <cstring> -#include "FIRResampler.h" +#include "../include/FIRResampler.h" using namespace SRCTools; diff --git a/audio/softsynth/mt32/srchelper/srctools/src/IIR2xResampler.cpp b/audio/softsynth/mt32/srchelper/srctools/src/IIR2xResampler.cpp index 061006a1e7..1adc593593 100644 --- a/audio/softsynth/mt32/srchelper/srctools/src/IIR2xResampler.cpp +++ b/audio/softsynth/mt32/srchelper/srctools/src/IIR2xResampler.cpp @@ -16,12 +16,12 @@ #include <cstddef> -#include "IIR2xResampler.h" +#include "../include/IIR2xResampler.h" namespace SRCTools { // Avoid denormals degrading performance, using biased input - static const BufferedSample BIAS = 1e-35f; + static const BufferedSample BIAS = 1e-20f; // Sharp elliptic filter with symmetric ripple: N=18, Ap=As=-106 dB, fp=0.238, fs = 0.25 (in terms of sample rate) static const IIRCoefficient FIR_BEST = 0.0014313792470984f; @@ -132,7 +132,7 @@ IIRResampler::~IIRResampler() { IIR2xInterpolator::IIR2xInterpolator(const Quality quality) : IIRResampler(quality), - phase() + phase(1) { for (unsigned int chIx = 0; chIx < IIR_RESAMPER_CHANNEL_COUNT; ++chIx) { lastInputSamples[chIx] = 0; @@ -141,7 +141,7 @@ IIR2xInterpolator::IIR2xInterpolator(const Quality quality) : IIR2xInterpolator::IIR2xInterpolator(const unsigned int useSectionsCount, const IIRCoefficient useFIR, const IIRSection useSections[]) : IIRResampler(useSectionsCount, useFIR, useSections), - phase() + phase(1) { for (unsigned int chIx = 0; chIx < IIR_RESAMPER_CHANNEL_COUNT; ++chIx) { lastInputSamples[chIx] = 0; diff --git a/audio/softsynth/mt32/srchelper/srctools/src/LinearResampler.cpp b/audio/softsynth/mt32/srchelper/srctools/src/LinearResampler.cpp index 98b9c77c73..e7b60c62a8 100644 --- a/audio/softsynth/mt32/srchelper/srctools/src/LinearResampler.cpp +++ b/audio/softsynth/mt32/srchelper/srctools/src/LinearResampler.cpp @@ -14,7 +14,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#include "LinearResampler.h" +#include "../include/LinearResampler.h" using namespace SRCTools; diff --git a/audio/softsynth/mt32/srchelper/srctools/src/ResamplerModel.cpp b/audio/softsynth/mt32/srchelper/srctools/src/ResamplerModel.cpp index 4d2d930837..44b969cbd1 100644 --- a/audio/softsynth/mt32/srchelper/srctools/src/ResamplerModel.cpp +++ b/audio/softsynth/mt32/srchelper/srctools/src/ResamplerModel.cpp @@ -17,12 +17,12 @@ #include <cmath> #include <cstddef> -#include "ResamplerModel.h" +#include "../include/ResamplerModel.h" -#include "ResamplerStage.h" -#include "SincResampler.h" -#include "IIR2xResampler.h" -#include "LinearResampler.h" +#include "../include/ResamplerStage.h" +#include "../include/SincResampler.h" +#include "../include/IIR2xResampler.h" +#include "../include/LinearResampler.h" namespace SRCTools { diff --git a/audio/softsynth/mt32/srchelper/srctools/src/SincResampler.cpp b/audio/softsynth/mt32/srchelper/srctools/src/SincResampler.cpp index 3ed028d261..fff2703746 100644 --- a/audio/softsynth/mt32/srchelper/srctools/src/SincResampler.cpp +++ b/audio/softsynth/mt32/srchelper/srctools/src/SincResampler.cpp @@ -16,11 +16,11 @@ #include <cmath> -#ifdef SINC_RESAMPLER_DEBUG_LOG +#ifdef SRCTOOLS_SINC_RESAMPLER_DEBUG_LOG #include <iostream> #endif -#include "SincResampler.h" +#include "../include/SincResampler.h" #ifndef M_PI static const double M_PI = 3.1415926535897932; @@ -124,7 +124,7 @@ ResamplerStage *SincResampler::createSincResampler(const double inputFrequency, unsigned int order = KaizerWindow::estimateOrder(dbSNR, fp, fs); const unsigned int kernelLength = order + 1; -#ifdef SINC_RESAMPLER_DEBUG_LOG +#ifdef SRCTOOLS_SINC_RESAMPLER_DEBUG_LOG std::clog << "FIR: " << upsampleFactor << "/" << downsampleFactor << ", N=" << kernelLength << ", NPh=" << kernelLength / double(upsampleFactor) << ", C=" << 0.5 / fc << ", fp=" << fp << ", fs=" << fs << ", M=" << maxUpsampleFactor << std::endl; #endif |