/* 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 .
*/
/*
* 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
#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;
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());
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());
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() {
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 (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;
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);
}
}