aboutsummaryrefslogtreecommitdiff
path: root/audio/softsynth/mt32
diff options
context:
space:
mode:
authorTarek Soliman2017-12-23 15:40:30 -0600
committerTarek Soliman2018-01-03 10:40:23 -0600
commit50d79c5f265aad592ae7f17209653ccbb1fde488 (patch)
tree1951526e3ff2910acb0588f3a23ba0e9e7e66544 /audio/softsynth/mt32
parentbb5e8d3a11711d409f89739cf3f054cd5bac8c4f (diff)
downloadscummvm-rg350-50d79c5f265aad592ae7f17209653ccbb1fde488.tar.gz
scummvm-rg350-50d79c5f265aad592ae7f17209653ccbb1fde488.tar.bz2
scummvm-rg350-50d79c5f265aad592ae7f17209653ccbb1fde488.zip
MT32: Update to munt 2.3.0
This uses upstream commit 939cc986d9ffd044f8c6149361127ad5d94e430f Closes gh-1091
Diffstat (limited to 'audio/softsynth/mt32')
-rw-r--r--audio/softsynth/mt32/Analog.cpp334
-rw-r--r--audio/softsynth/mt32/Analog.h31
-rw-r--r--audio/softsynth/mt32/BReverbModel.cpp675
-rw-r--r--audio/softsynth/mt32/BReverbModel.h102
-rw-r--r--audio/softsynth/mt32/Enumerations.h36
-rw-r--r--audio/softsynth/mt32/File.cpp2
-rw-r--r--audio/softsynth/mt32/File.h2
-rw-r--r--audio/softsynth/mt32/FileStream.cpp17
-rw-r--r--audio/softsynth/mt32/FileStream.h2
-rw-r--r--audio/softsynth/mt32/LA32FloatWaveGenerator.cpp65
-rw-r--r--audio/softsynth/mt32/LA32FloatWaveGenerator.h32
-rw-r--r--audio/softsynth/mt32/LA32Ramp.cpp17
-rw-r--r--audio/softsynth/mt32/LA32Ramp.h3
-rw-r--r--audio/softsynth/mt32/LA32WaveGenerator.cpp57
-rw-r--r--audio/softsynth/mt32/LA32WaveGenerator.h42
-rw-r--r--audio/softsynth/mt32/MemoryRegion.h2
-rw-r--r--audio/softsynth/mt32/MidiEventQueue.h2
-rw-r--r--audio/softsynth/mt32/MidiStreamParser.cpp4
-rw-r--r--audio/softsynth/mt32/MidiStreamParser.h2
-rw-r--r--audio/softsynth/mt32/Part.cpp17
-rw-r--r--audio/softsynth/mt32/Part.h2
-rw-r--r--audio/softsynth/mt32/Partial.cpp181
-rw-r--r--audio/softsynth/mt32/Partial.h22
-rw-r--r--audio/softsynth/mt32/PartialManager.cpp63
-rw-r--r--audio/softsynth/mt32/PartialManager.h10
-rw-r--r--audio/softsynth/mt32/Poly.cpp2
-rw-r--r--audio/softsynth/mt32/Poly.h2
-rw-r--r--audio/softsynth/mt32/ROMInfo.cpp4
-rw-r--r--audio/softsynth/mt32/ROMInfo.h2
-rw-r--r--audio/softsynth/mt32/SampleRateConverter.cpp49
-rw-r--r--audio/softsynth/mt32/SampleRateConverter.h21
-rw-r--r--audio/softsynth/mt32/Structures.h8
-rw-r--r--audio/softsynth/mt32/Synth.cpp722
-rw-r--r--audio/softsynth/mt32/Synth.h50
-rw-r--r--audio/softsynth/mt32/TVA.cpp31
-rw-r--r--audio/softsynth/mt32/TVA.h2
-rw-r--r--audio/softsynth/mt32/TVF.cpp19
-rw-r--r--audio/softsynth/mt32/TVF.h2
-rw-r--r--audio/softsynth/mt32/TVP.cpp84
-rw-r--r--audio/softsynth/mt32/TVP.h3
-rw-r--r--audio/softsynth/mt32/Tables.cpp2
-rw-r--r--audio/softsynth/mt32/Tables.h2
-rw-r--r--audio/softsynth/mt32/Types.h2
-rw-r--r--audio/softsynth/mt32/c_interface/c_interface.cpp115
-rw-r--r--audio/softsynth/mt32/c_interface/c_interface.h104
-rw-r--r--audio/softsynth/mt32/c_interface/c_types.h196
-rw-r--r--audio/softsynth/mt32/c_interface/cpp_interface.h79
-rw-r--r--audio/softsynth/mt32/config.h18
-rw-r--r--audio/softsynth/mt32/globals.h4
-rw-r--r--audio/softsynth/mt32/internals.h22
-rw-r--r--audio/softsynth/mt32/mmath.h2
-rw-r--r--audio/softsynth/mt32/module.mk19
-rw-r--r--audio/softsynth/mt32/mt32emu.h4
-rw-r--r--audio/softsynth/mt32/srchelper/InternalResampler.cpp12
-rw-r--r--audio/softsynth/mt32/srchelper/InternalResampler.h12
-rw-r--r--audio/softsynth/mt32/srchelper/SamplerateAdapter.cpp12
-rw-r--r--audio/softsynth/mt32/srchelper/SamplerateAdapter.h12
-rw-r--r--audio/softsynth/mt32/srchelper/SoxrAdapter.cpp14
-rw-r--r--audio/softsynth/mt32/srchelper/SoxrAdapter.h12
-rw-r--r--audio/softsynth/mt32/srchelper/srctools/include/FIRResampler.h6
-rw-r--r--audio/softsynth/mt32/srchelper/srctools/include/FloatSampleProvider.h8
-rw-r--r--audio/softsynth/mt32/srchelper/srctools/include/IIR2xResampler.h6
-rw-r--r--audio/softsynth/mt32/srchelper/srctools/include/LinearResampler.h6
-rw-r--r--audio/softsynth/mt32/srchelper/srctools/include/ResamplerModel.h6
-rw-r--r--audio/softsynth/mt32/srchelper/srctools/include/ResamplerStage.h8
-rw-r--r--audio/softsynth/mt32/srchelper/srctools/include/SincResampler.h6
-rw-r--r--audio/softsynth/mt32/srchelper/srctools/src/FIRResampler.cpp2
-rw-r--r--audio/softsynth/mt32/srchelper/srctools/src/IIR2xResampler.cpp8
-rw-r--r--audio/softsynth/mt32/srchelper/srctools/src/LinearResampler.cpp2
-rw-r--r--audio/softsynth/mt32/srchelper/srctools/src/ResamplerModel.cpp10
-rw-r--r--audio/softsynth/mt32/srchelper/srctools/src/SincResampler.cpp6
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 &currentSettings;
+ 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 &currentSettings == &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 &currentSettings == &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 &currentSettings;
- 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, &ampRamp);
@@ -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