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 }