1 module pixelperfectengine.audio.base.envgen; 2 3 import std.math : sqrt, pow; 4 5 import pixelperfectengine.system.etc : clamp; 6 import pixelperfectengine.audio.base.func : fastPow; 7 8 /* 9 * Copyright (C) 2015-2021, by Laszlo Szeremi under the Boost license. 10 * 11 * Pixel Perfect Engine, audio.base.envgen module. 12 * 13 * Contains ADSR envelop generation algorithms. 14 */ 15 16 /** 17 * ADSR Envelop generator struct. 18 * 19 * Uses floating-point arithmetics, since most targets will use that. 20 * Shaping is done through the shpF() and shp() functions. A 0.5 value should return a mostly linear output, and a 21 * 0.25 an "audio-grade logarithmic" for volume, but since the calculation is optimized for speed rather than accuracy, 22 * there will be imperfections. 23 */ 24 public struct ADSREnvelopGenerator { 25 /** 26 * Indicates the current stage of the envelop. 27 */ 28 public enum Stage : ubyte { 29 Off, 30 Attack, 31 Decay, 32 Sustain, 33 Release, 34 } 35 //Note: These values have a max value of 1.0, save for sustain rate, which can be negative. 36 //Decay and sustain rates are dependent on sustain level, so they sould be adjusted accordingly if timings of 37 //these must be kept constant. 38 public double attackRate = 1.0; ///Sets how long the attack phase will last (less = longer) 39 public double decayRate = 0.0; ///Sets how long the decay phase will last (less = longer) 40 public double sustainLevel = 1.0; ///Sets the level of sustain. 41 public double sustainControl = 0.0;///Controls how the sustain level will change (between -1.0 and 1.0) 42 public double releaseRate = 1.0; ///Sets how long the release phase will last (less = longer) 43 44 //mostly internal status values 45 protected double counter = 0; ///The current position of the counter + unshaped output 46 protected ubyte currStage; ///The current stage of the envelop generator 47 protected bool _keyState; ///If key is on, then it's set to true 48 protected bool _isRunning; ///If set, then the envelop is running 49 public bool isPercussive; ///If true, then the sustain stage is skipped 50 public static immutable double maxOutput = 1.0;///The maximum possible output of the envelop generator 51 public static immutable double minOutput = 0.0;///The minimum possible output of the envelop generator. 52 /** 53 * Advances the main counter by one amount. 54 * 55 * Returns the output. 56 */ 57 public double advance() @nogc @safe pure nothrow { 58 final switch (currStage) with (Stage) { 59 case Off: break; 60 case Attack: 61 counter +=attackRate; 62 if (counter >= maxOutput) { 63 counter = maxOutput; 64 currStage = Stage.Decay; 65 } 66 break; 67 case Decay: 68 counter -= decayRate; 69 if (counter <= sustainLevel) { 70 counter = sustainLevel; 71 currStage = isPercussive || !_keyState ? Stage.Release : Stage.Sustain; 72 } 73 break; 74 case Sustain: 75 counter -= sustainControl; 76 if (counter <= minOutput) { 77 counter = minOutput; 78 currStage = Stage.Off; 79 } else if (counter >= maxOutput) { 80 counter = maxOutput; 81 currStage = Stage.Off; 82 } 83 break; 84 case Release: 85 counter -= releaseRate; 86 if (counter <= minOutput) { 87 counter = minOutput; 88 currStage = Stage.Off; 89 } 90 break; 91 } 92 return counter; 93 } 94 /** 95 * Sets the key position to on. 96 */ 97 public void keyOn() @nogc @safe pure nothrow { 98 counter = 0; 99 _keyState = true; 100 currStage = Stage.Attack; 101 } 102 /** 103 * Sets the key position to on (no reset of counter). 104 */ 105 public void keyOnNoReset() @nogc @safe pure nothrow { 106 _keyState = true; 107 currStage = Stage.Attack; 108 } 109 /** 110 * Sets the key position to off. 111 */ 112 public void keyOff() @nogc @safe pure nothrow { 113 _keyState = false; 114 if (!isPercussive) 115 currStage = Stage.Release; 116 } 117 ///Returns the current stage 118 public ubyte position() @nogc @safe pure nothrow const { 119 return currStage; 120 } 121 ///Returns the current output as a floating-point value, between 0.0 and 1.0 122 public double output() @nogc @safe pure nothrow const { 123 return counter; 124 } 125 ///Returns true if the envelop generator is running 126 public bool isRunning() @nogc @safe pure nothrow const { 127 return _isRunning; 128 } 129 ///Returns true if key is on 130 public bool keypos() @nogc @safe pure nothrow const { 131 return _keyState; 132 } 133 ///Changes the shape of the output using a fast and very crude power of function. 134 ///Output is returned as a floating-point value between 0.0 and 1.0 135 public double shp(double g) @nogc @safe pure nothrow const { 136 //return g + (1 - counter) * (1 - counter) * (0 - g) + counter * counter * (1 - g); 137 /+const double c_2 = counter * counter; 138 return -2 * c_2 * g + c_2 + 2 * counter * g;+/ 139 double res = fastPow(counter, 2 + (g - 0.5) * 3); 140 return clamp(res, 0.0, 1.0); 141 } 142 143 } 144 /** 145 * Calculates the rate for an envelop parameter for a given amount of time. 146 * 147 * time: time in seconds. Can be a fraction of a second. 148 * freq: the frequency which the envelope generator is being updated at (e.g. sampling frequency) 149 * high: the higher value of the envelope stage. 150 * low: the lower value of the envelope stage. 151 * 152 * Note: The high and low values must be kept in order even in case of an ascending stage. 153 */ 154 public double calculateRate(double time, int freq, double high = ADSREnvelopGenerator.maxOutput, 155 double low = ADSREnvelopGenerator.minOutput) @nogc @safe pure nothrow { 156 return (high - low) / (freq * time); 157 158 }