diff --git a/src/deluge/dsp/compressor/compressor.cpp b/src/deluge/dsp/compressor/compressor.cpp index a61ce5ef17..6460a9bd89 100644 --- a/src/deluge/dsp/compressor/compressor.cpp +++ b/src/deluge/dsp/compressor/compressor.cpp @@ -25,9 +25,7 @@ Compressor::Compressor() { status = EnvelopeStage::OFF; lastValue = 2147483647; - envelopeOffset = ONE_Q31; pos = 0; - follower = false; attack = getParamFromUserValue(Param::Static::COMPRESSOR_ATTACK, 7); release = getParamFromUserValue(Param::Static::COMPRESSOR_RELEASE, 28); pendingHitStrength = 0; @@ -82,14 +80,11 @@ void Compressor::registerHitRetrospectively(int32_t strength, uint32_t numSample // If we're still in the release stage... if (numSamplesSinceRelease < releaseStageLengthInSamples) { pos = numSamplesSinceRelease * alteredRelease; - envelopeHeight = ONE_Q31 - envelopeOffset; - envelopeOffset = ONE_Q31; status = EnvelopeStage::RELEASE; } // Or if we're past the release stage... else { - envelopeOffset = ONE_Q31; status = EnvelopeStage::OFF; } } @@ -129,65 +124,35 @@ int32_t Compressor::getActualReleaseRate() { int32_t Compressor::render(uint16_t numSamples, int32_t shapeValue) { // Initial hit detected... - if (pendingHitStrength != 0 || follower) { + if (pendingHitStrength != 0) { int32_t newOffset = ONE_Q31 - pendingHitStrength; pendingHitStrength = 0; - //envelope offset is the value we're attack/decaying to + // Only actually do anything if this hit is going to cause a bigger dip than we're already currently experiencing if (newOffset < lastValue) { envelopeOffset = newOffset; // If attack is all the way down, jump directly to release stage if (attack == attackRateTable[0] << 2) { - envelopeHeight = ONE_Q31 - envelopeOffset; - envelopeOffset = ONE_Q31; - pos = 0; - status = EnvelopeStage::RELEASE; - } - else { - if (!follower || status == EnvelopeStage::HOLD) { - status = EnvelopeStage::ATTACK; - pos = 0; - } - else if (status != EnvelopeStage::ATTACK) { - status = EnvelopeStage::HOLD; - } - - envelopeHeight = lastValue - envelopeOffset; - } - } - //or if we're working in follower mode, in which case we want to start releasing whenever the current hit strength is below the envelope level - else if (follower && newOffset > envelopeOffset) { - envelopeOffset = newOffset; - envelopeHeight = newOffset - lastValue; - if (status == EnvelopeStage::HOLD) { - pos = 0; - status = EnvelopeStage::RELEASE; - } - else if (status != EnvelopeStage::RELEASE) { - status = EnvelopeStage::HOLD; + goto prepareForRelease; } + + status = EnvelopeStage::ATTACK; + envelopeHeight = lastValue - envelopeOffset; + pos = 0; } } + if (status == EnvelopeStage::ATTACK) { pos += numSamples * getActualAttackRate(); if (pos >= 8388608) { - //if we're in follower mode then we just hold the value - if (!follower) { - envelopeHeight = ONE_Q31 - envelopeOffset; - envelopeOffset = ONE_Q31; prepareForRelease: - pos = 0; - status = EnvelopeStage::RELEASE; - - goto doRelease; - } - else { - status = EnvelopeStage::HOLD; - goto doOff; - } + pos = 0; + status = EnvelopeStage::RELEASE; + envelopeHeight = ONE_Q31 - envelopeOffset; + goto doRelease; } //lastValue = (multiply_32x32_rshift32(envelopeHeight, decayTable4[pos >> 13]) << 1) + envelopeOffset; // Goes down quickly at first. Bad //lastValue = (multiply_32x32_rshift32(envelopeHeight, 2147483647 - (pos << 8)) << 1) + envelopeOffset; // Straight line @@ -226,17 +191,15 @@ int32_t Compressor::render(uint16_t numSamples, int32_t shapeValue) { preValue = straightness * (pos >> 8) + (getDecay8(8388608 - pos, 23) >> 16) * curvedness16; } - lastValue = envelopeOffset - envelopeHeight + (multiply_32x32_rshift32(preValue, envelopeHeight) << 1); + lastValue = ONE_Q31 - envelopeHeight + (multiply_32x32_rshift32(preValue, envelopeHeight) << 1); //lastValue = 2147483647 - (multiply_32x32_rshift32(decayTable8[pos >> 13], envelopeHeight) << 1); // Upside down exponential curve //lastValue = 2147483647 - (((int64_t)((sineWave[((pos >> 14) + 256) & 1023] >> 1) + 1073741824) * (int64_t)envelopeHeight) >> 31); // Sine wave. Not great //lastValue = (multiply_32x32_rshift32(pos * (pos >> 15), envelopeHeight) << 1); // Parabola. Doesn't "punch". } - - else { // Off or hold - + else { // Off doOff: - lastValue = envelopeOffset; + lastValue = ONE_Q31; } return lastValue - ONE_Q31; diff --git a/src/deluge/dsp/compressor/compressor.h b/src/deluge/dsp/compressor/compressor.h index b9c81d9309..c48cc96040 100644 --- a/src/deluge/dsp/compressor/compressor.h +++ b/src/deluge/dsp/compressor/compressor.h @@ -26,7 +26,7 @@ class Compressor { public: Compressor(); void cloneFrom(Compressor* other); - bool follower; + EnvelopeStage status; uint32_t pos; int32_t lastValue; diff --git a/src/deluge/dsp/master_compressor/master_compressor.cpp b/src/deluge/dsp/master_compressor/master_compressor.cpp index ee15577fa4..dd88713ece 100644 --- a/src/deluge/dsp/master_compressor/master_compressor.cpp +++ b/src/deluge/dsp/master_compressor/master_compressor.cpp @@ -23,20 +23,14 @@ #include "processing/engines/audio_engine.h" #include "util/fast_fixed_math.h" MasterCompressor::MasterCompressor() { - //compressor.setAttack((float)attack / 100.0); - attack = attackRateTable[2] << 2; - release = releaseRateTable[5] << 2; - //compressor.setRelease((float)release / 100.0); - //compressor.setThresh((float)threshold / 100.0); - //compressor.setRatio(1.0 / ((float)ratio / 100.0)); - shape = getParamFromUserValue(Param::Unpatched::COMPRESSOR_SHAPE, 1); + //an appropriate range is 0-50*one q 15 - threshold = ONE_Q31; - rawThreshold = 0; - follower = true; - //this is about a 1:1 ratio - ratio = ONE_Q31 >> 1; - syncLevel = SyncLevel::SYNC_LEVEL_NONE; + + thresholdKnobPos = 0; + + //this is 2:1 + ratioKnobPos = 0; + currentVolumeL = 0; currentVolumeR = 0; //auto make up gain @@ -54,44 +48,32 @@ void MasterCompressor::updateER() { 134217728, cableToLinearParamShortcut(currentSong->paramManager.getUnpatchedParamSet()->getValue( Param::Unpatched::GlobalEffectable::VOLUME))) >> 1; - songVolume = std::log(volumePostFX); + songVolume = std::log(volumePostFX) - 2; } else { songVolume = 16; } - threshdb = songVolume * (threshold / ONE_Q31f); + threshdb = songVolume * threshold; //16 is about the level of a single synth voice at max volume - er = std::max((songVolume - threshdb - 2) * (float(ratio) / ONE_Q31), 0); + er = std::max((songVolume - threshdb - 2) * ratio, 0); } void MasterCompressor::render(StereoSample* buffer, uint16_t numSamples, q31_t volAdjustL, q31_t volAdjustR) { - ratio = (rawRatio >> 1) + (3 << 28); - threshold = ONE_Q31 - rawThreshold; + //we update this every time since we won't know if the song volume changed updateER(); - q31_t over = std::max(0, (meanVolume - threshdb) / 21) * ONE_Q31; - q31_t clip = std::max(0, (meanVolume - 18) / 21) * ONE_Q31; - //add some extra reduction if we're into clipping + float over = std::max(0, (rms - threshdb)); - if (over > 0) { - registerHit(over); - } - out = Compressor::render(numSamples, shape); - out = (multiply_32x32_rshift32(out, ratio) << 1) - (multiply_32x32_rshift32(clip, ONE_Q31 - ratio)); - //out = multiply_32x32_rshift32(out, ratio) << 1; + float out = runEnvelope(over, numSamples); - //21 is the max internal volume (i.e. one_q31) - //min ratio is 8 up to 1 (i.e. infinity/brick wall, 1 db reduction per db over) - //base is arbitrary for scale, important part is the shape - //this will be negative - float reduction = 21 * (out / ONE_Q31f); + float reduction = -out * ratio; //this is the most gain available without overflow float dbGain = 0.85 + er + reduction; float gain = exp((dbGain)); gain = std::min(gain, 31); - lastGain = dbGain; + float finalVolumeL = gain * float(volAdjustL >> 9); float finalVolumeR = gain * float(volAdjustR >> 9); @@ -114,7 +96,17 @@ void MasterCompressor::render(StereoSample* buffer, uint16_t numSamples, q31_t v //9 converts to dB, quadrupled for display range since a 30db reduction is basically killing the signal gainReduction = std::clamp(-(reduction)*9 * 4, 0, 127); //calc compression for next round (feedback compressor) - meanVolume = calc_rms(buffer, numSamples); + rms = calc_rms(buffer, numSamples); +} + +float MasterCompressor::runEnvelope(float in, float numSamples) { + if (in > state) { + state = in + exp(a_ * numSamples) * (state - in); + } + else { + state = in + exp(r_ * numSamples) * (state - in); + } + return state; } //output range is 0-21 (2^31) @@ -126,28 +118,28 @@ float MasterCompressor::calc_rms(StereoSample* buffer, uint16_t numSamples) { q31_t offset = 0; //to remove dc offset float lastMean = mean; do { - q31_t s = std::abs(thisSample->l) + std::abs(thisSample->r); + q31_t s = std::max(std::abs(thisSample->l), std::abs(thisSample->r)); sum += multiply_32x32_rshift32(s, s) << 1; - offset += thisSample->l; } while (++thisSample != bufferEnd); float ns = float(numSamples); - float rms = ONE_Q31 * sqrt((float(sum) / ONE_Q31f) / ns); - float dc = std::abs(offset) / ns; + mean = (float(sum) / ONE_Q31f) / ns; //warning this is not good math but it's pretty close and way cheaper than doing it properly - mean = rms - dc / 1.4f; - mean = std::max(mean, 1.0f); + //good math would use a long FIR, this is a one pole IIR instead + //the more samples we have, the more weight we put on the current mean to avoid response slowing down + //at high cpu loads + mean = (mean * ns + lastMean) / (1 + ns); + float rms = ONE_Q31 * sqrt(mean); - float logmean = std::log((mean + lastMean) / 2); + float logmean = std::log(std::max(rms, 1.0f)); return logmean; } void MasterCompressor::setup(int32_t a, int32_t r, int32_t t, int32_t rat) { - attack = a; - release = r; - rawThreshold = t; - rawRatio = rat; - updateER(); + setAttack(a); + setRelease(r); + setThreshold(t); + setRatio(rat); } diff --git a/src/deluge/dsp/master_compressor/master_compressor.h b/src/deluge/dsp/master_compressor/master_compressor.h index 4045021e68..ee4aef7299 100644 --- a/src/deluge/dsp/master_compressor/master_compressor.h +++ b/src/deluge/dsp/master_compressor/master_compressor.h @@ -27,29 +27,72 @@ #include #include -class MasterCompressor : public Compressor { +class MasterCompressor { public: MasterCompressor(); void setup(int32_t attack, int32_t release, int32_t threshold, int32_t ratio); void render(StereoSample* buffer, uint16_t numSamples, q31_t volAdjustL, q31_t volAdjustR); + float runEnvelope(float in, float numSamples); + //attack/release in range 0 to 2^31 + inline q31_t getAttack() { return attackKnobPos; } + inline int32_t getAttackMS() { return attackMS; } + int32_t setAttack(q31_t attack) { + //this exp will be between 1 and 7ish, half the knob range is about 2.5 + attackMS = 0.5 + (exp(2 * float(attack) / ONE_Q31f) - 1) * 10; + a_ = (-1000.0 / 44100) / attackMS; + attackKnobPos = attack; + return attackMS; + }; + inline q31_t getRelease() { return releaseKnobPos; } + inline int32_t getReleaseMS() { return releaseMS; } + int32_t setRelease(q31_t release) { + //this exp will be between 1 and 7ish, half the knob range is about 2.5 + releaseMS = 50 + (exp(2 * float(release) / ONE_Q31f) - 1) * 50; + r_ = (-1000.0 / 44100) / releaseMS; + releaseKnobPos = release; + return releaseMS; + }; + q31_t getThreshold() { return thresholdKnobPos; } + void setThreshold(q31_t t) { + thresholdKnobPos = t; + threshold = 1 - 0.8 * (float(thresholdKnobPos) / ONE_Q31f); + updateER(); + } + q31_t getRatio() { return ratioKnobPos; } + q31_t setRatio(q31_t rat) { + ratioKnobPos = rat; + ratio = 0.5 + (float(ratioKnobPos) / ONE_Q31f) / 2; + return 1 / (1 - ratio); + } + void updateER(); float calc_rms(StereoSample* buffer, uint16_t numSamples); uint8_t gainReduction; - bool dither; - q31_t rawRatio; - q31_t rawThreshold; - q31_t threshold; - q31_t shape; - q31_t ratio; - q31_t out; - q31_t over; - q31_t currentVolumeL; - q31_t currentVolumeR; - float meanVolume; - float mean; - float lastGain; +private: + //parameters in use + float a_; + float r_; + float ratio; float er; float threshdb; + float threshold; + + //state + float state; + q31_t currentVolumeL; + q31_t currentVolumeR; + float rms; + float mean; + + //for display + float attackMS; + float releaseMS; + + //raw knob positions + q31_t thresholdKnobPos; + q31_t ratioKnobPos; + q31_t attackKnobPos; + q31_t releaseKnobPos; }; diff --git a/src/deluge/model/global_effectable/global_effectable.cpp b/src/deluge/model/global_effectable/global_effectable.cpp index 053cccc3c9..5f088471b6 100644 --- a/src/deluge/model/global_effectable/global_effectable.cpp +++ b/src/deluge/model/global_effectable/global_effectable.cpp @@ -295,36 +295,34 @@ bool GlobalEffectable::modEncoderButtonAction(uint8_t whichModEncoder, bool on, } int32_t GlobalEffectable::getKnobPosForNonExistentParam(int32_t whichModEncoder, ModelStackWithAutoParam* modelStack) { - int displayLevel = -64; + int current = 0; if (*getModKnobMode() == 4) { - int current; //this is only reachable in comp editing mode, otherwise it's an existent param if (whichModEncoder == 1) { //sidechain (threshold) - current = AudioEngine::mastercompressor.rawThreshold >> 24; - displayLevel = current; + current = (AudioEngine::mastercompressor.getThreshold() >> 24); } else if (whichModEncoder == 0) { switch (currentCompParam) { case CompParam::RATIO: - current = AudioEngine::mastercompressor.rawRatio >> 24; - displayLevel = current; + current = (AudioEngine::mastercompressor.getRatio() >> 24); + break; case CompParam::ATTACK: - current = getLookupIndexFromValue(AudioEngine::mastercompressor.attack >> 2, attackRateTable, 50); - displayLevel = (current * 128) / 50; + current = AudioEngine::mastercompressor.getAttack() >> 24; + break; case CompParam::RELEASE: - current = getLookupIndexFromValue(AudioEngine::mastercompressor.release >> 1, releaseRateTable, 50); - displayLevel = (current * 128) / 50; + current = AudioEngine::mastercompressor.getRelease() >> 24; + break; } } } - return displayLevel - 64; + return current - 64; } ActionResult GlobalEffectable::modEncoderActionForNonExistentParam(int32_t offset, int32_t whichModEncoder, @@ -335,48 +333,44 @@ ActionResult GlobalEffectable::modEncoderActionForNonExistentParam(int32_t offse int ledLevel; //this is only reachable in comp editing mode, otherwise it's an existent param if (whichModEncoder == 1) { //sidechain (threshold) - current = (AudioEngine::mastercompressor.rawThreshold >> 24) - 64; + current = (AudioEngine::mastercompressor.getThreshold() >> 24) - 64; current += offset; current = std::clamp(current, -64, 64); ledLevel = (64 + current); displayLevel = ((ledLevel)*kMaxMenuValue) / 128; - AudioEngine::mastercompressor.rawThreshold = lshiftAndSaturate<24>(current + 64); + AudioEngine::mastercompressor.setThreshold(lshiftAndSaturate<24>(current + 64)); indicator_leds::setKnobIndicatorLevel(1, ledLevel); } else if (whichModEncoder == 0) { switch (currentCompParam) { case CompParam::RATIO: - current = (AudioEngine::mastercompressor.rawRatio >> 24) - 64; + current = (AudioEngine::mastercompressor.getRatio() >> 24) - 64; current += offset; //this range is ratio of 2 to 20 current = std::clamp(current, -64, 64); ledLevel = (64 + current); displayLevel = ((ledLevel)*kMaxMenuValue) / 128; - AudioEngine::mastercompressor.rawRatio = lshiftAndSaturate<24>(current + 64); + displayLevel = AudioEngine::mastercompressor.setRatio(lshiftAndSaturate<24>(current + 64)); break; case CompParam::ATTACK: - current = getLookupIndexFromValue(AudioEngine::mastercompressor.attack >> 2, attackRateTable, 50); + current = (AudioEngine::mastercompressor.getAttack() >> 24) - 64; current += offset; - current = std::clamp(current, 1, 50); - displayLevel = current; - ledLevel = (displayLevel * 128) / 50; + current = std::clamp(current, -64, 64); + ledLevel = (64 + current); - AudioEngine::mastercompressor.attack = attackRateTable[current] << 2; + displayLevel = AudioEngine::mastercompressor.setAttack(lshiftAndSaturate<24>(current + 64)); break; case CompParam::RELEASE: - - current = getLookupIndexFromValue(AudioEngine::mastercompressor.release, releaseRateTable, 50); + current = (AudioEngine::mastercompressor.getRelease() >> 24) - 64; current += offset; - current = std::clamp(current, 0, 50); - displayLevel = current; - ledLevel = (displayLevel * 128) / 50; - - AudioEngine::mastercompressor.release = releaseRateTable[current]; + current = std::clamp(current, -64, 64); + ledLevel = (64 + current); + displayLevel = AudioEngine::mastercompressor.setRelease(lshiftAndSaturate<24>(current + 64)); break; } indicator_leds::setKnobIndicatorLevel(0, ledLevel); diff --git a/src/deluge/model/song/song.cpp b/src/deluge/model/song/song.cpp index c183f80ca6..96614013ca 100644 --- a/src/deluge/model/song/song.cpp +++ b/src/deluge/model/song/song.cpp @@ -136,10 +136,10 @@ Song::Song() : backedUpParamManagers(sizeof(BackedUpParamManager)) { reverbCompressorShape = -601295438; reverbCompressorSync = SYNC_LEVEL_8TH; - masterCompressorAttack = attackRateTable[2] << 2; - masterCompressorRelease = releaseRateTable[5] << 2; + masterCompressorAttack = 10 << 24; + masterCompressorRelease = 20 << 24; masterCompressorThresh = 0; - masterCompressorRatio = ONE_Q31 >> 1; + masterCompressorRatio = 0; AudioEngine::mastercompressor.gainReduction = 0.0; dirPath.set("SONGS"); @@ -1122,11 +1122,11 @@ void Song::writeToFile() { storageManager.writeClosingTag("reverb"); - storageManager.writeOpeningTagBeginning("masterCompressor"); - int32_t attack = AudioEngine::mastercompressor.attack; - int32_t release = AudioEngine::mastercompressor.release; - int32_t thresh = AudioEngine::mastercompressor.rawThreshold; - int32_t ratio = AudioEngine::mastercompressor.rawRatio; + storageManager.writeOpeningTagBeginning("songCompressor"); + int32_t attack = AudioEngine::mastercompressor.getAttack(); + int32_t release = AudioEngine::mastercompressor.getRelease(); + int32_t thresh = AudioEngine::mastercompressor.getThreshold(); + int32_t ratio = AudioEngine::mastercompressor.getRatio(); storageManager.writeAttribute("attack", attack); storageManager.writeAttribute("release", release); @@ -1476,7 +1476,7 @@ int32_t Song::readFromFile() { storageManager.exitTag("affectEntire"); } - else if (!strcmp(tagName, "masterCompressor")) { + else if (!strcmp(tagName, "songCompressor")) { while (*(tagName = storageManager.readNextTagOrAttributeName())) { if (!strcmp(tagName, "attack")) { //ms masterCompressorAttack = storageManager.readTagOrAttributeValueInt(); @@ -1498,7 +1498,7 @@ int32_t Song::readFromFile() { storageManager.exitTag(tagName); } } - storageManager.exitTag("masterCompressor"); + storageManager.exitTag("songCompressor"); } else if (!strcmp(tagName, "modeNotes")) { diff --git a/src/deluge/processing/engines/audio_engine.cpp b/src/deluge/processing/engines/audio_engine.cpp index 81ac68b0a1..f02d093d20 100644 --- a/src/deluge/processing/engines/audio_engine.cpp +++ b/src/deluge/processing/engines/audio_engine.cpp @@ -402,13 +402,15 @@ void routine() { #define MINSAMPLES 16 smoothedSamples = numSamples; - - if (numSamplesLastTime < numSamples) { - Debug::print("rendered "); - Debug::print(numSamplesLastTime); - Debug::print(" samples but output "); - Debug::println(numSamples); - } + //this is sometimes good for debugging but super spammy + //audiolog doesn't work because the render that notices the failure + //is one after the render with the problem + // if (numSamplesLastTime < numSamples) { + // Debug::println("rendered "); + // Debug::println(numSamplesLastTime); + // Debug::println(" samples but output "); + // Debug::println(numSamples); + // } // Consider direness and culling - before increasing the number of samples int32_t numSamplesLimit = 40; //storageManager.devVarC; diff --git a/src/deluge/util/fixedpoint.h b/src/deluge/util/fixedpoint.h index 0182f5dadf..762d11a078 100644 --- a/src/deluge/util/fixedpoint.h +++ b/src/deluge/util/fixedpoint.h @@ -22,7 +22,7 @@ typedef int32_t q31_t; #define ONE_Q31 2147483647 -#define ONE_Q31f float(ONE_Q31) +#define ONE_Q31f 2147483647.0 #define ONE_Q15 65536 #define NEGATIVE_ONE_Q31 -2147483648 #define ONE_OVER_SQRT2_Q31 1518500250