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 }