1 /*
2  * Copyright (C) 2020 Linux Studio Plugins Project <https://lsp-plug.in/>
3  *           (C) 2020 Vladimir Sadovnikov <sadko4u@gmail.com>
4  *
5  * This file is part of lsp-plugins
6  * Created on: 14 сент. 2016 г.
7  *
8  * lsp-plugins is free software: you can redistribute it and/or modify
9  * it under the terms of the GNU Lesser General Public License as published by
10  * the Free Software Foundation, either version 3 of the License, or
11  * any later version.
12  *
13  * lsp-plugins is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public License
19  * along with lsp-plugins. If not, see <https://www.gnu.org/licenses/>.
20  */
21 
22 #include <dsp/dsp.h>
23 #include <core/util/Sidechain.h>
24 
25 #define REFRESH_RATE        0x1000
26 #define MIN_GAP_ITEMS       0x200
27 
28 namespace lsp
29 {
Sidechain()30     Sidechain::Sidechain()
31     {
32         nReactivity         = 0;
33         fReactivity         = 0.0f;
34         fTau                = 0.0f;
35         fRmsValue           = 0.0f;
36         nSource             = SCS_MIDDLE;
37         nMode               = SCM_RMS;
38         nSampleRate         = 0;
39         nRefresh            = 0;
40         nChannels           = 0;
41         fMaxReactivity      = 0.0f;
42         fGain               = 1.0f;
43         bUpdate             = true;
44         bMidSide            = false;
45         pPreEq              = NULL;
46     }
47 
~Sidechain()48     Sidechain::~Sidechain()
49     {
50         destroy();
51     }
52 
destroy()53     void Sidechain::destroy()
54     {
55         sBuffer.destroy();
56     }
57 
init(size_t channels,float max_reactivity)58     bool Sidechain::init(size_t channels, float max_reactivity)
59     {
60         if ((channels != 1) && (channels != 2))
61             return false;
62 
63         nReactivity         = 0;
64         fReactivity         = 0.0f;
65         fTau                = 0.0f;
66         fRmsValue           = 0.0f;
67         nSource             = SCS_MIDDLE;
68         nMode               = SCM_RMS;
69         nSampleRate         = 0;
70         nRefresh            = 0;
71         nChannels           = channels;
72         fMaxReactivity      = max_reactivity;
73         fGain               = 1.0f;
74         bUpdate             = true;
75 
76         return true;
77     }
78 
set_sample_rate(size_t sr)79     void Sidechain::set_sample_rate(size_t sr)
80     {
81         nSampleRate         = sr;
82         bUpdate             = true;
83         size_t gap          = millis_to_samples(sr, fMaxReactivity);
84         size_t buf_size     = (gap < MIN_GAP_ITEMS) ? MIN_GAP_ITEMS : gap;
85         sBuffer.init(buf_size * 4, gap);
86     }
87 
update_settings()88     void Sidechain::update_settings()
89     {
90         if (!bUpdate)
91             return;
92 
93         ssize_t react       = millis_to_samples(nSampleRate, fReactivity);
94         nReactivity         = (react > 1) ? react : 1;
95         fTau                = 1.0f - expf(logf(1.0f - M_SQRT1_2) / (nReactivity)); // Tau is based on seconds
96         nRefresh            = REFRESH_RATE; // Force the function to be refreshed
97         bUpdate             = false;
98     }
99 
refresh_processing()100     void Sidechain::refresh_processing()
101     {
102         switch (nMode)
103         {
104             case SCM_PEAK:
105                 fRmsValue       = 0.0f;
106                 break;
107 
108             case SCM_UNIFORM:
109                 fRmsValue       = dsp::h_abs_sum(sBuffer.tail(nReactivity), nReactivity);
110                 break;
111 
112             case SCM_RMS:
113                 fRmsValue       = dsp::h_sqr_sum(sBuffer.tail(nReactivity), nReactivity);
114                 break;
115 
116             default:
117                 break;
118         }
119     }
120 
preprocess(float * out,const float ** in,size_t samples)121     bool Sidechain::preprocess(float *out, const float **in, size_t samples)
122     {
123         if (nChannels == 2)
124         {
125             if (bMidSide)
126             {
127                 switch (nSource)
128                 {
129                     case SCS_LEFT:
130                         dsp::ms_to_left(out, in[0], in[1], samples);
131                         if (pPreEq != NULL)
132                             pPreEq->process(out, out, samples);
133                         dsp::abs1(out, samples);
134                         break;
135                     case SCS_RIGHT:
136                         dsp::ms_to_right(out, in[0], in[1], samples);
137                         if (pPreEq != NULL)
138                             pPreEq->process(out, out, samples);
139                         dsp::abs1(out, samples);
140                         break;
141                     case SCS_MIDDLE:
142                         if (pPreEq != NULL)
143                         {
144                             pPreEq->process(out, in[0], samples);
145                             dsp::abs1(out, samples);
146                         }
147                         else
148                             dsp::abs2(out, in[0], samples);
149                         break;
150                     case SCS_SIDE:
151                         if (pPreEq != NULL)
152                         {
153                             pPreEq->process(out, in[1], samples);
154                             dsp::abs1(out, samples);
155                         }
156                         else
157                             dsp::abs2(out, in[1], samples);
158                         break;
159                     default:
160                         break;
161                 }
162             }
163             else
164             {
165                 switch (nSource)
166                 {
167                     case SCS_LEFT:
168                         if (pPreEq != NULL)
169                         {
170                             pPreEq->process(out, in[0], samples);
171                             dsp::abs1(out, samples);
172                         }
173                         else
174                             dsp::abs2(out, in[0], samples);
175                         break;
176                     case SCS_RIGHT:
177                         if (pPreEq != NULL)
178                         {
179                             pPreEq->process(out, in[1], samples);
180                             dsp::abs1(out, samples);
181                         }
182                         else
183                             dsp::abs2(out, in[1], samples);
184                         break;
185                     case SCS_MIDDLE:
186                         dsp::lr_to_mid(out, in[0], in[1], samples);
187                         if (pPreEq != NULL)
188                             pPreEq->process(out, out, samples);
189                         dsp::abs1(out, samples);
190                         break;
191                     case SCS_SIDE:
192                         dsp::lr_to_side(out, in[0], in[1], samples);
193                         if (pPreEq != NULL)
194                             pPreEq->process(out, out, samples);
195                         dsp::abs1(out, samples);
196                         break;
197                     default:
198                         break;
199                 }
200             }
201         }
202         else if (nChannels == 1)
203         {
204             if (pPreEq != NULL)
205             {
206                 pPreEq->process(out, in[0], samples);
207                 dsp::abs1(out, samples);
208             }
209             else
210                 dsp::abs2(out, in[0], samples);
211         }
212         else
213         {
214             dsp::fill_zero(out, samples);
215             if (pPreEq != NULL)
216             {
217                 pPreEq->process(out, out, samples);
218                 dsp::abs1(out, samples);
219             }
220             return false;
221         }
222 
223         return true;
224     }
225 
preprocess(float * out,const float * in)226     bool Sidechain::preprocess(float *out, const float *in)
227     {
228         float s;
229 
230         if (nChannels == 2)
231         {
232             if (bMidSide)
233             {
234                 switch (nSource)
235                 {
236                     case SCS_LEFT:
237                         s = in[0] + in[1];
238                         if (pPreEq != NULL)
239                             pPreEq->process(&s, &s, 1);
240                         break;
241                     case SCS_RIGHT:
242                         s = in[0] - in[1];
243                         if (pPreEq != NULL)
244                             pPreEq->process(&s, &s, 1);
245                         break;
246                     case SCS_MIDDLE:
247                         s = in[0];
248                         if (pPreEq != NULL)
249                             pPreEq->process(&s, &s, 1);
250                         break;
251                     case SCS_SIDE:
252                         s = in[1];
253                         if (pPreEq != NULL)
254                             pPreEq->process(&s, &s, 1);
255                         break;
256                     default:
257                         s = in[0];
258                         break;
259                 }
260             }
261             else
262             {
263                 switch (nSource)
264                 {
265                     case SCS_LEFT:
266                         s = in[0];
267                         break;
268                     case SCS_RIGHT:
269                         s = in[1];
270                         break;
271                     case SCS_MIDDLE:
272                         s = (in[0] + in[1])*0.5f;
273                         if (pPreEq != NULL)
274                             pPreEq->process(&s, &s, 1);
275                         break;
276                     case SCS_SIDE:
277                         s = (in[0] - in[1])*0.5f;
278                         if (pPreEq != NULL)
279                             pPreEq->process(&s, &s, 1);
280                         break;
281                     default:
282                         s = (in[0] + in[1])*0.5f;
283                         break;
284                 }
285             }
286         }
287         else if (nChannels == 1)
288         {
289             s       = in[0];
290             if (pPreEq != NULL)
291                 pPreEq->process(&s, &s, 1);
292         }
293         else
294         {
295             s       = 0.0f;
296             if (pPreEq != NULL)
297                 pPreEq->process(&s, &s, 1);
298             *out    = s;
299             return false;
300         }
301 
302         *out    = (s < 0.0f) ? -s : s;
303         return true;
304     }
305 
process(float * out,const float ** in,size_t samples)306     void Sidechain::process(float *out, const float **in, size_t samples)
307     {
308         // Check if need update settings
309         update_settings();
310 
311         // Determine what source to use
312         if (!preprocess(out, in, samples))
313             return;
314 
315         // Adjust pre-amplification
316         if (fGain != 1.0f)
317             dsp::mul_k2(out, fGain, samples);
318 
319         // Update refresh counter
320         nRefresh       += samples;
321         if (nRefresh >= REFRESH_RATE)
322         {
323             refresh_processing();
324             nRefresh   %= REFRESH_RATE;
325         }
326 
327         // Calculate sidechain function
328         switch (nMode)
329         {
330             // Peak processing
331             case SCM_PEAK:
332             {
333                 while (samples > 0)
334                 {
335                     size_t n    = sBuffer.append(out, samples);
336                     sBuffer.shift(n);
337                     out        += n;
338                     samples    -= n;
339                 }
340                 break;
341             }
342 
343             // Lo-pass filter processing
344             case SCM_LPF:
345             {
346                 while (samples > 0)
347                 {
348                     size_t n    = sBuffer.append(out, samples);
349                     sBuffer.shift(n);
350                     samples    -= n;
351 
352                     while (n--)
353                     {
354                         fRmsValue      += fTau * ((*out) - fRmsValue);
355                         *(out++)        = (fRmsValue < 0.0f) ? 0.0f : fRmsValue;
356                     }
357                 }
358                 break;
359             }
360 
361             // Uniform processing
362             case SCM_UNIFORM:
363             {
364                 if (nReactivity <= 0)
365                     break;
366                 float interval  = nReactivity;
367 
368                 while (samples > 0)
369                 {
370                     size_t n    = sBuffer.append(out, samples);
371                     float *p    = sBuffer.tail(nReactivity + n);
372                     samples    -= n;
373 
374                     for (size_t i=0; i<n; ++i)
375                     {
376                         fRmsValue      += *(out) - *(p++);
377                         *(out++)        = (fRmsValue < 0.0f) ? 0.0f : fRmsValue / interval;
378                     }
379 
380                     // Remove old sample
381                     sBuffer.shift(n);
382                 }
383                 break;
384             }
385 
386             // RMS processing
387             case SCM_RMS:
388             {
389                 if (nReactivity <= 0)
390                     break;
391                 float interval  = nReactivity;
392 
393                 while (samples > 0)
394                 {
395                     size_t n        = sBuffer.append(out, samples);
396                     float *p        = sBuffer.tail(nReactivity + n);
397                     samples        -= n;
398 
399                     for (size_t i=0; i<n; ++i)
400                     {
401                         float sample    = *out;
402                         float last      = *(p++);
403                         fRmsValue      += sample*sample - last*last;
404                         *(out++)        = (fRmsValue < 0.0f) ? 0.0f : sqrtf(fRmsValue / interval);
405                     }
406                     sBuffer.shift(n);
407                 }
408                 break;
409             }
410 
411             default:
412                 break;
413         }
414     }
415 
process(const float * in)416     float Sidechain::process(const float *in)
417     {
418         // Check if need update settings
419         update_settings();
420 
421         float out   = 0.0f;
422         if (!preprocess(&out, in))
423             return out;
424 
425         // Adjust pre-amplification
426         out *= fGain;
427 
428         // Update refresh counter
429         nRefresh       ++;
430         if (nRefresh >= REFRESH_RATE)
431         {
432             refresh_processing();
433             nRefresh   %= REFRESH_RATE;
434         }
435 
436         // Calculate sidechain function
437         switch (nMode)
438         {
439             // Peak processing
440             case SCM_PEAK:
441             {
442                 sBuffer.append(out);
443                 sBuffer.shift();
444                 break;
445             }
446 
447             // Lo-pass filter processing
448             case SCM_LPF:
449             {
450                 sBuffer.append(out);
451                 sBuffer.shift();
452                 fRmsValue      += fTau * (out - fRmsValue);
453                 out             = (fRmsValue < 0.0f) ? 0.0f : fRmsValue;
454                 break;
455             }
456 
457             // Uniform processing
458             case SCM_UNIFORM:
459             {
460                 if (nReactivity <= 0)
461                     break;
462                 sBuffer.append(out);
463                 fRmsValue      += out - sBuffer.last(nReactivity + 1);
464                 out             = (fRmsValue < 0.0f) ? 0.0f : fRmsValue / float(nReactivity);
465                 sBuffer.shift();
466                 break;
467             }
468 
469             // RMS processing
470             case SCM_RMS:
471             {
472                 if (nReactivity <= 0)
473                     break;
474                 sBuffer.append(out);
475                 float last      = sBuffer.last(nReactivity + 1);
476                 fRmsValue      += out*out - last*last;
477                 out             = (fRmsValue < 0.0f) ? 0.0f : sqrtf(fRmsValue / float(nReactivity));
478                 sBuffer.shift();
479                 break;
480             }
481 
482             default:
483                 break;
484         }
485 
486         return out;
487     }
488 
dump(IStateDumper * v) const489     void Sidechain::dump(IStateDumper *v) const
490     {
491         v->write_object("sBuffer", &sBuffer);
492         v->write("nReactivity", nReactivity);
493         v->write("fReactivity", fReactivity);
494         v->write("fTau", fTau);
495         v->write("fRmsValue", fRmsValue);
496         v->write("nSource", nSource);
497         v->write("nMode", nMode);
498         v->write("nSampleRate", nSampleRate);
499         v->write("nRefresh", nRefresh);
500         v->write("nChannels", nChannels);
501         v->write("fMaxReactivity", fMaxReactivity);
502         v->write("fGain", fGain);
503         v->write("bUpdate", bUpdate);
504         v->write("bMidSide", bMidSide);
505         v->write("pPreEq", pPreEq);
506     }
507 
508 } /* namespace lsp */
509