MT32: Update the MT32 emulator to the latest munt revision

Previous munt revision was 189f607c88e7404ad99abcf4b90f23b103003ed1
(Feb 09, 2012).
Current munt revision is f969d2081d41b669c1bfebd0026b5419c09517ae
(Nov 15, 2012)
This commit is contained in:
Filippos Karapetis 2012-11-15 14:09:52 +02:00
parent 97854df1a8
commit 3233edf9b8
17 changed files with 140 additions and 71 deletions

View File

@ -18,7 +18,7 @@
#include "mt32emu.h"
#include "AReverbModel.h"
using namespace MT32Emu;
namespace MT32Emu {
// Default reverb settings for modes 0-2
@ -235,3 +235,5 @@ void AReverbModel::process(const float *inLeft, const float *inRight, float *out
outRight++;
}
}
}

View File

@ -20,7 +20,7 @@
#include "mt32emu.h"
#include "DelayReverb.h"
using namespace MT32Emu;
namespace MT32Emu {
// CONFIRMED: The values below are found via analysis of digital samples. Checked with all time and level combinations.
@ -148,3 +148,5 @@ bool DelayReverb::isActive() const {
}
return false;
}
}

View File

@ -20,7 +20,7 @@
#include "freeverb.h"
using namespace MT32Emu;
namespace MT32Emu {
FreeverbModel::FreeverbModel(float useScaleTuning, float useFiltVal, float useWet, Bit8u useRoom, float useDamp) {
freeverb = NULL;
@ -76,3 +76,5 @@ bool FreeverbModel::isActive() const {
// FIXME: Not bothering to do this properly since we'll be replacing Freeverb soon...
return false;
}
}

View File

@ -79,11 +79,12 @@ LA32Ramp::LA32Ramp() :
void LA32Ramp::startRamp(Bit8u target, Bit8u increment) {
// CONFIRMED: From sample analysis, this appears to be very accurate.
// FIXME: We could use a table for this in future
if (increment == 0) {
largeIncrement = 0;
} else {
largeIncrement = (unsigned int)(EXP2F(((increment & 0x7F) + 24) / 8.0f) + 0.125f);
// Using integer argument here, no precision loss:
// (unsigned int)(EXP2F(((increment & 0x7F) + 24) / 8.0f) + 0.125f)
largeIncrement = (unsigned int)(EXP2I(((increment & 0x7F) + 24) << 9) + 0.125f);
}
descending = (increment & 0x80) != 0;
if (descending) {

View File

@ -544,9 +544,12 @@ void Part::allNotesOff() {
// should treat the hold pedal as usual.
for (Common::List<Poly *>::iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) {
Poly *poly = *polyIt;
// FIXME: This has special handling of key 0 in NoteOff that Mok has not yet confirmed
// applies to AllNotesOff.
poly->noteOff(holdpedal);
// 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.
if (poly->canSustain()) {
poly->noteOff(holdpedal);
}
}
}

View File

@ -22,7 +22,7 @@
#include "mt32emu.h"
#include "mmath.h"
using namespace MT32Emu;
namespace MT32Emu {
#ifdef INACCURATE_SMOOTH_PAN
// Mok wanted an option for smoother panning, and we love Mok.
@ -133,6 +133,25 @@ void Partial::startPartial(const Part *part, Poly *usePoly, const PatchCache *us
stereoVolume.leftVol = panVal / 7.0f;
stereoVolume.rightVol = 1.0f - stereoVolume.leftVol;
// SEMI-CONFIRMED: From sample analysis:
// Found that timbres with 3 or 4 partials (i.e. one using two partial pairs) are mixed in two different ways.
// Either partial pairs are added or subtracted, it depends on how the partial pairs are allocated.
// It seems that partials are grouped into quarters and if the partial pairs are allocated in different quarters the subtraction happens.
// Though, this matters little for the majority of timbres, it becomes crucial for timbres which contain several partials that sound very close.
// In this case that timbre can sound totally different depending of the way it is mixed up.
// Most easily this effect can be displayed with the help of a special timbre consisting of several identical square wave partials (3 or 4).
// Say, it is 3-partial timbre. Just play any two notes simultaneously and the polys very probably are mixed differently.
// Moreover, the partial allocator retains the last partial assignment it did and all the subsequent notes will sound the same as the last released one.
// The situation is better with 4-partial timbres since then a whole quarter is assigned for each poly. However, if a 3-partial timbre broke the normal
// whole-quarter assignment or after some partials got aborted, even 4-partial timbres can be found sounding differently.
// This behaviour is also confirmed with two more special timbres: one with identical sawtooth partials, and one with PCM wave 02.
// For my personal taste, this behaviour rather enriches the sounding and should be emulated.
// Also, the current partial allocator model probably needs to be refined.
if (debugPartialNum & 8) {
stereoVolume.leftVol = -stereoVolume.leftVol;
stereoVolume.rightVol = -stereoVolume.rightVol;
}
if (patchCache->PCMPartial) {
pcmNum = patchCache->pcm;
if (synth->controlROMMap->pcmCount > 128) {
@ -149,7 +168,7 @@ void Partial::startPartial(const Part *part, Poly *usePoly, const PatchCache *us
}
// CONFIRMED: pulseWidthVal calculation is based on information from Mok
pulseWidthVal = (poly->getVelocity() - 64) * (patchCache->srcPartial.wg.pulseWidthVeloSensitivity - 7) + synth->tables.pulseWidth100To255[patchCache->srcPartial.wg.pulseWidth];
pulseWidthVal = (poly->getVelocity() - 64) * (patchCache->srcPartial.wg.pulseWidthVeloSensitivity - 7) + Tables::getInstance().pulseWidth100To255[patchCache->srcPartial.wg.pulseWidth];
if (pulseWidthVal < 0) {
pulseWidthVal = 0;
} else if (pulseWidthVal > 255) {
@ -175,6 +194,7 @@ float Partial::getPCMSample(unsigned int position) {
}
unsigned long Partial::generateSamples(float *partialBuf, unsigned long length) {
const Tables &tables = Tables::getInstance();
if (!isActive() || alreadyOutputed) {
return 0;
}
@ -197,6 +217,9 @@ unsigned long Partial::generateSamples(float *partialBuf, unsigned long length)
deactivate();
break;
}
Bit16u pitch = tvp->nextPitch();
// SEMI-CONFIRMED: From sample analysis:
// (1) Tested with a single partial playing PCM wave 77 with pitchCoarse 36 and no keyfollow, velocity follow, etc.
// This gives results within +/- 2 at the output (before any DAC bitshifting)
@ -206,10 +229,17 @@ unsigned long Partial::generateSamples(float *partialBuf, unsigned long length)
// positive amps, so negative still needs to be explored, as well as lower levels.
//
// Also still partially unconfirmed is the behaviour when ramping between levels, as well as the timing.
float amp = EXP2F((32772 - ampRampVal / 2048) / -2048.0f);
Bit16u pitch = tvp->nextPitch();
float freq = synth->tables.pitchToFreq[pitch];
#if MT32EMU_ACCURATE_WG == 1
float amp = EXP2F((32772 - ampRampVal / 2048) / -2048.0f);
float freq = EXP2F(pitch / 4096.0f - 16.0f) * 32000.0f;
#else
static const float ampFactor = EXP2F(32772 / -2048.0f);
float amp = EXP2I(ampRampVal >> 10) * ampFactor;
static const float freqFactor = EXP2F(-16.0f) * 32000.0f;
float freq = EXP2I(pitch) * freqFactor;
#endif
if (patchCache->PCMPartial) {
// Render PCM waveform
@ -225,8 +255,14 @@ unsigned long Partial::generateSamples(float *partialBuf, unsigned long length)
// Linear interpolation
float firstSample = synth->pcmROMData[pcmAddr + intPCMPosition];
float nextSample = getPCMSample(intPCMPosition + 1);
sample = firstSample + (nextSample - firstSample) * (pcmPosition - intPCMPosition);
// We observe that for partial structures with ring modulation the interpolation is not applied to the slave PCM partial.
// It's assumed that the multiplication circuitry intended to perform the interpolation on the slave PCM partial
// is borrowed by the ring modulation circuit (or the LA32 chip has a similar lack of resources assigned to each partial pair).
if (pair == NULL || mixType == 0 || structurePosition == 0) {
sample = firstSample + (getPCMSample(intPCMPosition + 1) - firstSample) * (pcmPosition - intPCMPosition);
} else {
sample = firstSample;
}
float newPCMPosition = pcmPosition + positionDelta;
if (pcmWave->loop) {
@ -247,8 +283,8 @@ unsigned long Partial::generateSamples(float *partialBuf, unsigned long length)
// res corresponds to a value set in an LA32 register
Bit8u res = patchCache->srcPartial.tvf.resonance + 1;
// EXP2F(1.0f - (32 - res) / 4.0f);
float resAmp = synth->tables.resAmpMax[res];
// Using tiny exact table for computation of EXP2F(1.0f - (32 - res) / 4.0f)
float resAmp = tables.resAmpMax[res];
// The cutoffModifier may not be supposed to be directly added to the cutoff -
// it may for example need to be multiplied in some way.
@ -268,7 +304,8 @@ unsigned long Partial::generateSamples(float *partialBuf, unsigned long length)
#if MT32EMU_ACCURATE_WG == 1
cosineLen *= EXP2F((cutoffVal - 128.0f) / -16.0f); // found from sample analysis
#else
cosineLen *= synth->tables.cutoffToCosineLen[Bit32u((cutoffVal - 128.0f) * 8.0f)];
static const float cosineLenFactor = EXP2F(128.0f / -16.0f);
cosineLen *= EXP2I(Bit32u((256.0f - cutoffVal) * 256.0f)) * cosineLenFactor;
#endif
}
@ -281,7 +318,7 @@ unsigned long Partial::generateSamples(float *partialBuf, unsigned long length)
float pulseLen = 0.5f;
if (pulseWidthVal > 128) {
pulseLen += synth->tables.pulseLenFactor[pulseWidthVal - 128];
pulseLen += tables.pulseLenFactor[pulseWidthVal - 128];
}
pulseLen *= waveLen;
@ -303,7 +340,7 @@ unsigned long Partial::generateSamples(float *partialBuf, unsigned long length)
#if MT32EMU_ACCURATE_WG == 1
resAmp *= sinf(FLOAT_PI * (cutoffVal - 128.0f) / 32.0f);
#else
resAmp *= synth->tables.sinf10[Bit32u(64 * (cutoffVal - 128.0f))];
resAmp *= tables.sinf10[Bit32u(64 * (cutoffVal - 128.0f))];
#endif
}
@ -314,7 +351,7 @@ unsigned long Partial::generateSamples(float *partialBuf, unsigned long length)
#if MT32EMU_ACCURATE_WG == 1
sample = -cosf(FLOAT_PI * relWavePos / cosineLen);
#else
sample = -synth->tables.sinf10[Bit32u(2048.0f * relWavePos / cosineLen) + 1024];
sample = -tables.sinf10[Bit32u(2048.0f * relWavePos / cosineLen) + 1024];
#endif
} else
@ -328,7 +365,7 @@ unsigned long Partial::generateSamples(float *partialBuf, unsigned long length)
#if MT32EMU_ACCURATE_WG == 1
sample = cosf(FLOAT_PI * (relWavePos - (cosineLen + hLen)) / cosineLen);
#else
sample = synth->tables.sinf10[Bit32u(2048.0f * (relWavePos - (cosineLen + hLen)) / cosineLen) + 1024];
sample = tables.sinf10[Bit32u(2048.0f * (relWavePos - (cosineLen + hLen)) / cosineLen) + 1024];
#endif
} else {
@ -343,7 +380,8 @@ unsigned long Partial::generateSamples(float *partialBuf, unsigned long length)
#if MT32EMU_ACCURATE_WG == 1
sample *= EXP2F(-0.125f * (128.0f - cutoffVal));
#else
sample *= synth->tables.cutoffToFilterAmp[Bit32u(cutoffVal * 8.0f)];
static const float cutoffAttenuationFactor = EXP2F(-0.125f * 128.0f);
sample *= EXP2I(Bit32u(512.0f * cutoffVal)) * cutoffAttenuationFactor;
#endif
} else {
@ -363,11 +401,17 @@ unsigned long Partial::generateSamples(float *partialBuf, unsigned long length)
#if MT32EMU_ACCURATE_WG == 1
resSample *= sinf(FLOAT_PI * relWavePos / cosineLen);
#else
resSample *= synth->tables.sinf10[Bit32u(2048.0f * relWavePos / cosineLen) & 4095];
resSample *= tables.sinf10[Bit32u(2048.0f * relWavePos / cosineLen) & 4095];
#endif
// Resonance sine amp
float resAmpFade = EXP2F(-synth->tables.resAmpFadeFactor[res >> 2] * (relWavePos / cosineLen)); // seems to be exact
float resAmpFadeLog2 = -tables.resAmpFadeFactor[res >> 2] * (relWavePos / cosineLen); // seems to be exact
#if MT32EMU_ACCURATE_WG == 1
float resAmpFade = EXP2F(resAmpFadeLog2);
#else
static const float resAmpFadeFactor = EXP2F(-30.0f);
float resAmpFade = (resAmpFadeLog2 < -30.0f) ? 0.0f : EXP2I(Bit32u((30.0f + resAmpFadeLog2) * 4096.0f)) * resAmpFadeFactor;
#endif
// Now relWavePos set negative to the left from center of any cosine
relWavePos = wavePos;
@ -388,7 +432,7 @@ unsigned long Partial::generateSamples(float *partialBuf, unsigned long length)
#if MT32EMU_ACCURATE_WG == 1
resAmpFade *= 0.5f * (1.0f - cosf(FLOAT_PI * relWavePos / (0.5f * cosineLen)));
#else
resAmpFade *= 0.5f * (1.0f + synth->tables.sinf10[Bit32s(2048.0f * relWavePos / (0.5f * cosineLen)) + 3072]);
resAmpFade *= 0.5f * (1.0f + tables.sinf10[Bit32s(2048.0f * relWavePos / (0.5f * cosineLen)) + 3072]);
#endif
}
@ -400,7 +444,7 @@ unsigned long Partial::generateSamples(float *partialBuf, unsigned long length)
#if MT32EMU_ACCURATE_WG == 1
sample *= cosf(FLOAT_2PI * wavePos / waveLen);
#else
sample *= synth->tables.sinf10[(Bit32u(4096.0f * wavePos / waveLen) & 4095) + 1024];
sample *= tables.sinf10[(Bit32u(4096.0f * wavePos / waveLen) & 4095) + 1024];
#endif
}
@ -516,10 +560,9 @@ bool Partial::produceOutput(float *leftBuf, float *rightBuf, unsigned long lengt
}
}
if (numGenerated > pairNumGenerated) {
if (mixType == 1) {
mixBuffersRingMix(partialBuf + pairNumGenerated, NULL, numGenerated - pairNumGenerated);
} else {
mixBuffersRing(partialBuf + pairNumGenerated, NULL, numGenerated - pairNumGenerated);
if (mixType == 2) {
numGenerated = pairNumGenerated;
deactivate();
}
}
}
@ -555,3 +598,5 @@ void Partial::startDecayAll() {
tvp->startDecay();
tvf->startDecay();
}
}

View File

@ -20,7 +20,7 @@
#include "mt32emu.h"
#include "PartialManager.h"
using namespace MT32Emu;
namespace MT32Emu {
PartialManager::PartialManager(Synth *useSynth, Part **useParts) {
synth = useSynth;
@ -248,3 +248,5 @@ const Partial *PartialManager::getPartial(unsigned int partialNum) const {
}
return partialTable[partialNum];
}
}

View File

@ -58,6 +58,9 @@ bool Poly::noteOff(bool pedalHeld) {
return false;
}
if (pedalHeld) {
if (state == POLY_Held) {
return false;
}
state = POLY_Held;
} else {
startDecay();

View File

@ -423,7 +423,6 @@ bool Synth::open(SynthProperties &useProp) {
#if MT32EMU_MONITOR_INIT
printDebug("Initialising Constant Tables");
#endif
tables.init();
#if !MT32EMU_REDUCE_REVERB_MEMORY
for (int i = 0; i < 4; i++) {
reverbModels[i]->open(useProp.sampleRate);
@ -627,6 +626,11 @@ void Synth::playMsg(Bit32u msg) {
return;
}
playMsgOnPart(part, code, note, velocity);
// This ensures minimum 1-sample delay between sequential MIDI events
// Without this, a sequence of NoteOn and immediately succeeding NoteOff messages is always silent
// Technically, it's also impossible to send events through the MIDI interface faster than about each millisecond
prerender();
}
void Synth::playMsgOnPart(unsigned char part, unsigned char code, unsigned char note, unsigned char velocity) {

View File

@ -321,7 +321,6 @@ private:
Bit32u renderedSampleCount;
Tables tables;
MemParams mt32ram, mt32default;

View File

@ -154,7 +154,7 @@ void TVA::reset(const Part *newPart, const TimbreParam::PartialParam *newPartial
playing = true;
Tables *tables = &partial->getSynth()->tables;
const Tables *tables = &Tables::getInstance();
int key = partial->getPoly()->getKey();
int velocity = partial->getPoly()->getVelocity();
@ -215,7 +215,7 @@ void TVA::recalcSustain() {
return;
}
// We're sustaining. Recalculate all the values
Tables *tables = &partial->getSynth()->tables;
const Tables *tables = &Tables::getInstance();
int newTarget = calcBasicAmp(tables, partial, system_, partialParam, patchTemp, rhythmTemp, biasAmpSubtraction, veloAmpSubtraction, part->getExpression());
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.
@ -241,7 +241,7 @@ int TVA::getPhase() const {
}
void TVA::nextPhase() {
Tables *tables = &partial->getSynth()->tables;
const Tables *tables = &Tables::getInstance();
if (phase >= TVA_PHASE_DEAD || !playing) {
partial->getSynth()->printDebug("TVA::nextPhase(): Shouldn't have got here with phase %d, playing=%s", phase, playing ? "true" : "false");

View File

@ -117,7 +117,7 @@ void TVF::reset(const TimbreParam::PartialParam *newPartialParam, unsigned int b
unsigned int key = partial->getPoly()->getKey();
unsigned int velocity = partial->getPoly()->getVelocity();
Tables *tables = &partial->getSynth()->tables;
const Tables *tables = &Tables::getInstance();
baseCutoff = calcBaseCutoff(newPartialParam, basePitch, key);
#if MT32EMU_MONITOR_TVF >= 1
@ -179,7 +179,7 @@ void TVF::startDecay() {
}
void TVF::nextPhase() {
Tables *tables = &partial->getSynth()->tables;
const Tables *tables = &Tables::getInstance();
int newPhase = phase + 1;
switch (newPhase) {

View File

@ -171,9 +171,14 @@ void TVP::updatePitch() {
if (newPitch < 0) {
newPitch = 0;
}
// Note: Temporary #ifdef until we have proper "quirk" configuration
// This is about right emulation of MT-32 GEN0 quirk exploited in Colonel's Bequest timbre "Lightning"
#ifndef MT32EMU_QUIRK_PITCH_ENVELOPE_OVERFLOW_MT32
if (newPitch > 59392) {
newPitch = 59392;
}
#endif
pitch = (Bit16u)newPitch;
// FIXME: We're doing this here because that's what the CM-32L does - we should probably move this somewhere more appropriate in future.

View File

@ -22,18 +22,14 @@
#include "mt32emu.h"
#include "mmath.h"
using namespace MT32Emu;
namespace MT32Emu {
Tables::Tables() {
initialised = false;
const Tables &Tables::getInstance() {
static const Tables instance;
return instance;
}
void Tables::init() {
if (initialised) {
return;
}
initialised = true;
Tables::Tables() {
int lf;
for (lf = 0; lf <= 100; lf++) {
// CONFIRMED:KG: This matches a ROM table found by Mok
@ -83,19 +79,9 @@ void Tables::init() {
pulseLenFactor[i] = (1.241857812f - pt) * pt; // seems to be 2 ^ (5 / 16) = 1.241857812f
}
for (int i = 0; i < 65536; i++) {
// Aka (slightly slower): EXP2F(pitchVal / 4096.0f - 16.0f) * 32000.0f
pitchToFreq[i] = EXP2F(i / 4096.0f - 1.034215715f);
}
// found from sample analysis
for (int i = 0; i < 1024; i++) {
cutoffToCosineLen[i] = EXP2F(i / -128.0f);
}
// found from sample analysis
for (int i = 0; i < 1024; i++) {
cutoffToFilterAmp[i] = EXP2F(-0.125f * (128.0f - i / 8.0f));
// The LA32 chip presumably has such a table inside as the internal computaions seem to be performed using fixed point math with 12-bit fractions
for (int i = 0; i < 4096; i++) {
exp2[i] = EXP2F(i / 4096.0f);
}
// found from sample analysis
@ -117,3 +103,5 @@ void Tables::init() {
sinf10[i] = sin(FLOAT_PI * i / 2048.0f);
}
}
}

View File

@ -20,14 +20,21 @@
namespace MT32Emu {
// Sample rate to use in mixing
const unsigned int SAMPLE_RATE = 32000;
const int MIDDLEC = 60;
class Synth;
class Tables {
bool initialised;
private:
Tables();
Tables(Tables &);
public:
static const Tables &getInstance();
// Constant LUTs
// CONFIRMED: This is used to convert several parameters to amp-modifying values in the TVA envelope:
@ -47,16 +54,11 @@ public:
// CONFIRMED:
Bit8u pulseWidth100To255[101];
float exp2[4096];
float pulseLenFactor[128];
float pitchToFreq[65536];
float cutoffToCosineLen[1024];
float cutoffToFilterAmp[1024];
float resAmpMax[32];
float resAmpFadeFactor[8];
float sinf10[5120];
Tables();
void init();
};
}

View File

@ -52,6 +52,10 @@ static inline float EXP2F(float x) {
#endif
}
static inline float EXP2I(unsigned int i) {
return float(1 << (i >> 12)) * Tables::getInstance().exp2[i & 0x0FFF];
}
static inline float EXP10F(float x) {
return exp(FLOAT_LN_10 * x);
}

View File

@ -59,9 +59,16 @@
#define MT32EMU_MONITOR_TVA 0
#define MT32EMU_MONITOR_TVF 0
// 0: Use LUTs to speedup WG
// 1: Use precise float math
// The WG algorithm involves dozens of transcendent maths, e.g. exponents and trigonometry.
// Unfortunately, the majority of systems perform such computations inefficiently,
// standard math libs and FPUs make no optimisations for single precision floats,
// and use no LUTs to speedup computing internal taylor series. Though, there're rare exceptions,
// and there's a hope it will become common soon.
// So, this is the crucial point of speed optimisations. We have now eliminated all the transcendent maths
// within the critical path and use LUTs instead.
// Besides, since the LA32 chip is assumed to use similar LUTs inside, the overall emulation accuracy should be better.
// 0: Use LUTs to speedup WG. Most common setting. You can expect about 50% performance boost.
// 1: Use precise float math. Use this setting to achieve more accurate wave generator. If your system performs better with this setting, it is really notable. :)
#define MT32EMU_ACCURATE_WG 0
#define MT32EMU_USE_EXTINT 0