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