1 /**
2 * Various DSP smoothers.
3 *
4 * Copyright: Copyright Auburn Sounds 2015 and later.
5 * License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
6 * Authors:   Guillaume Piolat
7 */
8 module dplug.dsp.smooth;
9 
10 import std.algorithm.comparison;
11 import std.math;
12 
13 import dplug.core.math;
14 import dplug.core.ringbuf;
15 import dplug.core.nogc;
16 import dplug.core.vec;
17 
18 /// Smooth values exponentially with a 1-pole lowpass.
19 /// This is usually sufficient for most parameter smoothing.
20 struct ExpSmoother(T) if (is(T == float) || is(T == double))
21 {
22 public:
23     /// time: the time constant of the smoother.
24     /// threshold: absolute difference below which we consider current value and target equal
25     void initialize(float samplerate, float timeAttackRelease, T initialValue) nothrow @nogc
26     {
27         assert(isFinite(initialValue));
28 
29         _current = cast(T)(initialValue);
30         _sampleRate = samplerate;
31 
32         setAttackReleaseTime(timeAttackRelease);
33         
34         assert(isFinite(_expFactor));
35     }
36 
37     /// Changes attack and release time (given in seconds).
38     void setAttackReleaseTime(float timeAttackRelease) nothrow @nogc
39     {
40         _expFactor = cast(T)(expDecayFactor(timeAttackRelease, _sampleRate));
41     }
42 
43     /// Advance smoothing and return the next smoothed sample with respect
44     /// to tau time and samplerate.
45     T nextSample(T target) nothrow @nogc
46     {
47         T diff = target - _current;
48         if (diff != 0)
49         {
50             if (fast_fabs(diff) < 1e-10f) // to avoid subnormal, and excess churn
51             {
52                 _current = target;
53             }
54             else
55             {
56                 double temp = _current + diff * _expFactor; // Is double-precision really needed here?
57                 T newCurrent = cast(T)(temp);
58                 _current = newCurrent;
59             }
60         }
61         return _current;
62     }
63 
64     bool hasConverged(T target) nothrow @nogc
65     {
66         return target == _current;
67     }
68 
69     void nextBuffer(const(T)* input, T* output, int frames) nothrow @nogc
70     {
71         for (int i = 0; i < frames; ++i)
72         {
73             output[i] = nextSample(input[i]);
74         }
75     }
76 
77     void nextBuffer(T input, T* output, int frames) nothrow @nogc
78     {
79         for (int i = 0; i < frames; ++i)
80         {
81             output[i] = nextSample(input);
82         }
83     }
84 
85 private:
86     T _current;
87     T _expFactor;
88     float _sampleRate;
89 }
90 
91 unittest
92 {
93     ExpSmoother!float a;
94     ExpSmoother!double b;
95 }
96 
97 /// Same as ExpSmoother but have different attack and release decay factors.
98 struct AttackReleaseSmoother(T) if (is(T == float) || is(T == double))
99 {
100 public:
101     /// time: the time constant of the smoother.
102     /// threshold: absolute difference below which we consider current value and target equal
103     void initialize(float sampleRate, float timeAttackSecs, float timeReleaseSecs, T initialValue) nothrow @nogc
104     {
105         assert(isFinite(initialValue));
106         _sampleRate = sampleRate;
107         _current = cast(T)(initialValue);
108         setAttackTime(timeAttackSecs);
109         setReleaseTime(timeReleaseSecs);
110     }
111 
112     /// Changes attack time (given in seconds).
113     void setAttackTime(float timeAttackSecs) nothrow @nogc
114     {
115         _expFactorAttack = cast(T)(expDecayFactor(timeAttackSecs, _sampleRate));
116     }
117 
118     /// Changes release time (given in seconds).
119     void setReleaseTime(float timeReleaseSecs) nothrow @nogc
120     {
121         _expFactorRelease = cast(T)(expDecayFactor(timeReleaseSecs, _sampleRate));
122     }
123 
124     /// Advance smoothing and return the next smoothed sample with respect
125     /// to tau time and samplerate.
126     T nextSample(T target) nothrow @nogc
127     {
128         T diff = target - _current;
129         if (diff != 0)
130         {
131             if (fast_fabs(diff) < 1e-10f) // to avoid subnormal, and excess churn
132             {
133                 _current = target;
134             }
135             else
136             {
137                 double expFactor = (diff > 0) ? _expFactorAttack : _expFactorRelease;
138                 double temp = _current + diff * expFactor; // Is double-precision really needed here?
139                 T newCurrent = cast(T)(temp);
140                 _current = newCurrent;
141             }
142         }
143         return _current;
144     }
145 
146     void nextBuffer(const(T)* input, T* output, int frames)
147     {
148         for (int i = 0; i < frames; ++i)
149         {
150             output[i] = nextSample(input[i]);
151         }
152     }
153 
154     void nextBuffer(T input, T* output, int frames)
155     {
156         for (int i = 0; i < frames; ++i)
157         {
158             output[i] = nextSample(input);
159         }
160     }
161 
162 private:
163     T _current;
164     T _expFactorAttack;
165     T _expFactorRelease;
166     float _sampleRate;
167 }
168 
169 unittest
170 {
171     AttackReleaseSmoother!float a;
172     AttackReleaseSmoother!double b;
173 }
174 
175 /// Non-linear smoother using absolute difference.
176 /// Designed to have a nice phase response.
177 /// Warning: samplerate-dependent.
178 struct AbsSmoother(T) if (is(T == float) || is(T == double))
179 {
180 public:
181 
182     /// Initialize the AbsSmoother.
183     /// maxAbsDiff: maximum difference between filtered consecutive samples
184     void initialize(T initialValue, T maxAbsDiff) nothrow @nogc
185     {
186         assert(isFinite(initialValue));
187         _maxAbsDiff = maxAbsDiff;
188         _current = initialValue;
189     }
190 
191     T nextSample(T input) nothrow @nogc
192     {
193        T absDiff = abs(input - _current);
194        if (absDiff <= _maxAbsDiff)
195            _current = input;
196        else
197            _current = _current + absDiff * (input > _current ? 1 : -1);
198        return _current;
199     }
200 
201     void nextBuffer(const(T)* input, T* output, int frames) nothrow @nogc
202     {
203         for(int i = 0; i < frames; ++i)
204             output[i] = nextSample(input[i]);
205     }
206 
207 private:
208     T _current;
209     T _maxAbsDiff;
210 }
211 
212 unittest
213 {
214     AbsSmoother!float a;
215     AbsSmoother!double b;
216 }
217 
218 /// Smooth values over time with a linear slope.
219 /// This can be useful for some smoothing needs.
220 /// Intermediate between fast phase and actual smoothing.
221 struct LinearSmoother(T) if (is(T == float) || is(T == double))
222 {
223 public:
224 
225     /// Initialize the LinearSmoother.
226     void initialize(T initialValue, float periodSecs, float sampleRate) nothrow @nogc
227     {
228         _period = periodSecs;
229         _periodInv = 1 / periodSecs;
230         _sampleRateInv = 1 / sampleRate;
231 
232         // clear state
233         _current = initialValue;
234         _phase = 0;
235         _firstNextAfterInit = true;
236     }
237 
238     /// Set the target value and return the next sample.
239     T nextSample(T input) nothrow @nogc
240     {
241         _phase += _sampleRateInv;
242         if (_firstNextAfterInit || _phase > _period)
243         {
244             _phase -= _period;
245             _increment = (input - _current) * (_sampleRateInv * _periodInv);
246             _firstNextAfterInit = false;
247         }
248         _current += _increment;
249         return _current;
250     }
251 
252     void nextBuffer(const(T)* input, T* output, int frames) nothrow @nogc
253     {
254         for(int i = 0; i < frames; ++i)
255             output[i] = nextSample(input[i]);
256     }
257 
258 private:
259     T _current;
260     T _increment;
261     float _period;
262     float _periodInv;
263     float _sampleRateInv;
264     float _phase;
265     bool _firstNextAfterInit;
266 }
267 
268 unittest
269 {
270     LinearSmoother!float a;
271     LinearSmoother!double b;
272 }
273 
274 /// Can be very useful when filtering values with outliers.
275 /// For what it's meant to do, excellent phase response.
276 struct MedianFilter(T) if (is(T == float) || is(T == double))
277 {
278 public:
279 
280     void initialize(T initialValue, int samples) nothrow @nogc
281     {
282         assert(samples >= 2, "N must be >= 2");
283         assert(samples % 2 == 1, "N must be odd");
284 
285         _delay.reallocBuffer(samples - 1);
286         _delay[] = initialValue;
287 
288         _arr.reallocBuffer(samples);
289         _N = samples;
290     }
291 
292     ~this()
293     {
294         _delay.reallocBuffer(0);
295         _arr.reallocBuffer(0);
296     }
297 
298     T nextSample(T input) nothrow @nogc
299     {
300         // dramatically inefficient
301 
302         _arr[0] = input;
303         for (int i = 0; i < _N - 1; ++i)
304             _arr[i + 1] = _delay[i];
305 
306         // sort in place
307         grailSort!T(_arr[],  
308             (a, b) nothrow @nogc 
309             {
310                 if (a > b) return 1;
311                 else if (a < b) return -1;
312                 else return 0;
313             }
314         );
315 
316         T median = _arr[_N/2];
317 
318         for (int i = _N - 3; i >= 0; --i)
319             _delay[i + 1] = _delay[i];
320         _delay[0] = input;
321         return median;
322     }
323 
324     void nextBuffer(const(T)* input, T* output, int frames) nothrow @nogc
325     {
326         for(int i = 0; i < frames; ++i)
327             output[i] = nextSample(input[i]);
328     }
329 
330 private:
331     T[] _delay;
332     T[] _arr;
333     int _N;
334 }
335 
336 unittest
337 {
338     void test() nothrow @nogc 
339     {
340         MedianFilter!float a;
341         MedianFilter!double b;
342         a.initialize(0.0f, 3);
343         b.initialize(0.0f, 5);
344     }
345     test();
346 }
347 
348 
349 /// Simple FIR to smooth things cheaply.
350 /// Introduces (samples - 1) / 2 latency.
351 /// This one doesn't convert to integers internally so it may 
352 /// loose precision over time. Meants for finite signals.
353 struct UnstableMeanFilter(T) if (is(T == float) || is(T == double))
354 {
355 public:
356     /// Initialize mean filter with given number of samples.
357     void initialize(T initialValue, int samples) nothrow @nogc
358     {
359         _delay = RingBufferNoGC!T(samples);
360 
361         _invNFactor = cast(T)1 / samples;
362 
363         while(!_delay.isFull())
364             _delay.pushBack(initialValue);
365 
366         _sum = _delay.length * initialValue;
367     }
368 
369     /// Initialize with with cutoff frequency and samplerate.
370     void initialize(T initialValue, double cutoffHz, double samplerate) nothrow @nogc
371     {
372         int nSamples = cast(int)(0.5 + samplerate / (2 * cutoffHz));
373 
374         if (nSamples < 1)
375             nSamples = 1;
376 
377         initialize(initialValue, nSamples);
378     }
379 
380     // process next sample
381     T nextSample(T x) nothrow @nogc
382     {
383         _sum = _sum + x;
384         _sum = _sum - _delay.popFront();
385         _delay.pushBack(x);
386         return _sum * _invNFactor;
387     }
388 
389     void nextBuffer(const(T)* input, T* output, int frames) nothrow @nogc
390     {
391         for(int i = 0; i < frames; ++i)
392             output[i] = nextSample(input[i]);
393     }
394 
395 private:
396     RingBufferNoGC!T _delay;
397     double _sum; // should be approximately the sum of samples in delay
398     T _invNFactor;
399     T _factor;
400 }
401 
402 /// Simple FIR to smooth things cheaply.
403 /// Introduces (samples - 1) / 2 latency.
404 /// Converts everything to long for stability purpose.
405 /// So this may run forever as long as the input is below some threshold.
406 struct MeanFilter(T) if (is(T == float) || is(T == double))
407 {
408 public:
409     /// Initialize mean filter with given number of samples.
410     void initialize(T initialValue, int samples, T maxExpectedValue) nothrow @nogc
411     {
412         _delay = RingBufferNoGC!long(samples);
413 
414         _factor = cast(T)(2147483648.0 / maxExpectedValue);
415         _invNFactor = cast(T)1 / (_factor * samples);
416 
417         // clear state
418         // round to integer
419         long ivInt = toIntDomain(initialValue);
420 
421         while(!_delay.isFull())
422             _delay.pushBack(ivInt);
423 
424         _sum = cast(int)(_delay.length) * ivInt;
425     }
426 
427     /// Initialize with with cutoff frequency and samplerate.
428     void initialize(T initialValue, double cutoffHz, double samplerate, T maxExpectedValue) nothrow @nogc
429     {
430         int nSamples = cast(int)(0.5 + samplerate / (2 * cutoffHz));
431 
432         if (nSamples < 1)
433             nSamples = 1;
434 
435         initialize(initialValue, nSamples, maxExpectedValue);
436     }
437 
438     // process next sample
439     T nextSample(T x) nothrow @nogc
440     {
441         // round to integer
442         long input = cast(long)(cast(T)0.5 + x * _factor);
443         _sum = _sum + input;
444         _sum = _sum - _delay.popFront();
445         _delay.pushBack(input);
446         return cast(T)_sum * _invNFactor;
447     }
448 
449     void nextBuffer(const(T)* input, T* output, int frames) nothrow @nogc
450     {
451         for(int i = 0; i < frames; ++i)
452             output[i] = nextSample(input[i]);
453     }
454 
455 private:
456 
457     long toIntDomain(T x) pure const nothrow @nogc
458     {
459         return cast(long)(cast(T)0.5 + x * _factor);
460     }
461 
462     RingBufferNoGC!long _delay;
463     long _sum; // should always be the sum of samples in delay
464     T _invNFactor;
465     T _factor;
466 }
467 
468 unittest
469 {
470     void test() nothrow @nogc 
471     {
472         MeanFilter!float a;
473         MeanFilter!double b;
474         a.initialize(44100.0f, 0.001f, 0.001f, 0.0f);
475         b.initialize(44100.0f, 0.001f, 0.001f, 0.0f);
476     }
477     test();
478 }
479 
480 
481 /*
482 * Copyright (c) 2016 Aleksey Vaneev
483 * 
484 * Permission is hereby granted, free of charge, to any person obtaining a
485 * copy of this software and associated documentation files (the "Software"),
486 * to deal in the Software without restriction, including without limitation
487 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
488 * and/or sell copies of the Software, and to permit persons to whom the
489 * Software is furnished to do so, subject to the following conditions:
490 * 
491 * The above copyright notice and this permission notice shall be included in
492 * all copies or substantial portions of the Software.
493 * 
494 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
495 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
496 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
497 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
498 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
499 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
500 * DEALINGS IN THE SOFTWARE.
501 */
502 /**
503 * "gammaenv" produces smoothed-out S-curve envelope signal with the specified
504 * attack and release characteristics. The attack and release times can be
505 * further adjusted in real-time. Delay parameter is also specified as the
506 * percentage of the total time.
507 *
508 * The S-curve produced by this envelope algorithm closely resembles a
509 * sine-wave signal slightly augmented via the tanh() function. Such
510 * augmentation makes the shape slightly steeper and in the end allows the
511 * algorithm to follow it closer. The name "gammaenv" relates to this
512 * algorithm's version.
513 *
514 * The algorithm's topology is based on 5 sets of "leaky integrators" (the
515 * simplest form of 1st order low-pass filters). Each set (except the 5th) use
516 * 4 low-pass filters in series. Outputs of all sets are then simply
517 * summed/subtracted to produce the final result. The topology is numerically
518 * stable for any valid input signal, but may produce envelope overshoots
519 * depending on the input signal.
520 *
521 * Up to 25% of total attack (or release) time can be allocated (via Delay
522 * parameters) to the delay stage. The delay is implemented by the same
523 * topology: this has the benefit of not requiring additional memory
524 * buffering. For example, it is possible to get up to 250 ms delay on
525 * a 1-second envelope release stage without buffering.
526 *
527 * The processSymm() function provides the "perfect" implementation of the
528 * algorithm, but it is limited to the same attack and release times. A more
529 * universal process() function can work with any attack and release times,
530 * but it is about 2 times less efficient and the actual attack stage's
531 * envelope can range from the "designed" U to the undesired sharp V shape.
532 * Unfortunately, the author was unable to find an approach that could be
533 * similar to the processSymm() function while providing differing attack and
534 * release times (the best approach found so far lengthens the delay stage
535 * unpredictably).
536 */
537 /// gammaenv from Aleksey Vaneev is a better way to have an attack-release smoothing
538 struct GammaEnv(T) if (is(T == float) || is(T == double))
539 {
540 public:
541 
542     /**
543     * Function initializes or updates the internal variables. All public
544     * variables have to be defined before calling this function.
545     *
546     * @param SampleRate Sample rate.
547     */
548     void initialize(double sampleRate, double Attack, double Release, 
549                                        double AttackDelay, double ReleaseDelay,
550                                        bool isInverse) nothrow @nogc
551     {
552         double a;
553         double adly;
554         double b;
555         double bdly;
556 
557         if( Attack < Release )
558         {
559             a = Attack;
560             b = Release;
561             adly = AttackDelay;
562             bdly = ReleaseDelay;
563         }
564         else
565         {
566             b = Attack;
567             a = Release;
568             bdly = AttackDelay;
569             adly = ReleaseDelay;
570         }
571 
572         _isInverse = isInverse;
573 
574         // FUTURE: move this in processing whenever attack or release changes
575         calcMults( sampleRate, a, adly, enva.ptr, enva5 );
576         calcMults( sampleRate, b, bdly, envb.ptr, envb5 );
577 
578         clearState(0);
579     }
580 
581     double nextSample( double v ) nothrow @nogc
582     {
583         const double resa = nextSampleSymmetric( v );
584         const double cres = ( _isInverse ? resa <= prevr : resa >= prevr );
585         int i;
586 
587         if( cres )
588         {
589             for( i = 0; i < envcount4; i += 4 )
590             {
591                 envr[ i + 0 ] = resa;
592                 envr[ i + 1 ] = resa;
593                 envr[ i + 2 ] = resa;
594                 envr[ i + 3 ] = resa;
595             }
596 
597             envr5 = resa;
598             prevr = resa;
599 
600             return( resa );
601         }
602 
603         envr[ 0 ] += ( v - envr[ 0 ]) * envb[ 0 ];
604         envr[ 1 ] += ( envr5 - envr[ 1 ]) * envb[ 1 ];
605         envr[ 2 ] += ( envr[ 4 * 3 + 1 ] - envr[ 2 ]) * envb[ 2 ];
606         envr[ 3 ] += ( envr[ 4 * 3 + 0 ] - envr[ 3 ]) * envb[ 3 ];
607         envr5 += ( envr[ 4 * 3 + 0 ] - envr5 ) * envb5;
608 
609         for( i = 4; i < envcount4; i += 4 )
610         {
611             envr[ i + 0 ] += ( envr[ i - 4 ] - envr[ i + 0 ]) * envb[ 0 ];
612             envr[ i + 1 ] += ( envr[ i - 3 ] - envr[ i + 1 ]) * envb[ 1 ];
613             envr[ i + 2 ] += ( envr[ i - 2 ] - envr[ i + 2 ]) * envb[ 2 ];
614             envr[ i + 3 ] += ( envr[ i - 1 ] - envr[ i + 3 ]) * envb[ 3 ];
615         }
616 
617         prevr = envr[ i - 4 ] + envr[ i - 3 ] + envr[ i - 2 ] -
618             envr[ i - 1 ] - envr5;
619 
620         return( prevr );
621     }
622 
623     void nextBuffer(const(T)* input, T* output, int frames) nothrow @nogc
624     {
625         for (int i = 0; i < frames; ++i)
626         {
627             output[i] = nextSample(input[i]);
628         }
629     }
630 
631 private:
632     enum int envcount = 4; ///< The number of envelopes in use.
633     enum int envcount4 = envcount * 4; ///< =envcount * 4 (fixed).
634     double[ envcount4 ] env; ///< Signal envelope stages 1-4.
635     double[ 4 ] enva; ///< Attack stage envelope multipliers 1-4.
636     double[ 4 ] envb; ///< Release stage envelope multipliers 1-4.
637     double[ envcount4 ] envr; ///< Signal envelope (release) stages 1-4.
638     double env5; ///< Signal envelope stage 5.
639     double enva5; ///< Attack stage envelope multiplier 5.
640     double envb5; ///< Release stage envelope multiplier 5.
641     double envr5; ///< Signal envelope (release) stage 5.
642     double prevr; ///< Previous output (release).
643     bool _isInverse;
644 
645     /**
646     * Function clears state of *this object.
647     *
648     * @param initv Initial state value.
649     */
650 
651     void clearState( const double initv ) nothrow @nogc
652     {
653         int i;
654 
655         for( i = 0; i < envcount4; i += 4 )
656         {
657             env[ i + 0 ] = initv;
658             env[ i + 1 ] = initv;
659             env[ i + 2 ] = initv;
660             env[ i + 3 ] = initv;
661             envr[ i + 0 ] = initv;
662             envr[ i + 1 ] = initv;
663             envr[ i + 2 ] = initv;
664             envr[ i + 3 ] = initv;
665         }
666 
667         env5 = initv;
668         envr5 = initv;
669         prevr = initv;
670     }
671 
672 
673     /**
674     * Function performs 1 sample processing and produces output sample
675     * (symmetric mode, attack and release should be equal).
676     */
677     double nextSampleSymmetric(double v) nothrow @nogc
678     {
679         env[ 0 ] += ( v - env[ 0 ]) * enva[ 0 ];
680         env[ 1 ] += ( env5 - env[ 1 ]) * enva[ 1 ];
681         env[ 2 ] += ( env[ 4 * 3 + 1 ] - env[ 2 ]) * enva[ 2 ];
682         env[ 3 ] += ( env[ 4 * 3 + 0 ] - env[ 3 ]) * enva[ 3 ];
683         env5 += ( env[ 4 * 3 + 0 ] - env5 ) * enva5;
684         int i;
685 
686         for( i = 4; i < envcount4; i += 4 )
687         {
688             env[ i + 0 ] += ( env[ i - 4 ] - env[ i + 0 ]) * enva[ 0 ];
689             env[ i + 1 ] += ( env[ i - 3 ] - env[ i + 1 ]) * enva[ 1 ];
690             env[ i + 2 ] += ( env[ i - 2 ] - env[ i + 2 ]) * enva[ 2 ];
691             env[ i + 3 ] += ( env[ i - 1 ] - env[ i + 3 ]) * enva[ 3 ];
692         }
693 
694         return( env[ i - 4 ] + env[ i - 3 ] + env[ i - 2 ] -
695                 env[ i - 1 ] - env5 );
696     }
697 
698 
699     static void calcMults( const double SampleRate, const double Time,
700                            const double o, double* envs, ref double envs5 ) nothrow @nogc
701     {
702         const double o2 = o * o;
703 
704         if( o <= 0.074 )
705         {
706             envs[ 3 ] = 0.44548 + 0.00920770 * cos( 90.2666 * o ) -
707                 3.18551 * o - 0.132021 * cos( 377.561 * o2 ) -
708                 90.2666 * o * o2 * cos( 90.2666 * o );
709         }
710         else
711             if( o <= 0.139 )
712             {
713                 envs[ 3 ] = 0.00814353 + 3.07059 * o + 0.00356226 *
714                     cos( 879.555 * o2 );
715             }
716             else
717                 if( o <= 0.180 )
718                 {
719                     envs[ 3 ] = 0.701590 + o2 * ( 824.473 * o * o2 - 11.8404 );
720                 }
721                 else
722                 {
723                     envs[ 3 ] = 1.86814 + o * ( 84.0061 * o2 - 10.8637 ) -
724                         0.0122863 / o2;
725                 }
726 
727         const double e3 = envs[ 3 ];
728 
729         envs[ 0 ] = 0.901351 + o * ( 12.2872 * e3 + o * ( 78.0614 -
730                                                           213.130 * o ) - 9.82962 ) + e3 * ( 0.024808 *
731                                                                                              exp( 7.29048 * e3 ) - 5.4571 * e3 );
732         const double e0 = envs[ 0 ];
733 
734         const double e3exp = exp( 1.31354 * e3 + 0.181498 * o );
735         envs[ 1 ] = e3 * ( e0 * ( 2.75054 * o - 1.0 ) - 0.611813 * e3 *
736                            e3exp ) + 0.821369 * e3exp - 0.845698;
737         const double e1 = envs[ 1 ];
738 
739         envs[ 2 ] = 0.860352 + e3 * ( 1.17208 - 0.579576 * e0 ) + o * ( e0 *
740                                                                         ( 1.94324 - 1.95438 * o ) + 1.20652 * e3 ) - 1.08482 * e0 -
741             2.14670 * e1;
742 
743         if( o >= 0.0750 )
744         {
745             envs5 = 0.00118;
746         }
747         else
748         {
749             envs5 = e0 * ( 2.68318 - 2.08720 * o ) + 0.485294 * log( e3 ) +
750                 3.5805e-10 * exp( 27.0504 * e0 ) - 0.851199 - 1.24658 * e3 -
751                 0.885938 * log( e0 );
752         }
753 
754         const double c = 2 * PI / SampleRate;
755         envs[ 0 ] = calcLP1CoeffLim( c / ( Time * envs[ 0 ]));
756         envs[ 1 ] = calcLP1CoeffLim( c / ( Time * envs[ 1 ]));
757         envs[ 2 ] = calcLP1CoeffLim( c / ( Time * envs[ 2 ]));
758         envs[ 3 ] = calcLP1CoeffLim( c / ( Time * envs[ 3 ]));
759         envs5 = calcLP1CoeffLim( c / ( Time * envs5 ));
760     }
761 
762     /**
763     * Function calculates first-order low-pass filter coefficient for the
764     * given Theta frequency (0 to pi, inclusive). Returned coefficient in the
765     * form ( 1.0 - coeff ) can be used as a coefficient for a high-pass
766     * filter. This ( 1.0 - coeff ) can be also used as a gain factor for the
767     * high-pass filter so that when high-passed signal is summed with
768     * low-passed signal at the same Theta frequency the resuling sum signal
769     * is unity.
770     *
771     * @param theta Low-pass filter's circular frequency, >= 0.
772     */
773     static double calcLP1Coeff( const double theta ) nothrow @nogc
774     {
775         const double costheta2 = 2.0 - cos( theta );
776         return( 1.0 - ( costheta2 - sqrt( costheta2 * costheta2 - 1.0 )));
777     }
778 
779     /**
780     * Function checks the supplied parameter, limits it to "pi" and calls the
781     * calcLP1Coeff() function.
782     *
783     * @param theta Low-pass filter's circular frequency, >= 0.
784     */
785     static double calcLP1CoeffLim( const double theta ) nothrow @nogc
786     {
787         return( calcLP1Coeff( theta < PI ? theta : PI ));
788     }
789 }
790 
791 unittest
792 {
793     GammaEnv!float a;
794     GammaEnv!double b;
795 }