From 4c40b234125af642cbd0e420380001f497f35b98 Mon Sep 17 00:00:00 2001 From: OBattler Date: Thu, 10 Aug 2017 23:46:06 +0200 Subject: [PATCH] Updated MUNT to 2.2.0. --- src/Makefile.mingw | 6 +- src/SOUND/midi_mt32.c | 65 +++--------- src/SOUND/munt/Analog.h | 2 +- src/SOUND/munt/BReverbModel.h | 2 +- src/SOUND/munt/Enumerations.h | 1 - src/SOUND/munt/LA32FloatWaveGenerator.cpp | 15 ++- src/SOUND/munt/LA32Ramp.cpp | 15 ++- src/SOUND/munt/LA32Ramp.h | 1 + src/SOUND/munt/LA32WaveGenerator.cpp | 31 +++--- src/SOUND/munt/MidiStreamParser.cpp | 2 +- src/SOUND/munt/Part.cpp | 15 ++- src/SOUND/munt/Partial.cpp | 8 +- src/SOUND/munt/Partial.h | 1 + src/SOUND/munt/ROMInfo.cpp | 2 + src/SOUND/munt/SampleRateConverter_dummy.cpp | 60 +++++++++++ src/SOUND/munt/Structures.h | 6 ++ src/SOUND/munt/Synth.cpp | 99 ++++++++++++++++--- src/SOUND/munt/Synth.h | 24 ++++- src/SOUND/munt/TVA.cpp | 29 ++++-- src/SOUND/munt/TVF.cpp | 17 +++- src/SOUND/munt/TVP.cpp | 82 +++++++++------ src/SOUND/munt/TVP.h | 1 - src/SOUND/munt/c_interface/c_interface.cpp | 96 ++++++++++++++++-- src/SOUND/munt/c_interface/c_interface.h | 20 +++- src/SOUND/munt/c_interface/c_types.h | 16 ++- src/SOUND/munt/c_interface/cpp_interface.h | 11 +++ src/SOUND/munt/config.h | 4 +- src/SOUND/munt/globals.h | 2 +- .../srctools/include/FloatSampleProvider.h | 2 +- .../srctools/include/ResamplerStage.h | 2 +- 30 files changed, 471 insertions(+), 166 deletions(-) create mode 100644 src/SOUND/munt/SampleRateConverter_dummy.cpp diff --git a/src/Makefile.mingw b/src/Makefile.mingw index 8f3007457..5adf466e6 100644 --- a/src/Makefile.mingw +++ b/src/Makefile.mingw @@ -8,7 +8,7 @@ # # Modified Makefile for Win32 (MinGW32) environment. # -# Version: @(#)Makefile.mingw 1.0.34 2017/08/09 +# Version: @(#)Makefile.mingw 1.0.35 2017/08/10 # # Authors: Miran Grca, # Fred N. van Kempen, @@ -224,8 +224,8 @@ SNDOBJ = sound.o \ Analog.o BReverbModel.o File.o FileStream.o LA32Ramp.o \ LA32FloatWaveGenerator.o LA32WaveGenerator.o \ MidiStreamParser.o Part.o Partial.o PartialManager.o \ - Poly.o ROMInfo.o Synth.o Tables.o TVA.o TVF.o TVP.o \ - sha1.o c_interface.o \ + Poly.o ROMInfo.o SampleRateConverter_dummy.o Synth.o \ + Tables.o TVA.o TVF.o TVP.o sha1.o c_interface.o \ midi_system.o \ snd_speaker.o snd_ps1.o snd_pssj.o \ snd_adlib.o snd_adlibgold.o snd_ad1848.o \ diff --git a/src/SOUND/midi_mt32.c b/src/SOUND/midi_mt32.c index dbcc46a50..ddf561ee0 100644 --- a/src/SOUND/midi_mt32.c +++ b/src/SOUND/midi_mt32.c @@ -178,6 +178,7 @@ void* mt32emu_init(wchar_t *control_rom, wchar_t *pcm_rom) mt32emu_set_reverb_enabled(context, device_get_config_int("reverb")); mt32emu_set_reverb_output_gain(context, device_get_config_int("reverb_output_gain")/100.0f); mt32emu_set_reversed_stereo_enabled(context, device_get_config_int("reversed_stereo")); + mt32emu_set_nice_amp_ramp_enabled(context, device_get_config_int("nice_ramp")); pclog("mt32 output gain: %f\n", mt32emu_get_output_gain(context)); pclog("mt32 reverb output gain: %f\n", mt32emu_get_reverb_output_gain(context)); @@ -248,32 +249,11 @@ static device_config_t mt32_config[] = { .name = "output_gain", .description = "Output Gain", - .type = CONFIG_SELECTION, - .selection = + .type = CONFIG_SPINNER, + .spinner = { - { - .description = "100%", - .value = 100 - }, - { - .description = "75%", - .value = 75 - }, - { - .description = "50%", - .value = 50 - }, - { - .description = "25%", - .value = 25 - }, - { - .description = "0%", - .value = 0 - }, - { - .description = "" - } + .min = 0, + .max = 100 }, .default_int = 100 }, @@ -286,32 +266,11 @@ static device_config_t mt32_config[] = { .name = "reverb_output_gain", .description = "Reverb Output Gain", - .type = CONFIG_SELECTION, - .selection = + .type = CONFIG_SPINNER, + .spinner = { - { - .description = "100%", - .value = 100 - }, - { - .description = "75%", - .value = 75 - }, - { - .description = "50%", - .value = 50 - }, - { - .description = "25%", - .value = 25 - }, - { - .description = "0%", - .value = 0 - }, - { - .description = "" - } + .min = 0, + .max = 100 }, .default_int = 100 }, @@ -321,6 +280,12 @@ static device_config_t mt32_config[] = .type = CONFIG_BINARY, .default_int = 0 }, + { + .name = "nice_ramp", + .description = "Nice ramp", + .type = CONFIG_BINARY, + .default_int = 1 + }, { .type = -1 } diff --git a/src/SOUND/munt/Analog.h b/src/SOUND/munt/Analog.h index edaab4fe2..3b6dcabfa 100644 --- a/src/SOUND/munt/Analog.h +++ b/src/SOUND/munt/Analog.h @@ -38,7 +38,7 @@ class Analog { public: static Analog *createAnalog(const AnalogOutputMode mode, const bool oldMT32AnalogLPF, const RendererType rendererType); - virtual ~Analog() {}; + virtual ~Analog() {} virtual unsigned int getOutputSampleRate() const = 0; virtual Bit32u getDACStreamsLength(const Bit32u outputLength) const = 0; virtual void setSynthOutputGain(const float synthGain) = 0; diff --git a/src/SOUND/munt/BReverbModel.h b/src/SOUND/munt/BReverbModel.h index 2d0c5b5ff..5b1d41198 100644 --- a/src/SOUND/munt/BReverbModel.h +++ b/src/SOUND/munt/BReverbModel.h @@ -29,7 +29,7 @@ class BReverbModel { public: static BReverbModel *createBReverbModel(const ReverbMode mode, const bool mt32CompatibleModel, const RendererType rendererType); - virtual ~BReverbModel() {}; + 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()). virtual void open() = 0; diff --git a/src/SOUND/munt/Enumerations.h b/src/SOUND/munt/Enumerations.h index 8dad8e88e..bb580ca5b 100644 --- a/src/SOUND/munt/Enumerations.h +++ b/src/SOUND/munt/Enumerations.h @@ -85,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), diff --git a/src/SOUND/munt/LA32FloatWaveGenerator.cpp b/src/SOUND/munt/LA32FloatWaveGenerator.cpp index 5cc04fdeb..6ff4aa37b 100644 --- a/src/SOUND/munt/LA32FloatWaveGenerator.cpp +++ b/src/SOUND/munt/LA32FloatWaveGenerator.cpp @@ -165,12 +165,6 @@ float LA32FloatWaveGenerator::generateNextSample(const Bit32u ampVal, const Bit1 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 >= MIDDLE_CUTOFF_VALUE) && (cutoffVal < RESONANCE_DECAY_THRESHOLD_CUTOFF_VALUE)) { resAmp *= sin(FLOAT_PI * (cutoffVal - MIDDLE_CUTOFF_VALUE) / 32.0f); @@ -328,8 +322,13 @@ static inline float produceDistortedSample(float sample) { } 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. @@ -340,7 +339,7 @@ float LA32FloatPartialPair::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 LA32FloatPartialPair::deactivate(const PairType useMaster) { diff --git a/src/SOUND/munt/LA32Ramp.cpp b/src/SOUND/munt/LA32Ramp.cpp index fda38d440..9dcf143fb 100644 --- a/src/SOUND/munt/LA32Ramp.cpp +++ b/src/SOUND/munt/LA32Ramp.cpp @@ -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/src/SOUND/munt/LA32Ramp.h b/src/SOUND/munt/LA32Ramp.h index 25e70c22a..959a1ad37 100644 --- a/src/SOUND/munt/LA32Ramp.h +++ b/src/SOUND/munt/LA32Ramp.h @@ -39,6 +39,7 @@ public: Bit32u nextValue(); bool checkInterrupt(); void reset(); + bool isBelowCurrent(Bit8u target) const; }; } // namespace MT32Emu diff --git a/src/SOUND/munt/LA32WaveGenerator.cpp b/src/SOUND/munt/LA32WaveGenerator.cpp index 9bdf40610..f6f692880 100644 --- a/src/SOUND/munt/LA32WaveGenerator.cpp +++ b/src/SOUND/munt/LA32WaveGenerator.cpp @@ -378,22 +378,16 @@ Bit16s LA32IntPartialPair::unlogAndMixWGOutput(const LA32WaveGenerator &wg) { return firstSample + secondSample; } +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. @@ -401,10 +395,17 @@ Bit16s LA32IntPartialPair::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 LA32IntPartialPair::deactivate(const PairType useMaster) { diff --git a/src/SOUND/munt/MidiStreamParser.cpp b/src/SOUND/munt/MidiStreamParser.cpp index 3a70bec18..a426a20cc 100644 --- a/src/SOUND/munt/MidiStreamParser.cpp +++ b/src/SOUND/munt/MidiStreamParser.cpp @@ -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/src/SOUND/munt/Part.cpp b/src/SOUND/munt/Part.cpp index 85cc23970..9c85ce559 100644 --- a/src/SOUND/munt/Part.cpp +++ b/src/SOUND/munt/Part.cpp @@ -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/src/SOUND/munt/Partial.cpp b/src/SOUND/munt/Partial.cpp index 72a3af0ab..da5e7c4a1 100644 --- a/src/SOUND/munt/Partial.cpp +++ b/src/SOUND/munt/Partial.cpp @@ -272,6 +272,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; } @@ -368,7 +372,7 @@ bool Partial::doProduceOutput(Sample *leftBuf, Sample *rightBuf, Bit32u length, bool Partial::produceOutput(IntSample *leftBuf, IntSample *rightBuf, Bit32u length) { if (floatMode) { - synth->printDebug("Partial: Invalid call to produceOutput()!\n", synth->getSelectedRendererType()); + synth->printDebug("Partial: Invalid call to produceOutput()! Renderer = %d\n", synth->getSelectedRendererType()); return false; } return doProduceOutput(leftBuf, rightBuf, length, static_cast(la32Pair)); @@ -376,7 +380,7 @@ bool Partial::produceOutput(IntSample *leftBuf, IntSample *rightBuf, Bit32u leng bool Partial::produceOutput(FloatSample *leftBuf, FloatSample *rightBuf, Bit32u length) { if (!floatMode) { - synth->printDebug("Partial: Invalid call to produceOutput()!\n", synth->getSelectedRendererType()); + synth->printDebug("Partial: Invalid call to produceOutput()! Renderer = %d\n", synth->getSelectedRendererType()); return false; } return doProduceOutput(leftBuf, rightBuf, length, static_cast(la32Pair)); diff --git a/src/SOUND/munt/Partial.h b/src/SOUND/munt/Partial.h index bdb65952e..95f4c3fc2 100644 --- a/src/SOUND/munt/Partial.h +++ b/src/SOUND/munt/Partial.h @@ -108,6 +108,7 @@ public: void startAbort(); void startDecayAll(); bool shouldReverb(); + bool isRingModulatingNoMix() const; bool hasRingModulatingSlave() const; bool isRingModulatingSlave() const; bool isPCM() const; diff --git a/src/SOUND/munt/ROMInfo.cpp b/src/SOUND/munt/ROMInfo.cpp index 3eef26adf..8c813a4e6 100644 --- a/src/SOUND/munt/ROMInfo.cpp +++ b/src/SOUND/munt/ROMInfo.cpp @@ -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/src/SOUND/munt/SampleRateConverter_dummy.cpp b/src/SOUND/munt/SampleRateConverter_dummy.cpp new file mode 100644 index 000000000..84d51e736 --- /dev/null +++ b/src/SOUND/munt/SampleRateConverter_dummy.cpp @@ -0,0 +1,60 @@ +/* Copyright (C) 2015-2017 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 "SampleRateConverter.h" + +#include "Synth.h" + +using namespace MT32Emu; + +static inline void *createDelegate(Synth &synth, double targetSampleRate, SamplerateConversionQuality quality) { + return 0; +} + +AnalogOutputMode SampleRateConverter::getBestAnalogOutputMode(double targetSampleRate) { + return AnalogOutputMode_COARSE; +} + +SampleRateConverter::SampleRateConverter(Synth &useSynth, double targetSampleRate, SamplerateConversionQuality useQuality) : + synthInternalToTargetSampleRateRatio(SAMPLE_RATE / targetSampleRate), + useSynthDelegate(useSynth.getStereoOutputSampleRate() == targetSampleRate), + srcDelegate(useSynthDelegate ? &useSynth : createDelegate(useSynth, targetSampleRate, useQuality)) +{} + +SampleRateConverter::~SampleRateConverter() { +} + +void SampleRateConverter::getOutputSamples(float *buffer, unsigned int length) { + if (useSynthDelegate) { + static_cast(srcDelegate)->render(buffer, length); + return; + } +} + +void SampleRateConverter::getOutputSamples(Bit16s *outBuffer, unsigned int length) { + if (useSynthDelegate) { + static_cast(srcDelegate)->render(outBuffer, length); + return; + } +} + +double SampleRateConverter::convertOutputToSynthTimestamp(double outputTimestamp) const { + return outputTimestamp * synthInternalToTargetSampleRateRatio; +} + +double SampleRateConverter::convertSynthToOutputTimestamp(double synthTimestamp) const { + return synthTimestamp / synthInternalToTargetSampleRateRatio; +} diff --git a/src/SOUND/munt/Structures.h b/src/SOUND/munt/Structures.h index d3abf4ee7..d116aaeb4 100644 --- a/src/SOUND/munt/Structures.h +++ b/src/SOUND/munt/Structures.h @@ -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/src/SOUND/munt/Synth.cpp b/src/SOUND/munt/Synth.cpp index e8464fc90..f4eda5c79 100644 --- a/src/SOUND/munt/Synth.cpp +++ b/src/SOUND/munt/Synth.cpp @@ -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] = { @@ -78,7 +103,7 @@ protected: Synth &synth; void printDebug(const char *msg) const { - synth.printDebug(msg); + synth.printDebug("%s", msg); } bool isActivated() const { @@ -168,6 +193,8 @@ public: class Extensions { public: RendererType selectedRendererType; + Bit32s masterTunePitchDelta; + bool niceAmpRamp; }; Bit32u Synth::getLibraryVersionInt() { @@ -222,6 +249,7 @@ Synth::Synth(ReportHandler *useReportHandler) : setOutputGain(1.0f); setReverbOutputGain(1.0f); setReversedStereoEnabled(false); + setNiceAmpRampEnabled(true); selectRendererType(RendererType_BIT16S); patchTempMemoryRegion = NULL; @@ -387,6 +415,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(); @@ -679,6 +715,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]; @@ -865,13 +902,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; @@ -1539,6 +1580,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) @@ -1650,9 +1693,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; @@ -2013,21 +2072,35 @@ void RendererImpl::convertSamplesToOutput(IntSample *buffer, Bit32u l } static inline float produceDistortedSample(float sample) { - if (sample < -2.0f) { - return sample + 4.0f; - } else if (2.0f < sample) { - return sample - 4.0f; + // 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::produceLA32Output(FloatSample *buffer, Bit32u len) { - if (synth.getDACInputMode() == DACInputMode_GENERATION2) { + 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 = produceDistortedSample(*buffer); + *buffer *= 2.0f; buffer++; } + break; + case DACInputMode_GENERATION2: + while (len--) { + *buffer = produceDistortedSample(2.0f * *buffer); + buffer++; + } + break; + default: + break; } } @@ -2035,7 +2108,7 @@ template <> void RendererImpl::convertSamplesToOutput(FloatSample *buffer, Bit32u len) { if (synth.getDACInputMode() == DACInputMode_GENERATION1) { while (len--) { - *buffer = produceDistortedSample(*buffer); + *buffer = produceDistortedSample(2.0f * *buffer); buffer++; } } diff --git a/src/SOUND/munt/Synth.h b/src/SOUND/munt/Synth.h index 4ce023201..cde080c9d 100644 --- a/src/SOUND/munt/Synth.h +++ b/src/SOUND/munt/Synth.h @@ -124,6 +124,7 @@ friend class RhythmPart; friend class SamplerateAdapter; friend class SoxrAdapter; friend class TVA; +friend class TVF; friend class TVP; private: @@ -230,6 +231,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 @@ -258,11 +262,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: @@ -308,6 +312,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). @@ -382,7 +390,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; @@ -395,7 +402,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; @@ -405,6 +411,16 @@ 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. diff --git a/src/SOUND/munt/TVA.cpp b/src/SOUND/munt/TVA.cpp index d75abe50a..3f7064f9a 100644 --- a/src/SOUND/munt/TVA.cpp +++ b/src/SOUND/munt/TVA.cpp @@ -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/src/SOUND/munt/TVF.cpp b/src/SOUND/munt/TVF.cpp index cafeb4a8d..7ba9c7f2e 100644 --- a/src/SOUND/munt/TVF.cpp +++ b/src/SOUND/munt/TVF.cpp @@ -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/src/SOUND/munt/TVP.cpp b/src/SOUND/munt/TVP.cpp index 9adedf851..a3b364048 100644 --- a/src/SOUND/munt/TVP.cpp +++ b/src/SOUND/munt/TVP.cpp @@ -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/src/SOUND/munt/TVP.h b/src/SOUND/munt/TVP.h index 55f89c5f4..896e8c11a 100644 --- a/src/SOUND/munt/TVP.h +++ b/src/SOUND/munt/TVP.h @@ -36,7 +36,6 @@ private: const TimbreParam::PartialParam *partialParam; const MemParams::PatchTemp *patchTemp; - int maxCounter; int processTimerIncrement; int counter; Bit32u timeElapsed; diff --git a/src/SOUND/munt/c_interface/c_interface.cpp b/src/SOUND/munt/c_interface/c_interface.cpp index 9028c2612..24bb1460e 100644 --- a/src/SOUND/munt/c_interface/c_interface.cpp +++ b/src/SOUND/munt/c_interface/c_interface.cpp @@ -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 @@ -305,7 +323,7 @@ extern "C" { mt32emu_service_i mt32emu_get_service_i() { mt32emu_service_i i; - i.v0 = &SERVICE_VTABLE; + i.v2 = &SERVICE_VTABLE; return i; } @@ -329,6 +347,10 @@ mt32emu_bit32u mt32emu_get_stereo_output_samplerate(const mt32emu_analog_output_ return Synth::getStereoOutputSampleRate(static_cast(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; @@ -339,12 +361,22 @@ mt32emu_context mt32emu_create_context(mt32emu_report_handler_i report_handler, 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); @@ -417,6 +449,16 @@ void mt32emu_set_analog_output_mode(mt32emu_context context, const mt32emu_analo context->analogOutputMode = static_cast(analog_output_mode); } +void mt32emu_set_stereo_output_samplerate(mt32emu_context context, const double samplerate) { + if (0.0 <= samplerate) { + context->srcState->outputSampleRate = 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(renderer_type)); } @@ -432,11 +474,16 @@ mt32emu_return_code mt32emu_open_synth(mt32emu_const_context context) { if (!context->synth->open(*context->controlROMImage, *context->pcmROMImage, context->partialCount, context->analogOutputMode)) { 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) { @@ -444,7 +491,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) { @@ -460,6 +524,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); @@ -584,12 +652,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/src/SOUND/munt/c_interface/c_interface.h b/src/SOUND/munt/c_interface/c_interface.h index 09c004b0d..2ca3a3b04 100644 --- a/src/SOUND/munt/c_interface/c_interface.h +++ b/src/SOUND/munt/c_interface/c_interface.h @@ -209,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). @@ -324,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. */ @@ -339,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. */ @@ -350,6 +354,18 @@ MT32EMU_EXPORT void mt32emu_set_reversed_stereo_enabled(mt32emu_const_context co /** Returns whether left and right output channels are swapped. */ MT32EMU_EXPORT mt32emu_boolean mt32emu_is_reversed_stereo_enabled(mt32emu_const_context context); +/** + * 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 diff --git a/src/SOUND/munt/c_interface/c_types.h b/src/SOUND/munt/c_interface/c_types.h index bf4dab8dc..db612e282 100644 --- a/src/SOUND/munt/c_interface/c_types.h +++ b/src/SOUND/munt/c_interface/c_types.h @@ -120,7 +120,8 @@ typedef enum { typedef enum { MT32EMU_SERVICE_VERSION_0 = 0, MT32EMU_SERVICE_VERSION_1 = 1, - MT32EMU_SERVICE_VERSION_CURRENT = MT32EMU_SERVICE_VERSION_1 + MT32EMU_SERVICE_VERSION_2 = 2, + MT32EMU_SERVICE_VERSION_CURRENT = MT32EMU_SERVICE_VERSION_2 } mt32emu_service_version; /* === Report Handler Interface === */ @@ -297,6 +298,11 @@ typedef union mt32emu_service_i mt32emu_service_i; 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; @@ -306,6 +312,12 @@ typedef struct { 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. @@ -314,9 +326,11 @@ typedef struct { 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/src/SOUND/munt/c_interface/cpp_interface.h b/src/SOUND/munt/c_interface/cpp_interface.h index f40ed7037..3b02c0325 100644 --- a/src/SOUND/munt/c_interface/cpp_interface.h +++ b/src/SOUND/munt/c_interface/cpp_interface.h @@ -61,6 +61,7 @@ mt32emu_service_i mt32emu_get_service_i(); #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 @@ -90,6 +91,8 @@ mt32emu_service_i mt32emu_get_service_i(); #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 @@ -213,6 +216,7 @@ public: 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); } @@ -249,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); } @@ -271,6 +278,7 @@ private: #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 }; @@ -421,6 +429,7 @@ static mt32emu_midi_receiver_i getMidiReceiverThunk() { #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 @@ -450,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/src/SOUND/munt/config.h b/src/SOUND/munt/config.h index 7b6cd736f..9cbdf2bc6 100644 --- a/src/SOUND/munt/config.h +++ b/src/SOUND/munt/config.h @@ -18,9 +18,9 @@ #ifndef MT32EMU_CONFIG_H #define MT32EMU_CONFIG_H -#define MT32EMU_VERSION "2.1.0" +#define MT32EMU_VERSION "2.2.0" #define MT32EMU_VERSION_MAJOR 2 -#define MT32EMU_VERSION_MINOR 1 +#define MT32EMU_VERSION_MINOR 2 #define MT32EMU_VERSION_PATCH 0 /* Library Exports Configuration diff --git a/src/SOUND/munt/globals.h b/src/SOUND/munt/globals.h index d9b4e02c1..2d984c82b 100644 --- a/src/SOUND/munt/globals.h +++ b/src/SOUND/munt/globals.h @@ -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/src/SOUND/munt/srchelper/srctools/include/FloatSampleProvider.h b/src/SOUND/munt/srchelper/srctools/include/FloatSampleProvider.h index e48afcced..9820769f7 100644 --- a/src/SOUND/munt/srchelper/srctools/include/FloatSampleProvider.h +++ b/src/SOUND/munt/srchelper/srctools/include/FloatSampleProvider.h @@ -24,7 +24,7 @@ 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; }; diff --git a/src/SOUND/munt/srchelper/srctools/include/ResamplerStage.h b/src/SOUND/munt/srchelper/srctools/include/ResamplerStage.h index 6aea9624d..e335c0c38 100644 --- a/src/SOUND/munt/srchelper/srctools/include/ResamplerStage.h +++ b/src/SOUND/munt/srchelper/srctools/include/ResamplerStage.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;