diff options
author | Filippos Karapetis | 2013-03-02 14:09:39 +0200 |
---|---|---|
committer | Filippos Karapetis | 2013-03-02 14:09:39 +0200 |
commit | f4cc45d367442f850baecd814003ec09790a43b0 (patch) | |
tree | bfc904c0e8ced825749fa910696fae1d24fe10ca | |
parent | 8884c240fc4683975cd76802b600c019ebcb260c (diff) | |
download | scummvm-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.cpp | 8 | ||||
-rw-r--r-- | audio/softsynth/mt32/LA32WaveGenerator.cpp | 417 | ||||
-rw-r--r-- | audio/softsynth/mt32/LA32WaveGenerator.h | 246 | ||||
-rw-r--r-- | audio/softsynth/mt32/LegacyWaveGenerator.cpp | 347 | ||||
-rw-r--r-- | audio/softsynth/mt32/LegacyWaveGenerator.h | 146 | ||||
-rw-r--r-- | audio/softsynth/mt32/Partial.cpp | 438 | ||||
-rw-r--r-- | audio/softsynth/mt32/Partial.h | 19 | ||||
-rw-r--r-- | audio/softsynth/mt32/Synth.cpp | 14 | ||||
-rw-r--r-- | audio/softsynth/mt32/Synth.h | 2 | ||||
-rw-r--r-- | audio/softsynth/mt32/Tables.cpp | 31 | ||||
-rw-r--r-- | audio/softsynth/mt32/Tables.h | 7 | ||||
-rw-r--r-- | audio/softsynth/mt32/mmath.h | 4 | ||||
-rw-r--r-- | audio/softsynth/mt32/module.mk | 2 | ||||
-rw-r--r-- | audio/softsynth/mt32/mt32emu.h | 18 |
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, &Ramp)), 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, &Ramp); + 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" |