From e34b5be8e3d4842e273f08821b6a7bd7ba65e843 Mon Sep 17 00:00:00 2001 From: Filippos Karapetis Date: Sun, 21 Dec 2014 22:13:28 +0200 Subject: MT32: Update to munt 1.5.0 This syncs with munt commit 4041a16a5d --- audio/softsynth/mt32/Analog.cpp | 348 ++++++++++++++++++++++++ audio/softsynth/mt32/Analog.h | 57 ++++ audio/softsynth/mt32/BReverbModel.cpp | 10 +- audio/softsynth/mt32/BReverbModel.h | 2 +- audio/softsynth/mt32/LA32FloatWaveGenerator.cpp | 2 +- audio/softsynth/mt32/LA32Ramp.cpp | 2 +- audio/softsynth/mt32/LA32WaveGenerator.cpp | 10 +- audio/softsynth/mt32/MemoryRegion.h | 124 +++++++++ audio/softsynth/mt32/MidiEventQueue.h | 67 +++++ audio/softsynth/mt32/Part.cpp | 1 + audio/softsynth/mt32/Partial.cpp | 5 +- audio/softsynth/mt32/PartialManager.cpp | 1 + audio/softsynth/mt32/Poly.cpp | 1 + audio/softsynth/mt32/Poly.h | 1 + audio/softsynth/mt32/ROMInfo.cpp | 15 +- audio/softsynth/mt32/ROMInfo.h | 4 +- audio/softsynth/mt32/Structures.h | 47 ++-- audio/softsynth/mt32/Synth.cpp | 248 +++++++++++------ audio/softsynth/mt32/Synth.h | 347 +++++++++-------------- audio/softsynth/mt32/TVA.cpp | 1 + audio/softsynth/mt32/TVF.cpp | 1 + audio/softsynth/mt32/TVP.cpp | 1 + audio/softsynth/mt32/Tables.cpp | 5 +- audio/softsynth/mt32/Tables.h | 15 +- audio/softsynth/mt32/Types.h | 40 +++ audio/softsynth/mt32/internals.h | 83 ++++++ audio/softsynth/mt32/module.mk | 1 + audio/softsynth/mt32/mt32emu.h | 67 +---- 28 files changed, 1083 insertions(+), 423 deletions(-) create mode 100644 audio/softsynth/mt32/Analog.cpp create mode 100644 audio/softsynth/mt32/Analog.h create mode 100644 audio/softsynth/mt32/MemoryRegion.h create mode 100644 audio/softsynth/mt32/MidiEventQueue.h create mode 100644 audio/softsynth/mt32/Types.h create mode 100644 audio/softsynth/mt32/internals.h (limited to 'audio/softsynth') diff --git a/audio/softsynth/mt32/Analog.cpp b/audio/softsynth/mt32/Analog.cpp new file mode 100644 index 0000000000..8ac28e401a --- /dev/null +++ b/audio/softsynth/mt32/Analog.cpp @@ -0,0 +1,348 @@ +/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher + * Copyright (C) 2011, 2012, 2013, 2014 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +//#include +#include "Analog.h" + +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. + * The frequency response of the LPF is computed directly, the effect of the S&H is approximated by multiplying the LPF frequency + * response by the corresponding sinc. Although, the LPF has DC gain of 3.2, we ignore this in the emulation and use normalised model. + * 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[] = { + 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[] = { + 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; + +// Integer versions of the FIRs above multiplied by (1 << 14) and rounded. +static const SampleEx COARSE_LPF_TAPS_MT32[] = { + 20848, -3609, -2589, 2943, -1827, 887, -385, 180, -114 +}; + +static const SampleEx COARSE_LPF_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. + * As with the filter above, the analogue LPF frequency response is obtained for 1536 pin grid for range up to 96 kHz and multiplied + * by the corresponding sinc. The result is further squared, windowed and passed to generalised Parks-McClellan routine as a desired response. + * Finally, the minimum phase factor is found that's essentially the coefficients below. + * Relative error in the audible frequency range doesn't exceed 0.0006%, attenuation in the stopband is better than 100 dB. + * This level of performance makes it nearly bit-accurate for standard 16-bit sample resolution. + */ + +// FIR version for MT-32 first generation. +static const float 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, + 0.003559874f, -0.005105248f, -0.005647917f, -0.004157918f, -0.002065664f, 0.00158747f, 0.003762585f, + 0.001867137f, -0.001090028f, -0.001433979f, -0.00022367f, 4.34308E-05f, -0.000247827f, 0.000157087f, + 0.000605823f, 0.000197317f, -0.000370511f, -0.000261202f, 9.96069E-05f, 9.85073E-05f, -5.28754E-05f, + -1.00912E-05f, 7.69943E-05f, 2.03162E-05f, -5.67967E-05f, -3.30637E-05f, 1.61958E-05f, 1.73041E-05f +}; + +// FIR version for new MT-32 and CM-32L/LAPC-I. +static const float 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, + -0.011027427f, -0.012933664f, 0.001158097f, 0.006765958f, 0.00046778f, -0.002191106f, 0.001561017f, + 0.001842871f, -0.001996876f, -0.002315836f, 0.000980965f, 0.001817454f, -0.000243272f, -0.000972848f, + 0.000149941f, 0.000498886f, -0.000204436f, -0.000347415f, 0.000142386f, 0.000249137f, -4.32946E-05f, + -0.000131231f, 3.88575E-07f, 4.48813E-05f, -1.31906E-06f, -1.03499E-05f, 7.71971E-06f, 2.86721E-06f +}; + +// According to the CM-64 PCB schematic, there is a difference in the values of the LPF entrance resistors for the reverb and non-reverb channels. +// This effectively results in non-unity LPF DC gain for the reverb channel of 0.68 while the LPF has unity DC gain for the LA32 output channels. +// In emulation, the reverb output gain is multiplied by this factor to compensate for the LPF gain difference. +static const float CM32L_REVERB_TO_LA32_ANALOG_OUTPUT_GAIN_FACTOR = 0.68f; + +static const unsigned int OUTPUT_GAIN_FRACTION_BITS = 8; +static const float OUTPUT_GAIN_MULTIPLIER = float(1 << OUTPUT_GAIN_FRACTION_BITS); + +static const unsigned int COARSE_LPF_DELAY_LINE_LENGTH = 8; // Must be a power of 2 +static const unsigned int ACCURATE_LPF_DELAY_LINE_LENGTH = 16; // Must be a power of 2 +static const unsigned int ACCURATE_LPF_NUMBER_OF_PHASES = 3; // Upsampling factor +static const unsigned int ACCURATE_LPF_PHASE_INCREMENT_REGULAR = 2; // Downsampling factor +static const unsigned int ACCURATE_LPF_PHASE_INCREMENT_OVERSAMPLED = 1; // No downsampling +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 } }; + +class AbstractLowPassFilter { +public: + static AbstractLowPassFilter &createLowPassFilter(AnalogOutputMode mode, bool oldMT32AnalogLPF); + static void muteRingBuffer(SampleEx *ringBuffer, unsigned int length); + + 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) {} +}; + +class NullLowPassFilter : public AbstractLowPassFilter { +public: + SampleEx process(SampleEx sample); +}; + +class CoarseLowPassFilter : public AbstractLowPassFilter { +private: + const SampleEx * const LPF_TAPS; + SampleEx ringBuffer[COARSE_LPF_DELAY_LINE_LENGTH]; + unsigned int ringBufferPosition; + +public: + CoarseLowPassFilter(bool oldMT32AnalogLPF); + SampleEx process(SampleEx sample); +}; + +class AccurateLowPassFilter : public AbstractLowPassFilter { +private: + const float * 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]; + unsigned int ringBufferPosition; + unsigned int phase; + +public: + AccurateLowPassFilter(bool oldMT32AnalogLPF, bool oversample); + SampleEx process(SampleEx sample); + bool hasNextSample() const; + unsigned int getOutputSampleRate() const; + unsigned int estimateInSampleCount(unsigned int outSamples) const; + void addPositionIncrement(unsigned int positionIncrement); +}; + +Analog::Analog(const AnalogOutputMode mode, const ControlROMFeatureSet *controlROMFeatures) : + leftChannelLPF(AbstractLowPassFilter::createLowPassFilter(mode, controlROMFeatures->isOldMT32AnalogLPF())), + rightChannelLPF(AbstractLowPassFilter::createLowPassFilter(mode, controlROMFeatures->isOldMT32AnalogLPF())), + synthGain(0), + reverbGain(0) +{} + +Analog::~Analog() { + delete &leftChannelLPF; + delete &rightChannelLPF; +} + +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; + } + + 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; + +#if !MT32EMU_USE_FLOAT_SAMPLES + inSampleL >>= OUTPUT_GAIN_FRACTION_BITS; + inSampleR >>= OUTPUT_GAIN_FRACTION_BITS; +#endif + + outSampleL = leftChannelLPF.process(inSampleL); + outSampleR = rightChannelLPF.process(inSampleR); + } + + *((*outStream)++) = Synth::clipSampleEx(outSampleL); + *((*outStream)++) = Synth::clipSampleEx(outSampleR); + } +} + +unsigned int Analog::getOutputSampleRate() const { + return leftChannelLPF.getOutputSampleRate(); +} + +Bit32u Analog::getDACStreamsLength(Bit32u outputLength) const { + return leftChannelLPF.estimateInSampleCount(outputLength); +} + +void Analog::setSynthOutputGain(float useSynthGain) { +#if MT32EMU_USE_FLOAT_SAMPLES + 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 +} + +AbstractLowPassFilter &AbstractLowPassFilter::createLowPassFilter(AnalogOutputMode mode, bool oldMT32AnalogLPF) { + switch (mode) { + case AnalogOutputMode_COARSE: + return *new CoarseLowPassFilter(oldMT32AnalogLPF); + case AnalogOutputMode_ACCURATE: + return *new AccurateLowPassFilter(oldMT32AnalogLPF, false); + case AnalogOutputMode_OVERSAMPLED: + return *new AccurateLowPassFilter(oldMT32AnalogLPF, true); + default: + return *new NullLowPassFilter; + } +} + +void AbstractLowPassFilter::muteRingBuffer(SampleEx *ringBuffer, unsigned int length) { + +#if MT32EMU_USE_FLOAT_SAMPLES + + SampleEx *p = ringBuffer; + while (length--) { + *(p++) = 0.0f; + } + +#else + + memset(ringBuffer, 0, length * sizeof(SampleEx)); + +#endif + +} + +bool AbstractLowPassFilter::hasNextSample() const { + return false; +} + +unsigned int AbstractLowPassFilter::getOutputSampleRate() const { + return SAMPLE_RATE; +} + +unsigned int AbstractLowPassFilter::estimateInSampleCount(unsigned int outSamples) const { + return outSamples; +} + +SampleEx NullLowPassFilter::process(const SampleEx inSample) { + return inSample; +} + +CoarseLowPassFilter::CoarseLowPassFilter(bool oldMT32AnalogLPF) : + LPF_TAPS(oldMT32AnalogLPF ? COARSE_LPF_TAPS_MT32 : COARSE_LPF_TAPS_CM32L), + ringBufferPosition(0) +{ + muteRingBuffer(ringBuffer, COARSE_LPF_DELAY_LINE_LENGTH); +} + +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 + + return sample; +} + +AccurateLowPassFilter::AccurateLowPassFilter(const bool oldMT32AnalogLPF, const bool oversample) : + LPF_TAPS(oldMT32AnalogLPF ? ACCURATE_LPF_TAPS_MT32 : ACCURATE_LPF_TAPS_CM32L), + deltas(oversample ? ACCURATE_LPF_DELTAS_OVERSAMPLED : ACCURATE_LPF_DELTAS_REGULAR), + phaseIncrement(oversample ? ACCURATE_LPF_PHASE_INCREMENT_OVERSAMPLED : ACCURATE_LPF_PHASE_INCREMENT_REGULAR), + outputSampleRate(SAMPLE_RATE * ACCURATE_LPF_NUMBER_OF_PHASES / phaseIncrement), + ringBufferPosition(0), + phase(0) +{ + muteRingBuffer(ringBuffer, ACCURATE_LPF_DELAY_LINE_LENGTH); +} + +SampleEx AccurateLowPassFilter::process(const SampleEx 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; + if (!hasNextSample()) { + ringBuffer[ringBufferPosition] = inSample; + } + + for (unsigned int tapIx = phase, delaySampleIx = 0; delaySampleIx < ACCURATE_LPF_DELAY_LINE_LENGTH; delaySampleIx++, tapIx += ACCURATE_LPF_NUMBER_OF_PHASES) { + sample += LPF_TAPS[tapIx] * ringBuffer[(delaySampleIx + ringBufferPosition) & DELAY_LINE_MASK]; + } + + phase += phaseIncrement; + if (ACCURATE_LPF_NUMBER_OF_PHASES <= phase) { + phase -= ACCURATE_LPF_NUMBER_OF_PHASES; + ringBufferPosition = (ringBufferPosition - 1) & DELAY_LINE_MASK; + } + + return SampleEx(ACCURATE_LPF_NUMBER_OF_PHASES * sample); +} + +bool AccurateLowPassFilter::hasNextSample() const { + return phaseIncrement <= phase; +} + +unsigned int AccurateLowPassFilter::getOutputSampleRate() const { + return outputSampleRate; +} + +unsigned int AccurateLowPassFilter::estimateInSampleCount(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]; +} + +void AccurateLowPassFilter::addPositionIncrement(const unsigned int positionIncrement) { + phase = (phase + positionIncrement * phaseIncrement) % ACCURATE_LPF_NUMBER_OF_PHASES; +} + +} diff --git a/audio/softsynth/mt32/Analog.h b/audio/softsynth/mt32/Analog.h new file mode 100644 index 0000000000..a48db72485 --- /dev/null +++ b/audio/softsynth/mt32/Analog.h @@ -0,0 +1,57 @@ +/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher + * Copyright (C) 2011, 2012, 2013, 2014 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#ifndef MT32EMU_ANALOG_H +#define MT32EMU_ANALOG_H + +#include "mt32emu.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 + * aside from quite poor attenuation of the mirror spectra above 16 kHz which is due to a relatively low filter order. + * + * As the final mixing of multiplexed output signal is performed after the DAC, this function is migrated here from Synth. + * Saying precisely, mixing is performed within the LPF as the entrance resistors are actually components of a LPF + * designed using the multiple feedback topology. Nevertheless, the schematic separates them. + */ +class Analog { +public: + Analog(AnalogOutputMode mode, const ControlROMFeatureSet *controlROMFeatures); + ~Analog(); + void process(Sample **outStream, const Sample *nonReverbLeft, const Sample *nonReverbRight, const Sample *reverbDryLeft, const Sample *reverbDryRight, const Sample *reverbWetLeft, const Sample *reverbWetRight, const 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 &); +}; + +} + +#endif diff --git a/audio/softsynth/mt32/BReverbModel.cpp b/audio/softsynth/mt32/BReverbModel.cpp index 37b3e9670d..5e02db8f99 100644 --- a/audio/softsynth/mt32/BReverbModel.cpp +++ b/audio/softsynth/mt32/BReverbModel.cpp @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -//#include +//#include #include "mt32emu.h" #include "BReverbModel.h" @@ -501,9 +501,9 @@ void BReverbModel::process(const Sample *inLeft, const Sample *inRight, Sample * * 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::clipBit16s(Synth::clipBit16s(Synth::clipBit16s(Synth::clipBit16s((Bit32s)outL1 + Bit32s(outL1 >> 1)) + (Bit32s)outL2) + Bit32s(outL2 >> 1)) + (Bit32s)outL3); + 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::clipBit16s((Bit32s)outL1 + Bit32s(outL1 >> 1) + (Bit32s)outL2 + Bit32s(outL2 >> 1) + (Bit32s)outL3); + Sample outSample = Synth::clipSampleEx((SampleEx)outL1 + SampleEx(outL1 >> 1) + (SampleEx)outL2 + SampleEx(outL2 >> 1) + (SampleEx)outL3); #endif *(outLeft++) = weirdMul(outSample, wetLevel, 0xFF); } @@ -515,9 +515,9 @@ void BReverbModel::process(const Sample *inLeft, const Sample *inRight, Sample * 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::clipBit16s(Synth::clipBit16s(Synth::clipBit16s(Synth::clipBit16s((Bit32s)outR1 + Bit32s(outR1 >> 1)) + (Bit32s)outR2) + Bit32s(outR2 >> 1)) + (Bit32s)outR3); + 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::clipBit16s((Bit32s)outR1 + Bit32s(outR1 >> 1) + (Bit32s)outR2 + Bit32s(outR2 >> 1) + (Bit32s)outR3); + Sample outSample = Synth::clipSampleEx((SampleEx)outR1 + SampleEx(outR1 >> 1) + (SampleEx)outR2 + SampleEx(outR2 >> 1) + (SampleEx)outR3); #endif *(outRight++) = weirdMul(outSample, wetLevel, 0xFF); } diff --git a/audio/softsynth/mt32/BReverbModel.h b/audio/softsynth/mt32/BReverbModel.h index 9b840900c3..764daf1a9e 100644 --- a/audio/softsynth/mt32/BReverbModel.h +++ b/audio/softsynth/mt32/BReverbModel.h @@ -95,7 +95,6 @@ class BReverbModel { const bool tapDelayMode; Bit32u dryAmp; Bit32u wetLevel; - void mute(); static const BReverbSettings &getCM32L_LAPCSettings(const ReverbMode mode); static const BReverbSettings &getMT32Settings(const ReverbMode mode); @@ -107,6 +106,7 @@ public: void open(); // 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, unsigned long numSamples); bool isActive() const; diff --git a/audio/softsynth/mt32/LA32FloatWaveGenerator.cpp b/audio/softsynth/mt32/LA32FloatWaveGenerator.cpp index 9265d80c88..42d820ebad 100644 --- a/audio/softsynth/mt32/LA32FloatWaveGenerator.cpp +++ b/audio/softsynth/mt32/LA32FloatWaveGenerator.cpp @@ -18,7 +18,7 @@ //#include #include "mt32emu.h" #include "mmath.h" -#include "LA32FloatWaveGenerator.h" +#include "internals.h" namespace MT32Emu { diff --git a/audio/softsynth/mt32/LA32Ramp.cpp b/audio/softsynth/mt32/LA32Ramp.cpp index 454612c842..2b31a330d2 100644 --- a/audio/softsynth/mt32/LA32Ramp.cpp +++ b/audio/softsynth/mt32/LA32Ramp.cpp @@ -50,8 +50,8 @@ We haven't fully explored: //#include #include "mt32emu.h" -#include "LA32Ramp.h" #include "mmath.h" +#include "internals.h" namespace MT32Emu { diff --git a/audio/softsynth/mt32/LA32WaveGenerator.cpp b/audio/softsynth/mt32/LA32WaveGenerator.cpp index 7ac7cc6aaa..765f75fa61 100644 --- a/audio/softsynth/mt32/LA32WaveGenerator.cpp +++ b/audio/softsynth/mt32/LA32WaveGenerator.cpp @@ -15,15 +15,15 @@ * along with this program. If not, see . */ -//#include -#include "mt32emu.h" -#include "mmath.h" -#include "LA32WaveGenerator.h" - #if MT32EMU_USE_FLOAT_SAMPLES #include "LA32FloatWaveGenerator.cpp" #else +//#include +#include "mt32emu.h" +#include "mmath.h" +#include "internals.h" + namespace MT32Emu { static const Bit32u SINE_SEGMENT_RELATIVE_LENGTH = 1 << 18; diff --git a/audio/softsynth/mt32/MemoryRegion.h b/audio/softsynth/mt32/MemoryRegion.h new file mode 100644 index 0000000000..c0cb041e11 --- /dev/null +++ b/audio/softsynth/mt32/MemoryRegion.h @@ -0,0 +1,124 @@ +/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher + * Copyright (C) 2011, 2012, 2013, 2014 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#ifndef MT32EMU_MEMORY_REGION_H +#define MT32EMU_MEMORY_REGION_H + +namespace MT32Emu { + +enum MemoryRegionType { + MR_PatchTemp, MR_RhythmTemp, MR_TimbreTemp, MR_Patches, MR_Timbres, MR_System, MR_Display, MR_Reset +}; + +class MemoryRegion { +private: + Synth *synth; + Bit8u *realMemory; + Bit8u *maxTable; +public: + MemoryRegionType type; + Bit32u startAddr, entrySize, entries; + + MemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable, MemoryRegionType useType, Bit32u useStartAddr, Bit32u useEntrySize, Bit32u useEntries) { + synth = useSynth; + realMemory = useRealMemory; + maxTable = useMaxTable; + type = useType; + startAddr = useStartAddr; + entrySize = useEntrySize; + entries = useEntries; + } + int lastTouched(Bit32u addr, Bit32u len) const { + return (offset(addr) + len - 1) / entrySize; + } + int firstTouchedOffset(Bit32u addr) const { + return offset(addr) % entrySize; + } + int firstTouched(Bit32u addr) const { + return offset(addr) / entrySize; + } + Bit32u regionEnd() const { + return startAddr + entrySize * entries; + } + bool contains(Bit32u addr) const { + return addr >= startAddr && addr < regionEnd(); + } + int offset(Bit32u addr) const { + return addr - startAddr; + } + Bit32u getClampedLen(Bit32u addr, Bit32u len) const { + if (addr + len > regionEnd()) + return regionEnd() - addr; + return len; + } + Bit32u next(Bit32u addr, Bit32u len) const { + if (addr + len > regionEnd()) { + return regionEnd() - addr; + } + return 0; + } + Bit8u getMaxValue(int off) const { + if (maxTable == NULL) + return 0xFF; + return maxTable[off % entrySize]; + } + Bit8u *getRealMemory() const { + return realMemory; + } + bool isReadable() const { + return getRealMemory() != NULL; + } + void read(unsigned int entry, unsigned int off, Bit8u *dst, unsigned int len) const; + void write(unsigned int entry, unsigned int off, const Bit8u *src, unsigned int len, bool init = false) const; +}; + +class PatchTempMemoryRegion : public MemoryRegion { +public: + PatchTempMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_PatchTemp, MT32EMU_MEMADDR(0x030000), sizeof(MemParams::PatchTemp), 9) {} +}; +class RhythmTempMemoryRegion : public MemoryRegion { +public: + RhythmTempMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_RhythmTemp, MT32EMU_MEMADDR(0x030110), sizeof(MemParams::RhythmTemp), 85) {} +}; +class TimbreTempMemoryRegion : public MemoryRegion { +public: + TimbreTempMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_TimbreTemp, MT32EMU_MEMADDR(0x040000), sizeof(TimbreParam), 8) {} +}; +class PatchesMemoryRegion : public MemoryRegion { +public: + PatchesMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_Patches, MT32EMU_MEMADDR(0x050000), sizeof(PatchParam), 128) {} +}; +class TimbresMemoryRegion : public MemoryRegion { +public: + TimbresMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_Timbres, MT32EMU_MEMADDR(0x080000), sizeof(MemParams::PaddedTimbre), 64 + 64 + 64 + 64) {} +}; +class SystemMemoryRegion : public MemoryRegion { +public: + SystemMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_System, MT32EMU_MEMADDR(0x100000), sizeof(MemParams::System), 1) {} +}; +class DisplayMemoryRegion : public MemoryRegion { +public: + DisplayMemoryRegion(Synth *useSynth) : MemoryRegion(useSynth, NULL, NULL, MR_Display, MT32EMU_MEMADDR(0x200000), MAX_SYSEX_SIZE - 1, 1) {} +}; +class ResetMemoryRegion : public MemoryRegion { +public: + ResetMemoryRegion(Synth *useSynth) : MemoryRegion(useSynth, NULL, NULL, MR_Reset, MT32EMU_MEMADDR(0x7F0000), 0x3FFF, 1) {} +}; + +} + +#endif diff --git a/audio/softsynth/mt32/MidiEventQueue.h b/audio/softsynth/mt32/MidiEventQueue.h new file mode 100644 index 0000000000..b1948c5f8e --- /dev/null +++ b/audio/softsynth/mt32/MidiEventQueue.h @@ -0,0 +1,67 @@ +/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher + * Copyright (C) 2011, 2012, 2013, 2014 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#ifndef MT32EMU_MIDI_EVENT_QUEUE_H +#define MT32EMU_MIDI_EVENT_QUEUE_H + +namespace MT32Emu { + +/** + * Used to safely store timestamped MIDI events in a local queue. + */ +struct MidiEvent { + Bit32u shortMessageData; + const Bit8u *sysexData; + Bit32u sysexLength; + Bit32u timestamp; + + ~MidiEvent(); + void setShortMessage(Bit32u shortMessageData, Bit32u timestamp); + void setSysex(const Bit8u *sysexData, Bit32u sysexLength, Bit32u timestamp); +}; + +/** + * Simple queue implementation using a ring buffer to store incoming MIDI event before the synth actually processes it. + * It is intended to: + * - get rid of prerenderer while retaining graceful partial abortion + * - add fair emulation of the MIDI interface delays + * - extend the synth interface with the default implementation of a typical rendering loop. + * THREAD SAFETY: + * It is safe to use either in a single thread environment or when there are only two threads - one performs only reading + * and one performs only writing. More complicated usage requires external synchronisation. + */ +class MidiEventQueue { +private: + MidiEvent * const ringBuffer; + const Bit32u ringBufferMask; + volatile Bit32u startPosition; + volatile Bit32u endPosition; + +public: + MidiEventQueue(Bit32u ringBufferSize = DEFAULT_MIDI_EVENT_QUEUE_SIZE); // Must be a power of 2 + ~MidiEventQueue(); + void reset(); + bool pushShortMessage(Bit32u shortMessageData, Bit32u timestamp); + bool pushSysex(const Bit8u *sysexData, Bit32u sysexLength, Bit32u timestamp); + const MidiEvent *peekMidiEvent(); + void dropMidiEvent(); + bool isFull() const; +}; + +} + +#endif diff --git a/audio/softsynth/mt32/Part.cpp b/audio/softsynth/mt32/Part.cpp index d92473b5db..cffc3ed744 100644 --- a/audio/softsynth/mt32/Part.cpp +++ b/audio/softsynth/mt32/Part.cpp @@ -19,6 +19,7 @@ //#include #include "mt32emu.h" +#include "internals.h" #include "PartialManager.h" namespace MT32Emu { diff --git a/audio/softsynth/mt32/Partial.cpp b/audio/softsynth/mt32/Partial.cpp index 7dcc6e945a..7348087509 100644 --- a/audio/softsynth/mt32/Partial.cpp +++ b/audio/softsynth/mt32/Partial.cpp @@ -21,6 +21,7 @@ #include "mt32emu.h" #include "mmath.h" +#include "internals.h" namespace MT32Emu { @@ -312,8 +313,8 @@ bool Partial::produceOutput(Sample *leftBuf, Sample *rightBuf, unsigned long len // Though, it is unknown whether this overflow is exploited somewhere. Sample leftOut = Sample((sample * leftPanValue) >> 8); Sample rightOut = Sample((sample * rightPanValue) >> 8); - *leftBuf = Synth::clipBit16s((Bit32s)*leftBuf + (Bit32s)leftOut); - *rightBuf = Synth::clipBit16s((Bit32s)*rightBuf + (Bit32s)rightOut); + *leftBuf = Synth::clipSampleEx((SampleEx)*leftBuf + (SampleEx)leftOut); + *rightBuf = Synth::clipSampleEx((SampleEx)*rightBuf + (SampleEx)rightOut); leftBuf++; rightBuf++; #endif diff --git a/audio/softsynth/mt32/PartialManager.cpp b/audio/softsynth/mt32/PartialManager.cpp index fe73087581..8ca6e4e3d7 100644 --- a/audio/softsynth/mt32/PartialManager.cpp +++ b/audio/softsynth/mt32/PartialManager.cpp @@ -18,6 +18,7 @@ //#include #include "mt32emu.h" +#include "internals.h" #include "PartialManager.h" namespace MT32Emu { diff --git a/audio/softsynth/mt32/Poly.cpp b/audio/softsynth/mt32/Poly.cpp index e07ceb4231..badcd8fb96 100644 --- a/audio/softsynth/mt32/Poly.cpp +++ b/audio/softsynth/mt32/Poly.cpp @@ -16,6 +16,7 @@ */ #include "mt32emu.h" +#include "internals.h" namespace MT32Emu { diff --git a/audio/softsynth/mt32/Poly.h b/audio/softsynth/mt32/Poly.h index 9c6431ce36..e2614369bb 100644 --- a/audio/softsynth/mt32/Poly.h +++ b/audio/softsynth/mt32/Poly.h @@ -21,6 +21,7 @@ namespace MT32Emu { class Part; +class Partial; enum PolyState { POLY_Playing, diff --git a/audio/softsynth/mt32/ROMInfo.cpp b/audio/softsynth/mt32/ROMInfo.cpp index eb9622620f..7c0127078b 100644 --- a/audio/softsynth/mt32/ROMInfo.cpp +++ b/audio/softsynth/mt32/ROMInfo.cpp @@ -21,8 +21,8 @@ namespace MT32Emu { static const ROMInfo *getKnownROMInfoFromList(unsigned int index) { - static const ControlROMFeatureSet MT32_COMPATIBLE(true); - static const ControlROMFeatureSet CM32L_COMPATIBLE(false); + static const ControlROMFeatureSet MT32_COMPATIBLE(true, true); + static const ControlROMFeatureSet CM32L_COMPATIBLE(false, false); // Known ROMs static const ROMInfo CTRL_MT32_V1_04 = {65536, "5a5cb5a77d7d55ee69657c2f870416daed52dea7", ROMInfo::Control, "ctrl_mt32_1_04", "MT-32 Control v1.04", ROMInfo::Full, NULL, &MT32_COMPATIBLE}; @@ -106,7 +106,6 @@ void ROMImage::freeROMImage(const ROMImage *romImage) { delete romImage; } - Common::File* ROMImage::getFile() const { return file; } @@ -115,11 +114,17 @@ const ROMInfo* ROMImage::getROMInfo() const { return romInfo; } -ControlROMFeatureSet::ControlROMFeatureSet(bool useDefaultReverbMT32Compatible) : defaultReverbMT32Compatible(useDefaultReverbMT32Compatible) { -} +ControlROMFeatureSet::ControlROMFeatureSet(bool useDefaultReverbMT32Compatible, bool useOldMT32AnalogLPF) : + defaultReverbMT32Compatible(useDefaultReverbMT32Compatible), + oldMT32AnalogLPF(useOldMT32AnalogLPF) +{} bool ControlROMFeatureSet::isDefaultReverbMT32Compatible() const { return defaultReverbMT32Compatible; } +bool ControlROMFeatureSet::isOldMT32AnalogLPF() const { + return oldMT32AnalogLPF; +} + } diff --git a/audio/softsynth/mt32/ROMInfo.h b/audio/softsynth/mt32/ROMInfo.h index cecbb6054f..4682620a15 100644 --- a/audio/softsynth/mt32/ROMInfo.h +++ b/audio/softsynth/mt32/ROMInfo.h @@ -77,10 +77,12 @@ public: struct ControlROMFeatureSet { private: unsigned int defaultReverbMT32Compatible : 1; + unsigned int oldMT32AnalogLPF : 1; public: - ControlROMFeatureSet(bool defaultReverbMT32Compatible); + ControlROMFeatureSet(bool defaultReverbMT32Compatible, bool oldMT32AnalogLPF); bool isDefaultReverbMT32Compatible() const; + bool isOldMT32AnalogLPF() const; }; } diff --git a/audio/softsynth/mt32/Structures.h b/audio/softsynth/mt32/Structures.h index 35dcee90d6..4dada3a847 100644 --- a/audio/softsynth/mt32/Structures.h +++ b/audio/softsynth/mt32/Structures.h @@ -31,19 +31,6 @@ namespace MT32Emu { #define MT32EMU_ALIGN_PACKED __attribute__((packed)) #endif -typedef unsigned int Bit32u; -typedef signed int Bit32s; -typedef unsigned short int Bit16u; -typedef signed short int Bit16s; -typedef unsigned char Bit8u; -typedef signed char Bit8s; - -#if MT32EMU_USE_FLOAT_SAMPLES -typedef float Sample; -#else -typedef Bit16s Sample; -#endif - // The following structures represent the MT-32's memory // Since sysex allows this memory to be written to in blocks of bytes, // we keep this packed so that we can copy data into the various @@ -184,7 +171,37 @@ struct MemParams { #pragma pack() #endif -struct ControlROMPCMStruct; +struct ControlROMMap { + Bit16u idPos; + Bit16u idLen; + const char *idBytes; + Bit16u pcmTable; // 4 * pcmCount bytes + Bit16u pcmCount; + Bit16u timbreAMap; // 128 bytes + Bit16u timbreAOffset; + bool timbreACompressed; + Bit16u timbreBMap; // 128 bytes + Bit16u timbreBOffset; + bool timbreBCompressed; + Bit16u timbreRMap; // 2 * timbreRCount bytes + Bit16u timbreRCount; + Bit16u rhythmSettings; // 4 * rhythmSettingsCount bytes + Bit16u rhythmSettingsCount; + Bit16u reserveSettings; // 9 bytes + Bit16u panSettings; // 8 bytes + Bit16u programSettings; // 8 bytes + Bit16u rhythmMaxTable; // 4 bytes + Bit16u patchMaxTable; // 16 bytes + Bit16u systemMaxTable; // 23 bytes + Bit16u timbreMaxTable; // 72 bytes +}; + +struct ControlROMPCMStruct { + Bit8u pos; + Bit8u len; + Bit8u pitchLSB; + Bit8u pitchMSB; +}; struct PCMWaveEntry { Bit32u addr; @@ -216,8 +233,6 @@ struct PatchCache { const TimbreParam::PartialParam *partialParam; }; -class Partial; // Forward reference for class defined in partial.h - } #endif diff --git a/audio/softsynth/mt32/Synth.cpp b/audio/softsynth/mt32/Synth.cpp index 3bff429875..6df7eb9e31 100644 --- a/audio/softsynth/mt32/Synth.cpp +++ b/audio/softsynth/mt32/Synth.cpp @@ -22,12 +22,19 @@ #include "mt32emu.h" #include "mmath.h" -#include "PartialManager.h" +#include "internals.h" + +#include "Analog.h" #include "BReverbModel.h" -#include "common/debug.h" +#include "MemoryRegion.h" +#include "MidiEventQueue.h" +#include "PartialManager.h" 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; + static const ControlROMMap ControlROMMaps[7] = { // ID IDc IDbytes PCMmap PCMc tmbrA tmbrAO, tmbrAC tmbrB tmbrBO, tmbrBC tmbrR trC rhythm rhyC rsrv panpot prog rhyMax patMax sysMax timMax {0x4014, 22, "\000 ver1.04 14 July 87 ", 0x3000, 128, 0x8000, 0x0000, false, 0xC000, 0x4000, false, 0x3200, 30, 0x73A6, 85, 0x57C7, 0x57E2, 0x57D0, 0x5252, 0x525E, 0x526E, 0x520A}, @@ -46,18 +53,15 @@ static inline void advanceStreamPosition(Sample *&stream, Bit32u posDelta) { } } -Bit8u Synth::calcSysexChecksum(const Bit8u *data, Bit32u len, Bit8u checksum) { +Bit8u Synth::calcSysexChecksum(const Bit8u *data, const Bit32u len, const Bit8u initChecksum) { + unsigned int checksum = -initChecksum; for (unsigned int i = 0; i < len; i++) { - checksum = checksum + data[i]; + checksum -= data[i]; } - checksum = checksum & 0x7f; - if (checksum) { - checksum = 0x80 - checksum; - } - return checksum; + return Bit8u(checksum & 0x7f); } -Synth::Synth(ReportHandler *useReportHandler) { +Synth::Synth(ReportHandler *useReportHandler) : mt32ram(*new MemParams()), mt32default(*new MemParams()) { isOpen = false; reverbOverridden = false; partialCount = DEFAULT_MAX_PARTIALS; @@ -75,6 +79,7 @@ Synth::Synth(ReportHandler *useReportHandler) { reverbModels[i] = NULL; } reverbModel = NULL; + analog = NULL; setDACInputMode(DACInputMode_NICE); setMIDIDelayMode(MIDIDelayMode_DELAY_SHORT_MESSAGES_ONLY); setOutputGain(1.0f); @@ -92,6 +97,8 @@ Synth::~Synth() { if (isDefaultReportHandler) { delete reportHandler; } + delete &mt32ram; + delete &mt32default; } void ReportHandler::showLCDMessage(const char *data) { @@ -126,7 +133,7 @@ void Synth::printDebug(const char *fmt, ...) { va_list ap; va_start(ap, fmt); #if MT32EMU_DEBUG_SAMPLESTAMPS > 0 - reportHandler->printDebug("[%u] ", renderedSampleCount); + reportHandler->printDebug("[%u] ", (char *)&renderedSampleCount); #endif reportHandler->printDebug(fmt, ap); va_end(ap); @@ -211,10 +218,7 @@ MIDIDelayMode Synth::getMIDIDelayMode() const { void Synth::setOutputGain(float newOutputGain) { if (newOutputGain < 0.0f) newOutputGain = -newOutputGain; outputGain = newOutputGain; -#if !MT32EMU_USE_FLOAT_SAMPLES - if (256.0f < newOutputGain) newOutputGain = 256.0f; - effectiveOutputGain = int(newOutputGain * 256.0f); -#endif + if (analog != NULL) analog->setSynthOutputGain(newOutputGain); } float Synth::getOutputGain() const { @@ -224,13 +228,7 @@ float Synth::getOutputGain() const { void Synth::setReverbOutputGain(float newReverbOutputGain) { if (newReverbOutputGain < 0.0f) newReverbOutputGain = -newReverbOutputGain; reverbOutputGain = newReverbOutputGain; - if (!isMT32ReverbCompatibilityMode()) newReverbOutputGain *= CM32L_REVERB_TO_LA32_ANALOG_OUTPUT_GAIN_FACTOR; -#if MT32EMU_USE_FLOAT_SAMPLES - effectiveReverbOutputGain = newReverbOutputGain; -#else - if (256.0f < newReverbOutputGain) newReverbOutputGain = 256.0f; - effectiveReverbOutputGain = int(newReverbOutputGain * 256.0f); -#endif + if (analog != NULL) analog->setReverbOutputGain(newReverbOutputGain, isMT32ReverbCompatibilityMode()); } float Synth::getReverbOutputGain() const { @@ -393,7 +391,11 @@ bool Synth::initTimbres(Bit16u mapAddress, Bit16u offset, int count, int startTi return true; } -bool Synth::open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage, unsigned int usePartialCount) { +bool Synth::open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage, AnalogOutputMode analogOutputMode) { + return open(controlROMImage, pcmROMImage, DEFAULT_MAX_PARTIALS, analogOutputMode); +} + +bool Synth::open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage, unsigned int usePartialCount, AnalogOutputMode analogOutputMode) { if (isOpen) { return false; } @@ -548,6 +550,10 @@ bool Synth::open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage, u midiQueue = new MidiEventQueue(); + analog = new Analog(analogOutputMode, controlROMFeatures); + setOutputGain(outputGain); + setReverbOutputGain(reverbOutputGain); + isOpen = true; isEnabled = false; @@ -565,6 +571,9 @@ void Synth::close(bool forced) { delete midiQueue; midiQueue = NULL; + delete analog; + analog = NULL; + delete partialManager; partialManager = NULL; @@ -603,16 +612,37 @@ void Synth::flushMIDIQueue() { } } -void Synth::setMIDIEventQueueSize(Bit32u useSize) { - if (midiQueue != NULL) { - flushMIDIQueue(); - delete midiQueue; - midiQueue = new MidiEventQueue(useSize); +Bit32u Synth::setMIDIEventQueueSize(Bit32u useSize) { + static const Bit32u MAX_QUEUE_SIZE = (1 << 24); // This results in about 256 Mb - much greater than any reasonable value + + if (midiQueue == NULL) return 0; + flushMIDIQueue(); + + // Find a power of 2 that is >= useSize + Bit32u binarySize = 1; + if (useSize < MAX_QUEUE_SIZE) { + // Using simple linear search as this isn't time critical + while (binarySize < useSize) binarySize <<= 1; + } else { + binarySize = MAX_QUEUE_SIZE; } + delete midiQueue; + midiQueue = new MidiEventQueue(binarySize); + return binarySize; } Bit32u Synth::getShortMessageLength(Bit32u msg) { - if ((msg & 0xF0) == 0xF0) return 1; + if ((msg & 0xF0) == 0xF0) { + switch (msg & 0xFF) { + case 0xF1: + case 0xF3: + return 2; + case 0xF2: + return 3; + default: + return 1; + } + } // NOTE: This calculation isn't quite correct // as it doesn't consider the running status byte return ((msg & 0xE0) == 0xC0) ? 2 : 3; @@ -638,6 +668,7 @@ bool Synth::playMsg(Bit32u msg, Bit32u timestamp) { if (midiDelayMode != MIDIDelayMode_IMMEDIATE) { timestamp = addMIDIInterfaceDelay(getShortMessageLength(msg), timestamp); } + if (!isEnabled) isEnabled = true; return midiQueue->pushShortMessage(msg, timestamp); } @@ -650,16 +681,19 @@ bool Synth::playSysex(const Bit8u *sysex, Bit32u len, Bit32u timestamp) { if (midiDelayMode == MIDIDelayMode_DELAY_ALL) { timestamp = addMIDIInterfaceDelay(len, timestamp); } + if (!isEnabled) isEnabled = true; return midiQueue->pushSysex(sysex, len, timestamp); } void Synth::playMsgNow(Bit32u msg) { - // FIXME: Implement active sensing + // NOTE: Active sense IS implemented in real hardware. However, realtime processing is clearly out of the library scope. + // It is assumed that realtime consumers of the library respond to these MIDI events as appropriate. + unsigned char code = (unsigned char)((msg & 0x0000F0) >> 4); unsigned char chan = (unsigned char)(msg & 0x00000F); unsigned char note = (unsigned char)((msg & 0x007F00) >> 8); unsigned char velocity = (unsigned char)((msg & 0x7F0000) >> 16); - isEnabled = true; + if (!isEnabled) isEnabled = true; //printDebug("Playing chan %d, code 0x%01x note: 0x%02x", chan, code, note); @@ -831,7 +865,7 @@ void Synth::playSysexWithoutHeader(unsigned char device, unsigned char command, printDebug("playSysexWithoutHeader: Message is too short (%d bytes)!", len); return; } - unsigned char checksum = calcSysexChecksum(sysex, len - 1, 0); + Bit8u checksum = calcSysexChecksum(sysex, len - 1); if (checksum != sysex[len - 1]) { printDebug("playSysexWithoutHeader: Message checksum is incorrect (provided: %02x, expected: %02x)!", sysex[len - 1], checksum); return; @@ -1410,9 +1444,8 @@ void MidiEvent::setSysex(const Bit8u *useSysexData, Bit32u useSysexLength, Bit32 memcpy(dstSysexData, useSysexData, sysexLength); } -MidiEventQueue::MidiEventQueue(Bit32u useRingBufferSize) : ringBufferSize(useRingBufferSize) { - ringBuffer = new MidiEvent[ringBufferSize]; - memset(ringBuffer, 0, ringBufferSize * sizeof(MidiEvent)); +MidiEventQueue::MidiEventQueue(Bit32u useRingBufferSize) : ringBuffer(new MidiEvent[useRingBufferSize]), ringBufferMask(useRingBufferSize - 1) { + memset(ringBuffer, 0, useRingBufferSize * sizeof(MidiEvent)); reset(); } @@ -1426,7 +1459,7 @@ void MidiEventQueue::reset() { } bool MidiEventQueue::pushShortMessage(Bit32u shortMessageData, Bit32u timestamp) { - unsigned int newEndPosition = (endPosition + 1) % ringBufferSize; + Bit32u newEndPosition = (endPosition + 1) & ringBufferMask; // Is ring buffer full? if (startPosition == newEndPosition) return false; ringBuffer[endPosition].setShortMessage(shortMessageData, timestamp); @@ -1435,7 +1468,7 @@ bool MidiEventQueue::pushShortMessage(Bit32u shortMessageData, Bit32u timestamp) } bool MidiEventQueue::pushSysex(const Bit8u *sysexData, Bit32u sysexLength, Bit32u timestamp) { - unsigned int newEndPosition = (endPosition + 1) % ringBufferSize; + Bit32u newEndPosition = (endPosition + 1) & ringBufferMask; // Is ring buffer full? if (startPosition == newEndPosition) return false; ringBuffer[endPosition].setSysex(sysexData, sysexLength, timestamp); @@ -1450,31 +1483,36 @@ const MidiEvent *MidiEventQueue::peekMidiEvent() { void MidiEventQueue::dropMidiEvent() { // Is ring buffer empty? if (startPosition != endPosition) { - startPosition = (startPosition + 1) % ringBufferSize; + startPosition = (startPosition + 1) & ringBufferMask; } } +bool MidiEventQueue::isFull() const { + return startPosition == ((endPosition + 1) & ringBufferMask); +} + +unsigned int Synth::getStereoOutputSampleRate() const { + return (analog == NULL) ? SAMPLE_RATE : analog->getOutputSampleRate(); +} + void Synth::render(Sample *stream, Bit32u len) { - Sample tmpNonReverbLeft[MAX_SAMPLES_PER_RUN]; - Sample tmpNonReverbRight[MAX_SAMPLES_PER_RUN]; - Sample tmpReverbDryLeft[MAX_SAMPLES_PER_RUN]; - Sample tmpReverbDryRight[MAX_SAMPLES_PER_RUN]; - Sample tmpReverbWetLeft[MAX_SAMPLES_PER_RUN]; - Sample tmpReverbWetRight[MAX_SAMPLES_PER_RUN]; + if (!isEnabled) { + renderedSampleCount += analog->getDACStreamsLength(len); + analog->process(NULL, NULL, NULL, NULL, NULL, NULL, NULL, len); + muteSampleBuffer(stream, len << 1); + return; + } + + // As in AnalogOutputMode_ACCURATE mode output is upsampled, buffer size MAX_SAMPLES_PER_RUN is more than enough. + 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]; while (len > 0) { - Bit32u thisLen = len > MAX_SAMPLES_PER_RUN ? MAX_SAMPLES_PER_RUN : len; - renderStreams(tmpNonReverbLeft, tmpNonReverbRight, tmpReverbDryLeft, tmpReverbDryRight, tmpReverbWetLeft, tmpReverbWetRight, thisLen); - for (Bit32u i = 0; i < thisLen; i++) { -#if MT32EMU_USE_FLOAT_SAMPLES - *(stream++) = tmpNonReverbLeft[i] + tmpReverbDryLeft[i] + tmpReverbWetLeft[i]; - *(stream++) = tmpNonReverbRight[i] + tmpReverbDryRight[i] + tmpReverbWetRight[i]; -#else - *(stream++) = clipBit16s((Bit32s)tmpNonReverbLeft[i] + (Bit32s)tmpReverbDryLeft[i] + (Bit32s)tmpReverbWetLeft[i]); - *(stream++) = clipBit16s((Bit32s)tmpNonReverbRight[i] + (Bit32s)tmpReverbDryRight[i] + (Bit32s)tmpReverbWetRight[i]); -#endif - } - len -= thisLen; + Bit32u thisPassLen = len > MAX_SAMPLES_PER_RUN ? MAX_SAMPLES_PER_RUN : len; + renderStreams(tmpNonReverbLeft, tmpNonReverbRight, tmpReverbDryLeft, tmpReverbDryRight, tmpReverbWetLeft, tmpReverbWetRight, analog->getDACStreamsLength(thisPassLen)); + analog->process(&stream, tmpNonReverbLeft, tmpNonReverbRight, tmpReverbDryLeft, tmpReverbDryRight, tmpReverbWetLeft, tmpReverbWetRight, thisPassLen); + len -= thisPassLen; } } @@ -1518,7 +1556,10 @@ void Synth::renderStreams(Sample *nonReverbLeft, Sample *nonReverbRight, Sample // 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 Synth::produceLA32Output(Sample *buffer, Bit32u len) { -#if !MT32EMU_USE_FLOAT_SAMPLES +#if MT32EMU_USE_FLOAT_SAMPLES + (void)buffer; + (void)len; +#else switch (dacInputMode) { case DACInputMode_GENERATION2: while (len--) { @@ -1528,7 +1569,7 @@ void Synth::produceLA32Output(Sample *buffer, Bit32u len) { break; case DACInputMode_NICE: while (len--) { - *buffer = clipBit16s(Bit32s(*buffer) << 1); + *buffer = clipSampleEx(SampleEx(*buffer) << 1); ++buffer; } break; @@ -1538,26 +1579,16 @@ void Synth::produceLA32Output(Sample *buffer, Bit32u len) { #endif } -void Synth::convertSamplesToOutput(Sample *buffer, Bit32u len, bool reverb) { - if (dacInputMode == DACInputMode_PURE) return; - +void Synth::convertSamplesToOutput(Sample *buffer, Bit32u len) { #if MT32EMU_USE_FLOAT_SAMPLES - float gain = reverb ? effectiveReverbOutputGain : outputGain; - while (len--) { - *(buffer++) *= gain; - } + (void)buffer; + (void)len; #else - int gain = reverb ? effectiveReverbOutputGain : effectiveOutputGain; if (dacInputMode == DACInputMode_GENERATION1) { while (len--) { - Bit32s target = Bit16s((*buffer & 0x8000) | ((*buffer << 1) & 0x7FFE)); - *(buffer++) = clipBit16s((target * gain) >> 8); + *buffer = Sample((*buffer & 0x8000) | ((*buffer << 1) & 0x7FFE)); + ++buffer; } - return; - } - while (len--) { - *buffer = clipBit16s((Bit32s(*buffer) * gain) >> 8); - ++buffer; } #endif } @@ -1566,18 +1597,18 @@ void Synth::doRenderStreams(Sample *nonReverbLeft, Sample *nonReverbRight, Sampl // Even if LA32 output isn't desired, we proceed anyway with temp buffers Sample tmpBufNonReverbLeft[MAX_SAMPLES_PER_RUN], tmpBufNonReverbRight[MAX_SAMPLES_PER_RUN]; if (nonReverbLeft == NULL) nonReverbLeft = tmpBufNonReverbLeft; - if (nonReverbLeft == NULL) nonReverbRight = tmpBufNonReverbRight; + if (nonReverbRight == NULL) nonReverbRight = tmpBufNonReverbRight; Sample tmpBufReverbDryLeft[MAX_SAMPLES_PER_RUN], tmpBufReverbDryRight[MAX_SAMPLES_PER_RUN]; if (reverbDryLeft == NULL) reverbDryLeft = tmpBufReverbDryLeft; if (reverbDryRight == NULL) reverbDryRight = tmpBufReverbDryRight; - muteSampleBuffer(nonReverbLeft, len); - muteSampleBuffer(nonReverbRight, len); - muteSampleBuffer(reverbDryLeft, len); - muteSampleBuffer(reverbDryRight, len); - if (isEnabled) { + muteSampleBuffer(nonReverbLeft, len); + muteSampleBuffer(nonReverbRight, len); + muteSampleBuffer(reverbDryLeft, len); + muteSampleBuffer(reverbDryRight, len); + for (unsigned int i = 0; i < getPartialCount(); i++) { if (partialManager->shouldReverb(i)) { partialManager->produceOutput(i, reverbDryLeft, reverbDryRight, len); @@ -1591,8 +1622,8 @@ void Synth::doRenderStreams(Sample *nonReverbLeft, Sample *nonReverbRight, Sampl if (isReverbEnabled()) { reverbModel->process(reverbDryLeft, reverbDryRight, reverbWetLeft, reverbWetRight, len); - if (reverbWetLeft != NULL) convertSamplesToOutput(reverbWetLeft, len, true); - if (reverbWetRight != NULL) convertSamplesToOutput(reverbWetRight, len, true); + if (reverbWetLeft != NULL) convertSamplesToOutput(reverbWetLeft, len); + if (reverbWetRight != NULL) convertSamplesToOutput(reverbWetRight, len); } else { muteSampleBuffer(reverbWetLeft, len); muteSampleBuffer(reverbWetRight, len); @@ -1601,15 +1632,20 @@ void Synth::doRenderStreams(Sample *nonReverbLeft, Sample *nonReverbRight, Sampl // Don't bother with conversion if the output is going to be unused if (nonReverbLeft != tmpBufNonReverbLeft) { produceLA32Output(nonReverbLeft, len); - convertSamplesToOutput(nonReverbLeft, len, false); + convertSamplesToOutput(nonReverbLeft, len); } if (nonReverbRight != tmpBufNonReverbRight) { produceLA32Output(nonReverbRight, len); - convertSamplesToOutput(nonReverbRight, len, false); + convertSamplesToOutput(nonReverbRight, len); } - if (reverbDryLeft != tmpBufReverbDryLeft) convertSamplesToOutput(reverbDryLeft, len, false); - if (reverbDryRight != tmpBufReverbDryRight) convertSamplesToOutput(reverbDryRight, len, false); + if (reverbDryLeft != tmpBufReverbDryLeft) convertSamplesToOutput(reverbDryLeft, len); + if (reverbDryRight != tmpBufReverbDryRight) convertSamplesToOutput(reverbDryRight, len); } else { + // Avoid muting buffers that wasn't requested + if (nonReverbLeft != tmpBufNonReverbLeft) muteSampleBuffer(nonReverbLeft, len); + if (nonReverbRight != tmpBufNonReverbRight) muteSampleBuffer(nonReverbRight, len); + if (reverbDryLeft != tmpBufReverbDryLeft) muteSampleBuffer(reverbDryLeft, len); + if (reverbDryRight != tmpBufReverbDryRight) muteSampleBuffer(reverbDryRight, len); muteSampleBuffer(reverbWetLeft, len); muteSampleBuffer(reverbWetRight, len); } @@ -1651,14 +1687,48 @@ bool Synth::isActive() const { return false; } -const Partial *Synth::getPartial(unsigned int partialNum) const { - return partialManager->getPartial(partialNum); -} - unsigned int Synth::getPartialCount() const { return partialCount; } +void Synth::getPartStates(bool *partStates) const { + for (int partNumber = 0; partNumber < 9; partNumber++) { + const Part *part = parts[partNumber]; + partStates[partNumber] = part->getActiveNonReleasingPartialCount() > 0; + } +} + +void Synth::getPartialStates(PartialState *partialStates) const { + static const PartialState partialPhaseToState[8] = { + PartialState_ATTACK, PartialState_ATTACK, PartialState_ATTACK, PartialState_ATTACK, + PartialState_SUSTAIN, PartialState_SUSTAIN, PartialState_RELEASE, PartialState_INACTIVE + }; + + for (unsigned int partialNum = 0; partialNum < getPartialCount(); partialNum++) { + const Partial *partial = partialManager->getPartial(partialNum); + partialStates[partialNum] = partial->isActive() ? partialPhaseToState[partial->getTVA()->getPhase()] : PartialState_INACTIVE; + } +} + +unsigned int Synth::getPlayingNotes(unsigned int partNumber, Bit8u *keys, Bit8u *velocities) const { + unsigned int playingNotes = 0; + if (isOpen && (partNumber < 9)) { + const Part *part = parts[partNumber]; + const Poly *poly = part->getFirstActivePoly(); + while (poly != NULL) { + keys[playingNotes] = (Bit8u)poly->getKey(); + velocities[playingNotes] = (Bit8u)poly->getVelocity(); + playingNotes++; + poly = poly->getNext(); + } + } + return playingNotes; +} + +const char *Synth::getPatchName(unsigned int partNumber) const { + return (!isOpen || partNumber > 8) ? NULL : parts[partNumber]->getCurrentInstr(); +} + const Part *Synth::getPart(unsigned int partNum) const { if (partNum > 8) { return NULL; diff --git a/audio/softsynth/mt32/Synth.h b/audio/softsynth/mt32/Synth.h index 37fb7b280a..97d4644ee2 100644 --- a/audio/softsynth/mt32/Synth.h +++ b/audio/softsynth/mt32/Synth.h @@ -19,15 +19,31 @@ #define MT32EMU_SYNTH_H //#include +//#include namespace MT32Emu { -class TableInitialiser; +class Analog; +class BReverbModel; +class MemoryRegion; +class MidiEventQueue; +class Part; +class Poly; class Partial; class PartialManager; -class Part; -class ROMImage; -class BReverbModel; + +class PatchTempMemoryRegion; +class RhythmTempMemoryRegion; +class TimbreTempMemoryRegion; +class PatchesMemoryRegion; +class TimbresMemoryRegion; +class SystemMemoryRegion; +class DisplayMemoryRegion; +class ResetMemoryRegion; + +struct ControlROMMap; +struct PCMWaveEntry; +struct MemParams; /** * Methods for emulating the connection between the LA32 and the DAC, which involves @@ -43,8 +59,7 @@ enum DACInputMode { // Produces samples that exactly match the bits output from the emulated LA32. // * 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, meaning its volume relative to the reverb - // output when mixed together directly will sound wrong. + // * Half the volume of any of the other modes. // * Output gain is ignored for both LA32 and reverb output. // * Perfect for developers while debugging :) DACInputMode_PURE, @@ -60,6 +75,7 @@ enum DACInputMode { DACInputMode_GENERATION2 }; +// Methods for emulating the effective delay of incoming MIDI messages introduced by a MIDI interface. enum MIDIDelayMode { // Process incoming MIDI events immediately. MIDIDelayMode_IMMEDIATE, @@ -72,6 +88,35 @@ enum MIDIDelayMode { MIDIDelayMode_DELAY_ALL }; +// Methods for emulating the effects of analogue circuits of real hardware units on the output signal. +enum AnalogOutputMode { + // Only digital path is emulated. The output samples correspond to the digital signal at the DAC entrance. + AnalogOutputMode_DIGITAL_ONLY, + // Coarse emulation of LPF circuit. High frequencies are boosted, sample rate remains unchanged. + AnalogOutputMode_COARSE, + // Finer emulation of LPF circuit. Output signal is upsampled to 48 kHz to allow emulation of audible mirror spectra above 16 kHz, + // which is passed through the LPF circuit without significant attenuation. + AnalogOutputMode_ACCURATE, + // Same as AnalogOutputMode_ACCURATE mode but the output signal is 2x oversampled, i.e. the output sample rate is 96 kHz. + // This makes subsequent resampling easier. Besides, due to nonlinear passband of the LPF emulated, it takes fewer number of MACs + // compared to a regular LPF FIR implementations. + AnalogOutputMode_OVERSAMPLED +}; + +enum ReverbMode { + REVERB_MODE_ROOM, + REVERB_MODE_HALL, + REVERB_MODE_PLATE, + REVERB_MODE_TAP_DELAY +}; + +enum PartialState { + PartialState_INACTIVE, + PartialState_ATTACK, + PartialState_SUSTAIN, + PartialState_RELEASE +}; + const Bit8u SYSEX_MANUFACTURER_ROLAND = 0x41; const Bit8u SYSEX_MDL_MT32 = 0x16; @@ -87,148 +132,10 @@ const Bit8u SYSEX_CMD_EOD = 0x45; // End of data const Bit8u SYSEX_CMD_ERR = 0x4E; // Communications error const Bit8u SYSEX_CMD_RJC = 0x4F; // Rejection -const int MAX_SYSEX_SIZE = 512; +const int MAX_SYSEX_SIZE = 512; // FIXME: Does this correspond to a real MIDI buffer used in h/w devices? const unsigned int CONTROL_ROM_SIZE = 64 * 1024; -struct ControlROMPCMStruct { - Bit8u pos; - Bit8u len; - Bit8u pitchLSB; - Bit8u pitchMSB; -}; - -struct ControlROMMap { - Bit16u idPos; - Bit16u idLen; - const char *idBytes; - Bit16u pcmTable; // 4 * pcmCount bytes - Bit16u pcmCount; - Bit16u timbreAMap; // 128 bytes - Bit16u timbreAOffset; - bool timbreACompressed; - Bit16u timbreBMap; // 128 bytes - Bit16u timbreBOffset; - bool timbreBCompressed; - Bit16u timbreRMap; // 2 * timbreRCount bytes - Bit16u timbreRCount; - Bit16u rhythmSettings; // 4 * rhythmSettingsCount bytes - Bit16u rhythmSettingsCount; - Bit16u reserveSettings; // 9 bytes - Bit16u panSettings; // 8 bytes - Bit16u programSettings; // 8 bytes - Bit16u rhythmMaxTable; // 4 bytes - Bit16u patchMaxTable; // 16 bytes - Bit16u systemMaxTable; // 23 bytes - Bit16u timbreMaxTable; // 72 bytes -}; - -enum MemoryRegionType { - MR_PatchTemp, MR_RhythmTemp, MR_TimbreTemp, MR_Patches, MR_Timbres, MR_System, MR_Display, MR_Reset -}; - -enum ReverbMode { - REVERB_MODE_ROOM, - REVERB_MODE_HALL, - REVERB_MODE_PLATE, - REVERB_MODE_TAP_DELAY -}; - -class MemoryRegion { -private: - Synth *synth; - Bit8u *realMemory; - Bit8u *maxTable; -public: - MemoryRegionType type; - Bit32u startAddr, entrySize, entries; - - MemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable, MemoryRegionType useType, Bit32u useStartAddr, Bit32u useEntrySize, Bit32u useEntries) { - synth = useSynth; - realMemory = useRealMemory; - maxTable = useMaxTable; - type = useType; - startAddr = useStartAddr; - entrySize = useEntrySize; - entries = useEntries; - } - int lastTouched(Bit32u addr, Bit32u len) const { - return (offset(addr) + len - 1) / entrySize; - } - int firstTouchedOffset(Bit32u addr) const { - return offset(addr) % entrySize; - } - int firstTouched(Bit32u addr) const { - return offset(addr) / entrySize; - } - Bit32u regionEnd() const { - return startAddr + entrySize * entries; - } - bool contains(Bit32u addr) const { - return addr >= startAddr && addr < regionEnd(); - } - int offset(Bit32u addr) const { - return addr - startAddr; - } - Bit32u getClampedLen(Bit32u addr, Bit32u len) const { - if (addr + len > regionEnd()) - return regionEnd() - addr; - return len; - } - Bit32u next(Bit32u addr, Bit32u len) const { - if (addr + len > regionEnd()) { - return regionEnd() - addr; - } - return 0; - } - Bit8u getMaxValue(int off) const { - if (maxTable == NULL) - return 0xFF; - return maxTable[off % entrySize]; - } - Bit8u *getRealMemory() const { - return realMemory; - } - bool isReadable() const { - return getRealMemory() != NULL; - } - void read(unsigned int entry, unsigned int off, Bit8u *dst, unsigned int len) const; - void write(unsigned int entry, unsigned int off, const Bit8u *src, unsigned int len, bool init = false) const; -}; - -class PatchTempMemoryRegion : public MemoryRegion { -public: - PatchTempMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_PatchTemp, MT32EMU_MEMADDR(0x030000), sizeof(MemParams::PatchTemp), 9) {} -}; -class RhythmTempMemoryRegion : public MemoryRegion { -public: - RhythmTempMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_RhythmTemp, MT32EMU_MEMADDR(0x030110), sizeof(MemParams::RhythmTemp), 85) {} -}; -class TimbreTempMemoryRegion : public MemoryRegion { -public: - TimbreTempMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_TimbreTemp, MT32EMU_MEMADDR(0x040000), sizeof(TimbreParam), 8) {} -}; -class PatchesMemoryRegion : public MemoryRegion { -public: - PatchesMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_Patches, MT32EMU_MEMADDR(0x050000), sizeof(PatchParam), 128) {} -}; -class TimbresMemoryRegion : public MemoryRegion { -public: - TimbresMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_Timbres, MT32EMU_MEMADDR(0x080000), sizeof(MemParams::PaddedTimbre), 64 + 64 + 64 + 64) {} -}; -class SystemMemoryRegion : public MemoryRegion { -public: - SystemMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_System, MT32EMU_MEMADDR(0x100000), sizeof(MemParams::System), 1) {} -}; -class DisplayMemoryRegion : public MemoryRegion { -public: - DisplayMemoryRegion(Synth *useSynth) : MemoryRegion(useSynth, NULL, NULL, MR_Display, MT32EMU_MEMADDR(0x200000), MAX_SYSEX_SIZE - 1, 1) {} -}; -class ResetMemoryRegion : public MemoryRegion { -public: - ResetMemoryRegion(Synth *useSynth) : MemoryRegion(useSynth, NULL, NULL, MR_Reset, MT32EMU_MEMADDR(0x7F0000), 0x3FFF, 1) {} -}; - class ReportHandler { friend class Synth; @@ -254,47 +161,6 @@ protected: virtual void onProgramChanged(int /* partNum */, int /* bankNum */, const char * /* patchName */) {} }; -/** - * Used to safely store timestamped MIDI events in a local queue. - */ -struct MidiEvent { - Bit32u shortMessageData; - const Bit8u *sysexData; - Bit32u sysexLength; - Bit32u timestamp; - - ~MidiEvent(); - void setShortMessage(Bit32u shortMessageData, Bit32u timestamp); - void setSysex(const Bit8u *sysexData, Bit32u sysexLength, Bit32u timestamp); -}; - -/** - * Simple queue implementation using a ring buffer to store incoming MIDI event before the synth actually processes it. - * It is intended to: - * - get rid of prerenderer while retaining graceful partial abortion - * - add fair emulation of the MIDI interface delays - * - extend the synth interface with the default implementation of a typical rendering loop. - * THREAD SAFETY: - * It is safe to use either in a single thread environment or when there are only two threads - one performs only reading - * and one performs only writing. More complicated usage requires external synchronisation. - */ -class MidiEventQueue { -private: - MidiEvent *ringBuffer; - Bit32u ringBufferSize; - volatile Bit32u startPosition; - volatile Bit32u endPosition; - -public: - MidiEventQueue(Bit32u ringBufferSize = DEFAULT_MIDI_EVENT_QUEUE_SIZE); - ~MidiEventQueue(); - void reset(); - bool pushShortMessage(Bit32u shortMessageData, Bit32u timestamp); - bool pushSysex(const Bit8u *sysexData, Bit32u sysexLength, Bit32u timestamp); - const MidiEvent *peekMidiEvent(); - void dropMidiEvent(); -}; - class Synth { friend class Part; friend class RhythmPart; @@ -335,7 +201,7 @@ private: volatile Bit32u lastReceivedMIDIEventTimestamp; volatile Bit32u renderedSampleCount; - MemParams mt32ram, mt32default; + MemParams &mt32ram, &mt32default; BReverbModel *reverbModels[4]; BReverbModel *reverbModel; @@ -346,12 +212,6 @@ private: float outputGain; float reverbOutputGain; -#if MT32EMU_USE_FLOAT_SAMPLES - float effectiveReverbOutputGain; -#else - int effectiveOutputGain; - int effectiveReverbOutputGain; -#endif bool reversedStereoEnabled; @@ -368,11 +228,12 @@ private: // We emulate this by delaying new MIDI events processing until abortion finishes. Poly *abortingPoly; - Bit32u getShortMessageLength(Bit32u msg); + Analog *analog; + Bit32u addMIDIInterfaceDelay(Bit32u len, Bit32u timestamp); void produceLA32Output(Sample *buffer, Bit32u len); - void convertSamplesToOutput(Sample *buffer, Bit32u len, bool reverb); + void convertSamplesToOutput(Sample *buffer, Bit32u len); bool isAbortingPoly() const; void doRenderStreams(Sample *nonReverbLeft, Sample *nonReverbRight, Sample *reverbDryLeft, Sample *reverbDryRight, Sample *reverbWetLeft, Sample *reverbWetRight, Bit32u len); @@ -404,13 +265,20 @@ private: void newTimbreSet(int partNum, Bit8u timbreGroup, const char patchName[]); void printDebug(const char *fmt, ...); + // partNum should be 0..7 for Part 1..8, or 8 for Rhythm + const Part *getPart(unsigned int partNum) const; + public: - static inline Bit16s clipBit16s(Bit32s sample) { + static inline Sample clipSampleEx(SampleEx sampleEx) { +#if MT32EMU_USE_FLOAT_SAMPLES + return sampleEx; +#else // Clamp values above 32767 to 32767, and values below -32768 to -32768 // FIXME: Do we really need this stuff? I think these branches are very well predicted. Instead, this introduces a chain. // The version below is actually a bit faster on my system... - //return ((sample + 0x8000) & ~0xFFFF) ? (sample >> 31) ^ 0x7FFF : (Bit16s)sample; - return ((-0x8000 <= sample) && (sample <= 0x7FFF)) ? (Bit16s)sample : (sample >> 31) ^ 0x7FFF; + //return ((sampleEx + 0x8000) & ~0xFFFF) ? (sampleEx >> 31) ^ 0x7FFF : (Sample)sampleEx; + return ((-0x8000 <= sampleEx) && (sampleEx <= 0x7FFF)) ? (Sample)sampleEx : (sampleEx >> 31) ^ 0x7FFF; +#endif } static inline void muteSampleBuffer(Sample *buffer, Bit32u len) { @@ -426,7 +294,8 @@ public: #endif } - static Bit8u calcSysexChecksum(const Bit8u *data, Bit32u len, Bit8u checksum); + static Bit32u getShortMessageLength(Bit32u msg); + static Bit8u calcSysexChecksum(const Bit8u *data, const Bit32u len, const Bit8u initChecksum = 0); // Optionally sets callbacks for reporting various errors, information and debug messages Synth(ReportHandler *useReportHandler = NULL); @@ -435,8 +304,12 @@ public: // Used to initialise the MT-32. Must be called before any other function. // Returns true if initialization was sucessful, otherwise returns false. // controlROMImage and pcmROMImage represent Control and PCM ROM images for use by synth. - // usePartialCount sets the maximum number of partials playing simultaneously for this session. - bool open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage, unsigned int usePartialCount = DEFAULT_MAX_PARTIALS); + // usePartialCount sets the maximum number of partials playing simultaneously for this session (optional). + // analogOutputMode sets the mode for emulation of analogue circuitry of the hardware units (optional). + bool open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage, unsigned int usePartialCount = DEFAULT_MAX_PARTIALS, AnalogOutputMode analogOutputMode = AnalogOutputMode_COARSE); + + // Overloaded method which opens the synth with default partial count. + bool open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage, AnalogOutputMode analogOutputMode); // Closes the MT-32 and deallocates any memory used by the synthesizer void close(bool forced = false); @@ -444,29 +317,34 @@ public: // All the enqueued events are processed by the synth immediately. void flushMIDIQueue(); - // Sets size of the internal MIDI event queue. + // Sets size of the internal MIDI event queue. The queue size is set to the minimum power of 2 that is greater or equal to the size specified. // The queue is flushed before reallocation. - void setMIDIEventQueueSize(Bit32u); + // Returns the actual queue size being used. + Bit32u setMIDIEventQueueSize(Bit32u); // Enqueues a MIDI event for subsequent playback. - // The minimum delay involves the delay introduced while the event is transferred via MIDI interface + // 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). + // The minimum delay involves emulation of the delay introduced while the event is transferred via MIDI interface // and emulation of the MCU busy-loop while it frees partials for use by a new Poly. - // Calls from multiple threads must be synchronised, although, - // no synchronisation is required with the rendering thread. + // Calls from multiple threads must be synchronised, although, no synchronisation is required with the rendering thread. + // The methods return false if the MIDI event queue is full and the message cannot be enqueued. - // 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. + // Enqueues a single short MIDI message. The message must contain a status byte. bool playMsg(Bit32u msg, Bit32u timestamp); + // Enqueues a single well formed System Exclusive MIDI message. bool playSysex(const Bit8u *sysex, Bit32u len, Bit32u timestamp); - // The MIDI event will be processed ASAP. + + // Overloaded methods for the MIDI events to be processed ASAP. bool playMsg(Bit32u msg); bool playSysex(const Bit8u *sysex, Bit32u len); // WARNING: // The methods below don't ensure minimum 1-sample delay between sequential MIDI events, // and a sequence of NoteOn and immediately succeeding NoteOff messages is always silent. + // A thread that invokes these methods must be explicitly synchronised with the thread performing sample rendering. - // Sends a 4-byte MIDI message to the MT-32 for immediate playback. + // Sends a short MIDI message to the synth for immediate playback. The message must contain a status byte. void playMsgNow(Bit32u msg); void playMsgOnPart(unsigned char part, unsigned char code, unsigned char note, unsigned char velocity); @@ -495,12 +373,17 @@ public: void setMIDIDelayMode(MIDIDelayMode mode); MIDIDelayMode getMIDIDelayMode() const; - // Sets output gain factor. Applied to all output samples and unrelated with the synth's Master volume. + // 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 void setOutputGain(float); float getOutputGain() const; - // Sets output gain factor for the reverb wet output. setOutputGain() doesn't change reverb output gain. + // Sets output gain factor for the reverb wet output channels. It rather corresponds to the gain of the output + // analog circuitry of the hardware units. However, together with setOutputGain() it offers to the user a capability + // to control the gain of reverb and non-reverb output channels independently. + // // Note: We're currently emulate CM-32L/CM-64 reverb quite accurately and the reverb output level closely // corresponds to the level of digital capture. Although, according to the CM-64 PCB schematic, // there is a difference in the reverb analogue circuit, and the resulting output gain is 0.68 @@ -512,12 +395,21 @@ public: void setReversedStereoEnabled(bool enabled); bool isReversedStereoEnabled(); - // Renders samples to the specified output stream. - // The length is in frames, not bytes (in 16-bit stereo, - // one frame is 4 bytes). + // Returns actual sample rate used in emulation of stereo analog circuitry of hardware units. + // See comment for render() below. + unsigned int getStereoOutputSampleRate() const; + + // Renders samples to the specified output stream as if they were sampled at the analog stereo output. + // When AnalogOutputMode is set to ACCURATE, the output signal is upsampled to 48 kHz in order + // to retain emulation accuracy in whole audible frequency spectra. Otherwise, native digital signal sample rate is retained. + // getStereoOutputSampleRate() can be used to query actual sample rate of the output signal. + // The length is in frames, not bytes (in 16-bit stereo, one frame is 4 bytes). void render(Sample *stream, Bit32u len); - // Renders samples to the specified output streams (any or all of which may be NULL). + // Renders samples to the specified output streams as if they appeared at the DAC entrance. + // No further processing performed in analog circuitry emulation is applied to the signal. + // NULL may be specified in place of any or all of the stream buffers. + // The length is in samples, not bytes. void renderStreams(Sample *nonReverbLeft, Sample *nonReverbRight, Sample *reverbDryLeft, Sample *reverbDryRight, Sample *reverbWetLeft, Sample *reverbWetRight, Bit32u len); // Returns true when there is at least one active partial, otherwise false. @@ -526,15 +418,28 @@ public: // Returns true if hasActivePartials() returns true, or reverb is (somewhat unreliably) detected as being active. bool isActive() const; - const Partial *getPartial(unsigned int partialNum) const; - // Returns the maximum number of partials playing simultaneously. unsigned int getPartialCount() const; - void readMemory(Bit32u addr, Bit32u len, Bit8u *data); + // Fills in current states of all the parts into the array provided. The array must have at least 9 entries to fit values for all the parts. + // If the value returned for a part is true, there is at least one active non-releasing partial playing on this part. + // This info is useful in emulating behaviour of LCD display of the hardware units. + void getPartStates(bool *partStates) const; - // partNum should be 0..7 for Part 1..8, or 8 for Rhythm - const Part *getPart(unsigned int partNum) const; + // Fills in current states of all the partials into the array provided. The array must be large enough to accommodate states of all the partials. + void getPartialStates(PartialState *partialStates) const; + + // Fills in information about currently playing notes on the specified part into the arrays provided. The arrays must be large enough + // to accommodate data for all the playing notes. The maximum number of simultaneously playing notes cannot exceed the number of partials. + // Argument partNumber should be 0..7 for Part 1..8, or 8 for Rhythm. + // Returns the number of currently playing notes on the specified part. + unsigned int getPlayingNotes(unsigned int partNumber, Bit8u *keys, Bit8u *velocities) const; + + // Returns name of the patch set on the specified part. + // Argument partNumber should be 0..7 for Part 1..8, or 8 for Rhythm. + const char *getPatchName(unsigned int partNumber) const; + + void readMemory(Bit32u addr, Bit32u len, Bit8u *data); }; } diff --git a/audio/softsynth/mt32/TVA.cpp b/audio/softsynth/mt32/TVA.cpp index 3fefb791f2..894e53f14a 100644 --- a/audio/softsynth/mt32/TVA.cpp +++ b/audio/softsynth/mt32/TVA.cpp @@ -23,6 +23,7 @@ #include "mt32emu.h" #include "mmath.h" +#include "internals.h" namespace MT32Emu { diff --git a/audio/softsynth/mt32/TVF.cpp b/audio/softsynth/mt32/TVF.cpp index bf8d50a7c9..164cf2b4cb 100644 --- a/audio/softsynth/mt32/TVF.cpp +++ b/audio/softsynth/mt32/TVF.cpp @@ -19,6 +19,7 @@ #include "mt32emu.h" #include "mmath.h" +#include "internals.h" namespace MT32Emu { diff --git a/audio/softsynth/mt32/TVP.cpp b/audio/softsynth/mt32/TVP.cpp index 374646e5f1..a8003d96dc 100644 --- a/audio/softsynth/mt32/TVP.cpp +++ b/audio/softsynth/mt32/TVP.cpp @@ -19,6 +19,7 @@ //#include #include "mt32emu.h" +#include "internals.h" namespace MT32Emu { diff --git a/audio/softsynth/mt32/Tables.cpp b/audio/softsynth/mt32/Tables.cpp index ae9f11fff0..7e165b5a7a 100644 --- a/audio/softsynth/mt32/Tables.cpp +++ b/audio/softsynth/mt32/Tables.cpp @@ -16,14 +16,15 @@ */ //#include -//#include -//#include #include "mt32emu.h" #include "mmath.h" +#include "Tables.h" namespace MT32Emu { +// UNUSED: const int MIDDLEC = 60; + const Tables &Tables::getInstance() { static const Tables instance; return instance; diff --git a/audio/softsynth/mt32/Tables.h b/audio/softsynth/mt32/Tables.h index e7b97af515..8865c7fac8 100644 --- a/audio/softsynth/mt32/Tables.h +++ b/audio/softsynth/mt32/Tables.h @@ -20,24 +20,11 @@ namespace MT32Emu { -// Sample rate to use in mixing. With the progress of development, we've found way too many thing dependent. -// In order to achieve further advance in emulation accuracy, sample rate made fixed throughout the emulator. -// The output from the synth is supposed to be resampled to convert the sample rate. -const unsigned int SAMPLE_RATE = 32000; - -// MIDI interface data transfer rate in samples. Used to simulate the transfer delay. -const double MIDI_DATA_TRANSFER_RATE = (double)SAMPLE_RATE / 31250.0 * 8.0; - -const float CM32L_REVERB_TO_LA32_ANALOG_OUTPUT_GAIN_FACTOR = 0.68f; - -const int MIDDLEC = 60; - -class Synth; - class Tables { private: Tables(); Tables(Tables &); + ~Tables() {} public: static const Tables &getInstance(); diff --git a/audio/softsynth/mt32/Types.h b/audio/softsynth/mt32/Types.h new file mode 100644 index 0000000000..934b1a1173 --- /dev/null +++ b/audio/softsynth/mt32/Types.h @@ -0,0 +1,40 @@ +/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher + * Copyright (C) 2011, 2012, 2013, 2014 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#ifndef MT32EMU_TYPES_H +#define MT32EMU_TYPES_H + +namespace MT32Emu { + +typedef unsigned int Bit32u; +typedef signed int Bit32s; +typedef unsigned short int Bit16u; +typedef signed short int Bit16s; +typedef unsigned char Bit8u; +typedef signed char Bit8s; + +#if MT32EMU_USE_FLOAT_SAMPLES +typedef float Sample; +typedef float SampleEx; +#else +typedef Bit16s Sample; +typedef Bit32s SampleEx; +#endif + +} + +#endif diff --git a/audio/softsynth/mt32/internals.h b/audio/softsynth/mt32/internals.h new file mode 100644 index 0000000000..ef56819a42 --- /dev/null +++ b/audio/softsynth/mt32/internals.h @@ -0,0 +1,83 @@ +/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher + * Copyright (C) 2011, 2012, 2013, 2014 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#ifndef MT32EMU_INTERNALS_H +#define MT32EMU_INTERNALS_H + +// Debugging + +// 0: Standard debug output is not stamped with the rendered sample count +// 1: Standard debug output is stamped with the rendered sample count +// NOTE: The "samplestamp" corresponds to the end of the last completed rendering run. +// This is important to bear in mind for debug output that occurs during a run. +#define MT32EMU_DEBUG_SAMPLESTAMPS 0 + +// 0: No debug output for initialisation progress +// 1: Debug output for initialisation progress +#define MT32EMU_MONITOR_INIT 0 + +// 0: No debug output for MIDI events +// 1: Debug output for weird MIDI events +#define MT32EMU_MONITOR_MIDI 0 + +// 0: No debug output for note on/off +// 1: Basic debug output for note on/off +// 2: Comprehensive debug output for note on/off +#define MT32EMU_MONITOR_INSTRUMENTS 0 + +// 0: No debug output for partial allocations +// 1: Show partial stats when an allocation fails +// 2: Show partial stats with every new poly +// 3: Show individual partial allocations/deactivations +#define MT32EMU_MONITOR_PARTIALS 0 + +// 0: No debug output for sysex +// 1: Basic debug output for sysex +#define MT32EMU_MONITOR_SYSEX 0 + +// 0: No debug output for sysex writes to the timbre areas +// 1: Debug output with the name and location of newly-written timbres +// 2: Complete dump of timbre parameters for newly-written timbres +#define MT32EMU_MONITOR_TIMBRES 0 + +// 0: No TVA/TVF-related debug output. +// 1: Shows changes to TVA/TVF target, increment and phase. +#define MT32EMU_MONITOR_TVA 0 +#define MT32EMU_MONITOR_TVF 0 + +// Configuration + +// If non-zero, deletes reverb buffers that are not in use to save memory. +// If zero, keeps reverb buffers for all modes around all the time to avoid allocating/freeing in the critical path. +#define MT32EMU_REDUCE_REVERB_MEMORY 1 + +// 0: Maximum speed at the cost of a bit lower emulation accuracy. +// 1: Maximum achievable emulation accuracy. +#define MT32EMU_BOSS_REVERB_PRECISE_MODE 0 + +#include "Structures.h" +#include "Tables.h" +#include "Poly.h" +#include "LA32Ramp.h" +#include "LA32WaveGenerator.h" +#include "TVA.h" +#include "TVP.h" +#include "TVF.h" +#include "Partial.h" +#include "Part.h" + +#endif diff --git a/audio/softsynth/mt32/module.mk b/audio/softsynth/mt32/module.mk index 1c8aa125ab..f966da8d08 100644 --- a/audio/softsynth/mt32/module.mk +++ b/audio/softsynth/mt32/module.mk @@ -1,6 +1,7 @@ MODULE := audio/softsynth/mt32 MODULE_OBJS := \ + Analog.o \ BReverbModel.o \ LA32Ramp.o \ LA32WaveGenerator.o \ diff --git a/audio/softsynth/mt32/mt32emu.h b/audio/softsynth/mt32/mt32emu.h index d738a5de35..1574c08f0d 100644 --- a/audio/softsynth/mt32/mt32emu.h +++ b/audio/softsynth/mt32/mt32emu.h @@ -18,63 +18,20 @@ #ifndef MT32EMU_MT32EMU_H #define MT32EMU_MT32EMU_H -// Debugging - -// 0: Standard debug output is not stamped with the rendered sample count -// 1: Standard debug output is stamped with the rendered sample count -// NOTE: The "samplestamp" corresponds to the end of the last completed rendering run. -// This is important to bear in mind for debug output that occurs during a run. -#define MT32EMU_DEBUG_SAMPLESTAMPS 0 - -// 0: No debug output for initialisation progress -// 1: Debug output for initialisation progress -#define MT32EMU_MONITOR_INIT 0 - -// 0: No debug output for MIDI events -// 1: Debug output for weird MIDI events -#define MT32EMU_MONITOR_MIDI 0 - -// 0: No debug output for note on/off -// 1: Basic debug output for note on/off -// 2: Comprehensive debug output for note on/off -#define MT32EMU_MONITOR_INSTRUMENTS 0 - -// 0: No debug output for partial allocations -// 1: Show partial stats when an allocation fails -// 2: Show partial stats with every new poly -// 3: Show individual partial allocations/deactivations -#define MT32EMU_MONITOR_PARTIALS 0 - -// 0: No debug output for sysex -// 1: Basic debug output for sysex -#define MT32EMU_MONITOR_SYSEX 0 - -// 0: No debug output for sysex writes to the timbre areas -// 1: Debug output with the name and location of newly-written timbres -// 2: Complete dump of timbre parameters for newly-written timbres -#define MT32EMU_MONITOR_TIMBRES 0 - -// 0: No TVA/TVF-related debug output. -// 1: Shows changes to TVA/TVF target, increment and phase. -#define MT32EMU_MONITOR_TVA 0 -#define MT32EMU_MONITOR_TVF 0 - // Configuration -// If non-zero, deletes reverb buffers that are not in use to save memory. -// If zero, keeps reverb buffers for all modes around all the time to avoid allocating/freeing in the critical path. -#define MT32EMU_REDUCE_REVERB_MEMORY 1 - -// 0: Maximum speed at the cost of a bit lower emulation accuracy. -// 1: Maximum achievable emulation accuracy. -#define MT32EMU_BOSS_REVERB_PRECISE_MODE 0 - // 0: Use 16-bit signed samples and refined wave generator based on logarithmic fixed-point computations and LUTs. Maximum emulation accuracy and speed. // 1: Use float samples in the wave generator and renderer. Maximum output quality and minimum noise. #define MT32EMU_USE_FLOAT_SAMPLES 0 namespace MT32Emu { +// Sample rate to use in mixing. With the progress of development, we've found way too many thing dependent. +// In order to achieve further advance in emulation accuracy, sample rate made fixed throughout the emulator, +// except the emulation of analogue path. +// The output from the synth is supposed to be resampled externally in order to convert to the desired sample rate. +const unsigned int SAMPLE_RATE = 32000; + // The default value for the maximum number of partials playing simultaneously. const unsigned int DEFAULT_MAX_PARTIALS = 32; @@ -97,17 +54,7 @@ const unsigned int MAX_SAMPLES_PER_RUN = 4096; const unsigned int DEFAULT_MIDI_EVENT_QUEUE_SIZE = 1024; } -#include "Structures.h" -#include "common/file.h" -#include "Tables.h" -#include "Poly.h" -#include "LA32Ramp.h" -#include "LA32WaveGenerator.h" -#include "TVA.h" -#include "TVP.h" -#include "TVF.h" -#include "Partial.h" -#include "Part.h" +#include "Types.h" #include "ROMInfo.h" #include "Synth.h" -- cgit v1.2.3