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 ? 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 currStage = Stage.Release; 115 } 116 ///Returns the current stage 117 public ubyte position() @nogc @safe pure nothrow const { 118 return currStage; 119 } 120 ///Returns the current output as a floating-point value, between 0.0 and 1.0 121 public double output() @nogc @safe pure nothrow const { 122 return counter; 123 } 124 ///Returns true if the envelop generator is running 125 public bool isRunning() @nogc @safe pure nothrow const { 126 return _isRunning; 127 } 128 ///Returns true if key is on 129 public bool keypos() @nogc @safe pure nothrow const { 130 return _keyState; 131 } 132 ///Changes the shape of the output using a fast and very crude power of function. 133 ///Output is returned as a floating-point value between 0.0 and 1.0 134 public double shp(double g) @nogc @safe pure nothrow const { 135 //return g + (1 - counter) * (1 - counter) * (0 - g) + counter * counter * (1 - g); 136 /+const double c_2 = counter * counter; 137 return -2 * c_2 * g + c_2 + 2 * counter * g;+/ 138 double res = fastPow(counter, 2 + (g - 0.5) * 3); 139 return clamp(res, 0.0, 1.0); 140 } 141 142 } 143 /** 144 * Calculates the rate for an envelop parameter for a given amount of time. 145 * 146 * time: time in seconds. Can be a fraction of a second. 147 * freq: the frequency which the envelope generator is being updated at (e.g. sampling frequency) 148 * high: the higher value of the envelope stage. 149 * low: the lower value of the envelope stage. 150 * 151 * Note: The high and low values must be kept in order even in case of an ascending stage. 152 */ 153 public double calculateRate(double time, int freq, double high = ADSREnvelopGenerator.maxOutput, 154 double low = ADSREnvelopGenerator.minOutput) @nogc @safe pure nothrow { 155 return (high - low) / (freq * time); 156 157 }