Updated MUNT to 2.2.0.

This commit is contained in:
OBattler
2017-08-10 23:46:06 +02:00
parent 97b873a3c5
commit 4c40b23412
30 changed files with 471 additions and 166 deletions

View File

@@ -8,7 +8,7 @@
#
# Modified Makefile for Win32 (MinGW32) environment.
#
# Version: @(#)Makefile.mingw 1.0.34 2017/08/09
# Version: @(#)Makefile.mingw 1.0.35 2017/08/10
#
# Authors: Miran Grca, <mgrca8@gmail.com>
# Fred N. van Kempen, <decwiz@yahoo.com>
@@ -224,8 +224,8 @@ SNDOBJ = sound.o \
Analog.o BReverbModel.o File.o FileStream.o LA32Ramp.o \
LA32FloatWaveGenerator.o LA32WaveGenerator.o \
MidiStreamParser.o Part.o Partial.o PartialManager.o \
Poly.o ROMInfo.o Synth.o Tables.o TVA.o TVF.o TVP.o \
sha1.o c_interface.o \
Poly.o ROMInfo.o SampleRateConverter_dummy.o Synth.o \
Tables.o TVA.o TVF.o TVP.o sha1.o c_interface.o \
midi_system.o \
snd_speaker.o snd_ps1.o snd_pssj.o \
snd_adlib.o snd_adlibgold.o snd_ad1848.o \

View File

@@ -178,6 +178,7 @@ void* mt32emu_init(wchar_t *control_rom, wchar_t *pcm_rom)
mt32emu_set_reverb_enabled(context, device_get_config_int("reverb"));
mt32emu_set_reverb_output_gain(context, device_get_config_int("reverb_output_gain")/100.0f);
mt32emu_set_reversed_stereo_enabled(context, device_get_config_int("reversed_stereo"));
mt32emu_set_nice_amp_ramp_enabled(context, device_get_config_int("nice_ramp"));
pclog("mt32 output gain: %f\n", mt32emu_get_output_gain(context));
pclog("mt32 reverb output gain: %f\n", mt32emu_get_reverb_output_gain(context));
@@ -248,32 +249,11 @@ static device_config_t mt32_config[] =
{
.name = "output_gain",
.description = "Output Gain",
.type = CONFIG_SELECTION,
.selection =
.type = CONFIG_SPINNER,
.spinner =
{
{
.description = "100%",
.value = 100
},
{
.description = "75%",
.value = 75
},
{
.description = "50%",
.value = 50
},
{
.description = "25%",
.value = 25
},
{
.description = "0%",
.value = 0
},
{
.description = ""
}
.min = 0,
.max = 100
},
.default_int = 100
},
@@ -286,32 +266,11 @@ static device_config_t mt32_config[] =
{
.name = "reverb_output_gain",
.description = "Reverb Output Gain",
.type = CONFIG_SELECTION,
.selection =
.type = CONFIG_SPINNER,
.spinner =
{
{
.description = "100%",
.value = 100
},
{
.description = "75%",
.value = 75
},
{
.description = "50%",
.value = 50
},
{
.description = "25%",
.value = 25
},
{
.description = "0%",
.value = 0
},
{
.description = ""
}
.min = 0,
.max = 100
},
.default_int = 100
},
@@ -321,6 +280,12 @@ static device_config_t mt32_config[] =
.type = CONFIG_BINARY,
.default_int = 0
},
{
.name = "nice_ramp",
.description = "Nice ramp",
.type = CONFIG_BINARY,
.default_int = 1
},
{
.type = -1
}

View File

@@ -38,7 +38,7 @@ class Analog {
public:
static Analog *createAnalog(const AnalogOutputMode mode, const bool oldMT32AnalogLPF, const RendererType rendererType);
virtual ~Analog() {};
virtual ~Analog() {}
virtual unsigned int getOutputSampleRate() const = 0;
virtual Bit32u getDACStreamsLength(const Bit32u outputLength) const = 0;
virtual void setSynthOutputGain(const float synthGain) = 0;

View File

@@ -29,7 +29,7 @@ class BReverbModel {
public:
static BReverbModel *createBReverbModel(const ReverbMode mode, const bool mt32CompatibleModel, const RendererType rendererType);
virtual ~BReverbModel() {};
virtual ~BReverbModel() {}
virtual bool isOpen() const = 0;
// After construction or a close(), open() must be called at least once before any other call (with the exception of close()).
virtual void open() = 0;

View File

@@ -85,7 +85,6 @@ enum MT32EMU_DAC_INPUT_MODE_NAME {
* Nicer overdrive characteristics than the DAC hacks (it simply clips samples within range)
* Much less likely to overdrive than any other mode.
* Half the volume of any of the other modes.
* Output gain is ignored for both LA32 and reverb output.
* Perfect for developers while debugging :)
*/
MT32EMU_DAC_INPUT_MODE(PURE),

View File

@@ -165,12 +165,6 @@ float LA32FloatWaveGenerator::generateNextSample(const Bit32u ampVal, const Bit1
hLen = 0.0f;
}
// Ignore pulsewidths too high for given freq and cutoff
float lLen = waveLen - hLen - 2 * cosineLen;
if (lLen < 0.0f) {
lLen = 0.0f;
}
// Correct resAmp for cutoff in range 50..66
if ((cutoffVal >= MIDDLE_CUTOFF_VALUE) && (cutoffVal < RESONANCE_DECAY_THRESHOLD_CUTOFF_VALUE)) {
resAmp *= sin(FLOAT_PI * (cutoffVal - MIDDLE_CUTOFF_VALUE) / 32.0f);
@@ -328,8 +322,13 @@ static inline float produceDistortedSample(float sample) {
}
float LA32FloatPartialPair::nextOutSample() {
// Note, LA32FloatWaveGenerator produces each sample normalised in terms of a single playing partial,
// so the unity sample corresponds to the internal LA32 logarithmic fixed-point unity sample.
// However, each logarithmic sample is then unlogged to a 14-bit signed integer value, i.e. the max absolute value is 8192.
// Thus, considering that samples are further mapped to a 16-bit signed integer,
// we apply a conversion factor 0.25 to produce properly normalised float samples.
if (!ringModulated) {
return masterOutputSample + slaveOutputSample;
return 0.25f * (masterOutputSample + slaveOutputSample);
}
/*
* SEMI-CONFIRMED: Ring modulation model derived from sample analysis of specially constructed patches which exploit distortion.
@@ -340,7 +339,7 @@ float LA32FloatPartialPair::nextOutSample() {
* Most probably the overflow is caused by limited precision of the multiplication circuit as the very similar distortion occurs with panning.
*/
float ringModulatedSample = produceDistortedSample(masterOutputSample) * produceDistortedSample(slaveOutputSample);
return mixed ? masterOutputSample + ringModulatedSample : ringModulatedSample;
return 0.25f * (mixed ? masterOutputSample + ringModulatedSample : ringModulatedSample);
}
void LA32FloatPartialPair::deactivate(const PairType useMaster) {

View File

@@ -56,8 +56,8 @@ We haven't fully explored:
namespace MT32Emu {
// SEMI-CONFIRMED from sample analysis.
const int TARGET_MULT = 0x40000;
const unsigned int MAX_CURRENT = 0xFF * TARGET_MULT;
const unsigned int TARGET_SHIFTS = 18;
const unsigned int MAX_CURRENT = 0xFF << TARGET_SHIFTS;
// We simulate the delay in handling "target was reached" interrupts by waiting
// this many samples before setting interruptRaised.
@@ -96,7 +96,7 @@ void LA32Ramp::startRamp(Bit8u target, Bit8u increment) {
largeIncrement++;
}
largeTarget = target * TARGET_MULT;
largeTarget = target << TARGET_SHIFTS;
interruptCountdown = 0;
interruptRaised = false;
}
@@ -152,4 +152,13 @@ void LA32Ramp::reset() {
interruptRaised = false;
}
// This is actually beyond the LA32 ramp interface.
// Instead of polling the current value, MCU receives an interrupt when a ramp completes.
// However, this is a simple way to work around the specific behaviour of TVA
// when in sustain phase which one normally wants to avoid.
// See TVA::recalcSustain() for details.
bool LA32Ramp::isBelowCurrent(Bit8u target) const {
return Bit32u(target << TARGET_SHIFTS) < current;
}
} // namespace MT32Emu

View File

@@ -39,6 +39,7 @@ public:
Bit32u nextValue();
bool checkInterrupt();
void reset();
bool isBelowCurrent(Bit8u target) const;
};
} // namespace MT32Emu

View File

@@ -378,22 +378,16 @@ Bit16s LA32IntPartialPair::unlogAndMixWGOutput(const LA32WaveGenerator &wg) {
return firstSample + secondSample;
}
static inline Bit16s produceDistortedSample(Bit16s sample) {
return ((sample & 0x2000) == 0) ? Bit16s(sample & 0x1fff) : Bit16s(sample | ~0x1fff);
}
Bit16s LA32IntPartialPair::nextOutSample() {
if (!ringModulated) {
return unlogAndMixWGOutput(master) + unlogAndMixWGOutput(slave);
}
/*
* SEMI-CONFIRMED: Ring modulation model derived from sample analysis of specially constructed patches which exploit distortion.
* LA32 ring modulator found to produce distorted output in case if the absolute value of maximal amplitude of one of the input partials exceeds 8191.
* This is easy to reproduce using synth partials with resonance values close to the maximum. It looks like an integer overflow happens in this case.
* As the distortion is strictly bound to the amplitude of the complete mixed square + resonance wave in the linear space,
* it is reasonable to assume the ring modulation is performed also in the linear space by sample multiplication.
* Most probably the overflow is caused by limited precision of the multiplication circuit as the very similar distortion occurs with panning.
*/
Bit16s nonOverdrivenMasterSample = unlogAndMixWGOutput(master); // Store master partial sample for further mixing
Bit16s masterSample = nonOverdrivenMasterSample << 2;
masterSample >>= 2;
Bit16s masterSample = unlogAndMixWGOutput(master); // Store master partial sample for further mixing
/* SEMI-CONFIRMED from sample analysis:
* We observe that for partial structures with ring modulation the interpolation is not applied to the slave PCM partial.
@@ -401,10 +395,17 @@ Bit16s LA32IntPartialPair::nextOutSample() {
* is borrowed by the ring modulation circuit (or the LA32 chip has a similar lack of resources assigned to each partial pair).
*/
Bit16s slaveSample = slave.isPCMWave() ? LA32Utilites::unlog(slave.getOutputLogSample(true)) : unlogAndMixWGOutput(slave);
slaveSample <<= 2;
slaveSample >>= 2;
Bit16s ringModulatedSample = Bit16s((Bit32s(masterSample) * Bit32s(slaveSample)) >> 13);
return mixed ? nonOverdrivenMasterSample + ringModulatedSample : ringModulatedSample;
/* SEMI-CONFIRMED: Ring modulation model derived from sample analysis of specially constructed patches which exploit distortion.
* LA32 ring modulator found to produce distorted output in case if the absolute value of maximal amplitude of one of the input partials exceeds 8191.
* This is easy to reproduce using synth partials with resonance values close to the maximum. It looks like an integer overflow happens in this case.
* As the distortion is strictly bound to the amplitude of the complete mixed square + resonance wave in the linear space,
* it is reasonable to assume the ring modulation is performed also in the linear space by sample multiplication.
* Most probably the overflow is caused by limited precision of the multiplication circuit as the very similar distortion occurs with panning.
*/
Bit16s ringModulatedSample = Bit16s((Bit32s(produceDistortedSample(masterSample)) * Bit32s(produceDistortedSample(slaveSample))) >> 13);
return mixed ? masterSample + ringModulatedSample : ringModulatedSample;
}
void LA32IntPartialPair::deactivate(const PairType useMaster) {

View File

@@ -119,7 +119,7 @@ void MidiStreamParserImpl::parseStream(const Bit8u *stream, Bit32u length) {
void MidiStreamParserImpl::processShortMessage(const Bit32u message) {
// Adds running status to the MIDI message if it doesn't contain one
Bit8u status = Bit8u(message);
Bit8u status = Bit8u(message & 0xFF);
if (0xF8 <= status) {
midiReceiver.handleSystemRealtimeMessage(status);
} else if (processStatusByte(status)) {

View File

@@ -340,10 +340,13 @@ void RhythmPart::setPan(unsigned int midiPan) {
void Part::setPan(unsigned int midiPan) {
// NOTE: Panning is inverted compared to GM.
// CM-32L: Divide by 8.5
patchTemp->panpot = Bit8u((midiPan << 3) / 68);
// FIXME: MT-32: Divide by 9
//patchTemp->panpot = Bit8u(midiPan / 9);
if (synth->controlROMFeatures->quirkPanMult) {
// MT-32: Divide by 9
patchTemp->panpot = Bit8u(midiPan / 9);
} else {
// CM-32L: Divide by 8.5
patchTemp->panpot = Bit8u((midiPan << 3) / 68);
}
//synth->printDebug("%s (%s): Set pan to %d", name, currentInstr, panpot);
}
@@ -352,6 +355,10 @@ void Part::setPan(unsigned int midiPan) {
* Applies key shift to a MIDI key and converts it into an internal key value in the range 12-108.
*/
unsigned int Part::midiKeyToKey(unsigned int midiKey) {
if (synth->controlROMFeatures->quirkKeyShift) {
// NOTE: On MT-32 GEN0, key isn't adjusted, and keyShift is applied further in TVP, unlike newer units:
return midiKey;
}
int key = midiKey + patchTemp->patch.keyShift;
if (key < 36) {
// After keyShift is applied, key < 36, so move up by octaves

View File

@@ -272,6 +272,10 @@ bool Partial::isRingModulatingSlave() const {
return pair != NULL && structurePosition == 1 && (mixType == 1 || mixType == 2);
}
bool Partial::isRingModulatingNoMix() const {
return pair != NULL && ((structurePosition == 1 && mixType == 1) || mixType == 2);
}
bool Partial::isPCM() const {
return pcmWave != NULL;
}
@@ -368,7 +372,7 @@ bool Partial::doProduceOutput(Sample *leftBuf, Sample *rightBuf, Bit32u length,
bool Partial::produceOutput(IntSample *leftBuf, IntSample *rightBuf, Bit32u length) {
if (floatMode) {
synth->printDebug("Partial: Invalid call to produceOutput()!\n", synth->getSelectedRendererType());
synth->printDebug("Partial: Invalid call to produceOutput()! Renderer = %d\n", synth->getSelectedRendererType());
return false;
}
return doProduceOutput(leftBuf, rightBuf, length, static_cast<LA32IntPartialPair *>(la32Pair));
@@ -376,7 +380,7 @@ bool Partial::produceOutput(IntSample *leftBuf, IntSample *rightBuf, Bit32u leng
bool Partial::produceOutput(FloatSample *leftBuf, FloatSample *rightBuf, Bit32u length) {
if (!floatMode) {
synth->printDebug("Partial: Invalid call to produceOutput()!\n", synth->getSelectedRendererType());
synth->printDebug("Partial: Invalid call to produceOutput()! Renderer = %d\n", synth->getSelectedRendererType());
return false;
}
return doProduceOutput(leftBuf, rightBuf, length, static_cast<LA32FloatPartialPair *>(la32Pair));

View File

@@ -108,6 +108,7 @@ public:
void startAbort();
void startDecayAll();
bool shouldReverb();
bool isRingModulatingNoMix() const;
bool hasRingModulatingSlave() const;
bool isRingModulatingSlave() const;
bool isPCM() const;

View File

@@ -31,6 +31,7 @@ static const ROMInfo *getKnownROMInfoFromList(Bit32u index) {
static const ROMInfo CTRL_MT32_V1_07 = {65536, "b083518fffb7f66b03c23b7eb4f868e62dc5a987", ROMInfo::Control, "ctrl_mt32_1_07", "MT-32 Control v1.07", ROMInfo::Full, NULL};
static const ROMInfo CTRL_MT32_BLUER = {65536, "7b8c2a5ddb42fd0732e2f22b3340dcf5360edf92", ROMInfo::Control, "ctrl_mt32_bluer", "MT-32 Control BlueRidge", ROMInfo::Full, NULL};
static const ROMInfo CTRL_MT32_V2_04 = {131072, "2c16432b6c73dd2a3947cba950a0f4c19d6180eb", ROMInfo::Control, "ctrl_mt32_2_04", "MT-32 Control v2.04", ROMInfo::Full, NULL};
static const ROMInfo CTRL_CM32L_V1_00 = {65536, "73683d585cd6948cc19547942ca0e14a0319456d", ROMInfo::Control, "ctrl_cm32l_1_00", "CM-32L/LAPC-I Control v1.00", ROMInfo::Full, NULL};
static const ROMInfo CTRL_CM32L_V1_02 = {65536, "a439fbb390da38cada95a7cbb1d6ca199cd66ef8", ROMInfo::Control, "ctrl_cm32l_1_02", "CM-32L/LAPC-I Control v1.02", ROMInfo::Full, NULL};
@@ -43,6 +44,7 @@ static const ROMInfo *getKnownROMInfoFromList(Bit32u index) {
&CTRL_MT32_V1_06,
&CTRL_MT32_V1_07,
&CTRL_MT32_BLUER,
&CTRL_MT32_V2_04,
&CTRL_CM32L_V1_00,
&CTRL_CM32L_V1_02,
&PCM_MT32,

View File

@@ -0,0 +1,60 @@
/* Copyright (C) 2015-2017 Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "SampleRateConverter.h"
#include "Synth.h"
using namespace MT32Emu;
static inline void *createDelegate(Synth &synth, double targetSampleRate, SamplerateConversionQuality quality) {
return 0;
}
AnalogOutputMode SampleRateConverter::getBestAnalogOutputMode(double targetSampleRate) {
return AnalogOutputMode_COARSE;
}
SampleRateConverter::SampleRateConverter(Synth &useSynth, double targetSampleRate, SamplerateConversionQuality useQuality) :
synthInternalToTargetSampleRateRatio(SAMPLE_RATE / targetSampleRate),
useSynthDelegate(useSynth.getStereoOutputSampleRate() == targetSampleRate),
srcDelegate(useSynthDelegate ? &useSynth : createDelegate(useSynth, targetSampleRate, useQuality))
{}
SampleRateConverter::~SampleRateConverter() {
}
void SampleRateConverter::getOutputSamples(float *buffer, unsigned int length) {
if (useSynthDelegate) {
static_cast<Synth *>(srcDelegate)->render(buffer, length);
return;
}
}
void SampleRateConverter::getOutputSamples(Bit16s *outBuffer, unsigned int length) {
if (useSynthDelegate) {
static_cast<Synth *>(srcDelegate)->render(outBuffer, length);
return;
}
}
double SampleRateConverter::convertOutputToSynthTimestamp(double outputTimestamp) const {
return outputTimestamp * synthInternalToTargetSampleRateRatio;
}
double SampleRateConverter::convertSynthToOutputTimestamp(double synthTimestamp) const {
return synthTimestamp / synthInternalToTargetSampleRateRatio;
}

View File

@@ -184,7 +184,13 @@ struct SoundGroup {
#endif
struct ControlROMFeatureSet {
unsigned int quirkBasePitchOverflow : 1;
unsigned int quirkPitchEnvelopeOverflow : 1;
unsigned int quirkRingModulationNoMix : 1;
unsigned int quirkTVAZeroEnvLevels : 1;
unsigned int quirkPanMult : 1;
unsigned int quirkKeyShift : 1;
unsigned int quirkTVFBaseCutoffLimit : 1;
// Features below don't actually depend on control ROM version, which is used to identify hardware model
unsigned int defaultReverbMT32Compatible : 1;

View File

@@ -32,25 +32,50 @@
#include "ROMInfo.h"
#include "TVA.h"
#if MT32EMU_MONITOR_SYSEX > 0
#include "mmath.h"
#endif
namespace MT32Emu {
// MIDI interface data transfer rate in samples. Used to simulate the transfer delay.
static const double MIDI_DATA_TRANSFER_RATE = double(SAMPLE_RATE) / 31250.0 * 8.0;
// FIXME: there should be more specific feature sets for various MT-32 control ROM versions
static const ControlROMFeatureSet OLD_MT32_COMPATIBLE = { true, true, true };
static const ControlROMFeatureSet CM32L_COMPATIBLE = { false, false, false };
static const ControlROMFeatureSet OLD_MT32_COMPATIBLE = {
true, // quirkBasePitchOverflow
true, // quirkPitchEnvelopeOverflow
true, // quirkRingModulationNoMix
true, // quirkTVAZeroEnvLevels
true, // quirkPanMult
true, // quirkKeyShift
true, // quirkTVFBaseCutoffLimit
true, // defaultReverbMT32Compatible
true // oldMT32AnalogLPF
};
static const ControlROMFeatureSet CM32L_COMPATIBLE = {
false, // quirkBasePitchOverflow
false, // quirkPitchEnvelopeOverflow
false, // quirkRingModulationNoMix
false, // quirkTVAZeroEnvLevels
false, // quirkPanMult
false, // quirkKeyShift
false, // quirkTVFBaseCutoffLimit
false, // defaultReverbMT32Compatible
false // oldMT32AnalogLPF
};
static const ControlROMMap ControlROMMaps[7] = {
static const ControlROMMap ControlROMMaps[8] = {
// ID Features PCMmap PCMc tmbrA tmbrAO, tmbrAC tmbrB tmbrBO tmbrBC tmbrR trC rhythm rhyC rsrv panpot prog rhyMax patMax sysMax timMax sndGrp sGC
{ "ctrl_mt32_1_04", OLD_MT32_COMPATIBLE, 0x3000, 128, 0x8000, 0x0000, false, 0xC000, 0x4000, false, 0x3200, 30, 0x73A6, 85, 0x57C7, 0x57E2, 0x57D0, 0x5252, 0x525E, 0x526E, 0x520A, 0x7064, 19 },
{ "ctrl_mt32_1_05", OLD_MT32_COMPATIBLE, 0x3000, 128, 0x8000, 0x0000, false, 0xC000, 0x4000, false, 0x3200, 30, 0x7414, 85, 0x57C7, 0x57E2, 0x57D0, 0x5252, 0x525E, 0x526E, 0x520A, 0x70CA, 19 },
{ "ctrl_mt32_1_06", OLD_MT32_COMPATIBLE, 0x3000, 128, 0x8000, 0x0000, false, 0xC000, 0x4000, false, 0x3200, 30, 0x7414, 85, 0x57D9, 0x57F4, 0x57E2, 0x5264, 0x5270, 0x5280, 0x521C, 0x70CA, 19 },
{ "ctrl_mt32_1_07", OLD_MT32_COMPATIBLE, 0x3000, 128, 0x8000, 0x0000, false, 0xC000, 0x4000, false, 0x3200, 30, 0x73fe, 85, 0x57B1, 0x57CC, 0x57BA, 0x523C, 0x5248, 0x5258, 0x51F4, 0x70B0, 19 }, // MT-32 revision 1
{"ctrl_mt32_bluer", OLD_MT32_COMPATIBLE, 0x3000, 128, 0x8000, 0x0000, false, 0xC000, 0x4000, false, 0x3200, 30, 0x741C, 85, 0x57E5, 0x5800, 0x57EE, 0x5270, 0x527C, 0x528C, 0x5228, 0x70CE, 19 }, // MT-32 Blue Ridge mod
{"ctrl_mt32_2_04", CM32L_COMPATIBLE, 0x8100, 128, 0x8000, 0x8000, true, 0x8080, 0x8000, true, 0x8500, 30, 0x8580, 85, 0x4F5D, 0x4F78, 0x4F66, 0x4899, 0x489D, 0x48B6, 0x48CD, 0x5A58, 19 },
{"ctrl_cm32l_1_00", CM32L_COMPATIBLE, 0x8100, 256, 0x8000, 0x8000, true, 0x8080, 0x8000, true, 0x8500, 64, 0x8580, 85, 0x4F65, 0x4F80, 0x4F6E, 0x48A1, 0x48A5, 0x48BE, 0x48D5, 0x5A6C, 19 },
{"ctrl_cm32l_1_02", CM32L_COMPATIBLE, 0x8100, 256, 0x8000, 0x8000, true, 0x8080, 0x8000, true, 0x8500, 64, 0x8580, 85, 0x4F93, 0x4FAE, 0x4F9C, 0x48CB, 0x48CF, 0x48E8, 0x48FF, 0x5A96, 19 } // CM-32L
// (Note that all but CM-32L ROM actually have 86 entries for rhythmTemp)
// (Note that old MT-32 ROMs actually have 86 entries for rhythmTemp)
};
static const PartialState PARTIAL_PHASE_TO_STATE[8] = {
@@ -78,7 +103,7 @@ protected:
Synth &synth;
void printDebug(const char *msg) const {
synth.printDebug(msg);
synth.printDebug("%s", msg);
}
bool isActivated() const {
@@ -168,6 +193,8 @@ public:
class Extensions {
public:
RendererType selectedRendererType;
Bit32s masterTunePitchDelta;
bool niceAmpRamp;
};
Bit32u Synth::getLibraryVersionInt() {
@@ -222,6 +249,7 @@ Synth::Synth(ReportHandler *useReportHandler) :
setOutputGain(1.0f);
setReverbOutputGain(1.0f);
setReversedStereoEnabled(false);
setNiceAmpRampEnabled(true);
selectRendererType(RendererType_BIT16S);
patchTempMemoryRegion = NULL;
@@ -387,6 +415,14 @@ bool Synth::isReversedStereoEnabled() const {
return reversedStereoEnabled;
}
void Synth::setNiceAmpRampEnabled(bool enabled) {
extensions.niceAmpRamp = enabled;
}
bool Synth::isNiceAmpRampEnabled() const {
return extensions.niceAmpRamp;
}
bool Synth::loadControlROM(const ROMImage &controlROMImage) {
File *file = controlROMImage.getFile();
const ROMInfo *controlROMInfo = controlROMImage.getROMInfo();
@@ -679,6 +715,7 @@ bool Synth::open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage, B
bool oldReverbOverridden = reverbOverridden;
reverbOverridden = false;
refreshSystem();
resetMasterTunePitchDelta();
reverbOverridden = oldReverbOverridden;
char(*writableSoundGroupNames)[9] = new char[controlROMMap->soundGroupsCount][9];
@@ -865,13 +902,17 @@ Bit32u Synth::addMIDIInterfaceDelay(Bit32u len, Bit32u timestamp) {
return timestamp;
}
Bit32u Synth::getInternalRenderedSampleCount() const {
return renderedSampleCount;
}
bool Synth::playMsg(Bit32u msg) {
return playMsg(msg, renderedSampleCount);
}
bool Synth::playMsg(Bit32u msg, Bit32u timestamp) {
if ((msg & 0xF8) == 0xF8) {
reportHandler->onMIDISystemRealtime(Bit8u(msg));
reportHandler->onMIDISystemRealtime(Bit8u(msg & 0xFF));
return true;
}
if (midiQueue == NULL) return false;
@@ -1539,6 +1580,8 @@ void Synth::writeMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u le
}
void Synth::refreshSystemMasterTune() {
// 171 is ~half a semitone.
extensions.masterTunePitchDelta = ((mt32ram.system.masterTune - 64) * 171) >> 6; // PORTABILITY NOTE: Assumes arithmetic shift.
#if MT32EMU_MONITOR_SYSEX > 0
//FIXME:KG: This is just an educated guess.
// The LAPC-I documentation claims a range of 427.5Hz-452.6Hz (similar to what we have here)
@@ -1650,9 +1693,25 @@ void Synth::reset() {
}
}
refreshSystem();
resetMasterTunePitchDelta();
isActive();
}
void Synth::resetMasterTunePitchDelta() {
// This effectively resets master tune to 440.0Hz.
// Despite that the manual claims 442.0Hz is the default setting for master tune,
// it doesn't actually take effect upon a reset due to a bug in the reset routine.
// CONFIRMED: This bug is present in all supported Control ROMs.
extensions.masterTunePitchDelta = 0;
#if MT32EMU_MONITOR_SYSEX > 0
printDebug(" Actual Master Tune reset to 440.0");
#endif
}
Bit32s Synth::getMasterTunePitchDelta() const {
return extensions.masterTunePitchDelta;
}
MidiEvent::~MidiEvent() {
if (sysexData != NULL) {
delete[] sysexData;
@@ -2013,21 +2072,35 @@ void RendererImpl<IntSample>::convertSamplesToOutput(IntSample *buffer, Bit32u l
}
static inline float produceDistortedSample(float sample) {
if (sample < -2.0f) {
return sample + 4.0f;
} else if (2.0f < sample) {
return sample - 4.0f;
// Here we roughly simulate the distortion caused by the DAC bit shift.
if (sample < -1.0f) {
return sample + 2.0f;
} else if (1.0f < sample) {
return sample - 2.0f;
}
return sample;
}
template <>
void RendererImpl<FloatSample>::produceLA32Output(FloatSample *buffer, Bit32u len) {
if (synth.getDACInputMode() == DACInputMode_GENERATION2) {
switch (synth.getDACInputMode()) {
case DACInputMode_NICE:
// Note, we do not do any clamping for floats here to avoid introducing distortions.
// This means that the output signal may actually overshoot the unity when the volume is set too high.
// We leave it up to the consumer whether the output is to be clamped or properly normalised further on.
while (len--) {
*buffer = produceDistortedSample(*buffer);
*buffer *= 2.0f;
buffer++;
}
break;
case DACInputMode_GENERATION2:
while (len--) {
*buffer = produceDistortedSample(2.0f * *buffer);
buffer++;
}
break;
default:
break;
}
}
@@ -2035,7 +2108,7 @@ template <>
void RendererImpl<FloatSample>::convertSamplesToOutput(FloatSample *buffer, Bit32u len) {
if (synth.getDACInputMode() == DACInputMode_GENERATION1) {
while (len--) {
*buffer = produceDistortedSample(*buffer);
*buffer = produceDistortedSample(2.0f * *buffer);
buffer++;
}
}

View File

@@ -124,6 +124,7 @@ friend class RhythmPart;
friend class SamplerateAdapter;
friend class SoxrAdapter;
friend class TVA;
friend class TVF;
friend class TVP;
private:
@@ -230,6 +231,9 @@ private:
// partNum should be 0..7 for Part 1..8, or 8 for Rhythm
const Part *getPart(Bit8u partNum) const;
void resetMasterTunePitchDelta();
Bit32s getMasterTunePitchDelta() const;
public:
static inline Bit16s clipSampleEx(Bit32s sampleEx) {
// Clamp values above 32767 to 32767, and values below -32768 to -32768
@@ -258,11 +262,11 @@ public:
}
static inline Bit16s convertSample(float sample) {
return Synth::clipSampleEx(Bit32s(sample * 16384.0f)); // This multiplier takes into account the DAC bit shift
return Synth::clipSampleEx(Bit32s(sample * 32768.0f)); // This multiplier corresponds to normalised floats
}
static inline float convertSample(Bit16s sample) {
return float(sample) / 16384.0f; // This multiplier takes into account the DAC bit shift
return float(sample) / 32768.0f; // This multiplier corresponds to normalised floats
}
// Returns library version as an integer in format: 0x00MMmmpp, where:
@@ -308,6 +312,10 @@ public:
// Returns the actual queue size being used.
MT32EMU_EXPORT Bit32u setMIDIEventQueueSize(Bit32u);
// Returns current value of the global counter of samples rendered since the synth was created (at the native sample rate 32000 Hz).
// This method helps to compute accurate timestamp of a MIDI message to use with the methods below.
MT32EMU_EXPORT Bit32u getInternalRenderedSampleCount() const;
// Enqueues a MIDI event for subsequent playback.
// The MIDI event will be processed not before the specified timestamp.
// The timestamp is measured as the global rendered sample count since the synth was created (at the native sample rate 32000 Hz).
@@ -382,7 +390,6 @@ public:
// Sets output gain factor for synth output channels. Applied to all output samples and unrelated with the synth's Master volume,
// it rather corresponds to the gain of the output analog circuitry of the hardware units. However, together with setReverbOutputGain()
// it offers to the user a capability to control the gain of reverb and non-reverb output channels independently.
// Ignored in DACInputMode_PURE
MT32EMU_EXPORT void setOutputGain(float gain);
// Returns current output gain factor for synth output channels.
MT32EMU_EXPORT float getOutputGain() const;
@@ -395,7 +402,6 @@ public:
// corresponds to the level of digital capture. Although, according to the CM-64 PCB schematic,
// there is a difference in the reverb analogue circuit, and the resulting output gain is 0.68
// of that for LA32 analogue output. This factor is applied to the reverb output gain.
// Ignored in DACInputMode_PURE
MT32EMU_EXPORT void setReverbOutputGain(float gain);
// Returns current output gain factor for reverb wet output channels.
MT32EMU_EXPORT float getReverbOutputGain() const;
@@ -405,6 +411,16 @@ public:
// Returns whether left and right output channels are swapped.
MT32EMU_EXPORT bool isReversedStereoEnabled() const;
// Allows to toggle the NiceAmpRamp mode.
// In this mode, we want to ensure that amp ramp never jumps to the target
// value and always gradually increases or decreases. It seems that real units
// do not bother to always check if a newly started ramp leads to a jump.
// We also prefer the quality improvement over the emulation accuracy,
// so this mode is enabled by default.
MT32EMU_EXPORT void setNiceAmpRampEnabled(bool enabled);
// Returns whether NiceAmpRamp mode is enabled.
MT32EMU_EXPORT bool isNiceAmpRampEnabled() const;
// Selects new type of the wave generator and renderer to be used during subsequent calls to open().
// By default, RendererType_BIT16S is selected.
// See RendererType for details.

View File

@@ -43,7 +43,7 @@ void TVA::startRamp(Bit8u newTarget, Bit8u newIncrement, int newPhase) {
phase = newPhase;
ampRamp->startRamp(newTarget, newIncrement);
#if MT32EMU_MONITOR_TVA >= 1
partial->getSynth()->printDebug("[+%lu] [Partial %d] TVA,ramp,%d,%d,%d,%d", partial->debugGetSampleNum(), partial->debugGetPartialNum(), (newIncrement & 0x80) ? -1 : 1, (newIncrement & 0x7F), newPhase);
partial->getSynth()->printDebug("[+%lu] [Partial %d] TVA,ramp,%x,%s%x,%d", partial->debugGetSampleNum(), partial->debugGetPartialNum(), newTarget, (newIncrement & 0x80) ? "-" : "+", (newIncrement & 0x7F), newPhase);
#endif
}
@@ -99,10 +99,10 @@ static int calcVeloAmpSubtraction(Bit8u veloSensitivity, unsigned int velocity)
return absVelocityMult - (velocityMult >> 8); // PORTABILITY NOTE: Assumes arithmetic shift
}
static int calcBasicAmp(const Tables *tables, const Partial *partial, const MemParams::System *system, const TimbreParam::PartialParam *partialParam, const MemParams::PatchTemp *patchTemp, const MemParams::RhythmTemp *rhythmTemp, int biasAmpSubtraction, int veloAmpSubtraction, Bit8u expression) {
static int calcBasicAmp(const Tables *tables, const Partial *partial, const MemParams::System *system, const TimbreParam::PartialParam *partialParam, const MemParams::PatchTemp *patchTemp, const MemParams::RhythmTemp *rhythmTemp, int biasAmpSubtraction, int veloAmpSubtraction, Bit8u expression, bool hasRingModQuirk) {
int amp = 155;
if (!partial->isRingModulatingSlave()) {
if (!(hasRingModQuirk ? partial->isRingModulatingNoMix() : partial->isRingModulatingSlave())) {
amp -= tables->masterVolToAmpSubtraction[system->masterVol];
if (amp < 0) {
return 0;
@@ -169,7 +169,7 @@ void TVA::reset(const Part *newPart, const TimbreParam::PartialParam *newPartial
biasAmpSubtraction = calcBiasAmpSubtractions(partialParam, key);
veloAmpSubtraction = calcVeloAmpSubtraction(partialParam->tva.veloSensitivity, velocity);
int newTarget = calcBasicAmp(tables, partial, system, partialParam, patchTemp, newRhythmTemp, biasAmpSubtraction, veloAmpSubtraction, part->getExpression());
int newTarget = calcBasicAmp(tables, partial, system, partialParam, patchTemp, newRhythmTemp, biasAmpSubtraction, veloAmpSubtraction, part->getExpression(), partial->getSynth()->controlROMFeatures->quirkRingModulationNoMix);
int newPhase;
if (partialParam->tva.envTime[0] == 0) {
// Initially go to the TVA_PHASE_ATTACK target amp, and spend the next phase going from there to the TVA_PHASE_2 target amp
@@ -221,18 +221,29 @@ void TVA::recalcSustain() {
}
// We're sustaining. Recalculate all the values
const Tables *tables = &Tables::getInstance();
int newTarget = calcBasicAmp(tables, partial, system, partialParam, patchTemp, rhythmTemp, biasAmpSubtraction, veloAmpSubtraction, part->getExpression());
int newTarget = calcBasicAmp(tables, partial, system, partialParam, patchTemp, rhythmTemp, biasAmpSubtraction, veloAmpSubtraction, part->getExpression(), partial->getSynth()->controlROMFeatures->quirkRingModulationNoMix);
newTarget += partialParam->tva.envLevel[3];
// Since we're in TVA_PHASE_SUSTAIN at this point, we know that target has been reached and an interrupt fired, so we can rely on it being the current amp.
// Although we're in TVA_PHASE_SUSTAIN at this point, we cannot be sure that there is no active ramp at the moment.
// In case the channel volume or the expression changes frequently, the previously started ramp may still be in progress.
// Real hardware units ignore this possibility and rely on the assumption that the target is the current amp.
// This is OK in most situations but when the ramp that is currently in progress needs to change direction
// due to a volume/expression update, this leads to a jump in the amp that is audible as an unpleasant click.
// To avoid that, we compare the newTarget with the the actual current ramp value and correct the direction if necessary.
int targetDelta = newTarget - target;
// Calculate an increment to get to the new amp value in a short, more or less consistent amount of time
Bit8u newIncrement;
if (targetDelta >= 0) {
bool descending = targetDelta < 0;
if (!descending) {
newIncrement = tables->envLogarithmicTime[Bit8u(targetDelta)] - 2;
} else {
newIncrement = (tables->envLogarithmicTime[Bit8u(-targetDelta)] - 2) | 0x80;
}
if (part->getSynth()->isNiceAmpRampEnabled() && (descending != ampRamp->isBelowCurrent(newTarget))) {
newIncrement ^= 0x80;
}
// Configure so that once the transition's complete and nextPhase() is called, we'll just re-enter sustain phase (or decay phase, depending on parameters at the time).
startRamp(newTarget, newIncrement, TVA_PHASE_SUSTAIN - 1);
}
@@ -260,7 +271,7 @@ void TVA::nextPhase() {
}
bool allLevelsZeroFromNowOn = false;
if (partialParam->tva.envLevel[3] == 0) {
if (!partial->getSynth()->controlROMFeatures->quirkTVAZeroEnvLevels && partialParam->tva.envLevel[3] == 0) {
if (newPhase == TVA_PHASE_4) {
allLevelsZeroFromNowOn = true;
} else if (partialParam->tva.envLevel[2] == 0) {
@@ -283,7 +294,7 @@ void TVA::nextPhase() {
int envPointIndex = phase;
if (!allLevelsZeroFromNowOn) {
newTarget = calcBasicAmp(tables, partial, system, partialParam, patchTemp, rhythmTemp, biasAmpSubtraction, veloAmpSubtraction, part->getExpression());
newTarget = calcBasicAmp(tables, partial, system, partialParam, patchTemp, rhythmTemp, biasAmpSubtraction, veloAmpSubtraction, part->getExpression(), partial->getSynth()->controlROMFeatures->quirkRingModulationNoMix);
if (newPhase == TVA_PHASE_SUSTAIN || newPhase == TVA_PHASE_RELEASE) {
if (partialParam->tva.envLevel[3] == 0) {

View File

@@ -21,6 +21,7 @@
#include "LA32Ramp.h"
#include "Partial.h"
#include "Poly.h"
#include "Synth.h"
#include "Tables.h"
namespace MT32Emu {
@@ -52,7 +53,7 @@ enum {
PHASE_DONE = 7
};
static int calcBaseCutoff(const TimbreParam::PartialParam *partialParam, Bit32u basePitch, unsigned int key) {
static int calcBaseCutoff(const TimbreParam::PartialParam *partialParam, Bit32u basePitch, unsigned int key, bool quirkTVFBaseCutoffLimit) {
// This table matches the values used by a real LAPC-I.
static const Bit8s biasLevelToBiasMult[] = {85, 42, 21, 16, 10, 5, 2, 0, -2, -5, -10, -16, -21, -74, -85};
// These values represent unique options with no consistent pattern, so we have to use something like a table in any case.
@@ -90,8 +91,14 @@ static int calcBaseCutoff(const TimbreParam::PartialParam *partialParam, Bit32u
if (pitchDeltaThing > 0) {
baseCutoff -= pitchDeltaThing;
}
} else if (baseCutoff < -2048) {
baseCutoff = -2048;
} else if (quirkTVFBaseCutoffLimit) {
if (baseCutoff <= -0x400) {
baseCutoff = -400;
}
} else {
if (baseCutoff < -2048) {
baseCutoff = -2048;
}
}
baseCutoff += 2056;
baseCutoff >>= 4; // PORTABILITY NOTE: Hmm... Depends whether it could've been below -2056, but maybe arithmetic shift assumed?
@@ -110,7 +117,7 @@ void TVF::startRamp(Bit8u newTarget, Bit8u newIncrement, int newPhase) {
phase = newPhase;
cutoffModifierRamp->startRamp(newTarget, newIncrement);
#if MT32EMU_MONITOR_TVF >= 1
partial->getSynth()->printDebug("[+%lu] [Partial %d] TVF,ramp,%d,%d,%d,%d", partial->debugGetSampleNum(), partial->debugGetPartialNum(), newTarget, (newIncrement & 0x80) ? -1 : 1, (newIncrement & 0x7F), newPhase);
partial->getSynth()->printDebug("[+%lu] [Partial %d] TVF,ramp,%x,%s%x,%d", partial->debugGetSampleNum(), partial->debugGetPartialNum(), newTarget, (newIncrement & 0x80) ? "-" : "+", (newIncrement & 0x7F), newPhase);
#endif
}
@@ -122,7 +129,7 @@ void TVF::reset(const TimbreParam::PartialParam *newPartialParam, unsigned int b
const Tables *tables = &Tables::getInstance();
baseCutoff = calcBaseCutoff(newPartialParam, basePitch, key);
baseCutoff = calcBaseCutoff(newPartialParam, basePitch, key, partial->getSynth()->controlROMFeatures->quirkTVFBaseCutoffLimit);
#if MT32EMU_MONITOR_TVF >= 1
partial->getSynth()->printDebug("[+%lu] [Partial %d] TVF,base,%d", partial->debugGetSampleNum(), partial->debugGetPartialNum(), baseCutoff);
#endif

View File

@@ -51,13 +51,15 @@ static Bit16u keyToPitchTable[] = {
21845, 22187, 22528, 22869
};
// We want to do processing 4000 times per second. FIXME: This is pretty arbitrary.
static const int NOMINAL_PROCESS_TIMER_PERIOD_SAMPLES = SAMPLE_RATE / 4000;
// The timer runs at 500kHz. This is how much to increment it after 8 samples passes.
// We multiply by 8 to get rid of the fraction and deal with just integers.
static const int PROCESS_TIMER_INCREMENT_x8 = 8 * 500000 / SAMPLE_RATE;
TVP::TVP(const Partial *usePartial) :
partial(usePartial), system(&usePartial->getSynth()->mt32ram.system) {
// We want to do processing 4000 times per second. FIXME: This is pretty arbitrary.
maxCounter = SAMPLE_RATE / 4000;
// The timer runs at 500kHz. We only need to bother updating it every maxCounter samples, before we do processing.
// This is how much to increment it by every maxCounter samples.
processTimerIncrement = 500000 * maxCounter / SAMPLE_RATE;
}
static Bit16s keyToPitch(unsigned int key) {
@@ -76,13 +78,15 @@ static inline Bit32s fineToPitch(Bit8u fine) {
return (fine - 50) * 4096 / 1200; // One cent per fine offset
}
static Bit32u calcBasePitch(const Partial *partial, const TimbreParam::PartialParam *partialParam, const MemParams::PatchTemp *patchTemp, unsigned int key) {
static Bit32u calcBasePitch(const Partial *partial, const TimbreParam::PartialParam *partialParam, const MemParams::PatchTemp *patchTemp, unsigned int key, const ControlROMFeatureSet *controlROMFeatures) {
Bit32s basePitch = keyToPitch(key);
basePitch = (basePitch * pitchKeyfollowMult[partialParam->wg.pitchKeyfollow]) >> 13; // PORTABILITY NOTE: Assumes arithmetic shift
basePitch += coarseToPitch(partialParam->wg.pitchCoarse);
basePitch += fineToPitch(partialParam->wg.pitchFine);
// NOTE:Mok: This is done on MT-32, but not LAPC-I:
//pitch += coarseToPitch(patchTemp->patch.keyShift + 12);
if (controlROMFeatures->quirkKeyShift) {
// NOTE:Mok: This is done on MT-32, but not LAPC-I:
basePitch += coarseToPitch(patchTemp->patch.keyShift + 12);
}
basePitch += fineToPitch(patchTemp->patch.fineTune);
const ControlROMPCMStruct *controlROMPCMStruct = partial->getControlROMPCMStruct();
@@ -97,7 +101,12 @@ static Bit32u calcBasePitch(const Partial *partial, const TimbreParam::PartialPa
basePitch += 33037;
}
}
if (basePitch < 0) {
// MT-32 GEN0 does 16-bit calculations here, allowing an integer overflow.
// This quirk is observable playing the patch defined for timbre "HIT BOTTOM" in Larry 3.
if (controlROMFeatures->quirkBasePitchOverflow) {
basePitch = basePitch & 0xffff;
} else if (basePitch < 0) {
basePitch = 0;
}
if (basePitch > 59392) {
@@ -107,18 +116,22 @@ static Bit32u calcBasePitch(const Partial *partial, const TimbreParam::PartialPa
}
static Bit32u calcVeloMult(Bit8u veloSensitivity, unsigned int velocity) {
if (veloSensitivity == 0 || veloSensitivity > 3) {
// Note that on CM-32L/LAPC-I veloSensitivity is never > 3, since it's clipped to 3 by the max tables.
if (veloSensitivity == 0) {
return 21845; // aka floor(4096 / 12 * 64), aka ~64 semitones
}
unsigned int reversedVelocity = 127 - velocity;
unsigned int scaledReversedVelocity;
if (veloSensitivity > 3) {
// Note that on CM-32L/LAPC-I veloSensitivity is never > 3, since it's clipped to 3 by the max tables.
// MT-32 GEN0 has a bug here that leads to unspecified behaviour. We assume it is as follows.
scaledReversedVelocity = (reversedVelocity << 8) >> ((3 - veloSensitivity) & 0x1f);
} else {
scaledReversedVelocity = reversedVelocity << (5 + veloSensitivity);
}
// When velocity is 127, the multiplier is 21845, aka ~64 semitones (regardless of veloSensitivity).
// The lower the velocity, the lower the multiplier. The veloSensitivity determines the amount decreased per velocity value.
// The minimum multiplier (with velocity 0, veloSensitivity 3) is 170 (~half a semitone).
Bit32u veloMult = 32768;
veloMult -= (127 - velocity) << (5 + veloSensitivity);
veloMult *= 21845;
veloMult >>= 15;
return veloMult;
// The minimum multiplier on CM-32L/LAPC-I (with velocity 0, veloSensitivity 3) is 170 (~half a semitone).
return ((32768 - scaledReversedVelocity) * 21845) >> 15;
}
static Bit32s calcTargetPitchOffsetWithoutLFO(const TimbreParam::PartialParam *partialParam, int levelIndex, unsigned int velocity) {
@@ -139,7 +152,7 @@ void TVP::reset(const Part *usePart, const TimbreParam::PartialParam *usePartial
// FIXME: We're using a per-TVP timer instead of a system-wide one for convenience.
timeElapsed = 0;
basePitch = calcBasePitch(partial, partialParam, patchTemp, key);
basePitch = calcBasePitch(partial, partialParam, patchTemp, key, partial->getSynth()->controlROMFeatures);
currentPitchOffset = calcTargetPitchOffsetWithoutLFO(partialParam, 0, velocity);
targetPitchOffsetWithoutLFO = currentPitchOffset;
phase = 0;
@@ -166,22 +179,23 @@ Bit32u TVP::getBasePitch() const {
void TVP::updatePitch() {
Bit32s newPitch = basePitch + currentPitchOffset;
if (!partial->isPCM() || (partial->getControlROMPCMStruct()->len & 0x01) == 0) { // FIXME: Use !partial->pcmWaveEntry->unaffectedByMasterTune instead
// FIXME: masterTune recalculation doesn't really happen here, and there are various bugs not yet emulated
// FIXME: There are various bugs not yet emulated
// 171 is ~half a semitone.
newPitch += ((system->masterTune - 64) * 171) >> 6; // PORTABILITY NOTE: Assumes arithmetic shift.
newPitch += partial->getSynth()->getMasterTunePitchDelta();
}
if ((partialParam->wg.pitchBenderEnabled & 1) != 0) {
newPitch += part->getPitchBend();
}
if (newPitch < 0) {
// MT-32 GEN0 does 16-bit calculations here, allowing an integer overflow.
// This quirk is exploited e.g. in Colonel's Bequest timbres "Lightning" and "SwmpBackgr".
if (partial->getSynth()->controlROMFeatures->quirkPitchEnvelopeOverflow) {
newPitch = newPitch & 0xffff;
} else if (newPitch < 0) {
newPitch = 0;
}
// Skipping this check seems about right emulation of MT-32 GEN0 quirk exploited in Colonel's Bequest timbre "Lightning"
if (partial->getSynth()->controlROMFeatures->quirkPitchEnvelopeOverflow == 0) {
if (newPitch > 59392) {
newPitch = 59392;
}
if (newPitch > 59392) {
newPitch = 59392;
}
pitch = Bit16u(newPitch);
@@ -284,13 +298,19 @@ void TVP::startDecay() {
}
Bit16u TVP::nextPitch() {
// FIXME: Write explanation of counter and time increment
// We emulate MCU software timer using these counter and processTimerIncrement variables.
// The value of nominalProcessTimerPeriod approximates the period in samples
// between subsequent firings of the timer that normally occur.
// However, accurate emulation is quite complicated because the timer is not guaranteed to fire in time.
// This makes pitch variations on real unit non-deterministic and dependent on various factors.
if (counter == 0) {
timeElapsed += processTimerIncrement;
timeElapsed = timeElapsed & 0x00FFFFFF;
timeElapsed = (timeElapsed + processTimerIncrement) & 0x00FFFFFF;
// This roughly emulates pitch deviations observed on real units when playing a single partial that uses TVP/LFO.
counter = NOMINAL_PROCESS_TIMER_PERIOD_SAMPLES + (rand() & 3);
processTimerIncrement = (PROCESS_TIMER_INCREMENT_x8 * counter) >> 3;
process();
}
counter = (counter + 1) % maxCounter;
counter--;
return pitch;
}

View File

@@ -36,7 +36,6 @@ private:
const TimbreParam::PartialParam *partialParam;
const MemParams::PatchTemp *patchTemp;
int maxCounter;
int processTimerIncrement;
int counter;
Bit32u timeElapsed;

View File

@@ -22,6 +22,7 @@
#include "../ROMInfo.h"
#include "../Synth.h"
#include "../MidiStreamParser.h"
#include "../SampleRateConverter.h"
#include "c_types.h"
#include "c_interface.h"
@@ -30,11 +31,17 @@ using namespace MT32Emu;
namespace MT32Emu {
struct SamplerateConversionState {
double outputSampleRate;
SamplerateConversionQuality srcQuality;
SampleRateConverter *src;
};
static mt32emu_service_version getSynthVersionID(mt32emu_service_i) {
return MT32EMU_SERVICE_VERSION_CURRENT;
}
static const mt32emu_service_i_v0 SERVICE_VTABLE = {
static const mt32emu_service_i_v2 SERVICE_VTABLE = {
getSynthVersionID,
mt32emu_get_supported_report_handler_version,
mt32emu_get_supported_midi_receiver_version,
@@ -95,7 +102,17 @@ static const mt32emu_service_i_v0 SERVICE_VTABLE = {
mt32emu_get_partial_states,
mt32emu_get_playing_notes,
mt32emu_get_patch_name,
mt32emu_read_memory
mt32emu_read_memory,
mt32emu_get_best_analog_output_mode,
mt32emu_set_stereo_output_samplerate,
mt32emu_set_samplerate_conversion_quality,
mt32emu_select_renderer_type,
mt32emu_get_selected_renderer_type,
mt32emu_convert_output_to_synth_timestamp,
mt32emu_convert_synth_to_output_timestamp,
mt32emu_get_internal_rendered_sample_count,
mt32emu_set_nice_amp_ramp_enabled,
mt32emu_is_nice_amp_ramp_enabled
};
} // namespace MT32Emu
@@ -108,6 +125,7 @@ struct mt32emu_data {
DefaultMidiStreamParser *midiParser;
Bit32u partialCount;
AnalogOutputMode analogOutputMode;
SamplerateConversionState *srcState;
};
// Internal C++ utility stuff
@@ -305,7 +323,7 @@ extern "C" {
mt32emu_service_i mt32emu_get_service_i() {
mt32emu_service_i i;
i.v0 = &SERVICE_VTABLE;
i.v2 = &SERVICE_VTABLE;
return i;
}
@@ -329,6 +347,10 @@ mt32emu_bit32u mt32emu_get_stereo_output_samplerate(const mt32emu_analog_output_
return Synth::getStereoOutputSampleRate(static_cast<AnalogOutputMode>(analog_output_mode));
}
mt32emu_analog_output_mode mt32emu_get_best_analog_output_mode(const double target_samplerate) {
return mt32emu_analog_output_mode(SampleRateConverter::getBestAnalogOutputMode(target_samplerate));
}
mt32emu_context mt32emu_create_context(mt32emu_report_handler_i report_handler, void *instance_data) {
mt32emu_data *data = new mt32emu_data;
data->reportHandler = (report_handler.v0 != NULL) ? new DelegatingReportHandlerAdapter(report_handler, instance_data) : new ReportHandler;
@@ -339,12 +361,22 @@ mt32emu_context mt32emu_create_context(mt32emu_report_handler_i report_handler,
data->partialCount = DEFAULT_MAX_PARTIALS;
data->analogOutputMode = AnalogOutputMode_COARSE;
data->srcState = new SamplerateConversionState;
data->srcState->outputSampleRate = 0.0;
data->srcState->srcQuality = SamplerateConversionQuality_GOOD;
data->srcState->src = NULL;
return data;
}
void mt32emu_free_context(mt32emu_context data) {
if (data == NULL) return;
delete data->srcState->src;
data->srcState->src = NULL;
delete data->srcState;
data->srcState = NULL;
if (data->controlROMImage != NULL) {
delete data->controlROMImage->getFile();
ROMImage::freeROMImage(data->controlROMImage);
@@ -417,6 +449,16 @@ void mt32emu_set_analog_output_mode(mt32emu_context context, const mt32emu_analo
context->analogOutputMode = static_cast<AnalogOutputMode>(analog_output_mode);
}
void mt32emu_set_stereo_output_samplerate(mt32emu_context context, const double samplerate) {
if (0.0 <= samplerate) {
context->srcState->outputSampleRate = samplerate;
}
}
void mt32emu_set_samplerate_conversion_quality(mt32emu_context context, const mt32emu_samplerate_conversion_quality quality) {
context->srcState->srcQuality = SamplerateConversionQuality(quality);
}
void mt32emu_select_renderer_type(mt32emu_context context, const mt32emu_renderer_type renderer_type) {
context->synth->selectRendererType(static_cast<RendererType>(renderer_type));
}
@@ -432,11 +474,16 @@ mt32emu_return_code mt32emu_open_synth(mt32emu_const_context context) {
if (!context->synth->open(*context->controlROMImage, *context->pcmROMImage, context->partialCount, context->analogOutputMode)) {
return MT32EMU_RC_FAILED;
}
SamplerateConversionState &srcState = *context->srcState;
const double outputSampleRate = (0.0 < srcState.outputSampleRate) ? srcState.outputSampleRate : context->synth->getStereoOutputSampleRate();
srcState.src = new SampleRateConverter(*context->synth, outputSampleRate, srcState.srcQuality);
return MT32EMU_RC_OK;
}
void mt32emu_close_synth(mt32emu_const_context context) {
context->synth->close();
delete context->srcState->src;
context->srcState->src = NULL;
}
mt32emu_boolean mt32emu_is_open(mt32emu_const_context context) {
@@ -444,7 +491,24 @@ mt32emu_boolean mt32emu_is_open(mt32emu_const_context context) {
}
mt32emu_bit32u mt32emu_get_actual_stereo_output_samplerate(mt32emu_const_context context) {
return context->synth->getStereoOutputSampleRate();
if (context->srcState->src == NULL) {
return context->synth->getStereoOutputSampleRate();
}
return mt32emu_bit32u(0.5 + context->srcState->src->convertSynthToOutputTimestamp(SAMPLE_RATE));
}
mt32emu_bit32u mt32emu_convert_output_to_synth_timestamp(mt32emu_const_context context, mt32emu_bit32u output_timestamp) {
if (context->srcState->src == NULL) {
return output_timestamp;
}
return mt32emu_bit32u(0.5 + context->srcState->src->convertOutputToSynthTimestamp(output_timestamp));
}
mt32emu_bit32u mt32emu_convert_synth_to_output_timestamp(mt32emu_const_context context, mt32emu_bit32u synth_timestamp) {
if (context->srcState->src == NULL) {
return synth_timestamp;
}
return mt32emu_bit32u(0.5 + context->srcState->src->convertSynthToOutputTimestamp(synth_timestamp));
}
void mt32emu_flush_midi_queue(mt32emu_const_context context) {
@@ -460,6 +524,10 @@ void mt32emu_set_midi_receiver(mt32emu_context context, mt32emu_midi_receiver_i
context->midiParser = (midi_receiver.v0 != NULL) ? new DelegatingMidiStreamParser(context, midi_receiver, instance_data) : new DefaultMidiStreamParser(*context->synth);
}
mt32emu_bit32u mt32emu_get_internal_rendered_sample_count(mt32emu_const_context context) {
return context->synth->getInternalRenderedSampleCount();
}
void mt32emu_parse_stream(mt32emu_const_context context, const mt32emu_bit8u *stream, mt32emu_bit32u length) {
context->midiParser->resetTimestamp();
context->midiParser->parseStream(stream, length);
@@ -584,12 +652,28 @@ mt32emu_boolean mt32emu_is_reversed_stereo_enabled(mt32emu_const_context context
return context->synth->isReversedStereoEnabled() ? MT32EMU_BOOL_TRUE : MT32EMU_BOOL_FALSE;
}
void mt32emu_set_nice_amp_ramp_enabled(mt32emu_const_context context, const mt32emu_boolean enabled) {
context->synth->setNiceAmpRampEnabled(enabled != MT32EMU_BOOL_FALSE);
}
mt32emu_boolean mt32emu_is_nice_amp_ramp_enabled(mt32emu_const_context context) {
return context->synth->isNiceAmpRampEnabled() ? MT32EMU_BOOL_TRUE : MT32EMU_BOOL_FALSE;
}
void mt32emu_render_bit16s(mt32emu_const_context context, mt32emu_bit16s *stream, mt32emu_bit32u len) {
context->synth->render(stream, len);
if (context->srcState->src != NULL) {
context->srcState->src->getOutputSamples(stream, len);
} else {
context->synth->render(stream, len);
}
}
void mt32emu_render_float(mt32emu_const_context context, float *stream, mt32emu_bit32u len) {
context->synth->render(stream, len);
if (context->srcState->src != NULL) {
context->srcState->src->getOutputSamples(stream, len);
} else {
context->synth->render(stream, len);
}
}
void mt32emu_render_bit16s_streams(mt32emu_const_context context, const mt32emu_dac_output_bit16s_streams *streams, mt32emu_bit32u len) {

View File

@@ -209,6 +209,12 @@ MT32EMU_EXPORT mt32emu_bit32u mt32emu_set_midi_event_queue_size(mt32emu_const_co
*/
MT32EMU_EXPORT void mt32emu_set_midi_receiver(mt32emu_context context, mt32emu_midi_receiver_i midi_receiver, void *instance_data);
/**
* Returns current value of the global counter of samples rendered since the synth was created (at the native sample rate 32000 Hz).
* This method helps to compute accurate timestamp of a MIDI message to use with the methods below.
*/
MT32EMU_EXPORT mt32emu_bit32u mt32emu_get_internal_rendered_sample_count(mt32emu_const_context context);
/* Enqueues a MIDI event for subsequent playback.
* The MIDI event will be processed not before the specified timestamp.
* The timestamp is measured as the global rendered sample count since the synth was created (at the native sample rate 32000 Hz).
@@ -324,7 +330,6 @@ MT32EMU_EXPORT mt32emu_midi_delay_mode mt32emu_get_midi_delay_mode(mt32emu_const
* Sets output gain factor for synth output channels. Applied to all output samples and unrelated with the synth's Master volume,
* it rather corresponds to the gain of the output analog circuitry of the hardware units. However, together with mt32emu_set_reverb_output_gain()
* it offers to the user a capability to control the gain of reverb and non-reverb output channels independently.
* Ignored in MT32EMU_DAC_PURE mode.
*/
MT32EMU_EXPORT void mt32emu_set_output_gain(mt32emu_const_context context, float gain);
/** Returns current output gain factor for synth output channels. */
@@ -339,7 +344,6 @@ MT32EMU_EXPORT float mt32emu_get_output_gain(mt32emu_const_context context);
* corresponds to the level of digital capture. Although, according to the CM-64 PCB schematic,
* there is a difference in the reverb analogue circuit, and the resulting output gain is 0.68
* of that for LA32 analogue output. This factor is applied to the reverb output gain.
* Ignored in MT32EMU_DAC_PURE mode.
*/
MT32EMU_EXPORT void mt32emu_set_reverb_output_gain(mt32emu_const_context context, float gain);
/** Returns current output gain factor for reverb wet output channels. */
@@ -350,6 +354,18 @@ MT32EMU_EXPORT void mt32emu_set_reversed_stereo_enabled(mt32emu_const_context co
/** Returns whether left and right output channels are swapped. */
MT32EMU_EXPORT mt32emu_boolean mt32emu_is_reversed_stereo_enabled(mt32emu_const_context context);
/**
* Allows to toggle the NiceAmpRamp mode.
* In this mode, we want to ensure that amp ramp never jumps to the target
* value and always gradually increases or decreases. It seems that real units
* do not bother to always check if a newly started ramp leads to a jump.
* We also prefer the quality improvement over the emulation accuracy,
* so this mode is enabled by default.
*/
MT32EMU_EXPORT void mt32emu_set_nice_amp_ramp_enabled(mt32emu_const_context context, const mt32emu_boolean enabled);
/** Returns whether NiceAmpRamp mode is enabled. */
MT32EMU_EXPORT mt32emu_boolean mt32emu_is_nice_amp_ramp_enabled(mt32emu_const_context context);
/**
* Renders samples to the specified output stream as if they were sampled at the analog stereo output at the desired sample rate.
* If the output sample rate is not specified explicitly, the default output sample rate is used which depends on the current

View File

@@ -120,7 +120,8 @@ typedef enum {
typedef enum {
MT32EMU_SERVICE_VERSION_0 = 0,
MT32EMU_SERVICE_VERSION_1 = 1,
MT32EMU_SERVICE_VERSION_CURRENT = MT32EMU_SERVICE_VERSION_1
MT32EMU_SERVICE_VERSION_2 = 2,
MT32EMU_SERVICE_VERSION_CURRENT = MT32EMU_SERVICE_VERSION_2
} mt32emu_service_version;
/* === Report Handler Interface === */
@@ -297,6 +298,11 @@ typedef union mt32emu_service_i mt32emu_service_i;
mt32emu_bit32u (*convertOutputToSynthTimestamp)(mt32emu_const_context context, mt32emu_bit32u output_timestamp); \
mt32emu_bit32u (*convertSynthToOutputTimestamp)(mt32emu_const_context context, mt32emu_bit32u synth_timestamp);
#define MT32EMU_SERVICE_I_V2 \
mt32emu_bit32u (*getInternalRenderedSampleCount)(mt32emu_const_context context); \
void (*setNiceAmpRampEnabled)(mt32emu_const_context context, const mt32emu_boolean enabled); \
mt32emu_boolean (*isNiceAmpRampEnabled)(mt32emu_const_context context);
typedef struct {
MT32EMU_SERVICE_I_V0
} mt32emu_service_i_v0;
@@ -306,6 +312,12 @@ typedef struct {
MT32EMU_SERVICE_I_V1
} mt32emu_service_i_v1;
typedef struct {
MT32EMU_SERVICE_I_V0
MT32EMU_SERVICE_I_V1
MT32EMU_SERVICE_I_V2
} mt32emu_service_i_v2;
/**
* Extensible interface for all the library services.
* Union intended to view an interface of any subsequent version as any parent interface not requiring a cast.
@@ -314,9 +326,11 @@ typedef struct {
union mt32emu_service_i {
const mt32emu_service_i_v0 *v0;
const mt32emu_service_i_v1 *v1;
const mt32emu_service_i_v2 *v2;
};
#undef MT32EMU_SERVICE_I_V0
#undef MT32EMU_SERVICE_I_V1
#undef MT32EMU_SERVICE_I_V2
#endif /* #ifndef MT32EMU_C_TYPES_H */

View File

@@ -61,6 +61,7 @@ mt32emu_service_i mt32emu_get_service_i();
#define mt32emu_flush_midi_queue i.v0->flushMIDIQueue
#define mt32emu_set_midi_event_queue_size i.v0->setMIDIEventQueueSize
#define mt32emu_set_midi_receiver i.v0->setMIDIReceiver
#define mt32emu_get_internal_rendered_sample_count iV2()->getInternalRenderedSampleCount
#define mt32emu_parse_stream i.v0->parseStream
#define mt32emu_parse_stream_at i.v0->parseStream_At
#define mt32emu_play_short_message i.v0->playShortMessage
@@ -90,6 +91,8 @@ mt32emu_service_i mt32emu_get_service_i();
#define mt32emu_get_reverb_output_gain i.v0->getReverbOutputGain
#define mt32emu_set_reversed_stereo_enabled i.v0->setReversedStereoEnabled
#define mt32emu_is_reversed_stereo_enabled i.v0->isReversedStereoEnabled
#define mt32emu_set_nice_amp_ramp_enabled iV2()->setNiceAmpRampEnabled
#define mt32emu_is_nice_amp_ramp_enabled iV2()->isNiceAmpRampEnabled
#define mt32emu_render_bit16s i.v0->renderBit16s
#define mt32emu_render_float i.v0->renderFloat
#define mt32emu_render_bit16s_streams i.v0->renderBit16sStreams
@@ -213,6 +216,7 @@ public:
void setMIDIReceiver(mt32emu_midi_receiver_i midi_receiver, void *instance_data) { mt32emu_set_midi_receiver(c, midi_receiver, instance_data); }
void setMIDIReceiver(IMidiReceiver &midi_receiver) { setMIDIReceiver(CppInterfaceImpl::getMidiReceiverThunk(), &midi_receiver); }
Bit32u getInternalRenderedSampleCount() { return mt32emu_get_internal_rendered_sample_count(c); }
void parseStream(const Bit8u *stream, Bit32u length) { mt32emu_parse_stream(c, stream, length); }
void parseStream_At(const Bit8u *stream, Bit32u length, Bit32u timestamp) { mt32emu_parse_stream_at(c, stream, length, timestamp); }
void playShortMessage(Bit32u message) { mt32emu_play_short_message(c, message); }
@@ -249,6 +253,9 @@ public:
void setReversedStereoEnabled(const bool enabled) { mt32emu_set_reversed_stereo_enabled(c, enabled ? MT32EMU_BOOL_TRUE : MT32EMU_BOOL_FALSE); }
bool isReversedStereoEnabled() { return mt32emu_is_reversed_stereo_enabled(c) != MT32EMU_BOOL_FALSE; }
void setNiceAmpRampEnabled(const bool enabled) { mt32emu_set_nice_amp_ramp_enabled(c, enabled ? MT32EMU_BOOL_TRUE : MT32EMU_BOOL_FALSE); }
bool isNiceAmpRampEnabled() { return mt32emu_is_nice_amp_ramp_enabled(c) != MT32EMU_BOOL_FALSE; }
void renderBit16s(Bit16s *stream, Bit32u len) { mt32emu_render_bit16s(c, stream, len); }
void renderFloat(float *stream, Bit32u len) { mt32emu_render_float(c, stream, len); }
void renderBit16sStreams(const mt32emu_dac_output_bit16s_streams *streams, Bit32u len) { mt32emu_render_bit16s_streams(c, streams, len); }
@@ -271,6 +278,7 @@ private:
#if MT32EMU_API_TYPE == 2
const mt32emu_service_i_v1 *iV1() { return (getVersionID() < MT32EMU_SERVICE_VERSION_1) ? NULL : i.v1; }
const mt32emu_service_i_v2 *iV2() { return (getVersionID() < MT32EMU_SERVICE_VERSION_2) ? NULL : i.v2; }
#endif
};
@@ -421,6 +429,7 @@ static mt32emu_midi_receiver_i getMidiReceiverThunk() {
#undef mt32emu_flush_midi_queue
#undef mt32emu_set_midi_event_queue_size
#undef mt32emu_set_midi_receiver
#undef mt32emu_get_internal_rendered_sample_count
#undef mt32emu_parse_stream
#undef mt32emu_parse_stream_at
#undef mt32emu_play_short_message
@@ -450,6 +459,8 @@ static mt32emu_midi_receiver_i getMidiReceiverThunk() {
#undef mt32emu_get_reverb_output_gain
#undef mt32emu_set_reversed_stereo_enabled
#undef mt32emu_is_reversed_stereo_enabled
#undef mt32emu_set_nice_amp_ramp_enabled
#undef mt32emu_is_nice_amp_ramp_enabled
#undef mt32emu_render_bit16s
#undef mt32emu_render_float
#undef mt32emu_render_bit16s_streams

View File

@@ -18,9 +18,9 @@
#ifndef MT32EMU_CONFIG_H
#define MT32EMU_CONFIG_H
#define MT32EMU_VERSION "2.1.0"
#define MT32EMU_VERSION "2.2.0"
#define MT32EMU_VERSION_MAJOR 2
#define MT32EMU_VERSION_MINOR 1
#define MT32EMU_VERSION_MINOR 2
#define MT32EMU_VERSION_PATCH 0
/* Library Exports Configuration

View File

@@ -87,7 +87,7 @@
#define MT32EMU_MAX_STREAM_BUFFER_SIZE 32768
/* This should correspond to the MIDI buffer size used in real h/w devices.
* CM-32L control ROM seems using 1000 bytes, old MT-32 isn't confirmed by now.
* CM-32L control ROM is using 1000 bytes, and MT-32 GEN0 is using only 240 bytes (semi-confirmed by now).
*/
#define MT32EMU_SYSEX_BUFFER_SIZE 1000

View File

@@ -24,7 +24,7 @@ typedef float FloatSample;
/** Interface defines an abstract source of samples. It can either define a single channel stream or a stream with interleaved channels. */
class FloatSampleProvider {
public:
virtual ~FloatSampleProvider() {};
virtual ~FloatSampleProvider() {}
virtual void getOutputSamples(FloatSample *outBuffer, unsigned int size) = 0;
};

View File

@@ -24,7 +24,7 @@ namespace SRCTools {
/** Interface defines an abstract source of samples. It can either define a single channel stream or a stream with interleaved channels. */
class ResamplerStage {
public:
virtual ~ResamplerStage() {};
virtual ~ResamplerStage() {}
/** Returns a lower estimation of required number of input samples to produce the specified number of output samples. */
virtual unsigned int estimateInLength(const unsigned int outLength) const = 0;