1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380
|
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2022 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, Bit8u partVolume, 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[partVolume];
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;
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, part->getVolume(), 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, part->getVolume(), 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 (partialParam->tva.envLevel[3] == 0) {
if (newPhase == TVA_PHASE_4) {
allLevelsZeroFromNowOn = true;
} else if (!partial->getSynth()->controlROMFeatures->quirkTVAZeroEnvLevels && 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, part->getVolume(), 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
|