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 }