xref: /aosp_15_r20/external/oboe/apps/OboeTester/app/src/main/cpp/synth/EnvelopeADSR.h (revision 05767d913155b055644481607e6fa1e35e2fe72c)
1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  * This code was translated from the JSyn Java code.
17  * JSyn is Copyright 2009 Phil Burk, Mobileer Inc
18  * JSyn is licensed under the Apache License, Version 2.0
19  */
20 
21 #ifndef SYNTHMARK_ENVELOPE_ADSR_H
22 #define SYNTHMARK_ENVELOPE_ADSR_H
23 
24 #include <cstdint>
25 #include <math.h>
26 #include "SynthTools.h"
27 #include "UnitGenerator.h"
28 
29 namespace marksynth {
30 
31 /**
32  * Generate a contour that can be used to control amplitude or
33  * other parameters.
34  */
35 
36 class EnvelopeADSR  : public UnitGenerator
37 {
38 public:
EnvelopeADSR()39     EnvelopeADSR()
40     : mAttack(0.05)
41     , mDecay(0.6)
42     , mSustainLevel(0.4)
43     , mRelease(2.5)
44     {}
45 
46     virtual ~EnvelopeADSR() = default;
47 
48 #define MIN_DURATION (1.0 / 100000.0)
49 
50     enum State {
51         IDLE, ATTACKING, DECAYING, SUSTAINING, RELEASING
52     };
53 
setGate(bool gate)54     void setGate(bool gate) {
55         triggered = gate;
56     }
57 
isIdle()58     bool isIdle() {
59         return mState == State::IDLE;
60     }
61 
62     /**
63      * Time in seconds for the falling stage to go from 0 dB to -90 dB. The decay stage will stop at
64      * the sustain level. But we calculate the time to fall to -90 dB so that the decay
65      * <em>rate</em> will be unaffected by the sustain level.
66      */
setDecayTime(synth_float_t time)67     void setDecayTime(synth_float_t time) {
68         mDecay = time;
69     }
70 
getDecayTime()71     synth_float_t getDecayTime() {
72         return mDecay;
73     }
74 
75     /**
76      * Time in seconds for the rising stage of the envelope to go from 0.0 to 1.0. The attack is a
77      * linear ramp.
78      */
setAttackTime(synth_float_t time)79     void setAttackTime(synth_float_t time) {
80         mAttack = time;
81     }
82 
getAttackTime()83     synth_float_t getAttackTime() {
84         return mAttack;
85     }
86 
generate(int32_t numSamples)87     void generate(int32_t numSamples) {
88         for (int i = 0; i < numSamples; i++) {
89             switch (mState) {
90                 case IDLE:
91                     for (; i < numSamples; i++) {
92                         output[i] = mLevel;
93                         if (triggered) {
94                             startAttack();
95                             break;
96                         }
97                     }
98                     break;
99 
100                 case ATTACKING:
101                     for (; i < numSamples; i++) {
102                         // Increment first so we can render fast attacks.
103                         mLevel += increment;
104                         if (mLevel >= 1.0) {
105                             mLevel = 1.0;
106                             output[i] = mLevel;
107                             startDecay();
108                             break;
109                         } else {
110                             output[i] = mLevel;
111                             if (!triggered) {
112                                 startRelease();
113                                 break;
114                             }
115                         }
116                     }
117                     break;
118 
119                 case DECAYING:
120                     for (; i < numSamples; i++) {
121                         output[i] = mLevel;
122                         mLevel *= mScaler; // exponential decay
123                         if (mLevel < kAmplitudeDb96) {
124                             startIdle();
125                             break;
126                         } else if (!triggered) {
127                             startRelease();
128                             break;
129                         } else if (mLevel < mSustainLevel) {
130                             mLevel = mSustainLevel;
131                             startSustain();
132                             break;
133                         }
134                     }
135                     break;
136 
137                 case SUSTAINING:
138                     for (; i < numSamples; i++) {
139                         mLevel = mSustainLevel;
140                         output[i] = mLevel;
141                         if (!triggered) {
142                             startRelease();
143                             break;
144                         }
145                     }
146                     break;
147 
148                 case RELEASING:
149                     for (; i < numSamples; i++) {
150                         output[i] = mLevel;
151                         mLevel *= mScaler; // exponential decay
152                         if (triggered) {
153                             startAttack();
154                             break;
155                         } else if (mLevel < kAmplitudeDb96) {
156                             startIdle();
157                             break;
158                         }
159                     }
160                     break;
161             }
162         }
163     }
164 
165 private:
166 
startIdle()167     void startIdle() {
168         mState = State::IDLE;
169         mLevel = 0.0;
170     }
171 
startAttack()172     void startAttack() {
173         if (mAttack < MIN_DURATION) {
174             mLevel = 1.0;
175             startDecay();
176         } else {
177             increment = mSamplePeriod / mAttack;
178             mState = State::ATTACKING;
179         }
180     }
181 
startDecay()182     void startDecay() {
183         double duration = mDecay;
184         if (duration < MIN_DURATION) {
185             startSustain();
186         } else {
187             mScaler = SynthTools::convertTimeToExponentialScaler(duration, mSampleRate);
188             mState = State::DECAYING;
189         }
190     }
191 
startSustain()192     void startSustain() {
193         mState = State::SUSTAINING;
194     }
195 
startRelease()196     void startRelease() {
197         double duration = mRelease;
198         if (duration < MIN_DURATION) {
199             duration = MIN_DURATION;
200         }
201         mScaler = SynthTools::convertTimeToExponentialScaler(duration, mSampleRate);
202         mState = State::RELEASING;
203     }
204 
205     synth_float_t mAttack;
206     synth_float_t mDecay;
207     /**
208      * Level for the sustain stage. The envelope will hold here until the input goes to zero or
209      * less. This should be set between 0.0 and 1.0.
210      */
211     synth_float_t mSustainLevel;
212     /**
213      * Time in seconds to go from 0 dB to -90 dB. This stage is triggered when the input goes to
214      * zero or less. The release stage will start from the sustain level. But we calculate the time
215      * to fall from full amplitude so that the release <em>rate</em> will be unaffected by the
216      * sustain level.
217      */
218     synth_float_t mRelease;
219 
220     State mState = State::IDLE;
221     synth_float_t mScaler = 1.0;
222     synth_float_t mLevel = 0.0;
223     synth_float_t increment = 0;
224     bool triggered = false;
225 
226 };
227 
228 };
229 #endif // SYNTHMARK_ENVELOPE_ADSR_H
230