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 }