mirror of
https://github.com/libretro/scummvm.git
synced 2025-02-03 17:33:05 +00:00
50d79c5f26
This uses upstream commit 939cc986d9ffd044f8c6149361127ad5d94e430f Closes gh-1091
382 lines
14 KiB
C++
382 lines
14 KiB
C++
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
|
* Copyright (C) 2011-2017 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 "internals.h"
|
|
|
|
#include "TVA.h"
|
|
#include "Part.h"
|
|
#include "Partial.h"
|
|
#include "Poly.h"
|
|
#include "Synth.h"
|
|
#include "Tables.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), phase(TVA_PHASE_DEAD) {
|
|
}
|
|
|
|
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,%x,%s%x,%d", partial->debugGetSampleNum(), partial->debugGetPartialNum(), newTarget, (newIncrement & 0x80) ? "-" : "+", (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, bool hasRingModQuirk) {
|
|
int amp = 155;
|
|
|
|
if (!(hasRingModQuirk ? partial->isRingModulatingNoMix() : 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;
|
|
}
|
|
|
|
static 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;
|
|
|
|
const Tables *tables = &Tables::getInstance();
|
|
|
|
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(), partial->getSynth()->controlROMFeatures->quirkRingModulationNoMix);
|
|
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
|
|
const Tables *tables = &Tables::getInstance();
|
|
int newTarget = calcBasicAmp(tables, partial, system, partialParam, patchTemp, rhythmTemp, biasAmpSubtraction, veloAmpSubtraction, part->getExpression(), partial->getSynth()->controlROMFeatures->quirkRingModulationNoMix);
|
|
newTarget += partialParam->tva.envLevel[3];
|
|
|
|
// Although we're in TVA_PHASE_SUSTAIN at this point, we cannot be sure that there is no active ramp at the moment.
|
|
// In case the channel volume or the expression changes frequently, the previously started ramp may still be in progress.
|
|
// Real hardware units ignore this possibility and rely on the assumption that the target is the current amp.
|
|
// This is OK in most situations but when the ramp that is currently in progress needs to change direction
|
|
// due to a volume/expression update, this leads to a jump in the amp that is audible as an unpleasant click.
|
|
// To avoid that, we compare the newTarget with the the actual current ramp value and correct the direction if necessary.
|
|
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;
|
|
bool descending = targetDelta < 0;
|
|
if (!descending) {
|
|
newIncrement = tables->envLogarithmicTime[Bit8u(targetDelta)] - 2;
|
|
} else {
|
|
newIncrement = (tables->envLogarithmicTime[Bit8u(-targetDelta)] - 2) | 0x80;
|
|
}
|
|
if (part->getSynth()->isNiceAmpRampEnabled() && (descending != ampRamp->isBelowCurrent(newTarget))) {
|
|
newIncrement ^= 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() {
|
|
const Tables *tables = &Tables::getInstance();
|
|
|
|
if (phase >= TVA_PHASE_DEAD || !playing) {
|
|
partial->getSynth()->printDebug("TVA::nextPhase(): Shouldn't have got here with phase %d, playing=%s", phase, playing ? "true" : "false");
|
|
return;
|
|
}
|
|
int newPhase = phase + 1;
|
|
|
|
if (newPhase == TVA_PHASE_DEAD) {
|
|
end(newPhase);
|
|
return;
|
|
}
|
|
|
|
bool allLevelsZeroFromNowOn = false;
|
|
if (!partial->getSynth()->controlROMFeatures->quirkTVAZeroEnvLevels && 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 = 0; // Initialised to please compilers
|
|
int envPointIndex = phase;
|
|
|
|
if (!allLevelsZeroFromNowOn) {
|
|
newTarget = calcBasicAmp(tables, partial, system, partialParam, patchTemp, rhythmTemp, biasAmpSubtraction, veloAmpSubtraction, part->getExpression(), partial->getSynth()->controlROMFeatures->quirkRingModulationNoMix);
|
|
|
|
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);
|
|
}
|
|
|
|
} // namespace MT32Emu
|