[Scummvm-git-logs] scummvm master -> d94505d46e9226210ede4ec9e63f091270273b09

lotharsm noreply at scummvm.org
Thu Sep 15 10:13:54 UTC 2022


This automated email contains information about 1 new commit which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .

Summary:
d94505d46e AUDIO: MT32: Update MT32 emulation code to mt32emu 2.7.0


Commit: d94505d46e9226210ede4ec9e63f091270273b09
    https://github.com/scummvm/scummvm/commit/d94505d46e9226210ede4ec9e63f091270273b09
Author: Lothar Serra Mari (mail at serra.me)
Date: 2022-09-15T11:52:47+02:00

Commit Message:
AUDIO: MT32: Update MT32 emulation code to mt32emu 2.7.0

Changed paths:
  A audio/softsynth/mt32/Display.cpp
  A audio/softsynth/mt32/Display.h
    NEWS.md
    audio/softsynth/mt32/Analog.cpp
    audio/softsynth/mt32/Analog.h
    audio/softsynth/mt32/BReverbModel.cpp
    audio/softsynth/mt32/BReverbModel.h
    audio/softsynth/mt32/Enumerations.h
    audio/softsynth/mt32/File.cpp
    audio/softsynth/mt32/File.h
    audio/softsynth/mt32/FileStream.cpp
    audio/softsynth/mt32/FileStream.h
    audio/softsynth/mt32/LA32FloatWaveGenerator.cpp
    audio/softsynth/mt32/LA32FloatWaveGenerator.h
    audio/softsynth/mt32/LA32Ramp.cpp
    audio/softsynth/mt32/LA32Ramp.h
    audio/softsynth/mt32/LA32WaveGenerator.cpp
    audio/softsynth/mt32/LA32WaveGenerator.h
    audio/softsynth/mt32/MemoryRegion.h
    audio/softsynth/mt32/MidiEventQueue.h
    audio/softsynth/mt32/MidiStreamParser.cpp
    audio/softsynth/mt32/MidiStreamParser.h
    audio/softsynth/mt32/Part.cpp
    audio/softsynth/mt32/Part.h
    audio/softsynth/mt32/Partial.cpp
    audio/softsynth/mt32/Partial.h
    audio/softsynth/mt32/PartialManager.cpp
    audio/softsynth/mt32/PartialManager.h
    audio/softsynth/mt32/Poly.cpp
    audio/softsynth/mt32/Poly.h
    audio/softsynth/mt32/ROMInfo.cpp
    audio/softsynth/mt32/ROMInfo.h
    audio/softsynth/mt32/SampleRateConverter.cpp
    audio/softsynth/mt32/SampleRateConverter.h
    audio/softsynth/mt32/Structures.h
    audio/softsynth/mt32/Synth.cpp
    audio/softsynth/mt32/Synth.h
    audio/softsynth/mt32/TVA.cpp
    audio/softsynth/mt32/TVA.h
    audio/softsynth/mt32/TVF.cpp
    audio/softsynth/mt32/TVF.h
    audio/softsynth/mt32/TVP.cpp
    audio/softsynth/mt32/TVP.h
    audio/softsynth/mt32/Tables.cpp
    audio/softsynth/mt32/Tables.h
    audio/softsynth/mt32/Types.h
    audio/softsynth/mt32/VersionTagging.cpp
    audio/softsynth/mt32/VersionTagging.h
    audio/softsynth/mt32/c_interface/c_interface.cpp
    audio/softsynth/mt32/c_interface/c_interface.h
    audio/softsynth/mt32/c_interface/c_types.h
    audio/softsynth/mt32/c_interface/cpp_interface.h
    audio/softsynth/mt32/config.h
    audio/softsynth/mt32/globals.h
    audio/softsynth/mt32/internals.h
    audio/softsynth/mt32/mmath.h
    audio/softsynth/mt32/module.mk
    audio/softsynth/mt32/mt32emu.h
    audio/softsynth/mt32/mt32emu.pc.in
    audio/softsynth/mt32/srchelper/InternalResampler.cpp
    audio/softsynth/mt32/srchelper/InternalResampler.h
    audio/softsynth/mt32/srchelper/SamplerateAdapter.cpp
    audio/softsynth/mt32/srchelper/SamplerateAdapter.h
    audio/softsynth/mt32/srchelper/SoxrAdapter.cpp
    audio/softsynth/mt32/srchelper/SoxrAdapter.h
    audio/softsynth/mt32/srchelper/srctools/include/FIRResampler.h
    audio/softsynth/mt32/srchelper/srctools/include/FloatSampleProvider.h
    audio/softsynth/mt32/srchelper/srctools/include/IIR2xResampler.h
    audio/softsynth/mt32/srchelper/srctools/include/LinearResampler.h
    audio/softsynth/mt32/srchelper/srctools/include/ResamplerModel.h
    audio/softsynth/mt32/srchelper/srctools/include/ResamplerStage.h
    audio/softsynth/mt32/srchelper/srctools/include/SincResampler.h
    audio/softsynth/mt32/srchelper/srctools/src/FIRResampler.cpp
    audio/softsynth/mt32/srchelper/srctools/src/IIR2xResampler.cpp
    audio/softsynth/mt32/srchelper/srctools/src/LinearResampler.cpp
    audio/softsynth/mt32/srchelper/srctools/src/ResamplerModel.cpp
    audio/softsynth/mt32/srchelper/srctools/src/SincResampler.cpp
    doc/de/NEUES.md


diff --git a/NEWS.md b/NEWS.md
index 1a056d2e66d..fbd38116c1e 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -12,6 +12,7 @@ For a more comprehensive changelog of the latest experimental code, see:
 
  General:
    - Reduced amount of false positives in Mass Add.
+   - Updated the Roland MT-32 emulation code to Munt mt32emu 2.7.0.
 
  AGOS:
    - Added option to disable the fade-out effects on room transition for
diff --git a/audio/softsynth/mt32/Analog.cpp b/audio/softsynth/mt32/Analog.cpp
index 291d527c141..41fb19b44b9 100644
--- a/audio/softsynth/mt32/Analog.cpp
+++ b/audio/softsynth/mt32/Analog.cpp
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 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
@@ -127,7 +127,7 @@ public:
 template <class SampleEx>
 class NullLowPassFilter : public AbstractLowPassFilter<SampleEx> {
 public:
-	SampleEx process(const SampleEx sample) override {
+	SampleEx process(const SampleEx sample) {
 		return sample;
 	}
 };
@@ -150,7 +150,7 @@ public:
 		Synth::muteSampleBuffer(ringBuffer, COARSE_LPF_DELAY_LINE_LENGTH);
 	}
 
-	SampleEx process(const SampleEx inSample) override {
+	SampleEx process(const SampleEx inSample) {
 		static const unsigned int DELAY_LINE_MASK = COARSE_LPF_DELAY_LINE_LENGTH - 1;
 
 		SampleEx sample = lpfTaps[COARSE_LPF_DELAY_LINE_LENGTH] * ringBuffer[ringBufferPosition];
@@ -179,12 +179,12 @@ private:
 
 public:
 	AccurateLowPassFilter(const bool oldMT32AnalogLPF, const bool oversample);
-	FloatSample process(const FloatSample sample) override;
-	IntSampleEx process(const IntSampleEx sample) override;
-	bool hasNextSample() const override;
-	unsigned int getOutputSampleRate() const override;
-	unsigned int estimateInSampleCount(const unsigned int outSamples) const override;
-	void addPositionIncrement(const unsigned int positionIncrement) override;
+	FloatSample process(const FloatSample sample);
+	IntSampleEx process(const IntSampleEx sample);
+	bool hasNextSample() const;
+	unsigned int getOutputSampleRate() const;
+	unsigned int estimateInSampleCount(const unsigned int outSamples) const;
+	void addPositionIncrement(const unsigned int positionIncrement);
 };
 
 static inline IntSampleEx normaliseSample(const IntSampleEx sample) {
@@ -223,23 +223,23 @@ public:
 		delete &rightChannelLPF;
 	}
 
-	unsigned int getOutputSampleRate() const override {
+	unsigned int getOutputSampleRate() const {
 		return leftChannelLPF.getOutputSampleRate();
 	}
 
-	Bit32u getDACStreamsLength(const Bit32u outputLength) const override {
+	Bit32u getDACStreamsLength(const Bit32u outputLength) const {
 		return leftChannelLPF.estimateInSampleCount(outputLength);
 	}
 
-	void setSynthOutputGain(const float synthGain) override;
-	void setReverbOutputGain(const float reverbGain, const bool mt32ReverbCompatibilityMode) override;
+	void setSynthOutputGain(const float synthGain);
+	void setReverbOutputGain(const float reverbGain, const bool mt32ReverbCompatibilityMode);
 
-	bool process(IntSample *outStream, const IntSample *nonReverbLeft, const IntSample *nonReverbRight, const IntSample *reverbDryLeft, const IntSample *reverbDryRight, const IntSample *reverbWetLeft, const IntSample *reverbWetRight, Bit32u outLength) override;
-	bool process(FloatSample *outStream, const FloatSample *nonReverbLeft, const FloatSample *nonReverbRight, const FloatSample *reverbDryLeft, const FloatSample *reverbDryRight, const FloatSample *reverbWetLeft, const FloatSample *reverbWetRight, Bit32u outLength) override;
+	bool process(IntSample *outStream, const IntSample *nonReverbLeft, const IntSample *nonReverbRight, const IntSample *reverbDryLeft, const IntSample *reverbDryRight, const IntSample *reverbWetLeft, const IntSample *reverbWetRight, Bit32u outLength);
+	bool process(FloatSample *outStream, const FloatSample *nonReverbLeft, const FloatSample *nonReverbRight, const FloatSample *reverbDryLeft, const FloatSample *reverbDryRight, const FloatSample *reverbWetLeft, const FloatSample *reverbWetRight, Bit32u outLength);
 
 	template <class Sample>
 	void produceOutput(Sample *outStream, const Sample *nonReverbLeft, const Sample *nonReverbRight, const Sample *reverbDryLeft, const Sample *reverbDryRight, const Sample *reverbWetLeft, const Sample *reverbWetRight, Bit32u outLength) {
-		if (outStream == nullptr) {
+		if (outStream == NULL) {
 			leftChannelLPF.addPositionIncrement(outLength);
 			rightChannelLPF.addPositionIncrement(outLength);
 			return;
@@ -276,7 +276,7 @@ Analog *Analog::createAnalog(const AnalogOutputMode mode, const bool oldMT32Anal
 	default:
 		break;
 	}
-	return nullptr;
+	return NULL;
 }
 
 template<>
diff --git a/audio/softsynth/mt32/Analog.h b/audio/softsynth/mt32/Analog.h
index 307cfc34b32..62c092d9dcb 100644
--- a/audio/softsynth/mt32/Analog.h
+++ b/audio/softsynth/mt32/Analog.h
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License as published by
diff --git a/audio/softsynth/mt32/BReverbModel.cpp b/audio/softsynth/mt32/BReverbModel.cpp
index 0e109c9d493..05a2e4240c9 100644
--- a/audio/softsynth/mt32/BReverbModel.cpp
+++ b/audio/softsynth/mt32/BReverbModel.cpp
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 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
@@ -116,7 +116,7 @@ static const BReverbSettings &getCM32L_LAPCSettings(const ReverbMode mode) {
 	static const BReverbSettings REVERB_MODE_0_SETTINGS = {MODE_0_NUMBER_OF_ALLPASSES, MODE_0_ALLPASSES, MODE_0_NUMBER_OF_COMBS, MODE_0_COMBS, MODE_0_OUTL, MODE_0_OUTR, MODE_0_COMB_FACTOR, MODE_0_COMB_FEEDBACK, MODE_0_DRY_AMP, MODE_0_WET_AMP, MODE_0_LPF_AMP};
 	static const BReverbSettings REVERB_MODE_1_SETTINGS = {MODE_1_NUMBER_OF_ALLPASSES, MODE_1_ALLPASSES, MODE_1_NUMBER_OF_COMBS, MODE_1_COMBS, MODE_1_OUTL, MODE_1_OUTR, MODE_1_COMB_FACTOR, MODE_1_COMB_FEEDBACK, MODE_1_DRY_AMP, MODE_1_WET_AMP, MODE_1_LPF_AMP};
 	static const BReverbSettings REVERB_MODE_2_SETTINGS = {MODE_2_NUMBER_OF_ALLPASSES, MODE_2_ALLPASSES, MODE_2_NUMBER_OF_COMBS, MODE_2_COMBS, MODE_2_OUTL, MODE_2_OUTR, MODE_2_COMB_FACTOR, MODE_2_COMB_FEEDBACK, MODE_2_DRY_AMP, MODE_2_WET_AMP, MODE_2_LPF_AMP};
-	static const BReverbSettings REVERB_MODE_3_SETTINGS = {MODE_3_NUMBER_OF_ALLPASSES, nullptr, MODE_3_NUMBER_OF_COMBS, MODE_3_DELAY, MODE_3_OUTL, MODE_3_OUTR, MODE_3_COMB_FACTOR, MODE_3_COMB_FEEDBACK, MODE_3_DRY_AMP, MODE_3_WET_AMP, 0};
+	static const BReverbSettings REVERB_MODE_3_SETTINGS = {MODE_3_NUMBER_OF_ALLPASSES, NULL, MODE_3_NUMBER_OF_COMBS, MODE_3_DELAY, MODE_3_OUTL, MODE_3_OUTR, MODE_3_COMB_FACTOR, MODE_3_COMB_FEEDBACK, MODE_3_DRY_AMP, MODE_3_WET_AMP, 0};
 
 	static const BReverbSettings * const REVERB_SETTINGS[] = {&REVERB_MODE_0_SETTINGS, &REVERB_MODE_1_SETTINGS, &REVERB_MODE_2_SETTINGS, &REVERB_MODE_3_SETTINGS};
 
@@ -185,7 +185,7 @@ static const BReverbSettings &getMT32Settings(const ReverbMode mode) {
 	static const BReverbSettings REVERB_MODE_0_SETTINGS = {MODE_0_NUMBER_OF_ALLPASSES, MODE_0_ALLPASSES, MODE_0_NUMBER_OF_COMBS, MODE_0_COMBS, MODE_0_OUTL, MODE_0_OUTR, MODE_0_COMB_FACTOR, MODE_0_COMB_FEEDBACK, MODE_0_DRY_AMP, MODE_0_WET_AMP, MODE_0_LPF_AMP};
 	static const BReverbSettings REVERB_MODE_1_SETTINGS = {MODE_1_NUMBER_OF_ALLPASSES, MODE_1_ALLPASSES, MODE_1_NUMBER_OF_COMBS, MODE_1_COMBS, MODE_1_OUTL, MODE_1_OUTR, MODE_1_COMB_FACTOR, MODE_1_COMB_FEEDBACK, MODE_1_DRY_AMP, MODE_1_WET_AMP, MODE_1_LPF_AMP};
 	static const BReverbSettings REVERB_MODE_2_SETTINGS = {MODE_2_NUMBER_OF_ALLPASSES, MODE_2_ALLPASSES, MODE_2_NUMBER_OF_COMBS, MODE_2_COMBS, MODE_2_OUTL, MODE_2_OUTR, MODE_2_COMB_FACTOR, MODE_2_COMB_FEEDBACK, MODE_2_DRY_AMP, MODE_2_WET_AMP, MODE_2_LPF_AMP};
-	static const BReverbSettings REVERB_MODE_3_SETTINGS = {MODE_3_NUMBER_OF_ALLPASSES, nullptr, MODE_3_NUMBER_OF_COMBS, MODE_3_DELAY, MODE_3_OUTL, MODE_3_OUTR, MODE_3_COMB_FACTOR, MODE_3_COMB_FEEDBACK, MODE_3_DRY_AMP, MODE_3_WET_AMP, 0};
+	static const BReverbSettings REVERB_MODE_3_SETTINGS = {MODE_3_NUMBER_OF_ALLPASSES, NULL, MODE_3_NUMBER_OF_COMBS, MODE_3_DELAY, MODE_3_OUTL, MODE_3_OUTR, MODE_3_COMB_FACTOR, MODE_3_COMB_FEEDBACK, MODE_3_DRY_AMP, MODE_3_WET_AMP, 0};
 
 	static const BReverbSettings * const REVERB_SETTINGS[] = {&REVERB_MODE_0_SETTINGS, &REVERB_MODE_1_SETTINGS, &REVERB_MODE_2_SETTINGS, &REVERB_MODE_3_SETTINGS};
 
@@ -289,7 +289,7 @@ public:
 
 	virtual ~RingBuffer() {
 		delete[] buffer;
-		buffer = nullptr;
+		buffer = NULL;
 	}
 
 	Sample next() {
@@ -300,7 +300,7 @@ public:
 	}
 
 	bool isEmpty() const {
-		if (buffer == nullptr) return true;
+		if (buffer == NULL) return true;
 
 		Sample *buf = buffer;
 		for (Bit32u i = 0; i < size; i++) {
@@ -446,7 +446,7 @@ public:
 	Bit8u wetLevel;
 
 	BReverbModelImpl(const ReverbMode mode, const bool mt32CompatibleModel) :
-		allpasses(nullptr), combs(nullptr),
+		allpasses(NULL), combs(NULL),
 		currentSettings(mt32CompatibleModel ? getMT32Settings(mode) : getCM32L_LAPCSettings(mode)),
 		tapDelayMode(mode == REVERB_MODE_TAP_DELAY)
 	{}
@@ -455,11 +455,11 @@ public:
 		close();
 	}
 
-	bool isOpen() const override {
-		return combs != nullptr;
+	bool isOpen() const {
+		return combs != NULL;
 	}
 
-	void open() override {
+	void open() {
 		if (isOpen()) return;
 		if (currentSettings.numberOfAllpasses > 0) {
 			allpasses = new AllpassFilter<Sample>*[currentSettings.numberOfAllpasses];
@@ -479,43 +479,43 @@ public:
 		mute();
 	}
 
-	void close() override {
-		if (allpasses != nullptr) {
+	void close() {
+		if (allpasses != NULL) {
 			for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) {
-				if (allpasses[i] != nullptr) {
+				if (allpasses[i] != NULL) {
 					delete allpasses[i];
-					allpasses[i] = nullptr;
+					allpasses[i] = NULL;
 				}
 			}
 			delete[] allpasses;
-			allpasses = nullptr;
+			allpasses = NULL;
 		}
-		if (combs != nullptr) {
+		if (combs != NULL) {
 			for (Bit32u i = 0; i < currentSettings.numberOfCombs; i++) {
-				if (combs[i] != nullptr) {
+				if (combs[i] != NULL) {
 					delete combs[i];
-					combs[i] = nullptr;
+					combs[i] = NULL;
 				}
 			}
 			delete[] combs;
-			combs = nullptr;
+			combs = NULL;
 		}
 	}
 
-	void mute() override {
-		if (allpasses != nullptr) {
+	void mute() {
+		if (allpasses != NULL) {
 			for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) {
 				allpasses[i]->mute();
 			}
 		}
-		if (combs != nullptr) {
+		if (combs != NULL) {
 			for (Bit32u i = 0; i < currentSettings.numberOfCombs; i++) {
 				combs[i]->mute();
 			}
 		}
 	}
 
-	void setParameters(Bit8u time, Bit8u level) override {
+	void setParameters(Bit8u time, Bit8u level) {
 		if (!isOpen()) return;
 		level &= 7;
 		time &= 7;
@@ -542,7 +542,7 @@ public:
 		}
 	}
 
-	bool isActive() const override {
+	bool isActive() const {
 		if (!isOpen()) return false;
 		for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) {
 			if (!allpasses[i]->isEmpty()) return true;
@@ -553,7 +553,7 @@ public:
 		return false;
 	}
 
-	bool isMT32Compatible(const ReverbMode mode) const override {
+	bool isMT32Compatible(const ReverbMode mode) const {
 		return &currentSettings == &getMT32Settings(mode);
 	}
 
@@ -580,10 +580,10 @@ public:
 			if (tapDelayMode) {
 				TapDelayCombFilter<Sample> *comb = static_cast<TapDelayCombFilter<Sample> *>(*combs);
 				comb->process(dry);
-				if (outLeft != nullptr) {
+				if (outLeft != NULL) {
 					*(outLeft++) = weirdMul(comb->getLeftOutput(), wetLevel, 0xFF);
 				}
-				if (outRight != nullptr) {
+				if (outRight != NULL) {
 					*(outRight++) = weirdMul(comb->getRightOutput(), wetLevel, 0xFF);
 				}
 			} else {
@@ -605,13 +605,13 @@ public:
 				combs[2]->process(link);
 				combs[3]->process(link);
 
-				if (outLeft != nullptr) {
+				if (outLeft != NULL) {
 					Sample outL2 = combs[2]->getOutputAt(currentSettings.outLPositions[1]);
 					Sample outL3 = combs[3]->getOutputAt(currentSettings.outLPositions[2]);
 					Sample outSample = mixCombs(outL1, outL2, outL3);
 					*(outLeft++) = weirdMul(outSample, wetLevel, 0xFF);
 				}
-				if (outRight != nullptr) {
+				if (outRight != NULL) {
 					Sample outR1 = combs[1]->getOutputAt(currentSettings.outRPositions[0]);
 					Sample outR2 = combs[2]->getOutputAt(currentSettings.outRPositions[1]);
 					Sample outR3 = combs[3]->getOutputAt(currentSettings.outRPositions[2]);
@@ -622,8 +622,8 @@ public:
 		} // while ((numSamples--) > 0)
 	} // produceOutput
 
-	bool process(const IntSample *inLeft, const IntSample *inRight, IntSample *outLeft, IntSample *outRight, Bit32u numSamples) override;
-	bool process(const FloatSample *inLeft, const FloatSample *inRight, FloatSample *outLeft, FloatSample *outRight, Bit32u numSamples) override;
+	bool process(const IntSample *inLeft, const IntSample *inRight, IntSample *outLeft, IntSample *outRight, Bit32u numSamples);
+	bool process(const FloatSample *inLeft, const FloatSample *inRight, FloatSample *outLeft, FloatSample *outRight, Bit32u numSamples);
 };
 
 BReverbModel *BReverbModel::createBReverbModel(const ReverbMode mode, const bool mt32CompatibleModel, const RendererType rendererType) {
@@ -636,7 +636,7 @@ BReverbModel *BReverbModel::createBReverbModel(const ReverbMode mode, const bool
 	default:
 		break;
 	}
-	return nullptr;
+	return NULL;
 }
 
 template <>
diff --git a/audio/softsynth/mt32/BReverbModel.h b/audio/softsynth/mt32/BReverbModel.h
index ea911d3bb29..ff34e9543cb 100644
--- a/audio/softsynth/mt32/BReverbModel.h
+++ b/audio/softsynth/mt32/BReverbModel.h
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License as published by
diff --git a/audio/softsynth/mt32/Display.cpp b/audio/softsynth/mt32/Display.cpp
new file mode 100644
index 00000000000..e04ea2cd171
--- /dev/null
+++ b/audio/softsynth/mt32/Display.cpp
@@ -0,0 +1,354 @@
+/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
+ * Copyright (C) 2011-2022 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <cstdlib>
+#include <cstring>
+
+#include "internals.h"
+
+#include "Display.h"
+#include "Part.h"
+#include "Structures.h"
+#include "Synth.h"
+
+namespace MT32Emu {
+
+/* Details on the emulation model.
+ *
+ * There are four display modes emulated:
+ * - main (Master Volume), set upon startup after showing the welcoming banner;
+ * - program change notification;
+ * - custom display message received via a SysEx;
+ * - error banner (e.g. the MIDI message checksum error).
+ * Stuff like cursor blinking, patch selection mode, test mode, reaction to the front panel buttons, etc. is out of scope, as more
+ * convenient UI/UX solutions are likely desired in applications, if at all.
+ *
+ * Note, despite the LAPC and CM devices come without the LCD and the front panel buttons, the control ROM does support these,
+ * if connected to the main board. That's intended for running the test mode in a service centre, as documented.
+ *
+ * Within the aforementioned scope, the observable hardware behaviour differs noticeably, depending on the control ROM version.
+ * At least three milestones can be identified:
+ * - with MT-32 control ROM V1.06, custom messages are no longer shown unless the display is in the main (Master Volume) mode;
+ * - with MT-32 control ROM V2.04, new function introduced - Display Reset yet added many other changes (taking the full SysEx
+ *   address into account when processing custom messages and special handling of the ASCII control characters are among them);
+ *   all the second-gen devices, including LAPC-I and CM-32L, behave very similarly;
+ * - in the third-gen devices, the LCD support was partially cut down in the control ROM (basically, only the status
+ *   of the test mode, the ROM version and the checksum warnings are shown) - it's not fun, so this is NOT emulated.
+ *
+ * Features of the old-gen units.
+ * - Any message with the first address byte 0x20 is processed and has some effect on the LCD. Messages with any other first
+ *   address byte (e.g. starting with 0x21 or 0x1F7F7F with an overlap) are not considered display-relevant.
+ * - The second and the third address byte are largely irrelevant. Only presence of the second address byte makes an observable
+ *   difference, not the data within.
+ * - Any string received in the custom message is normalised - all ASCII control characters are replaced with spaces, messages
+ *   shorter than 20 bytes are filled up with spaces to the full supported length. However, should a timbre name contain an ASCII
+ *   control character, it is displayed nevertheless, with zero meaning the end-of-string.
+ * - Special message 0x20 (of just 1 address byte) shows the contents of the custom message buffer with either the last received
+ *   message or the empty buffer initially filled with spaces. See the note below about the priorities of the display modes.
+ * - Messages containing two or three bytes with just the address are considered empty and fill the custom message buffer with
+ *   all spaces. The contents of the empty buffer is then shown, depending on the priority of the current display mode.
+ * - Timing: custom messages are shown until an external event occurs like pressing a front panel button, receiving a new custom
+ *   message, program change, etc., and for indefinitely long otherwise. A program change notification is shown for about 1300
+ *   milliseconds; when the timer expires, the display returns to the main mode (irrespective to the current display mode).
+ *   When an error occurs, the warning is shown for a limited time only, similarly to the program change notifications.
+ * - The earlier old-gen devices treat all display modes with equal priority, except the main mode, which has a lower one. This
+ *   makes it possible e.g. to replace the error banner with a custom message or a program change notification, and so on.
+ *   A slightly improved behaviour is observed since the control ROM V1.06, when custom messages were de-prioritised. But still,
+ *   a program change beats an error banner even in the later models.
+ *
+ * Features of the second-gen units.
+ * - All three bytes in SysEx address are now relevant.
+ *   - It is possible to replace individual characters in the custom message buffer which are addressed individually within
+ *     the range 0x200000-0x200013.
+ *   - Writes to higher addresses up to 0x20007F simply make the custom message buffer shown, with either the last received message
+ *     or the empty buffer initially filled with spaces.
+ *   - Writes to address 0x200100 trigger the Display Reset function which resets the display to the main (Master Volume) mode.
+ *     Similarly, showing an error banner is ended. If a program change notification is shown, this function does nothing, however.
+ *   - Writes to other addresses are not considered display-relevant, albeit writing a long string to lower addresses
+ *     (e.g. 0x1F7F7F) that overlaps the display range does result in updating and showing the custom display message.
+ *   - Writing a long string that covers the custom message buffer and address 0x200100 does both things, i.e. updates the buffer
+ *     and triggers the Display Reset function.
+ * - While the display is not in a user interaction mode, custom messages and error banners have the highest display priority.
+ *   As long as these are shown, program change notifications are suppressed. The display only leaves this mode when the Display
+ *   Reset function is triggered or a front panel button is pressed. Notably, when the user enters the menu, all custom messages
+ *   are ignored, including the Display Reset command, but error banners are shown nevertheless.
+ * - Sending cut down messages with partially specified address rather leads to undefined behaviour, except for a two-byte message
+ *   0x20 0x00 which consistently shows the content of the custom message buffer (if priority permits). Otherwise, the behaviour
+ *   depends on the previously submitted address, e.g. the two-byte version of Display Reset may fail depending on the third byte
+ *   of the previous message. One-byte message 0x20 seemingly does Display Reset yet writes a zero character to a position derived
+ *   from the third byte of the preceding message.
+ *
+ * Some notes on the behaviour that is common to all hardware models.
+ * - The display is DM2011 with LSI SED1200D-0A. This unit supports 4 user-programmable characters stored in CGRAM, all 4 get
+ *   loaded at startup. Character #0 is empty (with the cursor underline), #1 is the full block (used to mark active parts),
+ *   #2 is the pipe character (identical to #124 from the CGROM) and #3 is a variation on "down arrow". During normal operation,
+ *   those duplicated characters #2 and #124 are both used in different places and character #3 can only be made visible by adding
+ *   it either to a custom timbre name or a custom message. Character #0 is probably never shown as this code has special meaning
+ *   in the processing routines. For simplicity, we only use characters #124 and #1 in this model.
+ * - When the main mode is active, the current state of the first 5 parts and the rhythm part is represented by replacing the part
+ *   symbol with the full rectangle character (#1 from the CGRAM). For voice parts, the rectangle is shown as long as at least one
+ *   partial is playing in a non-releasing phase on that part. For the rhythm part, the rectangle blinks briefly when a new NoteOn
+ *   message is received on that part (sometimes even when that actually produces no sound).
+ */
+
+static const char MASTER_VOLUME_WITH_DELIMITER[] = "|  0";
+static const char MASTER_VOLUME_WITH_DELIMITER_AND_PREFIX[] = "|vol:  0";
+static const Bit8u RHYTHM_PART_CODE = 'R';
+static const Bit8u FIELD_DELIMITER = '|';
+static const Bit8u ACTIVE_PART_INDICATOR = 1;
+
+static const Bit32u DISPLAYED_VOICE_PARTS_COUNT = 5;
+static const Bit32u SOUND_GROUP_NAME_WITH_DELIMITER_SIZE = 8;
+static const Bit32u MASTER_VOLUME_WITH_DELIMITER_SIZE = sizeof(MASTER_VOLUME_WITH_DELIMITER) - 1;
+static const Bit32u MASTER_VOLUME_WITH_DELIMITER_AND_PREFIX_SIZE = sizeof(MASTER_VOLUME_WITH_DELIMITER_AND_PREFIX) - 1;
+
+// This is the period to show those short blinks of MIDI MESSAGE LED and the rhythm part state.
+// Two related countdowns are initialised to 8 and touched each 10 milliseconds by the software timer 0 interrupt handler.
+static const Bit32u BLINK_TIME_MILLIS = 80;
+static const Bit32u BLINK_TIME_FRAMES = BLINK_TIME_MILLIS * SAMPLE_RATE / 1000;
+
+// This is based on the (free-running) TIMER1 overflow interrupt. The timer is 16-bit and clocked at 500KHz.
+// The message is displayed until 10 overflow interrupts occur. At the standard sample rate, it counts
+// precisely as 41943.04 frame times.
+static const Bit32u SCHEDULED_DISPLAY_MODE_RESET_FRAMES = 41943;
+
+/**
+ * Copies up to lengthLimit characters from possibly null-terminated source to destination. The character of destination located
+ * at the position of the null terminator (if any) in source and the rest of destination are left untouched.
+ */
+static void copyNullTerminatedString(Bit8u *destination, const Bit8u *source, Bit32u lengthLimit) {
+	for (Bit32u i = 0; i < lengthLimit; i++) {
+		Bit8u c = source[i];
+		if (c == 0) break;
+		destination[i] = c;
+	}
+}
+
+Display::Display(Synth &useSynth) :
+	synth(useSynth),
+	lastLEDState(),
+	lcdDirty(),
+	lcdUpdateSignalled(),
+	lastRhythmPartState(),
+	mode(Mode_STARTUP_MESSAGE),
+	midiMessagePlayedSinceLastReset(),
+	rhythmNotePlayedSinceLastReset()
+{
+	scheduleDisplayReset();
+	const Bit8u *startupMessage = &synth.controlROMData[synth.controlROMMap->startupMessage];
+	memcpy(displayBuffer, startupMessage, LCD_TEXT_SIZE);
+	memset(customMessageBuffer, ' ', LCD_TEXT_SIZE);
+	memset(voicePartStates, 0, sizeof voicePartStates);
+}
+
+void Display::checkDisplayStateUpdated(bool &midiMessageLEDState, bool &midiMessageLEDUpdated, bool &lcdUpdated) {
+	midiMessageLEDState = midiMessagePlayedSinceLastReset;
+	maybeResetTimer(midiMessagePlayedSinceLastReset, midiMessageLEDResetTimestamp);
+	// Note, the LED represents activity of the voice parts only.
+	for (Bit32u partIndex = 0; !midiMessageLEDState && partIndex < 8; partIndex++) {
+		midiMessageLEDState = voicePartStates[partIndex];
+	}
+	midiMessageLEDUpdated = lastLEDState != midiMessageLEDState;
+	lastLEDState = midiMessageLEDState;
+
+	if (displayResetScheduled && shouldResetTimer(displayResetTimestamp)) setMainDisplayMode();
+
+	if (lastRhythmPartState != rhythmNotePlayedSinceLastReset && mode == Mode_MAIN) lcdDirty = true;
+	lastRhythmPartState = rhythmNotePlayedSinceLastReset;
+	maybeResetTimer(rhythmNotePlayedSinceLastReset, rhythmStateResetTimestamp);
+
+	lcdUpdated = lcdDirty && !lcdUpdateSignalled;
+	if (lcdUpdated) lcdUpdateSignalled = true;
+}
+
+bool Display::getDisplayState(char *targetBuffer, bool narrowLCD) {
+	if (lcdUpdateSignalled) {
+		lcdDirty = false;
+		lcdUpdateSignalled = false;
+
+		switch (mode) {
+		case Mode_CUSTOM_MESSAGE:
+			if (synth.isDisplayOldMT32Compatible()) {
+				memcpy(displayBuffer, customMessageBuffer, LCD_TEXT_SIZE);
+			} else {
+				copyNullTerminatedString(displayBuffer, customMessageBuffer, LCD_TEXT_SIZE);
+			}
+			break;
+		case Mode_ERROR_MESSAGE: {
+			const Bit8u *sysexErrorMessage = &synth.controlROMData[synth.controlROMMap->sysexErrorMessage];
+			memcpy(displayBuffer, sysexErrorMessage, LCD_TEXT_SIZE);
+			break;
+		}
+		case Mode_PROGRAM_CHANGE: {
+			Bit8u *writePosition = displayBuffer;
+			*writePosition++ = '1' + lastProgramChangePartIndex;
+			*writePosition++ = FIELD_DELIMITER;
+			if (narrowLCD) {
+				writePosition[TIMBRE_NAME_SIZE] = 0;
+			} else {
+				memcpy(writePosition, lastProgramChangeSoundGroupName, SOUND_GROUP_NAME_WITH_DELIMITER_SIZE);
+				writePosition += SOUND_GROUP_NAME_WITH_DELIMITER_SIZE;
+			}
+			copyNullTerminatedString(writePosition, lastProgramChangeTimbreName, TIMBRE_NAME_SIZE);
+			break;
+		}
+		case Mode_MAIN: {
+			Bit8u *writePosition = displayBuffer;
+			for (Bit32u partIndex = 0; partIndex < DISPLAYED_VOICE_PARTS_COUNT; partIndex++) {
+				*writePosition++ = voicePartStates[partIndex] ? ACTIVE_PART_INDICATOR : '1' + partIndex;
+				*writePosition++ = ' ';
+			}
+			*writePosition++ = lastRhythmPartState ? ACTIVE_PART_INDICATOR : RHYTHM_PART_CODE;
+			*writePosition++ = ' ';
+			if (narrowLCD) {
+				memcpy(writePosition, MASTER_VOLUME_WITH_DELIMITER, MASTER_VOLUME_WITH_DELIMITER_SIZE);
+				writePosition += MASTER_VOLUME_WITH_DELIMITER_SIZE;
+				*writePosition = 0;
+			} else {
+				memcpy(writePosition, MASTER_VOLUME_WITH_DELIMITER_AND_PREFIX, MASTER_VOLUME_WITH_DELIMITER_AND_PREFIX_SIZE);
+				writePosition += MASTER_VOLUME_WITH_DELIMITER_AND_PREFIX_SIZE;
+			}
+			Bit32u masterVol = synth.mt32ram.system.masterVol;
+			while (masterVol > 0) {
+				std::div_t result = std::div(masterVol, 10);
+				*--writePosition = '0' + result.rem;
+				masterVol = result.quot;
+			}
+			break;
+		}
+		default:
+			break;
+		}
+	}
+
+	memcpy(targetBuffer, displayBuffer, LCD_TEXT_SIZE);
+	targetBuffer[LCD_TEXT_SIZE] = 0;
+	return lastLEDState;
+}
+
+void Display::setMainDisplayMode() {
+	displayResetScheduled = false;
+	mode = Mode_MAIN;
+	lcdDirty = true;
+}
+
+void Display::midiMessagePlayed() {
+	midiMessagePlayedSinceLastReset = true;
+	midiMessageLEDResetTimestamp = synth.renderedSampleCount + BLINK_TIME_FRAMES;
+}
+
+void Display::rhythmNotePlayed() {
+	rhythmNotePlayedSinceLastReset = true;
+	rhythmStateResetTimestamp = synth.renderedSampleCount + BLINK_TIME_FRAMES;
+	midiMessagePlayed();
+	if (synth.isDisplayOldMT32Compatible() && mode == Mode_CUSTOM_MESSAGE) setMainDisplayMode();
+}
+
+void Display::voicePartStateChanged(Bit8u partIndex, bool activated) {
+	if (mode == Mode_MAIN) lcdDirty = true;
+	voicePartStates[partIndex] = activated;
+	if (synth.isDisplayOldMT32Compatible() && mode == Mode_CUSTOM_MESSAGE) setMainDisplayMode();
+}
+
+void Display::masterVolumeChanged() {
+	if (mode == Mode_MAIN) lcdDirty = true;
+}
+
+void Display::programChanged(Bit8u partIndex) {
+	if (!synth.isDisplayOldMT32Compatible() && (mode == Mode_CUSTOM_MESSAGE || mode == Mode_ERROR_MESSAGE)) return;
+	mode = Mode_PROGRAM_CHANGE;
+	lcdDirty = true;
+	scheduleDisplayReset();
+	lastProgramChangePartIndex = partIndex;
+	const Part *part = synth.getPart(partIndex);
+	lastProgramChangeSoundGroupName = synth.getSoundGroupName(part);
+	memcpy(lastProgramChangeTimbreName, part->getCurrentInstr(), TIMBRE_NAME_SIZE);
+}
+
+void Display::checksumErrorOccurred() {
+	if (mode != Mode_ERROR_MESSAGE) {
+		mode = Mode_ERROR_MESSAGE;
+		lcdDirty = true;
+	}
+	if (synth.isDisplayOldMT32Compatible()) {
+		scheduleDisplayReset();
+	} else {
+		displayResetScheduled = false;
+	}
+}
+
+bool Display::customDisplayMessageReceived(const Bit8u *message, Bit32u startIndex, Bit32u length) {
+	if (synth.isDisplayOldMT32Compatible()) {
+		for (Bit32u i = 0; i < LCD_TEXT_SIZE; i++) {
+			Bit8u c = i < length ? message[i] : ' ';
+			if (c < 32 || 127 < c) c = ' ';
+			customMessageBuffer[i] = c;
+		}
+		if (!synth.controlROMFeatures->quirkDisplayCustomMessagePriority
+			&& (mode == Mode_PROGRAM_CHANGE || mode == Mode_ERROR_MESSAGE)) return false;
+		// Note, real devices keep the display reset timer running.
+	} else {
+		if (startIndex > 0x80) return false;
+		if (startIndex == 0x80) {
+			if (mode != Mode_PROGRAM_CHANGE) setMainDisplayMode();
+			return false;
+		}
+		displayResetScheduled = false;
+		if (startIndex < LCD_TEXT_SIZE) {
+			if (length > LCD_TEXT_SIZE - startIndex) length = LCD_TEXT_SIZE - startIndex;
+			memcpy(customMessageBuffer + startIndex, message, length);
+		}
+	}
+	mode = Mode_CUSTOM_MESSAGE;
+	lcdDirty = true;
+	return true;
+}
+
+void Display::displayControlMessageReceived(const Bit8u *messageBytes, Bit32u length) {
+	Bit8u emptyMessage[] = { 0 };
+	if (synth.isDisplayOldMT32Compatible()) {
+		if (length == 1) {
+			customDisplayMessageReceived(customMessageBuffer, 0, LCD_TEXT_SIZE);
+		} else {
+			customDisplayMessageReceived(emptyMessage, 0, 0);
+		}
+	} else {
+		// Always assume the third byte to be zero for simplicity.
+		if (length == 2) {
+			customDisplayMessageReceived(emptyMessage, messageBytes[1] << 7, 0);
+		} else if (length == 1) {
+			customMessageBuffer[0] = 0;
+			customDisplayMessageReceived(emptyMessage, 0x80, 0);
+		}
+	}
+}
+
+void Display::scheduleDisplayReset() {
+	displayResetTimestamp = synth.renderedSampleCount + SCHEDULED_DISPLAY_MODE_RESET_FRAMES;
+	displayResetScheduled = true;
+}
+
+bool Display::shouldResetTimer(Bit32u scheduledResetTimestamp) {
+	// Deals with wrapping of renderedSampleCount.
+	return Bit32s(scheduledResetTimestamp - synth.renderedSampleCount) < 0;
+}
+
+void Display::maybeResetTimer(bool &timerState, Bit32u scheduledResetTimestamp) {
+	if (timerState && shouldResetTimer(scheduledResetTimestamp)) timerState = false;
+}
+
+} // namespace MT32Emu
diff --git a/audio/softsynth/mt32/Display.h b/audio/softsynth/mt32/Display.h
new file mode 100644
index 00000000000..1802b0bd28c
--- /dev/null
+++ b/audio/softsynth/mt32/Display.h
@@ -0,0 +1,91 @@
+/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
+ * Copyright (C) 2011-2022 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MT32EMU_DISPLAY_H
+#define MT32EMU_DISPLAY_H
+
+#include "globals.h"
+#include "Types.h"
+
+namespace MT32Emu {
+
+class Synth;
+
+/** Facilitates emulation of internal state of the MIDI MESSAGE LED and the MT-32 LCD. */
+class Display {
+public:
+	static const unsigned int LCD_TEXT_SIZE = 20;
+
+	enum Mode {
+		Mode_MAIN, // a.k.a. Master Volume
+		Mode_STARTUP_MESSAGE,
+		Mode_PROGRAM_CHANGE,
+		Mode_CUSTOM_MESSAGE,
+		Mode_ERROR_MESSAGE
+	};
+
+	Display(Synth &synth);
+	void checkDisplayStateUpdated(bool &midiMessageLEDState, bool &midiMessageLEDUpdated, bool &lcdUpdated);
+	/** Returns whether the MIDI MESSAGE LED is ON and fills the targetBuffer parameter. */
+	bool getDisplayState(char *targetBuffer, bool narrowLCD);
+	void setMainDisplayMode();
+
+	void midiMessagePlayed();
+	void rhythmNotePlayed();
+	void voicePartStateChanged(Bit8u partIndex, bool activated);
+	void masterVolumeChanged();
+	void programChanged(Bit8u partIndex);
+	void checksumErrorOccurred();
+	bool customDisplayMessageReceived(const Bit8u *message, Bit32u startIndex, Bit32u length);
+	void displayControlMessageReceived(const Bit8u *messageBytes, Bit32u length);
+
+private:
+	typedef Bit8u DisplayBuffer[LCD_TEXT_SIZE];
+
+	static const unsigned int TIMBRE_NAME_SIZE = 10;
+
+	Synth &synth;
+
+	bool lastLEDState;
+	bool lcdDirty;
+	bool lcdUpdateSignalled;
+	bool lastRhythmPartState;
+	bool voicePartStates[8];
+
+	Bit8u lastProgramChangePartIndex;
+	const char *lastProgramChangeSoundGroupName;
+	Bit8u lastProgramChangeTimbreName[TIMBRE_NAME_SIZE];
+
+	Mode mode;
+	Bit32u displayResetTimestamp;
+	bool displayResetScheduled;
+	Bit32u midiMessageLEDResetTimestamp;
+	bool midiMessagePlayedSinceLastReset;
+	Bit32u rhythmStateResetTimestamp;
+	bool rhythmNotePlayedSinceLastReset;
+
+	DisplayBuffer displayBuffer;
+	DisplayBuffer customMessageBuffer;
+
+	void scheduleDisplayReset();
+	bool shouldResetTimer(Bit32u scheduledResetTimestamp);
+	void maybeResetTimer(bool &timerState, Bit32u scheduledResetTimestamp);
+};
+
+} // namespace MT32Emu
+
+#endif // #ifndef MT32EMU_DISPLAY_H
diff --git a/audio/softsynth/mt32/Enumerations.h b/audio/softsynth/mt32/Enumerations.h
index fe05d210097..3cbfdd4c8f8 100644
--- a/audio/softsynth/mt32/Enumerations.h
+++ b/audio/softsynth/mt32/Enumerations.h
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License as published by
diff --git a/audio/softsynth/mt32/File.cpp b/audio/softsynth/mt32/File.cpp
index 0cc282921de..fb2febeb173 100644
--- a/audio/softsynth/mt32/File.cpp
+++ b/audio/softsynth/mt32/File.cpp
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 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
@@ -27,14 +27,14 @@ namespace MT32Emu {
 AbstractFile::AbstractFile() : sha1DigestCalculated(false) {
 	sha1Digest[0] = 0;
 
-	reserved = nullptr;
+	reserved = NULL;
 }
 
 AbstractFile::AbstractFile(const SHA1Digest &useSHA1Digest) : sha1DigestCalculated(true) {
 	memcpy(sha1Digest, useSHA1Digest, sizeof(SHA1Digest) - 1);
 	sha1Digest[sizeof(SHA1Digest) - 1] = 0; // Ensure terminator char.
 
-	reserved = nullptr;
+	reserved = NULL;
 }
 
 const File::SHA1Digest &AbstractFile::getSHA1() {
@@ -49,7 +49,7 @@ const File::SHA1Digest &AbstractFile::getSHA1() {
 	}
 
 	const Bit8u *data = getData();
-	if (data == nullptr) {
+	if (data == NULL) {
 		return sha1Digest;
 	}
 
diff --git a/audio/softsynth/mt32/File.h b/audio/softsynth/mt32/File.h
index 06ab33917d0..2aa34b4c774 100644
--- a/audio/softsynth/mt32/File.h
+++ b/audio/softsynth/mt32/File.h
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License as published by
diff --git a/audio/softsynth/mt32/FileStream.cpp b/audio/softsynth/mt32/FileStream.cpp
index 30f9d27cca5..4424c282ff0 100644
--- a/audio/softsynth/mt32/FileStream.cpp
+++ b/audio/softsynth/mt32/FileStream.cpp
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License as published by
@@ -21,6 +21,11 @@
 
 #include "internals.h"
 
+// Disable MSVC STL exceptions
+#ifdef _MSC_VER
+#define _HAS_EXCEPTIONS 0
+#endif
+
 #include "FileStream.h"
 
 namespace MT32Emu {
@@ -42,7 +47,7 @@ static inline void configureSystemLocale() {
 
 using std::ios_base;
 
-FileStream::FileStream() : ifsp(*new std::ifstream), data(nullptr), size(0)
+FileStream::FileStream() : ifsp(*new std::ifstream), data(NULL), size(0)
 {}
 
 FileStream::~FileStream() {
@@ -64,24 +69,24 @@ size_t FileStream::getSize() {
 }
 
 const Bit8u *FileStream::getData() {
-	if (data != nullptr) {
+	if (data != NULL) {
 		return data;
 	}
 	if (!ifsp.is_open()) {
-		return nullptr;
+		return NULL;
 	}
 	if (getSize() == 0) {
-		return nullptr;
+		return NULL;
 	}
 	Bit8u *fileData = new Bit8u[size];
-	if (fileData == nullptr) {
-		return nullptr;
+	if (fileData == NULL) {
+		return NULL;
 	}
 	ifsp.seekg(0);
 	ifsp.read(reinterpret_cast<char *>(fileData), std::streamsize(size));
 	if (size_t(ifsp.tellg()) != size) {
 		delete[] fileData;
-		return nullptr;
+		return NULL;
 	}
 	data = fileData;
 	close();
diff --git a/audio/softsynth/mt32/FileStream.h b/audio/softsynth/mt32/FileStream.h
index c5c96973c68..3b397686962 100644
--- a/audio/softsynth/mt32/FileStream.h
+++ b/audio/softsynth/mt32/FileStream.h
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License as published by
diff --git a/audio/softsynth/mt32/LA32FloatWaveGenerator.cpp b/audio/softsynth/mt32/LA32FloatWaveGenerator.cpp
index f02eca5fc67..7aea6c240af 100644
--- a/audio/softsynth/mt32/LA32FloatWaveGenerator.cpp
+++ b/audio/softsynth/mt32/LA32FloatWaveGenerator.cpp
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 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
@@ -49,7 +49,7 @@ void LA32FloatWaveGenerator::initSynth(const bool useSawtoothWaveform, const Bit
 	wavePos = 0.0f;
 	lastFreq = 0.0f;
 
-	pcmWaveAddress = nullptr;
+	pcmWaveAddress = NULL;
 	active = true;
 }
 
@@ -236,7 +236,7 @@ float LA32FloatWaveGenerator::generateNextSample(const Bit32u ampVal, const Bit1
 				relWavePos -= cosineLen + hLen;
 			}
 
-			// To ensure the output wave has no breaks, two different windows are appied to the beginning and the ending of the resonance sine segment
+			// To ensure the output wave has no breaks, two different windows are applied to the beginning and the ending of the resonance sine segment
 			if (relWavePos < 0.5f * cosineLen) {
 				float syncSine = sin(FLOAT_PI * relWavePos / cosineLen);
 				if (relWavePos < 0.0f) {
@@ -278,7 +278,7 @@ bool LA32FloatWaveGenerator::isActive() const {
 }
 
 bool LA32FloatWaveGenerator::isPCMWave() const {
-	return pcmWaveAddress != nullptr;
+	return pcmWaveAddress != NULL;
 }
 
 void LA32FloatPartialPair::init(const bool useRingModulated, const bool useMixed) {
diff --git a/audio/softsynth/mt32/LA32FloatWaveGenerator.h b/audio/softsynth/mt32/LA32FloatWaveGenerator.h
index 5807b19484e..b34c1fa868e 100644
--- a/audio/softsynth/mt32/LA32FloatWaveGenerator.h
+++ b/audio/softsynth/mt32/LA32FloatWaveGenerator.h
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License as published by
diff --git a/audio/softsynth/mt32/LA32Ramp.cpp b/audio/softsynth/mt32/LA32Ramp.cpp
index d67744280c2..cc61d8357d7 100644
--- a/audio/softsynth/mt32/LA32Ramp.cpp
+++ b/audio/softsynth/mt32/LA32Ramp.cpp
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License as published by
diff --git a/audio/softsynth/mt32/LA32Ramp.h b/audio/softsynth/mt32/LA32Ramp.h
index 618aaaec1ad..178e16b604e 100644
--- a/audio/softsynth/mt32/LA32Ramp.h
+++ b/audio/softsynth/mt32/LA32Ramp.h
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License as published by
diff --git a/audio/softsynth/mt32/LA32WaveGenerator.cpp b/audio/softsynth/mt32/LA32WaveGenerator.cpp
index 62f66f2a960..cf1a34c9c2b 100644
--- a/audio/softsynth/mt32/LA32WaveGenerator.cpp
+++ b/audio/softsynth/mt32/LA32WaveGenerator.cpp
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 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
@@ -173,7 +173,7 @@ void LA32WaveGenerator::generateNextResonanceWaveLogSample() {
 	// Unsure about resonanceSinePosition here. It's possible that dedicated counter & decrement are used. Although, cutoff is finely ramped, so maybe not.
 	logSampleValue += resonanceAmpSubtraction + (((resonanceSinePosition >> 4) * decayFactor) >> 8);
 
-	// To ensure the output wave has no breaks, two different windows are appied to the beginning and the ending of the resonance sine segment
+	// To ensure the output wave has no breaks, two different windows are applied to the beginning and the ending of the resonance sine segment
 	if (phase == POSITIVE_RISING_SINE_SEGMENT || phase == NEGATIVE_FALLING_SINE_SEGMENT) {
 		// The window is synchronous sine here
 		logSampleValue += Tables::getInstance().logsin9[(squareWavePosition >> 9) & 511] << 2;
@@ -183,7 +183,7 @@ void LA32WaveGenerator::generateNextResonanceWaveLogSample() {
 	}
 
 	if (cutoffVal < MIDDLE_CUTOFF_VALUE) {
-		// For the cutoff values below the cutoff middle point, it seems the amp of the resonance wave is expotentially decayed
+		// For the cutoff values below the cutoff middle point, it seems the amp of the resonance wave is exponentially decayed
 		logSampleValue += 31743 + ((MIDDLE_CUTOFF_VALUE - cutoffVal) >> 9);
 	} else if (cutoffVal < RESONANCE_DECAY_THRESHOLD_CUTOFF_VALUE) {
 		// For the cutoff values below this point, the amp of the resonance wave is sinusoidally decayed
@@ -269,7 +269,7 @@ void LA32WaveGenerator::initSynth(const bool useSawtoothWaveform, const Bit8u us
 	resonanceAmpSubtraction = (32 - resonance) << 10;
 	resAmpDecayFactor = Tables::getInstance().resAmpDecayFactor[resonance >> 2] << 2;
 
-	pcmWaveAddress = nullptr;
+	pcmWaveAddress = NULL;
 	active = true;
 }
 
@@ -330,7 +330,7 @@ bool LA32WaveGenerator::isActive() const {
 }
 
 bool LA32WaveGenerator::isPCMWave() const {
-	return pcmWaveAddress != nullptr;
+	return pcmWaveAddress != NULL;
 }
 
 Bit32u LA32WaveGenerator::getPCMInterpolationFactor() const {
diff --git a/audio/softsynth/mt32/LA32WaveGenerator.h b/audio/softsynth/mt32/LA32WaveGenerator.h
index 3de831ae079..71d909df885 100644
--- a/audio/softsynth/mt32/LA32WaveGenerator.h
+++ b/audio/softsynth/mt32/LA32WaveGenerator.h
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License as published by
diff --git a/audio/softsynth/mt32/MemoryRegion.h b/audio/softsynth/mt32/MemoryRegion.h
index af47edf47ed..1f224768ce9 100644
--- a/audio/softsynth/mt32/MemoryRegion.h
+++ b/audio/softsynth/mt32/MemoryRegion.h
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 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
@@ -120,7 +120,9 @@ public:
 };
 class DisplayMemoryRegion : public MemoryRegion {
 public:
-	DisplayMemoryRegion(Synth *useSynth) : MemoryRegion(useSynth, NULL, NULL, MR_Display, MT32EMU_MEMADDR(0x200000), SYSEX_BUFFER_SIZE - 1, 1) {}
+	// Note, we set realMemory to NULL despite the real devices buffer inbound strings. However, it is impossible to retrieve them.
+	// This entrySize permits emulation of handling a 20-byte display message sent to an old-gen device at address 0x207F7F.
+	DisplayMemoryRegion(Synth *useSynth) : MemoryRegion(useSynth, NULL, NULL, MR_Display, MT32EMU_MEMADDR(0x200000), 0x4013, 1) {}
 };
 class ResetMemoryRegion : public MemoryRegion {
 public:
diff --git a/audio/softsynth/mt32/MidiEventQueue.h b/audio/softsynth/mt32/MidiEventQueue.h
index af67ca437cd..b458b81902b 100644
--- a/audio/softsynth/mt32/MidiEventQueue.h
+++ b/audio/softsynth/mt32/MidiEventQueue.h
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License as published by
diff --git a/audio/softsynth/mt32/MidiStreamParser.cpp b/audio/softsynth/mt32/MidiStreamParser.cpp
index 462474d9149..7b64f97f9f9 100644
--- a/audio/softsynth/mt32/MidiStreamParser.cpp
+++ b/audio/softsynth/mt32/MidiStreamParser.cpp
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 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
@@ -80,7 +80,7 @@ MidiStreamParserImpl::MidiStreamParserImpl(MidiReceiver &useReceiver, MidiReport
 	streamBufferSize = 0;
 	runningStatus = 0;
 
-	reserved = nullptr;
+	reserved = NULL;
 }
 
 MidiStreamParserImpl::~MidiStreamParserImpl() {
diff --git a/audio/softsynth/mt32/MidiStreamParser.h b/audio/softsynth/mt32/MidiStreamParser.h
index 580b9dd30c5..d3a76c8a037 100644
--- a/audio/softsynth/mt32/MidiStreamParser.h
+++ b/audio/softsynth/mt32/MidiStreamParser.h
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License as published by
diff --git a/audio/softsynth/mt32/Part.cpp b/audio/softsynth/mt32/Part.cpp
index e0acb23329b..5888b97b2d5 100644
--- a/audio/softsynth/mt32/Part.cpp
+++ b/audio/softsynth/mt32/Part.cpp
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 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
@@ -52,17 +52,19 @@ Part::Part(Synth *useSynth, unsigned int usePartNum) {
 	patchTemp = &synth->mt32ram.patchTemp[partNum];
 	if (usePartNum == 8) {
 		// Nasty hack for rhythm
-		timbreTemp = nullptr;
+		timbreTemp = NULL;
 	} else {
 		sprintf(name, "Part %d", partNum + 1);
 		timbreTemp = &synth->mt32ram.timbreTemp[partNum];
 	}
 	currentInstr[0] = 0;
 	currentInstr[10] = 0;
+	volumeOverride = 255;
 	modulation = 0;
 	expression = 100;
 	pitchBend = 0;
 	activePartialCount = 0;
+	activeNonReleasingPolyCount = 0;
 	memset(patchCache, 0, sizeof(patchCache));
 }
 
@@ -166,7 +168,7 @@ void Part::refresh() {
 		patchCache[t].reverb = patchTemp->patch.reverbSwitch > 0;
 	}
 	memcpy(currentInstr, timbreTemp->common.name, 10);
-	synth->newTimbreSet(partNum, patchTemp->patch.timbreGroup, patchTemp->patch.timbreNum, currentInstr);
+	synth->newTimbreSet(partNum);
 	updatePitchBenderRange();
 }
 
@@ -235,7 +237,7 @@ void Part::backupCacheToPartials(PatchCache cache[4]) {
 	// if so then duplicate the cached data from the part to the partial so that
 	// we can change the part's cache without affecting the partial.
 	// We delay this until now to avoid a copy operation with every note played
-	for (Poly *poly = activePolys.getFirst(); poly != nullptr; poly = poly->getNext()) {
+	for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
 		poly->backupCacheToPartials(cache);
 	}
 }
@@ -317,7 +319,21 @@ void Part::setVolume(unsigned int midiVolume) {
 }
 
 Bit8u Part::getVolume() const {
-	return patchTemp->outputLevel;
+	return volumeOverride <= 100 ? volumeOverride : patchTemp->outputLevel;
+}
+
+void Part::setVolumeOverride(Bit8u volume) {
+	volumeOverride = volume;
+	// When volume is 0, we want the part to stop producing any sound at all.
+	// For that to achieve, we have to actually stop processing NoteOn MIDI messages; merely
+	// returning 0 volume is not enough - the output may still be generated at a very low level.
+	// But first, we have to stop all the currently playing polys. This behaviour may also help
+	// with performance issues, because parts muted this way barely consume CPU resources.
+	if (volume == 0) allSoundOff();
+}
+
+Bit8u Part::getVolumeOverride() const {
+	return volumeOverride;
 }
 
 Bit8u Part::getExpression() const {
@@ -380,6 +396,7 @@ void RhythmPart::noteOn(unsigned int midiKey, unsigned int velocity) {
 		synth->printDebug("%s: Attempted to play invalid key %d (velocity %d)", name, midiKey, velocity);
 		return;
 	}
+	synth->rhythmNotePlayed();
 	unsigned int key = midiKey;
 	unsigned int drumNum = key - 24;
 	int drumTimbreNum = rhythmTemp[drumNum].timbre;
@@ -427,11 +444,11 @@ void Part::noteOn(unsigned int midiKey, unsigned int velocity) {
 	synth->printDebug(" PatchTemp: outputLevel %u, panpot %u", patchTemp->outputLevel, patchTemp->panpot);
 #endif
 #endif
-	playPoly(patchCache, nullptr, midiKey, key, velocity);
+	playPoly(patchCache, NULL, midiKey, key, velocity);
 }
 
 bool Part::abortFirstPoly(unsigned int key) {
-	for (Poly *poly = activePolys.getFirst(); poly != nullptr; poly = poly->getNext()) {
+	for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
 		if (poly->getKey() == key) {
 			return poly->startAbort();
 		}
@@ -440,7 +457,7 @@ bool Part::abortFirstPoly(unsigned int key) {
 }
 
 bool Part::abortFirstPoly(PolyState polyState) {
-	for (Poly *poly = activePolys.getFirst(); poly != nullptr; poly = poly->getNext()) {
+	for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
 		if (poly->getState() == polyState) {
 			return poly->startAbort();
 		}
@@ -486,7 +503,7 @@ void Part::playPoly(const PatchCache cache[4], const MemParams::RhythmTemp *rhyt
 	if (synth->isAbortingPoly()) return;
 
 	Poly *poly = synth->partialManager->assignPolyToPart(this);
-	if (poly == nullptr) {
+	if (poly == NULL) {
 		synth->printDebug("%s (%s): No free poly to play key %d (velocity %d)", name, currentInstr, midiKey, velocity);
 		return;
 	}
@@ -503,13 +520,13 @@ void Part::playPoly(const PatchCache cache[4], const MemParams::RhythmTemp *rhyt
 			partials[x] = synth->partialManager->allocPartial(partNum);
 			activePartialCount++;
 		} else {
-			partials[x] = nullptr;
+			partials[x] = NULL;
 		}
 	}
 	poly->reset(key, velocity, cache[0].sustain, partials);
 
 	for (int x = 0; x < 4; x++) {
-		if (partials[x] != nullptr) {
+		if (partials[x] != NULL) {
 #if MT32EMU_MONITOR_PARTIALS > 2
 			synth->printDebug("%s (%s): Allocated partial %d", name, currentInstr, partials[x]->debugGetPartialNum());
 #endif
@@ -525,7 +542,7 @@ void Part::playPoly(const PatchCache cache[4], const MemParams::RhythmTemp *rhyt
 void Part::allNotesOff() {
 	// The MIDI specification states - and Mok confirms - that all notes off (0x7B)
 	// should treat the hold pedal as usual.
-	for (Poly *poly = activePolys.getFirst(); poly != nullptr; poly = poly->getNext()) {
+	for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
 		// FIXME: This has special handling of key 0 in NoteOff that Mok has not yet confirmed applies to AllNotesOff.
 		// if (poly->canSustain() || poly->getKey() == 0) {
 		// FIXME: The real devices are found to be ignoring non-sustaining polys while processing AllNotesOff. Need to be confirmed.
@@ -539,13 +556,13 @@ void Part::allSoundOff() {
 	// MIDI "All sound off" (0x78) should release notes immediately regardless of the hold pedal.
 	// This controller is not actually implemented by the synths, though (according to the docs and Mok) -
 	// we're only using this method internally.
-	for (Poly *poly = activePolys.getFirst(); poly != nullptr; poly = poly->getNext()) {
+	for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
 		poly->startDecay();
 	}
 }
 
 void Part::stopPedalHold() {
-	for (Poly *poly = activePolys.getFirst(); poly != nullptr; poly = poly->getNext()) {
+	for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
 		poly->stopPedalHold();
 	}
 }
@@ -563,7 +580,7 @@ void Part::stopNote(unsigned int key) {
 	synth->printDebug("%s (%s): stopping key %d", name, currentInstr, key);
 #endif
 
-	for (Poly *poly = activePolys.getFirst(); poly != nullptr; poly = poly->getNext()) {
+	for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
 		// Generally, non-sustaining instruments ignore note off. They die away eventually anyway.
 		// Key 0 (only used by special cases on rhythm part) reacts to note off even if non-sustaining or pedal held.
 		if (poly->getKey() == key && (poly->canSustain() || key == 0)) {
@@ -588,7 +605,7 @@ const Poly *Part::getFirstActivePoly() const {
 
 unsigned int Part::getActiveNonReleasingPartialCount() const {
 	unsigned int activeNonReleasingPartialCount = 0;
-	for (Poly *poly = activePolys.getFirst(); poly != nullptr; poly = poly->getNext()) {
+	for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
 		if (poly->getState() != POLY_Releasing) {
 			activeNonReleasingPartialCount += poly->getActivePartialCount();
 		}
@@ -609,7 +626,28 @@ void Part::partialDeactivated(Poly *poly) {
 	}
 }
 
-PolyList::PolyList() : firstPoly(nullptr), lastPoly(nullptr) {}
+void RhythmPart::polyStateChanged(PolyState, PolyState) {}
+
+void Part::polyStateChanged(PolyState oldState, PolyState newState) {
+	switch (newState) {
+	case POLY_Playing:
+		if (activeNonReleasingPolyCount++ == 0) synth->voicePartStateChanged(partNum, true);
+		break;
+	case POLY_Releasing:
+	case POLY_Inactive:
+		if (oldState == POLY_Playing || oldState == POLY_Held) {
+			if (--activeNonReleasingPolyCount == 0) synth->voicePartStateChanged(partNum, false);
+		}
+		break;
+	default:
+		break;
+	}
+#ifdef MT32EMU_TRACE_POLY_STATE_CHANGES
+	synth->printDebug("Part %d: Changed poly state %d->%d, activeNonReleasingPolyCount=%d", partNum, oldState, newState, activeNonReleasingPolyCount);
+#endif
+}
+
+PolyList::PolyList() : firstPoly(NULL), lastPoly(NULL) {}
 
 bool PolyList::isEmpty() const {
 #ifdef MT32EMU_POLY_LIST_DEBUG
@@ -617,7 +655,7 @@ bool PolyList::isEmpty() const {
 		printf("PolyList: desynchronised firstPoly & lastPoly pointers\n");
 	}
 #endif
-	return firstPoly == nullptr && lastPoly == nullptr;
+	return firstPoly == NULL && lastPoly == NULL;
 }
 
 Poly *PolyList::getFirst() const {
@@ -636,7 +674,7 @@ void PolyList::prepend(Poly *poly) {
 #endif
 	poly->setNext(firstPoly);
 	firstPoly = poly;
-	if (lastPoly == nullptr) {
+	if (lastPoly == NULL) {
 		lastPoly = poly;
 	}
 }
@@ -647,8 +685,8 @@ void PolyList::append(Poly *poly) {
 		printf("PolyList: Non-NULL next field in a Poly being appended is ignored\n");
 	}
 #endif
-	poly->setNext(nullptr);
-	if (lastPoly != nullptr) {
+	poly->setNext(NULL);
+	if (lastPoly != NULL) {
 #ifdef MT32EMU_POLY_LIST_DEBUG
 		if (lastPoly->getNext() != NULL) {
 			printf("PolyList: Non-NULL next field in the lastPoly\n");
@@ -657,7 +695,7 @@ void PolyList::append(Poly *poly) {
 		lastPoly->setNext(poly);
 	}
 	lastPoly = poly;
-	if (firstPoly == nullptr) {
+	if (firstPoly == NULL) {
 		firstPoly = poly;
 	}
 }
@@ -665,15 +703,15 @@ void PolyList::append(Poly *poly) {
 Poly *PolyList::takeFirst() {
 	Poly *oldFirst = firstPoly;
 	firstPoly = oldFirst->getNext();
-	if (firstPoly == nullptr) {
+	if (firstPoly == NULL) {
 #ifdef MT32EMU_POLY_LIST_DEBUG
 		if (lastPoly != oldFirst) {
 			printf("PolyList: firstPoly != lastPoly in a list with a single Poly\n");
 		}
 #endif
-		lastPoly = nullptr;
+		lastPoly = NULL;
 	}
-	oldFirst->setNext(nullptr);
+	oldFirst->setNext(NULL);
 	return oldFirst;
 }
 
@@ -682,7 +720,7 @@ void PolyList::remove(Poly * const polyToRemove) {
 		takeFirst();
 		return;
 	}
-	for (Poly *poly = firstPoly; poly != nullptr; poly = poly->getNext()) {
+	for (Poly *poly = firstPoly; poly != NULL; poly = poly->getNext()) {
 		if (poly->getNext() == polyToRemove) {
 			if (polyToRemove == lastPoly) {
 #ifdef MT32EMU_POLY_LIST_DEBUG
@@ -693,7 +731,7 @@ void PolyList::remove(Poly * const polyToRemove) {
 				lastPoly = poly;
 			}
 			poly->setNext(polyToRemove->getNext());
-			polyToRemove->setNext(nullptr);
+			polyToRemove->setNext(NULL);
 			break;
 		}
 	}
diff --git a/audio/softsynth/mt32/Part.h b/audio/softsynth/mt32/Part.h
index f81a89bd0e5..d266efb7ea4 100644
--- a/audio/softsynth/mt32/Part.h
+++ b/audio/softsynth/mt32/Part.h
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 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
@@ -55,6 +55,7 @@ private:
 	bool holdpedal;
 
 	unsigned int activePartialCount;
+	unsigned int activeNonReleasingPolyCount;
 	PatchCache patchCache[4];
 	PolyList activePolys;
 
@@ -69,6 +70,8 @@ protected:
 	MemParams::PatchTemp *patchTemp;
 	char name[8]; // "Part 1".."Part 8", "Rhythm"
 	char currentInstr[11];
+	// Values outside the valid range 0..100 imply no override.
+	Bit8u volumeOverride;
 	Bit8u modulation;
 	Bit8u expression;
 	Bit32s pitchBend;
@@ -95,8 +98,10 @@ public:
 	virtual void noteOff(unsigned int midiKey);
 	void allNotesOff();
 	void allSoundOff();
-	Bit8u getVolume() const; // Internal volume, 0-100, exposed for use by ExternalInterface
-	void setVolume(unsigned int midiVolume);
+	Bit8u getVolume() const; // Effective output level, valid range 0..100.
+	void setVolume(unsigned int midiVolume); // Valid range 0..127, as defined for MIDI controller 7.
+	Bit8u getVolumeOverride() const;
+	void setVolumeOverride(Bit8u volumeOverride);
 	Bit8u getModulation() const;
 	void setModulation(unsigned int midiModulation);
 	Bit8u getExpression() const;
@@ -122,6 +127,7 @@ public:
 
 	// This should only be called by Poly
 	void partialDeactivated(Poly *poly);
+	virtual void polyStateChanged(PolyState oldState, PolyState newState);
 
 	// These are rather specialised, and should probably only be used by PartialManager
 	bool abortFirstPoly(PolyState polyState);
@@ -146,6 +152,7 @@ public:
 	unsigned int getAbsTimbreNum() const;
 	void setPan(unsigned int midiPan);
 	void setProgram(unsigned int patchNum);
+	void polyStateChanged(PolyState oldState, PolyState newState);
 };
 
 } // namespace MT32Emu
diff --git a/audio/softsynth/mt32/Partial.cpp b/audio/softsynth/mt32/Partial.cpp
index 364fea4aede..2a4b21d9f09 100644
--- a/audio/softsynth/mt32/Partial.cpp
+++ b/audio/softsynth/mt32/Partial.cpp
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 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
@@ -60,8 +60,8 @@ Partial::Partial(Synth *useSynth, int usePartialIndex) :
 	tvp = new TVP(this);
 	tvf = new TVF(this, &cutoffModifierRamp);
 	ownerPart = -1;
-	poly = nullptr;
-	pair = nullptr;
+	poly = NULL;
+	pair = NULL;
 	switch (synth->getSelectedRendererType()) {
 	case RendererType_BIT16S:
 		la32Pair = new LA32IntPartialPair;
@@ -70,7 +70,7 @@ Partial::Partial(Synth *useSynth, int usePartialIndex) :
 		la32Pair = new LA32FloatPartialPair;
 		break;
 	default:
-		la32Pair = nullptr;
+		la32Pair = NULL;
 	}
 }
 
@@ -114,7 +114,7 @@ void Partial::deactivate() {
 	}
 	ownerPart = -1;
 	synth->partialManager->partialDeactivated(partialIndex);
-	if (poly != nullptr) {
+	if (poly != NULL) {
 		poly->partialDeactivated(this);
 	}
 #if MT32EMU_MONITOR_PARTIALS > 2
@@ -127,17 +127,17 @@ void Partial::deactivate() {
 		la32Pair->deactivate(LA32PartialPair::MASTER);
 		if (hasRingModulatingSlave()) {
 			pair->deactivate();
-			pair = nullptr;
+			pair = NULL;
 		}
 	}
-	if (pair != nullptr) {
-		pair->pair = nullptr;
+	if (pair != NULL) {
+		pair->pair = NULL;
 	}
 }
 
 void Partial::startPartial(const Part *part, Poly *usePoly, const PatchCache *usePatchCache, const MemParams::RhythmTemp *rhythmTemp, Partial *pairPartial) {
-	if (usePoly == nullptr || usePatchCache == nullptr) {
-		synth->printDebug("[Partial %d] *** Error: Starting partial for owner %d, usePoly=%s, usePatchCache=%s", partialIndex, ownerPart, usePoly == nullptr ? "*** NULL ***" : "OK", usePatchCache == nullptr ? "*** NULL ***" : "OK");
+	if (usePoly == NULL || usePatchCache == NULL) {
+		synth->printDebug("[Partial %d] *** Error: Starting partial for owner %d, usePoly=%s, usePatchCache=%s", partialIndex, ownerPart, usePoly == NULL ? "*** NULL ***" : "OK", usePatchCache == NULL ? "*** NULL ***" : "OK");
 		return;
 	}
 	patchCache = usePatchCache;
@@ -145,7 +145,7 @@ void Partial::startPartial(const Part *part, Poly *usePoly, const PatchCache *us
 	mixType = patchCache->structureMix;
 	structurePosition = patchCache->structurePosition;
 
-	Bit8u panSetting = rhythmTemp != nullptr ? rhythmTemp->panpot : part->getPatchTemp()->panpot;
+	Bit8u panSetting = rhythmTemp != NULL ? rhythmTemp->panpot : part->getPatchTemp()->panpot;
 	if (mixType == 3) {
 		if (structurePosition == 0) {
 			panSetting = PAN_NUMERATOR_MASTER[panSetting] << 1;
@@ -154,7 +154,7 @@ void Partial::startPartial(const Part *part, Poly *usePoly, const PatchCache *us
 		}
 		// Do a normal mix independent of any pair partial.
 		mixType = 0;
-		pairPartial = nullptr;
+		pairPartial = NULL;
 	} else if (!synth->isNicePanningEnabled()) {
 		// Mok wanted an option for smoother panning, and we love Mok.
 		// CONFIRMED by Mok: exactly bytes like this (right shifted) are sent to the LA32.
@@ -197,7 +197,7 @@ void Partial::startPartial(const Part *part, Poly *usePoly, const PatchCache *us
 		}
 		pcmWave = &synth->pcmWaves[pcmNum];
 	} else {
-		pcmWave = nullptr;
+		pcmWave = NULL;
 	}
 
 	// CONFIRMED: pulseWidthVal calculation is based on information from Mok
@@ -264,26 +264,26 @@ Bit32u Partial::getCutoffValue() {
 }
 
 bool Partial::hasRingModulatingSlave() const {
-	return pair != nullptr && structurePosition == 0 && (mixType == 1 || mixType == 2);
+	return pair != NULL && structurePosition == 0 && (mixType == 1 || mixType == 2);
 }
 
 bool Partial::isRingModulatingSlave() const {
-	return pair != nullptr && structurePosition == 1 && (mixType == 1 || mixType == 2);
+	return pair != NULL && structurePosition == 1 && (mixType == 1 || mixType == 2);
 }
 
 bool Partial::isRingModulatingNoMix() const {
-	return pair != nullptr && ((structurePosition == 1 && mixType == 1) || mixType == 2);
+	return pair != NULL && ((structurePosition == 1 && mixType == 1) || mixType == 2);
 }
 
 bool Partial::isPCM() const {
-	return pcmWave != nullptr;
+	return pcmWave != NULL;
 }
 
 const ControlROMPCMStruct *Partial::getControlROMPCMStruct() const {
-	if (pcmWave != nullptr) {
+	if (pcmWave != NULL) {
 		return pcmWave->controlROMPCMStruct;
 	}
-	return nullptr;
+	return NULL;
 }
 
 Synth *Partial::getSynth() const {
@@ -305,7 +305,7 @@ bool Partial::canProduceOutput() {
 	if (!isActive() || alreadyOutputed || isRingModulatingSlave()) {
 		return false;
 	}
-	if (poly == nullptr) {
+	if (poly == NULL) {
 		synth->printDebug("[Partial %d] *** ERROR: poly is NULL at Partial::produceOutput()!", partialIndex);
 		return false;
 	}
diff --git a/audio/softsynth/mt32/Partial.h b/audio/softsynth/mt32/Partial.h
index 3a147b7af53..bfc6f6dcade 100644
--- a/audio/softsynth/mt32/Partial.h
+++ b/audio/softsynth/mt32/Partial.h
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License as published by
diff --git a/audio/softsynth/mt32/PartialManager.cpp b/audio/softsynth/mt32/PartialManager.cpp
index 8ff3ca0cf3f..609adaa74ef 100644
--- a/audio/softsynth/mt32/PartialManager.cpp
+++ b/audio/softsynth/mt32/PartialManager.cpp
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 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
@@ -46,7 +46,7 @@ PartialManager::PartialManager(Synth *useSynth, Part **useParts) {
 PartialManager::~PartialManager(void) {
 	for (unsigned int i = 0; i < synth->getPartialCount(); i++) {
 		delete partialTable[i];
-		if (freePolys[i] != nullptr) delete freePolys[i];
+		if (freePolys[i] != NULL) delete freePolys[i];
 	}
 	delete[] partialTable;
 	delete[] inactivePartials;
@@ -97,7 +97,7 @@ Partial *PartialManager::allocPartial(int partNum) {
 		const Partial *partial = partialTable[i];
 		synth->printDebug("[Partial %d]: activation=%d, owner part=%d\n", i, partial->isActive(), partial->getOwnerPart());
 	}
-	return nullptr;
+	return NULL;
 }
 
 unsigned int PartialManager::getFreePartialCount() {
@@ -257,7 +257,7 @@ bool PartialManager::freePartials(unsigned int needed, int partNum) {
 
 const Partial *PartialManager::getPartial(unsigned int partialNum) const {
 	if (partialNum > synth->getPartialCount() - 1) {
-		return nullptr;
+		return NULL;
 	}
 	return partialTable[partialNum];
 }
@@ -265,12 +265,12 @@ const Partial *PartialManager::getPartial(unsigned int partialNum) const {
 Poly *PartialManager::assignPolyToPart(Part *part) {
 	if (firstFreePolyIndex < synth->getPartialCount()) {
 		Poly *poly = freePolys[firstFreePolyIndex];
-		freePolys[firstFreePolyIndex] = nullptr;
+		freePolys[firstFreePolyIndex] = NULL;
 		firstFreePolyIndex++;
 		poly->setPart(part);
 		return poly;
 	}
-	return nullptr;
+	return NULL;
 }
 
 void PartialManager::polyFreed(Poly *poly) {
@@ -279,7 +279,7 @@ void PartialManager::polyFreed(Poly *poly) {
 		for (Bit32u partNum = 0; partNum < 9; partNum++) {
 			const Poly *activePoly = synth->getPart(partNum)->getFirstActivePoly();
 			Bit32u polyCount = 0;
-			while (activePoly != nullptr) {
+			while (activePoly != NULL) {
 				activePoly = activePoly->getNext();
 				polyCount++;
 			}
@@ -289,7 +289,7 @@ void PartialManager::polyFreed(Poly *poly) {
 		firstFreePolyIndex--;
 		freePolys[firstFreePolyIndex] = poly;
 	}
-	poly->setPart(nullptr);
+	poly->setPart(NULL);
 }
 
 void PartialManager::partialDeactivated(int partialIndex) {
diff --git a/audio/softsynth/mt32/PartialManager.h b/audio/softsynth/mt32/PartialManager.h
index bbb09adc42e..5c019effe02 100644
--- a/audio/softsynth/mt32/PartialManager.h
+++ b/audio/softsynth/mt32/PartialManager.h
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License as published by
diff --git a/audio/softsynth/mt32/Poly.cpp b/audio/softsynth/mt32/Poly.cpp
index 7d4876cd1cf..0306e51ffe8 100644
--- a/audio/softsynth/mt32/Poly.cpp
+++ b/audio/softsynth/mt32/Poly.cpp
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 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
@@ -27,16 +27,16 @@
 namespace MT32Emu {
 
 Poly::Poly() {
-	part = nullptr;
+	part = NULL;
 	key = 255;
 	velocity = 255;
 	sustain = false;
 	activePartialCount = 0;
 	for (int i = 0; i < 4; i++) {
-		partials[i] = nullptr;
+		partials[i] = NULL;
 	}
 	state = POLY_Inactive;
-	next = nullptr;
+	next = NULL;
 }
 
 void Poly::setPart(Part *usePart) {
@@ -48,12 +48,12 @@ void Poly::reset(unsigned int newKey, unsigned int newVelocity, bool newSustain,
 		// This should never happen
 		part->getSynth()->printDebug("Resetting active poly. Active partial count: %i\n", activePartialCount);
 		for (int i = 0; i < 4; i++) {
-			if (partials[i] != nullptr && partials[i]->isActive()) {
+			if (partials[i] != NULL && partials[i]->isActive()) {
 				partials[i]->deactivate();
 				activePartialCount--;
 			}
 		}
-		state = POLY_Inactive;
+		setState(POLY_Inactive);
 	}
 
 	key = newKey;
@@ -63,9 +63,9 @@ void Poly::reset(unsigned int newKey, unsigned int newVelocity, bool newSustain,
 	activePartialCount = 0;
 	for (int i = 0; i < 4; i++) {
 		partials[i] = newPartials[i];
-		if (newPartials[i] != nullptr) {
+		if (newPartials[i] != NULL) {
 			activePartialCount++;
-			state = POLY_Playing;
+			setState(POLY_Playing);
 		}
 	}
 }
@@ -80,7 +80,7 @@ bool Poly::noteOff(bool pedalHeld) {
 		if (state == POLY_Held) {
 			return false;
 		}
-		state = POLY_Held;
+		setState(POLY_Held);
 	} else {
 		startDecay();
 	}
@@ -98,11 +98,11 @@ bool Poly::startDecay() {
 	if (state == POLY_Inactive || state == POLY_Releasing) {
 		return false;
 	}
-	state = POLY_Releasing;
+	setState(POLY_Releasing);
 
 	for (int t = 0; t < 4; t++) {
 		Partial *partial = partials[t];
-		if (partial != nullptr) {
+		if (partial != NULL) {
 			partial->startDecayAll();
 		}
 	}
@@ -115,7 +115,7 @@ bool Poly::startAbort() {
 	}
 	for (int t = 0; t < 4; t++) {
 		Partial *partial = partials[t];
-		if (partial != nullptr) {
+		if (partial != NULL) {
 			partial->startAbort();
 			part->getSynth()->abortingPoly = this;
 		}
@@ -123,10 +123,17 @@ bool Poly::startAbort() {
 	return true;
 }
 
+void Poly::setState(PolyState newState) {
+	if (state == newState) return;
+	PolyState oldState = state;
+	state = newState;
+	part->polyStateChanged(oldState, newState);
+}
+
 void Poly::backupCacheToPartials(PatchCache cache[4]) {
 	for (int partialNum = 0; partialNum < 4; partialNum++) {
 		Partial *partial = partials[partialNum];
-		if (partial != nullptr) {
+		if (partial != NULL) {
 			partial->backupCache(cache[partialNum]);
 		}
 	}
@@ -166,14 +173,14 @@ bool Poly::isActive() const {
 void Poly::partialDeactivated(Partial *partial) {
 	for (int i = 0; i < 4; i++) {
 		if (partials[i] == partial) {
-			partials[i] = nullptr;
+			partials[i] = NULL;
 			activePartialCount--;
 		}
 	}
 	if (activePartialCount == 0) {
-		state = POLY_Inactive;
+		setState(POLY_Inactive);
 		if (part->getSynth()->abortingPoly == this) {
-			part->getSynth()->abortingPoly = nullptr;
+			part->getSynth()->abortingPoly = NULL;
 		}
 	}
 	part->partialDeactivated(this);
diff --git a/audio/softsynth/mt32/Poly.h b/audio/softsynth/mt32/Poly.h
index 6085820d8e9..dd6def0943c 100644
--- a/audio/softsynth/mt32/Poly.h
+++ b/audio/softsynth/mt32/Poly.h
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 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
@@ -41,6 +41,8 @@ private:
 
 	Poly *next;
 
+	void setState(PolyState state);
+
 public:
 	Poly();
 	void setPart(Part *usePart);
diff --git a/audio/softsynth/mt32/ROMInfo.cpp b/audio/softsynth/mt32/ROMInfo.cpp
index 9a09dc9a1fd..0f58cd292cb 100644
--- a/audio/softsynth/mt32/ROMInfo.cpp
+++ b/audio/softsynth/mt32/ROMInfo.cpp
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License as published by
@@ -37,9 +37,13 @@ struct ROMInfoLists {
 	ROMInfoList mt32_1_06;
 	ROMInfoList mt32_1_07;
 	ROMInfoList mt32_bluer;
+	ROMInfoList mt32_2_03;
 	ROMInfoList mt32_2_04;
+	ROMInfoList mt32_2_06;
+	ROMInfoList mt32_2_07;
 	ROMInfoList cm32l_1_00;
 	ROMInfoList cm32l_1_02;
+	ROMInfoList cm32ln_1_00;
 	ROMInfoList fullROMInfos;
 	ROMInfoList partialROMInfos;
 	ROMInfoList allROMInfos;
@@ -50,33 +54,37 @@ struct ROMInfoLists {
 #define _CALC_ARRAY_LENGTH(x) Bit32u(sizeof (x) / sizeof *(x) - 1)
 
 static const ROMInfoLists &getROMInfoLists() {
-	static ROMInfo CTRL_MT32_V1_04_A = {32768, "9cd4858014c4e8a9dff96053f784bfaac1092a2e", ROMInfo::Control, "ctrl_mt32_1_04_a", "MT-32 Control v1.04", ROMInfo::Mux0, nullptr};
+	static ROMInfo CTRL_MT32_V1_04_A = {32768, "9cd4858014c4e8a9dff96053f784bfaac1092a2e", ROMInfo::Control, "ctrl_mt32_1_04_a", "MT-32 Control v1.04", ROMInfo::Mux0, NULL};
 	static ROMInfo CTRL_MT32_V1_04_B = {32768, "fe8db469b5bfeb37edb269fd47e3ce6d91014652", ROMInfo::Control, "ctrl_mt32_1_04_b", "MT-32 Control v1.04", ROMInfo::Mux1, &CTRL_MT32_V1_04_A};
-	static ROMInfo CTRL_MT32_V1_04 = {65536, "5a5cb5a77d7d55ee69657c2f870416daed52dea7", ROMInfo::Control, "ctrl_mt32_1_04", "MT-32 Control v1.04", ROMInfo::Full, nullptr};
-	static ROMInfo CTRL_MT32_V1_05_A = {32768, "57a09d80d2f7ca5b9734edbe9645e6e700f83701", ROMInfo::Control, "ctrl_mt32_1_05_a", "MT-32 Control v1.05", ROMInfo::Mux0, nullptr};
+	static ROMInfo CTRL_MT32_V1_04 = {65536, "5a5cb5a77d7d55ee69657c2f870416daed52dea7", ROMInfo::Control, "ctrl_mt32_1_04", "MT-32 Control v1.04", ROMInfo::Full, NULL};
+	static ROMInfo CTRL_MT32_V1_05_A = {32768, "57a09d80d2f7ca5b9734edbe9645e6e700f83701", ROMInfo::Control, "ctrl_mt32_1_05_a", "MT-32 Control v1.05", ROMInfo::Mux0, NULL};
 	static ROMInfo CTRL_MT32_V1_05_B = {32768, "52e3c6666db9ef962591a8ee99be0cde17f3a6b6", ROMInfo::Control, "ctrl_mt32_1_05_b", "MT-32 Control v1.05", ROMInfo::Mux1, &CTRL_MT32_V1_05_A};
-	static ROMInfo CTRL_MT32_V1_05 = {65536, "e17a3a6d265bf1fa150312061134293d2b58288c", ROMInfo::Control, "ctrl_mt32_1_05", "MT-32 Control v1.05", ROMInfo::Full, nullptr};
-	static ROMInfo CTRL_MT32_V1_06_A = {32768, "cc83bf23cee533097fb4c7e2c116e43b50ebacc8", ROMInfo::Control, "ctrl_mt32_1_06_a", "MT-32 Control v1.06", ROMInfo::Mux0, nullptr};
+	static ROMInfo CTRL_MT32_V1_05 = {65536, "e17a3a6d265bf1fa150312061134293d2b58288c", ROMInfo::Control, "ctrl_mt32_1_05", "MT-32 Control v1.05", ROMInfo::Full, NULL};
+	static ROMInfo CTRL_MT32_V1_06_A = {32768, "cc83bf23cee533097fb4c7e2c116e43b50ebacc8", ROMInfo::Control, "ctrl_mt32_1_06_a", "MT-32 Control v1.06", ROMInfo::Mux0, NULL};
 	static ROMInfo CTRL_MT32_V1_06_B = {32768, "bf4f15666bc46679579498386704893b630c1171", ROMInfo::Control, "ctrl_mt32_1_06_b", "MT-32 Control v1.06", ROMInfo::Mux1, &CTRL_MT32_V1_06_A};
-	static ROMInfo CTRL_MT32_V1_06 = {65536, "a553481f4e2794c10cfe597fef154eef0d8257de", ROMInfo::Control, "ctrl_mt32_1_06", "MT-32 Control v1.06", ROMInfo::Full, nullptr};
-	static ROMInfo CTRL_MT32_V1_07_A = {32768, "13f06b38f0d9e0fc050b6503ab777bb938603260", ROMInfo::Control, "ctrl_mt32_1_07_a", "MT-32 Control v1.07", ROMInfo::Mux0, nullptr};
+	static ROMInfo CTRL_MT32_V1_06 = {65536, "a553481f4e2794c10cfe597fef154eef0d8257de", ROMInfo::Control, "ctrl_mt32_1_06", "MT-32 Control v1.06", ROMInfo::Full, NULL};
+	static ROMInfo CTRL_MT32_V1_07_A = {32768, "13f06b38f0d9e0fc050b6503ab777bb938603260", ROMInfo::Control, "ctrl_mt32_1_07_a", "MT-32 Control v1.07", ROMInfo::Mux0, NULL};
 	static ROMInfo CTRL_MT32_V1_07_B = {32768, "c55e165487d71fa88bd8c5e9c083bc456c1a89aa", ROMInfo::Control, "ctrl_mt32_1_07_b", "MT-32 Control v1.07", ROMInfo::Mux1, &CTRL_MT32_V1_07_A};
-	static ROMInfo CTRL_MT32_V1_07 = {65536, "b083518fffb7f66b03c23b7eb4f868e62dc5a987", ROMInfo::Control, "ctrl_mt32_1_07", "MT-32 Control v1.07", ROMInfo::Full, nullptr};
-	static ROMInfo CTRL_MT32_BLUER_A = {32768, "11a6ae5d8b6ee328b371af7f1e40b82125aa6b4d", ROMInfo::Control, "ctrl_mt32_bluer_a", "MT-32 Control BlueRidge", ROMInfo::Mux0, nullptr};
+	static ROMInfo CTRL_MT32_V1_07 = {65536, "b083518fffb7f66b03c23b7eb4f868e62dc5a987", ROMInfo::Control, "ctrl_mt32_1_07", "MT-32 Control v1.07", ROMInfo::Full, NULL};
+	static ROMInfo CTRL_MT32_BLUER_A = {32768, "11a6ae5d8b6ee328b371af7f1e40b82125aa6b4d", ROMInfo::Control, "ctrl_mt32_bluer_a", "MT-32 Control BlueRidge", ROMInfo::Mux0, NULL};
 	static ROMInfo CTRL_MT32_BLUER_B = {32768, "e0934320d7cbb5edfaa29e0d01ae835ef620085b", ROMInfo::Control, "ctrl_mt32_bluer_b", "MT-32 Control BlueRidge", ROMInfo::Mux1, &CTRL_MT32_BLUER_A};
-	static ROMInfo CTRL_MT32_BLUER = {65536, "7b8c2a5ddb42fd0732e2f22b3340dcf5360edf92", ROMInfo::Control, "ctrl_mt32_bluer", "MT-32 Control BlueRidge", ROMInfo::Full, nullptr};
+	static 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, nullptr};
-	static const ROMInfo CTRL_CM32L_V1_00 = {65536, "73683d585cd6948cc19547942ca0e14a0319456d", ROMInfo::Control, "ctrl_cm32l_1_00", "CM-32L/LAPC-I Control v1.00", ROMInfo::Full, nullptr};
-	static const ROMInfo CTRL_CM32L_V1_02 = {65536, "a439fbb390da38cada95a7cbb1d6ca199cd66ef8", ROMInfo::Control, "ctrl_cm32l_1_02", "CM-32L/LAPC-I Control v1.02", ROMInfo::Full, nullptr};
+	static const ROMInfo CTRL_MT32_V2_03 = {131072, "5837064c9df4741a55f7c4d8787ac158dff2d3ce", ROMInfo::Control, "ctrl_mt32_2_03", "MT-32 Control v2.03", 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_MT32_V2_06 = {131072, "2869cf4c235d671668cfcb62415e2ce8323ad4ed", ROMInfo::Control, "ctrl_mt32_2_06", "MT-32 Control v2.06", ROMInfo::Full, NULL};
+	static const ROMInfo CTRL_MT32_V2_07 = {131072, "47b52adefedaec475c925e54340e37673c11707c", ROMInfo::Control, "ctrl_mt32_2_07", "MT-32 Control v2.07", 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};
+	static const ROMInfo CTRL_CM32LN_V1_00 = {65536, "dc1c5b1b90a4646d00f7daf3679733c7badc7077", ROMInfo::Control, "ctrl_cm32ln_1_00", "CM-32LN/CM-500/LAPC-N Control v1.00", ROMInfo::Full, NULL};
 
-	static ROMInfo PCM_MT32_L = {262144, "3a1e19b0cd4036623fd1d1d11f5f25995585962b", ROMInfo::PCM, "pcm_mt32_l", "MT-32 PCM ROM", ROMInfo::FirstHalf, nullptr};
+	static ROMInfo PCM_MT32_L = {262144, "3a1e19b0cd4036623fd1d1d11f5f25995585962b", ROMInfo::PCM, "pcm_mt32_l", "MT-32 PCM ROM", ROMInfo::FirstHalf, NULL};
 	static ROMInfo PCM_MT32_H = {262144, "2cadb99d21a6a4a6f5b61b6218d16e9b43f61d01", ROMInfo::PCM, "pcm_mt32_h", "MT-32 PCM ROM", ROMInfo::SecondHalf, &PCM_MT32_L};
-	static ROMInfo PCM_MT32 = {524288, "f6b1eebc4b2d200ec6d3d21d51325d5b48c60252", ROMInfo::PCM, "pcm_mt32", "MT-32 PCM ROM", ROMInfo::Full, nullptr};
+	static ROMInfo PCM_MT32 = {524288, "f6b1eebc4b2d200ec6d3d21d51325d5b48c60252", ROMInfo::PCM, "pcm_mt32", "MT-32 PCM ROM", ROMInfo::Full, NULL};
 	// Alias of PCM_MT32 ROM, only useful for pairing with PCM_CM32L_H.
-	static ROMInfo PCM_CM32L_L = {524288, "f6b1eebc4b2d200ec6d3d21d51325d5b48c60252", ROMInfo::PCM, "pcm_cm32l_l", "CM-32L/CM-64/LAPC-I PCM ROM", ROMInfo::FirstHalf, nullptr};
+	static ROMInfo PCM_CM32L_L = {524288, "f6b1eebc4b2d200ec6d3d21d51325d5b48c60252", ROMInfo::PCM, "pcm_cm32l_l", "CM-32L/CM-64/LAPC-I PCM ROM", ROMInfo::FirstHalf, NULL};
 	static ROMInfo PCM_CM32L_H = {524288, "3ad889fde5db5b6437cbc2eb6e305312fec3df93", ROMInfo::PCM, "pcm_cm32l_h", "CM-32L/CM-64/LAPC-I PCM ROM", ROMInfo::SecondHalf, &PCM_CM32L_L};
-	static ROMInfo PCM_CM32L = {1048576, "289cc298ad532b702461bfc738009d9ebe8025ea", ROMInfo::PCM, "pcm_cm32l", "CM-32L/CM-64/LAPC-I PCM ROM", ROMInfo::Full, nullptr};
+	static ROMInfo PCM_CM32L = {1048576, "289cc298ad532b702461bfc738009d9ebe8025ea", ROMInfo::PCM, "pcm_cm32l", "CM-32L/CM-64/LAPC-I PCM ROM", ROMInfo::Full, NULL};
 
 	static const ROMInfo * const FULL_ROM_INFOS[] = {
 		&CTRL_MT32_V1_04,
@@ -84,12 +92,16 @@ static const ROMInfoLists &getROMInfoLists() {
 		&CTRL_MT32_V1_06,
 		&CTRL_MT32_V1_07,
 		&CTRL_MT32_BLUER,
+		&CTRL_MT32_V2_03,
 		&CTRL_MT32_V2_04,
+		&CTRL_MT32_V2_06,
+		&CTRL_MT32_V2_07,
 		&CTRL_CM32L_V1_00,
 		&CTRL_CM32L_V1_02,
+		&CTRL_CM32LN_V1_00,
 		&PCM_MT32,
 		&PCM_CM32L,
-		nullptr
+		NULL
 	};
 	static const ROMInfo * const PARTIAL_ROM_INFOS[] = {
 		&CTRL_MT32_V1_04_A, &CTRL_MT32_V1_04_B,
@@ -99,11 +111,11 @@ static const ROMInfoLists &getROMInfoLists() {
 		&CTRL_MT32_BLUER_A, &CTRL_MT32_BLUER_B,
 		&PCM_MT32_L, &PCM_MT32_H,
 		&PCM_CM32L_L, &PCM_CM32L_H,
-		nullptr
+		NULL
 	};
 	static const ROMInfo *ALL_ROM_INFOS[_CALC_ARRAY_LENGTH(FULL_ROM_INFOS) + _CALC_ARRAY_LENGTH(PARTIAL_ROM_INFOS) + 1];
 
-	if (CTRL_MT32_V1_04_A.pairROMInfo == nullptr) {
+	if (CTRL_MT32_V1_04_A.pairROMInfo == NULL) {
 		CTRL_MT32_V1_04_A.pairROMInfo = &CTRL_MT32_V1_04_B;
 		CTRL_MT32_V1_05_A.pairROMInfo = &CTRL_MT32_V1_05_B;
 		CTRL_MT32_V1_06_A.pairROMInfo = &CTRL_MT32_V1_06_B;
@@ -116,14 +128,18 @@ static const ROMInfoLists &getROMInfoLists() {
 		memcpy(&ALL_ROM_INFOS[_CALC_ARRAY_LENGTH(FULL_ROM_INFOS)], PARTIAL_ROM_INFOS, sizeof PARTIAL_ROM_INFOS); // Includes NULL terminator.
 	}
 
-	static const ROMInfo * const MT32_V1_04_ROMS[] = {&CTRL_MT32_V1_04, &PCM_MT32, &CTRL_MT32_V1_04_A, &CTRL_MT32_V1_04_B, &PCM_MT32_L, &PCM_MT32_H, nullptr};
-	static const ROMInfo * const MT32_V1_05_ROMS[] = {&CTRL_MT32_V1_05, &PCM_MT32, &CTRL_MT32_V1_05_A, &CTRL_MT32_V1_05_B, &PCM_MT32_L, &PCM_MT32_H, nullptr};
-	static const ROMInfo * const MT32_V1_06_ROMS[] = {&CTRL_MT32_V1_06, &PCM_MT32, &CTRL_MT32_V1_06_A, &CTRL_MT32_V1_06_B, &PCM_MT32_L, &PCM_MT32_H, nullptr};
-	static const ROMInfo * const MT32_V1_07_ROMS[] = {&CTRL_MT32_V1_07, &PCM_MT32, &CTRL_MT32_V1_07_A, &CTRL_MT32_V1_07_B, &PCM_MT32_L, &PCM_MT32_H, nullptr};
-	static const ROMInfo * const MT32_BLUER_ROMS[] = {&CTRL_MT32_BLUER, &PCM_MT32, &CTRL_MT32_BLUER_A, &CTRL_MT32_BLUER_B, &PCM_MT32_L, &PCM_MT32_H, nullptr};
-	static const ROMInfo * const MT32_V2_04_ROMS[] = {&CTRL_MT32_V2_04, &PCM_MT32, &PCM_MT32_L, &PCM_MT32_H, nullptr};
-	static const ROMInfo * const CM32L_V1_00_ROMS[] = {&CTRL_CM32L_V1_00, &PCM_CM32L, &PCM_CM32L_L, &PCM_CM32L_H, nullptr};
-	static const ROMInfo * const CM32L_V1_02_ROMS[] = {&CTRL_CM32L_V1_02, &PCM_CM32L, &PCM_CM32L_L, &PCM_CM32L_H, nullptr};
+	static const ROMInfo * const MT32_V1_04_ROMS[] = {&CTRL_MT32_V1_04, &PCM_MT32, &CTRL_MT32_V1_04_A, &CTRL_MT32_V1_04_B, &PCM_MT32_L, &PCM_MT32_H, NULL};
+	static const ROMInfo * const MT32_V1_05_ROMS[] = {&CTRL_MT32_V1_05, &PCM_MT32, &CTRL_MT32_V1_05_A, &CTRL_MT32_V1_05_B, &PCM_MT32_L, &PCM_MT32_H, NULL};
+	static const ROMInfo * const MT32_V1_06_ROMS[] = {&CTRL_MT32_V1_06, &PCM_MT32, &CTRL_MT32_V1_06_A, &CTRL_MT32_V1_06_B, &PCM_MT32_L, &PCM_MT32_H, NULL};
+	static const ROMInfo * const MT32_V1_07_ROMS[] = {&CTRL_MT32_V1_07, &PCM_MT32, &CTRL_MT32_V1_07_A, &CTRL_MT32_V1_07_B, &PCM_MT32_L, &PCM_MT32_H, NULL};
+	static const ROMInfo * const MT32_BLUER_ROMS[] = {&CTRL_MT32_BLUER, &PCM_MT32, &CTRL_MT32_BLUER_A, &CTRL_MT32_BLUER_B, &PCM_MT32_L, &PCM_MT32_H, NULL};
+	static const ROMInfo * const MT32_V2_03_ROMS[] = {&CTRL_MT32_V2_03, &PCM_MT32, &PCM_MT32_L, &PCM_MT32_H, NULL};
+	static const ROMInfo * const MT32_V2_04_ROMS[] = {&CTRL_MT32_V2_04, &PCM_MT32, &PCM_MT32_L, &PCM_MT32_H, NULL};
+	static const ROMInfo * const MT32_V2_06_ROMS[] = {&CTRL_MT32_V2_06, &PCM_MT32, &PCM_MT32_L, &PCM_MT32_H, NULL};
+	static const ROMInfo * const MT32_V2_07_ROMS[] = {&CTRL_MT32_V2_07, &PCM_MT32, &PCM_MT32_L, &PCM_MT32_H, NULL};
+	static const ROMInfo * const CM32L_V1_00_ROMS[] = {&CTRL_CM32L_V1_00, &PCM_CM32L, &PCM_CM32L_L, &PCM_CM32L_H, NULL};
+	static const ROMInfo * const CM32L_V1_02_ROMS[] = {&CTRL_CM32L_V1_02, &PCM_CM32L, &PCM_CM32L_L, &PCM_CM32L_H, NULL};
+	static const ROMInfo * const CM32LN_V1_00_ROMS[] = {&CTRL_CM32LN_V1_00, &PCM_CM32L, NULL};
 
 	static const ROMInfoLists romInfoLists = {
 		{MT32_V1_04_ROMS, _CALC_ARRAY_LENGTH(MT32_V1_04_ROMS)},
@@ -131,9 +147,13 @@ static const ROMInfoLists &getROMInfoLists() {
 		{MT32_V1_06_ROMS, _CALC_ARRAY_LENGTH(MT32_V1_06_ROMS)},
 		{MT32_V1_07_ROMS, _CALC_ARRAY_LENGTH(MT32_V1_07_ROMS)},
 		{MT32_BLUER_ROMS, _CALC_ARRAY_LENGTH(MT32_BLUER_ROMS)},
+		{MT32_V2_03_ROMS, _CALC_ARRAY_LENGTH(MT32_V2_03_ROMS)},
 		{MT32_V2_04_ROMS, _CALC_ARRAY_LENGTH(MT32_V2_04_ROMS)},
+		{MT32_V2_06_ROMS, _CALC_ARRAY_LENGTH(MT32_V2_06_ROMS)},
+		{MT32_V2_07_ROMS, _CALC_ARRAY_LENGTH(MT32_V2_07_ROMS)},
 		{CM32L_V1_00_ROMS, _CALC_ARRAY_LENGTH(CM32L_V1_00_ROMS)},
 		{CM32L_V1_02_ROMS, _CALC_ARRAY_LENGTH(CM32L_V1_02_ROMS)},
+		{CM32LN_V1_00_ROMS, _CALC_ARRAY_LENGTH(CM32LN_V1_00_ROMS)},
 		{FULL_ROM_INFOS, _CALC_ARRAY_LENGTH(FULL_ROM_INFOS)},
 		{PARTIAL_ROM_INFOS, _CALC_ARRAY_LENGTH(PARTIAL_ROM_INFOS)},
 		{ALL_ROM_INFOS, _CALC_ARRAY_LENGTH(ALL_ROM_INFOS)}
@@ -155,13 +175,13 @@ const ROMInfo *ROMInfo::getROMInfo(File *file) {
 
 const ROMInfo *ROMInfo::getROMInfo(File *file, const ROMInfo * const *romInfos) {
 	size_t fileSize = file->getSize();
-	for (Bit32u i = 0; romInfos[i] != nullptr; i++) {
+	for (Bit32u i = 0; romInfos[i] != NULL; i++) {
 		const ROMInfo *romInfo = romInfos[i];
 		if (fileSize == romInfo->fileSize && !strcmp(file->getSHA1(), romInfo->sha1Digest)) {
 			return romInfo;
 		}
 	}
-	return nullptr;
+	return NULL;
 }
 
 void ROMInfo::freeROMInfo(const ROMInfo *romInfo) {
@@ -178,7 +198,7 @@ const ROMInfo **ROMInfo::getROMInfoList(Bit32u types, Bit32u pairTypes) {
 			*currentROMInList++ = romInfo;
 		}
 	}
-	*currentROMInList = nullptr;
+	*currentROMInList = NULL;
 	return romInfoList;
 }
 
@@ -187,17 +207,17 @@ void ROMInfo::freeROMInfoList(const ROMInfo **romInfoList) {
 }
 
 const ROMInfo * const *ROMInfo::getAllROMInfos(Bit32u *itemCount) {
-	if (itemCount != nullptr) *itemCount = getROMInfoLists().allROMInfos.itemCount;
+	if (itemCount != NULL) *itemCount = getROMInfoLists().allROMInfos.itemCount;
 	return getROMInfoLists().allROMInfos.romInfos;
 }
 
 const ROMInfo * const *ROMInfo::getFullROMInfos(Bit32u *itemCount) {
-	if (itemCount != nullptr) *itemCount = getROMInfoLists().fullROMInfos.itemCount;
+	if (itemCount != NULL) *itemCount = getROMInfoLists().fullROMInfos.itemCount;
 	return getROMInfoLists().fullROMInfos.romInfos;
 }
 
 const ROMInfo * const *ROMInfo::getPartialROMInfos(Bit32u *itemCount) {
-	if (itemCount != nullptr) *itemCount = getROMInfoLists().partialROMInfos.itemCount;
+	if (itemCount != NULL) *itemCount = getROMInfoLists().partialROMInfos.itemCount;
 	return getROMInfoLists().partialROMInfos.romInfos;
 }
 
@@ -213,9 +233,9 @@ const ROMImage *ROMImage::appendImages(const ROMImage *romImageLow, const ROMIma
 	memcpy(data, romDataLow, partSize);
 	memcpy(data + partSize, romDataHigh, partSize);
 	const ROMImage *romImageFull = makeFullROMImage(data, 2 * partSize);
-	if (romImageFull->getROMInfo() == nullptr) {
+	if (romImageFull->getROMInfo() == NULL) {
 		freeROMImage(romImageFull);
-		return nullptr;
+		return NULL;
 	}
 	return romImageFull;
 }
@@ -231,9 +251,9 @@ const ROMImage *ROMImage::interleaveImages(const ROMImage *romImageEven, const R
 		*(writePtr++) = romDataOdd[romDataIx];
 	}
 	const ROMImage *romImageFull = makeFullROMImage(data, 2 * partSize);
-	if (romImageFull->getROMInfo() == nullptr) {
+	if (romImageFull->getROMInfo() == NULL) {
 		freeROMImage(romImageFull);
-		return nullptr;
+		return NULL;
 	}
 	return romImageFull;
 }
@@ -263,7 +283,7 @@ const ROMImage *ROMImage::makeROMImage(File *file1, File *file2) {
 	const ROMInfo * const *partialROMInfos = getROMInfoLists().partialROMInfos.romInfos;
 	const ROMImage *image1 = makeROMImage(file1, partialROMInfos);
 	const ROMImage *image2 = makeROMImage(file2, partialROMInfos);
-	const ROMImage *fullImage = image1->getROMInfo() == nullptr || image2->getROMInfo() == nullptr ? nullptr : mergeROMImages(image1, image2);
+	const ROMImage *fullImage = image1->getROMInfo() == NULL || image2->getROMInfo() == NULL ? NULL : mergeROMImages(image1, image2);
 	freeROMImage(image1);
 	freeROMImage(image2);
 	return fullImage;
@@ -275,7 +295,7 @@ void ROMImage::freeROMImage(const ROMImage *romImage) {
 
 const ROMImage *ROMImage::mergeROMImages(const ROMImage *romImage1, const ROMImage *romImage2) {
 	if (romImage1->romInfo->pairROMInfo != romImage2->romInfo) {
-		return nullptr;
+		return NULL;
 	}
 	switch (romImage1->romInfo->pairType) {
 	case ROMInfo::FirstHalf:
@@ -289,7 +309,7 @@ const ROMImage *ROMImage::mergeROMImages(const ROMImage *romImage1, const ROMIma
 	default:
 		break;
 	}
-	return nullptr;
+	return NULL;
 }
 
 File *ROMImage::getFile() const {
@@ -311,14 +331,18 @@ const MachineConfiguration * const *MachineConfiguration::getAllMachineConfigura
 	static const MachineConfiguration MT32_1_06 = MachineConfiguration("mt32_1_06", romInfoLists.mt32_1_06.romInfos, romInfoLists.mt32_1_06.itemCount);
 	static const MachineConfiguration MT32_1_07 = MachineConfiguration("mt32_1_07", romInfoLists.mt32_1_07.romInfos, romInfoLists.mt32_1_07.itemCount);
 	static const MachineConfiguration MT32_BLUER = MachineConfiguration("mt32_bluer", romInfoLists.mt32_bluer.romInfos, romInfoLists.mt32_bluer.itemCount);
+	static const MachineConfiguration MT32_2_03 = MachineConfiguration("mt32_2_03", romInfoLists.mt32_2_03.romInfos, romInfoLists.mt32_2_03.itemCount);
 	static const MachineConfiguration MT32_2_04 = MachineConfiguration("mt32_2_04", romInfoLists.mt32_2_04.romInfos, romInfoLists.mt32_2_04.itemCount);
+	static const MachineConfiguration MT32_2_06 = MachineConfiguration("mt32_2_06", romInfoLists.mt32_2_06.romInfos, romInfoLists.mt32_2_06.itemCount);
+	static const MachineConfiguration MT32_2_07 = MachineConfiguration("mt32_2_07", romInfoLists.mt32_2_07.romInfos, romInfoLists.mt32_2_07.itemCount);
 	static const MachineConfiguration CM32L_1_00 = MachineConfiguration("cm32l_1_00", romInfoLists.cm32l_1_00.romInfos, romInfoLists.cm32l_1_00.itemCount);
 	static const MachineConfiguration CM32L_1_02 = MachineConfiguration("cm32l_1_02", romInfoLists.cm32l_1_02.romInfos, romInfoLists.cm32l_1_02.itemCount);
+	static const MachineConfiguration CM32LN_1_00 = MachineConfiguration("cm32ln_1_00", romInfoLists.cm32ln_1_00.romInfos, romInfoLists.cm32ln_1_00.itemCount);
 	static const MachineConfiguration * const MACHINE_CONFIGURATIONS[] = {
-		&MT32_1_04, &MT32_1_05, &MT32_1_06, &MT32_1_07, &MT32_BLUER, &MT32_2_04, &CM32L_1_00, &CM32L_1_02, nullptr
+		&MT32_1_04, &MT32_1_05, &MT32_1_06, &MT32_1_07, &MT32_BLUER, &MT32_2_03, &MT32_2_04, &MT32_2_06, &MT32_2_07, &CM32L_1_00, &CM32L_1_02, &CM32LN_1_00, NULL
 	};
 
-	if (itemCount != nullptr) *itemCount = _CALC_ARRAY_LENGTH(MACHINE_CONFIGURATIONS);
+	if (itemCount != NULL) *itemCount = _CALC_ARRAY_LENGTH(MACHINE_CONFIGURATIONS);
 	return MACHINE_CONFIGURATIONS;
 }
 
@@ -331,7 +355,7 @@ const char *MachineConfiguration::getMachineID() const {
 }
 
 const ROMInfo * const *MachineConfiguration::getCompatibleROMInfos(Bit32u *itemCount) const {
-	if (itemCount != nullptr) *itemCount = romInfosCount;
+	if (itemCount != NULL) *itemCount = romInfosCount;
 	return romInfos;
 }
 
diff --git a/audio/softsynth/mt32/ROMInfo.h b/audio/softsynth/mt32/ROMInfo.h
index 1e52ee78d3a..1580362f4e1 100644
--- a/audio/softsynth/mt32/ROMInfo.h
+++ b/audio/softsynth/mt32/ROMInfo.h
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License as published by
diff --git a/audio/softsynth/mt32/SampleRateConverter.cpp b/audio/softsynth/mt32/SampleRateConverter.cpp
index 7beac346b70..cce6f59fd65 100644
--- a/audio/softsynth/mt32/SampleRateConverter.cpp
+++ b/audio/softsynth/mt32/SampleRateConverter.cpp
@@ -1,4 +1,4 @@
-/* Copyright (C) 2015-2021 Sergey V. Mikayev
+/* Copyright (C) 2015-2022 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
@@ -14,9 +14,7 @@
  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#define MT32EMU_WITH_LIBSOXR_RESAMPLER 0
-#define MT32EMU_WITH_LIBSAMPLERATE_RESAMPLER 0
-#define MT32EMU_WITH_INTERNAL_RESAMPLER 1
+#include <cstddef>
 
 #include "SampleRateConverter.h"
 
@@ -41,7 +39,7 @@ static inline void *createDelegate(Synth &synth, double targetSampleRate, Sample
 	return new InternalResampler(synth, targetSampleRate, quality);
 #else
 	(void)synth, (void)targetSampleRate, (void)quality;
-	return nullptr;
+	return NULL;
 #endif
 }
 
diff --git a/audio/softsynth/mt32/SampleRateConverter.h b/audio/softsynth/mt32/SampleRateConverter.h
index 4c501632a0a..6831ff2a0f3 100644
--- a/audio/softsynth/mt32/SampleRateConverter.h
+++ b/audio/softsynth/mt32/SampleRateConverter.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2015-2021 Sergey V. Mikayev
+/* Copyright (C) 2015-2022 Sergey V. Mikayev
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License as published by
diff --git a/audio/softsynth/mt32/Structures.h b/audio/softsynth/mt32/Structures.h
index 28dcb8514ce..29362020193 100644
--- a/audio/softsynth/mt32/Structures.h
+++ b/audio/softsynth/mt32/Structures.h
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 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
@@ -191,6 +191,9 @@ struct ControlROMFeatureSet {
 	unsigned int quirkPanMult : 1;
 	unsigned int quirkKeyShift : 1;
 	unsigned int quirkTVFBaseCutoffLimit : 1;
+	unsigned int quirkFastPitchChanges : 1;
+	unsigned int quirkDisplayCustomMessagePriority : 1;
+	unsigned int oldMT32DisplayFeatures : 1;
 
 	// Features below don't actually depend on control ROM version, which is used to identify hardware model
 	unsigned int defaultReverbMT32Compatible : 1;
@@ -221,6 +224,8 @@ struct ControlROMMap {
 	Bit16u timbreMaxTable; // 72 bytes
 	Bit16u soundGroupsTable; // 14 bytes each entry
 	Bit16u soundGroupsCount;
+	Bit16u startupMessage; // 20 characters + NULL terminator
+	Bit16u sysexErrorMessage; // 20 characters + NULL terminator
 };
 
 struct ControlROMPCMStruct {
diff --git a/audio/softsynth/mt32/Synth.cpp b/audio/softsynth/mt32/Synth.cpp
index dee8b1a961b..0b81edb9341 100644
--- a/audio/softsynth/mt32/Synth.cpp
+++ b/audio/softsynth/mt32/Synth.cpp
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License as published by
@@ -22,6 +22,7 @@
 #include "Synth.h"
 #include "Analog.h"
 #include "BReverbModel.h"
+#include "Display.h"
 #include "File.h"
 #include "MemoryRegion.h"
 #include "MidiEventQueue.h"
@@ -41,19 +42,35 @@ 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, // quirkBasePitchOverflow
-	true, // quirkPitchEnvelopeOverflow
-	true, // quirkRingModulationNoMix
-	true, // quirkTVAZeroEnvLevels
-	true, // quirkPanMult
-	true, // quirkKeyShift
-	true, // quirkTVFBaseCutoffLimit
-	true, // defaultReverbMT32Compatible
-	true // oldMT32AnalogLPF
+static const ControlROMFeatureSet OLD_MT32_ELDER = {
+	true,  // quirkBasePitchOverflow
+	true,  // quirkPitchEnvelopeOverflow
+	true,  // quirkRingModulationNoMix
+	true,  // quirkTVAZeroEnvLevels
+	true,  // quirkPanMult
+	true,  // quirkKeyShift
+	true,  // quirkTVFBaseCutoffLimit
+	false, // quirkFastPitchChanges
+	true,  // quirkDisplayCustomMessagePriority
+	true,  // oldMT32DisplayFeatures
+	true,  // defaultReverbMT32Compatible
+	true   // oldMT32AnalogLPF
 };
-static const ControlROMFeatureSet CM32L_COMPATIBLE = {
+static const ControlROMFeatureSet OLD_MT32_LATER = {
+	true,  // quirkBasePitchOverflow
+	true,  // quirkPitchEnvelopeOverflow
+	true,  // quirkRingModulationNoMix
+	true,  // quirkTVAZeroEnvLevels
+	true,  // quirkPanMult
+	true,  // quirkKeyShift
+	true,  // quirkTVFBaseCutoffLimit
+	false, // quirkFastPitchChanges
+	false, // quirkDisplayCustomMessagePriority
+	true,  // oldMT32DisplayFeatures
+	true,  // defaultReverbMT32Compatible
+	true   // oldMT32AnalogLPF
+};
+static const ControlROMFeatureSet NEW_MT32_COMPATIBLE = {
 	false, // quirkBasePitchOverflow
 	false, // quirkPitchEnvelopeOverflow
 	false, // quirkRingModulationNoMix
@@ -61,20 +78,41 @@ static const ControlROMFeatureSet CM32L_COMPATIBLE = {
 	false, // quirkPanMult
 	false, // quirkKeyShift
 	false, // quirkTVFBaseCutoffLimit
+	false, // quirkFastPitchChanges
+	false, // quirkDisplayCustomMessagePriority
+	false, // oldMT32DisplayFeatures
 	false, // defaultReverbMT32Compatible
-	false // oldMT32AnalogLPF
+	false  // oldMT32AnalogLPF
+};
+static const ControlROMFeatureSet CM32LN_COMPATIBLE = {
+	false, // quirkBasePitchOverflow
+	false, // quirkPitchEnvelopeOverflow
+	false, // quirkRingModulationNoMix
+	false, // quirkTVAZeroEnvLevels
+	false, // quirkPanMult
+	false, // quirkKeyShift
+	false, // quirkTVFBaseCutoffLimit
+	true,  // quirkFastPitchChanges
+	false, // quirkDisplayCustomMessagePriority
+	false, // oldMT32DisplayFeatures
+	false, // defaultReverbMT32Compatible
+	false  // oldMT32AnalogLPF
 };
 
-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
+static const ControlROMMap ControlROMMaps[] = {
+	//     ID                Features        PCMmap  PCMc  tmbrA  tmbrAO, tmbrAC tmbrB   tmbrBO  tmbrBC tmbrR   trC rhythm rhyC  rsrv   panpot   prog   rhyMax  patMax  sysMax  timMax  sndGrp sGC  stMsg   sErMsg
+	{"ctrl_mt32_1_04",    OLD_MT32_ELDER,    0x3000, 128, 0x8000, 0x0000, false, 0xC000, 0x4000, false, 0x3200, 30, 0x73A6, 85, 0x57C7, 0x57E2, 0x57D0, 0x5252, 0x525E, 0x526E, 0x520A, 0x7064, 19, 0x217A, 0x4BB6},
+	{"ctrl_mt32_1_05",    OLD_MT32_ELDER,    0x3000, 128, 0x8000, 0x0000, false, 0xC000, 0x4000, false, 0x3200, 30, 0x7414, 85, 0x57C7, 0x57E2, 0x57D0, 0x5252, 0x525E, 0x526E, 0x520A, 0x70CA, 19, 0x217A, 0x4BB6},
+	{"ctrl_mt32_1_06",    OLD_MT32_LATER,    0x3000, 128, 0x8000, 0x0000, false, 0xC000, 0x4000, false, 0x3200, 30, 0x7414, 85, 0x57D9, 0x57F4, 0x57E2, 0x5264, 0x5270, 0x5280, 0x521C, 0x70CA, 19, 0x217A, 0x4BBA},
+	{"ctrl_mt32_1_07",    OLD_MT32_LATER,    0x3000, 128, 0x8000, 0x0000, false, 0xC000, 0x4000, false, 0x3200, 30, 0x73fe, 85, 0x57B1, 0x57CC, 0x57BA, 0x523C, 0x5248, 0x5258, 0x51F4, 0x70B0, 19, 0x217A, 0x4B92},
+	{"ctrl_mt32_bluer",   OLD_MT32_LATER,    0x3000, 128, 0x8000, 0x0000, false, 0xC000, 0x4000, false, 0x3200, 30, 0x741C, 85, 0x57E5, 0x5800, 0x57EE, 0x5270, 0x527C, 0x528C, 0x5228, 0x70CE, 19, 0x217A, 0x4BC6},
+	{"ctrl_mt32_2_03",  NEW_MT32_COMPATIBLE, 0x8100, 128, 0x8000, 0x8000, true,  0x8080, 0x8000, true,  0x8500, 64, 0x8580, 85, 0x4F49, 0x4F64, 0x4F52, 0x4885, 0x4889, 0x48A2, 0x48B9, 0x5A44, 19, 0x1EF0, 0x4066},
+	{"ctrl_mt32_2_04",  NEW_MT32_COMPATIBLE, 0x8100, 128, 0x8000, 0x8000, true,  0x8080, 0x8000, true,  0x8500, 64, 0x8580, 85, 0x4F5D, 0x4F78, 0x4F66, 0x4899, 0x489D, 0x48B6, 0x48CD, 0x5A58, 19, 0x1EF0, 0x406D},
+	{"ctrl_mt32_2_06",  NEW_MT32_COMPATIBLE, 0x8100, 128, 0x8000, 0x8000, true,  0x8080, 0x8000, true,  0x8500, 64, 0x8580, 85, 0x4F69, 0x4F84, 0x4F72, 0x48A5, 0x48A9, 0x48C2, 0x48D9, 0x5A64, 19, 0x1EF0, 0x4021},
+	{"ctrl_mt32_2_07",  NEW_MT32_COMPATIBLE, 0x8100, 128, 0x8000, 0x8000, true,  0x8080, 0x8000, true,  0x8500, 64, 0x8580, 85, 0x4F81, 0x4F9C, 0x4F8A, 0x48B9, 0x48BD, 0x48D6, 0x48ED, 0x5A78, 19, 0x1EE7, 0x4035},
+	{"ctrl_cm32l_1_00", NEW_MT32_COMPATIBLE, 0x8100, 256, 0x8000, 0x8000, true,  0x8080, 0x8000, true,  0x8500, 64, 0x8580, 85, 0x4F65, 0x4F80, 0x4F6E, 0x48A1, 0x48A5, 0x48BE, 0x48D5, 0x5A6C, 19, 0x1EF0, 0x401D},
+	{"ctrl_cm32l_1_02", NEW_MT32_COMPATIBLE, 0x8100, 256, 0x8000, 0x8000, true,  0x8080, 0x8000, true,  0x8500, 64, 0x8580, 85, 0x4F93, 0x4FAE, 0x4F9C, 0x48CB, 0x48CF, 0x48E8, 0x48FF, 0x5A96, 19, 0x1EE7, 0x4047},
+	{"ctrl_cm32ln_1_00", CM32LN_COMPATIBLE,  0x8100, 256, 0x8000, 0x8000, true,  0x8080, 0x8000, true,  0x8500, 64, 0x8580, 85, 0x4EC7, 0x4EE2, 0x4ED0, 0x47FF, 0x4803, 0x481C, 0x4833, 0x55A2, 19, 0x1F59, 0x3F7C}
 	// (Note that old MT-32 ROMs actually have 86 entries for rhythmTemp)
 };
 
@@ -90,7 +128,7 @@ static inline PartialState getPartialState(PartialManager *partialManager, unsig
 
 template <class I, class O>
 static inline void convertSampleFormat(const I *inBuffer, O *outBuffer, const Bit32u len) {
-	if (inBuffer == nullptr || outBuffer == nullptr) return;
+	if (inBuffer == NULL || outBuffer == NULL) return;
 
 	const I *inBufferEnd = inBuffer + len;
 	while (inBuffer < inBufferEnd) {
@@ -138,6 +176,8 @@ protected:
 		synth.renderedSampleCount += count;
 	}
 
+	void updateDisplayState();
+
 public:
 	Renderer(Synth &useSynth) : synth(useSynth) {}
 
@@ -173,10 +213,10 @@ public:
 		tmpBuffers(createTmpBuffers())
 	{}
 
-	void render(IntSample *stereoStream, Bit32u len) override;
-	void render(FloatSample *stereoStream, Bit32u len) override;
-	void renderStreams(const DACOutputStreams<IntSample> &streams, Bit32u len) override;
-	void renderStreams(const DACOutputStreams<FloatSample> &streams, Bit32u len) override;
+	void render(IntSample *stereoStream, Bit32u len);
+	void render(FloatSample *stereoStream, Bit32u len);
+	void renderStreams(const DACOutputStreams<IntSample> &streams, Bit32u len);
+	void renderStreams(const DACOutputStreams<FloatSample> &streams, Bit32u len);
 
 	template <class O>
 	void doRenderAndConvert(O *stereoStream, Bit32u len);
@@ -209,10 +249,16 @@ public:
 
 	Bit32u midiEventQueueSize;
 	Bit32u midiEventQueueSysexStorageBufferSize;
+
+	Display *display;
+	bool oldMT32DisplayFeatures;
+
+	ReportHandler2 defaultReportHandler;
+	ReportHandler2 *reportHandler2;
 };
 
 Bit32u Synth::getLibraryVersionInt() {
-	return (MT32EMU_VERSION_MAJOR << 16) | (MT32EMU_VERSION_MINOR << 8) | (MT32EMU_VERSION_PATCH);
+	return MT32EMU_CURRENT_VERSION_INT;
 }
 
 const char *Synth::getLibraryVersionString() {
@@ -241,24 +287,19 @@ Synth::Synth(ReportHandler *useReportHandler) :
 	opened = false;
 	reverbOverridden = false;
 	partialCount = DEFAULT_MAX_PARTIALS;
-	controlROMMap = nullptr;
-	controlROMFeatures = nullptr;
+	controlROMMap = NULL;
+	controlROMFeatures = NULL;
 
-	if (useReportHandler == nullptr) {
-		reportHandler = new ReportHandler;
-		isDefaultReportHandler = true;
-	} else {
-		reportHandler = useReportHandler;
-		isDefaultReportHandler = false;
-	}
+	reportHandler = useReportHandler != NULL ? useReportHandler : &extensions.defaultReportHandler;
+	extensions.reportHandler2 = &extensions.defaultReportHandler;
 
 	extensions.preallocatedReverbMemory = false;
 	for (int i = REVERB_MODE_ROOM; i <= REVERB_MODE_TAP_DELAY; i++) {
-		reverbModels[i] = nullptr;
+		reverbModels[i] = NULL;
 	}
-	reverbModel = nullptr;
-	analog = nullptr;
-	renderer = nullptr;
+	reverbModel = NULL;
+	analog = NULL;
+	renderer = NULL;
 	setDACInputMode(DACInputMode_NICE);
 	setMIDIDelayMode(MIDIDelayMode_DELAY_SHORT_MESSAGES_ONLY);
 	setOutputGain(1.0f);
@@ -269,38 +310,47 @@ Synth::Synth(ReportHandler *useReportHandler) :
 	setNicePartialMixingEnabled(false);
 	selectRendererType(RendererType_BIT16S);
 
-	patchTempMemoryRegion = nullptr;
-	rhythmTempMemoryRegion = nullptr;
-	timbreTempMemoryRegion = nullptr;
-	patchesMemoryRegion = nullptr;
-	timbresMemoryRegion = nullptr;
-	systemMemoryRegion = nullptr;
-	displayMemoryRegion = nullptr;
-	resetMemoryRegion = nullptr;
-	paddedTimbreMaxTable = nullptr;
-
-	partialManager = nullptr;
-	pcmWaves = nullptr;
-	pcmROMData = nullptr;
-	soundGroupNames = nullptr;
-	midiQueue = nullptr;
+	patchTempMemoryRegion = NULL;
+	rhythmTempMemoryRegion = NULL;
+	timbreTempMemoryRegion = NULL;
+	patchesMemoryRegion = NULL;
+	timbresMemoryRegion = NULL;
+	systemMemoryRegion = NULL;
+	displayMemoryRegion = NULL;
+	resetMemoryRegion = NULL;
+	paddedTimbreMaxTable = NULL;
+
+	partialManager = NULL;
+	pcmWaves = NULL;
+	pcmROMData = NULL;
+	soundGroupNames = NULL;
+	midiQueue = NULL;
 	extensions.midiEventQueueSize = DEFAULT_MIDI_EVENT_QUEUE_SIZE;
 	extensions.midiEventQueueSysexStorageBufferSize = 0;
 	lastReceivedMIDIEventTimestamp = 0;
 	memset(parts, 0, sizeof(parts));
 	renderedSampleCount = 0;
+	extensions.display = NULL;
+	extensions.oldMT32DisplayFeatures = false;
 }
 
 Synth::~Synth() {
 	close(); // Make sure we're closed and everything is freed
-	if (isDefaultReportHandler) {
-		delete reportHandler;
-	}
 	delete &mt32ram;
 	delete &mt32default;
 	delete &extensions;
 }
 
+void Synth::setReportHandler2(ReportHandler2 *reportHandler2) {
+	if (reportHandler2 != NULL) {
+		reportHandler = reportHandler2;
+		extensions.reportHandler2 = reportHandler2;
+	} else {
+		reportHandler = &extensions.defaultReportHandler;
+		extensions.reportHandler2 = &extensions.defaultReportHandler;
+	}
+}
+
 void ReportHandler::showLCDMessage(const char *data) {
 	printf("WRITE-LCD: %s\n", data);
 }
@@ -310,26 +360,38 @@ void ReportHandler::printDebug(const char *fmt, va_list list) {
 	printf("\n");
 }
 
-void Synth::newTimbreSet(Bit8u partNum, Bit8u timbreGroup, Bit8u timbreNumber, const char patchName[]) {
-	const char *soundGroupName;
+void Synth::rhythmNotePlayed() const {
+	extensions.display->rhythmNotePlayed();
+}
+
+void Synth::voicePartStateChanged(Bit8u partNum, bool partActivated) const {
+	extensions.display->voicePartStateChanged(partNum, partActivated);
+}
+
+void Synth::newTimbreSet(Bit8u partNum) const {
+	const Part *part = getPart(partNum);
+	reportHandler->onProgramChanged(partNum, getSoundGroupName(part), part->getCurrentInstr());
+}
+
+const char *Synth::getSoundGroupName(const Part *part) const {
+	const PatchParam &patch = part->getPatchTemp()->patch;
+	return getSoundGroupName(patch.timbreGroup, patch.timbreNum);
+}
+
+const char *Synth::getSoundGroupName(Bit8u timbreGroup, Bit8u timbreNumber) const {
 	switch (timbreGroup) {
 	case 1:
 		timbreNumber += 64;
 		// Fall-through
 	case 0:
-		soundGroupName = soundGroupNames[soundGroupIx[timbreNumber]];
-		break;
+		return soundGroupNames[soundGroupIx[timbreNumber]];
 	case 2:
-		soundGroupName = soundGroupNames[controlROMMap->soundGroupsCount - 2];
-		break;
+		return soundGroupNames[controlROMMap->soundGroupsCount - 2];
 	case 3:
-		soundGroupName = soundGroupNames[controlROMMap->soundGroupsCount - 1];
-		break;
+		return soundGroupNames[controlROMMap->soundGroupsCount - 1];
 	default:
-		soundGroupName = nullptr;
-		break;
+		return NULL;
 	}
-	reportHandler->onProgramChanged(partNum, soundGroupName, patchName);
 }
 
 #define MT32EMU_PRINT_DEBUG \
@@ -365,12 +427,12 @@ void Synth::setReverbEnabled(bool newReverbEnabled) {
 		if (!extensions.preallocatedReverbMemory) {
 			reverbModel->close();
 		}
-		reverbModel = nullptr;
+		reverbModel = NULL;
 	}
 }
 
 bool Synth::isReverbEnabled() const {
-	return reverbModel != nullptr;
+	return reverbModel != NULL;
 }
 
 void Synth::setReverbOverridden(bool newReverbOverridden) {
@@ -433,7 +495,7 @@ MIDIDelayMode Synth::getMIDIDelayMode() const {
 void Synth::setOutputGain(float newOutputGain) {
 	if (newOutputGain < 0.0f) newOutputGain = -newOutputGain;
 	outputGain = newOutputGain;
-	if (analog != nullptr) analog->setSynthOutputGain(newOutputGain);
+	if (analog != NULL) analog->setSynthOutputGain(newOutputGain);
 }
 
 float Synth::getOutputGain() const {
@@ -443,13 +505,23 @@ float Synth::getOutputGain() const {
 void Synth::setReverbOutputGain(float newReverbOutputGain) {
 	if (newReverbOutputGain < 0.0f) newReverbOutputGain = -newReverbOutputGain;
 	reverbOutputGain = newReverbOutputGain;
-	if (analog != nullptr) analog->setReverbOutputGain(newReverbOutputGain, isMT32ReverbCompatibilityMode());
+	if (analog != NULL) analog->setReverbOutputGain(newReverbOutputGain, isMT32ReverbCompatibilityMode());
 }
 
 float Synth::getReverbOutputGain() const {
 	return reverbOutputGain;
 }
 
+void Synth::setPartVolumeOverride(Bit8u partNumber, Bit8u volumeOverride) {
+	if (opened && partNumber < 9) {
+		parts[partNumber]->setVolumeOverride(volumeOverride);
+	}
+}
+
+Bit8u Synth::getPartVolumeOverride(Bit8u partNumber) const {
+	return (!opened || partNumber > 8) ? 255 : parts[partNumber]->getVolumeOverride();
+}
+
 void Synth::setReversedStereoEnabled(bool enabled) {
 	reversedStereoEnabled = enabled;
 }
@@ -485,7 +557,7 @@ bool Synth::isNicePartialMixingEnabled() const {
 bool Synth::loadControlROM(const ROMImage &controlROMImage) {
 	File *file = controlROMImage.getFile();
 	const ROMInfo *controlROMInfo = controlROMImage.getROMInfo();
-	if ((controlROMInfo == nullptr)
+	if ((controlROMInfo == NULL)
 			|| (controlROMInfo->type != ROMInfo::Control)
 			|| (controlROMInfo->pairType != ROMInfo::Full)) {
 #if MT32EMU_MONITOR_INIT
@@ -501,8 +573,8 @@ bool Synth::loadControlROM(const ROMImage &controlROMImage) {
 	memcpy(controlROMData, fileData, CONTROL_ROM_SIZE);
 
 	// Control ROM successfully loaded, now check whether it's a known type
-	controlROMMap = nullptr;
-	controlROMFeatures = nullptr;
+	controlROMMap = NULL;
+	controlROMFeatures = NULL;
 	for (unsigned int i = 0; i < sizeof(ControlROMMaps) / sizeof(ControlROMMaps[0]); i++) {
 		if (strcmp(controlROMInfo->shortName, ControlROMMaps[i].shortName) == 0) {
 			controlROMMap = &ControlROMMaps[i];
@@ -519,7 +591,7 @@ bool Synth::loadControlROM(const ROMImage &controlROMImage) {
 bool Synth::loadPCMROM(const ROMImage &pcmROMImage) {
 	File *file = pcmROMImage.getFile();
 	const ROMInfo *pcmROMInfo = pcmROMImage.getROMInfo();
-	if ((pcmROMInfo == nullptr)
+	if ((pcmROMInfo == NULL)
 			|| (pcmROMInfo->type != ROMInfo::PCM)
 			|| (pcmROMInfo->pairType != ROMInfo::Full)) {
 		return false;
@@ -650,7 +722,7 @@ bool Synth::open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage, B
 		return false;
 	}
 	partialCount = usePartialCount;
-	abortingPoly = nullptr;
+	abortingPoly = NULL;
 	extensions.abortingPartIx = 0;
 
 	// This is to help detect bugs
@@ -717,6 +789,16 @@ bool Synth::open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage, B
 		return false;
 	}
 
+	if (controlROMMap->timbreRCount == 30) {
+		// We must initialise all 64 rhythm timbres to avoid undefined behaviour.
+		// SEMI-CONFIRMED: Old-gen MT-32 units likely map timbres 30..59 to 0..29.
+		// Attempts to play rhythm timbres 60..63 exhibit undefined behaviour.
+		// We want to emulate the wrap around, so merely copy the entire set of standard
+		// timbres once more. The last 4 dangerous timbres are zeroed out.
+		memcpy(&mt32ram.timbres[222], &mt32ram.timbres[192], sizeof(*mt32ram.timbres) * 30);
+		memset(&mt32ram.timbres[252], 0, sizeof(*mt32ram.timbres) * 4);
+	}
+
 #if MT32EMU_MONITOR_INIT
 	printDebug("Initialising Timbre Bank M");
 #endif
@@ -838,6 +920,9 @@ bool Synth::open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage, B
 			return false;
 	}
 
+	extensions.display = new Display(*this);
+	extensions.oldMT32DisplayFeatures = controlROMFeatures->oldMT32DisplayFeatures;
+
 	opened = true;
 	activated = false;
 
@@ -850,41 +935,44 @@ bool Synth::open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage, B
 void Synth::dispose() {
 	opened = false;
 
+	delete extensions.display;
+	extensions.display = NULL;
+
 	delete midiQueue;
-	midiQueue = nullptr;
+	midiQueue = NULL;
 
 	delete renderer;
-	renderer = nullptr;
+	renderer = NULL;
 
 	delete analog;
-	analog = nullptr;
+	analog = NULL;
 
 	delete partialManager;
-	partialManager = nullptr;
+	partialManager = NULL;
 
 	for (int i = 0; i < 9; i++) {
 		delete parts[i];
-		parts[i] = nullptr;
+		parts[i] = NULL;
 	}
 
 	delete[] soundGroupNames;
-	soundGroupNames = nullptr;
+	soundGroupNames = NULL;
 
 	delete[] pcmWaves;
-	pcmWaves = nullptr;
+	pcmWaves = NULL;
 
 	delete[] pcmROMData;
-	pcmROMData = nullptr;
+	pcmROMData = NULL;
 
 	deleteMemoryRegions();
 
 	for (int i = REVERB_MODE_ROOM; i <= REVERB_MODE_TAP_DELAY; i++) {
 		delete reverbModels[i];
-		reverbModels[i] = nullptr;
+		reverbModels[i] = NULL;
 	}
-	reverbModel = nullptr;
-	controlROMFeatures = nullptr;
-	controlROMMap = nullptr;
+	reverbModel = NULL;
+	controlROMFeatures = NULL;
+	controlROMMap = NULL;
 }
 
 void Synth::close() {
@@ -898,11 +986,11 @@ bool Synth::isOpen() const {
 }
 
 void Synth::flushMIDIQueue() {
-	if (midiQueue == nullptr) return;
+	if (midiQueue == NULL) return;
 	for (;;) {
 		const volatile MidiEventQueue::MidiEvent *midiEvent = midiQueue->peekMidiEvent();
-		if (midiEvent == nullptr) break;
-		if (midiEvent->sysexData == nullptr) {
+		if (midiEvent == NULL) break;
+		if (midiEvent->sysexData == NULL) {
 			playMsgNow(midiEvent->shortMessageData);
 		} else {
 			playSysexNow(midiEvent->sysexData, midiEvent->sysexLength);
@@ -926,7 +1014,7 @@ Bit32u Synth::setMIDIEventQueueSize(Bit32u useSize) {
 		binarySize = MAX_QUEUE_SIZE;
 	}
 	extensions.midiEventQueueSize = binarySize;
-	if (midiQueue != nullptr) {
+	if (midiQueue != NULL) {
 		flushMIDIQueue();
 		delete midiQueue;
 		midiQueue = new MidiEventQueue(binarySize, extensions.midiEventQueueSysexStorageBufferSize);
@@ -938,7 +1026,7 @@ void Synth::configureMIDIEventQueueSysexStorage(Bit32u storageBufferSize) {
 	if (extensions.midiEventQueueSysexStorageBufferSize == storageBufferSize) return;
 
 	extensions.midiEventQueueSysexStorageBufferSize = storageBufferSize;
-	if (midiQueue != nullptr) {
+	if (midiQueue != NULL) {
 		flushMIDIQueue();
 		delete midiQueue;
 		midiQueue = new MidiEventQueue(extensions.midiEventQueueSize, storageBufferSize);
@@ -986,7 +1074,7 @@ bool Synth::playMsg(Bit32u msg, Bit32u timestamp) {
 		reportHandler->onMIDISystemRealtime(Bit8u(msg & 0xFF));
 		return true;
 	}
-	if (midiQueue == nullptr) return false;
+	if (midiQueue == NULL) return false;
 	if (midiDelayMode != MIDIDelayMode_IMMEDIATE) {
 		timestamp = addMIDIInterfaceDelay(getShortMessageLength(msg), timestamp);
 	}
@@ -1002,7 +1090,7 @@ bool Synth::playSysex(const Bit8u *sysex, Bit32u len) {
 }
 
 bool Synth::playSysex(const Bit8u *sysex, Bit32u len, Bit32u timestamp) {
-	if (midiQueue == nullptr) return false;
+	if (midiQueue == NULL) return false;
 	if (midiDelayMode == MIDIDelayMode_DELAY_ALL) {
 		timestamp = addMIDIInterfaceDelay(len, timestamp);
 	}
@@ -1064,7 +1152,7 @@ void Synth::playMsgOnPart(Bit8u part, Bit8u code, Bit8u note, Bit8u velocity) {
 		if (velocity == 0) {
 			// MIDI defines note-on with velocity 0 as being the same as note-off with velocity 40
 			parts[part]->noteOff(note);
-		} else {
+		} else if (parts[part]->getVolumeOverride() > 0) {
 			parts[part]->noteOn(note, velocity);
 		}
 		break;
@@ -1130,16 +1218,21 @@ void Synth::playMsgOnPart(Bit8u part, Bit8u code, Bit8u note, Bit8u velocity) {
 #endif
 			return;
 		}
-
+		extensions.display->midiMessagePlayed();
 		break;
 	case 0xC: // Program change
 		//printDebug("Program change %01x", note);
 		parts[part]->setProgram(note);
+		if (part < 8) {
+			extensions.display->midiMessagePlayed();
+			extensions.display->programChanged(part);
+		}
 		break;
 	case 0xE: // Pitch bender
 		bend = (velocity << 7) | (note);
 		//printDebug("Pitch bender %02x", bend);
 		parts[part]->setBend(bend);
+		extensions.display->midiMessagePlayed();
 		break;
 	default:
 #if MT32EMU_MONITOR_MIDI > 0
@@ -1197,29 +1290,26 @@ void Synth::playSysexWithoutHeader(Bit8u device, Bit8u command, const Bit8u *sys
 		printDebug("playSysexWithoutHeader: Message is not intended for this device ID (provided: %02x, expected: 0x10 or channel)", int(device));
 		return;
 	}
-	// This is checked early in the real devices (before any sysex length checks or further processing)
-	// FIXME: Response to SYSEX_CMD_DAT reset with partials active (and in general) is untested.
-	if ((command == SYSEX_CMD_DT1 || command == SYSEX_CMD_DAT) && sysex[0] == 0x7F) {
-		reset();
-		return;
-	}
 
-	if (command == SYSEX_CMD_EOD) {
-#if MT32EMU_MONITOR_SYSEX > 0
-		printDebug("playSysexWithoutHeader: Ignored unsupported command %02x", command);
-#endif
-		return;
-	}
-	if (len < 4) {
+	// All models process the checksum before anything else and ignore messages lacking the checksum, or containing the checksum only.
+	if (len < 2) {
 		printDebug("playSysexWithoutHeader: Message is too short (%d bytes)!", len);
 		return;
 	}
 	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);
+		if (opened) extensions.display->checksumErrorOccurred();
 		return;
 	}
 	len -= 1; // Exclude checksum
+
+	if (command == SYSEX_CMD_EOD) {
+#if MT32EMU_MONITOR_SYSEX > 0
+		printDebug("playSysexWithoutHeader: Ignored unsupported command %02x", command);
+#endif
+		return;
+	}
 	switch (command) {
 	case SYSEX_CMD_WSD:
 #if MT32EMU_MONITOR_SYSEX > 0
@@ -1259,12 +1349,34 @@ void Synth::readSysex(Bit8u /*device*/, const Bit8u * /*sysex*/, Bit32u /*len*/)
 }
 
 void Synth::writeSysex(Bit8u device, const Bit8u *sysex, Bit32u len) {
-	if (!opened) return;
+	if (!opened || len < 1) return;
+
+	// This is checked early in the real devices (before any sysex length checks or further processing)
+	if (sysex[0] == 0x7F) {
+		if (!isDisplayOldMT32Compatible()) extensions.display->midiMessagePlayed();
+		reset();
+		return;
+	}
+
+	extensions.display->midiMessagePlayed();
 	reportHandler->onMIDIMessagePlayed();
+
+	if (len < 3) {
+		// A short message of just 1 or 2 bytes may be written to the display area yet it may cause a user-visible effect,
+		// similarly to the reset area.
+		if (sysex[0] == 0x20) {
+			extensions.display->displayControlMessageReceived(sysex, len);
+			return;
+		}
+		printDebug("writeSysex: Message is too short (%d bytes)!", len);
+		return;
+	}
+
 	Bit32u addr = (sysex[0] << 16) | (sysex[1] << 8) | (sysex[2]);
 	addr = MT32EMU_MEMADDR(addr);
 	sysex += 3;
 	len -= 3;
+
 	//printDebug("Sysex addr: 0x%06x", MT32EMU_SYSEXMEMADDR(addr));
 	// NOTE: Please keep both lower and upper bounds in each check, for ease of reading
 
@@ -1343,8 +1455,9 @@ void Synth::writeSysexGlobal(Bit32u addr, const Bit8u *sysex, Bit32u len) {
 		// Find the appropriate memory region
 		const MemoryRegion *region = findMemoryRegion(addr);
 
-		if (region == nullptr) {
+		if (region == NULL) {
 			printDebug("Sysex write to unrecognised address %06x, len %d", MT32EMU_SYSEXMEMADDR(addr), len);
+			// FIXME: Real devices may respond differently to a long SysEx that covers adjacent regions.
 			break;
 		}
 		writeMemoryRegion(region, addr, region->getClampedLen(addr, len), sysex);
@@ -1362,7 +1475,7 @@ void Synth::writeSysexGlobal(Bit32u addr, const Bit8u *sysex, Bit32u len) {
 void Synth::readMemory(Bit32u addr, Bit32u len, Bit8u *data) {
 	if (!opened) return;
 	const MemoryRegion *region = findMemoryRegion(addr);
-	if (region != nullptr) {
+	if (region != NULL) {
 		readMemoryRegion(region, addr, len, data);
 	}
 }
@@ -1391,24 +1504,24 @@ void Synth::initMemoryRegions() {
 
 void Synth::deleteMemoryRegions() {
 	delete patchTempMemoryRegion;
-	patchTempMemoryRegion = nullptr;
+	patchTempMemoryRegion = NULL;
 	delete rhythmTempMemoryRegion;
-	rhythmTempMemoryRegion = nullptr;
+	rhythmTempMemoryRegion = NULL;
 	delete timbreTempMemoryRegion;
-	timbreTempMemoryRegion = nullptr;
+	timbreTempMemoryRegion = NULL;
 	delete patchesMemoryRegion;
-	patchesMemoryRegion = nullptr;
+	patchesMemoryRegion = NULL;
 	delete timbresMemoryRegion;
-	timbresMemoryRegion = nullptr;
+	timbresMemoryRegion = NULL;
 	delete systemMemoryRegion;
-	systemMemoryRegion = nullptr;
+	systemMemoryRegion = NULL;
 	delete displayMemoryRegion;
-	displayMemoryRegion = nullptr;
+	displayMemoryRegion = NULL;
 	delete resetMemoryRegion;
-	resetMemoryRegion = nullptr;
+	resetMemoryRegion = NULL;
 
 	delete[] paddedTimbreMaxTable;
-	paddedTimbreMaxTable = nullptr;
+	paddedTimbreMaxTable = NULL;
 }
 
 MemoryRegion *Synth::findMemoryRegion(Bit32u addr) {
@@ -1421,14 +1534,14 @@ MemoryRegion *Synth::findMemoryRegion(Bit32u addr) {
 		systemMemoryRegion,
 		displayMemoryRegion,
 		resetMemoryRegion,
-		nullptr
+		NULL
 	};
-	for (int pos = 0; regions[pos] != nullptr; pos++) {
+	for (int pos = 0; regions[pos] != NULL; pos++) {
 		if (regions[pos]->contains(addr)) {
 			return regions[pos];
 		}
 	}
-	return nullptr;
+	return NULL;
 }
 
 void Synth::readMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, Bit8u *data) {
@@ -1469,7 +1582,7 @@ void Synth::writeMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u le
 #if MT32EMU_MONITOR_SYSEX > 0
 			printDebug("WRITE-PARTPATCH (%d-%d@%d..%d): %d; timbre=%d (%s), outlevel=%d", first, last, off, off + len, i, absTimbreNum, timbreName, mt32ram.patchTemp[i].outputLevel);
 #endif
-			if (parts[i] != nullptr) {
+			if (parts[i] != NULL) {
 				if (i != 8) {
 					// Note: Confirmed on CM-64 that we definitely *should* update the timbre here,
 					// but only in the case that the sysex actually writes to those values
@@ -1500,7 +1613,7 @@ void Synth::writeMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u le
 			printDebug("WRITE-RHYTHM (%d-%d@%d..%d): %d; level=%02x, panpot=%02x, reverb=%02x, timbre=%d (%s)", first, last, off, off + len, i, mt32ram.rhythmTemp[i].outputLevel, mt32ram.rhythmTemp[i].panpot, mt32ram.rhythmTemp[i].reverbSwitch, mt32ram.rhythmTemp[i].timbre, timbreName);
 #endif
 		}
-		if (parts[8] != nullptr) {
+		if (parts[8] != NULL) {
 			parts[8]->refresh();
 		}
 		break;
@@ -1513,7 +1626,7 @@ void Synth::writeMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u le
 #if MT32EMU_MONITOR_SYSEX > 0
 			printDebug("WRITE-PARTTIMBRE (%d-%d@%d..%d): timbre=%d (%s)", first, last, off, off + len, i, instrumentName);
 #endif
-			if (parts[i] != nullptr) {
+			if (parts[i] != NULL) {
 				parts[i]->refresh();
 			}
 		}
@@ -1622,7 +1735,7 @@ void Synth::writeMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u le
 			// FIXME:KG: Not sure if the stuff below should be done (for rhythm and/or parts)...
 			// Does the real MT-32 automatically do this?
 			for (unsigned int part = 0; part < 9; part++) {
-				if (parts[part] != nullptr) {
+				if (parts[part] != NULL) {
 					parts[part]->refreshTimbre(i);
 				}
 			}
@@ -1663,7 +1776,10 @@ void Synth::writeMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u le
 		}
 		break;
 	case MR_Display:
-		char buf[SYSEX_BUFFER_SIZE];
+		if (len > Display::LCD_TEXT_SIZE) len = Display::LCD_TEXT_SIZE;
+		if (!extensions.display->customDisplayMessageReceived(data, off, len)) break;
+		// Holds zero-terminated string of the maximum length.
+		char buf[Display::LCD_TEXT_SIZE + 1];
 		memcpy(&buf, &data[0], len);
 		buf[len] = 0;
 #if MT32EMU_MONITOR_SYSEX > 0
@@ -1709,7 +1825,7 @@ void Synth::refreshSystemReverbParameters() {
 	if (mt32ram.system.reverbTime == 0 && mt32ram.system.reverbLevel == 0) {
 		// Setting both time and level to 0 effectively disables wet reverb output on real devices.
 		// Take a shortcut in this case to reduce CPU load.
-		reverbModel = nullptr;
+		reverbModel = NULL;
 	} else {
 		reverbModel = reverbModels[mt32ram.system.reverbMode];
 	}
@@ -1719,7 +1835,7 @@ void Synth::refreshSystemReverbParameters() {
 				reverbModel->mute();
 			}
 		} else {
-			if (oldReverbModel != nullptr) {
+			if (oldReverbModel != NULL) {
 				oldReverbModel->close();
 			}
 			if (isReverbEnabled()) {
@@ -1746,7 +1862,7 @@ void Synth::refreshSystemChanAssign(Bit8u firstPart, Bit8u lastPart) {
 	// CONFIRMED: In the case of assigning a MIDI channel to multiple parts,
 	//            the messages received on that MIDI channel are handled by all the parts.
 	for (Bit32u i = 0; i <= 8; i++) {
-		if (parts[i] != nullptr && i >= firstPart && i <= lastPart) {
+		if (parts[i] != NULL && i >= firstPart && i <= lastPart) {
 			// CONFIRMED: Decay is started for all polys, and all controllers are reset, for every part whose assignment was touched by the sysex write.
 			parts[i]->allSoundOff();
 			parts[i]->resetAllControllers();
@@ -1769,6 +1885,10 @@ void Synth::refreshSystemChanAssign(Bit8u firstPart, Bit8u lastPart) {
 }
 
 void Synth::refreshSystemMasterVol() {
+	// Note, this should only occur when the user turns the volume knob. When the master volume is set via a SysEx, display
+	// doesn't actually update on all real devices. However, we anyway update the display, as we don't foresee a dedicated
+	// API for setting the master volume yet it's rather dubious that one really needs this quirk to be fairly emulated.
+	if (opened) extensions.display->masterVolumeChanged();
 #if MT32EMU_MONITOR_SYSEX > 0
 	printDebug(" Master volume: %d", mt32ram.system.masterVol);
 #endif
@@ -1818,6 +1938,32 @@ Bit32s Synth::getMasterTunePitchDelta() const {
 	return extensions.masterTunePitchDelta;
 }
 
+bool Synth::getDisplayState(char *targetBuffer, bool narrowLCD) const {
+	if (!opened) {
+		memset(targetBuffer, ' ', Display::LCD_TEXT_SIZE);
+		targetBuffer[Display::LCD_TEXT_SIZE] = 0;
+		return false;
+	}
+	return extensions.display->getDisplayState(targetBuffer, narrowLCD);
+}
+
+void Synth::setMainDisplayMode() {
+	if (opened) extensions.display->setMainDisplayMode();
+}
+
+
+void Synth::setDisplayCompatibility(bool oldMT32CompatibilityEnabled) {
+	extensions.oldMT32DisplayFeatures = oldMT32CompatibilityEnabled;
+}
+
+bool Synth::isDisplayOldMT32Compatible() const {
+	return extensions.oldMT32DisplayFeatures;
+}
+
+bool Synth::isDefaultDisplayOldMT32Compatible() const {
+	return opened && controlROMFeatures->oldMT32DisplayFeatures;
+}
+
 /** Defines an interface of a class that maintains storage of variable-sized data of SysEx messages. */
 class MidiEventQueue::SysexDataStorage {
 public:
@@ -1832,13 +1978,13 @@ public:
 /** Storage space for SysEx data is allocated dynamically on demand and is disposed lazily. */
 class DynamicSysexDataStorage : public MidiEventQueue::SysexDataStorage {
 public:
-	Bit8u *allocate(Bit32u sysexLength) override {
+	Bit8u *allocate(Bit32u sysexLength) {
 		return new Bit8u[sysexLength];
 	}
 
-	void reclaimUnused(const Bit8u *, Bit32u) override {}
+	void reclaimUnused(const Bit8u *, Bit32u) {}
 
-	void dispose(const Bit8u *sysexData, Bit32u) override {
+	void dispose(const Bit8u *sysexData, Bit32u) {
 		delete[] sysexData;
 	}
 };
@@ -1861,34 +2007,34 @@ public:
 		delete[] storageBuffer;
 	}
 
-	Bit8u *allocate(Bit32u sysexLength) override {
+	Bit8u *allocate(Bit32u sysexLength) {
 		Bit32u myStartPosition = startPosition;
 		Bit32u myEndPosition = endPosition;
 
 		// When the free space isn't contiguous, the data is allocated either right after the end position
 		// or at the buffer beginning, wherever it fits.
 		if (myStartPosition > myEndPosition) {
-			if (myStartPosition - myEndPosition <= sysexLength) return nullptr;
+			if (myStartPosition - myEndPosition <= sysexLength) return NULL;
 		} else if (storageBufferSize - myEndPosition < sysexLength) {
 			// There's not enough free space at the end to place the data block.
 			if (myStartPosition == myEndPosition) {
 				// The buffer is empty -> reset positions to the buffer beginning.
-				if (storageBufferSize <= sysexLength) return nullptr;
+				if (storageBufferSize <= sysexLength) return NULL;
 				if (myStartPosition != 0) {
 					myStartPosition = 0;
 					// It's OK to write startPosition here non-atomically. We don't expect any
 					// concurrent reads, as there must be no SysEx messages in the queue.
 					startPosition = myStartPosition;
 				}
-			} else if (myStartPosition <= sysexLength) return nullptr;
+			} else if (myStartPosition <= sysexLength) return NULL;
 			myEndPosition = 0;
 		}
 		endPosition = myEndPosition + sysexLength;
 		return storageBuffer + myEndPosition;
 	}
 
-	void reclaimUnused(const Bit8u *sysexData, Bit32u sysexLength) override {
-		if (sysexData == nullptr) return;
+	void reclaimUnused(const Bit8u *sysexData, Bit32u sysexLength) {
+		if (sysexData == NULL) return;
 		Bit32u allocatedPosition = startPosition;
 		if (storageBuffer + allocatedPosition == sysexData) {
 			startPosition = allocatedPosition + sysexLength;
@@ -1898,7 +2044,7 @@ public:
 		}
 	}
 
-	void dispose(const Bit8u *, Bit32u) override {}
+	void dispose(const Bit8u *, Bit32u) {}
 
 private:
 	Bit8u * const storageBuffer;
@@ -1921,7 +2067,7 @@ MidiEventQueue::MidiEventQueue(Bit32u useRingBufferSize, Bit32u storageBufferSiz
 	ringBuffer(new MidiEvent[useRingBufferSize]), ringBufferMask(useRingBufferSize - 1)
 {
 	for (Bit32u i = 0; i <= ringBufferMask; i++) {
-		ringBuffer[i].sysexData = nullptr;
+		ringBuffer[i].sysexData = NULL;
 	}
 	reset();
 }
@@ -1946,7 +2092,7 @@ bool MidiEventQueue::pushShortMessage(Bit32u shortMessageData, Bit32u timestamp)
 	if (startPosition == newEndPosition) return false;
 	volatile MidiEvent &newEvent = ringBuffer[endPosition];
 	sysexDataStorage.dispose(newEvent.sysexData, newEvent.sysexLength);
-	newEvent.sysexData = nullptr;
+	newEvent.sysexData = NULL;
 	newEvent.shortMessageData = shortMessageData;
 	newEvent.timestamp = timestamp;
 	endPosition = newEndPosition;
@@ -1960,7 +2106,7 @@ bool MidiEventQueue::pushSysex(const Bit8u *sysexData, Bit32u sysexLength, Bit32
 	volatile MidiEvent &newEvent = ringBuffer[endPosition];
 	sysexDataStorage.dispose(newEvent.sysexData, newEvent.sysexLength);
 	Bit8u *dstSysexData = sysexDataStorage.allocate(sysexLength);
-	if (dstSysexData == nullptr) return false;
+	if (dstSysexData == NULL) return false;
 	memcpy(dstSysexData, sysexData, sysexLength);
 	newEvent.sysexData = dstSysexData;
 	newEvent.sysexLength = sysexLength;
@@ -1970,7 +2116,7 @@ bool MidiEventQueue::pushSysex(const Bit8u *sysexData, Bit32u sysexLength, Bit32
 }
 
 const volatile MidiEventQueue::MidiEvent *MidiEventQueue::peekMidiEvent() {
-	return isEmpty() ? nullptr : &ringBuffer[startPosition];
+	return isEmpty() ? NULL : &ringBuffer[startPosition];
 }
 
 void MidiEventQueue::dropMidiEvent() {
@@ -1993,17 +2139,27 @@ RendererType Synth::getSelectedRendererType() const {
 }
 
 Bit32u Synth::getStereoOutputSampleRate() const {
-	return (analog == nullptr) ? SAMPLE_RATE : analog->getOutputSampleRate();
+	return (analog == NULL) ? SAMPLE_RATE : analog->getOutputSampleRate();
+}
+
+void Renderer::updateDisplayState() {
+	bool midiMessageLEDState;
+	bool midiMessageLEDStateUpdated;
+	bool lcdUpdated;
+	synth.extensions.display->checkDisplayStateUpdated(midiMessageLEDState, midiMessageLEDStateUpdated, lcdUpdated);
+	if (midiMessageLEDStateUpdated) synth.extensions.reportHandler2->onMidiMessageLEDStateUpdated(midiMessageLEDState);
+	if (lcdUpdated) synth.extensions.reportHandler2->onLCDStateUpdated();
 }
 
 template <class Sample>
 void RendererImpl<Sample>::doRender(Sample *stereoStream, Bit32u len) {
 	if (!isActivated()) {
 		incRenderedSampleCount(getAnalog().getDACStreamsLength(len));
-		if (!getAnalog().process(nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, stereoStream, len)) {
+		if (!getAnalog().process(NULL, NULL, NULL, NULL, NULL, NULL, stereoStream, len)) {
 			printDebug("RendererImpl: Invalid call to Analog::process()!\n");
 		}
 		Synth::muteSampleBuffer(stereoStream, len << 1);
+		updateDisplayState();
 		return;
 	}
 
@@ -2073,7 +2229,7 @@ void Synth::render(float *stream, Bit32u len) {
 
 template <class Sample>
 static inline void advanceStream(Sample *&stream, Bit32u len) {
-	if (stream != nullptr) {
+	if (stream != NULL) {
 		stream += len;
 	}
 }
@@ -2117,14 +2273,14 @@ void RendererImpl<Sample>::doRenderStreams(const DACOutputStreams<Sample> &strea
 		Bit32u thisLen = 1;
 		if (!isAbortingPoly()) {
 			const volatile MidiEventQueue::MidiEvent *nextEvent = getMidiQueue().peekMidiEvent();
-			Bit32s samplesToNextEvent = (nextEvent != nullptr) ? Bit32s(nextEvent->timestamp - getRenderedSampleCount()) : MAX_SAMPLES_PER_RUN;
+			Bit32s samplesToNextEvent = (nextEvent != NULL) ? Bit32s(nextEvent->timestamp - getRenderedSampleCount()) : MAX_SAMPLES_PER_RUN;
 			if (samplesToNextEvent > 0) {
 				thisLen = len > MAX_SAMPLES_PER_RUN ? MAX_SAMPLES_PER_RUN : len;
 				if (thisLen > Bit32u(samplesToNextEvent)) {
 					thisLen = samplesToNextEvent;
 				}
 			} else {
-				if (nextEvent->sysexData == nullptr) {
+				if (nextEvent->sysexData == NULL) {
 					synth.playMsgNow(nextEvent->shortMessageData);
 					// If a poly is aborting we don't drop the event from the queue.
 					// Instead, we'll return to it again when the abortion is done.
@@ -2311,10 +2467,10 @@ template <class Sample>
 void RendererImpl<Sample>::produceStreams(const DACOutputStreams<Sample> &streams, Bit32u len) {
 	if (isActivated()) {
 		// Even if LA32 output isn't desired, we proceed anyway with temp buffers
-		Sample *nonReverbLeft = streams.nonReverbLeft == nullptr ? tmpNonReverbLeft : streams.nonReverbLeft;
-		Sample *nonReverbRight = streams.nonReverbRight == nullptr ? tmpNonReverbRight : streams.nonReverbRight;
-		Sample *reverbDryLeft = streams.reverbDryLeft == nullptr ? tmpReverbDryLeft : streams.reverbDryLeft;
-		Sample *reverbDryRight = streams.reverbDryRight == nullptr ? tmpReverbDryRight : streams.reverbDryRight;
+		Sample *nonReverbLeft = streams.nonReverbLeft == NULL ? tmpNonReverbLeft : streams.nonReverbLeft;
+		Sample *nonReverbRight = streams.nonReverbRight == NULL ? tmpNonReverbRight : streams.nonReverbRight;
+		Sample *reverbDryLeft = streams.reverbDryLeft == NULL ? tmpReverbDryLeft : streams.reverbDryLeft;
+		Sample *reverbDryRight = streams.reverbDryRight == NULL ? tmpReverbDryRight : streams.reverbDryRight;
 
 		Synth::muteSampleBuffer(nonReverbLeft, len);
 		Synth::muteSampleBuffer(nonReverbRight, len);
@@ -2336,30 +2492,31 @@ void RendererImpl<Sample>::produceStreams(const DACOutputStreams<Sample> &stream
 			if (!getReverbModel().process(reverbDryLeft, reverbDryRight, streams.reverbWetLeft, streams.reverbWetRight, len)) {
 				printDebug("RendererImpl: Invalid call to BReverbModel::process()!\n");
 			}
-			if (streams.reverbWetLeft != nullptr) convertSamplesToOutput(streams.reverbWetLeft, len);
-			if (streams.reverbWetRight != nullptr) convertSamplesToOutput(streams.reverbWetRight, len);
+			if (streams.reverbWetLeft != NULL) convertSamplesToOutput(streams.reverbWetLeft, len);
+			if (streams.reverbWetRight != NULL) convertSamplesToOutput(streams.reverbWetRight, len);
 		} else {
 			Synth::muteSampleBuffer(streams.reverbWetLeft, len);
 			Synth::muteSampleBuffer(streams.reverbWetRight, len);
 		}
 
 		// Don't bother with conversion if the output is going to be unused
-		if (streams.nonReverbLeft != nullptr) {
+		if (streams.nonReverbLeft != NULL) {
 			produceLA32Output(nonReverbLeft, len);
 			convertSamplesToOutput(nonReverbLeft, len);
 		}
-		if (streams.nonReverbRight != nullptr) {
+		if (streams.nonReverbRight != NULL) {
 			produceLA32Output(nonReverbRight, len);
 			convertSamplesToOutput(nonReverbRight, len);
 		}
-		if (streams.reverbDryLeft != nullptr) convertSamplesToOutput(reverbDryLeft, len);
-		if (streams.reverbDryRight != nullptr) convertSamplesToOutput(reverbDryRight, len);
+		if (streams.reverbDryLeft != NULL) convertSamplesToOutput(reverbDryLeft, len);
+		if (streams.reverbDryRight != NULL) convertSamplesToOutput(reverbDryRight, len);
 	} else {
 		muteStreams(streams, len);
 	}
 
 	getPartialManager().clearAlreadyOutputed();
 	incRenderedSampleCount(len);
+	updateDisplayState();
 }
 
 void Synth::printPartialUsage(Bit32u sampleOffset) {
@@ -2456,7 +2613,7 @@ Bit32u Synth::getPlayingNotes(Bit8u partNumber, Bit8u *keys, Bit8u *velocities)
 	if (opened && (partNumber < 9)) {
 		const Part *part = parts[partNumber];
 		const Poly *poly = part->getFirstActivePoly();
-		while (poly != nullptr) {
+		while (poly != NULL) {
 			keys[playingNotes] = Bit8u(poly->getKey());
 			velocities[playingNotes] = Bit8u(poly->getVelocity());
 			playingNotes++;
@@ -2467,12 +2624,32 @@ Bit32u Synth::getPlayingNotes(Bit8u partNumber, Bit8u *keys, Bit8u *velocities)
 }
 
 const char *Synth::getPatchName(Bit8u partNumber) const {
-	return (!opened || partNumber > 8) ? nullptr : parts[partNumber]->getCurrentInstr();
+	return (!opened || partNumber > 8) ? NULL : parts[partNumber]->getCurrentInstr();
+}
+
+bool Synth::getSoundGroupName(char *soundGroupName, Bit8u timbreGroup, Bit8u timbreNumber) const {
+	if (!opened || 63 < timbreNumber) return false;
+	const char *foundGroupName = getSoundGroupName(timbreGroup, timbreNumber);
+	if (foundGroupName == NULL) return false;
+	memcpy(soundGroupName, foundGroupName, 7);
+	soundGroupName[7] = 0;
+	return true;
+}
+
+bool Synth::getSoundName(char *soundName, Bit8u timbreGroup, Bit8u timbreNumber) const {
+	if (!opened || 3 < timbreGroup) return false;
+	Bit8u timbresInGroup = 3 == timbreGroup ? controlROMMap->timbreRCount : 64;
+	if (timbresInGroup <= timbreNumber) return false;
+	TimbreParam::CommonParam &timbreCommon = mt32ram.timbres[timbreGroup * 64 + timbreNumber].timbre.common;
+	if (timbreCommon.partialMute == 0) return false;
+	memcpy(soundName, timbreCommon.name, sizeof timbreCommon.name);
+	soundName[sizeof timbreCommon.name] = 0;
+	return true;
 }
 
 const Part *Synth::getPart(Bit8u partNum) const {
 	if (partNum > 8) {
-		return nullptr;
+		return NULL;
 	}
 	return parts[partNum];
 }
@@ -2494,7 +2671,7 @@ void MemoryRegion::read(unsigned int entry, unsigned int off, Bit8u *dst, unsign
 		len = entrySize * entries - off;
 	}
 	Bit8u *src = getRealMemory();
-	if (src == nullptr) {
+	if (src == NULL) {
 #if MT32EMU_MONITOR_SYSEX > 0
 		synth->printDebug("read[%d]: unreadable region: entry=%d, off=%d, len=%d", type, entry, off, len);
 #endif
@@ -2520,7 +2697,7 @@ void MemoryRegion::write(unsigned int entry, unsigned int off, const Bit8u *src,
 		len = entrySize * entries - off;
 	}
 	Bit8u *dest = getRealMemory();
-	if (dest == nullptr) {
+	if (dest == NULL) {
 #if MT32EMU_MONITOR_SYSEX > 0
 		synth->printDebug("write[%d]: unwritable region: entry=%d, off=%d, len=%d", type, entry, off, len);
 #endif
diff --git a/audio/softsynth/mt32/Synth.h b/audio/softsynth/mt32/Synth.h
index 6b9e0c58717..0f88eb9f050 100644
--- a/audio/softsynth/mt32/Synth.h
+++ b/audio/softsynth/mt32/Synth.h
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 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
@@ -69,6 +69,9 @@ const Bit8u SYSEX_CMD_EOD = 0x45; // End of data
 const Bit8u SYSEX_CMD_ERR = 0x4E; // Communications error
 const Bit8u SYSEX_CMD_RJC = 0x4F; // Rejection
 
+// This value isn't quite correct: the new-gen MT-32 control ROMs (ver. 2.XX) are twice as big.
+// Nevertheless, this is still relevant for library internal usage because the higher half
+// of those ROMs only contains the demo songs in all cases.
 const Bit32u CONTROL_ROM_SIZE = 64 * 1024;
 
 // Set of multiplexed output streams appeared at the DAC entrance.
@@ -113,8 +116,21 @@ public:
 	virtual void onProgramChanged(Bit8u /* partNum */, const char * /* soundGroupName */, const char * /* patchName */) {}
 };
 
+// Extends ReportHandler, so that the client may supply callbacks for reporting signals about updated display state.
+class MT32EMU_EXPORT_V(2.6) ReportHandler2 : public ReportHandler {
+public:
+	virtual ~ReportHandler2() {}
+
+	// Invoked to signal about a change of the emulated LCD state. Use method Synth::getDisplayState to retrieve the actual data.
+	// This callback will not be invoked on further changes, until the client retrieves the LCD state.
+	virtual void onLCDStateUpdated() {}
+	// Invoked when the emulated MIDI MESSAGE LED changes state. The ledState parameter represents whether the LED is ON.
+	virtual void onMidiMessageLEDStateUpdated(bool /* ledState */) {}
+};
+
 class Synth {
 friend class DefaultMidiStreamParser;
+friend class Display;
 friend class MemoryRegion;
 friend class Part;
 friend class Partial;
@@ -177,7 +193,7 @@ private:
 	bool opened;
 	bool activated;
 
-	bool isDefaultReportHandler;
+	bool isDefaultReportHandler; // No longer used, retained for binary compatibility only.
 	ReportHandler *reportHandler;
 
 	PartialManager *partialManager;
@@ -227,7 +243,11 @@ private:
 
 	void printPartialUsage(Bit32u sampleOffset = 0);
 
-	void newTimbreSet(Bit8u partNum, Bit8u timbreGroup, Bit8u timbreNumber, const char patchName[]);
+	void rhythmNotePlayed() const;
+	void voicePartStateChanged(Bit8u partNum, bool activated) const;
+	void newTimbreSet(Bit8u partNum) const;
+	const char *getSoundGroupName(const Part *part) const;
+	const char *getSoundGroupName(Bit8u timbreGroup, Bit8u timbreNumber) const;
 	void printDebug(const char *fmt, ...);
 
 	// partNum should be 0..7 for Part 1..8, or 8 for Rhythm
@@ -290,6 +310,10 @@ public:
 	MT32EMU_EXPORT explicit Synth(ReportHandler *useReportHandler = NULL);
 	MT32EMU_EXPORT ~Synth();
 
+	// Sets an implementation of ReportHandler2 interface for reporting various errors, information and debug messages.
+	// If the argument is NULL, the default implementation is installed as a fallback.
+	MT32EMU_EXPORT_V(2.6) void setReportHandler2(ReportHandler2 *reportHandler2);
+
 	// Used to initialise the MT-32. Must be called before any other function.
 	// Returns true if initialization was successful, otherwise returns false.
 	// controlROMImage and pcmROMImage represent full Control and PCM ROM images for use by synth.
@@ -423,6 +447,22 @@ public:
 	// Returns current output gain factor for reverb wet output channels.
 	MT32EMU_EXPORT float getReverbOutputGain() const;
 
+	// Sets (or removes) an override for the current volume (output level) on a specific part.
+	// When the part volume is overridden, the MIDI controller Volume (7) on the MIDI channel this part is assigned to
+	// has no effect on the output level of this part. Similarly, the output level value set on this part via a SysEx that
+	// modifies the Patch temp structure is disregarded.
+	// To enable the override mode, argument volumeOverride should be in range 0..100, setting a value outside this range
+	// disables the previously set override, if any.
+	// Note: Setting volumeOverride to 0 mutes the part completely, meaning no sound is generated at all.
+	// This is unlike the behaviour of real devices - setting 0 volume on a part may leave it still producing
+	// sound at a very low level.
+	// Argument partNumber should be 0..7 for Part 1..8, or 8 for Rhythm.
+	MT32EMU_EXPORT_V(2.6) void setPartVolumeOverride(Bit8u partNumber, Bit8u volumeOverride);
+	// Returns the overridden volume previously set on a specific part; a value outside the range 0..100 means no override
+	// is currently in effect.
+	// Argument partNumber should be 0..7 for Part 1..8, or 8 for Rhythm.
+	MT32EMU_EXPORT_V(2.6) Bit8u getPartVolumeOverride(Bit8u partNumber) const;
+
 	// Swaps left and right output channels.
 	MT32EMU_EXPORT void setReversedStereoEnabled(bool enabled);
 	// Returns whether left and right output channels are swapped.
@@ -529,10 +569,53 @@ public:
 
 	// Returns name of the patch set on the specified part.
 	// Argument partNumber should be 0..7 for Part 1..8, or 8 for Rhythm.
+	// The returned value is a null-terminated string which is guaranteed to remain valid until the next call to one of render methods.
 	MT32EMU_EXPORT const char *getPatchName(Bit8u partNumber) const;
 
+	// Retrieves the name of the sound group the timbre identified by arguments timbreGroup and timbreNumber is associated with.
+	// Values 0-3 of timbreGroup correspond to the timbre banks GROUP A, GROUP B, MEMORY and RHYTHM.
+	// For all but the RHYTHM timbre bank, allowed values of timbreNumber are in range 0-63. The number of timbres
+	// contained in the RHYTHM bank depends on the used control ROM version.
+	// The argument soundGroupName must point to an array of at least 8 characters. The result is a null-terminated string.
+	// Returns whether the specified timbre has been found and the result written in soundGroupName.
+	MT32EMU_EXPORT_V(2.7) bool getSoundGroupName(char *soundGroupName, Bit8u timbreGroup, Bit8u timbreNumber) const;
+	// Retrieves the name of the timbre identified by arguments timbreGroup and timbreNumber.
+	// Values 0-3 of timbreGroup correspond to the timbre banks GROUP A, GROUP B, MEMORY and RHYTHM.
+	// For all but the RHYTHM timbre bank, allowed values of timbreNumber are in range 0-63. The number of timbres
+	// contained in the RHYTHM bank depends on the used control ROM version.
+	// The argument soundName must point to an array of at least 11 characters. The result is a null-terminated string.
+	// Returns whether the specified timbre has been found and the result written in soundName.
+	MT32EMU_EXPORT_V(2.7) bool getSoundName(char *soundName, Bit8u timbreGroup, Bit8u timbreNumber) const;
+
 	// Stores internal state of emulated synth into an array provided (as it would be acquired from hardware).
 	MT32EMU_EXPORT void readMemory(Bit32u addr, Bit32u len, Bit8u *data);
+
+	// Retrieves the current state of the emulated MT-32 display facilities.
+	// Typically, the state is updated during the rendering. When that happens, a related callback from ReportHandler2 is invoked.
+	// However, there might be no need to invoke this method after each update, e.g. when the render buffer is just a few milliseconds
+	// long.
+	// The argument targetBuffer must point to an array of at least 21 characters. The result is a null-terminated string.
+	// The optional argument narrowLCD enables a condensed representation of the displayed information in some cases. This is mainly
+	// intended to route the result to a hardware LCD that is only 16 characters wide. Automatic scrolling of longer strings
+	// is not supported.
+	// Returns whether the MIDI MESSAGE LED is ON and fills the targetBuffer parameter.
+	MT32EMU_EXPORT_V(2.6) bool getDisplayState(char *targetBuffer, bool narrowLCD = false) const;
+
+	// Resets the emulated LCD to the main mode (Master Volume). This has the same effect as pressing the Master Volume button
+	// while the display shows some other message. Useful for the new-gen devices as those require a special Display Reset SysEx
+	// to return to the main mode e.g. from showing a custom display message or a checksum error.
+	MT32EMU_EXPORT_V(2.6) void setMainDisplayMode();
+
+	// Permits to select an arbitrary display emulation model that does not necessarily match the actual behaviour implemented
+	// in the control ROM version being used.
+	// Invoking this method with the argument set to true forces emulation of the old-gen MT-32 display features.
+	// Otherwise, emulation of the new-gen devices is enforced (these include CM-32L and LAPC-I as if these were connected to an LCD).
+	MT32EMU_EXPORT_V(2.6) void setDisplayCompatibility(bool oldMT32CompatibilityEnabled);
+	// Returns whether the currently configured features of the emulated display are compatible with the old-gen MT-32 devices.
+	MT32EMU_EXPORT_V(2.6) bool isDisplayOldMT32Compatible() const;
+	// Returns whether the emulated display features configured by default depending on the actual control ROM version
+	// are compatible with the old-gen MT-32 devices.
+	MT32EMU_EXPORT_V(2.6) bool isDefaultDisplayOldMT32Compatible() const;
 }; // class Synth
 
 } // namespace MT32Emu
diff --git a/audio/softsynth/mt32/TVA.cpp b/audio/softsynth/mt32/TVA.cpp
index 35cee3ef9e0..e3f76181f16 100644
--- a/audio/softsynth/mt32/TVA.cpp
+++ b/audio/softsynth/mt32/TVA.cpp
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 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
@@ -99,7 +99,7 @@ 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, bool hasRingModQuirk) {
+static int calcBasicAmp(const Tables *tables, const Partial *partial, const MemParams::System *system, const TimbreParam::PartialParam *partialParam, Bit8u partVolume, const MemParams::RhythmTemp *rhythmTemp, int biasAmpSubtraction, int veloAmpSubtraction, Bit8u expression, bool hasRingModQuirk) {
 	int amp = 155;
 
 	if (!(hasRingModQuirk ? partial->isRingModulatingNoMix() : partial->isRingModulatingSlave())) {
@@ -107,7 +107,7 @@ static int calcBasicAmp(const Tables *tables, const Partial *partial, const MemP
 		if (amp < 0) {
 			return 0;
 		}
-		amp -= tables->levelToAmpSubtraction[patchTemp->outputLevel];
+		amp -= tables->levelToAmpSubtraction[partVolume];
 		if (amp < 0) {
 			return 0;
 		}
@@ -115,7 +115,7 @@ static int calcBasicAmp(const Tables *tables, const Partial *partial, const MemP
 		if (amp < 0) {
 			return 0;
 		}
-		if (rhythmTemp != nullptr) {
+		if (rhythmTemp != NULL) {
 			amp -= tables->levelToAmpSubtraction[rhythmTemp->outputLevel];
 			if (amp < 0) {
 				return 0;
@@ -154,7 +154,6 @@ static int calcKeyTimeSubtraction(Bit8u envTimeKeyfollow, int key) {
 void TVA::reset(const Part *newPart, const TimbreParam::PartialParam *newPartialParam, const MemParams::RhythmTemp *newRhythmTemp) {
 	part = newPart;
 	partialParam = newPartialParam;
-	patchTemp = newPart->getPatchTemp();
 	rhythmTemp = newRhythmTemp;
 
 	playing = true;
@@ -169,7 +168,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(), partial->getSynth()->controlROMFeatures->quirkRingModulationNoMix);
+	int newTarget = calcBasicAmp(tables, partial, system, partialParam, part->getVolume(), 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,7 +220,7 @@ 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(), partial->getSynth()->controlROMFeatures->quirkRingModulationNoMix);
+	int newTarget = calcBasicAmp(tables, partial, system, partialParam, part->getVolume(), rhythmTemp, biasAmpSubtraction, veloAmpSubtraction, part->getExpression(), partial->getSynth()->controlROMFeatures->quirkRingModulationNoMix);
 	newTarget += partialParam->tva.envLevel[3];
 
 	// Although we're in TVA_PHASE_SUSTAIN at this point, we cannot be sure that there is no active ramp at the moment.
@@ -229,7 +228,7 @@ void TVA::recalcSustain() {
 	// 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 actual current ramp value and correct the direction if necessary.
+	// 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
@@ -271,10 +270,10 @@ void TVA::nextPhase() {
 	}
 
 	bool allLevelsZeroFromNowOn = false;
-	if (!partial->getSynth()->controlROMFeatures->quirkTVAZeroEnvLevels && partialParam->tva.envLevel[3] == 0) {
+	if (partialParam->tva.envLevel[3] == 0) {
 		if (newPhase == TVA_PHASE_4) {
 			allLevelsZeroFromNowOn = true;
-		} else if (partialParam->tva.envLevel[2] == 0) {
+		} else if (!partial->getSynth()->controlROMFeatures->quirkTVAZeroEnvLevels && partialParam->tva.envLevel[2] == 0) {
 			if (newPhase == TVA_PHASE_3) {
 				allLevelsZeroFromNowOn = true;
 			} else if (partialParam->tva.envLevel[1] == 0) {
@@ -294,7 +293,7 @@ void TVA::nextPhase() {
 	int envPointIndex = phase;
 
 	if (!allLevelsZeroFromNowOn) {
-		newTarget = calcBasicAmp(tables, partial, system, partialParam, patchTemp, rhythmTemp, biasAmpSubtraction, veloAmpSubtraction, part->getExpression(), partial->getSynth()->controlROMFeatures->quirkRingModulationNoMix);
+		newTarget = calcBasicAmp(tables, partial, system, partialParam, part->getVolume(), rhythmTemp, biasAmpSubtraction, veloAmpSubtraction, part->getExpression(), partial->getSynth()->controlROMFeatures->quirkRingModulationNoMix);
 
 		if (newPhase == TVA_PHASE_SUSTAIN || newPhase == TVA_PHASE_RELEASE) {
 			if (partialParam->tva.envLevel[3] == 0) {
diff --git a/audio/softsynth/mt32/TVA.h b/audio/softsynth/mt32/TVA.h
index bf2f0078539..415909be826 100644
--- a/audio/softsynth/mt32/TVA.h
+++ b/audio/softsynth/mt32/TVA.h
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 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
@@ -67,7 +67,6 @@ private:
 
 	const Part *part;
 	const TimbreParam::PartialParam *partialParam;
-	const MemParams::PatchTemp *patchTemp;
 	const MemParams::RhythmTemp *rhythmTemp;
 
 	bool playing;
diff --git a/audio/softsynth/mt32/TVF.cpp b/audio/softsynth/mt32/TVF.cpp
index 921ba1a1a7e..47ce0a93655 100644
--- a/audio/softsynth/mt32/TVF.cpp
+++ b/audio/softsynth/mt32/TVF.cpp
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License as published by
diff --git a/audio/softsynth/mt32/TVF.h b/audio/softsynth/mt32/TVF.h
index 796e4d7414a..1b766e8ed63 100644
--- a/audio/softsynth/mt32/TVF.h
+++ b/audio/softsynth/mt32/TVF.h
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License as published by
diff --git a/audio/softsynth/mt32/TVP.cpp b/audio/softsynth/mt32/TVP.cpp
index 98d7bbad64e..9921f3a4d99 100644
--- a/audio/softsynth/mt32/TVP.cpp
+++ b/audio/softsynth/mt32/TVP.cpp
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 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
@@ -54,13 +54,32 @@ static Bit16u keyToPitchTable[] = {
 // 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;
+// In all hardware units we emulate, the main clock frequency of the MCU is 12MHz.
+// However, the MCU used in the 3rd-gen sound modules (like CM-500 and LAPC-N)
+// is significantly faster. Importantly, the software timer also works faster,
+// yet this fact has been seemingly missed. To be more specific, the software timer
+// ticks each 8 "state times", and 1 state time equals to 3 clock periods
+// for 8095 and 8098 but 2 clock periods for 80C198. That is, on MT-32 and CM-32L,
+// the software timer tick rate is 12,000,000 / 3 / 8 = 500kHz, but on the 3rd-gen
+// devices it's 12,000,000 / 2 / 8 = 750kHz instead.
+
+// For 1st- and 2nd-gen devices, the timer ticks at 500kHz. This is how much to increment
+// timeElapsed once 16 samples passes. We multiply by 16 to get rid of the fraction
+// and deal with just integers.
+static const int PROCESS_TIMER_TICKS_PER_SAMPLE_X16_1N2_GEN = (500000 << 4) / SAMPLE_RATE;
+// For 3rd-gen devices, the timer ticks at 750kHz. This is how much to increment
+// timeElapsed once 16 samples passes. We multiply by 16 to get rid of the fraction
+// and deal with just integers.
+static const int PROCESS_TIMER_TICKS_PER_SAMPLE_X16_3_GEN = (750000 << 4) / SAMPLE_RATE;
 
 TVP::TVP(const Partial *usePartial) :
-	partial(usePartial), system(&usePartial->getSynth()->mt32ram.system) {
-}
+	partial(usePartial),
+	system(&usePartial->getSynth()->mt32ram.system),
+	processTimerTicksPerSampleX16(
+		partial->getSynth()->controlROMFeatures->quirkFastPitchChanges
+		? PROCESS_TIMER_TICKS_PER_SAMPLE_X16_3_GEN
+		: PROCESS_TIMER_TICKS_PER_SAMPLE_X16_1N2_GEN)
+{}
 
 static Bit16s keyToPitch(unsigned int key) {
 	// We're using a table to do: return round_to_nearest_or_even((key - 60) * (4096.0 / 12.0))
@@ -90,7 +109,7 @@ static Bit32u calcBasePitch(const Partial *partial, const TimbreParam::PartialPa
 	basePitch += fineToPitch(patchTemp->patch.fineTune);
 
 	const ControlROMPCMStruct *controlROMPCMStruct = partial->getControlROMPCMStruct();
-	if (controlROMPCMStruct != nullptr) {
+	if (controlROMPCMStruct != NULL) {
 		basePitch += (Bit32s(controlROMPCMStruct->pitchMSB) << 8) | Bit32s(controlROMPCMStruct->pitchLSB);
 	} else {
 		if ((partialParam->wg.waveform & 1) == 0) {
@@ -301,7 +320,7 @@ void TVP::startDecay() {
 
 Bit16u TVP::nextPitch() {
 	// We emulate MCU software timer using these counter and processTimerIncrement variables.
-	// The value of nominalProcessTimerPeriod approximates the period in samples
+	// The value of NOMINAL_PROCESS_TIMER_PERIOD_SAMPLES 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.
@@ -309,7 +328,7 @@ Bit16u TVP::nextPitch() {
 		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;
+		processTimerIncrement = (processTimerTicksPerSampleX16 * counter) >> 4;
 		process();
 	}
 	counter--;
diff --git a/audio/softsynth/mt32/TVP.h b/audio/softsynth/mt32/TVP.h
index f5ef5474347..61bd2033e64 100644
--- a/audio/softsynth/mt32/TVP.h
+++ b/audio/softsynth/mt32/TVP.h
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License as published by
@@ -36,6 +36,7 @@ private:
 	const TimbreParam::PartialParam *partialParam;
 	const MemParams::PatchTemp *patchTemp;
 
+	const int processTimerTicksPerSampleX16;
 	int processTimerIncrement;
 	int counter;
 	Bit32u timeElapsed;
diff --git a/audio/softsynth/mt32/Tables.cpp b/audio/softsynth/mt32/Tables.cpp
index 143cd4c6d2a..dff042d2066 100644
--- a/audio/softsynth/mt32/Tables.cpp
+++ b/audio/softsynth/mt32/Tables.cpp
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License as published by
diff --git a/audio/softsynth/mt32/Tables.h b/audio/softsynth/mt32/Tables.h
index dd75f751b8f..2f90532153c 100644
--- a/audio/softsynth/mt32/Tables.h
+++ b/audio/softsynth/mt32/Tables.h
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License as published by
diff --git a/audio/softsynth/mt32/Types.h b/audio/softsynth/mt32/Types.h
index f9590d6a85e..12e4547500c 100644
--- a/audio/softsynth/mt32/Types.h
+++ b/audio/softsynth/mt32/Types.h
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License as published by
diff --git a/audio/softsynth/mt32/VersionTagging.cpp b/audio/softsynth/mt32/VersionTagging.cpp
index a621d544c45..0a3388f3bf9 100644
--- a/audio/softsynth/mt32/VersionTagging.cpp
+++ b/audio/softsynth/mt32/VersionTagging.cpp
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 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
@@ -23,8 +23,10 @@ extern "C" {
 // while for an application linked with a newer library version there will be no match.
 
 MT32EMU_EXPORT_V(2.5) extern const volatile char mt32emu_2_5 = 0;
+MT32EMU_EXPORT_V(2.6) extern const volatile char mt32emu_2_6 = 0;
+MT32EMU_EXPORT_V(2.7) extern const volatile char mt32emu_2_7 = 0;
 
-#if MT32EMU_VERSION_MAJOR > 2 || MT32EMU_VERSION_MINOR > 5
+#if MT32EMU_VERSION_MAJOR > 2 || MT32EMU_VERSION_MINOR > 7
 #error "Missing version tag definition for current library version"
 #endif
 }
diff --git a/audio/softsynth/mt32/VersionTagging.h b/audio/softsynth/mt32/VersionTagging.h
index ec63206a286..df211f3c065 100644
--- a/audio/softsynth/mt32/VersionTagging.h
+++ b/audio/softsynth/mt32/VersionTagging.h
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License as published by
@@ -20,25 +20,39 @@
 
 #include "globals.h"
 
-// This is intended to implement a simple check of a shared library version in runtime. Sadly, per-symbol versioning
-// is unavailable on many platforms, and even where it is, it's still not too easy to maintain for a C++ library.
-// Therefore, the goal here is just to ensure that the client application quickly bails out when attempted to run
-// with an older version of shared library, as well as to produce a more readable error message indicating a version mismatch
-// rather than a report about some missing symbols with unreadable mangled names.
-// This is an optional feature, since it adds some minor burden to both the library and client applications code,
-// albeit it is ought to work on platforms that do not implement symbol versioning.
+/* This is intended to implement a simple check of a shared library version in runtime. Sadly, per-symbol versioning
+ * is unavailable on many platforms, and even where it is, it's still not too easy to maintain for a C++ library.
+ * Therefore, the goal here is just to ensure that the client application quickly bails out when attempted to run
+ * with an older version of shared library, as well as to produce a more readable error message indicating a version mismatch
+ * rather than a report about some missing symbols with unreadable mangled names.
+ * This is an optional feature, since it adds some minor burden to both the library and client applications code,
+ * albeit it is ought to work on platforms that do not implement symbol versioning.
+ */
 
 #define MT32EMU_REALLY_BUILD_VERSION_TAG(major, minor) mt32emu_ ## major ## _ ## minor
-// This macro expansion step permits resolution the actual version numbers.
+/* This macro expansion step permits resolution the actual version numbers. */
 #define MT32EMU_BUILD_VERSION_TAG(major, minor) MT32EMU_REALLY_BUILD_VERSION_TAG(major, minor)
 #define MT32EMU_VERSION_TAG MT32EMU_BUILD_VERSION_TAG(MT32EMU_VERSION_MAJOR, MT32EMU_VERSION_MINOR)
 
+#if defined(__cplusplus)
+
 extern "C" {
 MT32EMU_EXPORT extern const volatile char MT32EMU_VERSION_TAG;
 }
 // This pulls the external reference in yet prevents it from being optimised out.
 static const volatile char mt32emu_version_tag = MT32EMU_VERSION_TAG;
 
+#else
+
+static void mt32emu_refer_version_tag(void) {
+	MT32EMU_EXPORT extern const volatile char MT32EMU_VERSION_TAG;
+	(void)MT32EMU_VERSION_TAG;
+}
+
+static void (*const volatile mt32emu_refer_version_tag_ref)(void) = mt32emu_refer_version_tag;
+
+#endif
+
 #undef MT32EMU_REALLY_BUILD_VERSION_TAG
 #undef MT32EMU_BUILD_VERSION_TAG
 #undef MT32EMU_VERSION_TAG
diff --git a/audio/softsynth/mt32/c_interface/c_interface.cpp b/audio/softsynth/mt32/c_interface/c_interface.cpp
index ae301616002..4c7706be8a6 100644
--- a/audio/softsynth/mt32/c_interface/c_interface.cpp
+++ b/audio/softsynth/mt32/c_interface/c_interface.cpp
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License as published by
@@ -39,11 +39,11 @@ struct SamplerateConversionState {
 	SampleRateConverter *src;
 };
 
-static mt32emu_service_version getSynthVersionID(mt32emu_service_i) {
+static mt32emu_service_version MT32EMU_C_CALL getSynthVersionID(mt32emu_service_i) {
 	return MT32EMU_SERVICE_VERSION_CURRENT;
 }
 
-static const mt32emu_service_i_v4 SERVICE_VTABLE = {
+static const mt32emu_service_i_v6 SERVICE_VTABLE = {
 	getSynthVersionID,
 	mt32emu_get_supported_report_handler_version,
 	mt32emu_get_supported_midi_receiver_version,
@@ -127,13 +127,22 @@ static const mt32emu_service_i_v4 SERVICE_VTABLE = {
 	mt32emu_identify_rom_file,
 	mt32emu_merge_and_add_rom_data,
 	mt32emu_merge_and_add_rom_files,
-	mt32emu_add_machine_rom_file
+	mt32emu_add_machine_rom_file,
+	mt32emu_get_display_state,
+	mt32emu_set_main_display_mode,
+	mt32emu_set_display_compatibility,
+	mt32emu_is_display_old_mt32_compatible,
+	mt32emu_is_default_display_old_mt32_compatible,
+	mt32emu_set_part_volume_override,
+	mt32emu_get_part_volume_override,
+	mt32emu_get_sound_group_name,
+	mt32emu_get_sound_name
 };
 
 } // namespace MT32Emu
 
 struct mt32emu_data {
-	ReportHandler *reportHandler;
+	ReportHandler2 *reportHandler;
 	Synth *synth;
 	const ROMImage *controlROMImage;
 	const ROMImage *pcmROMImage;
@@ -147,126 +156,145 @@ struct mt32emu_data {
 
 namespace MT32Emu {
 
-class DelegatingReportHandlerAdapter : public ReportHandler {
+class DelegatingReportHandlerAdapter : public ReportHandler2 {
 public:
 	DelegatingReportHandlerAdapter(mt32emu_report_handler_i useReportHandler, void *useInstanceData) :
 		delegate(useReportHandler), instanceData(useInstanceData) {}
 
-protected:
+private:
 	const mt32emu_report_handler_i delegate;
 	void * const instanceData;
 
-private:
-	void printDebug(const char *fmt, va_list list) override {
-		if (delegate.v0->printDebug == nullptr) {
+	bool isVersionLess(mt32emu_report_handler_version versionID) {
+		return delegate.v0->getVersionID(delegate) < versionID;
+	}
+
+	void printDebug(const char *fmt, va_list list) {
+		if (delegate.v0->printDebug == NULL) {
 			ReportHandler::printDebug(fmt, list);
 		} else {
 			delegate.v0->printDebug(instanceData, fmt, list);
 		}
 	}
 
-	void onErrorControlROM() override {
-		if (delegate.v0->onErrorControlROM == nullptr) {
+	void onErrorControlROM() {
+		if (delegate.v0->onErrorControlROM == NULL) {
 			ReportHandler::onErrorControlROM();
 		} else {
 			delegate.v0->onErrorControlROM(instanceData);
 		}
 	}
 
-	void onErrorPCMROM() override {
-		if (delegate.v0->onErrorPCMROM == nullptr) {
+	void onErrorPCMROM() {
+		if (delegate.v0->onErrorPCMROM == NULL) {
 			ReportHandler::onErrorPCMROM();
 		} else {
 			delegate.v0->onErrorPCMROM(instanceData);
 		}
 	}
 
-	void showLCDMessage(const char *message) override {
-		if (delegate.v0->showLCDMessage == nullptr) {
+	void showLCDMessage(const char *message) {
+		if (delegate.v0->showLCDMessage == NULL) {
 			ReportHandler::showLCDMessage(message);
 		} else {
 			delegate.v0->showLCDMessage(instanceData, message);
 		}
 	}
 
-	void onMIDIMessagePlayed() override {
-		if (delegate.v0->onMIDIMessagePlayed == nullptr) {
+	void onMIDIMessagePlayed() {
+		if (delegate.v0->onMIDIMessagePlayed == NULL) {
 			ReportHandler::onMIDIMessagePlayed();
 		} else {
 			delegate.v0->onMIDIMessagePlayed(instanceData);
 		}
 	}
 
-	bool onMIDIQueueOverflow() override {
-		if (delegate.v0->onMIDIQueueOverflow == nullptr) {
+	bool onMIDIQueueOverflow() {
+		if (delegate.v0->onMIDIQueueOverflow == NULL) {
 			return ReportHandler::onMIDIQueueOverflow();
 		}
 		return delegate.v0->onMIDIQueueOverflow(instanceData) != MT32EMU_BOOL_FALSE;
 	}
 
-	void onMIDISystemRealtime(Bit8u systemRealtime) override {
-		if (delegate.v0->onMIDISystemRealtime == nullptr) {
+	void onMIDISystemRealtime(Bit8u systemRealtime) {
+		if (delegate.v0->onMIDISystemRealtime == NULL) {
 			ReportHandler::onMIDISystemRealtime(systemRealtime);
 		} else {
 			delegate.v0->onMIDISystemRealtime(instanceData, systemRealtime);
 		}
 	}
 
-	void onDeviceReset() override {
-		if (delegate.v0->onDeviceReset == nullptr) {
+	void onDeviceReset() {
+		if (delegate.v0->onDeviceReset == NULL) {
 			ReportHandler::onDeviceReset();
 		} else {
 			delegate.v0->onDeviceReset(instanceData);
 		}
 	}
 
-	void onDeviceReconfig() override {
-		if (delegate.v0->onDeviceReconfig == nullptr) {
+	void onDeviceReconfig() {
+		if (delegate.v0->onDeviceReconfig == NULL) {
 			ReportHandler::onDeviceReconfig();
 		} else {
 			delegate.v0->onDeviceReconfig(instanceData);
 		}
 	}
 
-	void onNewReverbMode(Bit8u mode) override {
-		if (delegate.v0->onNewReverbMode == nullptr) {
+	void onNewReverbMode(Bit8u mode) {
+		if (delegate.v0->onNewReverbMode == NULL) {
 			ReportHandler::onNewReverbMode(mode);
 		} else {
 			delegate.v0->onNewReverbMode(instanceData, mode);
 		}
 	}
 
-	void onNewReverbTime(Bit8u time) override {
-		if (delegate.v0->onNewReverbTime == nullptr) {
+	void onNewReverbTime(Bit8u time) {
+		if (delegate.v0->onNewReverbTime == NULL) {
 			ReportHandler::onNewReverbTime(time);
 		} else {
 			delegate.v0->onNewReverbTime(instanceData, time);
 		}
 	}
 
-	void onNewReverbLevel(Bit8u level) override {
-		if (delegate.v0->onNewReverbLevel == nullptr) {
+	void onNewReverbLevel(Bit8u level) {
+		if (delegate.v0->onNewReverbLevel == NULL) {
 			ReportHandler::onNewReverbLevel(level);
 		} else {
 			delegate.v0->onNewReverbLevel(instanceData, level);
 		}
 	}
 
-	void onPolyStateChanged(Bit8u partNum) override {
-		if (delegate.v0->onPolyStateChanged == nullptr) {
+	void onPolyStateChanged(Bit8u partNum) {
+		if (delegate.v0->onPolyStateChanged == NULL) {
 			ReportHandler::onPolyStateChanged(partNum);
 		} else {
 			delegate.v0->onPolyStateChanged(instanceData, partNum);
 		}
 	}
 
-	void onProgramChanged(Bit8u partNum, const char *soundGroupName, const char *patchName) override {
-		if (delegate.v0->onProgramChanged == nullptr) {
+	void onProgramChanged(Bit8u partNum, const char *soundGroupName, const char *patchName) {
+		if (delegate.v0->onProgramChanged == NULL) {
 			ReportHandler::onProgramChanged(partNum, soundGroupName, patchName);
 		} else {
 			delegate.v0->onProgramChanged(instanceData, partNum, soundGroupName, patchName);
 		}
 	}
+
+	void onLCDStateUpdated() {
+		if (isVersionLess(MT32EMU_REPORT_HANDLER_VERSION_1) || delegate.v1->onLCDStateUpdated == NULL) {
+			ReportHandler2::onLCDStateUpdated();
+		} else {
+			delegate.v1->onLCDStateUpdated(instanceData);
+		}
+	}
+
+	void onMidiMessageLEDStateUpdated(bool ledState) {
+		if (isVersionLess(MT32EMU_REPORT_HANDLER_VERSION_1) || delegate.v1->onMidiMessageLEDStateUpdated == NULL) {
+			ReportHandler2::onMidiMessageLEDStateUpdated(ledState);
+		} else {
+			delegate.v1->onMidiMessageLEDStateUpdated(instanceData, ledState ? MT32EMU_BOOL_TRUE : MT32EMU_BOOL_FALSE);
+		}
+	}
 };
 
 class DelegatingMidiStreamParser : public DefaultMidiStreamParser {
@@ -279,24 +307,24 @@ protected:
 	void *instanceData;
 
 private:
-	void handleShortMessage(const Bit32u message) override {
-		if (delegate.v0->handleShortMessage == nullptr) {
+	void handleShortMessage(const Bit32u message) {
+		if (delegate.v0->handleShortMessage == NULL) {
 			DefaultMidiStreamParser::handleShortMessage(message);
 		} else {
 			delegate.v0->handleShortMessage(instanceData, message);
 		}
 	}
 
-	void handleSysex(const Bit8u *stream, const Bit32u length) override {
-		if (delegate.v0->handleSysex == nullptr) {
+	void handleSysex(const Bit8u *stream, const Bit32u length) {
+		if (delegate.v0->handleSysex == NULL) {
 			DefaultMidiStreamParser::handleSysex(stream, length);
 		} else {
 			delegate.v0->handleSysex(instanceData, stream, length);
 		}
 	}
 
-	void handleSystemRealtimeMessage(const Bit8u realtime) override {
-		if (delegate.v0->handleSystemRealtimeMessage == nullptr) {
+	void handleSystemRealtimeMessage(const Bit8u realtime) {
+		if (delegate.v0->handleSystemRealtimeMessage == NULL) {
 			DefaultMidiStreamParser::handleSystemRealtimeMessage(realtime);
 		} else {
 			delegate.v0->handleSystemRealtimeMessage(instanceData, realtime);
@@ -305,23 +333,23 @@ private:
 };
 
 static void fillROMInfo(mt32emu_rom_info *rom_info, const ROMInfo *controlROMInfo, const ROMInfo *pcmROMInfo) {
-	if (controlROMInfo != nullptr) {
+	if (controlROMInfo != NULL) {
 		rom_info->control_rom_id = controlROMInfo->shortName;
 		rom_info->control_rom_description = controlROMInfo->description;
 		rom_info->control_rom_sha1_digest = controlROMInfo->sha1Digest;
 	} else {
-		rom_info->control_rom_id = nullptr;
-		rom_info->control_rom_description = nullptr;
-		rom_info->control_rom_sha1_digest = nullptr;
+		rom_info->control_rom_id = NULL;
+		rom_info->control_rom_description = NULL;
+		rom_info->control_rom_sha1_digest = NULL;
 	}
-	if (pcmROMInfo != nullptr) {
+	if (pcmROMInfo != NULL) {
 		rom_info->pcm_rom_id = pcmROMInfo->shortName;
 		rom_info->pcm_rom_description = pcmROMInfo->description;
 		rom_info->pcm_rom_sha1_digest = pcmROMInfo->sha1Digest;
 	} else {
-		rom_info->pcm_rom_id = nullptr;
-		rom_info->pcm_rom_description = nullptr;
-		rom_info->pcm_rom_sha1_digest = nullptr;
+		rom_info->pcm_rom_id = NULL;
+		rom_info->pcm_rom_description = NULL;
+		rom_info->pcm_rom_sha1_digest = NULL;
 	}
 }
 
@@ -331,28 +359,28 @@ static const MachineConfiguration *findMachineConfiguration(const char *machine_
 	for (Bit32u i = 0; i < configurationCount; i++) {
 		if (!strcmp(configurations[i]->getMachineID(), machine_id)) return configurations[i];
 	}
-	return nullptr;
+	return NULL;
 }
 
 static mt32emu_return_code identifyROM(mt32emu_rom_info *rom_info, File *romFile, const char *machineID) {
 	const ROMInfo *romInfo;
-	if (machineID == nullptr) {
+	if (machineID == NULL) {
 		romInfo = ROMInfo::getROMInfo(romFile);
 	} else {
 		const MachineConfiguration *configuration = findMachineConfiguration(machineID);
-		if (configuration == nullptr) {
-			fillROMInfo(rom_info, nullptr, nullptr);
+		if (configuration == NULL) {
+			fillROMInfo(rom_info, NULL, NULL);
 			return MT32EMU_RC_MACHINE_NOT_IDENTIFIED;
 		}
 		romInfo = ROMInfo::getROMInfo(romFile, configuration->getCompatibleROMInfos());
 	}
-	if (romInfo == nullptr) {
-		fillROMInfo(rom_info, nullptr, nullptr);
+	if (romInfo == NULL) {
+		fillROMInfo(rom_info, NULL, NULL);
 		return MT32EMU_RC_ROM_NOT_IDENTIFIED;
 	}
-	if (romInfo->type == ROMInfo::Control) fillROMInfo(rom_info, romInfo, nullptr);
-	else if (romInfo->type == ROMInfo::PCM) fillROMInfo(rom_info, nullptr, romInfo);
-	else fillROMInfo(rom_info, nullptr, nullptr);
+	if (romInfo->type == ROMInfo::Control) fillROMInfo(rom_info, romInfo, NULL);
+	else if (romInfo->type == ROMInfo::PCM) fillROMInfo(rom_info, NULL, romInfo);
+	else fillROMInfo(rom_info, NULL, NULL);
 	return MT32EMU_RC_OK;
 }
 
@@ -366,10 +394,10 @@ static bool isROMInfoCompatible(const MachineConfiguration *machineConfiguration
 }
 
 static mt32emu_return_code replaceOrMergeROMImage(const ROMImage *&contextROMImage, const ROMImage *newROMImage, const MachineConfiguration *machineConfiguration, mt32emu_return_code addedFullROM, mt32emu_return_code addedPartialROM) {
-	if (contextROMImage != nullptr) {
-		if (machineConfiguration != nullptr) {
+	if (contextROMImage != NULL) {
+		if (machineConfiguration != NULL) {
 			const ROMImage *mergedROMImage = ROMImage::mergeROMImages(contextROMImage, newROMImage);
-			if (mergedROMImage != nullptr) {
+			if (mergedROMImage != NULL) {
 				if (newROMImage->isFileUserProvided()) delete newROMImage->getFile();
 				ROMImage::freeROMImage(newROMImage);
 				if (contextROMImage->isFileUserProvided()) delete contextROMImage->getFile();
@@ -391,16 +419,16 @@ static mt32emu_return_code replaceOrMergeROMImage(const ROMImage *&contextROMIma
 	return newROMImage->getROMInfo()->pairType == ROMInfo::Full ? addedFullROM: addedPartialROM;
 }
 
-static mt32emu_return_code addROMFiles(mt32emu_data *data, File *file1, File *file2 = nullptr, const MachineConfiguration *machineConfiguration = nullptr) {
+static mt32emu_return_code addROMFiles(mt32emu_data *data, File *file1, File *file2 = NULL, const MachineConfiguration *machineConfiguration = NULL) {
 	const ROMImage *romImage;
-	if (machineConfiguration != nullptr) {
+	if (machineConfiguration != NULL) {
 		romImage = ROMImage::makeROMImage(file1, machineConfiguration->getCompatibleROMInfos());
 	} else {
-		romImage = file2 == nullptr ? ROMImage::makeROMImage(file1, ROMInfo::getFullROMInfos()) : ROMImage::makeROMImage(file1, file2);
+		romImage = file2 == NULL ? ROMImage::makeROMImage(file1, ROMInfo::getFullROMInfos()) : ROMImage::makeROMImage(file1, file2);
 	}
-	if (romImage == nullptr) return MT32EMU_RC_ROMS_NOT_PAIRABLE;
+	if (romImage == NULL) return MT32EMU_RC_ROMS_NOT_PAIRABLE;
 	const ROMInfo *info = romImage->getROMInfo();
-	if (info == nullptr) {
+	if (info == NULL) {
 		ROMImage::freeROMImage(romImage);
 		return MT32EMU_RC_ROM_NOT_IDENTIFIED;
 	}
@@ -426,7 +454,7 @@ static mt32emu_return_code createFileStream(const char *filename, FileStream *&f
 		return MT32EMU_RC_OK;
 	}
 	delete fileStream;
-	fileStream = nullptr;
+	fileStream = NULL;
 	return rc;
 }
 
@@ -436,157 +464,162 @@ static mt32emu_return_code createFileStream(const char *filename, FileStream *&f
 
 extern "C" {
 
-mt32emu_service_i mt32emu_get_service_i() {
+mt32emu_service_i MT32EMU_C_CALL mt32emu_get_service_i() {
 	mt32emu_service_i i;
-	i.v4 = &SERVICE_VTABLE;
+	i.v6 = &SERVICE_VTABLE;
 	return i;
 }
 
-mt32emu_report_handler_version mt32emu_get_supported_report_handler_version() {
+mt32emu_report_handler_version MT32EMU_C_CALL mt32emu_get_supported_report_handler_version() {
 	return MT32EMU_REPORT_HANDLER_VERSION_CURRENT;
 }
 
-mt32emu_midi_receiver_version mt32emu_get_supported_midi_receiver_version() {
+mt32emu_midi_receiver_version MT32EMU_C_CALL mt32emu_get_supported_midi_receiver_version() {
 	return MT32EMU_MIDI_RECEIVER_VERSION_CURRENT;
 }
 
-mt32emu_bit32u mt32emu_get_library_version_int() {
+mt32emu_bit32u MT32EMU_C_CALL mt32emu_get_library_version_int() {
 	return Synth::getLibraryVersionInt();
 }
 
-const char *mt32emu_get_library_version_string() {
+const char * MT32EMU_C_CALL mt32emu_get_library_version_string() {
 	return Synth::getLibraryVersionString();
 }
 
-mt32emu_bit32u mt32emu_get_stereo_output_samplerate(const mt32emu_analog_output_mode analog_output_mode) {
+mt32emu_bit32u MT32EMU_C_CALL mt32emu_get_stereo_output_samplerate(const mt32emu_analog_output_mode analog_output_mode) {
 	return Synth::getStereoOutputSampleRate(static_cast<AnalogOutputMode>(analog_output_mode));
 }
 
-mt32emu_analog_output_mode mt32emu_get_best_analog_output_mode(const double target_samplerate) {
+mt32emu_analog_output_mode MT32EMU_C_CALL mt32emu_get_best_analog_output_mode(const double target_samplerate) {
 	return mt32emu_analog_output_mode(SampleRateConverter::getBestAnalogOutputMode(target_samplerate));
 }
 
-size_t mt32emu_get_machine_ids(const char **machine_ids, size_t machine_ids_size) {
+size_t MT32EMU_C_CALL mt32emu_get_machine_ids(const char **machine_ids, size_t machine_ids_size) {
 	Bit32u configurationCount;
 	const MachineConfiguration * const *configurations = MachineConfiguration::getAllMachineConfigurations(&configurationCount);
-	if (machine_ids != nullptr) {
+	if (machine_ids != NULL) {
 		for (Bit32u i = 0; i < machine_ids_size; i++) {
-			machine_ids[i] = i < configurationCount ? configurations[i]->getMachineID() : nullptr;
+			machine_ids[i] = i < configurationCount ? configurations[i]->getMachineID() : NULL;
 		}
 	}
 	return configurationCount;
 }
 
-size_t mt32emu_get_rom_ids(const char **rom_ids, size_t rom_ids_size, const char *machine_id) {
+size_t MT32EMU_C_CALL mt32emu_get_rom_ids(const char **rom_ids, size_t rom_ids_size, const char *machine_id) {
 	const ROMInfo * const *romInfos;
 	Bit32u romCount;
-	if (machine_id != nullptr) {
+	if (machine_id != NULL) {
 		const MachineConfiguration *configuration = findMachineConfiguration(machine_id);
-		if (configuration != nullptr) {
+		if (configuration != NULL) {
 			romInfos = configuration->getCompatibleROMInfos(&romCount);
 		} else {
-			romInfos = nullptr;
+			romInfos = NULL;
 			romCount = 0U;
 		}
 	} else {
 		romInfos = ROMInfo::getAllROMInfos(&romCount);
 	}
-	if (rom_ids != nullptr) {
+	if (rom_ids != NULL) {
 		for (size_t i = 0; i < rom_ids_size; i++) {
-			rom_ids[i] = i < romCount ? romInfos[i]->shortName : nullptr;
+			rom_ids[i] = i < romCount ? romInfos[i]->shortName : NULL;
 		}
 	}
 	return romCount;
 }
 
-mt32emu_return_code mt32emu_identify_rom_data(mt32emu_rom_info *rom_info, const mt32emu_bit8u *data, size_t data_size, const char *machine_id) {
+mt32emu_return_code MT32EMU_C_CALL mt32emu_identify_rom_data(mt32emu_rom_info *rom_info, const mt32emu_bit8u *data, size_t data_size, const char *machine_id) {
 	ArrayFile romFile = ArrayFile(data, data_size);
 	return identifyROM(rom_info, &romFile, machine_id);
 }
 
-mt32emu_return_code mt32emu_identify_rom_file(mt32emu_rom_info *rom_info, const char *filename, const char *machine_id) {
+mt32emu_return_code MT32EMU_C_CALL mt32emu_identify_rom_file(mt32emu_rom_info *rom_info, const char *filename, const char *machine_id) {
 	FileStream *fs;
 	mt32emu_return_code rc = createFileStream(filename, fs);
-	if (fs == nullptr) return rc;
+	if (fs == NULL) return rc;
 	rc = identifyROM(rom_info, fs, machine_id);
 	delete fs;
 	return rc;
 }
 
-mt32emu_context mt32emu_create_context(mt32emu_report_handler_i report_handler, void *instance_data) {
+mt32emu_context MT32EMU_C_CALL mt32emu_create_context(mt32emu_report_handler_i report_handler, void *instance_data) {
 	mt32emu_data *data = new mt32emu_data;
-	data->reportHandler = (report_handler.v0 != nullptr) ? new DelegatingReportHandlerAdapter(report_handler, instance_data) : new ReportHandler;
-	data->synth = new Synth(data->reportHandler);
+	data->synth = new Synth;
+	if (report_handler.v0 != NULL) {
+		data->reportHandler = new DelegatingReportHandlerAdapter(report_handler, instance_data);
+		data->synth->setReportHandler2(data->reportHandler);
+	} else {
+		data->reportHandler = NULL;
+	}
 	data->midiParser = new DefaultMidiStreamParser(*data->synth);
-	data->controlROMImage = nullptr;
-	data->pcmROMImage = nullptr;
+	data->controlROMImage = NULL;
+	data->pcmROMImage = NULL;
 	data->partialCount = DEFAULT_MAX_PARTIALS;
 	data->analogOutputMode = AnalogOutputMode_COARSE;
 
 	data->srcState = new SamplerateConversionState;
 	data->srcState->outputSampleRate = 0.0;
 	data->srcState->srcQuality = SamplerateConversionQuality_GOOD;
-	data->srcState->src = nullptr;
+	data->srcState->src = NULL;
 
 	return data;
 }
 
-void mt32emu_free_context(mt32emu_context data) {
-	if (data == nullptr) return;
+void MT32EMU_C_CALL mt32emu_free_context(mt32emu_context data) {
+	if (data == NULL) return;
 
 	delete data->srcState->src;
-	data->srcState->src = nullptr;
+	data->srcState->src = NULL;
 	delete data->srcState;
-	data->srcState = nullptr;
+	data->srcState = NULL;
 
-	if (data->controlROMImage != nullptr) {
+	if (data->controlROMImage != NULL) {
 		if (data->controlROMImage->isFileUserProvided()) delete data->controlROMImage->getFile();
 		ROMImage::freeROMImage(data->controlROMImage);
-		data->controlROMImage = nullptr;
+		data->controlROMImage = NULL;
 	}
-	if (data->pcmROMImage != nullptr) {
+	if (data->pcmROMImage != NULL) {
 		if (data->pcmROMImage->isFileUserProvided()) delete data->pcmROMImage->getFile();
 		ROMImage::freeROMImage(data->pcmROMImage);
-		data->pcmROMImage = nullptr;
+		data->pcmROMImage = NULL;
 	}
 	delete data->midiParser;
-	data->midiParser = nullptr;
+	data->midiParser = NULL;
 	delete data->synth;
-	data->synth = nullptr;
+	data->synth = NULL;
 	delete data->reportHandler;
-	data->reportHandler = nullptr;
+	data->reportHandler = NULL;
 	delete data;
 }
 
-mt32emu_return_code mt32emu_add_rom_data(mt32emu_context context, const mt32emu_bit8u *data, size_t data_size, const mt32emu_sha1_digest *sha1_digest) {
-	if (sha1_digest == nullptr) return addROMFiles(context, new ArrayFile(data, data_size));
+mt32emu_return_code MT32EMU_C_CALL mt32emu_add_rom_data(mt32emu_context context, const mt32emu_bit8u *data, size_t data_size, const mt32emu_sha1_digest *sha1_digest) {
+	if (sha1_digest == NULL) return addROMFiles(context, new ArrayFile(data, data_size));
 	return addROMFiles(context, new ArrayFile(data, data_size, *sha1_digest));
 }
 
-mt32emu_return_code mt32emu_add_rom_file(mt32emu_context context, const char *filename) {
+mt32emu_return_code MT32EMU_C_CALL mt32emu_add_rom_file(mt32emu_context context, const char *filename) {
 	FileStream *fs;
 	mt32emu_return_code rc = createFileStream(filename, fs);
-	if (fs != nullptr) rc = addROMFiles(context, fs);
+	if (fs != NULL) rc = addROMFiles(context, fs);
 	if (rc <= MT32EMU_RC_OK) delete fs;
 	return rc;
 }
 
-mt32emu_return_code mt32emu_merge_and_add_rom_data(mt32emu_context context, const mt32emu_bit8u *part1_data, size_t part1_data_size, const mt32emu_sha1_digest *part1_sha1_digest, const mt32emu_bit8u *part2_data, size_t part2_data_size, const mt32emu_sha1_digest *part2_sha1_digest) {
-	ArrayFile *file1 = part1_sha1_digest == nullptr ? new ArrayFile(part1_data, part1_data_size) : new ArrayFile(part1_data, part1_data_size, *part1_sha1_digest);
-	ArrayFile *file2 = part2_sha1_digest == nullptr ? new ArrayFile(part2_data, part2_data_size) : new ArrayFile(part2_data, part2_data_size, *part2_sha1_digest);
+mt32emu_return_code MT32EMU_C_CALL mt32emu_merge_and_add_rom_data(mt32emu_context context, const mt32emu_bit8u *part1_data, size_t part1_data_size, const mt32emu_sha1_digest *part1_sha1_digest, const mt32emu_bit8u *part2_data, size_t part2_data_size, const mt32emu_sha1_digest *part2_sha1_digest) {
+	ArrayFile *file1 = part1_sha1_digest == NULL ? new ArrayFile(part1_data, part1_data_size) : new ArrayFile(part1_data, part1_data_size, *part1_sha1_digest);
+	ArrayFile *file2 = part2_sha1_digest == NULL ? new ArrayFile(part2_data, part2_data_size) : new ArrayFile(part2_data, part2_data_size, *part2_sha1_digest);
 	mt32emu_return_code rc = addROMFiles(context, file1, file2);
 	delete file1;
 	delete file2;
 	return rc;
 }
 
-mt32emu_return_code mt32emu_merge_and_add_rom_files(mt32emu_context context, const char *part1_filename, const char *part2_filename) {
+mt32emu_return_code MT32EMU_C_CALL mt32emu_merge_and_add_rom_files(mt32emu_context context, const char *part1_filename, const char *part2_filename) {
 	FileStream *fs1;
 	mt32emu_return_code rc = createFileStream(part1_filename, fs1);
-	if (fs1 != nullptr) {
+	if (fs1 != NULL) {
 		FileStream *fs2;
 		rc = createFileStream(part2_filename, fs2);
-		if (fs2 != nullptr) {
+		if (fs2 != NULL) {
 			rc = addROMFiles(context, fs1, fs2);
 			delete fs2;
 		}
@@ -595,50 +628,50 @@ mt32emu_return_code mt32emu_merge_and_add_rom_files(mt32emu_context context, con
 	return rc;
 }
 
-mt32emu_return_code mt32emu_add_machine_rom_file(mt32emu_context context, const char *machine_id, const char *filename) {
+mt32emu_return_code MT32EMU_C_CALL mt32emu_add_machine_rom_file(mt32emu_context context, const char *machine_id, const char *filename) {
 	const MachineConfiguration *machineConfiguration = findMachineConfiguration(machine_id);
-	if (machineConfiguration == nullptr) return MT32EMU_RC_MACHINE_NOT_IDENTIFIED;
+	if (machineConfiguration == NULL) return MT32EMU_RC_MACHINE_NOT_IDENTIFIED;
 
 	FileStream *fs;
 	mt32emu_return_code rc = createFileStream(filename, fs);
-	if (fs == nullptr) return rc;
-	rc = addROMFiles(context, fs, nullptr, machineConfiguration);
+	if (fs == NULL) return rc;
+	rc = addROMFiles(context, fs, NULL, machineConfiguration);
 	if (rc <= MT32EMU_RC_OK) delete fs;
 	return rc;
 }
 
-void mt32emu_get_rom_info(mt32emu_const_context context, mt32emu_rom_info *rom_info) {
-	const ROMInfo *controlROMInfo = context->controlROMImage == nullptr ? nullptr : context->controlROMImage->getROMInfo();
-	const ROMInfo *pcmROMInfo = context->pcmROMImage == nullptr ? nullptr : context->pcmROMImage->getROMInfo();
+void MT32EMU_C_CALL mt32emu_get_rom_info(mt32emu_const_context context, mt32emu_rom_info *rom_info) {
+	const ROMInfo *controlROMInfo = context->controlROMImage == NULL ? NULL : context->controlROMImage->getROMInfo();
+	const ROMInfo *pcmROMInfo = context->pcmROMImage == NULL ? NULL : context->pcmROMImage->getROMInfo();
 	fillROMInfo(rom_info, controlROMInfo, pcmROMInfo);
 }
 
-void mt32emu_set_partial_count(mt32emu_context context, const mt32emu_bit32u partial_count) {
+void MT32EMU_C_CALL mt32emu_set_partial_count(mt32emu_context context, const mt32emu_bit32u partial_count) {
 	context->partialCount = partial_count;
 }
 
-void mt32emu_set_analog_output_mode(mt32emu_context context, const mt32emu_analog_output_mode analog_output_mode) {
+void MT32EMU_C_CALL mt32emu_set_analog_output_mode(mt32emu_context context, const mt32emu_analog_output_mode analog_output_mode) {
 	context->analogOutputMode = static_cast<AnalogOutputMode>(analog_output_mode);
 }
 
-void mt32emu_set_stereo_output_samplerate(mt32emu_context context, const double samplerate) {
+void MT32EMU_C_CALL mt32emu_set_stereo_output_samplerate(mt32emu_context context, const double samplerate) {
 	context->srcState->outputSampleRate = SampleRateConverter::getSupportedOutputSampleRate(samplerate);
 }
 
-void mt32emu_set_samplerate_conversion_quality(mt32emu_context context, const mt32emu_samplerate_conversion_quality quality) {
+void MT32EMU_C_CALL 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) {
+void MT32EMU_C_CALL mt32emu_select_renderer_type(mt32emu_context context, const mt32emu_renderer_type renderer_type) {
 	context->synth->selectRendererType(static_cast<RendererType>(renderer_type));
 }
 
-mt32emu_renderer_type mt32emu_get_selected_renderer_type(mt32emu_context context) {
+mt32emu_renderer_type MT32EMU_C_CALL mt32emu_get_selected_renderer_type(mt32emu_context context) {
 	return static_cast<mt32emu_renderer_type>(context->synth->getSelectedRendererType());
 }
 
-mt32emu_return_code mt32emu_open_synth(mt32emu_const_context context) {
-	if ((context->controlROMImage == nullptr) || (context->pcmROMImage == nullptr)) {
+mt32emu_return_code MT32EMU_C_CALL mt32emu_open_synth(mt32emu_const_context context) {
+	if ((context->controlROMImage == NULL) || (context->pcmROMImage == NULL)) {
 		return MT32EMU_RC_MISSING_ROMS;
 	}
 	if (!context->synth->open(*context->controlROMImage, *context->pcmROMImage, context->partialCount, context->analogOutputMode)) {
@@ -650,264 +683,300 @@ mt32emu_return_code mt32emu_open_synth(mt32emu_const_context context) {
 	return MT32EMU_RC_OK;
 }
 
-void mt32emu_close_synth(mt32emu_const_context context) {
+void MT32EMU_C_CALL mt32emu_close_synth(mt32emu_const_context context) {
 	context->synth->close();
 	delete context->srcState->src;
-	context->srcState->src = nullptr;
+	context->srcState->src = NULL;
 }
 
-mt32emu_boolean mt32emu_is_open(mt32emu_const_context context) {
+mt32emu_boolean MT32EMU_C_CALL mt32emu_is_open(mt32emu_const_context context) {
 	return context->synth->isOpen() ? MT32EMU_BOOL_TRUE : MT32EMU_BOOL_FALSE;
 }
 
-mt32emu_bit32u mt32emu_get_actual_stereo_output_samplerate(mt32emu_const_context context) {
-	if (context->srcState->src == nullptr) {
+mt32emu_bit32u MT32EMU_C_CALL mt32emu_get_actual_stereo_output_samplerate(mt32emu_const_context context) {
+	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 == nullptr) {
+mt32emu_bit32u MT32EMU_C_CALL 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 == nullptr) {
+mt32emu_bit32u MT32EMU_C_CALL 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) {
+void MT32EMU_C_CALL mt32emu_flush_midi_queue(mt32emu_const_context context) {
 	context->synth->flushMIDIQueue();
 }
 
-mt32emu_bit32u mt32emu_set_midi_event_queue_size(mt32emu_const_context context, const mt32emu_bit32u queue_size) {
+mt32emu_bit32u MT32EMU_C_CALL mt32emu_set_midi_event_queue_size(mt32emu_const_context context, const mt32emu_bit32u queue_size) {
 	return context->synth->setMIDIEventQueueSize(queue_size);
 }
 
-void mt32emu_configure_midi_event_queue_sysex_storage(mt32emu_const_context context, const mt32emu_bit32u storage_buffer_size) {
-	return context->synth->configureMIDIEventQueueSysexStorage(storage_buffer_size);
+void MT32EMU_C_CALL mt32emu_configure_midi_event_queue_sysex_storage(mt32emu_const_context context, const mt32emu_bit32u storage_buffer_size) {
+	context->synth->configureMIDIEventQueueSysexStorage(storage_buffer_size);
 }
 
-void mt32emu_set_midi_receiver(mt32emu_context context, mt32emu_midi_receiver_i midi_receiver, void *instance_data) {
+void MT32EMU_C_CALL mt32emu_set_midi_receiver(mt32emu_context context, mt32emu_midi_receiver_i midi_receiver, void *instance_data) {
 	delete context->midiParser;
-	context->midiParser = (midi_receiver.v0 != nullptr) ? new DelegatingMidiStreamParser(context, midi_receiver, instance_data) : new DefaultMidiStreamParser(*context->synth);
+	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) {
+mt32emu_bit32u MT32EMU_C_CALL 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) {
+void MT32EMU_C_CALL mt32emu_parse_stream(mt32emu_const_context context, const mt32emu_bit8u *stream, mt32emu_bit32u length) {
 	context->midiParser->resetTimestamp();
 	context->midiParser->parseStream(stream, length);
 }
 
-void mt32emu_parse_stream_at(mt32emu_const_context context, const mt32emu_bit8u *stream, mt32emu_bit32u length, mt32emu_bit32u timestamp) {
+void MT32EMU_C_CALL mt32emu_parse_stream_at(mt32emu_const_context context, const mt32emu_bit8u *stream, mt32emu_bit32u length, mt32emu_bit32u timestamp) {
 	context->midiParser->setTimestamp(timestamp);
 	context->midiParser->parseStream(stream, length);
 }
 
-void mt32emu_play_short_message(mt32emu_const_context context, mt32emu_bit32u message) {
+void MT32EMU_C_CALL mt32emu_play_short_message(mt32emu_const_context context, mt32emu_bit32u message) {
 	context->midiParser->resetTimestamp();
 	context->midiParser->processShortMessage(message);
 }
 
-void mt32emu_play_short_message_at(mt32emu_const_context context, mt32emu_bit32u message, mt32emu_bit32u timestamp) {
+void MT32EMU_C_CALL mt32emu_play_short_message_at(mt32emu_const_context context, mt32emu_bit32u message, mt32emu_bit32u timestamp) {
 	context->midiParser->setTimestamp(timestamp);
 	context->midiParser->processShortMessage(message);
 }
 
-mt32emu_return_code mt32emu_play_msg(mt32emu_const_context context, mt32emu_bit32u msg) {
+mt32emu_return_code MT32EMU_C_CALL mt32emu_play_msg(mt32emu_const_context context, mt32emu_bit32u msg) {
 	if (!context->synth->isOpen()) return MT32EMU_RC_NOT_OPENED;
 	return (context->synth->playMsg(msg)) ? MT32EMU_RC_OK : MT32EMU_RC_QUEUE_FULL;
 }
 
-mt32emu_return_code mt32emu_play_sysex(mt32emu_const_context context, const mt32emu_bit8u *sysex, mt32emu_bit32u len) {
+mt32emu_return_code MT32EMU_C_CALL mt32emu_play_sysex(mt32emu_const_context context, const mt32emu_bit8u *sysex, mt32emu_bit32u len) {
 	if (!context->synth->isOpen()) return MT32EMU_RC_NOT_OPENED;
 	return (context->synth->playSysex(sysex, len)) ? MT32EMU_RC_OK : MT32EMU_RC_QUEUE_FULL;
 }
 
-mt32emu_return_code mt32emu_play_msg_at(mt32emu_const_context context, mt32emu_bit32u msg, mt32emu_bit32u timestamp) {
+mt32emu_return_code MT32EMU_C_CALL mt32emu_play_msg_at(mt32emu_const_context context, mt32emu_bit32u msg, mt32emu_bit32u timestamp) {
 	if (!context->synth->isOpen()) return MT32EMU_RC_NOT_OPENED;
 	return (context->synth->playMsg(msg, timestamp)) ? MT32EMU_RC_OK : MT32EMU_RC_QUEUE_FULL;
 }
 
-mt32emu_return_code mt32emu_play_sysex_at(mt32emu_const_context context, const mt32emu_bit8u *sysex, mt32emu_bit32u len, mt32emu_bit32u timestamp) {
+mt32emu_return_code MT32EMU_C_CALL mt32emu_play_sysex_at(mt32emu_const_context context, const mt32emu_bit8u *sysex, mt32emu_bit32u len, mt32emu_bit32u timestamp) {
 	if (!context->synth->isOpen()) return MT32EMU_RC_NOT_OPENED;
 	return (context->synth->playSysex(sysex, len, timestamp)) ? MT32EMU_RC_OK : MT32EMU_RC_QUEUE_FULL;
 }
 
-void mt32emu_play_msg_now(mt32emu_const_context context, mt32emu_bit32u msg) {
+void MT32EMU_C_CALL mt32emu_play_msg_now(mt32emu_const_context context, mt32emu_bit32u msg) {
 	context->synth->playMsgNow(msg);
 }
 
-void mt32emu_play_msg_on_part(mt32emu_const_context context, mt32emu_bit8u part, mt32emu_bit8u code, mt32emu_bit8u note, mt32emu_bit8u velocity) {
+void MT32EMU_C_CALL mt32emu_play_msg_on_part(mt32emu_const_context context, mt32emu_bit8u part, mt32emu_bit8u code, mt32emu_bit8u note, mt32emu_bit8u velocity) {
 	context->synth->playMsgOnPart(part, code, note, velocity);
 }
 
-void mt32emu_play_sysex_now(mt32emu_const_context context, const mt32emu_bit8u *sysex, mt32emu_bit32u len) {
+void MT32EMU_C_CALL mt32emu_play_sysex_now(mt32emu_const_context context, const mt32emu_bit8u *sysex, mt32emu_bit32u len) {
 	context->synth->playSysexNow(sysex, len);
 }
 
-void mt32emu_write_sysex(mt32emu_const_context context, mt32emu_bit8u channel, const mt32emu_bit8u *sysex, mt32emu_bit32u len) {
+void MT32EMU_C_CALL mt32emu_write_sysex(mt32emu_const_context context, mt32emu_bit8u channel, const mt32emu_bit8u *sysex, mt32emu_bit32u len) {
 	context->synth->writeSysex(channel, sysex, len);
 }
 
-void mt32emu_set_reverb_enabled(mt32emu_const_context context, const mt32emu_boolean reverb_enabled) {
+void MT32EMU_C_CALL mt32emu_set_reverb_enabled(mt32emu_const_context context, const mt32emu_boolean reverb_enabled) {
 	context->synth->setReverbEnabled(reverb_enabled != MT32EMU_BOOL_FALSE);
 }
 
-mt32emu_boolean mt32emu_is_reverb_enabled(mt32emu_const_context context) {
+mt32emu_boolean MT32EMU_C_CALL mt32emu_is_reverb_enabled(mt32emu_const_context context) {
 	return context->synth->isReverbEnabled() ? MT32EMU_BOOL_TRUE : MT32EMU_BOOL_FALSE;
 }
 
-void mt32emu_set_reverb_overridden(mt32emu_const_context context, const mt32emu_boolean reverb_overridden) {
+void MT32EMU_C_CALL mt32emu_set_reverb_overridden(mt32emu_const_context context, const mt32emu_boolean reverb_overridden) {
 	context->synth->setReverbOverridden(reverb_overridden != MT32EMU_BOOL_FALSE);
 }
 
-mt32emu_boolean mt32emu_is_reverb_overridden(mt32emu_const_context context) {
+mt32emu_boolean MT32EMU_C_CALL mt32emu_is_reverb_overridden(mt32emu_const_context context) {
 	return context->synth->isReverbOverridden() ? MT32EMU_BOOL_TRUE : MT32EMU_BOOL_FALSE;
 }
 
-void mt32emu_set_reverb_compatibility_mode(mt32emu_const_context context, const mt32emu_boolean mt32_compatible_mode) {
+void MT32EMU_C_CALL mt32emu_set_reverb_compatibility_mode(mt32emu_const_context context, const mt32emu_boolean mt32_compatible_mode) {
 	context->synth->setReverbCompatibilityMode(mt32_compatible_mode != MT32EMU_BOOL_FALSE);
 }
 
-mt32emu_boolean mt32emu_is_mt32_reverb_compatibility_mode(mt32emu_const_context context) {
+mt32emu_boolean MT32EMU_C_CALL mt32emu_is_mt32_reverb_compatibility_mode(mt32emu_const_context context) {
 	return context->synth->isMT32ReverbCompatibilityMode() ? MT32EMU_BOOL_TRUE : MT32EMU_BOOL_FALSE;
 }
 
-mt32emu_boolean mt32emu_is_default_reverb_mt32_compatible(mt32emu_const_context context) {
+mt32emu_boolean MT32EMU_C_CALL mt32emu_is_default_reverb_mt32_compatible(mt32emu_const_context context) {
 	return context->synth->isDefaultReverbMT32Compatible() ? MT32EMU_BOOL_TRUE : MT32EMU_BOOL_FALSE;
 }
 
-void mt32emu_preallocate_reverb_memory(mt32emu_const_context context, const mt32emu_boolean enabled) {
-	return context->synth->preallocateReverbMemory(enabled != MT32EMU_BOOL_FALSE);
+void MT32EMU_C_CALL mt32emu_preallocate_reverb_memory(mt32emu_const_context context, const mt32emu_boolean enabled) {
+	context->synth->preallocateReverbMemory(enabled != MT32EMU_BOOL_FALSE);
 }
 
-void mt32emu_set_dac_input_mode(mt32emu_const_context context, const mt32emu_dac_input_mode mode) {
+void MT32EMU_C_CALL mt32emu_set_dac_input_mode(mt32emu_const_context context, const mt32emu_dac_input_mode mode) {
 	context->synth->setDACInputMode(static_cast<DACInputMode>(mode));
 }
 
-mt32emu_dac_input_mode mt32emu_get_dac_input_mode(mt32emu_const_context context) {
+mt32emu_dac_input_mode MT32EMU_C_CALL mt32emu_get_dac_input_mode(mt32emu_const_context context) {
 	return static_cast<mt32emu_dac_input_mode>(context->synth->getDACInputMode());
 }
 
-void mt32emu_set_midi_delay_mode(mt32emu_const_context context, const mt32emu_midi_delay_mode mode) {
+void MT32EMU_C_CALL mt32emu_set_midi_delay_mode(mt32emu_const_context context, const mt32emu_midi_delay_mode mode) {
 	context->synth->setMIDIDelayMode(static_cast<MIDIDelayMode>(mode));
 }
 
-mt32emu_midi_delay_mode mt32emu_get_midi_delay_mode(mt32emu_const_context context) {
+mt32emu_midi_delay_mode MT32EMU_C_CALL mt32emu_get_midi_delay_mode(mt32emu_const_context context) {
 	return static_cast<mt32emu_midi_delay_mode>(context->synth->getMIDIDelayMode());
 }
 
-void mt32emu_set_output_gain(mt32emu_const_context context, float gain) {
+void MT32EMU_C_CALL mt32emu_set_output_gain(mt32emu_const_context context, float gain) {
 	context->synth->setOutputGain(gain);
 }
 
-float mt32emu_get_output_gain(mt32emu_const_context context) {
+float MT32EMU_C_CALL mt32emu_get_output_gain(mt32emu_const_context context) {
 	return context->synth->getOutputGain();
 }
 
-void mt32emu_set_reverb_output_gain(mt32emu_const_context context, float gain) {
+void MT32EMU_C_CALL mt32emu_set_reverb_output_gain(mt32emu_const_context context, float gain) {
 	context->synth->setReverbOutputGain(gain);
 }
 
-float mt32emu_get_reverb_output_gain(mt32emu_const_context context) {
+float MT32EMU_C_CALL mt32emu_get_reverb_output_gain(mt32emu_const_context context) {
 	return context->synth->getReverbOutputGain();
 }
 
-void mt32emu_set_reversed_stereo_enabled(mt32emu_const_context context, const mt32emu_boolean enabled) {
+void MT32EMU_C_CALL mt32emu_set_part_volume_override(mt32emu_const_context context, mt32emu_bit8u part_number, mt32emu_bit8u volume_override) {
+	context->synth->setPartVolumeOverride(part_number, volume_override);
+}
+
+mt32emu_bit8u MT32EMU_C_CALL mt32emu_get_part_volume_override(mt32emu_const_context context, mt32emu_bit8u part_number) {
+	return context->synth->getPartVolumeOverride(part_number);
+}
+
+void MT32EMU_C_CALL mt32emu_set_reversed_stereo_enabled(mt32emu_const_context context, const mt32emu_boolean enabled) {
 	context->synth->setReversedStereoEnabled(enabled != MT32EMU_BOOL_FALSE);
 }
 
-mt32emu_boolean mt32emu_is_reversed_stereo_enabled(mt32emu_const_context context) {
+mt32emu_boolean MT32EMU_C_CALL 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) {
+void MT32EMU_C_CALL 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) {
+mt32emu_boolean MT32EMU_C_CALL mt32emu_is_nice_amp_ramp_enabled(mt32emu_const_context context) {
 	return context->synth->isNiceAmpRampEnabled() ? MT32EMU_BOOL_TRUE : MT32EMU_BOOL_FALSE;
 }
 
-MT32EMU_EXPORT void mt32emu_set_nice_panning_enabled(mt32emu_const_context context, const mt32emu_boolean enabled) {
+void MT32EMU_C_CALL mt32emu_set_nice_panning_enabled(mt32emu_const_context context, const mt32emu_boolean enabled) {
 	context->synth->setNicePanningEnabled(enabled != MT32EMU_BOOL_FALSE);
 }
 
-MT32EMU_EXPORT mt32emu_boolean mt32emu_is_nice_panning_enabled(mt32emu_const_context context) {
+mt32emu_boolean MT32EMU_C_CALL mt32emu_is_nice_panning_enabled(mt32emu_const_context context) {
 	return context->synth->isNicePanningEnabled() ? MT32EMU_BOOL_TRUE : MT32EMU_BOOL_FALSE;
 }
 
-MT32EMU_EXPORT void mt32emu_set_nice_partial_mixing_enabled(mt32emu_const_context context, const mt32emu_boolean enabled) {
+void MT32EMU_C_CALL mt32emu_set_nice_partial_mixing_enabled(mt32emu_const_context context, const mt32emu_boolean enabled) {
 	context->synth->setNicePartialMixingEnabled(enabled != MT32EMU_BOOL_FALSE);
 }
 
-MT32EMU_EXPORT mt32emu_boolean mt32emu_is_nice_partial_mixing_enabled(mt32emu_const_context context) {
+mt32emu_boolean MT32EMU_C_CALL mt32emu_is_nice_partial_mixing_enabled(mt32emu_const_context context) {
 	return context->synth->isNicePartialMixingEnabled() ? MT32EMU_BOOL_TRUE : MT32EMU_BOOL_FALSE;
 }
 
-void mt32emu_render_bit16s(mt32emu_const_context context, mt32emu_bit16s *stream, mt32emu_bit32u len) {
-	if (context->srcState->src != nullptr) {
+void MT32EMU_C_CALL mt32emu_render_bit16s(mt32emu_const_context context, mt32emu_bit16s *stream, mt32emu_bit32u 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) {
-	if (context->srcState->src != nullptr) {
+void MT32EMU_C_CALL mt32emu_render_float(mt32emu_const_context context, float *stream, mt32emu_bit32u 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) {
+void MT32EMU_C_CALL mt32emu_render_bit16s_streams(mt32emu_const_context context, const mt32emu_dac_output_bit16s_streams *streams, mt32emu_bit32u len) {
 	context->synth->renderStreams(*reinterpret_cast<const DACOutputStreams<Bit16s> *>(streams), len);
 }
 
-void mt32emu_render_float_streams(mt32emu_const_context context, const mt32emu_dac_output_float_streams *streams, mt32emu_bit32u len) {
+void MT32EMU_C_CALL mt32emu_render_float_streams(mt32emu_const_context context, const mt32emu_dac_output_float_streams *streams, mt32emu_bit32u len) {
 	context->synth->renderStreams(*reinterpret_cast<const DACOutputStreams<float> *>(streams), len);
 }
 
-mt32emu_boolean mt32emu_has_active_partials(mt32emu_const_context context) {
+mt32emu_boolean MT32EMU_C_CALL mt32emu_has_active_partials(mt32emu_const_context context) {
 	return context->synth->hasActivePartials() ? MT32EMU_BOOL_TRUE : MT32EMU_BOOL_FALSE;
 }
 
-mt32emu_boolean mt32emu_is_active(mt32emu_const_context context) {
+mt32emu_boolean MT32EMU_C_CALL mt32emu_is_active(mt32emu_const_context context) {
 	return context->synth->isActive() ? MT32EMU_BOOL_TRUE : MT32EMU_BOOL_FALSE;
 }
 
-mt32emu_bit32u mt32emu_get_partial_count(mt32emu_const_context context) {
+mt32emu_bit32u MT32EMU_C_CALL mt32emu_get_partial_count(mt32emu_const_context context) {
 	return context->synth->getPartialCount();
 }
 
-mt32emu_bit32u mt32emu_get_part_states(mt32emu_const_context context) {
+mt32emu_bit32u MT32EMU_C_CALL mt32emu_get_part_states(mt32emu_const_context context) {
 	return context->synth->getPartStates();
 }
 
-void mt32emu_get_partial_states(mt32emu_const_context context, mt32emu_bit8u *partial_states) {
+void MT32EMU_C_CALL mt32emu_get_partial_states(mt32emu_const_context context, mt32emu_bit8u *partial_states) {
 	context->synth->getPartialStates(partial_states);
 }
 
-mt32emu_bit32u mt32emu_get_playing_notes(mt32emu_const_context context, mt32emu_bit8u part_number, mt32emu_bit8u *keys, mt32emu_bit8u *velocities) {
+mt32emu_bit32u MT32EMU_C_CALL mt32emu_get_playing_notes(mt32emu_const_context context, mt32emu_bit8u part_number, mt32emu_bit8u *keys, mt32emu_bit8u *velocities) {
 	return context->synth->getPlayingNotes(part_number, keys, velocities);
 }
 
-const char *mt32emu_get_patch_name(mt32emu_const_context context, mt32emu_bit8u part_number) {
+const char * MT32EMU_C_CALL mt32emu_get_patch_name(mt32emu_const_context context, mt32emu_bit8u part_number) {
 	return context->synth->getPatchName(part_number);
 }
 
-void mt32emu_read_memory(mt32emu_const_context context, mt32emu_bit32u addr, mt32emu_bit32u len, mt32emu_bit8u *data) {
+mt32emu_boolean MT32EMU_C_CALL mt32emu_get_sound_group_name(mt32emu_const_context context, char *sound_group_name, mt32emu_bit8u timbre_group, mt32emu_bit8u timbre_number) {
+	return context->synth->getSoundGroupName(sound_group_name, timbre_group, timbre_number) ? MT32EMU_BOOL_TRUE : MT32EMU_BOOL_FALSE;
+}
+
+mt32emu_boolean MT32EMU_C_CALL mt32emu_get_sound_name(mt32emu_const_context context, char *sound_name, mt32emu_bit8u timbre_group, mt32emu_bit8u timbre_number) {
+	return context->synth->getSoundName(sound_name, timbre_group, timbre_number) ? MT32EMU_BOOL_TRUE : MT32EMU_BOOL_FALSE;
+}
+
+void MT32EMU_C_CALL mt32emu_read_memory(mt32emu_const_context context, mt32emu_bit32u addr, mt32emu_bit32u len, mt32emu_bit8u *data) {
 	context->synth->readMemory(addr, len, data);
 }
 
+mt32emu_boolean MT32EMU_C_CALL mt32emu_get_display_state(mt32emu_const_context context, char *target_buffer, const mt32emu_boolean narrow_lcd) {
+	return context->synth->getDisplayState(target_buffer, narrow_lcd != MT32EMU_BOOL_FALSE) ? MT32EMU_BOOL_TRUE : MT32EMU_BOOL_FALSE;
+}
+
+void MT32EMU_C_CALL mt32emu_set_main_display_mode(mt32emu_const_context context) {
+	context->synth->setMainDisplayMode();
+}
+
+void MT32EMU_C_CALL mt32emu_set_display_compatibility(mt32emu_const_context context, mt32emu_boolean old_mt32_compatibility_enabled) {
+	context->synth->setDisplayCompatibility(old_mt32_compatibility_enabled != MT32EMU_BOOL_FALSE);
+}
+
+mt32emu_boolean MT32EMU_C_CALL mt32emu_is_display_old_mt32_compatible(mt32emu_const_context context) {
+	return context->synth->isDisplayOldMT32Compatible() ? MT32EMU_BOOL_TRUE : MT32EMU_BOOL_FALSE;
+}
+
+mt32emu_boolean MT32EMU_C_CALL mt32emu_is_default_display_old_mt32_compatible(mt32emu_const_context context) {
+	return context->synth->isDefaultDisplayOldMT32Compatible() ? MT32EMU_BOOL_TRUE : MT32EMU_BOOL_FALSE;
+}
+
 } // extern "C"
diff --git a/audio/softsynth/mt32/c_interface/c_interface.h b/audio/softsynth/mt32/c_interface/c_interface.h
index 14e7263b392..5653c905164 100644
--- a/audio/softsynth/mt32/c_interface/c_interface.h
+++ b/audio/softsynth/mt32/c_interface/c_interface.h
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License as published by
@@ -37,7 +37,7 @@ extern "C" {
 /* === Interface handling === */
 
 /** Returns mt32emu_service_i interface. */
-MT32EMU_EXPORT mt32emu_service_i mt32emu_get_service_i(void);
+MT32EMU_EXPORT mt32emu_service_i MT32EMU_C_CALL mt32emu_get_service_i(void);
 
 #if MT32EMU_EXPORTS_TYPE == 2
 #undef MT32EMU_EXPORT
@@ -50,13 +50,13 @@ MT32EMU_EXPORT mt32emu_service_i mt32emu_get_service_i(void);
  * Returns the version ID of mt32emu_report_handler_i interface the library has been compiled with.
  * This allows a client to fall-back gracefully instead of silently not receiving expected event reports.
  */
-MT32EMU_EXPORT mt32emu_report_handler_version mt32emu_get_supported_report_handler_version(void);
+MT32EMU_EXPORT mt32emu_report_handler_version MT32EMU_C_CALL mt32emu_get_supported_report_handler_version(void);
 
 /**
  * Returns the version ID of mt32emu_midi_receiver_version_i interface the library has been compiled with.
  * This allows a client to fall-back gracefully instead of silently not receiving expected MIDI messages.
  */
-MT32EMU_EXPORT mt32emu_midi_receiver_version mt32emu_get_supported_midi_receiver_version(void);
+MT32EMU_EXPORT mt32emu_midi_receiver_version MT32EMU_C_CALL mt32emu_get_supported_midi_receiver_version(void);
 
 /* === Utility === */
 
@@ -66,25 +66,25 @@ MT32EMU_EXPORT mt32emu_midi_receiver_version mt32emu_get_supported_midi_receiver
  * mm - minor version number
  * pp - patch number
  */
-MT32EMU_EXPORT mt32emu_bit32u mt32emu_get_library_version_int(void);
+MT32EMU_EXPORT mt32emu_bit32u MT32EMU_C_CALL mt32emu_get_library_version_int(void);
 
 /**
  * Returns library version as a C-string in format: "MAJOR.MINOR.PATCH".
  */
-MT32EMU_EXPORT const char *mt32emu_get_library_version_string(void);
+MT32EMU_EXPORT const char * MT32EMU_C_CALL mt32emu_get_library_version_string(void);
 
 /**
  * Returns output sample rate used in emulation of stereo analog circuitry of hardware units for particular analog_output_mode.
  * See comment for mt32emu_analog_output_mode.
  */
-MT32EMU_EXPORT mt32emu_bit32u mt32emu_get_stereo_output_samplerate(const mt32emu_analog_output_mode analog_output_mode);
+MT32EMU_EXPORT mt32emu_bit32u MT32EMU_C_CALL mt32emu_get_stereo_output_samplerate(const mt32emu_analog_output_mode analog_output_mode);
 
 /**
  * Returns the value of analog_output_mode for which the output signal may retain its full frequency spectrum
  * at the sample rate specified by the target_samplerate argument.
  * See comment for mt32emu_analog_output_mode.
  */
-MT32EMU_EXPORT mt32emu_analog_output_mode mt32emu_get_best_analog_output_mode(const double target_samplerate);
+MT32EMU_EXPORT mt32emu_analog_output_mode MT32EMU_C_CALL mt32emu_get_best_analog_output_mode(const double target_samplerate);
 
 /* === ROM handling === */
 
@@ -94,7 +94,7 @@ MT32EMU_EXPORT mt32emu_analog_output_mode mt32emu_get_best_analog_output_mode(co
  * Returns the number of identifiers available for retrieval. The size of the target array to be allocated can be found
  * by passing NULL in argument machine_ids; argument machine_ids_size is ignored in this case.
  */
-MT32EMU_EXPORT_V(2.5) size_t mt32emu_get_machine_ids(const char **machine_ids, size_t machine_ids_size);
+MT32EMU_EXPORT_V(2.5) size_t MT32EMU_C_CALL mt32emu_get_machine_ids(const char **machine_ids, size_t machine_ids_size);
 /**
  * Retrieves a list of identifiers (as C-strings) of supported ROM images. Argument rom_ids points to the array of size
  * rom_ids_size to be filled. Optional argument machine_id can be used to indicate a specific machine to retrieve ROM identifiers
@@ -103,7 +103,7 @@ MT32EMU_EXPORT_V(2.5) size_t mt32emu_get_machine_ids(const char **machine_ids, s
  * by passing NULL in argument rom_ids; argument rom_ids_size is ignored in this case. If argument machine_id contains
  * an unrecognised value, 0 is returned.
  */
-MT32EMU_EXPORT_V(2.5) size_t mt32emu_get_rom_ids(const char **rom_ids, size_t rom_ids_size, const char *machine_id);
+MT32EMU_EXPORT_V(2.5) size_t MT32EMU_C_CALL mt32emu_get_rom_ids(const char **rom_ids, size_t rom_ids_size, const char *machine_id);
 
 /**
  * Identifies a ROM image the provided data array contains by its SHA1 digest. Optional argument machine_id can be used to indicate
@@ -113,7 +113,7 @@ MT32EMU_EXPORT_V(2.5) size_t mt32emu_get_rom_ids(const char **rom_ids, size_t ro
  * with the specified machine), all fields of rom_info are filled with NULLs.
  * Returns MT32EMU_RC_OK upon success or a negative error code otherwise.
  */
-MT32EMU_EXPORT_V(2.5) mt32emu_return_code mt32emu_identify_rom_data(mt32emu_rom_info *rom_info, const mt32emu_bit8u *data, size_t data_size, const char *machine_id);
+MT32EMU_EXPORT_V(2.5) mt32emu_return_code MT32EMU_C_CALL mt32emu_identify_rom_data(mt32emu_rom_info *rom_info, const mt32emu_bit8u *data, size_t data_size, const char *machine_id);
 /**
  * Loads the content of the file specified by argument filename and identifies a ROM image the file contains by its SHA1 digest.
  * Optional argument machine_id can be used to indicate a specific machine to identify the ROM image for; if NULL, the ROM image
@@ -123,15 +123,15 @@ MT32EMU_EXPORT_V(2.5) mt32emu_return_code mt32emu_identify_rom_data(mt32emu_rom_
  * with the specified machine), all fields of rom_info are filled with NULLs.
  * Returns MT32EMU_RC_OK upon success or a negative error code otherwise.
  */
-MT32EMU_EXPORT_V(2.5) mt32emu_return_code mt32emu_identify_rom_file(mt32emu_rom_info *rom_info, const char *filename, const char *machine_id);
+MT32EMU_EXPORT_V(2.5) mt32emu_return_code MT32EMU_C_CALL mt32emu_identify_rom_file(mt32emu_rom_info *rom_info, const char *filename, const char *machine_id);
 
 /* == Context-dependent functions == */
 
 /** Initialises a new emulation context and installs custom report handler if non-NULL. */
-MT32EMU_EXPORT mt32emu_context mt32emu_create_context(mt32emu_report_handler_i report_handler, void *instance_data);
+MT32EMU_EXPORT mt32emu_context MT32EMU_C_CALL mt32emu_create_context(mt32emu_report_handler_i report_handler, void *instance_data);
 
 /** Closes and destroys emulation context. */
-MT32EMU_EXPORT void mt32emu_free_context(mt32emu_context context);
+MT32EMU_EXPORT void MT32EMU_C_CALL mt32emu_free_context(mt32emu_context context);
 
 /**
  * Adds a new full ROM data image identified by its SHA1 digest to the emulation context replacing previously added ROM of the same
@@ -143,7 +143,7 @@ MT32EMU_EXPORT void mt32emu_free_context(mt32emu_context context);
  * mt32emu_open_synth().
  * Returns positive value upon success.
  */
-MT32EMU_EXPORT mt32emu_return_code mt32emu_add_rom_data(mt32emu_context context, const mt32emu_bit8u *data, size_t data_size, const mt32emu_sha1_digest *sha1_digest);
+MT32EMU_EXPORT mt32emu_return_code MT32EMU_C_CALL mt32emu_add_rom_data(mt32emu_context context, const mt32emu_bit8u *data, size_t data_size, const mt32emu_sha1_digest *sha1_digest);
 
 /**
  * Loads a ROM file that contains a full ROM data image, identifies it by the SHA1 digest, and adds it to the emulation context
@@ -152,7 +152,7 @@ MT32EMU_EXPORT mt32emu_return_code mt32emu_add_rom_data(mt32emu_context context,
  * mt32emu_open_synth().
  * Returns positive value upon success.
  */
-MT32EMU_EXPORT mt32emu_return_code mt32emu_add_rom_file(mt32emu_context context, const char *filename);
+MT32EMU_EXPORT mt32emu_return_code MT32EMU_C_CALL mt32emu_add_rom_file(mt32emu_context context, const char *filename);
 
 /**
  * Merges a pair of compatible ROM data image parts into a full image and adds it to the emulation context replacing previously
@@ -163,7 +163,7 @@ MT32EMU_EXPORT mt32emu_return_code mt32emu_add_rom_file(mt32emu_context context,
  * mt32emu_open_synth().
  * Returns positive value upon success.
  */
-MT32EMU_EXPORT_V(2.5) mt32emu_return_code mt32emu_merge_and_add_rom_data(mt32emu_context context, const mt32emu_bit8u *part1_data, size_t part1_data_size, const mt32emu_sha1_digest *part1_sha1_digest, const mt32emu_bit8u *part2_data, size_t part2_data_size, const mt32emu_sha1_digest *part2_sha1_digest);
+MT32EMU_EXPORT_V(2.5) mt32emu_return_code MT32EMU_C_CALL mt32emu_merge_and_add_rom_data(mt32emu_context context, const mt32emu_bit8u *part1_data, size_t part1_data_size, const mt32emu_sha1_digest *part1_sha1_digest, const mt32emu_bit8u *part2_data, size_t part2_data_size, const mt32emu_sha1_digest *part2_sha1_digest);
 
 /**
  * Loads a pair of files that contains compatible parts of a full ROM image, identifies them by the SHA1 digest, merges these
@@ -172,7 +172,7 @@ MT32EMU_EXPORT_V(2.5) mt32emu_return_code mt32emu_merge_and_add_rom_data(mt32emu
  * mt32emu_open_synth().
  * Returns positive value upon success.
  */
-MT32EMU_EXPORT_V(2.5) mt32emu_return_code mt32emu_merge_and_add_rom_files(mt32emu_context context, const char *part1_filename, const char *part2_filename);
+MT32EMU_EXPORT_V(2.5) mt32emu_return_code MT32EMU_C_CALL mt32emu_merge_and_add_rom_files(mt32emu_context context, const char *part1_filename, const char *part2_filename);
 
 /**
  * Loads a file that contains a ROM image of a specific machine, identifies it by the SHA1 digest, and adds it to the emulation
@@ -190,25 +190,25 @@ MT32EMU_EXPORT_V(2.5) mt32emu_return_code mt32emu_merge_and_add_rom_files(mt32em
  * Returns a positive value in case changes have been made, MT32EMU_RC_OK if the file has been ignored or a negative error code
  * upon failure.
  */
-MT32EMU_EXPORT_V(2.5) mt32emu_return_code mt32emu_add_machine_rom_file(mt32emu_context context, const char *machine_id, const char *filename);
+MT32EMU_EXPORT_V(2.5) mt32emu_return_code MT32EMU_C_CALL mt32emu_add_machine_rom_file(mt32emu_context context, const char *machine_id, const char *filename);
 
 /**
  * Fills in mt32emu_rom_info structure with identifiers and descriptions of control and PCM ROM files identified and added to the synth context.
  * If one of the ROM files is not loaded and identified yet, NULL is returned in the corresponding fields of the mt32emu_rom_info structure.
  */
-MT32EMU_EXPORT void mt32emu_get_rom_info(mt32emu_const_context context, mt32emu_rom_info *rom_info);
+MT32EMU_EXPORT void MT32EMU_C_CALL mt32emu_get_rom_info(mt32emu_const_context context, mt32emu_rom_info *rom_info);
 
 /**
  * Allows to override the default maximum number of partials playing simultaneously within the emulation session.
  * This function doesn't immediately change the state of already opened synth. Newly set value will take effect upon next call of mt32emu_open_synth().
  */
-MT32EMU_EXPORT void mt32emu_set_partial_count(mt32emu_context context, const mt32emu_bit32u partial_count);
+MT32EMU_EXPORT void MT32EMU_C_CALL mt32emu_set_partial_count(mt32emu_context context, const mt32emu_bit32u partial_count);
 
 /**
  * Allows to override the default mode for emulation of analogue circuitry of the hardware units within the emulation session.
  * This function doesn't immediately change the state of already opened synth. Newly set value will take effect upon next call of mt32emu_open_synth().
  */
-MT32EMU_EXPORT void mt32emu_set_analog_output_mode(mt32emu_context context, const mt32emu_analog_output_mode analog_output_mode);
+MT32EMU_EXPORT void MT32EMU_C_CALL mt32emu_set_analog_output_mode(mt32emu_context context, const mt32emu_analog_output_mode analog_output_mode);
 
 /**
  * Allows to convert the synthesiser output to any desired sample rate. The samplerate conversion
@@ -219,7 +219,7 @@ MT32EMU_EXPORT void mt32emu_set_analog_output_mode(mt32emu_context context, cons
  * This function doesn't immediately change the state of already opened synth.
  * Newly set value will take effect upon next call of mt32emu_open_synth().
  */
-MT32EMU_EXPORT void mt32emu_set_stereo_output_samplerate(mt32emu_context context, const double samplerate);
+MT32EMU_EXPORT void MT32EMU_C_CALL mt32emu_set_stereo_output_samplerate(mt32emu_context context, const double samplerate);
 
 /**
  * Several samplerate conversion quality options are provided which allow to trade-off the conversion speed vs.
@@ -228,33 +228,33 @@ MT32EMU_EXPORT void mt32emu_set_stereo_output_samplerate(mt32emu_context context
  * This function doesn't immediately change the state of already opened synth.
  * Newly set value will take effect upon next call of mt32emu_open_synth().
  */
-MT32EMU_EXPORT void mt32emu_set_samplerate_conversion_quality(mt32emu_context context, const mt32emu_samplerate_conversion_quality quality);
+MT32EMU_EXPORT void MT32EMU_C_CALL mt32emu_set_samplerate_conversion_quality(mt32emu_context context, const mt32emu_samplerate_conversion_quality quality);
 
 /**
  * Selects new type of the wave generator and renderer to be used during subsequent calls to mt32emu_open_synth().
  * By default, MT32EMU_RT_BIT16S is selected.
  * See mt32emu_renderer_type for details.
  */
-MT32EMU_EXPORT void mt32emu_select_renderer_type(mt32emu_context context, const mt32emu_renderer_type renderer_type);
+MT32EMU_EXPORT void MT32EMU_C_CALL mt32emu_select_renderer_type(mt32emu_context context, const mt32emu_renderer_type renderer_type);
 
 /**
  * Returns previously selected type of the wave generator and renderer.
  * See mt32emu_renderer_type for details.
  */
-MT32EMU_EXPORT mt32emu_renderer_type mt32emu_get_selected_renderer_type(mt32emu_context context);
+MT32EMU_EXPORT mt32emu_renderer_type MT32EMU_C_CALL mt32emu_get_selected_renderer_type(mt32emu_context context);
 
 /**
  * Prepares the emulation context to receive MIDI messages and produce output audio data using aforehand added set of ROMs,
  * and optionally set the maximum partial count and the analog output mode.
  * Returns MT32EMU_RC_OK upon success.
  */
-MT32EMU_EXPORT mt32emu_return_code mt32emu_open_synth(mt32emu_const_context context);
+MT32EMU_EXPORT mt32emu_return_code MT32EMU_C_CALL mt32emu_open_synth(mt32emu_const_context context);
 
 /** Closes the emulation context freeing allocated resources. Added ROMs remain unaffected and ready for reuse. */
-MT32EMU_EXPORT void mt32emu_close_synth(mt32emu_const_context context);
+MT32EMU_EXPORT void MT32EMU_C_CALL mt32emu_close_synth(mt32emu_const_context context);
 
 /** Returns true if the synth is in completely initialized state, otherwise returns false. */
-MT32EMU_EXPORT mt32emu_boolean mt32emu_is_open(mt32emu_const_context context);
+MT32EMU_EXPORT mt32emu_boolean MT32EMU_C_CALL mt32emu_is_open(mt32emu_const_context context);
 
 /**
  * Returns actual sample rate of the fully processed output stereo signal.
@@ -263,31 +263,31 @@ MT32EMU_EXPORT mt32emu_boolean mt32emu_is_open(mt32emu_const_context context);
  * Otherwise, the output samplerate is chosen depending on the emulation mode of stereo analog circuitry of hardware units.
  * See comment for mt32emu_analog_output_mode for more info.
  */
-MT32EMU_EXPORT mt32emu_bit32u mt32emu_get_actual_stereo_output_samplerate(mt32emu_const_context context);
+MT32EMU_EXPORT mt32emu_bit32u MT32EMU_C_CALL mt32emu_get_actual_stereo_output_samplerate(mt32emu_const_context context);
 
 /**
  * Returns the number of samples produced at the internal synth sample rate (32000 Hz)
  * that correspond to the given number of samples at the output sample rate.
  * Intended to facilitate audio time synchronisation.
  */
-MT32EMU_EXPORT mt32emu_bit32u mt32emu_convert_output_to_synth_timestamp(mt32emu_const_context context, mt32emu_bit32u output_timestamp);
+MT32EMU_EXPORT mt32emu_bit32u MT32EMU_C_CALL mt32emu_convert_output_to_synth_timestamp(mt32emu_const_context context, mt32emu_bit32u output_timestamp);
 
 /**
  * Returns the number of samples produced at the output sample rate
  * that correspond to the given number of samples at the internal synth sample rate (32000 Hz).
  * Intended to facilitate audio time synchronisation.
  */
-MT32EMU_EXPORT mt32emu_bit32u mt32emu_convert_synth_to_output_timestamp(mt32emu_const_context context, mt32emu_bit32u synth_timestamp);
+MT32EMU_EXPORT mt32emu_bit32u MT32EMU_C_CALL mt32emu_convert_synth_to_output_timestamp(mt32emu_const_context context, mt32emu_bit32u synth_timestamp);
 
 /** All the enqueued events are processed by the synth immediately. */
-MT32EMU_EXPORT void mt32emu_flush_midi_queue(mt32emu_const_context context);
+MT32EMU_EXPORT void MT32EMU_C_CALL mt32emu_flush_midi_queue(mt32emu_const_context context);
 
 /**
  * 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.
  * Returns the actual queue size being used.
  */
-MT32EMU_EXPORT mt32emu_bit32u mt32emu_set_midi_event_queue_size(mt32emu_const_context context, const mt32emu_bit32u queue_size);
+MT32EMU_EXPORT mt32emu_bit32u MT32EMU_C_CALL mt32emu_set_midi_event_queue_size(mt32emu_const_context context, const mt32emu_bit32u queue_size);
 
 /**
  * Configures the SysEx storage of the internal MIDI event queue.
@@ -300,7 +300,7 @@ MT32EMU_EXPORT mt32emu_bit32u mt32emu_set_midi_event_queue_size(mt32emu_const_co
  * by a SysEx event, that has been processed and thus is no longer necessary, is disposed instantly.
  * Note, the queue is flushed and recreated in the process so that its size remains intact.
  */
-void mt32emu_configure_midi_event_queue_sysex_storage(mt32emu_const_context context, const mt32emu_bit32u storage_buffer_size);
+MT32EMU_EXPORT void MT32EMU_C_CALL mt32emu_configure_midi_event_queue_sysex_storage(mt32emu_const_context context, const mt32emu_bit32u storage_buffer_size);
 
 /**
  * Installs custom MIDI receiver object intended for receiving MIDI messages generated by MIDI stream parser.
@@ -308,13 +308,13 @@ void mt32emu_configure_midi_event_queue_sysex_storage(mt32emu_const_context cont
  * By default, parsed short MIDI messages and System Exclusive messages are sent to the synth input MIDI queue.
  * This function allows to override default behaviour. If midi_receiver argument is set to NULL, the default behaviour is restored.
  */
-MT32EMU_EXPORT void mt32emu_set_midi_receiver(mt32emu_context context, mt32emu_midi_receiver_i midi_receiver, void *instance_data);
+MT32EMU_EXPORT void MT32EMU_C_CALL 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);
+MT32EMU_EXPORT mt32emu_bit32u MT32EMU_C_CALL 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.
@@ -331,7 +331,7 @@ MT32EMU_EXPORT mt32emu_bit32u mt32emu_get_internal_rendered_sample_count(mt32emu
  * When a System Realtime MIDI message is parsed, onMIDISystemRealtime callback is invoked.
  * NOTE: the total length of a SysEx message being fragmented shall not exceed MT32EMU_MAX_STREAM_BUFFER_SIZE (32768 bytes).
  */
-MT32EMU_EXPORT void mt32emu_parse_stream(mt32emu_const_context context, const mt32emu_bit8u *stream, mt32emu_bit32u length);
+MT32EMU_EXPORT void MT32EMU_C_CALL mt32emu_parse_stream(mt32emu_const_context context, const mt32emu_bit8u *stream, mt32emu_bit32u length);
 
 /**
  * Parses a block of raw MIDI bytes and enqueues parsed MIDI messages to play at specified time.
@@ -339,31 +339,31 @@ MT32EMU_EXPORT void mt32emu_parse_stream(mt32emu_const_context context, const mt
  * When a System Realtime MIDI message is parsed, onMIDISystemRealtime callback is invoked.
  * NOTE: the total length of a SysEx message being fragmented shall not exceed MT32EMU_MAX_STREAM_BUFFER_SIZE (32768 bytes).
  */
-MT32EMU_EXPORT void mt32emu_parse_stream_at(mt32emu_const_context context, const mt32emu_bit8u *stream, mt32emu_bit32u length, mt32emu_bit32u timestamp);
+MT32EMU_EXPORT void MT32EMU_C_CALL mt32emu_parse_stream_at(mt32emu_const_context context, const mt32emu_bit8u *stream, mt32emu_bit32u length, mt32emu_bit32u timestamp);
 
 /**
  * Enqueues a single mt32emu_bit32u-encoded short MIDI message with full processing ASAP.
  * The short MIDI message may contain no status byte, the running status is used in this case.
  * When the argument is a System Realtime MIDI message, onMIDISystemRealtime callback is invoked.
  */
-MT32EMU_EXPORT void mt32emu_play_short_message(mt32emu_const_context context, mt32emu_bit32u message);
+MT32EMU_EXPORT void MT32EMU_C_CALL mt32emu_play_short_message(mt32emu_const_context context, mt32emu_bit32u message);
 
 /**
  * Enqueues a single mt32emu_bit32u-encoded short MIDI message to play at specified time with full processing.
  * The short MIDI message may contain no status byte, the running status is used in this case.
  * When the argument is a System Realtime MIDI message, onMIDISystemRealtime callback is invoked.
  */
-MT32EMU_EXPORT void mt32emu_play_short_message_at(mt32emu_const_context context, mt32emu_bit32u message, mt32emu_bit32u timestamp);
+MT32EMU_EXPORT void MT32EMU_C_CALL mt32emu_play_short_message_at(mt32emu_const_context context, mt32emu_bit32u message, mt32emu_bit32u timestamp);
 
 /** Enqueues a single short MIDI message to be processed ASAP. The message must contain a status byte. */
-MT32EMU_EXPORT mt32emu_return_code mt32emu_play_msg(mt32emu_const_context context, mt32emu_bit32u msg);
+MT32EMU_EXPORT mt32emu_return_code MT32EMU_C_CALL mt32emu_play_msg(mt32emu_const_context context, mt32emu_bit32u msg);
 /** Enqueues a single well formed System Exclusive MIDI message to be processed ASAP. */
-MT32EMU_EXPORT mt32emu_return_code mt32emu_play_sysex(mt32emu_const_context context, const mt32emu_bit8u *sysex, mt32emu_bit32u len);
+MT32EMU_EXPORT mt32emu_return_code MT32EMU_C_CALL mt32emu_play_sysex(mt32emu_const_context context, const mt32emu_bit8u *sysex, mt32emu_bit32u len);
 
 /** Enqueues a single short MIDI message to play at specified time. The message must contain a status byte. */
-MT32EMU_EXPORT mt32emu_return_code mt32emu_play_msg_at(mt32emu_const_context context, mt32emu_bit32u msg, mt32emu_bit32u timestamp);
+MT32EMU_EXPORT mt32emu_return_code MT32EMU_C_CALL mt32emu_play_msg_at(mt32emu_const_context context, mt32emu_bit32u msg, mt32emu_bit32u timestamp);
 /** Enqueues a single well formed System Exclusive MIDI message to play at specified time. */
-MT32EMU_EXPORT mt32emu_return_code mt32emu_play_sysex_at(mt32emu_const_context context, const mt32emu_bit8u *sysex, mt32emu_bit32u len, mt32emu_bit32u timestamp);
+MT32EMU_EXPORT mt32emu_return_code MT32EMU_C_CALL mt32emu_play_sysex_at(mt32emu_const_context context, const mt32emu_bit8u *sysex, mt32emu_bit32u len, mt32emu_bit32u timestamp);
 
 /* WARNING:
  * The methods below don't ensure minimum 1-sample delay between sequential MIDI events,
@@ -375,73 +375,73 @@ MT32EMU_EXPORT mt32emu_return_code mt32emu_play_sysex_at(mt32emu_const_context c
  * Sends a short MIDI message to the synth for immediate playback. The message must contain a status byte.
  * See the WARNING above.
  */
-MT32EMU_EXPORT void mt32emu_play_msg_now(mt32emu_const_context context, mt32emu_bit32u msg);
+MT32EMU_EXPORT void MT32EMU_C_CALL mt32emu_play_msg_now(mt32emu_const_context context, mt32emu_bit32u msg);
 /**
  * Sends unpacked short MIDI message to the synth for immediate playback. The message must contain a status byte.
  * See the WARNING above.
  */
-MT32EMU_EXPORT void mt32emu_play_msg_on_part(mt32emu_const_context context, mt32emu_bit8u part, mt32emu_bit8u code, mt32emu_bit8u note, mt32emu_bit8u velocity);
+MT32EMU_EXPORT void MT32EMU_C_CALL mt32emu_play_msg_on_part(mt32emu_const_context context, mt32emu_bit8u part, mt32emu_bit8u code, mt32emu_bit8u note, mt32emu_bit8u velocity);
 
 /**
  * Sends a single well formed System Exclusive MIDI message for immediate processing. The length is in bytes.
  * See the WARNING above.
  */
-MT32EMU_EXPORT void mt32emu_play_sysex_now(mt32emu_const_context context, const mt32emu_bit8u *sysex, mt32emu_bit32u len);
+MT32EMU_EXPORT void MT32EMU_C_CALL mt32emu_play_sysex_now(mt32emu_const_context context, const mt32emu_bit8u *sysex, mt32emu_bit32u len);
 /**
  * Sends inner body of a System Exclusive MIDI message for direct processing. The length is in bytes.
  * See the WARNING above.
  */
-MT32EMU_EXPORT void mt32emu_write_sysex(mt32emu_const_context context, mt32emu_bit8u channel, const mt32emu_bit8u *sysex, mt32emu_bit32u len);
+MT32EMU_EXPORT void MT32EMU_C_CALL mt32emu_write_sysex(mt32emu_const_context context, mt32emu_bit8u channel, const mt32emu_bit8u *sysex, mt32emu_bit32u len);
 
 /** Allows to disable wet reverb output altogether. */
-MT32EMU_EXPORT void mt32emu_set_reverb_enabled(mt32emu_const_context context, const mt32emu_boolean reverb_enabled);
+MT32EMU_EXPORT void MT32EMU_C_CALL mt32emu_set_reverb_enabled(mt32emu_const_context context, const mt32emu_boolean reverb_enabled);
 /** Returns whether wet reverb output is enabled. */
-MT32EMU_EXPORT mt32emu_boolean mt32emu_is_reverb_enabled(mt32emu_const_context context);
+MT32EMU_EXPORT mt32emu_boolean MT32EMU_C_CALL mt32emu_is_reverb_enabled(mt32emu_const_context context);
 /**
  * Sets override reverb mode. In this mode, emulation ignores sysexes (or the related part of them) which control the reverb parameters.
  * This mode is in effect until it is turned off. When the synth is re-opened, the override mode is unchanged but the state
  * of the reverb model is reset to default.
  */
-MT32EMU_EXPORT void mt32emu_set_reverb_overridden(mt32emu_const_context context, const mt32emu_boolean reverb_overridden);
+MT32EMU_EXPORT void MT32EMU_C_CALL mt32emu_set_reverb_overridden(mt32emu_const_context context, const mt32emu_boolean reverb_overridden);
 /** Returns whether reverb settings are overridden. */
-MT32EMU_EXPORT mt32emu_boolean mt32emu_is_reverb_overridden(mt32emu_const_context context);
+MT32EMU_EXPORT mt32emu_boolean MT32EMU_C_CALL mt32emu_is_reverb_overridden(mt32emu_const_context context);
 /**
  * Forces reverb model compatibility mode. By default, the compatibility mode corresponds to the used control ROM version.
  * Invoking this method with the argument set to true forces emulation of old MT-32 reverb circuit.
  * When the argument is false, emulation of the reverb circuit used in new generation of MT-32 compatible modules is enforced
  * (these include CM-32L and LAPC-I).
  */
-MT32EMU_EXPORT void mt32emu_set_reverb_compatibility_mode(mt32emu_const_context context, const mt32emu_boolean mt32_compatible_mode);
+MT32EMU_EXPORT void MT32EMU_C_CALL mt32emu_set_reverb_compatibility_mode(mt32emu_const_context context, const mt32emu_boolean mt32_compatible_mode);
 /** Returns whether reverb is in old MT-32 compatibility mode. */
-MT32EMU_EXPORT mt32emu_boolean mt32emu_is_mt32_reverb_compatibility_mode(mt32emu_const_context context);
+MT32EMU_EXPORT mt32emu_boolean MT32EMU_C_CALL mt32emu_is_mt32_reverb_compatibility_mode(mt32emu_const_context context);
 /** Returns whether default reverb compatibility mode is the old MT-32 compatibility mode. */
-MT32EMU_EXPORT mt32emu_boolean mt32emu_is_default_reverb_mt32_compatible(mt32emu_const_context context);
+MT32EMU_EXPORT mt32emu_boolean MT32EMU_C_CALL mt32emu_is_default_reverb_mt32_compatible(mt32emu_const_context context);
 
 /**
  * If enabled, reverb buffers for all modes are kept around allocated all the time to avoid memory
  * allocating/freeing in the rendering thread, which may be required for realtime operation.
  * Otherwise, reverb buffers that are not in use are deleted to save memory (the default behaviour).
  */
-MT32EMU_EXPORT void mt32emu_preallocate_reverb_memory(mt32emu_const_context context, const mt32emu_boolean enabled);
+MT32EMU_EXPORT void MT32EMU_C_CALL mt32emu_preallocate_reverb_memory(mt32emu_const_context context, const mt32emu_boolean enabled);
 
 /** Sets new DAC input mode. See mt32emu_dac_input_mode for details. */
-MT32EMU_EXPORT void mt32emu_set_dac_input_mode(mt32emu_const_context context, const mt32emu_dac_input_mode mode);
+MT32EMU_EXPORT void MT32EMU_C_CALL mt32emu_set_dac_input_mode(mt32emu_const_context context, const mt32emu_dac_input_mode mode);
 /** Returns current DAC input mode. See mt32emu_dac_input_mode for details. */
-MT32EMU_EXPORT mt32emu_dac_input_mode mt32emu_get_dac_input_mode(mt32emu_const_context context);
+MT32EMU_EXPORT mt32emu_dac_input_mode MT32EMU_C_CALL mt32emu_get_dac_input_mode(mt32emu_const_context context);
 
 /** Sets new MIDI delay mode. See mt32emu_midi_delay_mode for details. */
-MT32EMU_EXPORT void mt32emu_set_midi_delay_mode(mt32emu_const_context context, const mt32emu_midi_delay_mode mode);
+MT32EMU_EXPORT void MT32EMU_C_CALL mt32emu_set_midi_delay_mode(mt32emu_const_context context, const mt32emu_midi_delay_mode mode);
 /** Returns current MIDI delay mode. See mt32emu_midi_delay_mode for details. */
-MT32EMU_EXPORT mt32emu_midi_delay_mode mt32emu_get_midi_delay_mode(mt32emu_const_context context);
+MT32EMU_EXPORT mt32emu_midi_delay_mode MT32EMU_C_CALL mt32emu_get_midi_delay_mode(mt32emu_const_context context);
 
 /**
  * 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.
  */
-MT32EMU_EXPORT void mt32emu_set_output_gain(mt32emu_const_context context, float gain);
+MT32EMU_EXPORT void MT32EMU_C_CALL mt32emu_set_output_gain(mt32emu_const_context context, float gain);
 /** Returns current output gain factor for synth output channels. */
-MT32EMU_EXPORT float mt32emu_get_output_gain(mt32emu_const_context context);
+MT32EMU_EXPORT float MT32EMU_C_CALL mt32emu_get_output_gain(mt32emu_const_context context);
 
 /**
  * Sets output gain factor for the reverb wet output channels. It rather corresponds to the gain of the output
@@ -453,14 +453,34 @@ MT32EMU_EXPORT float mt32emu_get_output_gain(mt32emu_const_context context);
  * 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.
  */
-MT32EMU_EXPORT void mt32emu_set_reverb_output_gain(mt32emu_const_context context, float gain);
+MT32EMU_EXPORT void MT32EMU_C_CALL mt32emu_set_reverb_output_gain(mt32emu_const_context context, float gain);
 /** Returns current output gain factor for reverb wet output channels. */
-MT32EMU_EXPORT float mt32emu_get_reverb_output_gain(mt32emu_const_context context);
+MT32EMU_EXPORT float MT32EMU_C_CALL mt32emu_get_reverb_output_gain(mt32emu_const_context context);
+
+/**
+ * Sets (or removes) an override for the current volume (output level) on a specific part.
+ * When the part volume is overridden, the MIDI controller Volume (7) on the MIDI channel this part is assigned to
+ * has no effect on the output level of this part. Similarly, the output level value set on this part via a SysEx that
+ * modifies the Patch temp structure is disregarded.
+ * To enable the override mode, argument volumeOverride should be in range 0..100, setting a value outside this range
+ * disables the previously set override, if any.
+ * Note: Setting volumeOverride to 0 mutes the part completely, meaning no sound is generated at all.
+ * This is unlike the behaviour of real devices - setting 0 volume on a part may leave it still producing
+ * sound at a very low level.
+ * Argument partNumber should be 0..7 for Part 1..8, or 8 for Rhythm.
+ */
+MT32EMU_EXPORT_V(2.6) void MT32EMU_C_CALL mt32emu_set_part_volume_override(mt32emu_const_context context, mt32emu_bit8u part_number, mt32emu_bit8u volume_override);
+/**
+ * Returns the overridden volume previously set on a specific part; a value outside the range 0..100 means no override
+ * is currently in effect.
+ * Argument partNumber should be 0..7 for Part 1..8, or 8 for Rhythm.
+ */
+MT32EMU_EXPORT_V(2.6) mt32emu_bit8u MT32EMU_C_CALL mt32emu_get_part_volume_override(mt32emu_const_context context, mt32emu_bit8u part_number);
 
 /** Swaps left and right output channels. */
-MT32EMU_EXPORT void mt32emu_set_reversed_stereo_enabled(mt32emu_const_context context, const mt32emu_boolean enabled);
+MT32EMU_EXPORT void MT32EMU_C_CALL mt32emu_set_reversed_stereo_enabled(mt32emu_const_context context, const mt32emu_boolean enabled);
 /** Returns whether left and right output channels are swapped. */
-MT32EMU_EXPORT mt32emu_boolean mt32emu_is_reversed_stereo_enabled(mt32emu_const_context context);
+MT32EMU_EXPORT mt32emu_boolean MT32EMU_C_CALL mt32emu_is_reversed_stereo_enabled(mt32emu_const_context context);
 
 /**
  * Allows to toggle the NiceAmpRamp mode.
@@ -470,9 +490,9 @@ MT32EMU_EXPORT mt32emu_boolean mt32emu_is_reversed_stereo_enabled(mt32emu_const_
  * 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);
+MT32EMU_EXPORT void MT32EMU_C_CALL 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);
+MT32EMU_EXPORT mt32emu_boolean MT32EMU_C_CALL mt32emu_is_nice_amp_ramp_enabled(mt32emu_const_context context);
 
 /**
  * Allows to toggle the NicePanning mode.
@@ -483,9 +503,9 @@ MT32EMU_EXPORT mt32emu_boolean mt32emu_is_nice_amp_ramp_enabled(mt32emu_const_co
  * making it smoother thus sacrificing the emulation accuracy.
  * This mode is disabled by default.
  */
-MT32EMU_EXPORT void mt32emu_set_nice_panning_enabled(mt32emu_const_context context, const mt32emu_boolean enabled);
+MT32EMU_EXPORT void MT32EMU_C_CALL mt32emu_set_nice_panning_enabled(mt32emu_const_context context, const mt32emu_boolean enabled);
 /** Returns whether NicePanning mode is enabled. */
-MT32EMU_EXPORT mt32emu_boolean mt32emu_is_nice_panning_enabled(mt32emu_const_context context);
+MT32EMU_EXPORT mt32emu_boolean MT32EMU_C_CALL mt32emu_is_nice_panning_enabled(mt32emu_const_context context);
 
 /**
  * Allows to toggle the NicePartialMixing mode.
@@ -497,9 +517,9 @@ MT32EMU_EXPORT mt32emu_boolean mt32emu_is_nice_panning_enabled(mt32emu_const_con
  * thus making the behaviour more predictable.
  * This mode is disabled by default.
  */
-MT32EMU_EXPORT void mt32emu_set_nice_partial_mixing_enabled(mt32emu_const_context context, const mt32emu_boolean enabled);
+MT32EMU_EXPORT void MT32EMU_C_CALL mt32emu_set_nice_partial_mixing_enabled(mt32emu_const_context context, const mt32emu_boolean enabled);
 /** Returns whether NicePartialMixing mode is enabled. */
-MT32EMU_EXPORT mt32emu_boolean mt32emu_is_nice_partial_mixing_enabled(mt32emu_const_context context);
+MT32EMU_EXPORT mt32emu_boolean MT32EMU_C_CALL mt32emu_is_nice_partial_mixing_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.
@@ -507,9 +527,9 @@ MT32EMU_EXPORT mt32emu_boolean mt32emu_is_nice_partial_mixing_enabled(mt32emu_co
  * mode of analog circuitry emulation. See mt32emu_analog_output_mode.
  * The length is in frames, not bytes (in 16-bit stereo, one frame is 4 bytes). Uses NATIVE byte ordering.
  */
-MT32EMU_EXPORT void mt32emu_render_bit16s(mt32emu_const_context context, mt32emu_bit16s *stream, mt32emu_bit32u len);
+MT32EMU_EXPORT void MT32EMU_C_CALL mt32emu_render_bit16s(mt32emu_const_context context, mt32emu_bit16s *stream, mt32emu_bit32u len);
 /** Same as above but outputs to a float stereo stream. */
-MT32EMU_EXPORT void mt32emu_render_float(mt32emu_const_context context, float *stream, mt32emu_bit32u len);
+MT32EMU_EXPORT void MT32EMU_C_CALL mt32emu_render_float(mt32emu_const_context context, float *stream, mt32emu_bit32u len);
 
 /**
  * Renders samples to the specified output streams as if they appeared at the DAC entrance.
@@ -517,25 +537,25 @@ MT32EMU_EXPORT void mt32emu_render_float(mt32emu_const_context context, float *s
  * NULL may be specified in place of any or all of the stream buffers to skip it.
  * The length is in samples, not bytes. Uses NATIVE byte ordering.
  */
-MT32EMU_EXPORT void mt32emu_render_bit16s_streams(mt32emu_const_context context, const mt32emu_dac_output_bit16s_streams *streams, mt32emu_bit32u len);
+MT32EMU_EXPORT void MT32EMU_C_CALL mt32emu_render_bit16s_streams(mt32emu_const_context context, const mt32emu_dac_output_bit16s_streams *streams, mt32emu_bit32u len);
 /** Same as above but outputs to float streams. */
-MT32EMU_EXPORT void mt32emu_render_float_streams(mt32emu_const_context context, const mt32emu_dac_output_float_streams *streams, mt32emu_bit32u len);
+MT32EMU_EXPORT void MT32EMU_C_CALL mt32emu_render_float_streams(mt32emu_const_context context, const mt32emu_dac_output_float_streams *streams, mt32emu_bit32u len);
 
 /** Returns true when there is at least one active partial, otherwise false. */
-MT32EMU_EXPORT mt32emu_boolean mt32emu_has_active_partials(mt32emu_const_context context);
+MT32EMU_EXPORT mt32emu_boolean MT32EMU_C_CALL mt32emu_has_active_partials(mt32emu_const_context context);
 
 /** Returns true if mt32emu_has_active_partials() returns true, or reverb is (somewhat unreliably) detected as being active. */
-MT32EMU_EXPORT mt32emu_boolean mt32emu_is_active(mt32emu_const_context context);
+MT32EMU_EXPORT mt32emu_boolean MT32EMU_C_CALL mt32emu_is_active(mt32emu_const_context context);
 
 /** Returns the maximum number of partials playing simultaneously. */
-MT32EMU_EXPORT mt32emu_bit32u mt32emu_get_partial_count(mt32emu_const_context context);
+MT32EMU_EXPORT mt32emu_bit32u MT32EMU_C_CALL mt32emu_get_partial_count(mt32emu_const_context context);
 
 /**
  * Returns current states of all the parts as a bit set. The least significant bit corresponds to the state of part 1,
  * total of 9 bits hold the states of all the parts. If the returned bit for a part is set, 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.
  */
-MT32EMU_EXPORT mt32emu_bit32u mt32emu_get_part_states(mt32emu_const_context context);
+MT32EMU_EXPORT mt32emu_bit32u MT32EMU_C_CALL mt32emu_get_part_states(mt32emu_const_context context);
 
 /**
  * Fills in current states of all the partials into the array provided. Each byte in the array holds states of 4 partials
@@ -543,7 +563,7 @@ MT32EMU_EXPORT mt32emu_bit32u mt32emu_get_part_states(mt32emu_const_context cont
  * The array must be large enough to accommodate states of all the partials.
  * @see getPartialCount()
  */
-MT32EMU_EXPORT void mt32emu_get_partial_states(mt32emu_const_context context, mt32emu_bit8u *partial_states);
+MT32EMU_EXPORT void MT32EMU_C_CALL mt32emu_get_partial_states(mt32emu_const_context context, mt32emu_bit8u *partial_states);
 
 /**
  * Fills in information about currently playing notes on the specified part into the arrays provided. The arrays must be large enough
@@ -551,16 +571,71 @@ MT32EMU_EXPORT void mt32emu_get_partial_states(mt32emu_const_context context, mt
  * 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.
  */
-MT32EMU_EXPORT mt32emu_bit32u mt32emu_get_playing_notes(mt32emu_const_context context, mt32emu_bit8u part_number, mt32emu_bit8u *keys, mt32emu_bit8u *velocities);
+MT32EMU_EXPORT mt32emu_bit32u MT32EMU_C_CALL mt32emu_get_playing_notes(mt32emu_const_context context, mt32emu_bit8u part_number, mt32emu_bit8u *keys, mt32emu_bit8u *velocities);
 
 /**
  * Returns name of the patch set on the specified part.
  * Argument partNumber should be 0..7 for Part 1..8, or 8 for Rhythm.
+ * The returned value is a null-terminated string which is guaranteed to remain valid until the next call to one of functions
+ * that perform sample rendering or immediate SysEx processing (e.g. mt32emu_play_sysex_now).
  */
-MT32EMU_EXPORT const char *mt32emu_get_patch_name(mt32emu_const_context context, mt32emu_bit8u part_number);
+MT32EMU_EXPORT const char * MT32EMU_C_CALL mt32emu_get_patch_name(mt32emu_const_context context, mt32emu_bit8u part_number);
+
+/**
+ * Retrieves the name of the sound group the timbre identified by arguments timbre_group and timbre_number is associated with.
+ * Values 0-3 of timbre_group correspond to the timbre banks GROUP A, GROUP B, MEMORY and RHYTHM.
+ * For all but the RHYTHM timbre bank, allowed values of timbre_number are in range 0-63. The number of timbres
+ * contained in the RHYTHM bank depends on the used control ROM version.
+ * The argument sound_group_name must point to an array of at least 8 characters. The result is a null-terminated string.
+ * Returns whether the specified timbre has been found and the result written in sound_group_name.
+ */
+MT32EMU_EXPORT_V(2.7) mt32emu_boolean MT32EMU_C_CALL mt32emu_get_sound_group_name(mt32emu_const_context context, char *sound_group_name, mt32emu_bit8u timbre_group, mt32emu_bit8u timbre_number);
+/**
+ * Retrieves the name of the timbre identified by arguments timbre_group and timbre_number.
+ * Values 0-3 of timbre_group correspond to the timbre banks GROUP A, GROUP B, MEMORY and RHYTHM.
+ * For all but the RHYTHM timbre bank, allowed values of timbre_number are in range 0-63. The number of timbres
+ * contained in the RHYTHM bank depends on the used control ROM version.
+ * The argument sound_name must point to an array of at least 11 characters. The result is a null-terminated string.
+ * Returns whether the specified timbre has been found and the result written in sound_name.
+ */
+MT32EMU_EXPORT_V(2.7) mt32emu_boolean MT32EMU_C_CALL mt32emu_get_sound_name(mt32emu_const_context context, char *sound_name, mt32emu_bit8u timbreGroup, mt32emu_bit8u timbreNumber);
 
 /** Stores internal state of emulated synth into an array provided (as it would be acquired from hardware). */
-MT32EMU_EXPORT void mt32emu_read_memory(mt32emu_const_context context, mt32emu_bit32u addr, mt32emu_bit32u len, mt32emu_bit8u *data);
+MT32EMU_EXPORT void MT32EMU_C_CALL mt32emu_read_memory(mt32emu_const_context context, mt32emu_bit32u addr, mt32emu_bit32u len, mt32emu_bit8u *data);
+
+/**
+ * Retrieves the current state of the emulated MT-32 display facilities.
+ * Typically, the state is updated during the rendering. When that happens, a related callback from mt32emu_report_handler_i_v1
+ * is invoked. However, there might be no need to invoke this method after each update, e.g. when the render buffer is just
+ * a few milliseconds long.
+ * The argument target_buffer must point to an array of at least 21 characters. The result is a null-terminated string.
+ * The argument narrow_lcd enables a condensed representation of the displayed information in some cases. This is mainly intended
+ * to route the result to a hardware LCD that is only 16 characters wide. Automatic scrolling of longer strings is not supported.
+ * Returns whether the MIDI MESSAGE LED is ON and fills the target_buffer parameter.
+ */
+MT32EMU_EXPORT_V(2.6) mt32emu_boolean MT32EMU_C_CALL mt32emu_get_display_state(mt32emu_const_context context, char *target_buffer, const mt32emu_boolean narrow_lcd);
+
+/**
+ * Resets the emulated LCD to the main mode (Master Volume). This has the same effect as pressing the Master Volume button
+ * while the display shows some other message. Useful for the new-gen devices as those require a special Display Reset SysEx
+ * to return to the main mode e.g. from showing a custom display message or a checksum error.
+ */
+MT32EMU_EXPORT_V(2.6) void MT32EMU_C_CALL mt32emu_set_main_display_mode(mt32emu_const_context context);
+
+/**
+ * Permits to select an arbitrary display emulation model that does not necessarily match the actual behaviour implemented
+ * in the control ROM version being used.
+ * Invoking this method with the argument set to true forces emulation of the old-gen MT-32 display features.
+ * Otherwise, emulation of the new-gen devices is enforced (these include CM-32L and LAPC-I as if these were connected to an LCD).
+ */
+MT32EMU_EXPORT_V(2.6) void MT32EMU_C_CALL mt32emu_set_display_compatibility(mt32emu_const_context context, mt32emu_boolean old_mt32_compatibility_enabled);
+/** Returns whether the currently configured features of the emulated display are compatible with the old-gen MT-32 devices. */
+MT32EMU_EXPORT_V(2.6) mt32emu_boolean MT32EMU_C_CALL mt32emu_is_display_old_mt32_compatible(mt32emu_const_context context);
+/**
+ * Returns whether the emulated display features configured by default depending on the actual control ROM version
+ * are compatible with the old-gen MT-32 devices.
+ */
+MT32EMU_EXPORT_V(2.6) mt32emu_boolean MT32EMU_C_CALL mt32emu_is_default_display_old_mt32_compatible(mt32emu_const_context context);
 
 #ifdef __cplusplus
 } // extern "C"
diff --git a/audio/softsynth/mt32/c_interface/c_types.h b/audio/softsynth/mt32/c_interface/c_types.h
index 6bcf0775538..8928bfeae3b 100644
--- a/audio/softsynth/mt32/c_interface/c_types.h
+++ b/audio/softsynth/mt32/c_interface/c_types.h
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 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
@@ -27,6 +27,12 @@
 #include "../Enumerations.h"
 #undef MT32EMU_C_ENUMERATIONS
 
+#ifdef _WIN32
+#  define MT32EMU_C_CALL __cdecl
+#else
+#  define MT32EMU_C_CALL
+#endif
+
 typedef unsigned int       mt32emu_bit32u;
 typedef   signed int       mt32emu_bit32s;
 typedef unsigned short int mt32emu_bit16u;
@@ -111,7 +117,8 @@ typedef struct {
 /** Report handler interface versions */
 typedef enum {
 	MT32EMU_REPORT_HANDLER_VERSION_0 = 0,
-	MT32EMU_REPORT_HANDLER_VERSION_CURRENT = MT32EMU_REPORT_HANDLER_VERSION_0
+	MT32EMU_REPORT_HANDLER_VERSION_1 = 1,
+	MT32EMU_REPORT_HANDLER_VERSION_CURRENT = MT32EMU_REPORT_HANDLER_VERSION_1
 } mt32emu_report_handler_version;
 
 /** MIDI receiver interface versions */
@@ -127,7 +134,9 @@ typedef enum {
 	MT32EMU_SERVICE_VERSION_2 = 2,
 	MT32EMU_SERVICE_VERSION_3 = 3,
 	MT32EMU_SERVICE_VERSION_4 = 4,
-	MT32EMU_SERVICE_VERSION_CURRENT = MT32EMU_SERVICE_VERSION_4
+	MT32EMU_SERVICE_VERSION_5 = 5,
+	MT32EMU_SERVICE_VERSION_6 = 6,
+	MT32EMU_SERVICE_VERSION_CURRENT = MT32EMU_SERVICE_VERSION_6
 } mt32emu_service_version;
 
 /* === Report Handler Interface === */
@@ -135,42 +144,59 @@ typedef enum {
 typedef union mt32emu_report_handler_i mt32emu_report_handler_i;
 
 /** Interface for handling reported events (initial version) */
-typedef struct {
-	/** Returns the actual interface version ID */
-	mt32emu_report_handler_version (*getVersionID)(mt32emu_report_handler_i i);
-
-	/** Callback for debug messages, in vprintf() format */
-	void (*printDebug)(void *instance_data, const char *fmt, va_list list);
-	/** Callbacks for reporting errors */
-	void (*onErrorControlROM)(void *instance_data);
-	void (*onErrorPCMROM)(void *instance_data);
-	/** Callback for reporting about displaying a new custom message on LCD */
-	void (*showLCDMessage)(void *instance_data, const char *message);
-	/** Callback for reporting actual processing of a MIDI message */
-	void (*onMIDIMessagePlayed)(void *instance_data);
+#define MT32EMU_REPORT_HANDLER_I_V0 \
+	/** Returns the actual interface version ID */ \
+	mt32emu_report_handler_version (MT32EMU_C_CALL *getVersionID)(mt32emu_report_handler_i i); \
+\
+	/** Callback for debug messages, in vprintf() format */ \
+	void (MT32EMU_C_CALL *printDebug)(void *instance_data, const char *fmt, va_list list); \
+	/** Callbacks for reporting errors */ \
+	void (MT32EMU_C_CALL *onErrorControlROM)(void *instance_data); \
+	void (MT32EMU_C_CALL *onErrorPCMROM)(void *instance_data); \
+	/** Callback for reporting about displaying a new custom message on LCD */ \
+	void (MT32EMU_C_CALL *showLCDMessage)(void *instance_data, const char *message); \
+	/** Callback for reporting actual processing of a MIDI message */ \
+	void (MT32EMU_C_CALL *onMIDIMessagePlayed)(void *instance_data); \
 	/**
 	 * Callback for reporting an overflow of the input MIDI queue.
 	 * Returns MT32EMU_BOOL_TRUE if a recovery action was taken
 	 * and yet another attempt to enqueue the MIDI event is desired.
-	 */
-	mt32emu_boolean (*onMIDIQueueOverflow)(void *instance_data);
+	 */ \
+	mt32emu_boolean (MT32EMU_C_CALL *onMIDIQueueOverflow)(void *instance_data); \
 	/**
 	 * Callback invoked when a System Realtime MIDI message is detected in functions
 	 * mt32emu_parse_stream and mt32emu_play_short_message and the likes.
-	 */
-	void (*onMIDISystemRealtime)(void *instance_data, mt32emu_bit8u system_realtime);
-	/** Callbacks for reporting system events */
-	void (*onDeviceReset)(void *instance_data);
-	void (*onDeviceReconfig)(void *instance_data);
-	/** Callbacks for reporting changes of reverb settings */
-	void (*onNewReverbMode)(void *instance_data, mt32emu_bit8u mode);
-	void (*onNewReverbTime)(void *instance_data, mt32emu_bit8u time);
-	void (*onNewReverbLevel)(void *instance_data, mt32emu_bit8u level);
-	/** Callbacks for reporting various information */
-	void (*onPolyStateChanged)(void *instance_data, mt32emu_bit8u part_num);
-	void (*onProgramChanged)(void *instance_data, mt32emu_bit8u part_num, const char *sound_group_name, const char *patch_name);
+	 */ \
+	void (MT32EMU_C_CALL *onMIDISystemRealtime)(void *instance_data, mt32emu_bit8u system_realtime); \
+	/** Callbacks for reporting system events */ \
+	void (MT32EMU_C_CALL *onDeviceReset)(void *instance_data); \
+	void (MT32EMU_C_CALL *onDeviceReconfig)(void *instance_data); \
+	/** Callbacks for reporting changes of reverb settings */ \
+	void (MT32EMU_C_CALL *onNewReverbMode)(void *instance_data, mt32emu_bit8u mode); \
+	void (MT32EMU_C_CALL *onNewReverbTime)(void *instance_data, mt32emu_bit8u time); \
+	void (MT32EMU_C_CALL *onNewReverbLevel)(void *instance_data, mt32emu_bit8u level); \
+	/** Callbacks for reporting various information */ \
+	void (MT32EMU_C_CALL *onPolyStateChanged)(void *instance_data, mt32emu_bit8u part_num); \
+	void (MT32EMU_C_CALL *onProgramChanged)(void *instance_data, mt32emu_bit8u part_num, const char *sound_group_name, const char *patch_name);
+
+#define MT32EMU_REPORT_HANDLER_I_V1 \
+	/**
+	 * Invoked to signal about a change of the emulated LCD state. Use mt32emu_get_display_state to retrieve the actual data.
+	 * This callback will not be invoked on further changes, until the client retrieves the LCD state.
+	 */ \
+	void (MT32EMU_C_CALL *onLCDStateUpdated)(void *instance_data); \
+	/** Invoked when the emulated MIDI MESSAGE LED changes state. The led_state parameter represents whether the LED is ON. */ \
+	void (MT32EMU_C_CALL *onMidiMessageLEDStateUpdated)(void *instance_data, mt32emu_boolean led_state);
+
+typedef struct {
+	MT32EMU_REPORT_HANDLER_I_V0
 } mt32emu_report_handler_i_v0;
 
+typedef struct {
+	MT32EMU_REPORT_HANDLER_I_V0
+	MT32EMU_REPORT_HANDLER_I_V1
+} mt32emu_report_handler_i_v1;
+
 /**
  * Extensible interface for handling reported events.
  * Union intended to view an interface of any subsequent version as any parent interface not requiring a cast.
@@ -178,8 +204,12 @@ typedef struct {
  */
 union mt32emu_report_handler_i {
 	const mt32emu_report_handler_i_v0 *v0;
+	const mt32emu_report_handler_i_v1 *v1;
 };
 
+#undef MT32EMU_REPORT_HANDLER_I_V0
+#undef MT32EMU_REPORT_HANDLER_I_V1
+
 /* === MIDI Receiver Interface === */
 
 typedef union mt32emu_midi_receiver_i mt32emu_midi_receiver_i;
@@ -187,16 +217,16 @@ typedef union mt32emu_midi_receiver_i mt32emu_midi_receiver_i;
 /** Interface for receiving MIDI messages generated by MIDI stream parser (initial version) */
 typedef struct {
 	/** Returns the actual interface version ID */
-	mt32emu_midi_receiver_version (*getVersionID)(mt32emu_midi_receiver_i i);
+	mt32emu_midi_receiver_version (MT32EMU_C_CALL *getVersionID)(mt32emu_midi_receiver_i i);
 
 	/** Invoked when a complete short MIDI message is parsed in the input MIDI stream. */
-	void (*handleShortMessage)(void *instance_data, const mt32emu_bit32u message);
+	void (MT32EMU_C_CALL *handleShortMessage)(void *instance_data, const mt32emu_bit32u message);
 
 	/** Invoked when a complete well-formed System Exclusive MIDI message is parsed in the input MIDI stream. */
-	void (*handleSysex)(void *instance_data, const mt32emu_bit8u stream[], const mt32emu_bit32u length);
+	void (MT32EMU_C_CALL *handleSysex)(void *instance_data, const mt32emu_bit8u stream[], const mt32emu_bit32u length);
 
 	/** Invoked when a System Realtime MIDI message is parsed in the input MIDI stream. */
-	void (*handleSystemRealtimeMessage)(void *instance_data, const mt32emu_bit8u realtime);
+	void (MT32EMU_C_CALL *handleSystemRealtimeMessage)(void *instance_data, const mt32emu_bit8u realtime);
 } mt32emu_midi_receiver_i_v0;
 
 /**
@@ -221,111 +251,124 @@ typedef union mt32emu_service_i mt32emu_service_i;
  */
 #define MT32EMU_SERVICE_I_V0 \
 	/** Returns the actual interface version ID */ \
-	mt32emu_service_version (*getVersionID)(mt32emu_service_i i); \
-	mt32emu_report_handler_version (*getSupportedReportHandlerVersionID)(void); \
-	mt32emu_midi_receiver_version (*getSupportedMIDIReceiverVersionID)(void); \
+	mt32emu_service_version (MT32EMU_C_CALL *getVersionID)(mt32emu_service_i i); \
+	mt32emu_report_handler_version (MT32EMU_C_CALL *getSupportedReportHandlerVersionID)(void); \
+	mt32emu_midi_receiver_version (MT32EMU_C_CALL *getSupportedMIDIReceiverVersionID)(void); \
 \
-	mt32emu_bit32u (*getLibraryVersionInt)(void); \
-	const char *(*getLibraryVersionString)(void); \
+	mt32emu_bit32u (MT32EMU_C_CALL *getLibraryVersionInt)(void); \
+	const char *(MT32EMU_C_CALL *getLibraryVersionString)(void); \
 \
-	mt32emu_bit32u (*getStereoOutputSamplerate)(const mt32emu_analog_output_mode analog_output_mode); \
+	mt32emu_bit32u (MT32EMU_C_CALL *getStereoOutputSamplerate)(const mt32emu_analog_output_mode analog_output_mode); \
 \
-	mt32emu_context (*createContext)(mt32emu_report_handler_i report_handler, void *instance_data); \
-	void (*freeContext)(mt32emu_context context); \
-	mt32emu_return_code (*addROMData)(mt32emu_context context, const mt32emu_bit8u *data, size_t data_size, const mt32emu_sha1_digest *sha1_digest); \
-	mt32emu_return_code (*addROMFile)(mt32emu_context context, const char *filename); \
-	void (*getROMInfo)(mt32emu_const_context context, mt32emu_rom_info *rom_info); \
-	void (*setPartialCount)(mt32emu_context context, const mt32emu_bit32u partial_count); \
-	void (*setAnalogOutputMode)(mt32emu_context context, const mt32emu_analog_output_mode analog_output_mode); \
-	mt32emu_return_code (*openSynth)(mt32emu_const_context context); \
-	void (*closeSynth)(mt32emu_const_context context); \
-	mt32emu_boolean (*isOpen)(mt32emu_const_context context); \
-	mt32emu_bit32u (*getActualStereoOutputSamplerate)(mt32emu_const_context context); \
-	void (*flushMIDIQueue)(mt32emu_const_context context); \
-	mt32emu_bit32u (*setMIDIEventQueueSize)(mt32emu_const_context context, const mt32emu_bit32u queue_size); \
-	void (*setMIDIReceiver)(mt32emu_context context, mt32emu_midi_receiver_i midi_receiver, void *instance_data); \
+	mt32emu_context (MT32EMU_C_CALL *createContext)(mt32emu_report_handler_i report_handler, void *instance_data); \
+	void (MT32EMU_C_CALL *freeContext)(mt32emu_context context); \
+	mt32emu_return_code (MT32EMU_C_CALL *addROMData)(mt32emu_context context, const mt32emu_bit8u *data, size_t data_size, const mt32emu_sha1_digest *sha1_digest); \
+	mt32emu_return_code (MT32EMU_C_CALL *addROMFile)(mt32emu_context context, const char *filename); \
+	void (MT32EMU_C_CALL *getROMInfo)(mt32emu_const_context context, mt32emu_rom_info *rom_info); \
+	void (MT32EMU_C_CALL *setPartialCount)(mt32emu_context context, const mt32emu_bit32u partial_count); \
+	void (MT32EMU_C_CALL *setAnalogOutputMode)(mt32emu_context context, const mt32emu_analog_output_mode analog_output_mode); \
+	mt32emu_return_code (MT32EMU_C_CALL *openSynth)(mt32emu_const_context context); \
+	void (MT32EMU_C_CALL *closeSynth)(mt32emu_const_context context); \
+	mt32emu_boolean (MT32EMU_C_CALL *isOpen)(mt32emu_const_context context); \
+	mt32emu_bit32u (MT32EMU_C_CALL *getActualStereoOutputSamplerate)(mt32emu_const_context context); \
+	void (MT32EMU_C_CALL *flushMIDIQueue)(mt32emu_const_context context); \
+	mt32emu_bit32u (MT32EMU_C_CALL *setMIDIEventQueueSize)(mt32emu_const_context context, const mt32emu_bit32u queue_size); \
+	void (MT32EMU_C_CALL *setMIDIReceiver)(mt32emu_context context, mt32emu_midi_receiver_i midi_receiver, void *instance_data); \
 \
-	void (*parseStream)(mt32emu_const_context context, const mt32emu_bit8u *stream, mt32emu_bit32u length); \
-	void (*parseStream_At)(mt32emu_const_context context, const mt32emu_bit8u *stream, mt32emu_bit32u length, mt32emu_bit32u timestamp); \
-	void (*playShortMessage)(mt32emu_const_context context, mt32emu_bit32u message); \
-	void (*playShortMessageAt)(mt32emu_const_context context, mt32emu_bit32u message, mt32emu_bit32u timestamp); \
-	mt32emu_return_code (*playMsg)(mt32emu_const_context context, mt32emu_bit32u msg); \
-	mt32emu_return_code (*playSysex)(mt32emu_const_context context, const mt32emu_bit8u *sysex, mt32emu_bit32u len); \
-	mt32emu_return_code (*playMsgAt)(mt32emu_const_context context, mt32emu_bit32u msg, mt32emu_bit32u timestamp); \
-	mt32emu_return_code (*playSysexAt)(mt32emu_const_context context, const mt32emu_bit8u *sysex, mt32emu_bit32u len, mt32emu_bit32u timestamp); \
+	void (MT32EMU_C_CALL *parseStream)(mt32emu_const_context context, const mt32emu_bit8u *stream, mt32emu_bit32u length); \
+	void (MT32EMU_C_CALL *parseStream_At)(mt32emu_const_context context, const mt32emu_bit8u *stream, mt32emu_bit32u length, mt32emu_bit32u timestamp); \
+	void (MT32EMU_C_CALL *playShortMessage)(mt32emu_const_context context, mt32emu_bit32u message); \
+	void (MT32EMU_C_CALL *playShortMessageAt)(mt32emu_const_context context, mt32emu_bit32u message, mt32emu_bit32u timestamp); \
+	mt32emu_return_code (MT32EMU_C_CALL *playMsg)(mt32emu_const_context context, mt32emu_bit32u msg); \
+	mt32emu_return_code (MT32EMU_C_CALL *playSysex)(mt32emu_const_context context, const mt32emu_bit8u *sysex, mt32emu_bit32u len); \
+	mt32emu_return_code (MT32EMU_C_CALL *playMsgAt)(mt32emu_const_context context, mt32emu_bit32u msg, mt32emu_bit32u timestamp); \
+	mt32emu_return_code (MT32EMU_C_CALL *playSysexAt)(mt32emu_const_context context, const mt32emu_bit8u *sysex, mt32emu_bit32u len, mt32emu_bit32u timestamp); \
 \
-	void (*playMsgNow)(mt32emu_const_context context, mt32emu_bit32u msg); \
-	void (*playMsgOnPart)(mt32emu_const_context context, mt32emu_bit8u part, mt32emu_bit8u code, mt32emu_bit8u note, mt32emu_bit8u velocity); \
-	void (*playSysexNow)(mt32emu_const_context context, const mt32emu_bit8u *sysex, mt32emu_bit32u len); \
-	void (*writeSysex)(mt32emu_const_context context, mt32emu_bit8u channel, const mt32emu_bit8u *sysex, mt32emu_bit32u len); \
+	void (MT32EMU_C_CALL *playMsgNow)(mt32emu_const_context context, mt32emu_bit32u msg); \
+	void (MT32EMU_C_CALL *playMsgOnPart)(mt32emu_const_context context, mt32emu_bit8u part, mt32emu_bit8u code, mt32emu_bit8u note, mt32emu_bit8u velocity); \
+	void (MT32EMU_C_CALL *playSysexNow)(mt32emu_const_context context, const mt32emu_bit8u *sysex, mt32emu_bit32u len); \
+	void (MT32EMU_C_CALL *writeSysex)(mt32emu_const_context context, mt32emu_bit8u channel, const mt32emu_bit8u *sysex, mt32emu_bit32u len); \
 \
-	void (*setReverbEnabled)(mt32emu_const_context context, const mt32emu_boolean reverb_enabled); \
-	mt32emu_boolean (*isReverbEnabled)(mt32emu_const_context context); \
-	void (*setReverbOverridden)(mt32emu_const_context context, const mt32emu_boolean reverb_overridden); \
-	mt32emu_boolean (*isReverbOverridden)(mt32emu_const_context context); \
-	void (*setReverbCompatibilityMode)(mt32emu_const_context context, const mt32emu_boolean mt32_compatible_mode); \
-	mt32emu_boolean (*isMT32ReverbCompatibilityMode)(mt32emu_const_context context); \
-	mt32emu_boolean (*isDefaultReverbMT32Compatible)(mt32emu_const_context context); \
+	void (MT32EMU_C_CALL *setReverbEnabled)(mt32emu_const_context context, const mt32emu_boolean reverb_enabled); \
+	mt32emu_boolean (MT32EMU_C_CALL *isReverbEnabled)(mt32emu_const_context context); \
+	void (MT32EMU_C_CALL *setReverbOverridden)(mt32emu_const_context context, const mt32emu_boolean reverb_overridden); \
+	mt32emu_boolean (MT32EMU_C_CALL *isReverbOverridden)(mt32emu_const_context context); \
+	void (MT32EMU_C_CALL *setReverbCompatibilityMode)(mt32emu_const_context context, const mt32emu_boolean mt32_compatible_mode); \
+	mt32emu_boolean (MT32EMU_C_CALL *isMT32ReverbCompatibilityMode)(mt32emu_const_context context); \
+	mt32emu_boolean (MT32EMU_C_CALL *isDefaultReverbMT32Compatible)(mt32emu_const_context context); \
 \
-	void (*setDACInputMode)(mt32emu_const_context context, const mt32emu_dac_input_mode mode); \
-	mt32emu_dac_input_mode (*getDACInputMode)(mt32emu_const_context context); \
+	void (MT32EMU_C_CALL *setDACInputMode)(mt32emu_const_context context, const mt32emu_dac_input_mode mode); \
+	mt32emu_dac_input_mode (MT32EMU_C_CALL *getDACInputMode)(mt32emu_const_context context); \
 \
-	void (*setMIDIDelayMode)(mt32emu_const_context context, const mt32emu_midi_delay_mode mode); \
-	mt32emu_midi_delay_mode (*getMIDIDelayMode)(mt32emu_const_context context); \
+	void (MT32EMU_C_CALL *setMIDIDelayMode)(mt32emu_const_context context, const mt32emu_midi_delay_mode mode); \
+	mt32emu_midi_delay_mode (MT32EMU_C_CALL *getMIDIDelayMode)(mt32emu_const_context context); \
 \
-	void (*setOutputGain)(mt32emu_const_context context, float gain); \
-	float (*getOutputGain)(mt32emu_const_context context); \
-	void (*setReverbOutputGain)(mt32emu_const_context context, float gain); \
-	float (*getReverbOutputGain)(mt32emu_const_context context); \
+	void (MT32EMU_C_CALL *setOutputGain)(mt32emu_const_context context, float gain); \
+	float (MT32EMU_C_CALL *getOutputGain)(mt32emu_const_context context); \
+	void (MT32EMU_C_CALL *setReverbOutputGain)(mt32emu_const_context context, float gain); \
+	float (MT32EMU_C_CALL *getReverbOutputGain)(mt32emu_const_context context); \
 \
-	void (*setReversedStereoEnabled)(mt32emu_const_context context, const mt32emu_boolean enabled); \
-	mt32emu_boolean (*isReversedStereoEnabled)(mt32emu_const_context context); \
+	void (MT32EMU_C_CALL *setReversedStereoEnabled)(mt32emu_const_context context, const mt32emu_boolean enabled); \
+	mt32emu_boolean (MT32EMU_C_CALL *isReversedStereoEnabled)(mt32emu_const_context context); \
 \
-	void (*renderBit16s)(mt32emu_const_context context, mt32emu_bit16s *stream, mt32emu_bit32u len); \
-	void (*renderFloat)(mt32emu_const_context context, float *stream, mt32emu_bit32u len); \
-	void (*renderBit16sStreams)(mt32emu_const_context context, const mt32emu_dac_output_bit16s_streams *streams, mt32emu_bit32u len); \
-	void (*renderFloatStreams)(mt32emu_const_context context, const mt32emu_dac_output_float_streams *streams, mt32emu_bit32u len); \
+	void (MT32EMU_C_CALL *renderBit16s)(mt32emu_const_context context, mt32emu_bit16s *stream, mt32emu_bit32u len); \
+	void (MT32EMU_C_CALL *renderFloat)(mt32emu_const_context context, float *stream, mt32emu_bit32u len); \
+	void (MT32EMU_C_CALL *renderBit16sStreams)(mt32emu_const_context context, const mt32emu_dac_output_bit16s_streams *streams, mt32emu_bit32u len); \
+	void (MT32EMU_C_CALL *renderFloatStreams)(mt32emu_const_context context, const mt32emu_dac_output_float_streams *streams, mt32emu_bit32u len); \
 \
-	mt32emu_boolean (*hasActivePartials)(mt32emu_const_context context); \
-	mt32emu_boolean (*isActive)(mt32emu_const_context context); \
-	mt32emu_bit32u (*getPartialCount)(mt32emu_const_context context); \
-	mt32emu_bit32u (*getPartStates)(mt32emu_const_context context); \
-	void (*getPartialStates)(mt32emu_const_context context, mt32emu_bit8u *partial_states); \
-	mt32emu_bit32u (*getPlayingNotes)(mt32emu_const_context context, mt32emu_bit8u part_number, mt32emu_bit8u *keys, mt32emu_bit8u *velocities); \
-	const char *(*getPatchName)(mt32emu_const_context context, mt32emu_bit8u part_number); \
-	void (*readMemory)(mt32emu_const_context context, mt32emu_bit32u addr, mt32emu_bit32u len, mt32emu_bit8u *data);
+	mt32emu_boolean (MT32EMU_C_CALL *hasActivePartials)(mt32emu_const_context context); \
+	mt32emu_boolean (MT32EMU_C_CALL *isActive)(mt32emu_const_context context); \
+	mt32emu_bit32u (MT32EMU_C_CALL *getPartialCount)(mt32emu_const_context context); \
+	mt32emu_bit32u (MT32EMU_C_CALL *getPartStates)(mt32emu_const_context context); \
+	void (MT32EMU_C_CALL *getPartialStates)(mt32emu_const_context context, mt32emu_bit8u *partial_states); \
+	mt32emu_bit32u (MT32EMU_C_CALL *getPlayingNotes)(mt32emu_const_context context, mt32emu_bit8u part_number, mt32emu_bit8u *keys, mt32emu_bit8u *velocities); \
+	const char *(MT32EMU_C_CALL *getPatchName)(mt32emu_const_context context, mt32emu_bit8u part_number); \
+	void (MT32EMU_C_CALL *readMemory)(mt32emu_const_context context, mt32emu_bit32u addr, mt32emu_bit32u len, mt32emu_bit8u *data);
 
 #define MT32EMU_SERVICE_I_V1 \
-	mt32emu_analog_output_mode (*getBestAnalogOutputMode)(const double target_samplerate); \
-	void (*setStereoOutputSampleRate)(mt32emu_context context, const double samplerate); \
-	void (*setSamplerateConversionQuality)(mt32emu_context context, const mt32emu_samplerate_conversion_quality quality); \
-	void (*selectRendererType)(mt32emu_context context, mt32emu_renderer_type renderer_type); \
-	mt32emu_renderer_type (*getSelectedRendererType)(mt32emu_context context); \
-	mt32emu_bit32u (*convertOutputToSynthTimestamp)(mt32emu_const_context context, mt32emu_bit32u output_timestamp); \
-	mt32emu_bit32u (*convertSynthToOutputTimestamp)(mt32emu_const_context context, mt32emu_bit32u synth_timestamp);
+	mt32emu_analog_output_mode (MT32EMU_C_CALL *getBestAnalogOutputMode)(const double target_samplerate); \
+	void (MT32EMU_C_CALL *setStereoOutputSampleRate)(mt32emu_context context, const double samplerate); \
+	void (MT32EMU_C_CALL *setSamplerateConversionQuality)(mt32emu_context context, const mt32emu_samplerate_conversion_quality quality); \
+	void (MT32EMU_C_CALL *selectRendererType)(mt32emu_context context, mt32emu_renderer_type renderer_type); \
+	mt32emu_renderer_type (MT32EMU_C_CALL *getSelectedRendererType)(mt32emu_context context); \
+	mt32emu_bit32u (MT32EMU_C_CALL *convertOutputToSynthTimestamp)(mt32emu_const_context context, mt32emu_bit32u output_timestamp); \
+	mt32emu_bit32u (MT32EMU_C_CALL *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);
+	mt32emu_bit32u (MT32EMU_C_CALL *getInternalRenderedSampleCount)(mt32emu_const_context context); \
+	void (MT32EMU_C_CALL *setNiceAmpRampEnabled)(mt32emu_const_context context, const mt32emu_boolean enabled); \
+	mt32emu_boolean (MT32EMU_C_CALL *isNiceAmpRampEnabled)(mt32emu_const_context context);
 
 #define MT32EMU_SERVICE_I_V3 \
-	void (*setNicePanningEnabled)(mt32emu_const_context context, const mt32emu_boolean enabled); \
-	mt32emu_boolean (*isNicePanningEnabled)(mt32emu_const_context context); \
-	void (*setNicePartialMixingEnabled)(mt32emu_const_context context, const mt32emu_boolean enabled); \
-	mt32emu_boolean (*isNicePartialMixingEnabled)(mt32emu_const_context context); \
-	void (*preallocateReverbMemory)(mt32emu_const_context context, const mt32emu_boolean enabled); \
-	void (*configureMIDIEventQueueSysexStorage)(mt32emu_const_context context, const mt32emu_bit32u storage_buffer_size);
+	void (MT32EMU_C_CALL *setNicePanningEnabled)(mt32emu_const_context context, const mt32emu_boolean enabled); \
+	mt32emu_boolean (MT32EMU_C_CALL *isNicePanningEnabled)(mt32emu_const_context context); \
+	void (MT32EMU_C_CALL *setNicePartialMixingEnabled)(mt32emu_const_context context, const mt32emu_boolean enabled); \
+	mt32emu_boolean (MT32EMU_C_CALL *isNicePartialMixingEnabled)(mt32emu_const_context context); \
+	void (MT32EMU_C_CALL *preallocateReverbMemory)(mt32emu_const_context context, const mt32emu_boolean enabled); \
+	void (MT32EMU_C_CALL *configureMIDIEventQueueSysexStorage)(mt32emu_const_context context, const mt32emu_bit32u storage_buffer_size);
 
 #define MT32EMU_SERVICE_I_V4 \
-	size_t (*getMachineIDs)(const char **machine_ids, size_t machine_ids_size); \
-	size_t (*getROMIDs)(const char **rom_ids, size_t rom_ids_size, const char *machine_id); \
-	mt32emu_return_code (*identifyROMData)(mt32emu_rom_info *rom_info, const mt32emu_bit8u *data, size_t data_size, const char *machine_id); \
-	mt32emu_return_code (*identifyROMFile)(mt32emu_rom_info *rom_info, const char *filename, const char *machine_id); \
+	size_t (MT32EMU_C_CALL *getMachineIDs)(const char **machine_ids, size_t machine_ids_size); \
+	size_t (MT32EMU_C_CALL *getROMIDs)(const char **rom_ids, size_t rom_ids_size, const char *machine_id); \
+	mt32emu_return_code (MT32EMU_C_CALL *identifyROMData)(mt32emu_rom_info *rom_info, const mt32emu_bit8u *data, size_t data_size, const char *machine_id); \
+	mt32emu_return_code (MT32EMU_C_CALL *identifyROMFile)(mt32emu_rom_info *rom_info, const char *filename, const char *machine_id); \
 \
-	mt32emu_return_code (*mergeAndAddROMData)(mt32emu_context context, const mt32emu_bit8u *part1_data, size_t part1_data_size, const mt32emu_sha1_digest *part1_sha1_digest, const mt32emu_bit8u *part2_data, size_t part2_data_size, const mt32emu_sha1_digest *part2_sha1_digest); \
-	mt32emu_return_code (*mergeAndAddROMFiles)(mt32emu_context context, const char *part1_filename, const char *part2_filename); \
-	mt32emu_return_code (*addMachineROMFile)(mt32emu_context context, const char *machine_id, const char *filename);
+	mt32emu_return_code (MT32EMU_C_CALL *mergeAndAddROMData)(mt32emu_context context, const mt32emu_bit8u *part1_data, size_t part1_data_size, const mt32emu_sha1_digest *part1_sha1_digest, const mt32emu_bit8u *part2_data, size_t part2_data_size, const mt32emu_sha1_digest *part2_sha1_digest); \
+	mt32emu_return_code (MT32EMU_C_CALL *mergeAndAddROMFiles)(mt32emu_context context, const char *part1_filename, const char *part2_filename); \
+	mt32emu_return_code (MT32EMU_C_CALL *addMachineROMFile)(mt32emu_context context, const char *machine_id, const char *filename);
+
+#define MT32EMU_SERVICE_I_V5 \
+	mt32emu_boolean (MT32EMU_C_CALL *getDisplayState)(mt32emu_const_context context, char *target_buffer, const mt32emu_boolean narrow_lcd); \
+	void (MT32EMU_C_CALL *setMainDisplayMode)(mt32emu_const_context context); \
+	void (MT32EMU_C_CALL *setDisplayCompatibility)(mt32emu_const_context context, mt32emu_boolean old_mt32_compatibility_enabled); \
+	mt32emu_boolean (MT32EMU_C_CALL *isDisplayOldMT32Compatible)(mt32emu_const_context context); \
+	mt32emu_boolean (MT32EMU_C_CALL *isDefaultDisplayOldMT32Compatible)(mt32emu_const_context context); \
+	void (MT32EMU_C_CALL *setPartVolumeOverride)(mt32emu_const_context context, mt32emu_bit8u part_number, mt32emu_bit8u volume_override); \
+	mt32emu_bit8u (MT32EMU_C_CALL *getPartVolumeOverride)(mt32emu_const_context context, mt32emu_bit8u part_number);
+
+#define MT32EMU_SERVICE_I_V6 \
+	mt32emu_boolean (MT32EMU_C_CALL *getSoundGroupName)(mt32emu_const_context context, char *sound_group_name, mt32emu_bit8u timbre_group, mt32emu_bit8u timbre_number); \
+	mt32emu_boolean (MT32EMU_C_CALL *getSoundName)(mt32emu_const_context context, char *sound_name, mt32emu_bit8u timbre_group, mt32emu_bit8u timbre_number);
 
 typedef struct {
 	MT32EMU_SERVICE_I_V0
@@ -357,6 +400,25 @@ typedef struct {
 	MT32EMU_SERVICE_I_V4
 } mt32emu_service_i_v4;
 
+typedef struct {
+	MT32EMU_SERVICE_I_V0
+	MT32EMU_SERVICE_I_V1
+	MT32EMU_SERVICE_I_V2
+	MT32EMU_SERVICE_I_V3
+	MT32EMU_SERVICE_I_V4
+	MT32EMU_SERVICE_I_V5
+} mt32emu_service_i_v5;
+
+typedef struct {
+	MT32EMU_SERVICE_I_V0
+	MT32EMU_SERVICE_I_V1
+	MT32EMU_SERVICE_I_V2
+	MT32EMU_SERVICE_I_V3
+	MT32EMU_SERVICE_I_V4
+	MT32EMU_SERVICE_I_V5
+	MT32EMU_SERVICE_I_V6
+} mt32emu_service_i_v6;
+
 /**
  * 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.
@@ -368,6 +430,8 @@ union mt32emu_service_i {
 	const mt32emu_service_i_v2 *v2;
 	const mt32emu_service_i_v3 *v3;
 	const mt32emu_service_i_v4 *v4;
+	const mt32emu_service_i_v5 *v5;
+	const mt32emu_service_i_v6 *v6;
 };
 
 #undef MT32EMU_SERVICE_I_V0
@@ -375,5 +439,7 @@ union mt32emu_service_i {
 #undef MT32EMU_SERVICE_I_V2
 #undef MT32EMU_SERVICE_I_V3
 #undef MT32EMU_SERVICE_I_V4
+#undef MT32EMU_SERVICE_I_V5
+#undef MT32EMU_SERVICE_I_V6
 
 #endif /* #ifndef MT32EMU_C_TYPES_H */
diff --git a/audio/softsynth/mt32/c_interface/cpp_interface.h b/audio/softsynth/mt32/c_interface/cpp_interface.h
index aa28327e87a..d22897b7426 100644
--- a/audio/softsynth/mt32/c_interface/cpp_interface.h
+++ b/audio/softsynth/mt32/c_interface/cpp_interface.h
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 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
@@ -98,6 +98,8 @@ mt32emu_service_i mt32emu_get_service_i();
 #define mt32emu_get_output_gain i.v0->getOutputGain
 #define mt32emu_set_reverb_output_gain i.v0->setReverbOutputGain
 #define mt32emu_get_reverb_output_gain i.v0->getReverbOutputGain
+#define mt32emu_set_part_volume_override iV5()->setPartVolumeOverride
+#define mt32emu_get_part_volume_override iV5()->getPartVolumeOverride
 #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
@@ -117,7 +119,14 @@ mt32emu_service_i mt32emu_get_service_i();
 #define mt32emu_get_partial_states i.v0->getPartialStates
 #define mt32emu_get_playing_notes i.v0->getPlayingNotes
 #define mt32emu_get_patch_name i.v0->getPatchName
+#define mt32emu_get_sound_group_name iV6()->getSoundGroupName
+#define mt32emu_get_sound_name iV6()->getSoundName
 #define mt32emu_read_memory i.v0->readMemory
+#define mt32emu_get_display_state iV5()->getDisplayState
+#define mt32emu_set_main_display_mode iV5()->setMainDisplayMode
+#define mt32emu_set_display_compatibility iV5()->setDisplayCompatibility
+#define mt32emu_is_display_old_mt32_compatible iV5()->isDisplayOldMT32Compatible
+#define mt32emu_is_default_display_old_mt32_compatible iV5()->isDefaultDisplayOldMT32Compatible
 
 #else // #if MT32EMU_API_TYPE == 2
 
@@ -130,7 +139,7 @@ namespace MT32Emu {
 namespace CppInterfaceImpl {
 
 static const mt32emu_report_handler_i NULL_REPORT_HANDLER = { NULL };
-static mt32emu_report_handler_i getReportHandlerThunk();
+static mt32emu_report_handler_i getReportHandlerThunk(mt32emu_report_handler_version);
 static mt32emu_midi_receiver_i getMidiReceiverThunk();
 
 }
@@ -143,8 +152,8 @@ static mt32emu_midi_receiver_i getMidiReceiverThunk();
  * See c_types.h and c_interface.h for description of the corresponding interface methods.
  */
 
-// Defines the interface for handling reported events.
-// Corresponds to the current version of mt32emu_report_handler_i interface.
+// Defines the interface for handling reported events (initial version).
+// Corresponds to the mt32emu_report_handler_i_v0 interface.
 class IReportHandler {
 public:
 	virtual void printDebug(const char *fmt, va_list list) = 0;
@@ -166,6 +175,17 @@ protected:
 	~IReportHandler() {}
 };
 
+// Extends IReportHandler, so that the client may supply callbacks for reporting signals about updated display state.
+// Corresponds to the mt32emu_report_handler_i_v1 interface.
+class IReportHandlerV1 : public IReportHandler {
+public:
+	virtual void onLCDStateUpdated() = 0;
+	virtual void onMidiMessageLEDStateUpdated(bool ledState) = 0;
+
+protected:
+	~IReportHandlerV1() {}
+};
+
 // Defines the interface for receiving MIDI messages generated by MIDI stream parser.
 // Corresponds to the current version of mt32emu_midi_receiver_i interface.
 class IMidiReceiver {
@@ -212,7 +232,8 @@ public:
 
 	mt32emu_context getContext() { return c; }
 	void createContext(mt32emu_report_handler_i report_handler = CppInterfaceImpl::NULL_REPORT_HANDLER, void *instance_data = NULL) { freeContext(); c = mt32emu_create_context(report_handler, instance_data); }
-	void createContext(IReportHandler &report_handler) { createContext(CppInterfaceImpl::getReportHandlerThunk(), &report_handler); }
+	void createContext(IReportHandler &report_handler) { createContext(CppInterfaceImpl::getReportHandlerThunk(MT32EMU_REPORT_HANDLER_VERSION_0), &report_handler); }
+	void createContext(IReportHandlerV1 &report_handler) { createContext(CppInterfaceImpl::getReportHandlerThunk(MT32EMU_REPORT_HANDLER_VERSION_1), &report_handler); }
 	void freeContext() { if (c != NULL) { mt32emu_free_context(c); c = NULL; } }
 	mt32emu_return_code addROMData(const Bit8u *data, size_t data_size, const mt32emu_sha1_digest *sha1_digest = NULL) { return mt32emu_add_rom_data(c, data, data_size, sha1_digest); }
 	mt32emu_return_code addROMFile(const char *filename) { return mt32emu_add_rom_file(c, filename); }
@@ -274,6 +295,9 @@ public:
 	void setReverbOutputGain(float gain) { mt32emu_set_reverb_output_gain(c, gain); }
 	float getReverbOutputGain() { return mt32emu_get_reverb_output_gain(c); }
 
+	void setPartVolumeOverride(Bit8u part_number, Bit8u volume_override) { mt32emu_set_part_volume_override(c, part_number, volume_override); }
+	Bit8u getPartVolumeOverride(Bit8u part_number) { return mt32emu_get_part_volume_override(c, part_number); }
+
 	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; }
 
@@ -298,8 +322,17 @@ public:
 	void getPartialStates(Bit8u *partial_states) { mt32emu_get_partial_states(c, partial_states); }
 	Bit32u getPlayingNotes(Bit8u part_number, Bit8u *keys, Bit8u *velocities) { return mt32emu_get_playing_notes(c, part_number, keys, velocities); }
 	const char *getPatchName(Bit8u part_number) { return mt32emu_get_patch_name(c, part_number); }
+	bool getSoundGroupName(char *soundGroupName, Bit8u timbreGroup, Bit8u timbreNumber) { return mt32emu_get_sound_group_name(c, soundGroupName, timbreGroup, timbreNumber) != MT32EMU_BOOL_FALSE; }
+	bool getSoundName(char *soundName, Bit8u timbreGroup, Bit8u timbreNumber) { return mt32emu_get_sound_name(c, soundName, timbreGroup, timbreNumber) != MT32EMU_BOOL_FALSE; }
 	void readMemory(Bit32u addr, Bit32u len, Bit8u *data) { mt32emu_read_memory(c, addr, len, data); }
 
+	bool getDisplayState(char *target_buffer, const bool narrow_lcd) { return mt32emu_get_display_state(c, target_buffer, narrow_lcd ? MT32EMU_BOOL_TRUE : MT32EMU_BOOL_FALSE) != MT32EMU_BOOL_FALSE; }
+	void setMainDisplayMode() { mt32emu_set_main_display_mode(c); }
+
+	void setDisplayCompatibility(const bool oldMT32CompatibilityEnabled) { mt32emu_set_display_compatibility(c, oldMT32CompatibilityEnabled ? MT32EMU_BOOL_TRUE : MT32EMU_BOOL_FALSE); }
+	bool isDisplayOldMT32Compatible() { return mt32emu_is_display_old_mt32_compatible(c) != MT32EMU_BOOL_FALSE; }
+	bool isDefaultDisplayOldMT32Compatible() { return mt32emu_is_default_display_old_mt32_compatible(c) != MT32EMU_BOOL_FALSE; }
+
 private:
 #if MT32EMU_API_TYPE == 2
 	const mt32emu_service_i i;
@@ -311,6 +344,8 @@ private:
 	const mt32emu_service_i_v2 *iV2() { return (getVersionID() < MT32EMU_SERVICE_VERSION_2) ? NULL : i.v2; }
 	const mt32emu_service_i_v3 *iV3() { return (getVersionID() < MT32EMU_SERVICE_VERSION_3) ? NULL : i.v3; }
 	const mt32emu_service_i_v4 *iV4() { return (getVersionID() < MT32EMU_SERVICE_VERSION_4) ? NULL : i.v4; }
+	const mt32emu_service_i_v5 *iV5() { return (getVersionID() < MT32EMU_SERVICE_VERSION_5) ? NULL : i.v5; }
+	const mt32emu_service_i_v6 *iV6() { return (getVersionID() < MT32EMU_SERVICE_VERSION_6) ? NULL : i.v6; }
 #endif
 
 	Service(const Service &);            // prevent copy-construction
@@ -319,103 +354,126 @@ private:
 
 namespace CppInterfaceImpl {
 
-static mt32emu_report_handler_version getReportHandlerVersionID(mt32emu_report_handler_i) {
-	return MT32EMU_REPORT_HANDLER_VERSION_CURRENT;
-}
+static mt32emu_report_handler_version MT32EMU_C_CALL getReportHandlerVersionID(mt32emu_report_handler_i);
 
-static void printDebug(void *instance_data, const char *fmt, va_list list) {
+static void MT32EMU_C_CALL printDebug(void *instance_data, const char *fmt, va_list list) {
 	static_cast<IReportHandler *>(instance_data)->printDebug(fmt, list);
 }
 
-static void onErrorControlROM(void *instance_data) {
+static void MT32EMU_C_CALL onErrorControlROM(void *instance_data) {
 	static_cast<IReportHandler *>(instance_data)->onErrorControlROM();
 }
 
-static void onErrorPCMROM(void *instance_data) {
+static void MT32EMU_C_CALL onErrorPCMROM(void *instance_data) {
 	static_cast<IReportHandler *>(instance_data)->onErrorPCMROM();
 }
 
-static void showLCDMessage(void *instance_data, const char *message) {
+static void MT32EMU_C_CALL showLCDMessage(void *instance_data, const char *message) {
 	static_cast<IReportHandler *>(instance_data)->showLCDMessage(message);
 }
 
-static void onMIDIMessagePlayed(void *instance_data) {
+static void MT32EMU_C_CALL onMIDIMessagePlayed(void *instance_data) {
 	static_cast<IReportHandler *>(instance_data)->onMIDIMessagePlayed();
 }
 
-static mt32emu_boolean onMIDIQueueOverflow(void *instance_data) {
+static mt32emu_boolean MT32EMU_C_CALL onMIDIQueueOverflow(void *instance_data) {
 	return static_cast<IReportHandler *>(instance_data)->onMIDIQueueOverflow() ? MT32EMU_BOOL_TRUE : MT32EMU_BOOL_FALSE;
 }
 
-static void onMIDISystemRealtime(void *instance_data, mt32emu_bit8u system_realtime) {
+static void MT32EMU_C_CALL onMIDISystemRealtime(void *instance_data, mt32emu_bit8u system_realtime) {
 	static_cast<IReportHandler *>(instance_data)->onMIDISystemRealtime(system_realtime);
 }
 
-static void onDeviceReset(void *instance_data) {
+static void MT32EMU_C_CALL onDeviceReset(void *instance_data) {
 	static_cast<IReportHandler *>(instance_data)->onDeviceReset();
 }
 
-static void onDeviceReconfig(void *instance_data) {
+static void MT32EMU_C_CALL onDeviceReconfig(void *instance_data) {
 	static_cast<IReportHandler *>(instance_data)->onDeviceReconfig();
 }
 
-static void onNewReverbMode(void *instance_data, mt32emu_bit8u mode) {
+static void MT32EMU_C_CALL onNewReverbMode(void *instance_data, mt32emu_bit8u mode) {
 	static_cast<IReportHandler *>(instance_data)->onNewReverbMode(mode);
 }
 
-static void onNewReverbTime(void *instance_data, mt32emu_bit8u time) {
+static void MT32EMU_C_CALL onNewReverbTime(void *instance_data, mt32emu_bit8u time) {
 	static_cast<IReportHandler *>(instance_data)->onNewReverbTime(time);
 }
 
-static void onNewReverbLevel(void *instance_data, mt32emu_bit8u level) {
+static void MT32EMU_C_CALL onNewReverbLevel(void *instance_data, mt32emu_bit8u level) {
 	static_cast<IReportHandler *>(instance_data)->onNewReverbLevel(level);
 }
 
-static void onPolyStateChanged(void *instance_data, mt32emu_bit8u part_num) {
+static void MT32EMU_C_CALL onPolyStateChanged(void *instance_data, mt32emu_bit8u part_num) {
 	static_cast<IReportHandler *>(instance_data)->onPolyStateChanged(part_num);
 }
 
-static void onProgramChanged(void *instance_data, mt32emu_bit8u part_num, const char *sound_group_name, const char *patch_name) {
+static void MT32EMU_C_CALL onProgramChanged(void *instance_data, mt32emu_bit8u part_num, const char *sound_group_name, const char *patch_name) {
 	static_cast<IReportHandler *>(instance_data)->onProgramChanged(part_num, sound_group_name, patch_name);
 }
 
-static mt32emu_report_handler_i getReportHandlerThunk() {
-	static const mt32emu_report_handler_i_v0 REPORT_HANDLER_V0_THUNK = {
-		getReportHandlerVersionID,
-		printDebug,
-		onErrorControlROM,
-		onErrorPCMROM,
-		showLCDMessage,
-		onMIDIMessagePlayed,
-		onMIDIQueueOverflow,
-		onMIDISystemRealtime,
-		onDeviceReset,
-		onDeviceReconfig,
-		onNewReverbMode,
-		onNewReverbTime,
-		onNewReverbLevel,
-		onPolyStateChanged,
-		onProgramChanged
-	};
+static void MT32EMU_C_CALL onLCDStateUpdated(void *instance_data) {
+	static_cast<IReportHandlerV1 *>(instance_data)->onLCDStateUpdated();
+}
+
+static void MT32EMU_C_CALL onMidiMessageLEDStateUpdated(void *instance_data, mt32emu_boolean led_state) {
+	static_cast<IReportHandlerV1 *>(instance_data)->onMidiMessageLEDStateUpdated(led_state != MT32EMU_BOOL_FALSE);
+}
+
+#define MT32EMU_REPORT_HANDLER_V0_THUNK \
+	getReportHandlerVersionID, \
+	printDebug, \
+	onErrorControlROM, \
+	onErrorPCMROM, \
+	showLCDMessage, \
+	onMIDIMessagePlayed, \
+	onMIDIQueueOverflow, \
+	onMIDISystemRealtime, \
+	onDeviceReset, \
+	onDeviceReconfig, \
+	onNewReverbMode, \
+	onNewReverbTime, \
+	onNewReverbLevel, \
+	onPolyStateChanged, \
+	onProgramChanged
+
+static const mt32emu_report_handler_i_v0 REPORT_HANDLER_V0_THUNK = {
+	MT32EMU_REPORT_HANDLER_V0_THUNK
+};
 
-	static const mt32emu_report_handler_i REPORT_HANDLER_THUNK = { &REPORT_HANDLER_V0_THUNK };
+static const mt32emu_report_handler_i_v1 REPORT_HANDLER_V1_THUNK = {
+	MT32EMU_REPORT_HANDLER_V0_THUNK,
+	onLCDStateUpdated,
+	onMidiMessageLEDStateUpdated
+};
+
+#undef MT32EMU_REPORT_HANDLER_THUNK_V0
+
+static mt32emu_report_handler_version MT32EMU_C_CALL getReportHandlerVersionID(mt32emu_report_handler_i thunk) {
+	if (thunk.v0 == &REPORT_HANDLER_V0_THUNK) return MT32EMU_REPORT_HANDLER_VERSION_0;
+	return MT32EMU_REPORT_HANDLER_VERSION_CURRENT;
+}
 
-	return REPORT_HANDLER_THUNK;
+static mt32emu_report_handler_i getReportHandlerThunk(mt32emu_report_handler_version versionID) {
+	mt32emu_report_handler_i thunk;
+	if (versionID == MT32EMU_REPORT_HANDLER_VERSION_0) thunk.v0 = &REPORT_HANDLER_V0_THUNK;
+	else thunk.v1 = &REPORT_HANDLER_V1_THUNK;
+	return thunk;
 }
 
-static mt32emu_midi_receiver_version getMidiReceiverVersionID(mt32emu_midi_receiver_i) {
+static mt32emu_midi_receiver_version MT32EMU_C_CALL getMidiReceiverVersionID(mt32emu_midi_receiver_i) {
 	return MT32EMU_MIDI_RECEIVER_VERSION_CURRENT;
 }
 
-static void handleShortMessage(void *instance_data, const mt32emu_bit32u message) {
+static void MT32EMU_C_CALL handleShortMessage(void *instance_data, const mt32emu_bit32u message) {
 	static_cast<IMidiReceiver *>(instance_data)->handleShortMessage(message);
 }
 
-static void handleSysex(void *instance_data, const mt32emu_bit8u stream[], const mt32emu_bit32u length) {
+static void MT32EMU_C_CALL handleSysex(void *instance_data, const mt32emu_bit8u stream[], const mt32emu_bit32u length) {
 	static_cast<IMidiReceiver *>(instance_data)->handleSysex(stream, length);
 }
 
-static void handleSystemRealtimeMessage(void *instance_data, const mt32emu_bit8u realtime) {
+static void MT32EMU_C_CALL handleSystemRealtimeMessage(void *instance_data, const mt32emu_bit8u realtime) {
 	static_cast<IMidiReceiver *>(instance_data)->handleSystemRealtimeMessage(realtime);
 }
 
@@ -520,7 +578,14 @@ static mt32emu_midi_receiver_i getMidiReceiverThunk() {
 #undef mt32emu_get_partial_states
 #undef mt32emu_get_playing_notes
 #undef mt32emu_get_patch_name
+#undef mt32emu_get_sound_group_name
+#undef mt32emu_get_sound_name
 #undef mt32emu_read_memory
+#undef mt32emu_get_display_state
+#undef mt32emu_set_main_display_mode
+#undef mt32emu_set_display_compatibility
+#undef mt32emu_is_display_old_mt32_compatible
+#undef mt32emu_is_default_display_old_mt32_compatible
 
 #endif // #if MT32EMU_API_TYPE == 2
 
diff --git a/audio/softsynth/mt32/config.h b/audio/softsynth/mt32/config.h
index d7c59d8d931..0509c1606e6 100644
--- a/audio/softsynth/mt32/config.h
+++ b/audio/softsynth/mt32/config.h
@@ -18,10 +18,10 @@
 #ifndef MT32EMU_CONFIG_H
 #define MT32EMU_CONFIG_H
 
-#define MT32EMU_VERSION      "2.5.1"
+#define MT32EMU_VERSION      "2.7.0"
 #define MT32EMU_VERSION_MAJOR 2
-#define MT32EMU_VERSION_MINOR 5
-#define MT32EMU_VERSION_PATCH 1
+#define MT32EMU_VERSION_MINOR 7
+#define MT32EMU_VERSION_PATCH 0
 
 /* Library Exports Configuration
  *
diff --git a/audio/softsynth/mt32/globals.h b/audio/softsynth/mt32/globals.h
index 79b7ae5eb4b..86ac1ca5b15 100644
--- a/audio/softsynth/mt32/globals.h
+++ b/audio/softsynth/mt32/globals.h
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 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
@@ -63,6 +63,27 @@
  */
 #define MT32EMU_EXPORT_V(symbol_version_tag) MT32EMU_EXPORT
 
+/* Helpers for compile-time version checks */
+
+/* Encodes the given version components to a single integer value to simplify further checks. */
+#define MT32EMU_VERSION_INT(major, minor, patch) ((major << 16) | (minor << 8) | patch)
+
+/* The version of this library build, as an integer. */
+#define MT32EMU_CURRENT_VERSION_INT MT32EMU_VERSION_INT(MT32EMU_VERSION_MAJOR, MT32EMU_VERSION_MINOR, MT32EMU_VERSION_PATCH)
+
+/* Compares the current library version with the given version components. Intended for feature checks. */
+#define MT32EMU_VERSION_ATLEAST(major, minor, patch) (MT32EMU_CURRENT_VERSION_INT >= MT32EMU_VERSION_INT(major, minor, patch))
+
+/* Implements a simple version check that ensures full API compatibility of this library build
+ * with the application requirements. The latter can be derived from the versions of used public symbols.
+ *
+ * Note: This macro is intended for a quick compile-time check. To ensure compatibility of an application
+ * linked with a shared library, an automatic version check can be engaged with help of the build option
+ * libmt32emu_WITH_VERSION_TAGGING. For a fine-grained feature checking in run-time, see functions
+ * mt32emu_get_library_version_int and Synth::getLibraryVersionInt.
+ */
+#define MT32EMU_IS_COMPATIBLE(major, minor) (MT32EMU_VERSION_MAJOR == major && MT32EMU_VERSION_MINOR >= minor)
+
 /* Useful constants */
 
 /* Sample rate to use in mixing. With the progress of development, we've found way too many thing dependent.
diff --git a/audio/softsynth/mt32/internals.h b/audio/softsynth/mt32/internals.h
index 1876b08352a..1b3ad0de224 100644
--- a/audio/softsynth/mt32/internals.h
+++ b/audio/softsynth/mt32/internals.h
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License as published by
diff --git a/audio/softsynth/mt32/mmath.h b/audio/softsynth/mt32/mmath.h
index e0d233222cf..3164c7bfca2 100644
--- a/audio/softsynth/mt32/mmath.h
+++ b/audio/softsynth/mt32/mmath.h
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License as published by
diff --git a/audio/softsynth/mt32/module.mk b/audio/softsynth/mt32/module.mk
index 5b7a602bb65..3c17650f046 100644
--- a/audio/softsynth/mt32/module.mk
+++ b/audio/softsynth/mt32/module.mk
@@ -3,6 +3,7 @@ MODULE := audio/softsynth/mt32
 MODULE_OBJS := \
 	Analog.o \
 	BReverbModel.o \
+	Display.o \
 	File.o \
 	FileStream.o \
 	LA32FloatWaveGenerator.o \
diff --git a/audio/softsynth/mt32/mt32emu.h b/audio/softsynth/mt32/mt32emu.h
index 399ddae069e..571b25571e6 100644
--- a/audio/softsynth/mt32/mt32emu.h
+++ b/audio/softsynth/mt32/mt32emu.h
@@ -1,5 +1,5 @@
 /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011-2022 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License as published by
diff --git a/audio/softsynth/mt32/mt32emu.pc.in b/audio/softsynth/mt32/mt32emu.pc.in
index 95ddf6fa1d8..24a3a8baa97 100644
--- a/audio/softsynth/mt32/mt32emu.pc.in
+++ b/audio/softsynth/mt32/mt32emu.pc.in
@@ -1,10 +1,10 @@
 prefix=@CMAKE_INSTALL_PREFIX@
 exec_prefix=${prefix}
-libdir=${exec_prefix}/@LIB_INSTALL_DIR@
-includedir=${prefix}/include
+libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@
+includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@
 
 Name: @PROJECT_NAME@
-Description: @libmt32emu_DESCRIPTION@
+Description: @libmt32emu_DESCRIPTION_SUMMARY@
 URL: @libmt32emu_URL@
 Version: @libmt32emu_VERSION@
 Requires.private: @libmt32emu_PC_REQUIRES_PRIVATE@
diff --git a/audio/softsynth/mt32/srchelper/InternalResampler.cpp b/audio/softsynth/mt32/srchelper/InternalResampler.cpp
index 7babc4e7bf2..4e6a5a7489e 100644
--- a/audio/softsynth/mt32/srchelper/InternalResampler.cpp
+++ b/audio/softsynth/mt32/srchelper/InternalResampler.cpp
@@ -1,4 +1,4 @@
-/* Copyright (C) 2015-2021 Sergey V. Mikayev
+/* Copyright (C) 2015-2022 Sergey V. Mikayev
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License as published by
@@ -32,7 +32,7 @@ public:
 	SynthWrapper(Synth &useSynth) : synth(useSynth)
 	{}
 
-	void getOutputSamples(FloatSample *outBuffer, unsigned int size) override {
+	void getOutputSamples(FloatSample *outBuffer, unsigned int size) {
 		synth.render(outBuffer, size);
 	}
 };
diff --git a/audio/softsynth/mt32/srchelper/InternalResampler.h b/audio/softsynth/mt32/srchelper/InternalResampler.h
index 9f094476ced..a80cc7dc433 100644
--- a/audio/softsynth/mt32/srchelper/InternalResampler.h
+++ b/audio/softsynth/mt32/srchelper/InternalResampler.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2015-2021 Sergey V. Mikayev
+/* Copyright (C) 2015-2022 Sergey V. Mikayev
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License as published by
diff --git a/audio/softsynth/mt32/srchelper/SamplerateAdapter.cpp b/audio/softsynth/mt32/srchelper/SamplerateAdapter.cpp
index dc95a5cd7e5..aeb695a2eef 100644
--- a/audio/softsynth/mt32/srchelper/SamplerateAdapter.cpp
+++ b/audio/softsynth/mt32/srchelper/SamplerateAdapter.cpp
@@ -1,4 +1,4 @@
-/* Copyright (C) 2015-2021 Sergey V. Mikayev
+/* Copyright (C) 2015-2022 Sergey V. Mikayev
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License as published by
diff --git a/audio/softsynth/mt32/srchelper/SamplerateAdapter.h b/audio/softsynth/mt32/srchelper/SamplerateAdapter.h
index 25209970140..6da9b64aaa8 100644
--- a/audio/softsynth/mt32/srchelper/SamplerateAdapter.h
+++ b/audio/softsynth/mt32/srchelper/SamplerateAdapter.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2015-2021 Sergey V. Mikayev
+/* Copyright (C) 2015-2022 Sergey V. Mikayev
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License as published by
diff --git a/audio/softsynth/mt32/srchelper/SoxrAdapter.cpp b/audio/softsynth/mt32/srchelper/SoxrAdapter.cpp
index 07697d9381a..754f55d56cd 100644
--- a/audio/softsynth/mt32/srchelper/SoxrAdapter.cpp
+++ b/audio/softsynth/mt32/srchelper/SoxrAdapter.cpp
@@ -1,4 +1,4 @@
-/* Copyright (C) 2015-2021 Sergey V. Mikayev
+/* Copyright (C) 2015-2022 Sergey V. Mikayev
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License as published by
diff --git a/audio/softsynth/mt32/srchelper/SoxrAdapter.h b/audio/softsynth/mt32/srchelper/SoxrAdapter.h
index f116d2f9dd9..2abcbdd8abf 100644
--- a/audio/softsynth/mt32/srchelper/SoxrAdapter.h
+++ b/audio/softsynth/mt32/srchelper/SoxrAdapter.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2015-2021 Sergey V. Mikayev
+/* Copyright (C) 2015-2022 Sergey V. Mikayev
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License as published by
diff --git a/audio/softsynth/mt32/srchelper/srctools/include/FIRResampler.h b/audio/softsynth/mt32/srchelper/srctools/include/FIRResampler.h
index 4d2a80247ae..b8e0be1bb7e 100644
--- a/audio/softsynth/mt32/srchelper/srctools/include/FIRResampler.h
+++ b/audio/softsynth/mt32/srchelper/srctools/include/FIRResampler.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2015-2021 Sergey V. Mikayev
+/* Copyright (C) 2015-2022 Sergey V. Mikayev
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License as published by
diff --git a/audio/softsynth/mt32/srchelper/srctools/include/FloatSampleProvider.h b/audio/softsynth/mt32/srchelper/srctools/include/FloatSampleProvider.h
index 6cfbdb0d314..9b88bced697 100644
--- a/audio/softsynth/mt32/srchelper/srctools/include/FloatSampleProvider.h
+++ b/audio/softsynth/mt32/srchelper/srctools/include/FloatSampleProvider.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2015-2021 Sergey V. Mikayev
+/* Copyright (C) 2015-2022 Sergey V. Mikayev
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License as published by
diff --git a/audio/softsynth/mt32/srchelper/srctools/include/IIR2xResampler.h b/audio/softsynth/mt32/srchelper/srctools/include/IIR2xResampler.h
index bb50ca8cf26..247d575685f 100644
--- a/audio/softsynth/mt32/srchelper/srctools/include/IIR2xResampler.h
+++ b/audio/softsynth/mt32/srchelper/srctools/include/IIR2xResampler.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2015-2021 Sergey V. Mikayev
+/* Copyright (C) 2015-2022 Sergey V. Mikayev
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License as published by
diff --git a/audio/softsynth/mt32/srchelper/srctools/include/LinearResampler.h b/audio/softsynth/mt32/srchelper/srctools/include/LinearResampler.h
index 0b67503d619..a55f4ae67b3 100644
--- a/audio/softsynth/mt32/srchelper/srctools/include/LinearResampler.h
+++ b/audio/softsynth/mt32/srchelper/srctools/include/LinearResampler.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2015-2021 Sergey V. Mikayev
+/* Copyright (C) 2015-2022 Sergey V. Mikayev
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License as published by
diff --git a/audio/softsynth/mt32/srchelper/srctools/include/ResamplerModel.h b/audio/softsynth/mt32/srchelper/srctools/include/ResamplerModel.h
index 354d3767963..622ccbb1892 100644
--- a/audio/softsynth/mt32/srchelper/srctools/include/ResamplerModel.h
+++ b/audio/softsynth/mt32/srchelper/srctools/include/ResamplerModel.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2015-2021 Sergey V. Mikayev
+/* Copyright (C) 2015-2022 Sergey V. Mikayev
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License as published by
diff --git a/audio/softsynth/mt32/srchelper/srctools/include/ResamplerStage.h b/audio/softsynth/mt32/srchelper/srctools/include/ResamplerStage.h
index bec447a0c66..2d8507037d8 100644
--- a/audio/softsynth/mt32/srchelper/srctools/include/ResamplerStage.h
+++ b/audio/softsynth/mt32/srchelper/srctools/include/ResamplerStage.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2015-2021 Sergey V. Mikayev
+/* Copyright (C) 2015-2022 Sergey V. Mikayev
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License as published by
diff --git a/audio/softsynth/mt32/srchelper/srctools/include/SincResampler.h b/audio/softsynth/mt32/srchelper/srctools/include/SincResampler.h
index 5ab048a96d1..8db9f5bb9dd 100644
--- a/audio/softsynth/mt32/srchelper/srctools/include/SincResampler.h
+++ b/audio/softsynth/mt32/srchelper/srctools/include/SincResampler.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2015-2021 Sergey V. Mikayev
+/* Copyright (C) 2015-2022 Sergey V. Mikayev
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License as published by
diff --git a/audio/softsynth/mt32/srchelper/srctools/src/FIRResampler.cpp b/audio/softsynth/mt32/srchelper/srctools/src/FIRResampler.cpp
index 5737840ac46..0df8acd16df 100644
--- a/audio/softsynth/mt32/srchelper/srctools/src/FIRResampler.cpp
+++ b/audio/softsynth/mt32/srchelper/srctools/src/FIRResampler.cpp
@@ -1,4 +1,4 @@
-/* Copyright (C) 2015-2021 Sergey V. Mikayev
+/* Copyright (C) 2015-2022 Sergey V. Mikayev
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License as published by
diff --git a/audio/softsynth/mt32/srchelper/srctools/src/IIR2xResampler.cpp b/audio/softsynth/mt32/srchelper/srctools/src/IIR2xResampler.cpp
index b0bf0673f5a..a09b48d9ef5 100644
--- a/audio/softsynth/mt32/srchelper/srctools/src/IIR2xResampler.cpp
+++ b/audio/softsynth/mt32/srchelper/srctools/src/IIR2xResampler.cpp
@@ -1,4 +1,4 @@
-/* Copyright (C) 2015-2021 Sergey V. Mikayev
+/* Copyright (C) 2015-2022 Sergey V. Mikayev
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License as published by
@@ -119,7 +119,7 @@ IIRResampler::Constants::Constants(const unsigned int useSectionsCount, const II
 }
 
 IIRResampler::IIRResampler(const Quality quality) :
-	constants(0, 0.0f, nullptr, quality)
+	constants(0, 0.0f, NULL, quality)
 {}
 
 IIRResampler::IIRResampler(const unsigned int useSectionsCount, const IIRCoefficient useFIR, const IIRSection useSections[]) :
diff --git a/audio/softsynth/mt32/srchelper/srctools/src/LinearResampler.cpp b/audio/softsynth/mt32/srchelper/srctools/src/LinearResampler.cpp
index c46070b301f..18224833bd7 100644
--- a/audio/softsynth/mt32/srchelper/srctools/src/LinearResampler.cpp
+++ b/audio/softsynth/mt32/srchelper/srctools/src/LinearResampler.cpp
@@ -1,4 +1,4 @@
-/* Copyright (C) 2015-2021 Sergey V. Mikayev
+/* Copyright (C) 2015-2022 Sergey V. Mikayev
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License as published by
diff --git a/audio/softsynth/mt32/srchelper/srctools/src/ResamplerModel.cpp b/audio/softsynth/mt32/srchelper/srctools/src/ResamplerModel.cpp
index 5e6b6ec2349..504024629e1 100644
--- a/audio/softsynth/mt32/srchelper/srctools/src/ResamplerModel.cpp
+++ b/audio/softsynth/mt32/srchelper/srctools/src/ResamplerModel.cpp
@@ -1,4 +1,4 @@
-/* Copyright (C) 2015-2021 Sergey V. Mikayev
+/* Copyright (C) 2015-2022 Sergey V. Mikayev
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License as published by
@@ -36,7 +36,7 @@ friend void freeResamplerModel(FloatSampleProvider &model, FloatSampleProvider &
 public:
 	CascadeStage(FloatSampleProvider &source, ResamplerStage &resamplerStage);
 
-	void getOutputSamples(FloatSample *outBuffer, unsigned int size) override;
+	void getOutputSamples(FloatSample *outBuffer, unsigned int size);
 
 protected:
 	ResamplerStage &resamplerStage;
@@ -120,7 +120,7 @@ void ResamplerModel::freeResamplerModel(FloatSampleProvider &model, FloatSampleP
 	FloatSampleProvider *currentStage = &model;
 	while (currentStage != &source) {
 		CascadeStage *cascadeStage = dynamic_cast<CascadeStage *>(currentStage);
-		if (cascadeStage == nullptr) return;
+		if (cascadeStage == NULL) return;
 		FloatSampleProvider &prevStage = cascadeStage->source;
 		delete currentStage;
 		currentStage = &prevStage;
diff --git a/audio/softsynth/mt32/srchelper/srctools/src/SincResampler.cpp b/audio/softsynth/mt32/srchelper/srctools/src/SincResampler.cpp
index 146a9a952c3..de904809392 100644
--- a/audio/softsynth/mt32/srchelper/srctools/src/SincResampler.cpp
+++ b/audio/softsynth/mt32/srchelper/srctools/src/SincResampler.cpp
@@ -1,4 +1,4 @@
-/* Copyright (C) 2015-2021 Sergey V. Mikayev
+/* Copyright (C) 2015-2022 Sergey V. Mikayev
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License as published by
diff --git a/doc/de/NEUES.md b/doc/de/NEUES.md
index d6c74b8fe3c..aa2795488e7 100644
--- a/doc/de/NEUES.md
+++ b/doc/de/NEUES.md
@@ -12,6 +12,7 @@ Umfangreichere Informationen über die Änderungen des aktuellen Codes findest D
 
  Allgemein:
    - Anzahl an falsch-positiven Einträgen reduziert, wenn das Gerät nach Spielen durchsucht wird.
+   - Code der Roland MT-32-Emulation auf Munt mt32emu 2.7.0 aktualisiert.
 
  AGOS:
    - Option hinzugefügt, mit der die Ausblende-Effekte bei einem Raum-Wechsel in Simon 1 und 2




More information about the Scummvm-git-logs mailing list