aboutsummaryrefslogtreecommitdiff
path: root/audio/softsynth/mt32
diff options
context:
space:
mode:
Diffstat (limited to 'audio/softsynth/mt32')
-rw-r--r--audio/softsynth/mt32/AReverbModel.cpp264
-rw-r--r--audio/softsynth/mt32/AReverbModel.h59
-rw-r--r--audio/softsynth/mt32/BReverbModel.cpp393
-rw-r--r--audio/softsynth/mt32/BReverbModel.h112
-rw-r--r--audio/softsynth/mt32/DelayReverb.cpp86
-rw-r--r--audio/softsynth/mt32/DelayReverb.h7
-rw-r--r--audio/softsynth/mt32/FreeverbModel.cpp8
-rw-r--r--audio/softsynth/mt32/FreeverbModel.h2
-rw-r--r--audio/softsynth/mt32/LA32Ramp.cpp5
-rw-r--r--audio/softsynth/mt32/Part.cpp160
-rw-r--r--audio/softsynth/mt32/Part.h22
-rw-r--r--audio/softsynth/mt32/Partial.cpp113
-rw-r--r--audio/softsynth/mt32/PartialManager.cpp4
-rw-r--r--audio/softsynth/mt32/Poly.cpp12
-rw-r--r--audio/softsynth/mt32/Poly.h5
-rw-r--r--audio/softsynth/mt32/ROMInfo.cpp111
-rw-r--r--audio/softsynth/mt32/ROMInfo.h77
-rw-r--r--audio/softsynth/mt32/Synth.cpp243
-rw-r--r--audio/softsynth/mt32/Synth.h130
-rw-r--r--audio/softsynth/mt32/TVA.cpp16
-rw-r--r--audio/softsynth/mt32/TVF.cpp4
-rw-r--r--audio/softsynth/mt32/TVP.cpp10
-rw-r--r--audio/softsynth/mt32/Tables.cpp44
-rw-r--r--audio/softsynth/mt32/Tables.h20
-rw-r--r--audio/softsynth/mt32/mmath.h4
-rw-r--r--audio/softsynth/mt32/module.mk2
-rw-r--r--audio/softsynth/mt32/mt32emu.h23
27 files changed, 1408 insertions, 528 deletions
diff --git a/audio/softsynth/mt32/AReverbModel.cpp b/audio/softsynth/mt32/AReverbModel.cpp
index 4ee6c87943..595b286a31 100644
--- a/audio/softsynth/mt32/AReverbModel.cpp
+++ b/audio/softsynth/mt32/AReverbModel.cpp
@@ -1,5 +1,5 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011, 2012 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
@@ -16,64 +16,97 @@
*/
#include "mt32emu.h"
-#include "AReverbModel.h"
-
-using namespace MT32Emu;
-
-// Default reverb settings for modes 0-2
-
-static const unsigned int NUM_ALLPASSES = 6;
-static const unsigned int NUM_DELAYS = 5;
-static const Bit32u MODE_0_ALLPASSES[] = {729, 78, 394, 994, 1250, 1889};
-static const Bit32u MODE_0_DELAYS[] = {846, 4, 1819, 778, 346};
-static const float MODE_0_TIMES[] = {0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.9f};
-static const float MODE_0_LEVELS[] = {0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 1.01575f};
+#if MT32EMU_USE_REVERBMODEL == 1
-static const Bit32u MODE_1_ALLPASSES[] = {176, 809, 1324, 1258};
-static const Bit32u MODE_1_DELAYS[] = {2262, 124, 974, 2516, 356};
-static const float MODE_1_TIMES[] = {0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.95f};
-static const float MODE_1_LEVELS[] = {0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 1.01575f};
-
-static const Bit32u MODE_2_ALLPASSES[] = {78, 729, 994, 389};
-static const Bit32u MODE_2_DELAYS[] = {846, 4, 1819, 778, 346};
-static const float MODE_2_TIMES[] = {0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f};
-static const float MODE_2_LEVELS[] = {0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f};
-
-const AReverbSettings AReverbModel::REVERB_MODE_0_SETTINGS = {MODE_0_ALLPASSES, MODE_0_DELAYS, MODE_0_TIMES, MODE_0_LEVELS, 0.687770909f, 0.5f, 0.5f};
-const AReverbSettings AReverbModel::REVERB_MODE_1_SETTINGS = {MODE_1_ALLPASSES, MODE_1_DELAYS, MODE_1_TIMES, MODE_1_LEVELS, 0.712025098f, 0.375f, 0.625f};
-const AReverbSettings AReverbModel::REVERB_MODE_2_SETTINGS = {MODE_2_ALLPASSES, MODE_2_DELAYS, MODE_2_TIMES, MODE_2_LEVELS, 0.939522749f, 0.0f, 0.0f};
+#include "AReverbModel.h"
-RingBuffer::RingBuffer(Bit32u newsize) {
- index = 0;
- size = newsize;
+// Analysing of state of reverb RAM address lines gives exact sizes of the buffers of filters used. This also indicates that
+// the reverb model implemented in the real devices consists of three series allpass filters preceded by a non-feedback comb (or a delay with a LPF)
+// and followed by three parallel comb filters
+
+namespace MT32Emu {
+
+// Because LA-32 chip makes it's output available to process by the Boss chip with a significant delay,
+// the Boss chip puts to the buffer the LA32 dry output when it is ready and performs processing of the _previously_ latched data.
+// Of course, the right way would be to use a dedicated variable for this, but our reverb model is way higher level,
+// so we can simply increase the input buffer size.
+static const Bit32u PROCESS_DELAY = 1;
+
+// Default reverb settings for modes 0-2. These correspond to CM-32L / LAPC-I "new" reverb settings. MT-32 reverb is a bit different.
+// Found by tracing reverb RAM data lines (thanks go to Lord_Nightmare & balrog).
+
+static const Bit32u NUM_ALLPASSES = 3;
+static const Bit32u NUM_COMBS = 4; // Well, actually there are 3 comb filters, but the entrance LPF + delay can be perfectly processed via a comb here.
+
+static const Bit32u MODE_0_ALLPASSES[] = {994, 729, 78};
+static const Bit32u MODE_0_COMBS[] = {705 + PROCESS_DELAY, 2349, 2839, 3632};
+static const Bit32u MODE_0_OUTL[] = {2349, 141, 1960};
+static const Bit32u MODE_0_OUTR[] = {1174, 1570, 145};
+static const Bit32u MODE_0_COMB_FACTOR[] = {0x3C, 0x60, 0x60, 0x60};
+static const Bit32u MODE_0_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98,
+ 0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98,
+ 0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98};
+static const Bit32u MODE_0_LEVELS[] = {10*1, 10*3, 10*5, 10*7, 11*9, 11*12, 11*15, 13*15};
+static const Bit32u MODE_0_LPF_AMP = 6;
+
+static const Bit32u MODE_1_ALLPASSES[] = {1324, 809, 176};
+static const Bit32u MODE_1_COMBS[] = {961 + PROCESS_DELAY, 2619, 3545, 4519};
+static const Bit32u MODE_1_OUTL[] = {2618, 1760, 4518};
+static const Bit32u MODE_1_OUTR[] = {1300, 3532, 2274};
+static const Bit32u MODE_1_COMB_FACTOR[] = {0x30, 0x60, 0x60, 0x60};
+static const Bit32u MODE_1_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x28, 0x48, 0x60, 0x70, 0x78, 0x80, 0x90, 0x98,
+ 0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98,
+ 0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98};
+static const Bit32u MODE_1_LEVELS[] = {10*1, 10*3, 11*5, 11*7, 11*9, 11*12, 11*15, 14*15};
+static const Bit32u MODE_1_LPF_AMP = 6;
+
+static const Bit32u MODE_2_ALLPASSES[] = {969, 644, 157};
+static const Bit32u MODE_2_COMBS[] = {116 + PROCESS_DELAY, 2259, 2839, 3539};
+static const Bit32u MODE_2_OUTL[] = {2259, 718, 1769};
+static const Bit32u MODE_2_OUTR[] = {1136, 2128, 1};
+static const Bit32u MODE_2_COMB_FACTOR[] = {0, 0x20, 0x20, 0x20};
+static const Bit32u MODE_2_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x30, 0x58, 0x78, 0x88, 0xA0, 0xB8, 0xC0, 0xD0,
+ 0x30, 0x58, 0x78, 0x88, 0xA0, 0xB8, 0xC0, 0xD0,
+ 0x30, 0x58, 0x78, 0x88, 0xA0, 0xB8, 0xC0, 0xD0};
+static const Bit32u MODE_2_LEVELS[] = {10*1, 10*3, 11*5, 11*7, 11*9, 11*12, 12*15, 14*15};
+static const Bit32u MODE_2_LPF_AMP = 8;
+
+static const AReverbSettings REVERB_MODE_0_SETTINGS = {MODE_0_ALLPASSES, MODE_0_COMBS, MODE_0_OUTL, MODE_0_OUTR, MODE_0_COMB_FACTOR, MODE_0_COMB_FEEDBACK, MODE_0_LEVELS, MODE_0_LPF_AMP};
+static const AReverbSettings REVERB_MODE_1_SETTINGS = {MODE_1_ALLPASSES, MODE_1_COMBS, MODE_1_OUTL, MODE_1_OUTR, MODE_1_COMB_FACTOR, MODE_1_COMB_FEEDBACK, MODE_1_LEVELS, MODE_1_LPF_AMP};
+static const AReverbSettings REVERB_MODE_2_SETTINGS = {MODE_2_ALLPASSES, MODE_2_COMBS, MODE_2_OUTL, MODE_2_OUTR, MODE_2_COMB_FACTOR, MODE_2_COMB_FEEDBACK, MODE_2_LEVELS, MODE_2_LPF_AMP};
+
+static const AReverbSettings * const REVERB_SETTINGS[] = {&REVERB_MODE_0_SETTINGS, &REVERB_MODE_1_SETTINGS, &REVERB_MODE_2_SETTINGS, &REVERB_MODE_0_SETTINGS};
+
+RingBuffer::RingBuffer(const Bit32u newsize) : size(newsize), index(0) {
buffer = new float[size];
}
RingBuffer::~RingBuffer() {
delete[] buffer;
buffer = NULL;
- size = 0;
}
float RingBuffer::next() {
- index++;
- if (index >= size) {
+ if (++index >= size) {
index = 0;
}
return buffer[index];
}
-bool RingBuffer::isEmpty() {
+bool RingBuffer::isEmpty() const {
if (buffer == NULL) return true;
float *buf = buffer;
- float total = 0;
+ float max = 0.001f;
for (Bit32u i = 0; i < size; i++) {
- total += (*buf < 0 ? -*buf : *buf);
+ if ((*buf < -max) || (*buf > max)) return false;
buf++;
}
- return ((total / size) < .0002 ? true : false);
+ return true;
}
void RingBuffer::mute() {
@@ -83,59 +116,66 @@ void RingBuffer::mute() {
}
}
-AllpassFilter::AllpassFilter(Bit32u useSize) : RingBuffer(useSize) {
-}
-
-Delay::Delay(Bit32u useSize) : RingBuffer(useSize) {
-}
+AllpassFilter::AllpassFilter(const Bit32u useSize) : RingBuffer(useSize) {}
-float AllpassFilter::process(float in) {
- // This model corresponds to the allpass filter implementation in the real CM-32L device
+float AllpassFilter::process(const float in) {
+ // This model corresponds to the allpass filter implementation of the real CM-32L device
// found from sample analysis
- float out;
-
- out = next();
+ const float bufferOut = next();
// store input - feedback / 2
- buffer[index] = in - 0.5f * out;
+ buffer[index] = in - 0.5f * bufferOut;
// return buffer output + feedforward / 2
- return out + 0.5f * buffer[index];
+ return bufferOut + 0.5f * buffer[index];
}
-float Delay::process(float in) {
- // Implements a very simple delay
+CombFilter::CombFilter(const Bit32u useSize) : RingBuffer(useSize) {}
- float out;
+void CombFilter::process(const float in) {
+ // This model corresponds to the comb filter implementation of the real CM-32L device
+ // found from sample analysis
+
+ // the previously stored value
+ float last = buffer[index];
- out = next();
+ // prepare input + feedback
+ float filterIn = in + next() * feedbackFactor;
- // store input
- buffer[index] = in;
+ // store input + feedback processed by a low-pass filter
+ buffer[index] = filterFactor * last - filterIn;
+}
- // return buffer output
- return out;
+float CombFilter::getOutputAt(const Bit32u outIndex) const {
+ return buffer[(size + index - outIndex) % size];
}
-AReverbModel::AReverbModel(const AReverbSettings *useSettings) : allpasses(NULL), delays(NULL), currentSettings(useSettings) {
+void CombFilter::setFeedbackFactor(const float useFeedbackFactor) {
+ feedbackFactor = useFeedbackFactor;
}
+void CombFilter::setFilterFactor(const float useFilterFactor) {
+ filterFactor = useFilterFactor;
+}
+
+AReverbModel::AReverbModel(const ReverbMode mode) : allpasses(NULL), combs(NULL), currentSettings(*REVERB_SETTINGS[mode]) {}
+
AReverbModel::~AReverbModel() {
close();
}
-void AReverbModel::open(unsigned int /*sampleRate*/) {
- // FIXME: filter sizes must be multiplied by sample rate to 32000Hz ratio
- // IIR filter values depend on sample rate as well
+void AReverbModel::open() {
allpasses = new AllpassFilter*[NUM_ALLPASSES];
for (Bit32u i = 0; i < NUM_ALLPASSES; i++) {
- allpasses[i] = new AllpassFilter(currentSettings->allpassSizes[i]);
+ allpasses[i] = new AllpassFilter(currentSettings.allpassSizes[i]);
}
- delays = new Delay*[NUM_DELAYS];
- for (Bit32u i = 0; i < NUM_DELAYS; i++) {
- delays[i] = new Delay(currentSettings->delaySizes[i]);
+ combs = new CombFilter*[NUM_COMBS];
+ for (Bit32u i = 0; i < NUM_COMBS; i++) {
+ combs[i] = new CombFilter(currentSettings.combSizes[i]);
+ combs[i]->setFilterFactor(currentSettings.filterFactor[i] / 256.0f);
}
+ lpfAmp = currentSettings.lpfAmp / 16.0f;
mute();
}
@@ -150,84 +190,80 @@ void AReverbModel::close() {
delete[] allpasses;
allpasses = NULL;
}
- if (delays != NULL) {
- for (Bit32u i = 0; i < NUM_DELAYS; i++) {
- if (delays[i] != NULL) {
- delete delays[i];
- delays[i] = NULL;
+ if (combs != NULL) {
+ for (Bit32u i = 0; i < NUM_COMBS; i++) {
+ if (combs[i] != NULL) {
+ delete combs[i];
+ combs[i] = NULL;
}
}
- delete[] delays;
- delays = NULL;
+ delete[] combs;
+ combs = NULL;
}
}
void AReverbModel::mute() {
+ if (allpasses == NULL || combs == NULL) return;
for (Bit32u i = 0; i < NUM_ALLPASSES; i++) {
allpasses[i]->mute();
}
- for (Bit32u i = 0; i < NUM_DELAYS; i++) {
- delays[i]->mute();
+ for (Bit32u i = 0; i < NUM_COMBS; i++) {
+ combs[i]->mute();
}
- filterhist1 = 0;
- filterhist2 = 0;
- combhist = 0;
}
void AReverbModel::setParameters(Bit8u time, Bit8u level) {
// FIXME: wetLevel definitely needs ramping when changed
// Although, most games don't set reverb level during MIDI playback
- decayTime = currentSettings->decayTimes[time];
- wetLevel = currentSettings->wetLevels[level];
+ if (combs == NULL) return;
+ level &= 7;
+ time &= 7;
+ for (Bit32u i = 0; i < NUM_COMBS; i++) {
+ combs[i]->setFeedbackFactor(currentSettings.decayTimes[(i << 3) + time] / 256.0f);
+ }
+ wetLevel = (level == 0 && time == 0) ? 0.0f : 0.5f * lpfAmp * currentSettings.wetLevels[level] / 256.0f;
}
bool AReverbModel::isActive() const {
- bool bActive = false;
for (Bit32u i = 0; i < NUM_ALLPASSES; i++) {
- bActive |= !allpasses[i]->isEmpty();
+ if (!allpasses[i]->isEmpty()) return true;
}
- for (Bit32u i = 0; i < NUM_DELAYS; i++) {
- bActive |= !delays[i]->isEmpty();
+ for (Bit32u i = 0; i < NUM_COMBS; i++) {
+ if (!combs[i]->isEmpty()) return true;
}
- return bActive;
+ return false;
}
void AReverbModel::process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples) {
-// Three series allpass filters followed by a delay, fourth allpass filter and another delay
- float dry, link, outL1, outL2, outR1, outR2;
+ float dry, link, outL1;
for (unsigned long i = 0; i < numSamples; i++) {
- dry = *inLeft + *inRight;
+ dry = wetLevel * (*inLeft + *inRight);
- // Implementation of 2-stage IIR single-pole low-pass filter
- // found at the entrance of reverb processing on real devices
- filterhist1 += (dry - filterhist1) * currentSettings->filtVal;
- filterhist2 += (filterhist1 - filterhist2) * currentSettings->filtVal;
+ // Get the last stored sample before processing in order not to loose it
+ link = combs[0]->getOutputAt(currentSettings.combSizes[0] - 1);
- link = allpasses[0]->process(-filterhist2);
- link = allpasses[1]->process(link);
+ combs[0]->process(-dry);
- // this implements a comb filter cross-linked with the fourth allpass filter
- link += combhist * decayTime;
+ link = allpasses[0]->process(link);
+ link = allpasses[1]->process(link);
link = allpasses[2]->process(link);
- link = delays[0]->process(link);
- outL1 = link;
- link = allpasses[3]->process(link);
- link = delays[1]->process(link);
- outR1 = link;
- link = allpasses[4]->process(link);
- link = delays[2]->process(link);
- outL2 = link;
- link = allpasses[5]->process(link);
- link = delays[3]->process(link);
- outR2 = link;
- link = delays[4]->process(link);
-
- // comb filter end point
- combhist = combhist * currentSettings->damp1 + link * currentSettings->damp2;
-
- *outLeft = (outL1 + outL2) * wetLevel;
- *outRight = (outR1 + outR2) * wetLevel;
+
+ // If the output position is equal to the comb size, get it now in order not to loose it
+ outL1 = 1.5f * combs[1]->getOutputAt(currentSettings.outLPositions[0] - 1);
+
+ combs[1]->process(link);
+ combs[2]->process(link);
+ combs[3]->process(link);
+
+ link = outL1 + 1.5f * combs[2]->getOutputAt(currentSettings.outLPositions[1]);
+ link += combs[3]->getOutputAt(currentSettings.outLPositions[2]);
+ *outLeft = link;
+
+ link = 1.5f * combs[1]->getOutputAt(currentSettings.outRPositions[0]);
+ link += 1.5f * combs[2]->getOutputAt(currentSettings.outRPositions[1]);
+ link += combs[3]->getOutputAt(currentSettings.outRPositions[2]);
+ *outRight = link;
inLeft++;
inRight++;
@@ -235,3 +271,7 @@ void AReverbModel::process(const float *inLeft, const float *inRight, float *out
outRight++;
}
}
+
+}
+
+#endif
diff --git a/audio/softsynth/mt32/AReverbModel.h b/audio/softsynth/mt32/AReverbModel.h
index 3fae08c34c..be1ca4916b 100644
--- a/audio/softsynth/mt32/AReverbModel.h
+++ b/audio/softsynth/mt32/AReverbModel.h
@@ -1,5 +1,5 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
- * Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
+ * Copyright (C) 2011, 2012 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,66 +21,67 @@
namespace MT32Emu {
struct AReverbSettings {
- const Bit32u *allpassSizes;
- const Bit32u *delaySizes;
- const float *decayTimes;
- const float *wetLevels;
- float filtVal;
- float damp1;
- float damp2;
+ const Bit32u * const allpassSizes;
+ const Bit32u * const combSizes;
+ const Bit32u * const outLPositions;
+ const Bit32u * const outRPositions;
+ const Bit32u * const filterFactor;
+ const Bit32u * const decayTimes;
+ const Bit32u * const wetLevels;
+ const Bit32u lpfAmp;
};
class RingBuffer {
protected:
float *buffer;
- Bit32u size;
+ const Bit32u size;
Bit32u index;
+
public:
- RingBuffer(Bit32u size);
+ RingBuffer(const Bit32u size);
virtual ~RingBuffer();
float next();
- bool isEmpty();
+ bool isEmpty() const;
void mute();
};
class AllpassFilter : public RingBuffer {
public:
- AllpassFilter(Bit32u size);
- float process(float in);
+ AllpassFilter(const Bit32u size);
+ float process(const float in);
};
-class Delay : public RingBuffer {
+class CombFilter : public RingBuffer {
+ float feedbackFactor;
+ float filterFactor;
+
public:
- Delay(Bit32u size);
- float process(float in);
+ CombFilter(const Bit32u size);
+ void process(const float in);
+ float getOutputAt(const Bit32u outIndex) const;
+ void setFeedbackFactor(const float useFeedbackFactor);
+ void setFilterFactor(const float useFilterFactor);
};
class AReverbModel : public ReverbModel {
AllpassFilter **allpasses;
- Delay **delays;
+ CombFilter **combs;
- const AReverbSettings *currentSettings;
- float decayTime;
+ const AReverbSettings &currentSettings;
+ float lpfAmp;
float wetLevel;
- float filterhist1, filterhist2;
- float combhist;
void mute();
+
public:
- AReverbModel(const AReverbSettings *newSettings);
+ AReverbModel(const ReverbMode mode);
~AReverbModel();
- void open(unsigned int sampleRate);
+ void open();
void close();
void setParameters(Bit8u time, Bit8u level);
void process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples);
bool isActive() const;
-
- static const AReverbSettings REVERB_MODE_0_SETTINGS;
- static const AReverbSettings REVERB_MODE_1_SETTINGS;
- static const AReverbSettings REVERB_MODE_2_SETTINGS;
};
-// Default reverb settings for modes 0-2
-
}
#endif
diff --git a/audio/softsynth/mt32/BReverbModel.cpp b/audio/softsynth/mt32/BReverbModel.cpp
new file mode 100644
index 0000000000..2570424187
--- /dev/null
+++ b/audio/softsynth/mt32/BReverbModel.cpp
@@ -0,0 +1,393 @@
+/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
+ * Copyright (C) 2011, 2012 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 "mt32emu.h"
+
+#if MT32EMU_USE_REVERBMODEL == 2
+
+#include "BReverbModel.h"
+
+// Analysing of state of reverb RAM address lines gives exact sizes of the buffers of filters used. This also indicates that
+// the reverb model implemented in the real devices consists of three series allpass filters preceded by a non-feedback comb (or a delay with a LPF)
+// and followed by three parallel comb filters
+
+namespace MT32Emu {
+
+// Because LA-32 chip makes it's output available to process by the Boss chip with a significant delay,
+// the Boss chip puts to the buffer the LA32 dry output when it is ready and performs processing of the _previously_ latched data.
+// Of course, the right way would be to use a dedicated variable for this, but our reverb model is way higher level,
+// so we can simply increase the input buffer size.
+static const Bit32u PROCESS_DELAY = 1;
+
+static const Bit32u MODE_3_ADDITIONAL_DELAY = 1;
+static const Bit32u MODE_3_FEEDBACK_DELAY = 1;
+
+// Default reverb settings for modes 0-2. These correspond to CM-32L / LAPC-I "new" reverb settings. MT-32 reverb is a bit different.
+// Found by tracing reverb RAM data lines (thanks go to Lord_Nightmare & balrog).
+
+static const Bit32u MODE_0_NUMBER_OF_ALLPASSES = 3;
+static const Bit32u MODE_0_ALLPASSES[] = {994, 729, 78};
+static const Bit32u MODE_0_NUMBER_OF_COMBS = 4; // Well, actually there are 3 comb filters, but the entrance LPF + delay can be processed via a hacked comb.
+static const Bit32u MODE_0_COMBS[] = {705 + PROCESS_DELAY, 2349, 2839, 3632};
+static const Bit32u MODE_0_OUTL[] = {2349, 141, 1960};
+static const Bit32u MODE_0_OUTR[] = {1174, 1570, 145};
+static const Bit32u MODE_0_COMB_FACTOR[] = {0xA0, 0x60, 0x60, 0x60};
+static const Bit32u MODE_0_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98,
+ 0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98,
+ 0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98};
+static const Bit32u MODE_0_DRY_AMP[] = {0xA0, 0xA0, 0xA0, 0xA0, 0xB0, 0xB0, 0xB0, 0xD0};
+static const Bit32u MODE_0_WET_AMP[] = {0x10, 0x30, 0x50, 0x70, 0x90, 0xC0, 0xF0, 0xF0};
+static const Bit32u MODE_0_LPF_AMP = 0x60;
+
+static const Bit32u MODE_1_NUMBER_OF_ALLPASSES = 3;
+static const Bit32u MODE_1_ALLPASSES[] = {1324, 809, 176};
+static const Bit32u MODE_1_NUMBER_OF_COMBS = 4; // Same as for mode 0 above
+static const Bit32u MODE_1_COMBS[] = {961 + PROCESS_DELAY, 2619, 3545, 4519};
+static const Bit32u MODE_1_OUTL[] = {2618, 1760, 4518};
+static const Bit32u MODE_1_OUTR[] = {1300, 3532, 2274};
+static const Bit32u MODE_1_COMB_FACTOR[] = {0x80, 0x60, 0x60, 0x60};
+static const Bit32u MODE_1_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x28, 0x48, 0x60, 0x70, 0x78, 0x80, 0x90, 0x98,
+ 0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98,
+ 0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98};
+static const Bit32u MODE_1_DRY_AMP[] = {0xA0, 0xA0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xE0};
+static const Bit32u MODE_1_WET_AMP[] = {0x10, 0x30, 0x50, 0x70, 0x90, 0xC0, 0xF0, 0xF0};
+static const Bit32u MODE_1_LPF_AMP = 0x60;
+
+static const Bit32u MODE_2_NUMBER_OF_ALLPASSES = 3;
+static const Bit32u MODE_2_ALLPASSES[] = {969, 644, 157};
+static const Bit32u MODE_2_NUMBER_OF_COMBS = 4; // Same as for mode 0 above
+static const Bit32u MODE_2_COMBS[] = {116 + PROCESS_DELAY, 2259, 2839, 3539};
+static const Bit32u MODE_2_OUTL[] = {2259, 718, 1769};
+static const Bit32u MODE_2_OUTR[] = {1136, 2128, 1};
+static const Bit32u MODE_2_COMB_FACTOR[] = {0, 0x20, 0x20, 0x20};
+static const Bit32u MODE_2_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x30, 0x58, 0x78, 0x88, 0xA0, 0xB8, 0xC0, 0xD0,
+ 0x30, 0x58, 0x78, 0x88, 0xA0, 0xB8, 0xC0, 0xD0,
+ 0x30, 0x58, 0x78, 0x88, 0xA0, 0xB8, 0xC0, 0xD0};
+static const Bit32u MODE_2_DRY_AMP[] = {0xA0, 0xA0, 0xB0, 0xB0, 0xB0, 0xB0, 0xC0, 0xE0};
+static const Bit32u MODE_2_WET_AMP[] = {0x10, 0x30, 0x50, 0x70, 0x90, 0xC0, 0xF0, 0xF0};
+static const Bit32u MODE_2_LPF_AMP = 0x80;
+
+static const Bit32u MODE_3_NUMBER_OF_ALLPASSES = 0;
+static const Bit32u MODE_3_NUMBER_OF_COMBS = 1;
+static const Bit32u MODE_3_DELAY[] = {16000 + MODE_3_FEEDBACK_DELAY + PROCESS_DELAY + MODE_3_ADDITIONAL_DELAY};
+static const Bit32u MODE_3_OUTL[] = {400, 624, 960, 1488, 2256, 3472, 5280, 8000};
+static const Bit32u MODE_3_OUTR[] = {800, 1248, 1920, 2976, 4512, 6944, 10560, 16000};
+static const Bit32u MODE_3_COMB_FACTOR[] = {0x68};
+static const Bit32u MODE_3_COMB_FEEDBACK[] = {0x68, 0x60};
+static const Bit32u MODE_3_DRY_AMP[] = {0x20, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50};
+static const Bit32u MODE_3_WET_AMP[] = {0x18, 0x18, 0x28, 0x40, 0x60, 0x80, 0xA8, 0xF8};
+
+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, 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};
+
+// This algorithm tries to emulate exactly Boss multiplication operation (at least this is what we see on reverb RAM data lines).
+// Also LA32 is suspected to use the similar one to perform PCM interpolation and ring modulation.
+static Bit32s weirdMul(Bit32s a, Bit8u addMask, Bit8u carryMask) {
+ Bit8u mask = 0x80;
+ Bit32s res = 0;
+ for (int i = 0; i < 8; i++) {
+ Bit32s carry = (a < 0) && (mask & carryMask) > 0 ? a & 1 : 0;
+ a >>= 1;
+ res += (mask & addMask) > 0 ? a + carry : 0;
+ mask >>= 1;
+ }
+ return res;
+}
+
+RingBuffer::RingBuffer(Bit32u newsize) : size(newsize), index(0) {
+ buffer = new Bit16s[size];
+}
+
+RingBuffer::~RingBuffer() {
+ delete[] buffer;
+ buffer = NULL;
+}
+
+Bit32s RingBuffer::next() {
+ if (++index >= size) {
+ index = 0;
+ }
+ return buffer[index];
+}
+
+bool RingBuffer::isEmpty() const {
+ if (buffer == NULL) return true;
+
+ Bit16s *buf = buffer;
+ for (Bit32u i = 0; i < size; i++) {
+ if (*buf < -8 || *buf > 8) return false;
+ buf++;
+ }
+ return true;
+}
+
+void RingBuffer::mute() {
+ Bit16s *buf = buffer;
+ for (Bit32u i = 0; i < size; i++) {
+ *buf++ = 0;
+ }
+}
+
+AllpassFilter::AllpassFilter(const Bit32u useSize) : RingBuffer(useSize) {}
+
+Bit32s AllpassFilter::process(const Bit32s in) {
+ // This model corresponds to the allpass filter implementation of the real CM-32L device
+ // found from sample analysis
+
+ Bit16s bufferOut = next();
+
+ // store input - feedback / 2
+ buffer[index] = in - (bufferOut >> 1);
+
+ // return buffer output + feedforward / 2
+ return bufferOut + (buffer[index] >> 1);
+}
+
+CombFilter::CombFilter(const Bit32u useSize, const Bit32u useFilterFactor) : RingBuffer(useSize), filterFactor(useFilterFactor) {}
+
+void CombFilter::process(const Bit32s in) {
+ // This model corresponds to the comb filter implementation of the real CM-32L device
+
+ // the previously stored value
+ Bit32s last = buffer[index];
+
+ // prepare input + feedback
+ Bit32s filterIn = in + weirdMul(next(), feedbackFactor, 0xF0 /* Maybe 0x80 ? */);
+
+ // store input + feedback processed by a low-pass filter
+ buffer[index] = weirdMul(last, filterFactor, 0x40) - filterIn;
+}
+
+Bit32s CombFilter::getOutputAt(const Bit32u outIndex) const {
+ return buffer[(size + index - outIndex) % size];
+}
+
+void CombFilter::setFeedbackFactor(const Bit32u useFeedbackFactor) {
+ feedbackFactor = useFeedbackFactor;
+}
+
+DelayWithLowPassFilter::DelayWithLowPassFilter(const Bit32u useSize, const Bit32u useFilterFactor, const Bit32u useAmp)
+ : CombFilter(useSize, useFilterFactor), amp(useAmp) {}
+
+void DelayWithLowPassFilter::process(const Bit32s in) {
+ // the previously stored value
+ Bit32s last = buffer[index];
+
+ // move to the next index
+ next();
+
+ // low-pass filter process
+ Bit32s lpfOut = weirdMul(last, filterFactor, 0xFF) + in;
+
+ // store lpfOut multiplied by LPF amp factor
+ buffer[index] = weirdMul(lpfOut, amp, 0xFF);
+}
+
+TapDelayCombFilter::TapDelayCombFilter(const Bit32u useSize, const Bit32u useFilterFactor) : CombFilter(useSize, useFilterFactor) {}
+
+void TapDelayCombFilter::process(const Bit32s in) {
+ // the previously stored value
+ Bit32s last = buffer[index];
+
+ // move to the next index
+ next();
+
+ // prepare input + feedback
+ // Actually, the size of the filter varies with the TIME parameter, the feedback sample is taken from the position just below the right output
+ Bit32s filterIn = in + weirdMul(getOutputAt(outR + MODE_3_FEEDBACK_DELAY), feedbackFactor, 0xF0);
+
+ // store input + feedback processed by a low-pass filter
+ buffer[index] = weirdMul(last, filterFactor, 0xF0) - filterIn;
+}
+
+Bit32s TapDelayCombFilter::getLeftOutput() const {
+ return getOutputAt(outL + PROCESS_DELAY + MODE_3_ADDITIONAL_DELAY);
+}
+
+Bit32s TapDelayCombFilter::getRightOutput() const {
+ return getOutputAt(outR + PROCESS_DELAY + MODE_3_ADDITIONAL_DELAY);
+}
+
+void TapDelayCombFilter::setOutputPositions(const Bit32u useOutL, const Bit32u useOutR) {
+ outL = useOutL;
+ outR = useOutR;
+}
+
+BReverbModel::BReverbModel(const ReverbMode mode)
+ : allpasses(NULL), combs(NULL), currentSettings(*REVERB_SETTINGS[mode]), tapDelayMode(mode == REVERB_MODE_TAP_DELAY) {}
+
+BReverbModel::~BReverbModel() {
+ close();
+}
+
+void BReverbModel::open() {
+ if (currentSettings.numberOfAllpasses > 0) {
+ allpasses = new AllpassFilter*[currentSettings.numberOfAllpasses];
+ for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) {
+ allpasses[i] = new AllpassFilter(currentSettings.allpassSizes[i]);
+ }
+ }
+ combs = new CombFilter*[currentSettings.numberOfCombs];
+ if (tapDelayMode) {
+ *combs = new TapDelayCombFilter(*currentSettings.combSizes, *currentSettings.filterFactors);
+ } else {
+ combs[0] = new DelayWithLowPassFilter(currentSettings.combSizes[0], currentSettings.filterFactors[0], currentSettings.lpfAmp);
+ for (Bit32u i = 1; i < currentSettings.numberOfCombs; i++) {
+ combs[i] = new CombFilter(currentSettings.combSizes[i], currentSettings.filterFactors[i]);
+ }
+ }
+ mute();
+}
+
+void BReverbModel::close() {
+ if (allpasses != NULL) {
+ for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) {
+ if (allpasses[i] != NULL) {
+ delete allpasses[i];
+ allpasses[i] = NULL;
+ }
+ }
+ delete[] allpasses;
+ allpasses = NULL;
+ }
+ if (combs != NULL) {
+ for (Bit32u i = 0; i < currentSettings.numberOfCombs; i++) {
+ if (combs[i] != NULL) {
+ delete combs[i];
+ combs[i] = NULL;
+ }
+ }
+ delete[] combs;
+ combs = NULL;
+ }
+}
+
+void BReverbModel::mute() {
+ if (allpasses != NULL) {
+ for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) {
+ allpasses[i]->mute();
+ }
+ }
+ if (combs != NULL) {
+ for (Bit32u i = 0; i < currentSettings.numberOfCombs; i++) {
+ combs[i]->mute();
+ }
+ }
+}
+
+void BReverbModel::setParameters(Bit8u time, Bit8u level) {
+ if (combs == NULL) return;
+ level &= 7;
+ time &= 7;
+ if (tapDelayMode) {
+ TapDelayCombFilter *comb = static_cast<TapDelayCombFilter *> (*combs);
+ comb->setOutputPositions(currentSettings.outLPositions[time], currentSettings.outRPositions[time & 7]);
+ comb->setFeedbackFactor(currentSettings.feedbackFactors[((level < 3) || (time < 6)) ? 0 : 1]);
+ } else {
+ for (Bit32u i = 0; i < currentSettings.numberOfCombs; i++) {
+ combs[i]->setFeedbackFactor(currentSettings.feedbackFactors[(i << 3) + time]);
+ }
+ }
+ if (time == 0 && level == 0) {
+ dryAmp = wetLevel = 0;
+ } else {
+ dryAmp = currentSettings.dryAmps[level];
+ wetLevel = currentSettings.wetLevels[level];
+ }
+}
+
+bool BReverbModel::isActive() const {
+ for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) {
+ if (!allpasses[i]->isEmpty()) return true;
+ }
+ for (Bit32u i = 0; i < currentSettings.numberOfCombs; i++) {
+ if (!combs[i]->isEmpty()) return true;
+ }
+ return false;
+}
+
+void BReverbModel::process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples) {
+ Bit32s dry, link, outL1, outR1;
+
+ for (unsigned long i = 0; i < numSamples; i++) {
+ if (tapDelayMode) {
+ dry = Bit32s(*inLeft * 8192.0f) + Bit32s(*inRight * 8192.0f);
+ } else {
+ dry = Bit32s(*inLeft * 8192.0f) / 2 + Bit32s(*inRight * 8192.0f) / 2;
+ }
+
+ // Looks like dryAmp doesn't change in MT-32 but it does in CM-32L / LAPC-I
+ dry = weirdMul(dry, dryAmp, 0xFF);
+
+ if (tapDelayMode) {
+ TapDelayCombFilter *comb = static_cast<TapDelayCombFilter *> (*combs);
+ comb->process(dry);
+ *outLeft = weirdMul(comb->getLeftOutput(), wetLevel, 0xFF) / 8192.0f;
+ *outRight = weirdMul(comb->getRightOutput(), wetLevel, 0xFF) / 8192.0f;
+ } else {
+ // Get the last stored sample before processing in order not to loose it
+ link = combs[0]->getOutputAt(currentSettings.combSizes[0] - 1);
+
+ // Entrance LPF. Note, comb.process() differs a bit here.
+ combs[0]->process(dry);
+
+ // This introduces reverb noise which actually makes output from the real Boss chip nondeterministic
+ link = link - 1;
+ link = allpasses[0]->process(link);
+ link = allpasses[1]->process(link);
+ link = allpasses[2]->process(link);
+
+ // If the output position is equal to the comb size, get it now in order not to loose it
+ outL1 = combs[1]->getOutputAt(currentSettings.outLPositions[0] - 1);
+ outL1 += outL1 >> 1;
+
+ combs[1]->process(link);
+ combs[2]->process(link);
+ combs[3]->process(link);
+
+ link = combs[2]->getOutputAt(currentSettings.outLPositions[1]);
+ link += link >> 1;
+ link += outL1;
+ link += combs[3]->getOutputAt(currentSettings.outLPositions[2]);
+ *outLeft = weirdMul(link, wetLevel, 0xFF) / 8192.0f;
+
+ outR1 = combs[1]->getOutputAt(currentSettings.outRPositions[0]);
+ outR1 += outR1 >> 1;
+ link = combs[2]->getOutputAt(currentSettings.outRPositions[1]);
+ link += link >> 1;
+ link += outR1;
+ link += combs[3]->getOutputAt(currentSettings.outRPositions[2]);
+ *outRight = weirdMul(link, wetLevel, 0xFF) / 8192.0f;
+ }
+
+ inLeft++;
+ inRight++;
+ outLeft++;
+ outRight++;
+ }
+}
+
+}
+
+#endif
diff --git a/audio/softsynth/mt32/BReverbModel.h b/audio/softsynth/mt32/BReverbModel.h
new file mode 100644
index 0000000000..f728d1a99c
--- /dev/null
+++ b/audio/softsynth/mt32/BReverbModel.h
@@ -0,0 +1,112 @@
+/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
+ * Copyright (C) 2011, 2012 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_B_REVERB_MODEL_H
+#define MT32EMU_B_REVERB_MODEL_H
+
+namespace MT32Emu {
+
+struct BReverbSettings {
+ const Bit32u numberOfAllpasses;
+ const Bit32u * const allpassSizes;
+ const Bit32u numberOfCombs;
+ const Bit32u * const combSizes;
+ const Bit32u * const outLPositions;
+ const Bit32u * const outRPositions;
+ const Bit32u * const filterFactors;
+ const Bit32u * const feedbackFactors;
+ const Bit32u * const dryAmps;
+ const Bit32u * const wetLevels;
+ const Bit32u lpfAmp;
+};
+
+class RingBuffer {
+protected:
+ Bit16s *buffer;
+ const Bit32u size;
+ Bit32u index;
+
+public:
+ RingBuffer(const Bit32u size);
+ virtual ~RingBuffer();
+ Bit32s next();
+ bool isEmpty() const;
+ void mute();
+};
+
+class AllpassFilter : public RingBuffer {
+public:
+ AllpassFilter(const Bit32u size);
+ Bit32s process(const Bit32s in);
+};
+
+class CombFilter : public RingBuffer {
+protected:
+ const Bit32u filterFactor;
+ Bit32u feedbackFactor;
+
+public:
+ CombFilter(const Bit32u size, const Bit32u useFilterFactor);
+ virtual void process(const Bit32s in); // Actually, no need to make it virtual, but for sure
+ Bit32s getOutputAt(const Bit32u outIndex) const;
+ void setFeedbackFactor(const Bit32u useFeedbackFactor);
+};
+
+class DelayWithLowPassFilter : public CombFilter {
+ Bit32u amp;
+
+public:
+ DelayWithLowPassFilter(const Bit32u useSize, const Bit32u useFilterFactor, const Bit32u useAmp);
+ void process(const Bit32s in);
+ void setFeedbackFactor(const Bit32u) {}
+};
+
+class TapDelayCombFilter : public CombFilter {
+ Bit32u outL;
+ Bit32u outR;
+
+public:
+ TapDelayCombFilter(const Bit32u useSize, const Bit32u useFilterFactor);
+ void process(const Bit32s in);
+ Bit32s getLeftOutput() const;
+ Bit32s getRightOutput() const;
+ void setOutputPositions(const Bit32u useOutL, const Bit32u useOutR);
+};
+
+class BReverbModel : public ReverbModel {
+ AllpassFilter **allpasses;
+ CombFilter **combs;
+
+ const BReverbSettings &currentSettings;
+ const bool tapDelayMode;
+ Bit32u dryAmp;
+ Bit32u wetLevel;
+ void mute();
+
+public:
+ BReverbModel(const ReverbMode mode);
+ ~BReverbModel();
+ void open();
+ void close();
+ void setParameters(Bit8u time, Bit8u level);
+ void process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples);
+ bool isActive() const;
+};
+
+}
+
+#endif
diff --git a/audio/softsynth/mt32/DelayReverb.cpp b/audio/softsynth/mt32/DelayReverb.cpp
index 89eebf0d79..bf925f8419 100644
--- a/audio/softsynth/mt32/DelayReverb.cpp
+++ b/audio/softsynth/mt32/DelayReverb.cpp
@@ -20,15 +20,14 @@
#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.
+// CONFIRMED: The values below are found via analysis of digital samples and tracing reverb RAM address / data lines. Checked with all time and level combinations.
// Obviously:
// rightDelay = (leftDelay - 2) * 2 + 2
// echoDelay = rightDelay - 1
// Leaving these separate in case it's useful for work on other reverb modes...
-const Bit32u REVERB_TIMINGS[8][3]= {
+static const Bit32u REVERB_TIMINGS[8][3]= {
// {leftDelay, rightDelay, feedbackDelay}
{402, 802, 801},
{626, 1250, 1249},
@@ -40,14 +39,16 @@ const Bit32u REVERB_TIMINGS[8][3]= {
{8002, 16002, 16001}
};
-const float REVERB_FADE[8] = {0.0f, -0.049400051f, -0.08220577f, -0.131861118f, -0.197344907f, -0.262956344f, -0.345162114f, -0.509508615f};
-const float REVERB_FEEDBACK67 = -0.629960524947437f; // = -EXP2F(-2 / 3)
-const float REVERB_FEEDBACK = -0.682034520443118f; // = -EXP2F(-53 / 96)
-const float LPF_VALUE = 0.594603558f; // = EXP2F(-0.75f)
+// Reverb amp is found as dryAmp * wetAmp
+static const Bit32u REVERB_AMP[8] = {0x20*0x18, 0x50*0x18, 0x50*0x28, 0x50*0x40, 0x50*0x60, 0x50*0x80, 0x50*0xA8, 0x50*0xF8};
+static const Bit32u REVERB_FEEDBACK67 = 0x60;
+static const Bit32u REVERB_FEEDBACK = 0x68;
+static const float LPF_VALUE = 0x68 / 256.0f;
+
+static const Bit32u BUFFER_SIZE = 16384;
DelayReverb::DelayReverb() {
buf = NULL;
- sampleRate = 0;
setParameters(0, 0);
}
@@ -55,27 +56,22 @@ DelayReverb::~DelayReverb() {
delete[] buf;
}
-void DelayReverb::open(unsigned int newSampleRate) {
- if (newSampleRate != sampleRate || buf == NULL) {
- sampleRate = newSampleRate;
-
+void DelayReverb::open() {
+ if (buf == NULL) {
delete[] buf;
- // If we ever need a speedup, set bufSize to EXP2F(ceil(log2(bufSize))) and use & instead of % to find buf indexes
- bufSize = 16384 * sampleRate / 32000;
- buf = new float[bufSize];
+ buf = new float[BUFFER_SIZE];
recalcParameters();
// mute buffer
bufIx = 0;
if (buf != NULL) {
- for (unsigned int i = 0; i < bufSize; i++) {
+ for (unsigned int i = 0; i < BUFFER_SIZE; i++) {
buf[i] = 0.0f;
}
}
}
- // FIXME: IIR filter value depends on sample rate as well
}
void DelayReverb::close() {
@@ -92,59 +88,55 @@ void DelayReverb::setParameters(Bit8u newTime, Bit8u newLevel) {
void DelayReverb::recalcParameters() {
// Number of samples between impulse and eventual appearance on the left channel
- delayLeft = REVERB_TIMINGS[time][0] * sampleRate / 32000;
+ delayLeft = REVERB_TIMINGS[time][0];
// Number of samples between impulse and eventual appearance on the right channel
- delayRight = REVERB_TIMINGS[time][1] * sampleRate / 32000;
+ delayRight = REVERB_TIMINGS[time][1];
// Number of samples between a response and that response feeding back/echoing
- delayFeedback = REVERB_TIMINGS[time][2] * sampleRate / 32000;
+ delayFeedback = REVERB_TIMINGS[time][2];
- if (time < 6) {
- feedback = REVERB_FEEDBACK;
+ if (level < 3 || time < 6) {
+ feedback = REVERB_FEEDBACK / 256.0f;
} else {
- feedback = REVERB_FEEDBACK67;
+ feedback = REVERB_FEEDBACK67 / 256.0f;
}
- // Fading speed, i.e. amplitude ratio of neighbor responses
- fade = REVERB_FADE[level];
+ // Overall output amp
+ amp = (level == 0 && time == 0) ? 0.0f : REVERB_AMP[level] / 65536.0f;
}
void DelayReverb::process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples) {
- if (buf == NULL) {
- return;
- }
+ if (buf == NULL) return;
for (unsigned int sampleIx = 0; sampleIx < numSamples; sampleIx++) {
// The ring buffer write index moves backwards; reads are all done with positive offsets.
- Bit32u bufIxPrev = (bufIx + 1) % bufSize;
- Bit32u bufIxLeft = (bufIx + delayLeft) % bufSize;
- Bit32u bufIxRight = (bufIx + delayRight) % bufSize;
- Bit32u bufIxFeedback = (bufIx + delayFeedback) % bufSize;
+ Bit32u bufIxPrev = (bufIx + 1) % BUFFER_SIZE;
+ Bit32u bufIxLeft = (bufIx + delayLeft) % BUFFER_SIZE;
+ Bit32u bufIxRight = (bufIx + delayRight) % BUFFER_SIZE;
+ Bit32u bufIxFeedback = (bufIx + delayFeedback) % BUFFER_SIZE;
// Attenuated input samples and feedback response are directly added to the current ring buffer location
- float sample = fade * (inLeft[sampleIx] + inRight[sampleIx]) + feedback * buf[bufIxFeedback];
+ float lpfIn = amp * (inLeft[sampleIx] + inRight[sampleIx]) + feedback * buf[bufIxFeedback];
// Single-pole IIR filter found on real devices
- buf[bufIx] = buf[bufIxPrev] + (sample - buf[bufIxPrev]) * LPF_VALUE;
+ buf[bufIx] = buf[bufIxPrev] * LPF_VALUE - lpfIn;
outLeft[sampleIx] = buf[bufIxLeft];
outRight[sampleIx] = buf[bufIxRight];
- bufIx = (bufSize + bufIx - 1) % bufSize;
+ bufIx = (BUFFER_SIZE + bufIx - 1) % BUFFER_SIZE;
}
}
bool DelayReverb::isActive() const {
- // Quick hack: Return true iff all samples in the left buffer are the same and
- // all samples in the right buffers are the same (within the sample output threshold).
- if (buf == NULL) {
- return false;
- }
- float last = buf[0] * 8192.0f;
- for (unsigned int i = 1; i < bufSize; i++) {
- float s = (buf[i] * 8192.0f);
- if (fabs(s - last) > 1.0f) {
- return true;
- }
+ if (buf == NULL) return false;
+
+ float *b = buf;
+ float max = 0.001f;
+ for (Bit32u i = 0; i < BUFFER_SIZE; i++) {
+ if ((*b < -max) || (*b > max)) return true;
+ b++;
}
return false;
}
+
+}
diff --git a/audio/softsynth/mt32/DelayReverb.h b/audio/softsynth/mt32/DelayReverb.h
index 7c030fb839..1abb49f128 100644
--- a/audio/softsynth/mt32/DelayReverb.h
+++ b/audio/softsynth/mt32/DelayReverb.h
@@ -25,17 +25,14 @@ private:
Bit8u time;
Bit8u level;
- unsigned int sampleRate;
- Bit32u bufSize;
Bit32u bufIx;
-
float *buf;
Bit32u delayLeft;
Bit32u delayRight;
Bit32u delayFeedback;
- float fade;
+ float amp;
float feedback;
void recalcParameters();
@@ -43,7 +40,7 @@ private:
public:
DelayReverb();
~DelayReverb();
- void open(unsigned int sampleRate);
+ void open();
void close();
void setParameters(Bit8u time, Bit8u level);
void process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples);
diff --git a/audio/softsynth/mt32/FreeverbModel.cpp b/audio/softsynth/mt32/FreeverbModel.cpp
index c11fa859d8..2ed302fddc 100644
--- a/audio/softsynth/mt32/FreeverbModel.cpp
+++ b/audio/softsynth/mt32/FreeverbModel.cpp
@@ -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;
@@ -35,9 +35,7 @@ FreeverbModel::~FreeverbModel() {
delete freeverb;
}
-void FreeverbModel::open(unsigned int /*sampleRate*/) {
- // FIXME: scaleTuning must be multiplied by sample rate to 32000Hz ratio
- // IIR filter values depend on sample rate as well
+void FreeverbModel::open() {
if (freeverb == NULL) {
freeverb = new revmodel(scaleTuning);
}
@@ -76,3 +74,5 @@ bool FreeverbModel::isActive() const {
// FIXME: Not bothering to do this properly since we'll be replacing Freeverb soon...
return false;
}
+
+}
diff --git a/audio/softsynth/mt32/FreeverbModel.h b/audio/softsynth/mt32/FreeverbModel.h
index 925b2dbf96..bf1102b14a 100644
--- a/audio/softsynth/mt32/FreeverbModel.h
+++ b/audio/softsynth/mt32/FreeverbModel.h
@@ -32,7 +32,7 @@ class FreeverbModel : public ReverbModel {
public:
FreeverbModel(float useScaleTuning, float useFiltVal, float useWet, Bit8u useRoom, float useDamp);
~FreeverbModel();
- void open(unsigned int sampleRate);
+ void open();
void close();
void setParameters(Bit8u time, Bit8u level);
void process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples);
diff --git a/audio/softsynth/mt32/LA32Ramp.cpp b/audio/softsynth/mt32/LA32Ramp.cpp
index 9f1f01c3c2..4e4d6b4f30 100644
--- a/audio/softsynth/mt32/LA32Ramp.cpp
+++ b/audio/softsynth/mt32/LA32Ramp.cpp
@@ -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) {
diff --git a/audio/softsynth/mt32/Part.cpp b/audio/softsynth/mt32/Part.cpp
index 75912f38a8..cd385898e1 100644
--- a/audio/softsynth/mt32/Part.cpp
+++ b/audio/softsynth/mt32/Part.cpp
@@ -68,18 +68,16 @@ Part::Part(Synth *useSynth, unsigned int usePartNum) {
activePartialCount = 0;
memset(patchCache, 0, sizeof(patchCache));
for (int i = 0; i < MT32EMU_MAX_POLY; i++) {
- freePolys.push_front(new Poly(this));
+ freePolys.prepend(new Poly(this));
}
}
Part::~Part() {
- while (!activePolys.empty()) {
- delete activePolys.front();
- activePolys.pop_front();
+ while (!activePolys.isEmpty()) {
+ delete activePolys.takeFirst();
}
- while (!freePolys.empty()) {
- delete freePolys.front();
- freePolys.pop_front();
+ while (!freePolys.isEmpty()) {
+ delete freePolys.takeFirst();
}
}
@@ -209,6 +207,7 @@ void RhythmPart::setTimbre(TimbreParam * /*timbre*/) {
void Part::setTimbre(TimbreParam *timbre) {
*timbreTemp = *timbre;
+ synth->newTimbreSet(partNum, timbre->common.name);
}
unsigned int RhythmPart::getAbsTimbreNum() const {
@@ -245,8 +244,8 @@ 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 (Common::List<Poly *>::iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) {
- (*polyIt)->backupCacheToPartials(cache);
+ for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
+ poly->backupCacheToPartials(cache);
}
}
@@ -445,8 +444,7 @@ void Part::abortPoly(Poly *poly) {
}
bool Part::abortFirstPoly(unsigned int key) {
- for (Common::List<Poly *>::iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) {
- Poly *poly = *polyIt;
+ for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
if (poly->getKey() == key) {
abortPoly(poly);
return true;
@@ -456,8 +454,7 @@ bool Part::abortFirstPoly(unsigned int key) {
}
bool Part::abortFirstPoly(PolyState polyState) {
- for (Common::List<Poly *>::iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) {
- Poly *poly = *polyIt;
+ for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
if (poly->getState() == polyState) {
abortPoly(poly);
return true;
@@ -474,10 +471,10 @@ bool Part::abortFirstPolyPreferHeld() {
}
bool Part::abortFirstPoly() {
- if (activePolys.empty()) {
+ if (activePolys.isEmpty()) {
return false;
}
- abortPoly(activePolys.front());
+ abortPoly(activePolys.getFirst());
return true;
}
@@ -502,17 +499,16 @@ void Part::playPoly(const PatchCache cache[4], const MemParams::RhythmTemp *rhyt
return;
}
- if (freePolys.empty()) {
+ if (freePolys.isEmpty()) {
synth->printDebug("%s (%s): No free poly to play key %d (velocity %d)", name, currentInstr, midiKey, velocity);
return;
}
- Poly *poly = freePolys.front();
- freePolys.pop_front();
+ Poly *poly = freePolys.takeFirst();
if (patchTemp->patch.assignMode & 1) {
// Priority to data first received
- activePolys.push_front(poly);
+ activePolys.prepend(poly);
} else {
- activePolys.push_back(poly);
+ activePolys.append(poly);
}
Partial *partials[4];
@@ -537,16 +533,20 @@ void Part::playPoly(const PatchCache cache[4], const MemParams::RhythmTemp *rhyt
#if MT32EMU_MONITOR_PARTIALS > 1
synth->printPartialUsage();
#endif
+ synth->partStateChanged(partNum, true);
+ synth->polyStateChanged(partNum);
}
void Part::allNotesOff() {
// The MIDI specification states - and Mok confirms - that all notes off (0x7B)
// 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);
+ 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.
+ if (poly->canSustain()) {
+ poly->noteOff(holdpedal);
+ }
}
}
@@ -554,15 +554,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 (Common::List<Poly *>::iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) {
- Poly *poly = *polyIt;
+ for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
poly->startDecay();
}
}
void Part::stopPedalHold() {
- for (Common::List<Poly *>::iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) {
- Poly *poly = *polyIt;
+ for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
poly->stopPedalHold();
}
}
@@ -580,8 +578,7 @@ void Part::stopNote(unsigned int key) {
synth->printDebug("%s (%s): stopping key %d", name, currentInstr, key);
#endif
- for (Common::List<Poly *>::iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) {
- Poly *poly = *polyIt;
+ 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)) {
@@ -602,8 +599,7 @@ unsigned int Part::getActivePartialCount() const {
unsigned int Part::getActiveNonReleasingPartialCount() const {
unsigned int activeNonReleasingPartialCount = 0;
- for (Common::List<Poly *>::const_iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) {
- Poly *poly = *polyIt;
+ for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
if (poly->getState() != POLY_Releasing) {
activeNonReleasingPartialCount += poly->getActivePartialCount();
}
@@ -615,7 +611,103 @@ void Part::partialDeactivated(Poly *poly) {
activePartialCount--;
if (!poly->isActive()) {
activePolys.remove(poly);
- freePolys.push_front(poly);
+ freePolys.prepend(poly);
+ synth->polyStateChanged(partNum);
+ }
+ if (activePartialCount == 0) {
+ synth->partStateChanged(partNum, false);
+ }
+}
+
+//#define POLY_LIST_DEBUG
+
+PolyList::PolyList() : firstPoly(NULL), lastPoly(NULL) {}
+
+bool PolyList::isEmpty() const {
+#ifdef POLY_LIST_DEBUG
+ if ((firstPoly == NULL || lastPoly == NULL) && firstPoly != lastPoly) {
+ printf("PolyList: desynchronised firstPoly & lastPoly pointers\n");
+ }
+#endif
+ return firstPoly == NULL && lastPoly == NULL;
+}
+
+Poly *PolyList::getFirst() const {
+ return firstPoly;
+}
+
+Poly *PolyList::getLast() const {
+ return lastPoly;
+}
+
+void PolyList::prepend(Poly *poly) {
+#ifdef POLY_LIST_DEBUG
+ if (poly->getNext() != NULL) {
+ printf("PolyList: Non-NULL next field in a Poly being prepended is ignored\n");
+ }
+#endif
+ poly->setNext(firstPoly);
+ firstPoly = poly;
+ if (lastPoly == NULL) {
+ lastPoly = poly;
+ }
+}
+
+void PolyList::append(Poly *poly) {
+#ifdef POLY_LIST_DEBUG
+ if (poly->getNext() != NULL) {
+ printf("PolyList: Non-NULL next field in a Poly being appended is ignored\n");
+ }
+#endif
+ poly->setNext(NULL);
+ if (lastPoly != NULL) {
+#ifdef POLY_LIST_DEBUG
+ if (lastPoly->getNext() != NULL) {
+ printf("PolyList: Non-NULL next field in the lastPoly\n");
+ }
+#endif
+ lastPoly->setNext(poly);
+ }
+ lastPoly = poly;
+ if (firstPoly == NULL) {
+ firstPoly = poly;
+ }
+}
+
+Poly *PolyList::takeFirst() {
+ Poly *oldFirst = firstPoly;
+ firstPoly = oldFirst->getNext();
+ if (firstPoly == NULL) {
+#ifdef POLY_LIST_DEBUG
+ if (lastPoly != oldFirst) {
+ printf("PolyList: firstPoly != lastPoly in a list with a single Poly\n");
+ }
+#endif
+ lastPoly = NULL;
+ }
+ oldFirst->setNext(NULL);
+ return oldFirst;
+}
+
+void PolyList::remove(Poly * const polyToRemove) {
+ if (polyToRemove == firstPoly) {
+ takeFirst();
+ return;
+ }
+ for (Poly *poly = firstPoly; poly != NULL; poly = poly->getNext()) {
+ if (poly->getNext() == polyToRemove) {
+ if (polyToRemove == lastPoly) {
+#ifdef POLY_LIST_DEBUG
+ if (lastPoly->getNext() != NULL) {
+ printf("PolyList: Non-NULL next field in the lastPoly\n");
+ }
+#endif
+ lastPoly = poly;
+ }
+ poly->setNext(polyToRemove->getNext());
+ polyToRemove->setNext(NULL);
+ break;
+ }
}
}
diff --git a/audio/softsynth/mt32/Part.h b/audio/softsynth/mt32/Part.h
index 5c59c6d61f..e5be41ff10 100644
--- a/audio/softsynth/mt32/Part.h
+++ b/audio/softsynth/mt32/Part.h
@@ -18,13 +18,27 @@
#ifndef MT32EMU_PART_H
#define MT32EMU_PART_H
-#include <common/list.h>
-
namespace MT32Emu {
class PartialManager;
class Synth;
+class PolyList {
+private:
+ Poly *firstPoly;
+ Poly *lastPoly;
+
+public:
+ PolyList();
+ bool isEmpty() const;
+ Poly *getFirst() const;
+ Poly *getLast() const;
+ void prepend(Poly *poly);
+ void append(Poly *poly);
+ Poly *takeFirst();
+ void remove(Poly * const poly);
+};
+
class Part {
private:
// Direct pointer to sysex-addressable memory dedicated to this part (valid for parts 1-8, NULL for rhythm)
@@ -37,8 +51,8 @@ private:
unsigned int activePartialCount;
PatchCache patchCache[4];
- Common::List<Poly *> freePolys;
- Common::List<Poly *> activePolys;
+ PolyList freePolys;
+ PolyList activePolys;
void setPatch(const PatchParam *patch);
unsigned int midiKeyToKey(unsigned int midiKey);
diff --git a/audio/softsynth/mt32/Partial.cpp b/audio/softsynth/mt32/Partial.cpp
index 03bec560b8..a4d1ab03fa 100644
--- a/audio/softsynth/mt32/Partial.cpp
+++ b/audio/softsynth/mt32/Partial.cpp
@@ -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.
@@ -85,6 +85,7 @@ void Partial::deactivate() {
pair->pair = NULL;
}
}
+ synth->partialStateChanged(this, tva->getPhase(), TVA_PHASE_DEAD);
#if MT32EMU_MONITOR_PARTIALS > 2
synth->printDebug("[+%lu] [Partial %d] Deactivated", sampleNum, debugPartialNum);
synth->printPartialUsage(sampleNum);
@@ -133,6 +134,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 +169,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 +195,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 +218,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 +230,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.
+
+#if MT32EMU_ACCURATE_WG == 1
float amp = EXP2F((32772 - ampRampVal / 2048) / -2048.0f);
+ float freq = EXP2F(pitch / 4096.0f - 16.0f) * SAMPLE_RATE;
+#else
+ static const float ampFactor = EXP2F(32772 / -2048.0f);
+ float amp = EXP2I(ampRampVal >> 10) * ampFactor;
- Bit16u pitch = tvp->nextPitch();
- float freq = synth->tables.pitchToFreq[pitch];
+ static const float freqFactor = EXP2F(-16.0f) * SAMPLE_RATE;
+ float freq = EXP2I(pitch) * freqFactor;
+#endif
if (patchCache->PCMPartial) {
// Render PCM waveform
@@ -221,12 +252,18 @@ unsigned long Partial::generateSamples(float *partialBuf, unsigned long length)
break;
}
Bit32u pcmAddr = pcmWave->addr;
- float positionDelta = freq * 2048.0f / synth->myProp.sampleRate;
+ float positionDelta = freq * 2048.0f / SAMPLE_RATE;
// 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 +284,12 @@ 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];
+ float resAmp;
+ {
+ // resAmp = EXP2F(1.0f - (32 - res) / 4.0f);
+ static const float resAmpFactor = EXP2F(-7);
+ resAmp = EXP2I(res << 10) * resAmpFactor;
+ }
// The cutoffModifier may not be supposed to be directly added to the cutoff -
// it may for example need to be multiplied in some way.
@@ -260,7 +301,7 @@ unsigned long Partial::generateSamples(float *partialBuf, unsigned long length)
}
// Wave length in samples
- float waveLen = synth->myProp.sampleRate / freq;
+ float waveLen = SAMPLE_RATE / freq;
// Init cosineLen
float cosineLen = 0.5f * waveLen;
@@ -268,7 +309,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
}
@@ -279,23 +321,26 @@ unsigned long Partial::generateSamples(float *partialBuf, unsigned long length)
relWavePos -= waveLen;
}
+ // Ratio of positive segment to wave length
float pulseLen = 0.5f;
if (pulseWidthVal > 128) {
- pulseLen += synth->tables.pulseLenFactor[pulseWidthVal - 128];
+ // pulseLen = EXP2F((64 - pulseWidthVal) / 64);
+ static const float pulseLenFactor = EXP2F(-192 / 64);
+ pulseLen = EXP2I((256 - pulseWidthVal) << 6) * pulseLenFactor;
}
pulseLen *= waveLen;
- float lLen = pulseLen - cosineLen;
+ float hLen = pulseLen - cosineLen;
// Ignore pulsewidths too high for given freq
- if (lLen < 0.0f) {
- lLen = 0.0f;
+ if (hLen < 0.0f) {
+ hLen = 0.0f;
}
// Ignore pulsewidths too high for given freq and cutoff
- float hLen = waveLen - lLen - 2 * cosineLen;
- if (hLen < 0.0f) {
- hLen = 0.0f;
+ float lLen = waveLen - hLen - 2 * cosineLen;
+ if (lLen < 0.0f) {
+ lLen = 0.0f;
}
// Correct resAmp for cutoff in range 50..66
@@ -303,7 +348,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 +359,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 +373,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 +388,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 +409,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 +440,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 +452,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 +568,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 +606,5 @@ void Partial::startDecayAll() {
tvp->startDecay();
tvf->startDecay();
}
+
+}
diff --git a/audio/softsynth/mt32/PartialManager.cpp b/audio/softsynth/mt32/PartialManager.cpp
index 0a6be826d6..f8c2dbcd48 100644
--- a/audio/softsynth/mt32/PartialManager.cpp
+++ b/audio/softsynth/mt32/PartialManager.cpp
@@ -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];
}
+
+}
diff --git a/audio/softsynth/mt32/Poly.cpp b/audio/softsynth/mt32/Poly.cpp
index a2f00db73c..46e30c0f02 100644
--- a/audio/softsynth/mt32/Poly.cpp
+++ b/audio/softsynth/mt32/Poly.cpp
@@ -29,6 +29,7 @@ Poly::Poly(Part *usePart) {
partials[i] = NULL;
}
state = POLY_Inactive;
+ next = NULL;
}
void Poly::reset(unsigned int newKey, unsigned int newVelocity, bool newSustain, Partial **newPartials) {
@@ -58,6 +59,9 @@ bool Poly::noteOff(bool pedalHeld) {
return false;
}
if (pedalHeld) {
+ if (state == POLY_Held) {
+ return false;
+ }
state = POLY_Held;
} else {
startDecay();
@@ -171,4 +175,12 @@ void Poly::partialDeactivated(Partial *partial) {
part->partialDeactivated(this);
}
+Poly *Poly::getNext() {
+ return next;
+}
+
+void Poly::setNext(Poly *poly) {
+ next = poly;
+}
+
}
diff --git a/audio/softsynth/mt32/Poly.h b/audio/softsynth/mt32/Poly.h
index cd15a776f5..e25b6d8993 100644
--- a/audio/softsynth/mt32/Poly.h
+++ b/audio/softsynth/mt32/Poly.h
@@ -41,6 +41,8 @@ private:
Partial *partials[4];
+ Poly *next;
+
public:
Poly(Part *part);
void reset(unsigned int key, unsigned int velocity, bool sustain, Partial **partials);
@@ -60,6 +62,9 @@ public:
bool isActive() const;
void partialDeactivated(Partial *partial);
+
+ Poly *getNext();
+ void setNext(Poly *poly);
};
}
diff --git a/audio/softsynth/mt32/ROMInfo.cpp b/audio/softsynth/mt32/ROMInfo.cpp
new file mode 100644
index 0000000000..514bc23496
--- /dev/null
+++ b/audio/softsynth/mt32/ROMInfo.cpp
@@ -0,0 +1,111 @@
+/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
+ * Copyright (C) 2011 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 <cstring>
+#include "ROMInfo.h"
+
+namespace MT32Emu {
+
+// Known ROMs
+static const ROMInfo CTRL_MT32_V1_04 = {65536, "5a5cb5a77d7d55ee69657c2f870416daed52dea7", ROMInfo::Control, "ctrl_mt32_1_04", "MT-32 Control v1.04", ROMInfo::Full, NULL, NULL};
+static const ROMInfo CTRL_MT32_V1_05 = {65536, "e17a3a6d265bf1fa150312061134293d2b58288c", ROMInfo::Control, "ctrl_mt32_1_05", "MT-32 Control v1.05", ROMInfo::Full, NULL, NULL};
+static const ROMInfo CTRL_MT32_V1_06 = {65536, "a553481f4e2794c10cfe597fef154eef0d8257de", ROMInfo::Control, "ctrl_mt32_1_06", "MT-32 Control v1.06", ROMInfo::Full, NULL, NULL};
+static const ROMInfo CTRL_MT32_V1_07 = {65536, "b083518fffb7f66b03c23b7eb4f868e62dc5a987", ROMInfo::Control, "ctrl_mt32_1_07", "MT-32 Control v1.07", ROMInfo::Full, NULL, NULL};
+static const ROMInfo CTRL_MT32_BLUER = {65536, "7b8c2a5ddb42fd0732e2f22b3340dcf5360edf92", ROMInfo::Control, "ctrl_mt32_bluer", "MT-32 Control BlueRidge", ROMInfo::Full, NULL, 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, 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, NULL};
+
+static const ROMInfo PCM_MT32 = {524288, "f6b1eebc4b2d200ec6d3d21d51325d5b48c60252", ROMInfo::PCM, "pcm_mt32", "MT-32 PCM ROM", ROMInfo::Full, NULL, NULL};
+static const ROMInfo PCM_CM32L = {1048576, "289cc298ad532b702461bfc738009d9ebe8025ea", ROMInfo::PCM, "pcm_cm32l", "CM-32L/CM-64/LAPC-I PCM ROM", ROMInfo::Full, NULL, NULL};
+
+static const ROMInfo * const ROM_INFOS[] = {
+ &CTRL_MT32_V1_04,
+ &CTRL_MT32_V1_05,
+ &CTRL_MT32_V1_06,
+ &CTRL_MT32_V1_07,
+ &CTRL_MT32_BLUER,
+ &CTRL_CM32L_V1_00,
+ &CTRL_CM32L_V1_02,
+ &PCM_MT32,
+ &PCM_CM32L,
+ NULL};
+
+const ROMInfo* ROMInfo::getROMInfo(Common::File *file) {
+ size_t fileSize = file->size();
+ // We haven't added the SHA1 checksum code in ScummVM, as the file size
+ // suffices for our needs for now.
+ //const char *fileDigest = file->getSHA1();
+ for (int i = 0; ROM_INFOS[i] != NULL; i++) {
+ const ROMInfo *romInfo = ROM_INFOS[i];
+ if (fileSize == romInfo->fileSize /*&& !strcmp(fileDigest, romInfo->sha1Digest)*/) {
+ return romInfo;
+ }
+ }
+ return NULL;
+}
+
+void ROMInfo::freeROMInfo(const ROMInfo *romInfo) {
+ (void) romInfo;
+}
+
+static int getROMCount() {
+ int count;
+ for(count = 0; ROM_INFOS[count] != NULL; count++) {
+ }
+ return count;
+}
+
+const ROMInfo** ROMInfo::getROMInfoList(unsigned int types, unsigned int pairTypes) {
+ const ROMInfo **romInfoList = new const ROMInfo*[getROMCount() + 1];
+ const ROMInfo **currentROMInList = romInfoList;
+ for(int i = 0; ROM_INFOS[i] != NULL; i++) {
+ const ROMInfo *romInfo = ROM_INFOS[i];
+ if ((types & (1 << romInfo->type)) && (pairTypes & (1 << romInfo->pairType))) {
+ *currentROMInList++ = romInfo;
+ }
+ }
+ *currentROMInList = NULL;
+ return romInfoList;
+}
+
+void ROMInfo::freeROMInfoList(const ROMInfo **romInfoList) {
+ delete[] romInfoList;
+}
+
+const ROMImage* ROMImage::makeROMImage(Common::File *file) {
+ ROMImage *romImage = new ROMImage;
+ romImage->file = file;
+ romImage->romInfo = ROMInfo::getROMInfo(romImage->file);
+ return romImage;
+}
+
+void ROMImage::freeROMImage(const ROMImage *romImage) {
+ ROMInfo::freeROMInfo(romImage->romInfo);
+ delete romImage;
+}
+
+
+Common::File* ROMImage::getFile() const {
+ return file;
+}
+
+const ROMInfo* ROMImage::getROMInfo() const {
+ return romInfo;
+}
+
+}
diff --git a/audio/softsynth/mt32/ROMInfo.h b/audio/softsynth/mt32/ROMInfo.h
new file mode 100644
index 0000000000..2ffd2b72c6
--- /dev/null
+++ b/audio/softsynth/mt32/ROMInfo.h
@@ -0,0 +1,77 @@
+/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
+ * Copyright (C) 2011 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_ROMINFO_H
+#define MT32EMU_ROMINFO_H
+
+//#include <cstddef>
+#include "common/file.h"
+
+namespace MT32Emu {
+
+// Defines vital info about ROM file to be used by synth and applications
+
+struct ROMInfo {
+public:
+ size_t fileSize;
+ const char *sha1Digest;
+ enum Type {PCM, Control, Reverb} type;
+ const char *shortName;
+ const char *description;
+ enum PairType {Full, FirstHalf, SecondHalf, Mux0, Mux1} pairType;
+ ROMInfo *pairROMInfo;
+ void *controlROMInfo;
+
+ // Returns a ROMInfo struct by inspecting the size and the SHA1 hash
+ static const ROMInfo* getROMInfo(Common::File *file);
+
+ // Currently no-op
+ static void freeROMInfo(const ROMInfo *romInfo);
+
+ // Allows retrieving a NULL-terminated list of ROMInfos for a range of types and pairTypes
+ // (specified by bitmasks)
+ // Useful for GUI/console app to output information on what ROMs it supports
+ static const ROMInfo** getROMInfoList(unsigned int types, unsigned int pairTypes);
+
+ // Frees the list of ROMInfos given
+ static void freeROMInfoList(const ROMInfo **romInfos);
+};
+
+// Synth::open() is to require a full control ROMImage and a full PCM ROMImage to work
+
+class ROMImage {
+private:
+ Common::File *file;
+ const ROMInfo *romInfo;
+
+public:
+
+ // Creates a ROMImage object given a ROMInfo and a File. Keeps a reference
+ // to the File and ROMInfo given, which must be freed separately by the user
+ // after the ROMImage is freed
+ static const ROMImage* makeROMImage(Common::File *file);
+
+ // Must only be done after all Synths using the ROMImage are deleted
+ static void freeROMImage(const ROMImage *romImage);
+
+ Common::File *getFile() const;
+ const ROMInfo *getROMInfo() const;
+};
+
+}
+
+#endif
diff --git a/audio/softsynth/mt32/Synth.cpp b/audio/softsynth/mt32/Synth.cpp
index 7a1b5c2275..7f4ba44999 100644
--- a/audio/softsynth/mt32/Synth.cpp
+++ b/audio/softsynth/mt32/Synth.cpp
@@ -27,8 +27,10 @@
#include "mmath.h"
#include "PartialManager.h"
-#if MT32EMU_USE_AREVERBMODEL == 1
+#if MT32EMU_USE_REVERBMODEL == 1
#include "AReverbModel.h"
+#elif MT32EMU_USE_REVERBMODEL == 2
+#include "BReverbModel.h"
#else
#include "FreeverbModel.h"
#endif
@@ -36,7 +38,7 @@
namespace MT32Emu {
-const ControlROMMap ControlROMMaps[7] = {
+static const ControlROMMap ControlROMMaps[7] = {
// ID IDc IDbytes PCMmap PCMc tmbrA tmbrAO, tmbrAC tmbrB tmbrBO, tmbrBC tmbrR trC rhythm rhyC rsrv panpot prog rhyMax patMax sysMax timMax
{0x4014, 22, "\000 ver1.04 14 July 87 ", 0x3000, 128, 0x8000, 0x0000, false, 0xC000, 0x4000, false, 0x3200, 30, 0x73A6, 85, 0x57C7, 0x57E2, 0x57D0, 0x5252, 0x525E, 0x526E, 0x520A},
{0x4014, 22, "\000 ver1.05 06 Aug, 87 ", 0x3000, 128, 0x8000, 0x0000, false, 0xC000, 0x4000, false, 0x3200, 30, 0x7414, 85, 0x57C7, 0x57E2, 0x57D0, 0x5252, 0x525E, 0x526E, 0x520A},
@@ -140,22 +142,36 @@ Bit8u Synth::calcSysexChecksum(const Bit8u *data, Bit32u len, Bit8u checksum) {
return checksum;
}
-Synth::Synth() {
+Synth::Synth(ReportHandler *useReportHandler) {
isOpen = false;
reverbEnabled = true;
reverbOverridden = false;
-#if MT32EMU_USE_AREVERBMODEL == 1
- reverbModels[0] = new AReverbModel(&AReverbModel::REVERB_MODE_0_SETTINGS);
- reverbModels[1] = new AReverbModel(&AReverbModel::REVERB_MODE_1_SETTINGS);
- reverbModels[2] = new AReverbModel(&AReverbModel::REVERB_MODE_2_SETTINGS);
+ if (useReportHandler == NULL) {
+ reportHandler = new ReportHandler;
+ isDefaultReportHandler = true;
+ } else {
+ reportHandler = useReportHandler;
+ isDefaultReportHandler = false;
+ }
+
+#if MT32EMU_USE_REVERBMODEL == 1
+ reverbModels[REVERB_MODE_ROOM] = new AReverbModel(REVERB_MODE_ROOM);
+ reverbModels[REVERB_MODE_HALL] = new AReverbModel(REVERB_MODE_HALL);
+ reverbModels[REVERB_MODE_PLATE] = new AReverbModel(REVERB_MODE_PLATE);
+ reverbModels[REVERB_MODE_TAP_DELAY] = new DelayReverb();
+#elif MT32EMU_USE_REVERBMODEL == 2
+ reverbModels[REVERB_MODE_ROOM] = new BReverbModel(REVERB_MODE_ROOM);
+ reverbModels[REVERB_MODE_HALL] = new BReverbModel(REVERB_MODE_HALL);
+ reverbModels[REVERB_MODE_PLATE] = new BReverbModel(REVERB_MODE_PLATE);
+ reverbModels[REVERB_MODE_TAP_DELAY] = new BReverbModel(REVERB_MODE_TAP_DELAY);
#else
- reverbModels[0] = new FreeverbModel(0.76f, 0.687770909f, 0.63f, 0, 0.5f);
- reverbModels[1] = new FreeverbModel(2.0f, 0.712025098f, 0.86f, 1, 0.5f);
- reverbModels[2] = new FreeverbModel(0.4f, 0.939522749f, 0.38f, 2, 0.05f);
+ reverbModels[REVERB_MODE_ROOM] = new FreeverbModel(0.76f, 0.687770909f, 0.63f, 0, 0.5f);
+ reverbModels[REVERB_MODE_HALL] = new FreeverbModel(2.0f, 0.712025098f, 0.86f, 1, 0.5f);
+ reverbModels[REVERB_MODE_PLATE] = new FreeverbModel(0.4f, 0.939522749f, 0.38f, 2, 0.05f);
+ reverbModels[REVERB_MODE_TAP_DELAY] = new DelayReverb();
#endif
- reverbModels[3] = new DelayReverb();
reverbModel = NULL;
setDACInputMode(DACInputMode_NICE);
setOutputGain(1.0f);
@@ -170,31 +186,49 @@ Synth::~Synth() {
for (int i = 0; i < 4; i++) {
delete reverbModels[i];
}
+ if (isDefaultReportHandler) {
+ delete reportHandler;
+ }
+}
+
+void ReportHandler::showLCDMessage(const char *data) {
+ printf("WRITE-LCD: %s", data);
+ printf("\n");
+}
+
+void ReportHandler::printDebug(const char *fmt, va_list list) {
+ vprintf(fmt, list);
+ printf("\n");
}
-int Synth::report(ReportType type, const void *data) {
- if (myProp.report != NULL) {
- return myProp.report(myProp.userData, type, data);
+void Synth::partStateChanged(int partNum, bool isPartActive) {
+ reportHandler->onPartStateChanged(partNum, isPartActive);
+}
+
+void Synth::polyStateChanged(int partNum) {
+ reportHandler->onPolyStateChanged(partNum);
+}
+
+void Synth::partialStateChanged(const Partial * const partial, int oldPartialPhase, int newPartialPhase) {
+ for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) {
+ if (getPartial(i) == partial) {
+ reportHandler->onPartialStateChanged(i, oldPartialPhase, newPartialPhase);
+ break;
+ }
}
- return 0;
}
-unsigned int Synth::getSampleRate() const {
- return myProp.sampleRate;
+void Synth::newTimbreSet(int partNum, char patchName[]) {
+ reportHandler->onProgramChanged(partNum, patchName);
}
void Synth::printDebug(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
- if (myProp.printDebug != NULL) {
- myProp.printDebug(myProp.userData, fmt, ap);
- } else {
#if MT32EMU_DEBUG_SAMPLESTAMPS > 0
- printf("[%u] ", renderedSampleCount);
+ reportHandler->printDebug("[%u] ", renderedSampleCount);
#endif
- vprintf(fmt, ap);
- printf("\n");
- }
+ reportHandler->printDebug(fmt, ap);
va_end(ap);
}
@@ -244,80 +278,60 @@ void Synth::setReverbOutputGain(float newReverbOutputGain) {
reverbOutputGain = newReverbOutputGain;
}
-Common::File *Synth::openFile(const char *filename) {
- if (myProp.openFile != NULL) {
- return myProp.openFile(myProp.userData, filename);
- }
- char pathBuf[2048];
- if (myProp.baseDir != NULL) {
- strcpy(&pathBuf[0], myProp.baseDir);
- strcat(&pathBuf[0], filename);
- filename = pathBuf;
- }
- Common::File *file = new Common::File();
- if (!file->open(filename)) {
- delete file;
- return NULL;
- }
- return file;
-}
-
-void Synth::closeFile(Common::File *file) {
- if (myProp.closeFile != NULL) {
- myProp.closeFile(myProp.userData, file);
- } else {
- file->close();
- delete file;
- }
-}
-
-LoadResult Synth::loadControlROM(const char *filename) {
- Common::File *file = openFile(filename); // ROM File
- if (file == NULL) {
- return LoadResult_NotFound;
- }
- size_t fileSize = file->size();
- if (fileSize != CONTROL_ROM_SIZE) {
- printDebug("Control ROM file %s size mismatch: %i", filename, fileSize);
+bool Synth::loadControlROM(const ROMImage &controlROMImage) {
+ if (&controlROMImage == NULL) return false;
+ Common::File *file = controlROMImage.getFile();
+ const ROMInfo *controlROMInfo = controlROMImage.getROMInfo();
+ if ((controlROMInfo == NULL)
+ || (controlROMInfo->type != ROMInfo::Control)
+ || (controlROMInfo->pairType != ROMInfo::Full)) {
+ return false;
}
+#if MT32EMU_MONITOR_INIT
+ printDebug("Found Control ROM: %s, %s", controlROMInfo->shortName, controlROMInfo->description);
+#endif
file->read(controlROMData, CONTROL_ROM_SIZE);
- if (file->err()) {
- closeFile(file);
- return LoadResult_Unreadable;
- }
- closeFile(file);
// Control ROM successfully loaded, now check whether it's a known type
controlROMMap = NULL;
for (unsigned int i = 0; i < sizeof(ControlROMMaps) / sizeof(ControlROMMaps[0]); i++) {
if (memcmp(&controlROMData[ControlROMMaps[i].idPos], ControlROMMaps[i].idBytes, ControlROMMaps[i].idLen) == 0) {
controlROMMap = &ControlROMMaps[i];
- return LoadResult_OK;
+ return true;
}
}
- printDebug("%s does not match a known control ROM type", filename);
- return LoadResult_Invalid;
+#if MT32EMU_MONITOR_INIT
+ printDebug("Control ROM failed to load");
+#endif
+ return false;
}
-LoadResult Synth::loadPCMROM(const char *filename) {
- Common::File *file = openFile(filename); // ROM File
- if (file == NULL) {
- return LoadResult_NotFound;
+bool Synth::loadPCMROM(const ROMImage &pcmROMImage) {
+ if (&pcmROMImage == NULL) return false;
+ Common::File *file = pcmROMImage.getFile();
+ const ROMInfo *pcmROMInfo = pcmROMImage.getROMInfo();
+ if ((pcmROMInfo == NULL)
+ || (pcmROMInfo->type != ROMInfo::PCM)
+ || (pcmROMInfo->pairType != ROMInfo::Full)) {
+ return false;
}
+#if MT32EMU_MONITOR_INIT
+ printDebug("Found PCM ROM: %s, %s", pcmROMInfo->shortName, pcmROMInfo->description);
+#endif
size_t fileSize = file->size();
- if (fileSize < (size_t)(2 * pcmROMSize)) {
- printDebug("PCM ROM file is too short (expected %d, got %d)", 2 * pcmROMSize, fileSize);
- closeFile(file);
- return LoadResult_Invalid;
- }
- if (file->err()) {
- closeFile(file);
- return LoadResult_Unreadable;
+ if (fileSize != (2 * pcmROMSize)) {
+#if MT32EMU_MONITOR_INIT
+ printDebug("PCM ROM file has wrong size (expected %d, got %d)", 2 * pcmROMSize, fileSize);
+#endif
+ return false;
}
- LoadResult rc = LoadResult_OK;
- for (int i = 0; i < pcmROMSize; i++) {
- Bit8u s = file->readByte();
- Bit8u c = file->readByte();
+
+ byte *buffer = new byte[file->size()];
+ file->read(buffer, file->size());
+ const byte *fileData = buffer;
+ for (size_t i = 0; i < pcmROMSize; i++) {
+ Bit8u s = *(fileData++);
+ Bit8u c = *(fileData++);
int order[16] = {0, 9, 1, 2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15, 8};
@@ -343,16 +357,18 @@ LoadResult Synth::loadPCMROM(const char *filename) {
pcmROMData[i] = lin;
}
- closeFile(file);
- return rc;
+
+ delete[] buffer;
+
+ return true;
}
bool Synth::initPCMList(Bit16u mapAddress, Bit16u count) {
ControlROMPCMStruct *tps = (ControlROMPCMStruct *)&controlROMData[mapAddress];
for (int i = 0; i < count; i++) {
- int rAddr = tps[i].pos * 0x800;
- int rLenExp = (tps[i].len & 0x70) >> 4;
- int rLen = 0x800 << rLenExp;
+ size_t rAddr = tps[i].pos * 0x800;
+ size_t rLenExp = (tps[i].len & 0x70) >> 4;
+ size_t rLen = 0x800 << rLenExp;
if (rAddr + rLen > pcmROMSize) {
printDebug("Control ROM error: Wave map entry %d points to invalid PCM address 0x%04X, length 0x%04X", i, rAddr, rLen);
return false;
@@ -414,26 +430,19 @@ bool Synth::initTimbres(Bit16u mapAddress, Bit16u offset, int count, int startTi
return true;
}
-bool Synth::open(SynthProperties &useProp) {
+bool Synth::open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage) {
if (isOpen) {
return false;
}
prerenderReadIx = prerenderWriteIx = 0;
- myProp = 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);
}
#endif
- if (useProp.baseDir != NULL) {
- char *baseDirCopy = new char[strlen(useProp.baseDir) + 1];
- strcpy(baseDirCopy, useProp.baseDir);
- myProp.baseDir = baseDirCopy;
- }
// This is to help detect bugs
memset(&mt32ram, '?', sizeof(mt32ram));
@@ -441,12 +450,10 @@ bool Synth::open(SynthProperties &useProp) {
#if MT32EMU_MONITOR_INIT
printDebug("Loading Control ROM");
#endif
- if (loadControlROM("CM32L_CONTROL.ROM") != LoadResult_OK) {
- if (loadControlROM("MT32_CONTROL.ROM") != LoadResult_OK) {
- printDebug("Init Error - Missing or invalid MT32_CONTROL.ROM");
- //report(ReportType_errorControlROM, &errno);
- return false;
- }
+ if (!loadControlROM(controlROMImage)) {
+ printDebug("Init Error - Missing or invalid Control ROM image");
+ reportHandler->onErrorControlROM();
+ return false;
}
initMemoryRegions();
@@ -460,12 +467,10 @@ bool Synth::open(SynthProperties &useProp) {
#if MT32EMU_MONITOR_INIT
printDebug("Loading PCM ROM");
#endif
- if (loadPCMROM("CM32L_PCM.ROM") != LoadResult_OK) {
- if (loadPCMROM("MT32_PCM.ROM") != LoadResult_OK) {
- printDebug("Init Error - Missing MT32_PCM.ROM");
- //report(ReportType_errorPCMROM, &errno);
- return false;
- }
+ if (!loadPCMROM(pcmROMImage)) {
+ printDebug("Init Error - Missing PCM ROM image");
+ reportHandler->onErrorPCMROM();
+ return false;
}
#if MT32EMU_MONITOR_INIT
@@ -594,9 +599,6 @@ void Synth::close() {
parts[i] = NULL;
}
- delete[] myProp.baseDir;
- myProp.baseDir = NULL;
-
delete[] pcmWaves;
delete[] pcmROMData;
@@ -627,6 +629,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) {
@@ -1178,7 +1185,7 @@ void Synth::writeMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u le
case MR_System:
region->write(0, off, data, len);
- report(ReportType_devReconfig, NULL);
+ reportHandler->onDeviceReconfig();
// FIXME: We haven't properly confirmed any of this behaviour
// In particular, we tend to reset things such as reverb even if the write contained
// the same parameters as were already set, which may be wrong.
@@ -1216,7 +1223,7 @@ void Synth::writeMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u le
#if MT32EMU_MONITOR_SYSEX > 0
printDebug("WRITE-LCD: %s", buf);
#endif
- report(ReportType_lcdMessage, buf);
+ reportHandler->showLCDMessage(buf);
break;
case MR_Reset:
reset();
@@ -1244,9 +1251,9 @@ void Synth::refreshSystemReverbParameters() {
#endif
return;
}
- report(ReportType_newReverbMode, &mt32ram.system.reverbMode);
- report(ReportType_newReverbTime, &mt32ram.system.reverbTime);
- report(ReportType_newReverbLevel, &mt32ram.system.reverbLevel);
+ reportHandler->onNewReverbMode(mt32ram.system.reverbMode);
+ reportHandler->onNewReverbTime(mt32ram.system.reverbTime);
+ reportHandler->onNewReverbLevel(mt32ram.system.reverbLevel);
ReverbModel *newReverbModel = reverbModels[mt32ram.system.reverbMode];
#if MT32EMU_REDUCE_REVERB_MEMORY
@@ -1254,7 +1261,7 @@ void Synth::refreshSystemReverbParameters() {
if (reverbModel != NULL) {
reverbModel->close();
}
- newReverbModel->open(myProp.sampleRate);
+ newReverbModel->open();
}
#endif
reverbModel = newReverbModel;
@@ -1309,7 +1316,7 @@ void Synth::reset() {
#if MT32EMU_MONITOR_SYSEX > 0
printDebug("RESET");
#endif
- report(ReportType_devReset, NULL);
+ reportHandler->onDeviceReset();
partialManager->deactivateAll();
mt32ram = mt32default;
for (int i = 0; i < 9; i++) {
diff --git a/audio/softsynth/mt32/Synth.h b/audio/softsynth/mt32/Synth.h
index ccabce7282..91375c0fc0 100644
--- a/audio/softsynth/mt32/Synth.h
+++ b/audio/softsynth/mt32/Synth.h
@@ -26,6 +26,7 @@ class TableInitialiser;
class Partial;
class PartialManager;
class Part;
+class ROMImage;
/**
* Methods for emulating the connection between the LA32 and the DAC, which involves
@@ -57,71 +58,6 @@ enum DACInputMode {
DACInputMode_GENERATION2
};
-enum ReportType {
- // Errors
- ReportType_errorControlROM = 1,
- ReportType_errorPCMROM,
- ReportType_errorSampleRate,
-
- // Progress
- ReportType_progressInit,
-
- // HW spec
- ReportType_availableSSE,
- ReportType_available3DNow,
- ReportType_usingSSE,
- ReportType_using3DNow,
-
- // General info
- ReportType_lcdMessage,
- ReportType_devReset,
- ReportType_devReconfig,
- ReportType_newReverbMode,
- ReportType_newReverbTime,
- ReportType_newReverbLevel
-};
-
-enum LoadResult {
- LoadResult_OK,
- LoadResult_NotFound,
- LoadResult_Unreadable,
- LoadResult_Invalid
-};
-
-struct SynthProperties {
- // Sample rate to use in mixing
- unsigned int sampleRate;
-
- // Deprecated - ignored. Use Synth::setReverbEnabled() instead.
- bool useReverb;
- // Deprecated - ignored. Use Synth::setReverbOverridden() instead.
- bool useDefaultReverb;
- // Deprecated - ignored. Use Synth::playSysex*() to configure reverb instead.
- unsigned char reverbType;
- // Deprecated - ignored. Use Synth::playSysex*() to configure reverb instead.
- unsigned char reverbTime;
- // Deprecated - ignored. Use Synth::playSysex*() to configure reverb instead.
- unsigned char reverbLevel;
- // The name of the directory in which the ROM and data files are stored (with trailing slash/backslash)
- // Not used if "openFile" is set. May be NULL in any case.
- const char *baseDir;
- // This is used as the first argument to all callbacks
- void *userData;
- // Callback for reporting various errors and information. May be NULL
- int (*report)(void *userData, ReportType type, const void *reportData);
- // Callback for debug messages, in vprintf() format
- void (*printDebug)(void *userData, const char *fmt, va_list list);
- // Callback for providing an implementation of File, opened and ready for use
- // May be NULL, in which case a default implementation will be used.
- Common::File *(*openFile)(void *userData, const char *filename);
- // Callback for closing a File. May be NULL, in which case the File will automatically be close()d/deleted.
- void (*closeFile)(void *userData, Common::File *file);
-};
-
-// This is the specification of the Callback routine used when calling the RecalcWaveforms
-// function
-typedef void (*recalcStatusCallback)(int percDone);
-
typedef void (*FloatToBit16sFunc)(Bit16s *target, const float *source, Bit32u len, float outputGain);
const Bit8u SYSEX_MANUFACTURER_ROLAND = 0x41;
@@ -179,6 +115,13 @@ enum MemoryRegionType {
MR_PatchTemp, MR_RhythmTemp, MR_TimbreTemp, MR_Patches, MR_Timbres, MR_System, MR_Display, MR_Reset
};
+enum ReverbMode {
+ REVERB_MODE_ROOM,
+ REVERB_MODE_HALL,
+ REVERB_MODE_PLATE,
+ REVERB_MODE_TAP_DELAY
+};
+
class MemoryRegion {
private:
Synth *synth;
@@ -278,7 +221,7 @@ class ReverbModel {
public:
virtual ~ReverbModel() {}
// After construction or a close(), open() will be called at least once before any other call (with the exception of close()).
- virtual void open(unsigned int sampleRate) = 0;
+ virtual void open() = 0;
// May be called multiple times without an open() in between.
virtual void close() = 0;
virtual void setParameters(Bit8u time, Bit8u level) = 0;
@@ -286,6 +229,32 @@ public:
virtual bool isActive() const = 0;
};
+class ReportHandler {
+friend class Synth;
+
+public:
+ virtual ~ReportHandler() {}
+
+protected:
+
+ // Callback for debug messages, in vprintf() format
+ virtual void printDebug(const char *fmt, va_list list);
+
+ // Callbacks for reporting various errors and information
+ virtual void onErrorControlROM() {}
+ virtual void onErrorPCMROM() {}
+ virtual void showLCDMessage(const char *message);
+ virtual void onDeviceReset() {}
+ virtual void onDeviceReconfig() {}
+ virtual void onNewReverbMode(Bit8u /* mode */) {}
+ virtual void onNewReverbTime(Bit8u /* time */) {}
+ virtual void onNewReverbLevel(Bit8u /* level */) {}
+ virtual void onPartStateChanged(int /* partNum */, bool /* isActive */) {}
+ virtual void onPolyStateChanged(int /* partNum */) {}
+ virtual void onPartialStateChanged(int /* partialNum */, int /* oldPartialPhase */, int /* newPartialPhase */) {}
+ virtual void onProgramChanged(int /* partNum */, char * /* patchName */) {}
+};
+
class Synth {
friend class Part;
friend class RhythmPart;
@@ -315,13 +284,12 @@ private:
const ControlROMMap *controlROMMap;
Bit8u controlROMData[CONTROL_ROM_SIZE];
float *pcmROMData;
- int pcmROMSize; // This is in 16-bit samples, therefore half the number of bytes in the ROM
+ size_t pcmROMSize; // This is in 16-bit samples, therefore half the number of bytes in the ROM
Bit8s chantable[32];
Bit32u renderedSampleCount;
- Tables tables;
MemParams mt32ram, mt32default;
@@ -337,6 +305,9 @@ private:
bool isOpen;
+ bool isDefaultReportHandler;
+ ReportHandler *reportHandler;
+
PartialManager *partialManager;
Part *parts[9];
@@ -369,8 +340,6 @@ private:
int prerenderReadIx;
int prerenderWriteIx;
- SynthProperties myProp;
-
bool prerender();
void copyPrerender(Bit16s *nonReverbLeft, Bit16s *nonReverbRight, Bit16s *reverbDryLeft, Bit16s *reverbDryRight, Bit16s *reverbWetLeft, Bit16s *reverbWetRight, Bit32u pos, Bit32u len);
void checkPrerender(Bit16s *nonReverbLeft, Bit16s *nonReverbRight, Bit16s *reverbDryLeft, Bit16s *reverbDryRight, Bit16s *reverbWetLeft, Bit16s *reverbWetRight, Bit32u &pos, Bit32u &len);
@@ -384,8 +353,8 @@ private:
void writeMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, const Bit8u *data);
void readMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, Bit8u *data);
- LoadResult loadControlROM(const char *filename);
- LoadResult loadPCMROM(const char *filename);
+ bool loadControlROM(const ROMImage &controlROMImage);
+ bool loadPCMROM(const ROMImage &pcmROMImage);
bool initPCMList(Bit16u mapAddress, Bit16u count);
bool initTimbres(Bit16u mapAddress, Bit16u offset, int timbreCount, int startTimbre, bool compressed);
@@ -399,24 +368,25 @@ private:
void refreshSystem();
void reset();
- unsigned int getSampleRate() const;
-
void printPartialUsage(unsigned long sampleOffset = 0);
-protected:
- int report(ReportType type, const void *reportData);
- Common::File *openFile(const char *filename);
- void closeFile(Common::File *file);
+
+ void partStateChanged(int partNum, bool isPartActive);
+ void polyStateChanged(int partNum);
+ void partialStateChanged(const Partial * const partial, int oldPartialPhase, int newPartialPhase);
+ void newTimbreSet(int partNum, char patchName[]);
void printDebug(const char *fmt, ...);
public:
static Bit8u calcSysexChecksum(const Bit8u *data, Bit32u len, Bit8u checksum);
- Synth();
+ // Optionally sets callbacks for reporting various errors, information and debug messages
+ Synth(ReportHandler *useReportHandler = NULL);
~Synth();
// Used to initialise the MT-32. Must be called before any other function.
// Returns true if initialization was sucessful, otherwise returns false.
- bool open(SynthProperties &useProp);
+ // controlROMImage and pcmROMImage represent Control and PCM ROM images for use by synth.
+ bool open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage);
// Closes the MT-32 and deallocates any memory used by the synthesizer
void close(void);
diff --git a/audio/softsynth/mt32/TVA.cpp b/audio/softsynth/mt32/TVA.cpp
index f3e3f7bbc7..19a01cfc73 100644
--- a/audio/softsynth/mt32/TVA.cpp
+++ b/audio/softsynth/mt32/TVA.cpp
@@ -30,10 +30,13 @@ namespace MT32Emu {
static Bit8u biasLevelToAmpSubtractionCoeff[13] = {255, 187, 137, 100, 74, 54, 40, 29, 21, 15, 10, 5, 0};
TVA::TVA(const Partial *usePartial, LA32Ramp *useAmpRamp) :
- partial(usePartial), ampRamp(useAmpRamp), system_(&usePartial->getSynth()->mt32ram.system) {
+ partial(usePartial), ampRamp(useAmpRamp), system_(&usePartial->getSynth()->mt32ram.system), phase(TVA_PHASE_DEAD) {
}
void TVA::startRamp(Bit8u newTarget, Bit8u newIncrement, int newPhase) {
+ if (newPhase != phase) {
+ partial->getSynth()->partialStateChanged(partial, phase, newPhase);
+ }
target = newTarget;
phase = newPhase;
ampRamp->startRamp(newTarget, newIncrement);
@@ -43,6 +46,9 @@ void TVA::startRamp(Bit8u newTarget, Bit8u newIncrement, int newPhase) {
}
void TVA::end(int newPhase) {
+ if (newPhase != phase) {
+ partial->getSynth()->partialStateChanged(partial, phase, newPhase);
+ }
phase = newPhase;
playing = false;
#if MT32EMU_MONITOR_TVA >= 1
@@ -154,7 +160,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 +221,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 +247,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");
@@ -274,7 +280,7 @@ void TVA::nextPhase() {
}
int newTarget;
- int newIncrement = 0;
+ int newIncrement = 0; // Initialised to please compilers
int envPointIndex = phase;
if (!allLevelsZeroFromNowOn) {
diff --git a/audio/softsynth/mt32/TVF.cpp b/audio/softsynth/mt32/TVF.cpp
index 80b592ea67..47c4917632 100644
--- a/audio/softsynth/mt32/TVF.cpp
+++ b/audio/softsynth/mt32/TVF.cpp
@@ -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) {
diff --git a/audio/softsynth/mt32/TVP.cpp b/audio/softsynth/mt32/TVP.cpp
index 0b339e8d71..5dc4ca6b66 100644
--- a/audio/softsynth/mt32/TVP.cpp
+++ b/audio/softsynth/mt32/TVP.cpp
@@ -47,12 +47,11 @@ static Bit16u keyToPitchTable[] = {
TVP::TVP(const Partial *usePartial) :
partial(usePartial), system_(&usePartial->getSynth()->mt32ram.system) {
- unsigned int sampleRate = usePartial->getSynth()->myProp.sampleRate;
// We want to do processing 4000 times per second. FIXME: This is pretty arbitrary.
- maxCounter = sampleRate / 4000;
+ 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 / sampleRate;
+ processTimerIncrement = 500000 * maxCounter / SAMPLE_RATE;
}
static Bit16s keyToPitch(unsigned int key) {
@@ -171,9 +170,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.
diff --git a/audio/softsynth/mt32/Tables.cpp b/audio/softsynth/mt32/Tables.cpp
index c9bd40b7a4..5353a74079 100644
--- a/audio/softsynth/mt32/Tables.cpp
+++ b/audio/softsynth/mt32/Tables.cpp
@@ -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
@@ -76,31 +72,9 @@ void Tables::init() {
//synth->printDebug("%d: %d", i, pulseWidth100To255[i]);
}
- // Ratio of negative segment to wave length
- for (int i = 0; i < 128; i++) {
- // Formula determined from sample analysis.
- float pt = 0.5f / 127.0f * i;
- 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));
- }
-
- // found from sample analysis
- for (int i = 0; i < 32; i++) {
- resAmpMax[i] = EXP2F(1.0f - (32 - i) / 4.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 +91,5 @@ void Tables::init() {
sinf10[i] = sin(FLOAT_PI * i / 2048.0f);
}
}
+
+}
diff --git a/audio/softsynth/mt32/Tables.h b/audio/softsynth/mt32/Tables.h
index a2b5ff5d56..c3e80e7e9b 100644
--- a/audio/softsynth/mt32/Tables.h
+++ b/audio/softsynth/mt32/Tables.h
@@ -20,14 +20,23 @@
namespace MT32Emu {
+// Sample rate to use in mixing. With the progress of development, we've found way too many thing dependent.
+// In order to achieve further advance in emulation accuracy, sample rate made fixed throughout the emulator.
+// The output from the synth is supposed to be resampled to convert the sample rate.
+const unsigned int SAMPLE_RATE = 32000;
+
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 +56,9 @@ public:
// CONFIRMED:
Bit8u pulseWidth100To255[101];
- float pulseLenFactor[128];
- float pitchToFreq[65536];
- float cutoffToCosineLen[1024];
- float cutoffToFilterAmp[1024];
- float resAmpMax[32];
+ float exp2[4096];
float resAmpFadeFactor[8];
float sinf10[5120];
-
- Tables();
- void init();
};
}
diff --git a/audio/softsynth/mt32/mmath.h b/audio/softsynth/mt32/mmath.h
index 226d73e27e..25c79d57a9 100644
--- a/audio/softsynth/mt32/mmath.h
+++ b/audio/softsynth/mt32/mmath.h
@@ -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);
}
diff --git a/audio/softsynth/mt32/module.mk b/audio/softsynth/mt32/module.mk
index 995e450076..ed6b0d33ed 100644
--- a/audio/softsynth/mt32/module.mk
+++ b/audio/softsynth/mt32/module.mk
@@ -2,6 +2,7 @@ MODULE := audio/softsynth/mt32
MODULE_OBJS := \
AReverbModel.o \
+ BReverbModel.o \
DelayReverb.o \
FreeverbModel.o \
LA32Ramp.o \
@@ -9,6 +10,7 @@ MODULE_OBJS := \
Partial.o \
PartialManager.o \
Poly.o \
+ ROMInfo.o \
Synth.o \
TVA.o \
TVF.o \
diff --git a/audio/softsynth/mt32/mt32emu.h b/audio/softsynth/mt32/mt32emu.h
index 091819b95c..ae5f4955b1 100644
--- a/audio/softsynth/mt32/mt32emu.h
+++ b/audio/softsynth/mt32/mt32emu.h
@@ -59,13 +59,18 @@
#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
-
// Configuration
// The maximum number of partials playing simultaneously
#define MT32EMU_MAX_PARTIALS 32
@@ -77,9 +82,10 @@
// If zero, keeps reverb buffers for all modes around all the time to avoid allocating/freeing in the critical path.
#define MT32EMU_REDUCE_REVERB_MEMORY 1
-// 0: Use standard Freeverb
-// 1: Use AReverb (currently not properly tuned)
-#define MT32EMU_USE_AREVERBMODEL 0
+// 0: Use legacy Freeverb
+// 1: Use Accurate Reverb model aka AReverb
+// 2: Use Bit-perfect Boss Reverb model aka BReverb (for developers, not much practical use)
+#define MT32EMU_USE_REVERBMODEL 1
namespace MT32Emu
{
@@ -109,6 +115,7 @@ const unsigned int MAX_PRERENDER_SAMPLES = 1024;
#include "TVF.h"
#include "Partial.h"
#include "Part.h"
+#include "ROMInfo.h"
#include "Synth.h"
#endif