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); }
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 (targetPhase != TVA_PHASE_SUSTAIN || partialParam->tva.envLevel[3] == 0) { return; } // We're sustaining. Recalculate all the values Tables *tables = &partial->getSynth()->tables; int newTargetAmp = calcBasicAmp(tables, partial, system, partialParam, patchTemp, rhythmTemp, biasAmpSubtraction, veloAmpSubtraction, part->getExpression()); newTargetAmp += partialParam->tva.envLevel[3]; // FIXME: This whole concept seems flawed. We don't really know what the *actual* amp value is, right? It may well not be la32TargetAmp yet (unless I've missed something). So we could end up going in the wrong direction... int ampDelta = newTargetAmp - la32TargetAmp; // Calculate an increment to get to the new amp value in a short, more or less consistent amount of time if (ampDelta >= 0) { setAmpIncrement(tables->envLogarithmicTime[(Bit8u)ampDelta] - 2); } else { setAmpIncrement((tables->envLogarithmicTime[(Bit8u)-ampDelta] - 2) | 0x80); } la32TargetAmp = newTargetAmp; // 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). targetPhase = TVA_PHASE_SUSTAIN - 1; }
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 newTargetAmp = calcBasicAmp(tables, partial, system, partialParam, patchTemp, newRhythmTemp, biasAmpSubtraction, veloAmpSubtraction, part->getExpression()); 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. newTargetAmp += partialParam->tva.envLevel[0]; targetPhase = TVA_PHASE_2 - 1; // 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. targetPhase = TVA_PHASE_ATTACK - 1; // The first target used in nextPhase() will be TVA_PHASE_ATTACK } // "Go downward as quickly as possible". // Since currentAmp is 0, nextAmp() will notice that we're already at or below the target and trying to go downward, // and therefore jump to the target immediately and call nextPhase(). setAmpIncrement(0x80 | 127); la32TargetAmp = (Bit8u)newTargetAmp; currentAmp = 0; }
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::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); }
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; // Initialised to please compilers 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 {
void TVA::nextPhase() { Tables *tables = &partial->getSynth()->tables; if (targetPhase >= TVA_PHASE_DEAD || !playing) { partial->getSynth()->printDebug("TVA::nextPhase(): Shouldn't have got here with targetPhase %d, playing=%s", targetPhase, playing ? "true" : "false"); return; } targetPhase++; if (targetPhase == TVA_PHASE_DEAD) { playing = false; return; } bool allLevelsZeroFromNowOn = false; if (partialParam->tva.envLevel[3] == 0) { if (targetPhase == TVA_PHASE_4) { allLevelsZeroFromNowOn = true; } else if (partialParam->tva.envLevel[2] == 0) { if (targetPhase == TVA_PHASE_3) { allLevelsZeroFromNowOn = true; } else if (partialParam->tva.envLevel[1] == 0) { if (targetPhase == TVA_PHASE_2) { allLevelsZeroFromNowOn = true; } else if (partialParam->tva.envLevel[0] == 0) { if (targetPhase == TVA_PHASE_ATTACK) { // this line added, missing in ROM - FIXME: Add description of repercussions allLevelsZeroFromNowOn = true; } } } } } int newTargetAmp; int newAmpIncrement; int envPointIndex = targetPhase - 1; if (!allLevelsZeroFromNowOn) { newTargetAmp = calcBasicAmp(tables, partial, system, partialParam, patchTemp, rhythmTemp, biasAmpSubtraction, veloAmpSubtraction, part->getExpression()); if (targetPhase == TVA_PHASE_SUSTAIN || targetPhase == TVA_PHASE_RELEASE) { if (partialParam->tva.envLevel[3] == 0) { playing = false; return; } if (!partial->getPoly()->canSustain()) { targetPhase = TVA_PHASE_RELEASE; newTargetAmp = 0; newAmpIncrement = -partialParam->tva.envTime[4]; if (newAmpIncrement >= 0) { // FIXME: This must mean newAmpIncrement was 0, and we're now making it 1, which makes us go in the wrong direction. WTF? newAmpIncrement++; } } else { newTargetAmp += partialParam->tva.envLevel[3]; newAmpIncrement = 0; } } else { newTargetAmp += partialParam->tva.envLevel[envPointIndex]; } } else { newTargetAmp = 0; } if ((targetPhase != TVA_PHASE_SUSTAIN && targetPhase != TVA_PHASE_RELEASE) || allLevelsZeroFromNowOn) { int envTimeSetting = partialParam->tva.envTime[envPointIndex]; if (targetPhase == 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 {