SpectMorph
smflexadsr.hh
1 // This Source Code Form is licensed MPL-2.0: http://mozilla.org/MPL/2.0
2 
3 #pragma once
4 
5 namespace SpectMorph
6 {
7 
8 class FlexADSR
9 {
10 public:
11  enum class Shape { FLEXIBLE, EXPONENTIAL, LINEAR };
12 private:
13  float attack_ = 0;
14  float attack_slope_ = 0;
15  float decay_ = 0;
16  float decay_slope_ = 0;
17  float sustain_level_ = 0;
18  float release_ = 0;
19  float release_slope_ = 0;
20  float level_ = 0;
21  float release_start_ = 0; /* initial level of release stage */
22  int sustain_steps_ = 0; /* sustain smoothing */
23  bool params_changed_ = true;
24  int rate_ = 48000;
25 
26  enum class State { ATTACK, DECAY, SUSTAIN, RELEASE, DONE };
27 
28  State state_ = State::DONE;
29  Shape shape_ = Shape::LINEAR;
30 
31  float a_ = 0;
32  float b_ = 0;
33  float c_ = 0;
34 
35  void
36  init_abc (float time_s, float slope)
37  {
38  bool positive = slope > 0;
39  slope = std::abs (slope);
40 
41  const float t1y = 0.5f + 0.25f * slope;
42 
43  a_ = slope * ( 1.0135809670870777f + slope * (-1.2970447050283254f + slope * 7.2390617313972063f));
44  b_ = slope * (-5.8998946320566281f + slope * ( 5.7282487210570903f + slope * -15.525953208626062f));
45  c_ = 1 - (t1y * a_ + b_) * t1y;
46 
47  if (!positive)
48  {
49  c_ += a_ + b_;
50  b_ = -2 * a_ - b_;
51  }
52 
53  const float time_factor = 1 / (rate_ * time_s);
54  a_ *= time_factor;
55  b_ *= time_factor;
56  c_ *= time_factor;
57 
58  /* abc so far is for:
59  *
60  * y += a * y * y + b * y + c
61  *
62  * now to save one addition later on, we add one to b, and update y using
63  *
64  * y = a * y * y + b * y + c
65  */
66  b_ += 1;
67  }
68 
69  void
70  compute_slope_params (float seconds, float start_x, float end_x)
71  {
72  if (!params_changed_)
73  return;
74 
75  int steps = std::max<int> (seconds * rate_, 1);
76 
77  if (shape_ == Shape::LINEAR)
78  {
79  // linear
80  a_ = 0;
81  b_ = 1;
82  c_ = (end_x - start_x) / steps;
83  }
84  else if (shape_ == Shape::EXPONENTIAL)
85  {
86  /* exponential: true exponential decay doesn't ever reach zero;
87  * therefore we need to fade out early
88  */
89  const double RATIO = (state_ == State::ATTACK) ? 0.2 : 0.001;
90 
91  const double f = -log ((RATIO + 1) / RATIO) / steps;
92  double factor = exp (f);
93  c_ = (end_x - RATIO * (start_x - end_x)) * (1 - factor);
94  b_ = factor;
95  a_ = 0;
96  }
97  else if (shape_ == Shape::FLEXIBLE)
98  {
99  auto pos_time = [] (auto x) { return std::max (x, 0.0001f); /* 0.1ms */ };
100  if (state_ == State::ATTACK)
101  {
102  init_abc (pos_time (attack_), attack_slope_);
103  }
104  else if (state_ == State::DECAY)
105  {
106  /* exact timing for linear decay slope */
107  float stretch = 1 / std::max (1 - sustain_level_, 0.01f);
108  init_abc (-pos_time (decay_ * stretch), decay_slope_);
109  }
110  else if (state_ == State::RELEASE)
111  {
112  init_abc (-pos_time (release_), release_slope_);
113 
114  /* stretch abc parameters to match release time */
115  float l = std::max (release_start_, 0.01f);
116  a_ /= l;
117  c_ *= l;
118  }
119  }
120  params_changed_ = false;
121  }
122 
123 public:
124  void
125  set_shape (Shape shape)
126  {
127  shape_ = shape;
128  params_changed_ = true;
129  }
130  void
131  set_attack (float f)
132  {
133  attack_ = f;
134  params_changed_ = true;
135  }
136  void
137  set_attack_slope (float f)
138  {
139  attack_slope_ = f;
140  params_changed_ = true;
141  }
142  void
143  set_decay (float f)
144  {
145  decay_ = f;
146  params_changed_ = true;
147  }
148  void
149  set_decay_slope (float f)
150  {
151  decay_slope_ = f;
152  params_changed_ = true;
153  }
154  void
155  set_sustain (float f)
156  {
157  sustain_level_ = f * 0.01f;
158  params_changed_ = true;
159  }
160  void
161  set_release (float f)
162  {
163  release_ = f;
164  params_changed_ = true;
165  }
166  void
167  set_release_slope (float f)
168  {
169  release_slope_ = f;
170  params_changed_ = true;
171  }
172  void
173  set_rate (int sample_rate)
174  {
175  rate_ = sample_rate;
176  params_changed_ = true;
177  }
178  void
179  start ()
180  {
181  level_ = 0;
182  state_ = State::ATTACK;
183  params_changed_ = true;
184  }
185  void
186  stop()
187  {
188  state_ = State::RELEASE;
189  release_start_ = level_;
190  params_changed_ = true;
191  }
192 private:
193  template<State STATE, Shape SHAPE>
194  void
195  process (uint *iptr, float *samples, uint n_samples)
196  {
197  uint i = *iptr;
198 
199  const float a = a_;
200  const float b = b_;
201  const float c = c_;
202  const float sustain_level = sustain_level_;
203 
204  float level = level_;
205 
206  while (i < n_samples)
207  {
208  samples[i++] = level;
209 
210  if (SHAPE == Shape::FLEXIBLE)
211  level = (a * level + b) * level + c;
212 
213  if (SHAPE == Shape::EXPONENTIAL)
214  level = b * level + c;
215 
216  if (SHAPE == Shape::LINEAR)
217  level += c;
218 
219  if (STATE == State::ATTACK && level > 1)
220  {
221  level = 1;
222  state_ = State::DECAY;
223  params_changed_ = true;
224  break;
225  }
226  if (STATE == State::DECAY && level < sustain_level)
227  {
228  state_ = State::SUSTAIN;
229  level = sustain_level;
230  params_changed_ = true;
231  break;
232  }
233  if (STATE == State::RELEASE && level < 1e-5f)
234  {
235  state_ = State::DONE;
236  level = 0;
237  break;
238  }
239  }
240  level_ = level;
241 
242  *iptr = i;
243  }
244  template<State STATE>
245  void
246  process (uint *iptr, float *samples, uint n_samples)
247  {
248  if (shape_ == Shape::LINEAR)
249  process<STATE, Shape::LINEAR> (iptr, samples, n_samples);
250 
251  if (shape_ == Shape::EXPONENTIAL)
252  process<STATE, Shape::EXPONENTIAL> (iptr, samples, n_samples);
253 
254  if (shape_ == Shape::FLEXIBLE)
255  process<STATE, Shape::FLEXIBLE> (iptr, samples, n_samples);
256  }
257 public:
258  void
259  process (float *samples, uint n_samples)
260  {
261  uint i = 0;
262  if (state_ == State::ATTACK)
263  {
264  compute_slope_params (attack_, 0, 1);
265  process<State::ATTACK> (&i, samples, n_samples);
266  }
267  if (state_ == State::DECAY)
268  {
269  compute_slope_params (decay_, 1, sustain_level_);
270  process<State::DECAY> (&i, samples, n_samples);
271  }
272  if (state_ == State::RELEASE)
273  {
274  compute_slope_params (release_, release_start_, 0);
275  process<State::RELEASE> (&i, samples, n_samples);
276  }
277  if (state_ == State::SUSTAIN)
278  {
279  if (params_changed_)
280  {
281  if (std::abs (sustain_level_ - level_) > 1e-5)
282  {
283  sustain_steps_ = std::max<int> (0.020f * rate_, 1);
284  c_ = (sustain_level_ - level_) / sustain_steps_;
285  }
286  else
287  {
288  sustain_steps_ = 0;
289  }
290  params_changed_ = false;
291  }
292  while (sustain_steps_ && i < n_samples) /* sustain smoothing */
293  {
294  samples[i++] = level_;
295  level_ += c_;
296  sustain_steps_--;
297  if (sustain_steps_ == 0)
298  level_ = sustain_level_;
299  }
300  while (i < n_samples)
301  samples[i++] = level_;
302  }
303  if (state_ == State::DONE)
304  {
305  while (i < n_samples)
306  samples[i++] = 0;
307  }
308  }
309  bool
310  is_constant() const
311  {
312  if (state_ == State::SUSTAIN)
313  {
314  return !params_changed_ && sustain_steps_ == 0;
315  }
316  return state_ == State::DONE;
317  }
318  bool
319  done() const
320  {
321  return state_ == State::DONE;
322  }
323 };
324 
325 }
Definition: smflexadsr.hh:9