From b94fcd997f6091895b53b10e4c68cde1352a1e04 Mon Sep 17 00:00:00 2001
From: m-m-adams <mark.adams@queensu.ca>
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<float>((songVolume - threshdb - 2) * ratio, 0);
+	er = std::max<float>((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<int32_t>(-(reduction)*9 * 4, 0, 127);
+	gainReduction = std::clamp<int32_t>(-(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 <algorithm> // for min(), max()
 #include <cassert>   // for assert()
 #include <cmath>
-#include <cstdint>
 
 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<CompParam>((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 7e71575bc8..a55fdd4619 100644
--- a/src/deluge/processing/engines/audio_engine.cpp
+++ b/src/deluge/processing/engines/audio_engine.cpp
@@ -1212,12 +1212,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) {