aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFilippos Karapetis2013-03-02 14:09:39 +0200
committerFilippos Karapetis2013-03-02 14:09:39 +0200
commitf4cc45d367442f850baecd814003ec09790a43b0 (patch)
treebfc904c0e8ced825749fa910696fae1d24fe10ca
parent8884c240fc4683975cd76802b600c019ebcb260c (diff)
downloadscummvm-rg350-f4cc45d367442f850baecd814003ec09790a43b0.tar.gz
scummvm-rg350-f4cc45d367442f850baecd814003ec09790a43b0.tar.bz2
scummvm-rg350-f4cc45d367442f850baecd814003ec09790a43b0.zip
MT32: Sync with the latest changes in munt
The major change is the addition of a refined wave generator based on logarithmic fixed-point computations and LUTs
-rw-r--r--audio/softsynth/mt32/LA32Ramp.cpp8
-rw-r--r--audio/softsynth/mt32/LA32WaveGenerator.cpp417
-rw-r--r--audio/softsynth/mt32/LA32WaveGenerator.h246
-rw-r--r--audio/softsynth/mt32/LegacyWaveGenerator.cpp347
-rw-r--r--audio/softsynth/mt32/LegacyWaveGenerator.h146
-rw-r--r--audio/softsynth/mt32/Partial.cpp438
-rw-r--r--audio/softsynth/mt32/Partial.h19
-rw-r--r--audio/softsynth/mt32/Synth.cpp14
-rw-r--r--audio/softsynth/mt32/Synth.h2
-rw-r--r--audio/softsynth/mt32/Tables.cpp31
-rw-r--r--audio/softsynth/mt32/Tables.h7
-rw-r--r--audio/softsynth/mt32/mmath.h4
-rw-r--r--audio/softsynth/mt32/module.mk2
-rw-r--r--audio/softsynth/mt32/mt32emu.h18
14 files changed, 1278 insertions, 421 deletions
diff --git a/audio/softsynth/mt32/LA32Ramp.cpp b/audio/softsynth/mt32/LA32Ramp.cpp
index c2b4596e07..b4ac6f1d46 100644
--- a/audio/softsynth/mt32/LA32Ramp.cpp
+++ b/audio/softsynth/mt32/LA32Ramp.cpp
@@ -82,9 +82,13 @@ void LA32Ramp::startRamp(Bit8u target, Bit8u increment) {
if (increment == 0) {
largeIncrement = 0;
} else {
- // Using integer argument here, no precision loss:
+ // Three bits in the fractional part, no need to interpolate
// (unsigned int)(EXP2F(((increment & 0x7F) + 24) / 8.0f) + 0.125f)
- largeIncrement = (unsigned int)(EXP2I(((increment & 0x7F) + 24) << 9) + 0.125f);
+ Bit32u expArg = increment & 0x7F;
+ largeIncrement = 8191 - Tables::getInstance().exp9[~(expArg << 6) & 511];
+ largeIncrement <<= expArg >> 3;
+ largeIncrement += 64;
+ largeIncrement >>= 9;
}
descending = (increment & 0x80) != 0;
if (descending) {
diff --git a/audio/softsynth/mt32/LA32WaveGenerator.cpp b/audio/softsynth/mt32/LA32WaveGenerator.cpp
new file mode 100644
index 0000000000..74dba7853a
--- /dev/null
+++ b/audio/softsynth/mt32/LA32WaveGenerator.cpp
@@ -0,0 +1,417 @@
+/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
+ * Copyright (C) 2011, 2012, 2013 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 <cmath>
+#include "mt32emu.h"
+#include "mmath.h"
+#include "LA32WaveGenerator.h"
+
+#if MT32EMU_ACCURATE_WG == 0
+
+namespace MT32Emu {
+
+static const Bit32u SINE_SEGMENT_RELATIVE_LENGTH = 1 << 18;
+static const Bit32u MIDDLE_CUTOFF_VALUE = 128 << 18;
+static const Bit32u RESONANCE_DECAY_THRESHOLD_CUTOFF_VALUE = 144 << 18;
+static const Bit32u MAX_CUTOFF_VALUE = 240 << 18;
+static const LogSample SILENCE = {65535, LogSample::POSITIVE};
+
+Bit16u LA32Utilites::interpolateExp(const Bit16u fract) {
+ Bit16u expTabIndex = fract >> 3;
+ Bit16u extraBits = fract & 7;
+ Bit16u expTabEntry2 = 8191 - Tables::getInstance().exp9[expTabIndex];
+ Bit16u expTabEntry1 = expTabIndex == 0 ? 8191 : (8191 - Tables::getInstance().exp9[expTabIndex - 1]);
+ return expTabEntry1 + (((expTabEntry2 - expTabEntry1) * extraBits) >> 3);
+}
+
+Bit16s LA32Utilites::unlog(const LogSample &logSample) {
+ //Bit16s sample = (Bit16s)EXP2F(13.0f - logSample.logValue / 1024.0f);
+ Bit32u intLogValue = logSample.logValue >> 12;
+ Bit16u fracLogValue = logSample.logValue & 4095;
+ Bit16s sample = interpolateExp(fracLogValue) >> intLogValue;
+ return logSample.sign == LogSample::POSITIVE ? sample : -sample;
+}
+
+void LA32Utilites::addLogSamples(LogSample &logSample1, const LogSample &logSample2) {
+ Bit32u logSampleValue = logSample1.logValue + logSample2.logValue;
+ logSample1.logValue = logSampleValue < 65536 ? (Bit16u)logSampleValue : 65535;
+ logSample1.sign = logSample1.sign == logSample2.sign ? LogSample::POSITIVE : LogSample::NEGATIVE;
+}
+
+Bit32u LA32WaveGenerator::getSampleStep() {
+ // sampleStep = EXP2F(pitch / 4096.0f + 4.0f)
+ Bit32u sampleStep = LA32Utilites::interpolateExp(~pitch & 4095);
+ sampleStep <<= pitch >> 12;
+ sampleStep >>= 8;
+ return sampleStep;
+}
+
+Bit32u LA32WaveGenerator::getResonanceWaveLengthFactor(Bit32u effectiveCutoffValue) {
+ // resonanceWaveLengthFactor = (Bit32u)EXP2F(12.0f + effectiveCutoffValue / 4096.0f);
+ Bit32u resonanceWaveLengthFactor = LA32Utilites::interpolateExp(~effectiveCutoffValue & 4095);
+ resonanceWaveLengthFactor <<= effectiveCutoffValue >> 12;
+ return resonanceWaveLengthFactor;
+}
+
+Bit32u LA32WaveGenerator::getHighLinearLength(Bit32u effectiveCutoffValue) {
+ // Ratio of positive segment to wave length
+ Bit32u effectivePulseWidthValue = 0;
+ if (pulseWidth > 128) {
+ effectivePulseWidthValue = (pulseWidth - 128) << 6;
+ }
+
+ Bit32u highLinearLength = 0;
+ // highLinearLength = EXP2F(19.0f - effectivePulseWidthValue / 4096.0f + effectiveCutoffValue / 4096.0f) - 2 * SINE_SEGMENT_RELATIVE_LENGTH;
+ if (effectivePulseWidthValue < effectiveCutoffValue) {
+ Bit32u expArg = effectiveCutoffValue - effectivePulseWidthValue;
+ highLinearLength = LA32Utilites::interpolateExp(~expArg & 4095);
+ highLinearLength <<= 7 + (expArg >> 12);
+ highLinearLength -= 2 * SINE_SEGMENT_RELATIVE_LENGTH;
+ }
+ return highLinearLength;
+}
+
+void LA32WaveGenerator::computePositions(Bit32u highLinearLength, Bit32u lowLinearLength, Bit32u resonanceWaveLengthFactor) {
+ // Assuming 12-bit multiplication used here
+ squareWavePosition = resonanceSinePosition = (wavePosition >> 8) * (resonanceWaveLengthFactor >> 4);
+ if (squareWavePosition < SINE_SEGMENT_RELATIVE_LENGTH) {
+ phase = POSITIVE_RISING_SINE_SEGMENT;
+ return;
+ }
+ squareWavePosition -= SINE_SEGMENT_RELATIVE_LENGTH;
+ if (squareWavePosition < highLinearLength) {
+ phase = POSITIVE_LINEAR_SEGMENT;
+ return;
+ }
+ squareWavePosition -= highLinearLength;
+ if (squareWavePosition < SINE_SEGMENT_RELATIVE_LENGTH) {
+ phase = POSITIVE_FALLING_SINE_SEGMENT;
+ return;
+ }
+ squareWavePosition -= SINE_SEGMENT_RELATIVE_LENGTH;
+ resonanceSinePosition = squareWavePosition;
+ if (squareWavePosition < SINE_SEGMENT_RELATIVE_LENGTH) {
+ phase = NEGATIVE_FALLING_SINE_SEGMENT;
+ return;
+ }
+ squareWavePosition -= SINE_SEGMENT_RELATIVE_LENGTH;
+ if (squareWavePosition < lowLinearLength) {
+ phase = NEGATIVE_LINEAR_SEGMENT;
+ return;
+ }
+ squareWavePosition -= lowLinearLength;
+ phase = NEGATIVE_RISING_SINE_SEGMENT;
+}
+
+void LA32WaveGenerator::advancePosition() {
+ wavePosition += getSampleStep();
+ wavePosition %= 4 * SINE_SEGMENT_RELATIVE_LENGTH;
+
+ Bit32u effectiveCutoffValue = (cutoffVal > MIDDLE_CUTOFF_VALUE) ? (cutoffVal - MIDDLE_CUTOFF_VALUE) >> 10 : 0;
+ Bit32u resonanceWaveLengthFactor = getResonanceWaveLengthFactor(effectiveCutoffValue);
+ Bit32u highLinearLength = getHighLinearLength(effectiveCutoffValue);
+ Bit32u lowLinearLength = (resonanceWaveLengthFactor << 8) - 4 * SINE_SEGMENT_RELATIVE_LENGTH - highLinearLength;
+ computePositions(highLinearLength, lowLinearLength, resonanceWaveLengthFactor);
+
+ // resonancePhase computation hack
+ *(int*)&resonancePhase = ((resonanceSinePosition >> 18) + (phase > POSITIVE_FALLING_SINE_SEGMENT ? 2 : 0)) & 3;
+}
+
+void LA32WaveGenerator::generateNextSquareWaveLogSample() {
+ Bit32u logSampleValue;
+ switch (phase) {
+ case POSITIVE_RISING_SINE_SEGMENT:
+ case NEGATIVE_FALLING_SINE_SEGMENT:
+ logSampleValue = Tables::getInstance().logsin9[(squareWavePosition >> 9) & 511];
+ break;
+ case POSITIVE_FALLING_SINE_SEGMENT:
+ case NEGATIVE_RISING_SINE_SEGMENT:
+ logSampleValue = Tables::getInstance().logsin9[~(squareWavePosition >> 9) & 511];
+ break;
+ case POSITIVE_LINEAR_SEGMENT:
+ case NEGATIVE_LINEAR_SEGMENT:
+ default:
+ logSampleValue = 0;
+ break;
+ }
+ logSampleValue <<= 2;
+ logSampleValue += amp >> 10;
+ if (cutoffVal < MIDDLE_CUTOFF_VALUE) {
+ logSampleValue += (MIDDLE_CUTOFF_VALUE - cutoffVal) >> 9;
+ }
+
+ squareLogSample.logValue = logSampleValue < 65536 ? (Bit16u)logSampleValue : 65535;
+ squareLogSample.sign = phase < NEGATIVE_FALLING_SINE_SEGMENT ? LogSample::POSITIVE : LogSample::NEGATIVE;
+}
+
+void LA32WaveGenerator::generateNextResonanceWaveLogSample() {
+ Bit32u logSampleValue;
+ if (resonancePhase == POSITIVE_FALLING_RESONANCE_SINE_SEGMENT || resonancePhase == NEGATIVE_RISING_RESONANCE_SINE_SEGMENT) {
+ logSampleValue = Tables::getInstance().logsin9[~(resonanceSinePosition >> 9) & 511];
+ } else {
+ logSampleValue = Tables::getInstance().logsin9[(resonanceSinePosition >> 9) & 511];
+ }
+ logSampleValue <<= 2;
+ logSampleValue += amp >> 10;
+
+ // From the digital captures, the decaying speed of the resonance sine is found a bit different for the positive and the negative segments
+ Bit32u decayFactor = phase < NEGATIVE_FALLING_SINE_SEGMENT ? resAmpDecayFactor : resAmpDecayFactor + 1;
+ // Unsure about resonanceSinePosition here. It's possible that dedicated counter & decrement are used. Although, cutoff is finely ramped, so maybe not.
+ logSampleValue += resonanceAmpSubtraction + (((resonanceSinePosition >> 4) * decayFactor) >> 8);
+
+ // To ensure the output wave has no breaks, two different windows are appied to the beginning and the ending of the resonance sine segment
+ if (phase == POSITIVE_RISING_SINE_SEGMENT || phase == NEGATIVE_FALLING_SINE_SEGMENT) {
+ // The window is synchronous sine here
+ logSampleValue += Tables::getInstance().logsin9[(squareWavePosition >> 9) & 511] << 2;
+ } else if (phase == POSITIVE_FALLING_SINE_SEGMENT || phase == NEGATIVE_RISING_SINE_SEGMENT) {
+ // The window is synchronous square sine here
+ logSampleValue += Tables::getInstance().logsin9[~(squareWavePosition >> 9) & 511] << 3;
+ }
+
+ if (cutoffVal < MIDDLE_CUTOFF_VALUE) {
+ // For the cutoff values below the cutoff middle point, it seems the amp of the resonance wave is expotentially decayed
+ logSampleValue += 31743 + ((MIDDLE_CUTOFF_VALUE - cutoffVal) >> 9);
+ } else if (cutoffVal < RESONANCE_DECAY_THRESHOLD_CUTOFF_VALUE) {
+ // For the cutoff values below this point, the amp of the resonance wave is sinusoidally decayed
+ Bit32u sineIx = (cutoffVal - MIDDLE_CUTOFF_VALUE) >> 13;
+ logSampleValue += Tables::getInstance().logsin9[sineIx] << 2;
+ }
+
+ // After all the amp decrements are added, it should be safe now to adjust the amp of the resonance wave to what we see on captures
+ logSampleValue -= 1 << 12;
+
+ resonanceLogSample.logValue = logSampleValue < 65536 ? (Bit16u)logSampleValue : 65535;
+ resonanceLogSample.sign = resonancePhase < NEGATIVE_FALLING_RESONANCE_SINE_SEGMENT ? LogSample::POSITIVE : LogSample::NEGATIVE;
+}
+
+void LA32WaveGenerator::generateNextSawtoothCosineLogSample(LogSample &logSample) const {
+ Bit32u sawtoothCosinePosition = wavePosition + (1 << 18);
+ if ((sawtoothCosinePosition & (1 << 18)) > 0) {
+ logSample.logValue = Tables::getInstance().logsin9[~(sawtoothCosinePosition >> 9) & 511];
+ } else {
+ logSample.logValue = Tables::getInstance().logsin9[(sawtoothCosinePosition >> 9) & 511];
+ }
+ logSample.logValue <<= 2;
+ logSample.sign = ((sawtoothCosinePosition & (1 << 19)) == 0) ? LogSample::POSITIVE : LogSample::NEGATIVE;
+}
+
+void LA32WaveGenerator::pcmSampleToLogSample(LogSample &logSample, const Bit16s pcmSample) const {
+ Bit32u logSampleValue = (32787 - (pcmSample & 32767)) << 1;
+ logSampleValue += amp >> 10;
+ logSample.logValue = logSampleValue < 65536 ? (Bit16u)logSampleValue : 65535;
+ logSample.sign = pcmSample < 0 ? LogSample::NEGATIVE : LogSample::POSITIVE;
+}
+
+void LA32WaveGenerator::generateNextPCMWaveLogSamples() {
+ // This should emulate the ladder we see in the PCM captures for pitches 01, 02, 07, etc.
+ // The most probable cause is the factor in the interpolation formula is one bit less
+ // accurate than the sample position counter
+ pcmInterpolationFactor = (wavePosition & 255) >> 1;
+ Bit32u pcmWaveTableIx = wavePosition >> 8;
+ pcmSampleToLogSample(firstPCMLogSample, pcmWaveAddress[pcmWaveTableIx]);
+ if (pcmWaveInterpolated) {
+ pcmWaveTableIx++;
+ if (pcmWaveTableIx < pcmWaveLength) {
+ pcmSampleToLogSample(secondPCMLogSample, pcmWaveAddress[pcmWaveTableIx]);
+ } else {
+ if (pcmWaveLooped) {
+ pcmWaveTableIx -= pcmWaveLength;
+ pcmSampleToLogSample(secondPCMLogSample, pcmWaveAddress[pcmWaveTableIx]);
+ } else {
+ secondPCMLogSample = SILENCE;
+ }
+ }
+ } else {
+ secondPCMLogSample = SILENCE;
+ }
+ // pcmSampleStep = EXP2F(pitch / 4096. - 5.);
+ Bit32u pcmSampleStep = LA32Utilites::interpolateExp(~pitch & 4095);
+ pcmSampleStep <<= pitch >> 12;
+ // Seeing the actual lengths of the PCM wave for pitches 00..12,
+ // the pcmPosition counter can be assumed to have 8-bit fractions
+ pcmSampleStep >>= 9;
+ wavePosition += pcmSampleStep;
+ if (wavePosition >= (pcmWaveLength << 8)) {
+ if (pcmWaveLooped) {
+ wavePosition -= pcmWaveLength << 8;
+ } else {
+ deactivate();
+ }
+ }
+}
+
+void LA32WaveGenerator::initSynth(const bool useSawtoothWaveform, const Bit8u usePulseWidth, const Bit8u useResonance) {
+ sawtoothWaveform = useSawtoothWaveform;
+ pulseWidth = usePulseWidth;
+ resonance = useResonance;
+
+ wavePosition = 0;
+
+ squareWavePosition = 0;
+ phase = POSITIVE_RISING_SINE_SEGMENT;
+
+ resonanceSinePosition = 0;
+ resonancePhase = POSITIVE_RISING_RESONANCE_SINE_SEGMENT;
+ resonanceAmpSubtraction = (32 - resonance) << 10;
+ resAmpDecayFactor = Tables::getInstance().resAmpDecayFactor[resonance >> 2] << 2;
+
+ pcmWaveAddress = NULL;
+ active = true;
+}
+
+void LA32WaveGenerator::initPCM(const Bit16s * const usePCMWaveAddress, const Bit32u usePCMWaveLength, const bool usePCMWaveLooped, const bool usePCMWaveInterpolated) {
+ pcmWaveAddress = usePCMWaveAddress;
+ pcmWaveLength = usePCMWaveLength;
+ pcmWaveLooped = usePCMWaveLooped;
+ pcmWaveInterpolated = usePCMWaveInterpolated;
+
+ wavePosition = 0;
+ active = true;
+}
+
+void LA32WaveGenerator::generateNextSample(const Bit32u useAmp, const Bit16u usePitch, const Bit32u useCutoffVal) {
+ if (!active) {
+ return;
+ }
+
+ amp = useAmp;
+ pitch = usePitch;
+
+ if (isPCMWave()) {
+ generateNextPCMWaveLogSamples();
+ return;
+ }
+
+ // The 240 cutoffVal limit was determined via sample analysis (internal Munt capture IDs: glop3, glop4).
+ // More research is needed to be sure that this is correct, however.
+ cutoffVal = (useCutoffVal > MAX_CUTOFF_VALUE) ? MAX_CUTOFF_VALUE : useCutoffVal;
+
+ generateNextSquareWaveLogSample();
+ generateNextResonanceWaveLogSample();
+ if (sawtoothWaveform) {
+ LogSample cosineLogSample;
+ generateNextSawtoothCosineLogSample(cosineLogSample);
+ LA32Utilites::addLogSamples(squareLogSample, cosineLogSample);
+ LA32Utilites::addLogSamples(resonanceLogSample, cosineLogSample);
+ }
+ advancePosition();
+}
+
+LogSample LA32WaveGenerator::getOutputLogSample(const bool first) const {
+ if (!isActive()) {
+ return SILENCE;
+ }
+ if (isPCMWave()) {
+ return first ? firstPCMLogSample : secondPCMLogSample;
+ }
+ return first ? squareLogSample : resonanceLogSample;
+}
+
+void LA32WaveGenerator::deactivate() {
+ active = false;
+}
+
+bool LA32WaveGenerator::isActive() const {
+ return active;
+}
+
+bool LA32WaveGenerator::isPCMWave() const {
+ return pcmWaveAddress != NULL;
+}
+
+Bit32u LA32WaveGenerator::getPCMInterpolationFactor() const {
+ return pcmInterpolationFactor;
+}
+
+void LA32PartialPair::init(const bool useRingModulated, const bool useMixed) {
+ ringModulated = useRingModulated;
+ mixed = useMixed;
+}
+
+void LA32PartialPair::initSynth(const PairType useMaster, const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance) {
+ if (useMaster == MASTER) {
+ master.initSynth(sawtoothWaveform, pulseWidth, resonance);
+ } else {
+ slave.initSynth(sawtoothWaveform, pulseWidth, resonance);
+ }
+}
+
+void LA32PartialPair::initPCM(const PairType useMaster, const Bit16s *pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped) {
+ if (useMaster == MASTER) {
+ master.initPCM(pcmWaveAddress, pcmWaveLength, pcmWaveLooped, true);
+ } else {
+ slave.initPCM(pcmWaveAddress, pcmWaveLength, pcmWaveLooped, !ringModulated);
+ }
+}
+
+void LA32PartialPair::generateNextSample(const PairType useMaster, const Bit32u amp, const Bit16u pitch, const Bit32u cutoff) {
+ if (useMaster == MASTER) {
+ master.generateNextSample(amp, pitch, cutoff);
+ } else {
+ slave.generateNextSample(amp, pitch, cutoff);
+ }
+}
+
+Bit16s LA32PartialPair::unlogAndMixWGOutput(const LA32WaveGenerator &wg, const LogSample * const ringModulatingLogSample) {
+ if (!wg.isActive() || ((ringModulatingLogSample != NULL) && (ringModulatingLogSample->logValue == SILENCE.logValue))) {
+ return 0;
+ }
+ LogSample firstLogSample = wg.getOutputLogSample(true);
+ LogSample secondLogSample = wg.getOutputLogSample(false);
+ if (ringModulatingLogSample != NULL) {
+ LA32Utilites::addLogSamples(firstLogSample, *ringModulatingLogSample);
+ LA32Utilites::addLogSamples(secondLogSample, *ringModulatingLogSample);
+ }
+ Bit16s firstSample = LA32Utilites::unlog(firstLogSample);
+ Bit16s secondSample = LA32Utilites::unlog(secondLogSample);
+ if (wg.isPCMWave()) {
+ return Bit16s(firstSample + ((Bit32s(secondSample - firstSample) * wg.getPCMInterpolationFactor()) >> 7));
+ }
+ return firstSample + secondSample;
+}
+
+Bit16s LA32PartialPair::nextOutSample() {
+ if (ringModulated) {
+ LogSample slaveFirstLogSample = slave.getOutputLogSample(true);
+ LogSample slaveSecondLogSample = slave.getOutputLogSample(false);
+ Bit16s sample = unlogAndMixWGOutput(master, &slaveFirstLogSample);
+ if (!slave.isPCMWave()) {
+ sample += unlogAndMixWGOutput(master, &slaveSecondLogSample);
+ }
+ if (mixed) {
+ sample += unlogAndMixWGOutput(master, NULL);
+ }
+ return sample;
+ }
+ return unlogAndMixWGOutput(master, NULL) + unlogAndMixWGOutput(slave, NULL);
+}
+
+void LA32PartialPair::deactivate(const PairType useMaster) {
+ if (useMaster == MASTER) {
+ master.deactivate();
+ } else {
+ slave.deactivate();
+ }
+}
+
+bool LA32PartialPair::isActive(const PairType useMaster) const {
+ return useMaster == MASTER ? master.isActive() : slave.isActive();
+}
+
+}
+
+#endif // #if MT32EMU_ACCURATE_WG == 0
diff --git a/audio/softsynth/mt32/LA32WaveGenerator.h b/audio/softsynth/mt32/LA32WaveGenerator.h
new file mode 100644
index 0000000000..37a4aead85
--- /dev/null
+++ b/audio/softsynth/mt32/LA32WaveGenerator.h
@@ -0,0 +1,246 @@
+/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
+ * Copyright (C) 2011, 2012, 2013 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/>.
+ */
+
+#if MT32EMU_ACCURATE_WG == 0
+
+#ifndef MT32EMU_LA32_WAVE_GENERATOR_H
+#define MT32EMU_LA32_WAVE_GENERATOR_H
+
+namespace MT32Emu {
+
+/**
+ * LA32 performs wave generation in the log-space that allows replacing multiplications by cheap additions
+ * It's assumed that only low-bit multiplications occur in a few places which are unavoidable like these:
+ * - interpolation of exponent table (obvious, a delta value has 4 bits)
+ * - computation of resonance amp decay envelope (the table contains values with 1-2 "1" bits except the very first value 31 but this case can be found using inversion)
+ * - interpolation of PCM samples (obvious, the wave position counter is in the linear space, there is no log() table in the chip)
+ * and it seems to be implemented in the same way as in the Boss chip, i.e. right shifted additions which involved noticeable precision loss
+ * Subtraction is supposed to be replaced by simple inversion
+ * As the logarithmic sine is always negative, all the logarithmic values are treated as decrements
+ */
+struct LogSample {
+ // 16-bit fixed point value, includes 12-bit fractional part
+ // 4-bit integer part allows to present any 16-bit sample in the log-space
+ // Obviously, the log value doesn't contain the sign of the resulting sample
+ Bit16u logValue;
+ enum {
+ POSITIVE,
+ NEGATIVE
+ } sign;
+};
+
+class LA32Utilites {
+public:
+ static Bit16u interpolateExp(const Bit16u fract);
+ static Bit16s unlog(const LogSample &logSample);
+ static void addLogSamples(LogSample &logSample1, const LogSample &logSample2);
+};
+
+/**
+ * LA32WaveGenerator is aimed to represent the exact model of LA32 wave generator.
+ * The output square wave is created by adding high / low linear segments in-between
+ * the rising and falling cosine segments. Basically, it’s very similar to the phase distortion synthesis.
+ * Behaviour of a true resonance filter is emulated by adding decaying sine wave.
+ * The beginning and the ending of the resonant sine is multiplied by a cosine window.
+ * To synthesise sawtooth waves, the resulting square wave is multiplied by synchronous cosine wave.
+ */
+class LA32WaveGenerator {
+ //***************************************************************************
+ // The local copy of partial parameters below
+ //***************************************************************************
+
+ bool active;
+
+ // True means the resulting square wave is to be multiplied by the synchronous cosine
+ bool sawtoothWaveform;
+
+ // Logarithmic amp of the wave generator
+ Bit32u amp;
+
+ // Logarithmic frequency of the resulting wave
+ Bit16u pitch;
+
+ // Values in range [1..31]
+ // Value 1 correspong to the minimum resonance
+ Bit8u resonance;
+
+ // Processed value in range [0..255]
+ // Values in range [0..128] have no effect and the resulting wave remains symmetrical
+ // Value 255 corresponds to the maximum possible asymmetric of the resulting wave
+ Bit8u pulseWidth;
+
+ // Composed of the base cutoff in range [78..178] left-shifted by 18 bits and the TVF modifier
+ Bit32u cutoffVal;
+
+ // Logarithmic PCM sample start address
+ const Bit16s *pcmWaveAddress;
+
+ // Logarithmic PCM sample length
+ Bit32u pcmWaveLength;
+
+ // true for looped logarithmic PCM samples
+ bool pcmWaveLooped;
+
+ // false for slave PCM partials in the structures with the ring modulation
+ bool pcmWaveInterpolated;
+
+ //***************************************************************************
+ // Internal variables below
+ //***************************************************************************
+
+ // Relative position within either the synth wave or the PCM sampled wave
+ // 0 - start of the positive rising sine segment of the square wave or start of the PCM sample
+ // 1048576 (2^20) - end of the negative rising sine segment of the square wave
+ // For PCM waves, the address of the currently playing sample equals (wavePosition / 256)
+ Bit32u wavePosition;
+
+ // Relative position within a square wave phase:
+ // 0 - start of the phase
+ // 262144 (2^18) - end of a sine phase in the square wave
+ Bit32u squareWavePosition;
+
+ // Relative position within the positive or negative wave segment:
+ // 0 - start of the corresponding positive or negative segment of the square wave
+ // 262144 (2^18) - corresponds to end of the first sine phase in the square wave
+ // The same increment sampleStep is used to indicate the current position
+ // since the length of the resonance wave is always equal to four square wave sine segments.
+ Bit32u resonanceSinePosition;
+
+ // The amp of the resonance sine wave grows with the resonance value
+ // As the resonance value cannot change while the partial is active, it is initialised once
+ Bit32u resonanceAmpSubtraction;
+
+ // The decay speed of resonance sine wave, depends on the resonance value
+ Bit32u resAmpDecayFactor;
+
+ // Fractional part of the pcmPosition
+ Bit32u pcmInterpolationFactor;
+
+ // Current phase of the square wave
+ enum {
+ POSITIVE_RISING_SINE_SEGMENT,
+ POSITIVE_LINEAR_SEGMENT,
+ POSITIVE_FALLING_SINE_SEGMENT,
+ NEGATIVE_FALLING_SINE_SEGMENT,
+ NEGATIVE_LINEAR_SEGMENT,
+ NEGATIVE_RISING_SINE_SEGMENT
+ } phase;
+
+ // Current phase of the resonance wave
+ enum {
+ POSITIVE_RISING_RESONANCE_SINE_SEGMENT,
+ POSITIVE_FALLING_RESONANCE_SINE_SEGMENT,
+ NEGATIVE_FALLING_RESONANCE_SINE_SEGMENT,
+ NEGATIVE_RISING_RESONANCE_SINE_SEGMENT
+ } resonancePhase;
+
+ // Resulting log-space samples of the square and resonance waves
+ LogSample squareLogSample;
+ LogSample resonanceLogSample;
+
+ // Processed neighbour log-space samples of the PCM wave
+ LogSample firstPCMLogSample;
+ LogSample secondPCMLogSample;
+
+ //***************************************************************************
+ // Internal methods below
+ //***************************************************************************
+
+ Bit32u getSampleStep();
+ Bit32u getResonanceWaveLengthFactor(Bit32u effectiveCutoffValue);
+ Bit32u getHighLinearLength(Bit32u effectiveCutoffValue);
+
+ void computePositions(Bit32u highLinearLength, Bit32u lowLinearLength, Bit32u resonanceWaveLengthFactor);
+ void advancePosition();
+
+ void generateNextSquareWaveLogSample();
+ void generateNextResonanceWaveLogSample();
+ void generateNextSawtoothCosineLogSample(LogSample &logSample) const;
+
+ void pcmSampleToLogSample(LogSample &logSample, const Bit16s pcmSample) const;
+ void generateNextPCMWaveLogSamples();
+
+public:
+ // Initialise the WG engine for generation of synth partial samples and set up the invariant parameters
+ void initSynth(const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance);
+
+ // Initialise the WG engine for generation of PCM partial samples and set up the invariant parameters
+ void initPCM(const Bit16s * const pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped, const bool pcmWaveInterpolated);
+
+ // Update parameters with respect to TVP, TVA and TVF, and generate next sample
+ void generateNextSample(const Bit32u amp, const Bit16u pitch, const Bit32u cutoff);
+
+ // WG output in the log-space consists of two components which are to be added (or ring modulated) in the linear-space afterwards
+ LogSample getOutputLogSample(const bool first) const;
+
+ // Deactivate the WG engine
+ void deactivate();
+
+ // Return active state of the WG engine
+ bool isActive() const;
+
+ // Return true if the WG engine generates PCM wave samples
+ bool isPCMWave() const;
+
+ // Return current PCM interpolation factor
+ Bit32u getPCMInterpolationFactor() const;
+};
+
+// LA32PartialPair contains a structure of two partials being mixed / ring modulated
+class LA32PartialPair {
+ LA32WaveGenerator master;
+ LA32WaveGenerator slave;
+ bool ringModulated;
+ bool mixed;
+
+ static Bit16s unlogAndMixWGOutput(const LA32WaveGenerator &wg, const LogSample * const ringModulatingLogSample);
+
+public:
+ enum PairType {
+ MASTER,
+ SLAVE
+ };
+
+ // ringModulated should be set to false for the structures with mixing or stereo output
+ // ringModulated should be set to true for the structures with ring modulation
+ // mixed is used for the structures with ring modulation and indicates whether the master partial output is mixed to the ring modulator output
+ void init(const bool ringModulated, const bool mixed);
+
+ // Initialise the WG engine for generation of synth partial samples and set up the invariant parameters
+ void initSynth(const PairType master, const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance);
+
+ // Initialise the WG engine for generation of PCM partial samples and set up the invariant parameters
+ void initPCM(const PairType master, const Bit16s * const pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped);
+
+ // Update parameters with respect to TVP, TVA and TVF, and generate next sample
+ void generateNextSample(const PairType master, const Bit32u amp, const Bit16u pitch, const Bit32u cutoff);
+
+ // Perform mixing / ring modulation and return the result
+ Bit16s nextOutSample();
+
+ // Deactivate the WG engine
+ void deactivate(const PairType master);
+
+ // Return active state of the WG engine
+ bool isActive(const PairType master) const;
+};
+
+} // namespace MT32Emu
+
+#endif // #ifndef MT32EMU_LA32_WAVE_GENERATOR_H
+
+#endif // #if MT32EMU_ACCURATE_WG == 0
diff --git a/audio/softsynth/mt32/LegacyWaveGenerator.cpp b/audio/softsynth/mt32/LegacyWaveGenerator.cpp
new file mode 100644
index 0000000000..139c89295f
--- /dev/null
+++ b/audio/softsynth/mt32/LegacyWaveGenerator.cpp
@@ -0,0 +1,347 @@
+/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
+ * Copyright (C) 2011, 2012, 2013 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 <cmath>
+#include "mt32emu.h"
+#include "mmath.h"
+#include "LegacyWaveGenerator.h"
+
+#if MT32EMU_ACCURATE_WG == 1
+
+namespace MT32Emu {
+
+static const float MIDDLE_CUTOFF_VALUE = 128.0f;
+static const float RESONANCE_DECAY_THRESHOLD_CUTOFF_VALUE = 144.0f;
+static const float MAX_CUTOFF_VALUE = 240.0f;
+
+float LA32WaveGenerator::getPCMSample(unsigned int position) {
+ if (position >= pcmWaveLength) {
+ if (!pcmWaveLooped) {
+ return 0;
+ }
+ position = position % pcmWaveLength;
+ }
+ Bit16s pcmSample = pcmWaveAddress[position];
+ float sampleValue = EXP2F(((pcmSample & 32767) - 32787.0f) / 2048.0f);
+ return ((pcmSample & 32768) == 0) ? sampleValue : -sampleValue;
+}
+
+void LA32WaveGenerator::initSynth(const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance) {
+ this->sawtoothWaveform = sawtoothWaveform;
+ this->pulseWidth = pulseWidth;
+ this->resonance = resonance;
+
+ wavePos = 0.0f;
+ lastFreq = 0.0f;
+
+ pcmWaveAddress = NULL;
+ active = true;
+}
+
+void LA32WaveGenerator::initPCM(const Bit16s * const pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped, const bool pcmWaveInterpolated) {
+ this->pcmWaveAddress = pcmWaveAddress;
+ this->pcmWaveLength = pcmWaveLength;
+ this->pcmWaveLooped = pcmWaveLooped;
+ this->pcmWaveInterpolated = pcmWaveInterpolated;
+
+ pcmPosition = 0.0f;
+ active = true;
+}
+
+float LA32WaveGenerator::generateNextSample(const Bit32u ampVal, const Bit16u pitch, const Bit32u cutoffRampVal) {
+ if (!active) {
+ return 0.0f;
+ }
+
+ this->amp = amp;
+ this->pitch = pitch;
+
+ float sample = 0.0f;
+
+ // 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)
+ // when sustaining at levels 156 - 255 with no modifiers.
+ // (2) Tested with a special square wave partial (internal capture ID tva5) at TVA envelope levels 155-255.
+ // This gives deltas between -1 and 0 compared to the real output. Note that this special partial only produces
+ // positive amps, so negative still needs to be explored, as well as lower levels.
+ //
+ // Also still partially unconfirmed is the behaviour when ramping between levels, as well as the timing.
+
+ float amp = EXP2F(ampVal / -1024.0f / 4096.0f);
+ float freq = EXP2F(pitch / 4096.0f - 16.0f) * SAMPLE_RATE;
+
+ if (isPCMWave()) {
+ // Render PCM waveform
+ int len = pcmWaveLength;
+ int intPCMPosition = (int)pcmPosition;
+ if (intPCMPosition >= len && !pcmWaveLooped) {
+ // We're now past the end of a non-looping PCM waveform so it's time to die.
+ deactivate();
+ return 0.0f;
+ }
+ float positionDelta = freq * 2048.0f / SAMPLE_RATE;
+
+ // Linear interpolation
+ float firstSample = getPCMSample(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 (pcmWaveInterpolated) {
+ sample = firstSample + (getPCMSample(intPCMPosition + 1) - firstSample) * (pcmPosition - intPCMPosition);
+ } else {
+ sample = firstSample;
+ }
+
+ float newPCMPosition = pcmPosition + positionDelta;
+ if (pcmWaveLooped) {
+ newPCMPosition = fmod(newPCMPosition, (float)pcmWaveLength);
+ }
+ pcmPosition = newPCMPosition;
+ } else {
+ // Render synthesised waveform
+ wavePos *= lastFreq / freq;
+ lastFreq = freq;
+
+ float resAmp = EXP2F(1.0f - (32 - resonance) / 4.0f);
+ {
+ //static const float resAmpFactor = EXP2F(-7);
+ //resAmp = EXP2I(resonance << 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.
+ // The 240 cutoffVal limit was determined via sample analysis (internal Munt capture IDs: glop3, glop4).
+ // More research is needed to be sure that this is correct, however.
+ float cutoffVal = cutoffRampVal / 262144.0f;
+ if (cutoffVal > MAX_CUTOFF_VALUE) {
+ cutoffVal = MAX_CUTOFF_VALUE;
+ }
+
+ // Wave length in samples
+ float waveLen = SAMPLE_RATE / freq;
+
+ // Init cosineLen
+ float cosineLen = 0.5f * waveLen;
+ if (cutoffVal > MIDDLE_CUTOFF_VALUE) {
+ cosineLen *= EXP2F((cutoffVal - MIDDLE_CUTOFF_VALUE) / -16.0f); // found from sample analysis
+ }
+
+ // Start playing in center of first cosine segment
+ // relWavePos is shifted by a half of cosineLen
+ float relWavePos = wavePos + 0.5f * cosineLen;
+ if (relWavePos > waveLen) {
+ relWavePos -= waveLen;
+ }
+
+ // Ratio of positive segment to wave length
+ float pulseLen = 0.5f;
+ if (pulseWidth > 128) {
+ pulseLen = EXP2F((64 - pulseWidth) / 64.0f);
+ //static const float pulseLenFactor = EXP2F(-192 / 64);
+ //pulseLen = EXP2I((256 - pulseWidthVal) << 6) * pulseLenFactor;
+ }
+ pulseLen *= waveLen;
+
+ float hLen = pulseLen - cosineLen;
+
+ // Ignore pulsewidths too high for given freq
+ if (hLen < 0.0f) {
+ hLen = 0.0f;
+ }
+
+ // Ignore pulsewidths too high for given freq and cutoff
+ float lLen = waveLen - hLen - 2 * cosineLen;
+ if (lLen < 0.0f) {
+ lLen = 0.0f;
+ }
+
+ // Correct resAmp for cutoff in range 50..66
+ if ((cutoffVal >= 128.0f) && (cutoffVal < 144.0f)) {
+ resAmp *= sinf(FLOAT_PI * (cutoffVal - 128.0f) / 32.0f);
+ }
+
+ // Produce filtered square wave with 2 cosine waves on slopes
+
+ // 1st cosine segment
+ if (relWavePos < cosineLen) {
+ sample = -cosf(FLOAT_PI * relWavePos / cosineLen);
+ } else
+
+ // high linear segment
+ if (relWavePos < (cosineLen + hLen)) {
+ sample = 1.f;
+ } else
+
+ // 2nd cosine segment
+ if (relWavePos < (2 * cosineLen + hLen)) {
+ sample = cosf(FLOAT_PI * (relWavePos - (cosineLen + hLen)) / cosineLen);
+ } else {
+
+ // low linear segment
+ sample = -1.f;
+ }
+
+ if (cutoffVal < 128.0f) {
+
+ // Attenuate samples below cutoff 50
+ // Found by sample analysis
+ sample *= EXP2F(-0.125f * (128.0f - cutoffVal));
+ } else {
+
+ // Add resonance sine. Effective for cutoff > 50 only
+ float resSample = 1.0f;
+
+ // Resonance decay speed factor
+ float resAmpDecayFactor = Tables::getInstance().resAmpDecayFactor[resonance >> 2];
+
+ // Now relWavePos counts from the middle of first cosine
+ relWavePos = wavePos;
+
+ // negative segments
+ if (!(relWavePos < (cosineLen + hLen))) {
+ resSample = -resSample;
+ relWavePos -= cosineLen + hLen;
+
+ // From the digital captures, the decaying speed of the resonance sine is found a bit different for the positive and the negative segments
+ resAmpDecayFactor += 0.25f;
+ }
+
+ // Resonance sine WG
+ resSample *= sinf(FLOAT_PI * relWavePos / cosineLen);
+
+ // Resonance sine amp
+ float resAmpFadeLog2 = -0.125f * resAmpDecayFactor * (relWavePos / cosineLen); // seems to be exact
+ float resAmpFade = EXP2F(resAmpFadeLog2);
+
+ // Now relWavePos set negative to the left from center of any cosine
+ relWavePos = wavePos;
+
+ // negative segment
+ if (!(wavePos < (waveLen - 0.5f * cosineLen))) {
+ relWavePos -= waveLen;
+ } else
+
+ // positive segment
+ if (!(wavePos < (hLen + 0.5f * cosineLen))) {
+ relWavePos -= cosineLen + hLen;
+ }
+
+ // To ensure the output wave has no breaks, two different windows are appied to the beginning and the ending of the resonance sine segment
+ if (relWavePos < 0.5f * cosineLen) {
+ float syncSine = sinf(FLOAT_PI * relWavePos / cosineLen);
+ if (relWavePos < 0.0f) {
+ // The window is synchronous square sine here
+ resAmpFade *= syncSine * syncSine;
+ } else {
+ // The window is synchronous sine here
+ resAmpFade *= syncSine;
+ }
+ }
+
+ sample += resSample * resAmp * resAmpFade;
+ }
+
+ // sawtooth waves
+ if (sawtoothWaveform) {
+ sample *= cosf(FLOAT_2PI * wavePos / waveLen);
+ }
+
+ wavePos++;
+
+ // wavePos isn't supposed to be > waveLen
+ if (wavePos > waveLen) {
+ wavePos -= waveLen;
+ }
+ }
+
+ // Multiply sample with current TVA value
+ sample *= amp;
+ return sample;
+}
+
+void LA32WaveGenerator::deactivate() {
+ active = false;
+}
+
+bool LA32WaveGenerator::isActive() const {
+ return active;
+}
+
+bool LA32WaveGenerator::isPCMWave() const {
+ return pcmWaveAddress != NULL;
+}
+
+void LA32PartialPair::init(const bool ringModulated, const bool mixed) {
+ this->ringModulated = ringModulated;
+ this->mixed = mixed;
+ masterOutputSample = 0.0f;
+ slaveOutputSample = 0.0f;
+}
+
+void LA32PartialPair::initSynth(const PairType useMaster, const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance) {
+ if (useMaster == MASTER) {
+ master.initSynth(sawtoothWaveform, pulseWidth, resonance);
+ } else {
+ slave.initSynth(sawtoothWaveform, pulseWidth, resonance);
+ }
+}
+
+void LA32PartialPair::initPCM(const PairType useMaster, const Bit16s *pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped) {
+ if (useMaster == MASTER) {
+ master.initPCM(pcmWaveAddress, pcmWaveLength, pcmWaveLooped, true);
+ } else {
+ slave.initPCM(pcmWaveAddress, pcmWaveLength, pcmWaveLooped, !ringModulated);
+ }
+}
+
+void LA32PartialPair::generateNextSample(const PairType useMaster, const Bit32u amp, const Bit16u pitch, const Bit32u cutoff) {
+ if (useMaster == MASTER) {
+ masterOutputSample = master.generateNextSample(amp, pitch, cutoff);
+ } else {
+ slaveOutputSample = slave.generateNextSample(amp, pitch, cutoff);
+ }
+}
+
+Bit16s LA32PartialPair::nextOutSample() {
+ float outputSample;
+ if (ringModulated) {
+ float ringModulatedSample = masterOutputSample * slaveOutputSample;
+ outputSample = mixed ? masterOutputSample + ringModulatedSample : ringModulatedSample;
+ } else {
+ outputSample = masterOutputSample + slaveOutputSample;
+ }
+ return Bit16s(outputSample * 8192.0f);
+}
+
+void LA32PartialPair::deactivate(const PairType useMaster) {
+ if (useMaster == MASTER) {
+ master.deactivate();
+ masterOutputSample = 0.0f;
+ } else {
+ slave.deactivate();
+ slaveOutputSample = 0.0f;
+ }
+}
+
+bool LA32PartialPair::isActive(const PairType useMaster) const {
+ return useMaster == MASTER ? master.isActive() : slave.isActive();
+}
+
+}
+
+#endif // #if MT32EMU_ACCURATE_WG == 1
diff --git a/audio/softsynth/mt32/LegacyWaveGenerator.h b/audio/softsynth/mt32/LegacyWaveGenerator.h
new file mode 100644
index 0000000000..81c1b9c713
--- /dev/null
+++ b/audio/softsynth/mt32/LegacyWaveGenerator.h
@@ -0,0 +1,146 @@
+/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
+ * Copyright (C) 2011, 2012, 2013 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/>.
+ */
+
+#if MT32EMU_ACCURATE_WG == 1
+
+#ifndef MT32EMU_LA32_WAVE_GENERATOR_H
+#define MT32EMU_LA32_WAVE_GENERATOR_H
+
+namespace MT32Emu {
+
+/**
+ * LA32WaveGenerator is aimed to represent the exact model of LA32 wave generator.
+ * The output square wave is created by adding high / low linear segments in-between
+ * the rising and falling cosine segments. Basically, it’s very similar to the phase distortion synthesis.
+ * Behaviour of a true resonance filter is emulated by adding decaying sine wave.
+ * The beginning and the ending of the resonant sine is multiplied by a cosine window.
+ * To synthesise sawtooth waves, the resulting square wave is multiplied by synchronous cosine wave.
+ */
+class LA32WaveGenerator {
+ //***************************************************************************
+ // The local copy of partial parameters below
+ //***************************************************************************
+
+ bool active;
+
+ // True means the resulting square wave is to be multiplied by the synchronous cosine
+ bool sawtoothWaveform;
+
+ // Logarithmic amp of the wave generator
+ Bit32u amp;
+
+ // Logarithmic frequency of the resulting wave
+ Bit16u pitch;
+
+ // Values in range [1..31]
+ // Value 1 correspong to the minimum resonance
+ Bit8u resonance;
+
+ // Processed value in range [0..255]
+ // Values in range [0..128] have no effect and the resulting wave remains symmetrical
+ // Value 255 corresponds to the maximum possible asymmetric of the resulting wave
+ Bit8u pulseWidth;
+
+ // Composed of the base cutoff in range [78..178] left-shifted by 18 bits and the TVF modifier
+ Bit32u cutoffVal;
+
+ // Logarithmic PCM sample start address
+ const Bit16s *pcmWaveAddress;
+
+ // Logarithmic PCM sample length
+ Bit32u pcmWaveLength;
+
+ // true for looped logarithmic PCM samples
+ bool pcmWaveLooped;
+
+ // false for slave PCM partials in the structures with the ring modulation
+ bool pcmWaveInterpolated;
+
+ //***************************************************************************
+ // Internal variables below
+ //***************************************************************************
+
+ float wavePos;
+ float lastFreq;
+ float pcmPosition;
+
+ float getPCMSample(unsigned int position);
+
+public:
+ // Initialise the WG engine for generation of synth partial samples and set up the invariant parameters
+ void initSynth(const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance);
+
+ // Initialise the WG engine for generation of PCM partial samples and set up the invariant parameters
+ void initPCM(const Bit16s * const pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped, const bool pcmWaveInterpolated);
+
+ // Update parameters with respect to TVP, TVA and TVF, and generate next sample
+ float generateNextSample(const Bit32u amp, const Bit16u pitch, const Bit32u cutoff);
+
+ // Deactivate the WG engine
+ void deactivate();
+
+ // Return active state of the WG engine
+ bool isActive() const;
+
+ // Return true if the WG engine generates PCM wave samples
+ bool isPCMWave() const;
+};
+
+// LA32PartialPair contains a structure of two partials being mixed / ring modulated
+class LA32PartialPair {
+ LA32WaveGenerator master;
+ LA32WaveGenerator slave;
+ bool ringModulated;
+ bool mixed;
+ float masterOutputSample;
+ float slaveOutputSample;
+
+public:
+ enum PairType {
+ MASTER,
+ SLAVE
+ };
+
+ // ringModulated should be set to false for the structures with mixing or stereo output
+ // ringModulated should be set to true for the structures with ring modulation
+ // mixed is used for the structures with ring modulation and indicates whether the master partial output is mixed to the ring modulator output
+ void init(const bool ringModulated, const bool mixed);
+
+ // Initialise the WG engine for generation of synth partial samples and set up the invariant parameters
+ void initSynth(const PairType master, const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance);
+
+ // Initialise the WG engine for generation of PCM partial samples and set up the invariant parameters
+ void initPCM(const PairType master, const Bit16s * const pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped);
+
+ // Update parameters with respect to TVP, TVA and TVF, and generate next sample
+ void generateNextSample(const PairType master, const Bit32u amp, const Bit16u pitch, const Bit32u cutoff);
+
+ // Perform mixing / ring modulation and return the result
+ Bit16s nextOutSample();
+
+ // Deactivate the WG engine
+ void deactivate(const PairType master);
+
+ // Return active state of the WG engine
+ bool isActive(const PairType master) const;
+};
+
+} // namespace MT32Emu
+
+#endif // #ifndef MT32EMU_LA32_WAVE_GENERATOR_H
+
+#endif // #if MT32EMU_ACCURATE_WG == 1
diff --git a/audio/softsynth/mt32/Partial.cpp b/audio/softsynth/mt32/Partial.cpp
index 0ef2f6242f..a0aec90ec4 100644
--- a/audio/softsynth/mt32/Partial.cpp
+++ b/audio/softsynth/mt32/Partial.cpp
@@ -35,7 +35,12 @@ static const float PAN_NUMERATOR_MASTER[] = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
static const float PAN_NUMERATOR_SLAVE[] = {0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 7.0f, 7.0f, 7.0f, 7.0f, 7.0f, 7.0f, 7.0f};
Partial::Partial(Synth *useSynth, int useDebugPartialNum) :
- synth(useSynth), debugPartialNum(useDebugPartialNum), sampleNum(0), tva(new TVA(this, &ampRamp)), tvp(new TVP(this)), tvf(new TVF(this, &cutoffModifierRamp)) {
+ synth(useSynth), debugPartialNum(useDebugPartialNum), sampleNum(0) {
+ // Initialisation of tva, tvp and tvf uses 'this' pointer
+ // and thus should not be in the initializer list to avoid a compiler warning
+ tva = new TVA(this, &ampRamp);
+ tvp = new TVP(this);
+ tvf = new TVF(this, &cutoffModifierRamp);
ownerPart = -1;
poly = NULL;
pair = NULL;
@@ -81,27 +86,23 @@ void Partial::deactivate() {
ownerPart = -1;
if (poly != NULL) {
poly->partialDeactivated(this);
- if (pair != NULL) {
- 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);
#endif
-}
-
-// DEPRECATED: This should probably go away eventually, it's currently only used as a kludge to protect our old assumptions that
-// rhythm part notes were always played as key MIDDLEC.
-int Partial::getKey() const {
- if (poly == NULL) {
- return -1;
- } else if (ownerPart == 8) {
- // FIXME: Hack, should go away after new pitch stuff is committed (and possibly some TVF changes)
- return MIDDLEC;
+ if (isRingModulatingSlave()) {
+ pair->la32Pair.deactivate(LA32PartialPair::SLAVE);
} else {
- return poly->getKey();
+ la32Pair.deactivate(LA32PartialPair::MASTER);
+ if (hasRingModulatingSlave()) {
+ pair->deactivate();
+ pair = NULL;
+ }
+ }
+ if (pair != NULL) {
+ pair->pair = NULL;
}
}
@@ -164,8 +165,6 @@ void Partial::startPartial(const Part *part, Poly *usePoly, const PatchCache *us
pcmWave = &synth->pcmWaves[pcmNum];
} else {
pcmWave = NULL;
- wavePos = 0.0f;
- lastFreq = 0.0;
}
// CONFIRMED: pulseWidthVal calculation is based on information from Mok
@@ -176,26 +175,65 @@ void Partial::startPartial(const Part *part, Poly *usePoly, const PatchCache *us
pulseWidthVal = 255;
}
- pcmPosition = 0.0f;
pair = pairPartial;
alreadyOutputed = false;
tva->reset(part, patchCache->partialParam, rhythmTemp);
tvp->reset(part, patchCache->partialParam);
tvf->reset(patchCache->partialParam, tvp->getBasePitch());
+
+ LA32PartialPair::PairType pairType;
+ LA32PartialPair *useLA32Pair;
+ if (isRingModulatingSlave()) {
+ pairType = LA32PartialPair::SLAVE;
+ useLA32Pair = &pair->la32Pair;
+ } else {
+ pairType = LA32PartialPair::MASTER;
+ la32Pair.init(hasRingModulatingSlave(), mixType == 1);
+ useLA32Pair = &la32Pair;
+ }
+ if (isPCM()) {
+ useLA32Pair->initPCM(pairType, &synth->pcmROMData[pcmWave->addr], pcmWave->len, pcmWave->loop);
+ } else {
+ useLA32Pair->initSynth(pairType, (patchCache->waveform & 1) != 0, pulseWidthVal, patchCache->srcPartial.tvf.resonance + 1);
+ }
+ if (!hasRingModulatingSlave()) {
+ la32Pair.deactivate(LA32PartialPair::SLAVE);
+ }
+ // Temporary integration hack
+ stereoVolume.leftVol /= 8192.0f;
+ stereoVolume.rightVol /= 8192.0f;
}
-float Partial::getPCMSample(unsigned int position) {
- if (position >= pcmWave->len) {
- if (!pcmWave->loop) {
- return 0;
- }
- position = position % pcmWave->len;
+Bit32u Partial::getAmpValue() {
+ // 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)
+ // when sustaining at levels 156 - 255 with no modifiers.
+ // (2) Tested with a special square wave partial (internal capture ID tva5) at TVA envelope levels 155-255.
+ // This gives deltas between -1 and 0 compared to the real output. Note that this special partial only produces
+ // 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.
+ // TODO: The tests above were performed using the float model, to be refined
+ Bit32u ampRampVal = 67117056 - ampRamp.nextValue();
+ if (ampRamp.checkInterrupt()) {
+ tva->handleInterrupt();
}
- return synth->pcmROMData[pcmWave->addr + position];
+ return ampRampVal;
}
-unsigned long Partial::generateSamples(float *partialBuf, unsigned long length) {
- const Tables &tables = Tables::getInstance();
+Bit32u Partial::getCutoffValue() {
+ if (isPCM()) {
+ return 0;
+ }
+ Bit32u cutoffModifierRampVal = cutoffModifierRamp.nextValue();
+ if (cutoffModifierRamp.checkInterrupt()) {
+ tvf->handleInterrupt();
+ }
+ return (tvf->getBaseCutoff() << 18) + cutoffModifierRampVal;
+}
+
+unsigned long Partial::generateSamples(Bit16s *partialBuf, unsigned long length) {
if (!isActive() || alreadyOutputed) {
return 0;
}
@@ -203,310 +241,31 @@ unsigned long Partial::generateSamples(float *partialBuf, unsigned long length)
synth->printDebug("[Partial %d] *** ERROR: poly is NULL at Partial::generateSamples()!", debugPartialNum);
return 0;
}
-
alreadyOutputed = true;
- // Generate samples
-
for (sampleNum = 0; sampleNum < length; sampleNum++) {
- float sample = 0;
- Bit32u ampRampVal = ampRamp.nextValue();
- if (ampRamp.checkInterrupt()) {
- tva->handleInterrupt();
- }
- if (!tva->isPlaying()) {
+ if (!tva->isPlaying() || !la32Pair.isActive(LA32PartialPair::MASTER)) {
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)
- // when sustaining at levels 156 - 255 with no modifiers.
- // (2) Tested with a special square wave partial (internal capture ID tva5) at TVA envelope levels 155-255.
- // This gives deltas between -1 and 0 compared to the real output. Note that this special partial only produces
- // 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;
-
- static const float freqFactor = EXP2F(-16.0f) * SAMPLE_RATE;
- float freq = EXP2I(pitch) * freqFactor;
-#endif
-
- if (patchCache->PCMPartial) {
- // Render PCM waveform
- int len = pcmWave->len;
- int intPCMPosition = (int)pcmPosition;
- if (intPCMPosition >= len && !pcmWave->loop) {
- // We're now past the end of a non-looping PCM waveform so it's time to die.
- deactivate();
- break;
- }
- Bit32u pcmAddr = pcmWave->addr;
- float positionDelta = freq * 2048.0f / SAMPLE_RATE;
-
- // Linear interpolation
- float firstSample = synth->pcmROMData[pcmAddr + 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) {
- newPCMPosition = fmod(newPCMPosition, (float)pcmWave->len);
- }
- pcmPosition = newPCMPosition;
- } else {
- // Render synthesised waveform
- wavePos *= lastFreq / freq;
- lastFreq = freq;
-
- Bit32u cutoffModifierRampVal = cutoffModifierRamp.nextValue();
- if (cutoffModifierRamp.checkInterrupt()) {
- tvf->handleInterrupt();
- }
- float cutoffModifier = cutoffModifierRampVal / 262144.0f;
-
- // res corresponds to a value set in an LA32 register
- Bit8u res = patchCache->srcPartial.tvf.resonance + 1;
-
- 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.
- // The 240 cutoffVal limit was determined via sample analysis (internal Munt capture IDs: glop3, glop4).
- // More research is needed to be sure that this is correct, however.
- float cutoffVal = tvf->getBaseCutoff() + cutoffModifier;
- if (cutoffVal > 240.0f) {
- cutoffVal = 240.0f;
- }
-
- // Wave length in samples
- float waveLen = SAMPLE_RATE / freq;
-
- // Init cosineLen
- float cosineLen = 0.5f * waveLen;
- if (cutoffVal > 128.0f) {
-#if MT32EMU_ACCURATE_WG == 1
- cosineLen *= EXP2F((cutoffVal - 128.0f) / -16.0f); // found from sample analysis
-#else
- static const float cosineLenFactor = EXP2F(128.0f / -16.0f);
- cosineLen *= EXP2I(Bit32u((256.0f - cutoffVal) * 256.0f)) * cosineLenFactor;
-#endif
- }
-
- // Start playing in center of first cosine segment
- // relWavePos is shifted by a half of cosineLen
- float relWavePos = wavePos + 0.5f * cosineLen;
- if (relWavePos > waveLen) {
- relWavePos -= waveLen;
- }
-
- // Ratio of positive segment to wave length
- float pulseLen = 0.5f;
- if (pulseWidthVal > 128) {
- // pulseLen = EXP2F((64 - pulseWidthVal) / 64);
- static const float pulseLenFactor = EXP2F(-192 / 64);
- pulseLen = EXP2I((256 - pulseWidthVal) << 6) * pulseLenFactor;
- }
- pulseLen *= waveLen;
-
- float hLen = pulseLen - cosineLen;
-
- // Ignore pulsewidths too high for given freq
- if (hLen < 0.0f) {
- hLen = 0.0f;
- }
-
- // Ignore pulsewidths too high for given freq and cutoff
- float lLen = waveLen - hLen - 2 * cosineLen;
- if (lLen < 0.0f) {
- lLen = 0.0f;
- }
-
- // Correct resAmp for cutoff in range 50..66
- if ((cutoffVal >= 128.0f) && (cutoffVal < 144.0f)) {
-#if MT32EMU_ACCURATE_WG == 1
- resAmp *= sinf(FLOAT_PI * (cutoffVal - 128.0f) / 32.0f);
-#else
- resAmp *= tables.sinf10[Bit32u(64 * (cutoffVal - 128.0f))];
-#endif
- }
-
- // Produce filtered square wave with 2 cosine waves on slopes
-
- // 1st cosine segment
- if (relWavePos < cosineLen) {
-#if MT32EMU_ACCURATE_WG == 1
- sample = -cosf(FLOAT_PI * relWavePos / cosineLen);
-#else
- sample = -tables.sinf10[Bit32u(2048.0f * relWavePos / cosineLen) + 1024];
-#endif
- } else
-
- // high linear segment
- if (relWavePos < (cosineLen + hLen)) {
- sample = 1.f;
- } else
-
- // 2nd cosine segment
- if (relWavePos < (2 * cosineLen + hLen)) {
-#if MT32EMU_ACCURATE_WG == 1
- sample = cosf(FLOAT_PI * (relWavePos - (cosineLen + hLen)) / cosineLen);
-#else
- sample = tables.sinf10[Bit32u(2048.0f * (relWavePos - (cosineLen + hLen)) / cosineLen) + 1024];
-#endif
- } else {
-
- // low linear segment
- sample = -1.f;
- }
-
- if (cutoffVal < 128.0f) {
-
- // Attenuate samples below cutoff 50
- // Found by sample analysis
-#if MT32EMU_ACCURATE_WG == 1
- sample *= EXP2F(-0.125f * (128.0f - cutoffVal));
-#else
- static const float cutoffAttenuationFactor = EXP2F(-0.125f * 128.0f);
- sample *= EXP2I(Bit32u(512.0f * cutoffVal)) * cutoffAttenuationFactor;
-#endif
- } else {
-
- // Add resonance sine. Effective for cutoff > 50 only
- float resSample = 1.0f;
-
- // Now relWavePos counts from the middle of first cosine
- relWavePos = wavePos;
-
- // negative segments
- if (!(relWavePos < (cosineLen + hLen))) {
- resSample = -resSample;
- relWavePos -= cosineLen + hLen;
+ la32Pair.generateNextSample(LA32PartialPair::MASTER, getAmpValue(), tvp->nextPitch(), getCutoffValue());
+ if (hasRingModulatingSlave()) {
+ la32Pair.generateNextSample(LA32PartialPair::SLAVE, pair->getAmpValue(), pair->tvp->nextPitch(), pair->getCutoffValue());
+ if (!pair->tva->isPlaying() || !la32Pair.isActive(LA32PartialPair::SLAVE)) {
+ pair->deactivate();
+ if (mixType == 2) {
+ deactivate();
+ break;
}
-
- // Resonance sine WG
-#if MT32EMU_ACCURATE_WG == 1
- resSample *= sinf(FLOAT_PI * relWavePos / cosineLen);
-#else
- resSample *= tables.sinf10[Bit32u(2048.0f * relWavePos / cosineLen) & 4095];
-#endif
-
- // Resonance sine amp
- 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;
-
- // negative segment
- if (!(wavePos < (waveLen - 0.5f * cosineLen))) {
- relWavePos -= waveLen;
- } else
-
- // positive segment
- if (!(wavePos < (hLen + 0.5f * cosineLen))) {
- relWavePos -= cosineLen + hLen;
- }
-
- // Fading to zero while within cosine segments to avoid jumps in the wave
- // Sample analysis suggests that this window is very close to cosine
- if (relWavePos < 0.5f * cosineLen) {
-#if MT32EMU_ACCURATE_WG == 1
- resAmpFade *= 0.5f * (1.0f - cosf(FLOAT_PI * relWavePos / (0.5f * cosineLen)));
-#else
- resAmpFade *= 0.5f * (1.0f + tables.sinf10[Bit32s(2048.0f * relWavePos / (0.5f * cosineLen)) + 3072]);
-#endif
- }
-
- sample += resSample * resAmp * resAmpFade;
- }
-
- // sawtooth waves
- if ((patchCache->waveform & 1) != 0) {
-#if MT32EMU_ACCURATE_WG == 1
- sample *= cosf(FLOAT_2PI * wavePos / waveLen);
-#else
- sample *= tables.sinf10[(Bit32u(4096.0f * wavePos / waveLen) & 4095) + 1024];
-#endif
- }
-
- wavePos++;
-
- // wavePos isn't supposed to be > waveLen
- if (wavePos > waveLen) {
- wavePos -= waveLen;
}
}
-
- // Multiply sample with current TVA value
- sample *= amp;
- *partialBuf++ = sample;
+ *partialBuf++ = la32Pair.nextOutSample();
}
unsigned long renderedSamples = sampleNum;
sampleNum = 0;
return renderedSamples;
}
-float *Partial::mixBuffersRingMix(float *buf1, float *buf2, unsigned long len) {
- if (buf1 == NULL) {
- return NULL;
- }
- if (buf2 == NULL) {
- return buf1;
- }
-
- while (len--) {
- // FIXME: At this point we have no idea whether this is remotely correct...
- *buf1 = *buf1 * *buf2 + *buf1;
- buf1++;
- buf2++;
- }
- return buf1;
-}
-
-float *Partial::mixBuffersRing(float *buf1, float *buf2, unsigned long len) {
- if (buf1 == NULL) {
- return NULL;
- }
- if (buf2 == NULL) {
- return NULL;
- }
-
- while (len--) {
- // FIXME: At this point we have no idea whether this is remotely correct...
- *buf1 = *buf1 * *buf2;
- buf1++;
- buf2++;
- }
- return buf1;
-}
-
bool Partial::hasRingModulatingSlave() const {
return pair != NULL && structurePosition == 0 && (mixType == 1 || mixType == 2);
}
@@ -538,53 +297,14 @@ bool Partial::produceOutput(float *leftBuf, float *rightBuf, unsigned long lengt
synth->printDebug("[Partial %d] *** ERROR: poly is NULL at Partial::produceOutput()!", debugPartialNum);
return false;
}
-
- float *partialBuf = &myBuffer[0];
- unsigned long numGenerated = generateSamples(partialBuf, length);
- if (mixType == 1 || mixType == 2) {
- float *pairBuf;
- unsigned long pairNumGenerated;
- if (pair == NULL) {
- pairBuf = NULL;
- pairNumGenerated = 0;
- } else {
- pairBuf = &pair->myBuffer[0];
- pairNumGenerated = pair->generateSamples(pairBuf, numGenerated);
- // pair will have been set to NULL if it deactivated within generateSamples()
- if (pair != NULL) {
- if (!isActive()) {
- pair->deactivate();
- pair = NULL;
- } else if (!pair->isActive()) {
- pair = NULL;
- }
- }
- }
- if (pairNumGenerated > 0) {
- if (mixType == 1) {
- mixBuffersRingMix(partialBuf, pairBuf, pairNumGenerated);
- } else {
- mixBuffersRing(partialBuf, pairBuf, pairNumGenerated);
- }
- }
- if (numGenerated > pairNumGenerated) {
- if (mixType == 2) {
- numGenerated = pairNumGenerated;
- deactivate();
- }
- }
- }
-
- for (unsigned int i = 0; i < numGenerated; i++) {
- *leftBuf++ = partialBuf[i] * stereoVolume.leftVol;
- }
+ unsigned long numGenerated = generateSamples(myBuffer, length);
for (unsigned int i = 0; i < numGenerated; i++) {
- *rightBuf++ = partialBuf[i] * stereoVolume.rightVol;
+ *leftBuf++ = myBuffer[i] * stereoVolume.leftVol;
+ *rightBuf++ = myBuffer[i] * stereoVolume.rightVol;
}
- while (numGenerated < length) {
+ for (; numGenerated < length; numGenerated++) {
*leftBuf++ = 0.0f;
*rightBuf++ = 0.0f;
- numGenerated++;
}
return true;
}
diff --git a/audio/softsynth/mt32/Partial.h b/audio/softsynth/mt32/Partial.h
index 9166bebffd..21b1bfe376 100644
--- a/audio/softsynth/mt32/Partial.h
+++ b/audio/softsynth/mt32/Partial.h
@@ -44,12 +44,7 @@ private:
int structurePosition; // 0 or 1 of a structure pair
StereoVolume stereoVolume;
- // Distance in (possibly fractional) samples from the start of the current pulse
- float wavePos;
-
- float lastFreq;
-
- float myBuffer[MAX_SAMPLES_PER_RUN];
+ Bit16s myBuffer[MAX_SAMPLES_PER_RUN];
// Only used for PCM partials
int pcmNum;
@@ -60,17 +55,16 @@ private:
// Range: 0-255
int pulseWidthVal;
- float pcmPosition;
-
Poly *poly;
LA32Ramp ampRamp;
LA32Ramp cutoffModifierRamp;
- float *mixBuffersRingMix(float *buf1, float *buf2, unsigned long len);
- float *mixBuffersRing(float *buf1, float *buf2, unsigned long len);
+ // TODO: This should be owned by PartialPair
+ LA32PartialPair la32Pair;
- float getPCMSample(unsigned int position);
+ Bit32u getAmpValue();
+ Bit32u getCutoffValue();
public:
const PatchCache *patchCache;
@@ -90,7 +84,6 @@ public:
unsigned long debugGetSampleNum() const;
int getOwnerPart() const;
- int getKey() const;
const Poly *getPoly() const;
bool isActive() const;
void activate(int part);
@@ -111,7 +104,7 @@ public:
bool produceOutput(float *leftBuf, float *rightBuf, unsigned long length);
// This function writes mono sample output to the provided buffer, and returns the number of samples written
- unsigned long generateSamples(float *partialBuf, unsigned long length);
+ unsigned long generateSamples(Bit16s *partialBuf, unsigned long length);
};
}
diff --git a/audio/softsynth/mt32/Synth.cpp b/audio/softsynth/mt32/Synth.cpp
index 6099b98039..b7af992b99 100644
--- a/audio/softsynth/mt32/Synth.cpp
+++ b/audio/softsynth/mt32/Synth.cpp
@@ -345,17 +345,7 @@ bool Synth::loadPCMROM(const ROMImage &pcmROMImage) {
}
log = log | (short)(bit << (15 - u));
}
- bool negative = log < 0;
- log &= 0x7FFF;
-
- // CONFIRMED from sample analysis to be 99.99%+ accurate with current TVA multiplier
- float lin = EXP2F((32787 - log) / -2048.0f);
-
- if (negative) {
- lin = -lin;
- }
-
- pcmROMData[i] = lin;
+ pcmROMData[i] = log;
}
delete[] buffer;
@@ -462,7 +452,7 @@ bool Synth::open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage) {
// 1MB PCM ROM for CM-32L, LAPC-I, CM-64, CM-500
// Note that the size below is given in samples (16-bit), not bytes
pcmROMSize = controlROMMap->pcmCount == 256 ? 512 * 1024 : 256 * 1024;
- pcmROMData = new float[pcmROMSize];
+ pcmROMData = new Bit16s[pcmROMSize];
#if MT32EMU_MONITOR_INIT
printDebug("Loading PCM ROM");
diff --git a/audio/softsynth/mt32/Synth.h b/audio/softsynth/mt32/Synth.h
index 70e0064ad2..56e88e6156 100644
--- a/audio/softsynth/mt32/Synth.h
+++ b/audio/softsynth/mt32/Synth.h
@@ -283,7 +283,7 @@ private:
const ControlROMMap *controlROMMap;
Bit8u controlROMData[CONTROL_ROM_SIZE];
- float *pcmROMData;
+ Bit16s *pcmROMData;
size_t pcmROMSize; // This is in 16-bit samples, therefore half the number of bytes in the ROM
Bit8s chantable[32];
diff --git a/audio/softsynth/mt32/Tables.cpp b/audio/softsynth/mt32/Tables.cpp
index c7e9437250..ffd784a4b4 100644
--- a/audio/softsynth/mt32/Tables.cpp
+++ b/audio/softsynth/mt32/Tables.cpp
@@ -72,24 +72,25 @@ Tables::Tables() {
//synth->printDebug("%d: %d", i, pulseWidth100To255[i]);
}
- // 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);
+ // The LA32 chip contains an exponent table inside. The table contains 12-bit integer values.
+ // The actual table size is 512 rows. The 9 higher bits of the fractional part of the argument are used as a lookup address.
+ // To improve the precision of computations, the lower bits are supposed to be used for interpolation as the LA32 chip also
+ // contains another 512-row table with inverted differences between the main table values.
+ for (int i = 0; i < 512; i++) {
+ exp9[i] = Bit16u(8191.5f - EXP2F(13.0f + ~i / 512.0f));
}
- // found from sample analysis
- resAmpFadeFactor[7] = 1.0f / 8.0f;
- resAmpFadeFactor[6] = 2.0f / 8.0f;
- resAmpFadeFactor[5] = 3.0f / 8.0f;
- resAmpFadeFactor[4] = 5.0f / 8.0f;
- resAmpFadeFactor[3] = 8.0f / 8.0f;
- resAmpFadeFactor[2] = 12.0f / 8.0f;
- resAmpFadeFactor[1] = 16.0f / 8.0f;
- resAmpFadeFactor[0] = 31.0f / 8.0f;
-
- for (int i = 0; i < 5120; i++) {
- sinf10[i] = sin(FLOAT_PI * i / 2048.0f);
+ // There is a logarithmic sine table inside the LA32 chip. The table contains 13-bit integer values.
+ for (int i = 1; i < 512; i++) {
+ logsin9[i] = Bit16u(0.5f - LOG2F(sinf((i + 0.5f) / 1024.0f * FLOAT_PI)) * 1024.0f);
}
+
+ // The very first value is clamped to the maximum possible 13-bit integer
+ logsin9[0] = 8191;
+
+ // found from sample analysis
+ static const Bit8u resAmpDecayFactorTable[] = {31, 16, 12, 8, 5, 3, 2, 1};
+ resAmpDecayFactor = resAmpDecayFactorTable;
}
}
diff --git a/audio/softsynth/mt32/Tables.h b/audio/softsynth/mt32/Tables.h
index 728d7ecada..8b4580df0e 100644
--- a/audio/softsynth/mt32/Tables.h
+++ b/audio/softsynth/mt32/Tables.h
@@ -56,9 +56,10 @@ public:
// CONFIRMED:
Bit8u pulseWidth100To255[101];
- float exp2[4096];
- float resAmpFadeFactor[8];
- float sinf10[5120];
+ Bit16u exp9[512];
+ Bit16u logsin9[512];
+
+ const Bit8u *resAmpDecayFactor;
};
}
diff --git a/audio/softsynth/mt32/mmath.h b/audio/softsynth/mt32/mmath.h
index f67433b48a..ee6a652c36 100644
--- a/audio/softsynth/mt32/mmath.h
+++ b/audio/softsynth/mt32/mmath.h
@@ -52,10 +52,6 @@ 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 ed6b0d33ed..e7afdfd2b4 100644
--- a/audio/softsynth/mt32/module.mk
+++ b/audio/softsynth/mt32/module.mk
@@ -6,6 +6,8 @@ MODULE_OBJS := \
DelayReverb.o \
FreeverbModel.o \
LA32Ramp.o \
+ LA32WaveGenerator.o \
+ LegacyWaveGenerator.o \
Part.o \
Partial.o \
PartialManager.o \
diff --git a/audio/softsynth/mt32/mt32emu.h b/audio/softsynth/mt32/mt32emu.h
index 6812f7bc8a..971a0886d5 100644
--- a/audio/softsynth/mt32/mt32emu.h
+++ b/audio/softsynth/mt32/mt32emu.h
@@ -59,18 +59,6 @@
#define MT32EMU_MONITOR_TVA 0
#define MT32EMU_MONITOR_TVF 0
-// 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
-
// Configuration
// The maximum number of partials playing simultaneously
#define MT32EMU_MAX_PARTIALS 32
@@ -87,6 +75,10 @@
// 2: Use Bit-perfect Boss Reverb model aka BReverb (for developers, not much practical use)
#define MT32EMU_USE_REVERBMODEL 1
+// 0: Use refined wave generator based on logarithmic fixed-point computations and LUTs
+// 1: Use legacy accurate wave generator based on float computations
+#define MT32EMU_ACCURATE_WG 0
+
namespace MT32Emu
{
// The higher this number, the more memory will be used, but the more samples can be processed in one run -
@@ -110,6 +102,8 @@ const unsigned int MAX_PRERENDER_SAMPLES = 1024;
#include "Tables.h"
#include "Poly.h"
#include "LA32Ramp.h"
+#include "LA32WaveGenerator.h"
+#include "LegacyWaveGenerator.h"
#include "TVA.h"
#include "TVP.h"
#include "TVF.h"