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