tracktion-engine 3.0-10-g034fdde4aa5
Tracktion Engine — High level data model for audio applications

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_Compressor.cpp
Go to the documentation of this file.
1 /*
2 ,--. ,--. ,--. ,--.
3 ,-' '-.,--.--.,--,--.,---.| |,-.,-' '-.`--' ,---. ,--,--, Copyright 2024
4 '-. .-'| .--' ,-. | .--'| /'-. .-',--.| .-. || \ Tracktion Software
5 | | | | \ '-' \ `--.| \ \ | | | |' '-' '| || | Corporation
6 `---' `--' `--`--'`---'`--'`--' `---' `--' `---' `--''--' www.tracktion.com
7
8 Tracktion Engine uses a GPL/commercial licence - see LICENCE.md for details.
9*/
10
11namespace tracktion { inline namespace engine
12{
13
14CompressorPlugin::CompressorPlugin (PluginCreationInfo info) : Plugin (info)
15{
16 thresholdGain = addParam ("threshold", TRANS("Threshold"), { 0.01f, 1.0f },
17 [] (float value) { return gainToDbString (value); },
18 [] (const juce::String& s) { return dbStringToGain (s); });
19
20 ratio = addParam ("ratio", TRANS("Ratio"), { 0.0f, 0.95f },
21 [] (float value) { return (value > 0.001f ? juce::String (1.0f / value, 2)
22 : juce::String ("INF")) + " : 1"; },
23 [] (const juce::String& s) { return s.getFloatValue(); });
24
25 attackMs = addParam ("attack", TRANS("Attack"), { 0.3f, 200.0f },
26 [] (float value) { return juce::String (value, 1) + " ms"; },
27 [] (const juce::String& s) { return s.getFloatValue(); });
28
29 releaseMs = addParam ("release", TRANS("Release"), { 10.0f, 300.0f },
30 [] (float value) { return juce::String (value, 1) + " ms"; },
31 [] (const juce::String& s) { return s.getFloatValue(); });
32
33 outputDb = addParam ("output gain", TRANS("Output gain"), { -10.0f, 24.0f },
34 [] (float value) { return juce::Decibels::toString (value); },
35 [] (const juce::String& s) { return dbStringToDb (s); });
36
37 sidechainDb = addParam ("input gain", TRANS("Sidechain gain"), { -24.0f, 24.0f },
38 [] (float value) { return juce::Decibels::toString (value); },
39 [] (const juce::String& s) { return dbStringToDb (s); });
40
41 auto um = getUndoManager();
42
43 thresholdValue.referTo (state, IDs::threshold, um, dbToGain (-6.0f));
44 thresholdGain->attachToCurrentValue (thresholdValue);
45
46 ratioValue.referTo (state, IDs::ratio, um, 0.5f);
47 ratio->attachToCurrentValue (ratioValue);
48
49 attackValue.referTo (state, IDs::attack, um, 100.0f);
50 attackMs->attachToCurrentValue (attackValue);
51
52 releaseValue.referTo (state, IDs::release, um, 100.0f);
53 releaseMs->attachToCurrentValue (releaseValue);
54
55 outputValue.referTo (state, IDs::outputDb, um);
56 outputDb->attachToCurrentValue (outputValue);
57
58 useSidechainTrigger.referTo (state, IDs::sidechainTrigger, um);
59
60 sidechainValue.referTo (state, IDs::inputDb, um);
61 sidechainDb->attachToCurrentValue (sidechainValue);
62}
63
64CompressorPlugin::~CompressorPlugin()
65{
66 notifyListenersOfDeletion();
67
68 thresholdGain->detachFromCurrentValue();
69 ratio->detachFromCurrentValue();
70 attackMs->detachFromCurrentValue();
71 releaseMs->detachFromCurrentValue();
72 outputDb->detachFromCurrentValue();
73 sidechainDb->detachFromCurrentValue();
74}
75
76const char* CompressorPlugin::xmlTypeName = "compressor";
77
78void CompressorPlugin::getChannelNames (juce::StringArray* ins,
80{
81 Plugin::getChannelNames (ins, outs);
82
83 if (ins != nullptr)
84 ins->add (TRANS("Sidechain Trigger"));
85}
86
87void CompressorPlugin::initialise (const PluginInitialisationInfo&)
88{
89 currentLevel = 0.0;
90 lastSamp = 0.0f;
91}
92
93void CompressorPlugin::deinitialise()
94{
95}
96
97static const float preFilterAmount = 0.9f; // more = smoother level detection
98
99void CompressorPlugin::applyToBuffer (const PluginRenderContext& fc)
100{
101 if (fc.destBuffer == nullptr)
102 return;
103
104 SCOPED_REALTIME_CHECK
105
106 const double logThreshold = std::log10 (0.01);
107 const double attackFactor = std::pow (10.0, logThreshold / (attackMs->getCurrentValue() * sampleRate / 1000.0));
108 const double releaseFactor = std::pow (10.0, logThreshold / (releaseMs->getCurrentValue() * sampleRate / 1000.0));
109 const float outputGain = dbToGain (outputDb->getCurrentValue());
110 const float thresh = thresholdGain->getCurrentValue();
111 const float rat = ratio->getCurrentValue();
112 const bool useSidechain = useSidechainTrigger.get();
113 const float sidechainGain = dbToGain (sidechainDb->getCurrentValue());
114
115 float* b1 = fc.destBuffer->getWritePointer (0, fc.bufferStartSample);
116
117 if (fc.destBuffer->getNumChannels() >= 2)
118 {
119 float* b2 = fc.destBuffer->getWritePointer (1, fc.bufferStartSample);
120 float* b3 = fc.destBuffer->getNumChannels() > 2 ? fc.destBuffer->getWritePointer (2, fc.bufferStartSample) : nullptr;
121
122 for (int i = fc.bufferNumSamples; --i >= 0;)
123 {
124 float samp1 = *b1 + 1.0f;
125 samp1 -= 1.0f;
126 float samp2 = *b2 + 1.0f;
127 samp2 -= 1.0f;
128
129 float sampAvg = 0.0f;
130
131 if (useSidechain && b3 != nullptr)
132 {
133 sampAvg = lastSamp * preFilterAmount
134 + std::abs (*b3++ * sidechainGain) * ((1.0f - preFilterAmount));
135 }
136 else
137 {
138 sampAvg = lastSamp * preFilterAmount
139 + std::abs (samp1 + samp2) * ((1.0f - preFilterAmount) * 0.5f);
140 }
141
142 JUCE_UNDENORMALISE (sampAvg);
143
144 lastSamp = sampAvg;
145
146 if (sampAvg > thresh)
147 currentLevel = (currentLevel - sampAvg) * attackFactor + sampAvg;
148 else
149 currentLevel = (currentLevel - sampAvg) * releaseFactor + sampAvg;
150
151 float r = outputGain;
152
153 if (currentLevel > thresh)
154 {
155 r *= (float)((thresh + (currentLevel - thresh) * rat)
156 / currentLevel);
157 }
158
159 *b1++ = samp1 * r;
160 *b2++ = samp2 * r;
161 }
162 }
163 else
164 {
165 for (int i = fc.bufferNumSamples; --i >= 0;)
166 {
167 const float samp = *b1;
168 const float sampAvg = lastSamp * preFilterAmount
169 + std::abs (samp) * (1.0f - preFilterAmount);
170 lastSamp = sampAvg;
171
172 JUCE_UNDENORMALISE (lastSamp);
173
174 if (sampAvg > thresh)
175 currentLevel = (currentLevel - sampAvg) * attackFactor + sampAvg;
176 else
177 currentLevel = (currentLevel - sampAvg) * releaseFactor + sampAvg;
178
179 float r = outputGain;
180
181 if (currentLevel > thresh)
182 r *= (float)((thresh + (currentLevel - thresh) * rat) / currentLevel);
183
184 *b1++ = samp * r;
185 }
186 }
187
188 clearChannels (*fc.destBuffer, 2, -1, fc.bufferStartSample, fc.bufferNumSamples);
189}
190
191float CompressorPlugin::getThreshold() const
192{
193 return thresholdGain->getCurrentValue();
194}
195
196void CompressorPlugin::setThreshold (float t)
197{
198 thresholdGain->setParameter (juce::jlimit (getMinThreshold(),
199 getMaxThreshold(), t),
201}
202
203float CompressorPlugin::getRatio() const
204{
205 return ratio->getCurrentValue();
206}
207
208void CompressorPlugin::setRatio (float r)
209{
210 ratio->setParameter (juce::jlimit (0.05f, 1.0f, r),
212}
213
214void CompressorPlugin::restorePluginStateFromValueTree (const juce::ValueTree& v)
215{
216 copyPropertiesToCachedValues (v, thresholdValue, ratioValue, attackValue, releaseValue,
217 outputValue, sidechainValue, useSidechainTrigger);
218
219 for (auto p : getAutomatableParameters())
220 p->updateFromAttachedValue();
221}
222
223void CompressorPlugin::valueTreePropertyChanged (juce::ValueTree& v, const juce::Identifier& id)
224{
225 if (v == state && id == IDs::sidechainTrigger)
226 propertiesChanged();
227
228 Plugin::valueTreePropertyChanged (v, id);
229}
230
231}} // namespace tracktion { inline namespace engine
Type * getWritePointer(int channelNumber) noexcept
int getNumChannels() const noexcept
static String toString(Type decibels, int decimalPlaces=2, Type minusInfinityDb=Type(defaultMinusInfinitydB), bool shouldIncludeSuffix=true, StringRef customMinusInfinityString={})
void add(String stringToAdd)
float getFloatValue() const noexcept
#define TRANS(stringLiteral)
#define JUCE_UNDENORMALISE(x)
T log10(T... args)
typedef float
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
sendNotification
Passed into Plugins when they are being initialised, to give them useful contextual information that ...
T pow(T... args)
The context passed to plugin render methods to provide it with buffers to fill.
int bufferNumSamples
The number of samples to write into the audio buffer.
juce::AudioBuffer< float > * destBuffer
The target audio buffer which needs to be filled.
int bufferStartSample
The index of the start point in the audio buffer from which data must be written.