diff options
Diffstat (limited to 'audio/softsynth/mt32/TVA.cpp')
-rw-r--r-- | audio/softsynth/mt32/TVA.cpp | 365 |
1 files changed, 365 insertions, 0 deletions
diff --git a/audio/softsynth/mt32/TVA.cpp b/audio/softsynth/mt32/TVA.cpp new file mode 100644 index 0000000000..c3be6db591 --- /dev/null +++ b/audio/softsynth/mt32/TVA.cpp @@ -0,0 +1,365 @@ +/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher + * Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/* + * This class emulates the calculations performed by the 8095 microcontroller in order to configure the LA-32's amplitude ramp for a single partial at each stage of its TVA envelope. + * Unless we introduced bugs, it should be pretty much 100% accurate according to Mok's specifications. +*/ +//#include <cmath> + +#include "mt32emu.h" +#include "mmath.h" + +namespace MT32Emu { + +// CONFIRMED: Matches a table in ROM - haven't got around to coming up with a formula for it yet. +static Bit8u biasLevelToAmpSubtractionCoeff[13] = {255, 187, 137, 100, 74, 54, 40, 29, 21, 15, 10, 5, 0}; + +TVA::TVA(const Partial *usePartial, LA32Ramp *useAmpRamp) : + partial(usePartial), ampRamp(useAmpRamp), system_(&usePartial->getSynth()->mt32ram.system) { +} + +void TVA::startRamp(Bit8u newTarget, Bit8u newIncrement, int newPhase) { + target = newTarget; + phase = newPhase; + ampRamp->startRamp(newTarget, newIncrement); +#if MT32EMU_MONITOR_TVA >= 1 + partial->getSynth()->printDebug("[+%lu] [Partial %d] TVA,ramp,%d,%d,%d,%d", partial->debugGetSampleNum(), partial->debugGetPartialNum(), (newIncrement & 0x80) ? -1 : 1, (newIncrement & 0x7F), newPhase); +#endif +} + +void TVA::end(int newPhase) { + phase = newPhase; + playing = false; +#if MT32EMU_MONITOR_TVA >= 1 + partial->getSynth()->printDebug("[+%lu] [Partial %d] TVA,end,%d", partial->debugGetSampleNum(), partial->debugGetPartialNum(), newPhase); +#endif +} + +static int multBias(Bit8u biasLevel, int bias) { + return (bias * biasLevelToAmpSubtractionCoeff[biasLevel]) >> 5; +} + +static int calcBiasAmpSubtraction(Bit8u biasPoint, Bit8u biasLevel, int key) { + if ((biasPoint & 0x40) == 0) { + int bias = biasPoint + 33 - key; + if (bias > 0) { + return multBias(biasLevel, bias); + } + } else { + int bias = biasPoint - 31 - key; + if (bias < 0) { + bias = -bias; + return multBias(biasLevel, bias); + } + } + return 0; +} + +static int calcBiasAmpSubtractions(const TimbreParam::PartialParam *partialParam, int key) { + int biasAmpSubtraction1 = calcBiasAmpSubtraction(partialParam->tva.biasPoint1, partialParam->tva.biasLevel1, key); + if (biasAmpSubtraction1 > 255) { + return 255; + } + int biasAmpSubtraction2 = calcBiasAmpSubtraction(partialParam->tva.biasPoint2, partialParam->tva.biasLevel2, key); + if (biasAmpSubtraction2 > 255) { + return 255; + } + int biasAmpSubtraction = biasAmpSubtraction1 + biasAmpSubtraction2; + if (biasAmpSubtraction > 255) { + return 255; + } + return biasAmpSubtraction; +} + +static int calcVeloAmpSubtraction(Bit8u veloSensitivity, unsigned int velocity) { + // FIXME:KG: Better variable names + int velocityMult = veloSensitivity - 50; + int absVelocityMult = velocityMult < 0 ? -velocityMult : velocityMult; + velocityMult = (signed)((unsigned)(velocityMult * ((signed)velocity - 64)) << 2); + return absVelocityMult - (velocityMult >> 8); // PORTABILITY NOTE: Assumes arithmetic shift +} + +static int calcBasicAmp(const Tables *tables, const Partial *partial, const MemParams::System *system_, const TimbreParam::PartialParam *partialParam, const MemParams::PatchTemp *patchTemp, const MemParams::RhythmTemp *rhythmTemp, int biasAmpSubtraction, int veloAmpSubtraction, Bit8u expression) { + int amp = 155; + + if (!partial->isRingModulatingSlave()) { + amp -= tables->masterVolToAmpSubtraction[system_->masterVol]; + if (amp < 0) { + return 0; + } + amp -= tables->levelToAmpSubtraction[patchTemp->outputLevel]; + if (amp < 0) { + return 0; + } + amp -= tables->levelToAmpSubtraction[expression]; + if (amp < 0) { + return 0; + } + if (rhythmTemp != NULL) { + amp -= tables->levelToAmpSubtraction[rhythmTemp->outputLevel]; + if (amp < 0) { + return 0; + } + } + } + amp -= biasAmpSubtraction; + if (amp < 0) { + return 0; + } + amp -= tables->levelToAmpSubtraction[partialParam->tva.level]; + if (amp < 0) { + return 0; + } + amp -= veloAmpSubtraction; + if (amp < 0) { + return 0; + } + if (amp > 155) { + amp = 155; + } + amp -= partialParam->tvf.resonance >> 1; + if (amp < 0) { + return 0; + } + return amp; +} + +int calcKeyTimeSubtraction(Bit8u envTimeKeyfollow, int key) { + if (envTimeKeyfollow == 0) { + return 0; + } + return (key - 60) >> (5 - envTimeKeyfollow); // PORTABILITY NOTE: Assumes arithmetic shift +} + +void TVA::reset(const Part *newPart, const TimbreParam::PartialParam *newPartialParam, const MemParams::RhythmTemp *newRhythmTemp) { + part = newPart; + partialParam = newPartialParam; + patchTemp = newPart->getPatchTemp(); + rhythmTemp = newRhythmTemp; + + playing = true; + + Tables *tables = &partial->getSynth()->tables; + + int key = partial->getPoly()->getKey(); + int velocity = partial->getPoly()->getVelocity(); + + keyTimeSubtraction = calcKeyTimeSubtraction(partialParam->tva.envTimeKeyfollow, key); + + biasAmpSubtraction = calcBiasAmpSubtractions(partialParam, key); + veloAmpSubtraction = calcVeloAmpSubtraction(partialParam->tva.veloSensitivity, velocity); + + int newTarget = calcBasicAmp(tables, partial, system_, partialParam, patchTemp, newRhythmTemp, biasAmpSubtraction, veloAmpSubtraction, part->getExpression()); + int newPhase; + if (partialParam->tva.envTime[0] == 0) { + // Initially go to the TVA_PHASE_ATTACK target amp, and spend the next phase going from there to the TVA_PHASE_2 target amp + // Note that this means that velocity never affects time for this partial. + newTarget += partialParam->tva.envLevel[0]; + newPhase = TVA_PHASE_ATTACK; // The first target used in nextPhase() will be TVA_PHASE_2 + } else { + // Initially go to the base amp determined by TVA level, part volume, etc., and spend the next phase going from there to the full TVA_PHASE_ATTACK target amp. + newPhase = TVA_PHASE_BASIC; // The first target used in nextPhase() will be TVA_PHASE_ATTACK + } + + ampRamp->reset();//currentAmp = 0; + + // "Go downward as quickly as possible". + // Since the current value is 0, the LA32Ramp will notice that we're already at or below the target and trying to go downward, + // and therefore jump to the target immediately and raise an interrupt. + startRamp((Bit8u)newTarget, 0x80 | 127, newPhase); +} + +void TVA::startAbort() { + startRamp(64, 0x80 | 127, TVA_PHASE_RELEASE); +} + +void TVA::startDecay() { + if (phase >= TVA_PHASE_RELEASE) { + return; + } + Bit8u newIncrement; + if (partialParam->tva.envTime[4] == 0) { + newIncrement = 1; + } else { + newIncrement = -partialParam->tva.envTime[4]; + } + // The next time nextPhase() is called, it will think TVA_PHASE_RELEASE has finished and the partial will be aborted + startRamp(0, newIncrement, TVA_PHASE_RELEASE); +} + +void TVA::handleInterrupt() { + nextPhase(); +} + +void TVA::recalcSustain() { + // We get pinged periodically by the pitch code to recalculate our values when in sustain. + // This is done so that the TVA will respond to things like MIDI expression and volume changes while it's sustaining, which it otherwise wouldn't do. + + // The check for envLevel[3] == 0 strikes me as slightly dumb. FIXME: Explain why + if (phase != TVA_PHASE_SUSTAIN || partialParam->tva.envLevel[3] == 0) { + return; + } + // We're sustaining. Recalculate all the values + Tables *tables = &partial->getSynth()->tables; + int newTarget = calcBasicAmp(tables, partial, system_, partialParam, patchTemp, rhythmTemp, biasAmpSubtraction, veloAmpSubtraction, part->getExpression()); + newTarget += partialParam->tva.envLevel[3]; + // Since we're in TVA_PHASE_SUSTAIN at this point, we know that target has been reached and an interrupt fired, so we can rely on it being the current amp. + int targetDelta = newTarget - target; + + // Calculate an increment to get to the new amp value in a short, more or less consistent amount of time + Bit8u newIncrement; + if (targetDelta >= 0) { + newIncrement = tables->envLogarithmicTime[(Bit8u)targetDelta] - 2; + } else { + newIncrement = (tables->envLogarithmicTime[(Bit8u)-targetDelta] - 2) | 0x80; + } + // Configure so that once the transition's complete and nextPhase() is called, we'll just re-enter sustain phase (or decay phase, depending on parameters at the time). + startRamp(newTarget, newIncrement, TVA_PHASE_SUSTAIN - 1); +} + +bool TVA::isPlaying() const { + return playing; +} + +int TVA::getPhase() const { + return phase; +} + +void TVA::nextPhase() { + Tables *tables = &partial->getSynth()->tables; + + if (phase >= TVA_PHASE_DEAD || !playing) { + partial->getSynth()->printDebug("TVA::nextPhase(): Shouldn't have got here with phase %d, playing=%s", phase, playing ? "true" : "false"); + return; + } + int newPhase = phase + 1; + + if (newPhase == TVA_PHASE_DEAD) { + end(newPhase); + return; + } + + bool allLevelsZeroFromNowOn = false; + if (partialParam->tva.envLevel[3] == 0) { + if (newPhase == TVA_PHASE_4) { + allLevelsZeroFromNowOn = true; + } else if (partialParam->tva.envLevel[2] == 0) { + if (newPhase == TVA_PHASE_3) { + allLevelsZeroFromNowOn = true; + } else if (partialParam->tva.envLevel[1] == 0) { + if (newPhase == TVA_PHASE_2) { + allLevelsZeroFromNowOn = true; + } else if (partialParam->tva.envLevel[0] == 0) { + if (newPhase == TVA_PHASE_ATTACK) { // this line added, missing in ROM - FIXME: Add description of repercussions + allLevelsZeroFromNowOn = true; + } + } + } + } + } + + int newTarget; + int newIncrement; + int envPointIndex = phase; + + if (!allLevelsZeroFromNowOn) { + newTarget = calcBasicAmp(tables, partial, system_, partialParam, patchTemp, rhythmTemp, biasAmpSubtraction, veloAmpSubtraction, part->getExpression()); + + if (newPhase == TVA_PHASE_SUSTAIN || newPhase == TVA_PHASE_RELEASE) { + if (partialParam->tva.envLevel[3] == 0) { + end(newPhase); + return; + } + if (!partial->getPoly()->canSustain()) { + newPhase = TVA_PHASE_RELEASE; + newTarget = 0; + newIncrement = -partialParam->tva.envTime[4]; + if (newIncrement == 0) { + // We can't let the increment be 0, or there would be no emulated interrupt. + // So we do an "upward" increment, which should set the amp to 0 extremely quickly + // and cause an "interrupt" to bring us back to nextPhase(). + newIncrement = 1; + } + } else { + newTarget += partialParam->tva.envLevel[3]; + newIncrement = 0; + } + } else { + newTarget += partialParam->tva.envLevel[envPointIndex]; + } + } else { + newTarget = 0; + } + + if ((newPhase != TVA_PHASE_SUSTAIN && newPhase != TVA_PHASE_RELEASE) || allLevelsZeroFromNowOn) { + int envTimeSetting = partialParam->tva.envTime[envPointIndex]; + + if (newPhase == TVA_PHASE_ATTACK) { + envTimeSetting -= ((signed)partial->getPoly()->getVelocity() - 64) >> (6 - partialParam->tva.envTimeVeloSensitivity); // PORTABILITY NOTE: Assumes arithmetic shift + + if (envTimeSetting <= 0 && partialParam->tva.envTime[envPointIndex] != 0) { + envTimeSetting = 1; + } + } else { + envTimeSetting -= keyTimeSubtraction; + } + if (envTimeSetting > 0) { + int targetDelta = newTarget - target; + if (targetDelta <= 0) { + if (targetDelta == 0) { + // target and newTarget are the same. + // We can't have an increment of 0 or we wouldn't get an emulated interrupt. + // So instead make the target one less than it really should be and set targetDelta accordingly. + targetDelta = -1; + newTarget--; + if (newTarget < 0) { + // Oops, newTarget is less than zero now, so let's do it the other way: + // Make newTarget one more than it really should've been and set targetDelta accordingly. + // FIXME (apparent bug in real firmware): + // This means targetDelta will be positive just below here where it's inverted, and we'll end up using envLogarithmicTime[-1], and we'll be setting newIncrement to be descending later on, etc.. + targetDelta = 1; + newTarget = -newTarget; + } + } + targetDelta = -targetDelta; + newIncrement = tables->envLogarithmicTime[(Bit8u)targetDelta] - envTimeSetting; + if (newIncrement <= 0) { + newIncrement = 1; + } + newIncrement = newIncrement | 0x80; + } else { + // FIXME: The last 22 or so entries in this table are 128 - surely that fucks things up, since that ends up being -128 signed? + newIncrement = tables->envLogarithmicTime[(Bit8u)targetDelta] - envTimeSetting; + if (newIncrement <= 0) { + newIncrement = 1; + } + } + } else { + newIncrement = newTarget >= target ? (0x80 | 127) : 127; + } + + // FIXME: What's the point of this? It's checked or set to non-zero everywhere above + if (newIncrement == 0) { + newIncrement = 1; + } + } + + startRamp((Bit8u)newTarget, (Bit8u)newIncrement, newPhase); +} + +} |