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 }