From 8179d418bf374083e620a73c1deae125009a342b Mon Sep 17 00:00:00 2001 From: m-m-adams Date: Thu, 9 Nov 2023 15:34:29 -0500 Subject: [PATCH] add a sidechain HPF --- src/definitions_cxx.hpp | 2 ++ .../master_compressor/master_compressor.cpp | 16 ++++++++----- .../dsp/master_compressor/master_compressor.h | 24 +++++++++++++++---- .../global_effectable/global_effectable.cpp | 15 +++++++++++- .../global_effectable_for_song.cpp | 4 ++-- src/deluge/model/song/song.cpp | 5 ++-- src/deluge/model/song/song.h | 1 + .../processing/engines/audio_engine.cpp | 12 +++++----- 8 files changed, 58 insertions(+), 21 deletions(-) diff --git a/src/definitions_cxx.hpp b/src/definitions_cxx.hpp index be2a4fcef4..1d91fdda0f 100644 --- a/src/definitions_cxx.hpp +++ b/src/definitions_cxx.hpp @@ -745,6 +745,8 @@ enum class CompParam { RATIO, ATTACK, RELEASE, + SIDECHAIN, + LAST, }; constexpr auto kNumModFXParams = util::to_underlying(ModFXParam::OFFSET) + 1; diff --git a/src/deluge/dsp/master_compressor/master_compressor.cpp b/src/deluge/dsp/master_compressor/master_compressor.cpp index dd88713ece..697cc9e9f6 100644 --- a/src/deluge/dsp/master_compressor/master_compressor.cpp +++ b/src/deluge/dsp/master_compressor/master_compressor.cpp @@ -27,7 +27,7 @@ MasterCompressor::MasterCompressor() { //an appropriate range is 0-50*one q 15 thresholdKnobPos = 0; - + sideChainKnobPos = ONE_Q31 >> 1; //this is 2:1 ratioKnobPos = 0; @@ -35,6 +35,7 @@ MasterCompressor::MasterCompressor() { currentVolumeR = 0; //auto make up gain updateER(); + setSidechain(sideChainKnobPos); } //16 is ln(1<<24) - 1, i.e. where we start clipping //since this applies to output @@ -55,7 +56,7 @@ void MasterCompressor::updateER() { } threshdb = songVolume * threshold; //16 is about the level of a single synth voice at max volume - er = std::max((songVolume - threshdb - 2) * ratio, 0); + er = std::max((songVolume - threshdb - 1) * ratio, 0); } void MasterCompressor::render(StereoSample* buffer, uint16_t numSamples, q31_t volAdjustL, q31_t volAdjustR) { @@ -94,7 +95,7 @@ void MasterCompressor::render(StereoSample* buffer, uint16_t numSamples, q31_t v } while (++thisSample != bufferEnd); //for LEDs //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); + gainReduction = std::clamp(-(reduction) * 9 * 4, 0, 127); //calc compression for next round (feedback compressor) rms = calc_rms(buffer, numSamples); } @@ -118,7 +119,9 @@ 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::max(std::abs(thisSample->l), std::abs(thisSample->r)); + q31_t l = thisSample->l - hpfL.doFilter(thisSample->l, a); + q31_t r = thisSample->r - hpfL.doFilter(thisSample->r, a); + q31_t s = std::max(std::abs(l), std::abs(r)); sum += multiply_32x32_rshift32(s, s) << 1; } while (++thisSample != bufferEnd); @@ -136,10 +139,11 @@ float MasterCompressor::calc_rms(StereoSample* buffer, uint16_t numSamples) { return logmean; } - -void MasterCompressor::setup(int32_t a, int32_t r, int32_t t, int32_t rat) { +//takes in knob positions in the range 0-ONE_Q31 +void MasterCompressor::setup(q31_t a, q31_t r, q31_t t, q31_t rat, q31_t fc) { setAttack(a); setRelease(r); setThreshold(t); setRatio(rat); + setSidechain(fc); } diff --git a/src/deluge/dsp/master_compressor/master_compressor.h b/src/deluge/dsp/master_compressor/master_compressor.h index ee4aef7299..e0a88538cb 100644 --- a/src/deluge/dsp/master_compressor/master_compressor.h +++ b/src/deluge/dsp/master_compressor/master_compressor.h @@ -19,18 +19,17 @@ #include "definitions_cxx.hpp" #include "dsp/compressor/compressor.h" +#include "dsp/filter/ladder_components.h" #include "util/functions.h" -#define INLINE inline #include // for min(), max() #include // for assert() #include -#include class MasterCompressor { public: MasterCompressor(); - void setup(int32_t attack, int32_t release, int32_t threshold, int32_t ratio); + void setup(q31_t attack, q31_t release, q31_t threshold, q31_t ratio, q31_t sidechain_fc); void render(StereoSample* buffer, uint16_t numSamples, q31_t volAdjustL, q31_t volAdjustR); float runEnvelope(float in, float numSamples); @@ -60,11 +59,23 @@ class MasterCompressor { updateER(); } q31_t getRatio() { return ratioKnobPos; } - q31_t setRatio(q31_t rat) { + int32_t setRatio(q31_t rat) { ratioKnobPos = rat; ratio = 0.5 + (float(ratioKnobPos) / ONE_Q31f) / 2; return 1 / (1 - ratio); } + q31_t getSidechain() { return sideChainKnobPos; } + + int32_t setSidechain(q31_t f) { + sideChainKnobPos = f; + //this exp will be between 1 and 5ish, half the knob range is about 2 + //the result will then be from 0 to 100hz with half the knob range at 60hz + float fc_hz = (exp(1.5 * float(f) / ONE_Q31f) - 1) * 30; + float fc = fc_hz / float(kSampleRate); + float wc = fc / (1 + fc); + a = wc * ONE_Q31; + return fc_hz; + } void updateER(); float calc_rms(StereoSample* buffer, uint16_t numSamples); @@ -78,6 +89,7 @@ class MasterCompressor { float er; float threshdb; float threshold; + q31_t a; //state float state; @@ -86,6 +98,9 @@ class MasterCompressor { float rms; float mean; + //sidechain filter + deluge::dsp::filter::BasicFilterComponent hpfL; + deluge::dsp::filter::BasicFilterComponent hpfR; //for display float attackMS; float releaseMS; @@ -95,4 +110,5 @@ class MasterCompressor { q31_t ratioKnobPos; q31_t attackKnobPos; q31_t releaseKnobPos; + q31_t sideChainKnobPos; }; diff --git a/src/deluge/model/global_effectable/global_effectable.cpp b/src/deluge/model/global_effectable/global_effectable.cpp index 5f088471b6..3e732741f3 100644 --- a/src/deluge/model/global_effectable/global_effectable.cpp +++ b/src/deluge/model/global_effectable/global_effectable.cpp @@ -276,7 +276,7 @@ bool GlobalEffectable::modEncoderButtonAction(uint8_t whichModEncoder, bool on, else { currentCompParam = static_cast((util::to_underlying(currentCompParam) + 1) % maxCompParam); - const char* params[3] = {"ratio", "attack", "release"}; + const char* params[util::to_underlying(CompParam::LAST)] = {"ratio", "attack", "release", "hpf"}; display->popupTextTemporary(params[int(currentCompParam)]); } } @@ -319,6 +319,10 @@ int32_t GlobalEffectable::getKnobPosForNonExistentParam(int32_t whichModEncoder, current = AudioEngine::mastercompressor.getRelease() >> 24; break; + + case CompParam::SIDECHAIN: + current = AudioEngine::mastercompressor.getSidechain() >> 24; + break; } } } @@ -372,6 +376,15 @@ ActionResult GlobalEffectable::modEncoderActionForNonExistentParam(int32_t offse displayLevel = AudioEngine::mastercompressor.setRelease(lshiftAndSaturate<24>(current + 64)); break; + + case CompParam::SIDECHAIN: + current = (AudioEngine::mastercompressor.getSidechain() >> 24) - 64; + current += offset; + current = std::clamp(current, -64, 64); + ledLevel = (64 + current); + + displayLevel = AudioEngine::mastercompressor.setSidechain(lshiftAndSaturate<24>(current + 64)); + break; } indicator_leds::setKnobIndicatorLevel(0, ledLevel); } diff --git a/src/deluge/model/global_effectable/global_effectable_for_song.cpp b/src/deluge/model/global_effectable/global_effectable_for_song.cpp index a124c2effe..ba67adb912 100644 --- a/src/deluge/model/global_effectable/global_effectable_for_song.cpp +++ b/src/deluge/model/global_effectable/global_effectable_for_song.cpp @@ -19,6 +19,6 @@ GlobalEffectableForSong::GlobalEffectableForSong() { modKnobMode = 1; - //attack and release can't go in the param manager so this keeps them from changing in clip comps - maxCompParam = 3; + //UI for kit compressors is TBD so they can only be accessed in song + maxCompParam = util::to_underlying(CompParam::LAST); } diff --git a/src/deluge/model/song/song.cpp b/src/deluge/model/song/song.cpp index 96614013ca..3f89d6e692 100644 --- a/src/deluge/model/song/song.cpp +++ b/src/deluge/model/song/song.cpp @@ -140,6 +140,7 @@ Song::Song() : backedUpParamManagers(sizeof(BackedUpParamManager)) { masterCompressorRelease = 20 << 24; masterCompressorThresh = 0; masterCompressorRatio = 0; + masterCompressorSidechainFC = ONE_Q31 >> 1; AudioEngine::mastercompressor.gainReduction = 0.0; dirPath.set("SONGS"); @@ -2670,7 +2671,7 @@ int32_t Song::getCurrentPresetScale() { // If we're here, must be this one! return p; -notThisOne : {} +notThisOne: {} } return 255; @@ -4559,7 +4560,7 @@ Instrument* Song::changeInstrumentType(Instrument* oldInstrument, InstrumentType return NULL; } -gotAnInstrument : {} +gotAnInstrument: {} } // Synth or Kit diff --git a/src/deluge/model/song/song.h b/src/deluge/model/song/song.h index 5b6e312bfd..817e3f69f6 100644 --- a/src/deluge/model/song/song.h +++ b/src/deluge/model/song/song.h @@ -330,6 +330,7 @@ class Song final : public TimelineCounter { int32_t masterCompressorRelease; int32_t masterCompressorThresh; int32_t masterCompressorRatio; + int32_t masterCompressorSidechainFC; private: bool fillModeActive; diff --git a/src/deluge/processing/engines/audio_engine.cpp b/src/deluge/processing/engines/audio_engine.cpp index f02d093d20..92a836dd65 100644 --- a/src/deluge/processing/engines/audio_engine.cpp +++ b/src/deluge/processing/engines/audio_engine.cpp @@ -1197,12 +1197,12 @@ void getReverbParamsFromSong(Song* song) { } void getMasterCompressorParamsFromSong(Song* song) { - int32_t a = song->masterCompressorAttack; - int32_t r = song->masterCompressorRelease; - int32_t t = song->masterCompressorThresh; - int32_t rat = song->masterCompressorRatio; - - mastercompressor.setup(a, r, t, rat); + q31_t a = song->masterCompressorAttack; + q31_t r = song->masterCompressorRelease; + q31_t t = song->masterCompressorThresh; + q31_t rat = song->masterCompressorRatio; + q31_t fc = song->masterCompressorSidechainFC; + mastercompressor.setup(a, r, t, rat, fc); } Voice* solicitVoice(Sound* forSound) {