Anklang 0.3.0-460-gc4ef46ba
ASE — Anklang Sound Engine (C++)

« « « Anklang Documentation
Loading...
Searching...
No Matches
blepsynth.cc
Go to the documentation of this file.
1 // This Source Code Form is licensed MPL-2.0: http://mozilla.org/MPL/2.0
2#include "ase/processor.hh"
3#include "ase/midievent.hh"
8#include "ase/internal.hh"
9
10// based on liquidsfz envelope.hh
11
12namespace {
13
14using namespace Ase;
15
16class FlexADSR
17{
18public:
19 enum class Shape { FLEXIBLE, EXPONENTIAL, LINEAR };
20private:
21 float attack_ = 0;
22 float attack_slope_ = 0;
23 float decay_ = 0;
24 float decay_slope_ = 0;
25 float sustain_level_ = 0;
26 float release_ = 0;
27 float release_slope_ = 0;
28 float level_ = 0;
29 float release_start_ = 0; /* initial level of release stage */
30 int sustain_steps_ = 0; /* sustain smoothing */
31 bool params_changed_ = true;
32 int rate_ = 48000;
33
34 enum class State { ATTACK, DECAY, SUSTAIN, RELEASE, DONE };
35
36 State state_ = State::DONE;
37 Shape shape_ = Shape::LINEAR;
38
39 float a_ = 0;
40 float b_ = 0;
41 float c_ = 0;
42
43 void
44 init_abc (float time_s, float slope)
45 {
46 bool positive = slope > 0;
47 slope = std::abs (slope);
48
49 const float t1y = 0.5f + 0.25f * slope;
50
51 a_ = slope * ( 1.0135809670870777f + slope * (-1.2970447050283254f + slope * 7.2390617313972063f));
52 b_ = slope * (-5.8998946320566281f + slope * ( 5.7282487210570903f + slope * -15.525953208626062f));
53 c_ = 1 - (t1y * a_ + b_) * t1y;
54
55 if (!positive)
56 {
57 c_ += a_ + b_;
58 b_ = -2 * a_ - b_;
59 }
60
61 const float time_factor = 1 / (rate_ * time_s);
62 a_ *= time_factor;
63 b_ *= time_factor;
64 c_ *= time_factor;
65
66 /* abc so far is for:
67 *
68 * y += a * y * y + b * y + c
69 *
70 * now to save one addition later on, we add one to b, and update y using
71 *
72 * y = a * y * y + b * y + c
73 */
74 b_ += 1;
75 }
76
77 void
78 compute_slope_params (float seconds, float start_x, float end_x)
79 {
80 if (!params_changed_)
81 return;
82
83 int steps = std::max<int> (seconds * rate_, 1);
84
85 if (shape_ == Shape::LINEAR)
86 {
87 // linear
88 a_ = 0;
89 b_ = 1;
90 c_ = (end_x - start_x) / steps;
91 }
92 else if (shape_ == Shape::EXPONENTIAL)
93 {
94 /* exponential: true exponential decay doesn't ever reach zero;
95 * therefore we need to fade out early
96 */
97 const double RATIO = (state_ == State::ATTACK) ? 0.2 : 0.001;
98
99 const double f = -log ((RATIO + 1) / RATIO) / steps;
100 double factor = exp (f);
101 c_ = (end_x - RATIO * (start_x - end_x)) * (1 - factor);
102 b_ = factor;
103 a_ = 0;
104 }
105 else if (shape_ == Shape::FLEXIBLE)
106 {
107 auto pos_time = [] (auto x) { return std::max (x, 0.0001f); /* 0.1ms */ };
108 if (state_ == State::ATTACK)
109 {
110 init_abc (pos_time (attack_), attack_slope_);
111 }
112 else if (state_ == State::DECAY)
113 {
114 /* exact timing for linear decay slope */
115 float stretch = 1 / std::max (1 - sustain_level_, 0.01f);
116 init_abc (-pos_time (decay_ * stretch), decay_slope_);
117 }
118 else if (state_ == State::RELEASE)
119 {
120 init_abc (-pos_time (release_), release_slope_);
121
122 /* stretch abc parameters to match release time */
123 float l = std::max (release_start_, 0.01f);
124 a_ /= l;
125 c_ *= l;
126 }
127 }
128 params_changed_ = false;
129 }
130
131public:
132 void
133 set_shape (Shape shape)
134 {
135 shape_ = shape;
136 params_changed_ = true;
137 }
138 void
139 set_attack (float f)
140 {
141 attack_ = f;
142 params_changed_ = true;
143 }
144 void
145 set_attack_slope (float f)
146 {
147 attack_slope_ = f;
148 params_changed_ = true;
149 }
150 void
151 set_decay (float f)
152 {
153 decay_ = f;
154 params_changed_ = true;
155 }
156 void
157 set_decay_slope (float f)
158 {
159 decay_slope_ = f;
160 params_changed_ = true;
161 }
162 void
163 set_sustain (float f)
164 {
165 sustain_level_ = f * 0.01f;
166 params_changed_ = true;
167 }
168 void
169 set_release (float f)
170 {
171 release_ = f;
172 params_changed_ = true;
173 }
174 void
175 set_release_slope (float f)
176 {
177 release_slope_ = f;
178 params_changed_ = true;
179 }
180 void
181 set_rate (int sample_rate)
182 {
183 rate_ = sample_rate;
184 params_changed_ = true;
185 }
186 void
187 start ()
188 {
189 level_ = 0;
190 state_ = State::ATTACK;
191 params_changed_ = true;
192 }
193 void
194 stop()
195 {
196 state_ = State::RELEASE;
197 release_start_ = level_;
198 params_changed_ = true;
199 }
200private:
201 template<State STATE, Shape SHAPE>
202 void
203 process (uint *iptr, float *samples, uint n_samples)
204 {
205 uint i = *iptr;
206
207 const float a = a_;
208 const float b = b_;
209 const float c = c_;
210 const float sustain_level = sustain_level_;
211
212 float level = level_;
213
214 while (i < n_samples)
215 {
216 samples[i++] = level;
217
218 if (SHAPE == Shape::FLEXIBLE)
219 level = (a * level + b) * level + c;
220
221 if (SHAPE == Shape::EXPONENTIAL)
222 level = b * level + c;
223
224 if (SHAPE == Shape::LINEAR)
225 level += c;
226
227 if (STATE == State::ATTACK && level > 1)
228 {
229 level = 1;
230 state_ = State::DECAY;
231 params_changed_ = true;
232 break;
233 }
234 if (STATE == State::DECAY && level < sustain_level)
235 {
236 state_ = State::SUSTAIN;
237 level = sustain_level;
238 params_changed_ = true;
239 break;
240 }
241 if (STATE == State::RELEASE && level < 1e-5f)
242 {
243 state_ = State::DONE;
244 level = 0;
245 break;
246 }
247 }
248 level_ = level;
249
250 *iptr = i;
251 }
252 template<State STATE>
253 void
254 process (uint *iptr, float *samples, uint n_samples)
255 {
256 if (shape_ == Shape::LINEAR)
257 process<STATE, Shape::LINEAR> (iptr, samples, n_samples);
258
259 if (shape_ == Shape::EXPONENTIAL)
260 process<STATE, Shape::EXPONENTIAL> (iptr, samples, n_samples);
261
262 if (shape_ == Shape::FLEXIBLE)
263 process<STATE, Shape::FLEXIBLE> (iptr, samples, n_samples);
264 }
265public:
266 void
267 process (float *samples, uint n_samples)
268 {
269 uint i = 0;
270 if (state_ == State::ATTACK)
271 {
272 compute_slope_params (attack_, 0, 1);
273 process<State::ATTACK> (&i, samples, n_samples);
274 }
275 if (state_ == State::DECAY)
276 {
277 compute_slope_params (decay_, 1, sustain_level_);
278 process<State::DECAY> (&i, samples, n_samples);
279 }
280 if (state_ == State::RELEASE)
281 {
282 compute_slope_params (release_, release_start_, 0);
283 process<State::RELEASE> (&i, samples, n_samples);
284 }
285 if (state_ == State::SUSTAIN)
286 {
287 if (params_changed_)
288 {
289 if (std::abs (sustain_level_ - level_) > 1e-5)
290 {
291 sustain_steps_ = std::max<int> (0.020f * rate_, 1);
292 c_ = (sustain_level_ - level_) / sustain_steps_;
293 }
294 else
295 {
296 sustain_steps_ = 0;
297 }
298 params_changed_ = false;
299 }
300 while (sustain_steps_ && i < n_samples) /* sustain smoothing */
301 {
302 samples[i++] = level_;
303 level_ += c_;
304 sustain_steps_--;
305 if (sustain_steps_ == 0)
306 level_ = sustain_level_;
307 }
308 while (i < n_samples)
309 samples[i++] = level_;
310 }
311 if (state_ == State::DONE)
312 {
313 while (i < n_samples)
314 samples[i++] = 0;
315 }
316 }
317 bool
318 is_constant() const
319 {
320 if (state_ == State::SUSTAIN)
321 {
322 return !params_changed_ && sustain_steps_ == 0;
323 }
324 return state_ == State::DONE;
325 }
326 bool
327 done() const
328 {
329 return state_ == State::DONE;
330 }
331};
332
333// == BlepSynth ==
334// subtractive synth based on band limited steps (MinBLEP):
335// - aliasing-free square/saw and similar sounds including hard sync
336class BlepSynth : public AudioProcessor {
337 OBusId stereout_;
338 bool old_c_, old_d_, old_e_, old_f_, old_g_;
339
340 enum ParamType : uint32_t {
341 OSC1_SHAPE = 1, OSC1_PULSE_WIDTH, OSC1_SUB, OSC1_SUB_WIDTH, OSC1_SYNC, OSC1_PITCH, OSC1_OCTAVE, OSC1_UNISON_VOICES, OSC1_UNISON_DETUNE, OSC1_UNISON_STEREO,
342 OSC2_SHAPE, OSC2_PULSE_WIDTH, OSC2_SUB, OSC2_SUB_WIDTH, OSC2_SYNC, OSC2_PITCH, OSC2_OCTAVE, OSC2_UNISON_VOICES, OSC2_UNISON_DETUNE, OSC2_UNISON_STEREO,
343 VE_MODEL, ATTACK, DECAY, SUSTAIN, RELEASE, ATTACK_SLOPE, DECAY_SLOPE, RELEASE_SLOPE,
344 CUTOFF, RESONANCE, DRIVE, KEY_TRACK, FILTER_TYPE, LADDER_MODE, SKFILTER_MODE,
345 FIL_ATTACK, FIL_DECAY, FIL_SUSTAIN, FIL_RELEASE, FIL_CUT_MOD,
346 MIX, VEL_TRACK, POST_GAIN,
347 KEY_C, KEY_D, KEY_E, KEY_F, KEY_G,
348 };
349
350 enum { FILTER_TYPE_BYPASS, FILTER_TYPE_LADDER, FILTER_TYPE_SKFILTER };
351 int filter_type_ = 0;
352
353 static constexpr int CUTOFF_MIN_MIDI = 15;
354 static constexpr int CUTOFF_MAX_MIDI = 144;
355
356 class Voice
357 {
358 public:
359 enum State {
360 IDLE,
361 ON,
362 RELEASE
363 // TODO: SUSTAIN / pedal
364 };
365 // TODO : enum class MonoType
366
367 FlexADSR envelope_;
368 FlexADSR fil_envelope_;
369 State state_ = IDLE;
370 int midi_note_ = -1;
371 int channel_ = 0;
372 double freq_ = 0;
373 float vel_gain_ = 0;
374 bool new_voice_ = false;
375
376 LinearSmooth cutoff_smooth_;
377 double last_cutoff_;
378 double last_key_track_;
379
380 LinearSmooth cut_mod_smooth_;
381 double last_cut_mod_;
382
383 LinearSmooth reso_smooth_;
384 double last_reso_;
385
386 LinearSmooth drive_smooth_;
387 double last_drive_;
388
389 BlepUtils::OscImpl osc1_;
390 BlepUtils::OscImpl osc2_;
391
392 static constexpr int FILTER_OVERSAMPLE = 4;
393
394 LadderVCF ladder_filter_ { FILTER_OVERSAMPLE };
395 SKFilter skfilter_ { FILTER_OVERSAMPLE };
396 };
397 std::vector<Voice> voices_;
398 std::vector<Voice *> active_voices_;
399 std::vector<Voice *> idle_voices_;
400 void
401 initialize (SpeakerArrangement busses) override
402 {
403 using namespace MakeIcon;
404 set_max_voices (32);
405
406 ParameterMap pmap;
407
408 auto oscparams = [&] (int oscnum) {
409 const uint I = oscnum + 1;
410 const uint O = oscnum * (OSC2_SHAPE - OSC1_SHAPE);
411 const String o = string_format ("osc_%u_", I);
412
413 const double shape_default = oscnum ? -100 : 0;
414 const double octave_default = oscnum;
415
416 pmap.group = _("Oscillator %u", I);
417 pmap[O+OSC1_SHAPE] = Param { o+"shape", _("Osc %u Shape", I), _("Shp%u", I), shape_default, "%", { -100, 100, }, };
418 pmap[O+OSC1_PULSE_WIDTH] = Param { o+"pulse_width", _("Osc %u Pulse Width", I), _("PW%u", I), 50, "%", { 0, 100, }, };
419 pmap[O+OSC1_SUB] = Param { o+"subharmonic", _("Osc %u Subharmonic", I), _("Sub%u", I), 0, "%", { 0, 100, }, };
420 pmap[O+OSC1_SUB_WIDTH] = Param { o+"subharmonic_width", _("Osc %u Subharmonic Width", I), _("SbW%u", I), 50, "%", { 0, 100, }, };
421 pmap[O+OSC1_SYNC] = Param { o+"sync_slave", _("Osc %u Sync Slave", I), _("Syn%u", I), 0, "Semitones", { 0, 60, }, };
422
423 pmap[O+OSC1_PITCH] = Param { o+"pitch", _("Osc %u Pitch", I), _("Pit%u", I), 0, "semitones", { -7, 7, }, };
424 pmap[O+OSC1_OCTAVE] = Param { o+"octave", _("Osc %u Octave", I), _("Oct%u", I), octave_default, "octaves", { -2, 3, }, };
425
426 /* TODO: unison_voices property should have stepping set to 1 */
427 pmap[O+OSC1_UNISON_VOICES] = Param { o+"unison_voices", _("Osc %u Unison Voices", I), _("Voi%u", I), 1, "Voices", { 1, 16, }, };
428 pmap[O+OSC1_UNISON_DETUNE] = Param { o+"unison_detune", _("Osc %u Unison Detune", I), _("Dtu%u", I), 6, "%", { 0.5, 50, }, };
429 pmap[O+OSC1_UNISON_STEREO] = Param { o+"unison_stereo", _("Osc %u Unison Stereo", I), _("Ste%u", I), 0, "%", { 0, 100, }, };
430 };
431
432 oscparams (0);
433
434 pmap.group = _("Mix");
435 pmap[MIX] = Param { "mix", _("Mix"), _("Mix"), 30, "%", { 0, 100 }, };
436 pmap[VEL_TRACK] = Param { "vel_track", _("Velocity Tracking"), _("VelTr"), 50, "%", { 0, 100, }, };
437 // TODO: post_gain probably should default to 0dB once we have track/mixer volumes
438 pmap[POST_GAIN] = Param { "post_gain", _("Post Gain"), _("Gain"), -12, "dB", { -24, 24, }, };
439
440 oscparams (1);
441
442 pmap.group = _("Volume Envelope");
443 ChoiceS ve_model_cs;
444 ve_model_cs += { "A", "Analog" };
445 ve_model_cs += { "F", "Flexible" };
446 pmap[VE_MODEL] = Param { "ve_model", _("Envelope Model"), _("Model"), 0, "", std::move (ve_model_cs), "", { String ("blurb=") + _("ADSR Model to be used"), } };
447
448 pmap[ATTACK] = Param { "attack", _("Attack"), _("A"), 20.0, "%", { 0, 100, }, };
449 pmap[DECAY] = Param { "decay", _("Decay"), _("D"), 30.0, "%", { 0, 100, }, };
450 pmap[SUSTAIN] = Param { "sustain", _("Sustain"), _("S"), 50.0, "%", { 0, 100, }, };
451 pmap[RELEASE] = Param { "release", _("Release"), _("R"), 30.0, "%", { 0, 100, }, };
452
453 pmap[ATTACK_SLOPE] = Param { "attack_slope", _("Attack Slope"), _("AS"), 50, "%", { -100, 100, }, };
454 pmap[DECAY_SLOPE] = Param { "decay_slope", _("Decay Slope"), _("DS"), -100, "%", { -100, 100, }, };
455 pmap[RELEASE_SLOPE] = Param { "release_slope", _("Release Slope"), _("RS"), -100, "%", { -100, 100, }, };
456
457 pmap.group = _("Filter");
458
459 pmap[CUTOFF] = Param { "cutoff", _("Cutoff"), _("Cutoff"), 60, "", { CUTOFF_MIN_MIDI, CUTOFF_MAX_MIDI, }, }; // cutoff as midi notes
460 pmap[RESONANCE] = Param { "resonance", _("Resonance"), _("Reso"), 25.0, "%", { 0, 100, }, };
461 pmap[DRIVE] = Param { "drive", _("Drive"), _("Drive"), 0, "dB", { -24, 36, }, };
462 pmap[KEY_TRACK] = Param { "key_tracking", _("Key Tracking"), _("KeyTr"), 50, "%", { 0, 100, }, };
463 ChoiceS filter_type_choices;
464 filter_type_choices += { "—"_uc, "Bypass Filter" };
465 filter_type_choices += { "LD"_uc, "Ladder Filter" };
466 filter_type_choices += { "SKF"_uc, "Sallen-Key Filter" };
467 pmap[FILTER_TYPE] = Param { "filter_type", _("Filter Type"), _("Type"), FILTER_TYPE_LADDER, "", std::move (filter_type_choices), "", { String ("blurb=") + _("Filter Type to be used"), } };
468
469 ChoiceS ladder_mode_choices;
470 ladder_mode_choices += { "LP1"_uc, "1 Pole Lowpass, 6dB/Octave" };
471 ladder_mode_choices += { "LP2"_uc, "2 Pole Lowpass, 12dB/Octave" };
472 ladder_mode_choices += { "LP3"_uc, "3 Pole Lowpass, 18dB/Octave" };
473 ladder_mode_choices += { "LP4"_uc, "4 Pole Lowpass, 24dB/Octave" };
474 pmap[LADDER_MODE] = Param { "ladder_mode", _("Filter Mode"), _("Mode"), 1, "", std::move (ladder_mode_choices), "", { String ("blurb=") + _("Ladder Filter Mode to be used"), } };
475
476 ChoiceS skfilter_mode_choices;
477 skfilter_mode_choices += { "LP1"_uc, "1 Pole Lowpass, 6dB/Octave" };
478 skfilter_mode_choices += { "LP2"_uc, "2 Pole Lowpass, 12dB/Octave" };
479 skfilter_mode_choices += { "LP3"_uc, "3 Pole Lowpass, 18dB/Octave" };
480 skfilter_mode_choices += { "LP4"_uc, "4 Pole Lowpass, 24dB/Octave" };
481 skfilter_mode_choices += { "LP6"_uc, "6 Pole Lowpass, 36dB/Octave" };
482 skfilter_mode_choices += { "LP8"_uc, "8 Pole Lowpass, 48dB/Octave" };
483 skfilter_mode_choices += { "BP2"_uc, "2 Pole Bandpass, 6dB/Octave" };
484 skfilter_mode_choices += { "BP4"_uc, "4 Pole Bandpass, 12dB/Octave" };
485 skfilter_mode_choices += { "BP6"_uc, "6 Pole Bandpass, 18dB/Octave" };
486 skfilter_mode_choices += { "BP8"_uc, "8 Pole Bandpass, 24dB/Octave" };
487 skfilter_mode_choices += { "HP1"_uc, "1 Pole Highpass, 6dB/Octave" };
488 skfilter_mode_choices += { "HP2"_uc, "2 Pole Highpass, 12dB/Octave" };
489 skfilter_mode_choices += { "HP3"_uc, "3 Pole Highpass, 18dB/Octave" };
490 skfilter_mode_choices += { "HP4"_uc, "4 Pole Highpass, 24dB/Octave" };
491 skfilter_mode_choices += { "HP6"_uc, "6 Pole Highpass, 36dB/Octave" };
492 skfilter_mode_choices += { "HP8"_uc, "8 Pole Highpass, 48dB/Octave" };
493 pmap[SKFILTER_MODE] = Param { "skfilter_mode", _("SKFilter Mode"), _("Mode"), 2, "", std::move (skfilter_mode_choices), "", { String ("blurb=") + _("Sallen-Key Filter Mode to be used"), } };
494
495 pmap.group = _("Filter Envelope");
496 pmap[FIL_ATTACK] = Param { "fil_attack", _("Attack"), _("A"), 40, "%", { 0, 100, }, };
497 pmap[FIL_DECAY] = Param { "fil_decay", _("Decay"), _("D"), 55, "%", { 0, 100, }, };
498 pmap[FIL_SUSTAIN] = Param { "fil_sustain", _("Sustain"), _("S"), 30, "%", { 0, 100, }, };
499 pmap[FIL_RELEASE] = Param { "fil_release", _("Release"), _("R"), 30, "%", { 0, 100, }, };
500 pmap[FIL_CUT_MOD] = Param { "fil_cut_mod", _("Env Cutoff Modulation"), _("CutMod"), 36, "semitones", { -96, 96, }, }; /* 8 octaves range */
501
502 pmap.group = _("Keyboard Input");
503 pmap[KEY_C] = Param { "c", _("Main Input 1"), _("C"), false, "", {}, GUIONLY + ":toggle" };
504 pmap[KEY_D] = Param { "d", _("Main Input 2"), _("D"), false, "", {}, GUIONLY + ":toggle" };
505 pmap[KEY_E] = Param { "e", _("Main Input 3"), _("E"), false, "", {}, GUIONLY + ":toggle" };
506 pmap[KEY_F] = Param { "f", _("Main Input 4"), _("F"), false, "", {}, GUIONLY + ":toggle" };
507 pmap[KEY_G] = Param { "g", _("Main Input 5"), _("G"), false, "", {}, GUIONLY + ":toggle" };
508 old_c_ = old_d_ = old_e_ = old_f_ = old_g_ = false;
509
510 install_params (pmap);
511
513 stereout_ = add_output_bus ("Stereo Out", SpeakerArrangement::STEREO);
514 assert_return (bus_info (stereout_).ident == "stereo_out");
515 }
516 void
517 set_max_voices (uint n_voices)
518 {
519 voices_.clear();
520 voices_.resize (n_voices);
521
522 active_voices_.clear();
523 active_voices_.reserve (n_voices);
524
525 idle_voices_.clear();
526 for (auto& v : voices_)
527 idle_voices_.push_back (&v);
528 }
529 Voice *
530 alloc_voice()
531 {
532 if (idle_voices_.empty()) // out of voices?
533 return nullptr;
534
535 Voice *voice = idle_voices_.back();
536 assert_return (voice->state_ == Voice::IDLE, nullptr); // every item in idle_voices should be idle
537
538 // move voice from idle to active list
539 idle_voices_.pop_back();
540 active_voices_.push_back (voice);
541
542 return voice;
543 }
544 void
545 free_unused_voices()
546 {
547 size_t new_voice_count = 0;
548
549 for (size_t i = 0; i < active_voices_.size(); i++)
550 {
551 Voice *voice = active_voices_[i];
552
553 if (voice->state_ == Voice::IDLE) // voice used?
554 {
555 idle_voices_.push_back (voice);
556 }
557 else
558 {
559 active_voices_[new_voice_count++] = voice;
560 }
561 }
562 active_voices_.resize (new_voice_count);
563 }
564 void
565 reset (uint64 target_stamp) override
566 {
567 set_max_voices (0);
568 set_max_voices (32);
569 adjust_all_params();
570 }
571 void
572 init_osc (BlepUtils::OscImpl& osc, float freq)
573 {
574 osc.frequency_base = freq;
575 osc.set_rate (sample_rate());
576#if 0
577 osc.freq_mod_octaves = properties->freq_mod_octaves;
578#endif
579 }
580 void
581 adjust_param (uint32_t tag) override
582 {
583 switch (tag)
584 {
585 case FILTER_TYPE:
586 {
587 int new_filter_type = irintf (get_param (FILTER_TYPE));
588 if (new_filter_type != filter_type_)
589 {
590 filter_type_ = new_filter_type;
591 for (Voice *voice : active_voices_)
592 {
593 if (filter_type_ == FILTER_TYPE_LADDER)
594 voice->ladder_filter_.reset();
595 if (filter_type_ == FILTER_TYPE_SKFILTER)
596 voice->skfilter_.reset();
597 }
598 }
599 set_parameter_used (LADDER_MODE, filter_type_ == FILTER_TYPE_LADDER);
600 set_parameter_used (SKFILTER_MODE, filter_type_ == FILTER_TYPE_SKFILTER);
601 }
602 break;
603 case ATTACK:
604 case DECAY:
605 case SUSTAIN:
606 case RELEASE:
607 case ATTACK_SLOPE:
608 case DECAY_SLOPE:
609 case RELEASE_SLOPE:
610 {
611 for (Voice *voice : active_voices_)
612 update_volume_envelope (voice);
613 break;
614 }
615 case FIL_ATTACK:
616 case FIL_DECAY:
617 case FIL_SUSTAIN:
618 case FIL_RELEASE:
619 {
620 for (Voice *voice : active_voices_)
621 update_filter_envelope (voice);
622 break;
623 }
624 case VE_MODEL:
625 {
626 bool ve_has_slope = irintf (get_param (VE_MODEL)) > 0; // exponential envelope has no slope parameters
627
628 set_parameter_used (ATTACK_SLOPE, ve_has_slope);
629 set_parameter_used (DECAY_SLOPE, ve_has_slope);
630 set_parameter_used (RELEASE_SLOPE, ve_has_slope);
631 break;
632 }
633 case KEY_C: check_note (KEY_C, old_c_, 60); break;
634 case KEY_D: check_note (KEY_D, old_d_, 62); break;
635 case KEY_E: check_note (KEY_E, old_e_, 64); break;
636 case KEY_F: check_note (KEY_F, old_f_, 65); break;
637 case KEY_G: check_note (KEY_G, old_g_, 67); break;
638 }
639 }
640 void
641 update_osc (BlepUtils::OscImpl& osc, int oscnum)
642 {
643 const uint O = oscnum * (OSC2_SHAPE - OSC1_SHAPE);
644 osc.shape_base = get_param (O+OSC1_SHAPE) * 0.01;
645 osc.pulse_width_base = get_param (O+OSC1_PULSE_WIDTH) * 0.01;
646 osc.sub_base = get_param (O+OSC1_SUB) * 0.01;
647 osc.sub_width_base = get_param (O+OSC1_SUB_WIDTH) * 0.01;
648 osc.sync_base = get_param (O+OSC1_SYNC);
649
650 int octave = irintf (get_param (O+OSC1_OCTAVE));
651 octave = CLAMP (octave, -2, 3);
652 osc.frequency_factor = fast_exp2 (octave + get_param (O+OSC1_PITCH) / 12.);
653
654 int unison_voices = irintf (get_param (O+OSC1_UNISON_VOICES));
655 unison_voices = CLAMP (unison_voices, 1, 16);
656 osc.set_unison (unison_voices, get_param (O+OSC1_UNISON_DETUNE), get_param (O+OSC1_UNISON_STEREO) * 0.01);
657
658 set_parameter_used (O + OSC1_UNISON_DETUNE, unison_voices > 1);
659 set_parameter_used (O + OSC1_UNISON_STEREO, unison_voices > 1);
660 }
661 static double
662 perc_to_s (double perc)
663 {
664 // 100% -> 8s; 50% -> 1s; 0% -> 0s
665 const double x = perc * 0.01;
666 return x * x * x * 8;
667 }
668 static String
669 perc_to_str (double perc)
670 {
671 double ms = perc_to_s (perc) * 1000;
672 if (ms > 1000)
673 return string_format ("%.2f s", ms / 1000);
674 if (ms > 100)
675 return string_format ("%.0f ms", ms);
676 if (ms > 10)
677 return string_format ("%.1f ms", ms);
678 return string_format ("%.2f ms", ms);
679 }
680 static String
681 hz_to_str (double hz)
682 {
683 if (hz > 10000)
684 return string_format ("%.1f kHz", hz / 1000);
685 if (hz > 1000)
686 return string_format ("%.2f kHz", hz / 1000);
687 if (hz > 100)
688 return string_format ("%.0f Hz", hz);
689 return string_format ("%.1f Hz", hz);
690 }
691 static float
692 velocity_to_gain (float velocity, float vel_track)
693 {
694 /* input: velocity [0..1]
695 * vel_track [0..1]
696 *
697 * convert, so that
698 * - gain (0) is (1 - vel_track)^2
699 * - gain (1) is 1
700 * - sqrt(gain(velocity)) is a straight line
701 *
702 * See Roger B. Dannenberg: The Interpretation of Midi Velocity
703 */
704 const float x = (1 - vel_track) + vel_track * velocity;
705
706 return x * x;
707 }
708 void
709 note_on (int channel, int midi_note, float vel)
710 {
711 Voice *voice = alloc_voice();
712 if (voice)
713 {
714 voice->freq_ = note_to_freq (midi_note);
715 voice->state_ = Voice::ON;
716 voice->channel_ = channel;
717 voice->midi_note_ = midi_note;
718 voice->vel_gain_ = velocity_to_gain (vel, get_param (VEL_TRACK) * 0.01);
719
720 // Volume Envelope
721 /* TODO: maybe use non-linear translation between level and sustain % */
722 switch (irintf (get_param (VE_MODEL)))
723 {
724 case 0: voice->envelope_.set_shape (FlexADSR::Shape::EXPONENTIAL);
725 break;
726 default: voice->envelope_.set_shape (FlexADSR::Shape::FLEXIBLE);
727 break;
728 }
729 update_volume_envelope (voice);
730 voice->envelope_.set_rate (sample_rate());
731 voice->envelope_.start();
732
733 // Filter Envelope
734 voice->fil_envelope_.set_shape (FlexADSR::Shape::LINEAR);
735 update_filter_envelope (voice);
736 voice->fil_envelope_.set_rate (sample_rate());
737 voice->fil_envelope_.start();
738
739 init_osc (voice->osc1_, voice->freq_);
740 init_osc (voice->osc2_, voice->freq_);
741
742 voice->osc1_.reset();
743 voice->osc2_.reset();
744
745 const float cutoff_min_hz = convert_cutoff (CUTOFF_MIN_MIDI);
746 const float cutoff_max_hz = convert_cutoff (CUTOFF_MAX_MIDI);
747
748 voice->ladder_filter_.reset();
749 voice->ladder_filter_.set_rate (sample_rate());
750 voice->ladder_filter_.set_frequency_range (cutoff_min_hz, cutoff_max_hz);
751
752 voice->skfilter_.reset();
753 voice->skfilter_.set_rate (sample_rate());
754 voice->skfilter_.set_frequency_range (cutoff_min_hz, cutoff_max_hz);
755 voice->new_voice_ = true;
756
757 voice->cutoff_smooth_.reset (sample_rate(), 0.020);
758 voice->last_cutoff_ = -5000; // force reset
759
760 voice->cut_mod_smooth_.reset (sample_rate(), 0.020);
761 voice->last_cut_mod_ = -5000; // force reset
762 voice->last_key_track_ = -5000;
763
764 voice->reso_smooth_.reset (sample_rate(), 0.020);
765 voice->last_reso_ = -5000; // force reset
766 //
767 voice->drive_smooth_.reset (sample_rate(), 0.020);
768 voice->last_drive_ = -5000; // force reset
769 }
770 }
771 void
772 note_off (int channel, int midi_note)
773 {
774 for (auto voice : active_voices_)
775 {
776 if (voice->state_ == Voice::ON && voice->midi_note_ == midi_note && voice->channel_ == channel)
777 {
778 voice->state_ = Voice::RELEASE;
779 voice->envelope_.stop();
780 voice->fil_envelope_.stop();
781 }
782 }
783 }
784 void
785 check_note (ParamType pid, bool& old_value, int note)
786 {
787 const bool value = get_param (pid) > 0.5;
788 if (value != old_value)
789 {
790 constexpr int channel = 0;
791 if (value)
792 note_on (channel, note, 100./127.);
793 else
794 note_off (channel, note);
795 old_value = value;
796 }
797 }
798 void
799 render_voice (Voice *voice, uint n_frames, float *mix_left_out, float *mix_right_out)
800 {
801 float osc1_left_out[n_frames];
802 float osc1_right_out[n_frames];
803 float osc2_left_out[n_frames];
804 float osc2_right_out[n_frames];
805
806 update_osc (voice->osc1_, 0);
807 update_osc (voice->osc2_, 1);
808 voice->osc1_.process_sample_stereo (osc1_left_out, osc1_right_out, n_frames);
809 voice->osc2_.process_sample_stereo (osc2_left_out, osc2_right_out, n_frames);
810
811 // apply volume envelope & mix
812 const float mix_norm = get_param (MIX) * 0.01;
813 const float v1 = voice->vel_gain_ * (1 - mix_norm);
814 const float v2 = voice->vel_gain_ * mix_norm;
815 for (uint i = 0; i < n_frames; i++)
816 {
817 mix_left_out[i] = osc1_left_out[i] * v1 + osc2_left_out[i] * v2;
818 mix_right_out[i] = osc1_right_out[i] * v1 + osc2_right_out[i] * v2;
819 }
820 /* --------- run filter - processing in place is ok --------- */
821 double cutoff = convert_cutoff (get_param (CUTOFF));
822 double key_track = get_param (KEY_TRACK) * 0.01;
823
824 if (fabs (voice->last_cutoff_ - cutoff) > 1e-7 || fabs (voice->last_key_track_ - key_track) > 1e-7)
825 {
826 const bool reset = voice->last_cutoff_ < -1000;
827
828 // original strategy for key tracking: cutoff * exp (amount * log (key / 261.63))
829 // but since cutoff_smooth_ is already in log2-frequency space, we can do it better
830
831 voice->cutoff_smooth_.set (fast_log2 (cutoff) + key_track * fast_log2 (voice->freq_ / c3_hertz), reset);
832 voice->last_cutoff_ = cutoff;
833 voice->last_key_track_ = key_track;
834 }
835 double cut_mod = get_param (FIL_CUT_MOD) / 12.; /* convert semitones to octaves */
836 if (fabs (voice->last_cut_mod_ - cut_mod) > 1e-7)
837 {
838 const bool reset = voice->last_cut_mod_ < -1000;
839
840 voice->cut_mod_smooth_.set (cut_mod, reset);
841 voice->last_cut_mod_ = cut_mod;
842 }
843 double resonance = get_param (RESONANCE) * 0.01;
844 if (fabs (voice->last_reso_ - resonance) > 1e-7)
845 {
846 const bool reset = voice->last_reso_ < -1000;
847
848 voice->reso_smooth_.set (resonance, reset);
849 voice->last_reso_ = resonance;
850 }
851 double drive = get_param (DRIVE);
852 if (fabs (voice->last_drive_ - drive) > 1e-7)
853 {
854 const bool reset = voice->last_drive_ < -1000;
855
856 voice->drive_smooth_.set (drive, reset);
857 voice->last_drive_ = drive;
858 }
859
860 auto filter_process_block = [&] (auto& filter)
861 {
862 auto gen_filter_input = [&] (float *freq_in, float *reso_in, float *drive_in, uint n_frames)
863 {
864 voice->fil_envelope_.process (freq_in, n_frames);
865
866 for (uint i = 0; i < n_frames; i++)
867 {
868 freq_in[i] = fast_exp2 (voice->cutoff_smooth_.get_next() + freq_in[i] * voice->cut_mod_smooth_.get_next());
869 reso_in[i] = voice->reso_smooth_.get_next();
870 drive_in[i] = voice->drive_smooth_.get_next();
871 }
872 };
873
874 bool const_freq = voice->cutoff_smooth_.is_constant() && voice->fil_envelope_.is_constant() && voice->cut_mod_smooth_.is_constant();
875 bool const_reso = voice->reso_smooth_.is_constant();
876 bool const_drive = voice->drive_smooth_.is_constant();
877
878 if (const_freq && const_reso && const_drive)
879 {
880 /* use more efficient version of the filter computation if all parameters are constants */
881 float freq, reso, drive;
882 gen_filter_input (&freq, &reso, &drive, 1);
883
884 filter.set_freq (freq);
885 filter.set_reso (reso);
886 filter.set_drive (drive);
887 filter.process_block (n_frames, mix_left_out, mix_right_out);
888 }
889 else
890 {
891 /* generic version: pass per-sample values for freq, reso and drive */
892 float freq_in[n_frames], reso_in[n_frames], drive_in[n_frames];
893 gen_filter_input (freq_in, reso_in, drive_in, n_frames);
894
895 filter.process_block (n_frames, mix_left_out, mix_right_out, freq_in, reso_in, drive_in);
896 }
897 };
898
899 if (filter_type_ == FILTER_TYPE_LADDER)
900 {
901 voice->ladder_filter_.set_mode (LadderVCF::Mode (irintf (get_param (LADDER_MODE))));
902 filter_process_block (voice->ladder_filter_);
903 }
904 else if (filter_type_ == FILTER_TYPE_SKFILTER)
905 {
906 voice->skfilter_.set_mode (SKFilter::Mode (irintf (get_param (SKFILTER_MODE))));
907 filter_process_block (voice->skfilter_);
908 }
909 }
910 void
911 set_parameter_used (uint32_t id, bool used)
912 {
913 // TODO: implement this function to enable/disable parameters in the gui
914 // printf ("TODO: set parameter %d used flag to %s\n", int (id), used ? "true" : "false");
915 }
916 void
917 update_volume_envelope (Voice *voice)
918 {
919 voice->envelope_.set_attack (perc_to_s (get_param (ATTACK)));
920 voice->envelope_.set_decay (perc_to_s (get_param (DECAY)));
921 voice->envelope_.set_sustain (get_param (SUSTAIN)); /* percent */
922 voice->envelope_.set_release (perc_to_s (get_param (RELEASE)));
923 voice->envelope_.set_attack_slope (get_param (ATTACK_SLOPE) * 0.01);
924 voice->envelope_.set_decay_slope (get_param (DECAY_SLOPE) * 0.01);
925 voice->envelope_.set_release_slope (get_param (RELEASE_SLOPE) * 0.01);
926 }
927 void
928 update_filter_envelope (Voice *voice)
929 {
930 voice->fil_envelope_.set_attack (perc_to_s (get_param (FIL_ATTACK)));
931 voice->fil_envelope_.set_decay (perc_to_s (get_param (FIL_DECAY)));
932 voice->fil_envelope_.set_sustain (get_param (FIL_SUSTAIN)); /* percent */
933 voice->fil_envelope_.set_release (perc_to_s (get_param (FIL_RELEASE)));
934 }
935 void
936 render_audio (float *left_out, float *right_out, uint n_frames)
937 {
938 if (!n_frames)
939 return;
940
941 bool need_free = false;
942
943 for (Voice *voice : active_voices_)
944 {
945 if (voice->new_voice_)
946 {
947 int idelay = 0;
948 if (filter_type_ == FILTER_TYPE_LADDER)
949 idelay = voice->ladder_filter_.delay();
950 if (filter_type_ == FILTER_TYPE_SKFILTER)
951 idelay = voice->skfilter_.delay();
952 if (idelay)
953 {
954 // compensate FIR oversampling filter latency
955 float junk[idelay];
956 render_voice (voice, idelay, junk, junk);
957 }
958 voice->new_voice_ = false;
959 }
960 float mix_left_out[n_frames];
961 float mix_right_out[n_frames];
962
963 render_voice (voice, n_frames, mix_left_out, mix_right_out);
964
965 // apply volume envelope
966 float volume_env[n_frames];
967 voice->envelope_.process (volume_env, n_frames);
968 float post_gain_factor = db2voltage (get_param (POST_GAIN));
969 for (uint i = 0; i < n_frames; i++)
970 {
971 float amp = post_gain_factor * volume_env[i];
972 left_out[i] += mix_left_out[i] * amp;
973 right_out[i] += mix_right_out[i] * amp;
974 }
975 if (voice->envelope_.done())
976 {
977 voice->state_ = Voice::IDLE;
978 need_free = true;
979 }
980 }
981 if (need_free)
982 free_unused_voices();
983 }
984 void
985 render (uint n_frames) override
986 {
987 assert_return (n_ochannels (stereout_) == 2);
988
989 float *left_out = oblock (stereout_, 0);
990 float *right_out = oblock (stereout_, 1);
991
992 floatfill (left_out, 0.f, n_frames);
993 floatfill (right_out, 0.f, n_frames);
994
995 uint offset = 0;
996 MidiEventInput evinput = midi_event_input();
997 for (const auto &ev : evinput)
998 {
999 // process any audio that is before the event
1000 render_audio (left_out + offset, right_out + offset, ev.frame - offset);
1001 offset = ev.frame;
1002
1003 switch (ev.message())
1004 {
1005 case MidiMessage::NOTE_OFF:
1006 note_off (ev.channel, ev.key);
1007 break;
1008 case MidiMessage::NOTE_ON:
1009 note_on (ev.channel, ev.key, ev.velocity);
1010 break;
1011 case MidiMessage::ALL_NOTES_OFF:
1012 for (auto voice : active_voices_)
1013 if (voice->state_ == Voice::ON && voice->channel_ == ev.channel)
1014 note_off (voice->channel_, voice->midi_note_);
1015 break;
1016 case MidiMessage::PARAM_VALUE:
1017 apply_event (ev);
1018 adjust_param (ev.param);
1019 break;
1020 default: ;
1021 }
1022 }
1023 // process frames after last event
1024 render_audio (left_out + offset, right_out + offset, n_frames - offset);
1025 }
1026
1027 static double
1028 convert_cutoff (double midi_note)
1029 {
1030 return 440 * std::pow (2, (midi_note - 69) / 12.);
1031 }
1033 param_value_to_text (uint32_t paramid, double value) const override
1034 {
1035 /* fake step=1 */
1036 for (int oscnum = 0; oscnum < 2; oscnum++)
1037 {
1038 const uint O = oscnum * (OSC2_SHAPE - OSC1_SHAPE);
1039 if (paramid == O+OSC1_UNISON_VOICES)
1040 return string_format ("%d Voices", irintf (value));
1041 if (paramid == O+OSC1_OCTAVE)
1042 return string_format ("%d Octaves", irintf (value));
1043 }
1044 for (auto p : { ATTACK, DECAY, RELEASE, FIL_ATTACK, FIL_DECAY, FIL_RELEASE })
1045 if (paramid == p)
1046 return perc_to_str (value);
1047 if (paramid == CUTOFF)
1048 return hz_to_str (convert_cutoff (value));
1049
1050 return AudioProcessor::param_value_to_text (paramid, value);
1051 }
1052public:
1053 BlepSynth (const ProcessorSetup &psetup) :
1054 AudioProcessor (psetup)
1055 {}
1056 static void
1057 static_info (AudioProcessorInfo &info)
1058 {
1059 info.version = "1";
1060 info.label = "BlepSynth";
1061 info.category = "Synth";
1062 info.creator_name = "Stefan Westerfeld";
1063 info.website_url = "https://anklang.testbit.eu";
1064 }
1065};
1066static auto blepsynth = register_audio_processor<BlepSynth> ("Ase::Devices::BlepSynth");
1067
1068} // Anon
Audio signal AudioProcessor base class, implemented by all effects and instruments.
Definition processor.hh:76
float note_to_freq(int note) const
Convert MIDI note to Hertz according to the current MusicalTuning.
Definition processor.cc:123
double get_param(Id32 paramid)
Fetch value of parameter id.
Definition processor.hh:525
virtual String param_value_to_text(uint32_t paramid, double value) const
Definition processor.cc:336
MidiEventInput midi_event_input()
Access the current MidiEvent inputs during render(), needs prepare_event_input().
Definition processor.cc:844
OBusId add_output_bus(CString uilabel, SpeakerArrangement speakerarrangement, const String &hints="", const String &blurb="")
Add an output bus with uilabel and channels configured via speakerarrangement.
Definition processor.cc:530
virtual void initialize(SpeakerArrangement busses)=0
Definition processor.cc:432
void apply_event(const MidiEvent &event)
Assign MidiEvent::PARAM_VALUE event values to parameters.
Definition processor.hh:387
void install_params(const AudioParams::Map &params)
Reset list of parameters, enqueues parameter value initializaiton events.
Definition processor.cc:213
float * oblock(OBusId b, uint c)
Definition processor.hh:561
exp
fabs
#define assert_return(expr,...)
Return from the current function if expr is unmet and issue an assertion warning.
Definition internal.hh:29
#define CLAMP(v, mi, ma)
Yield v clamped to [mi … ma].
Definition internal.hh:58
#define _(...)
Retrieve the translation of a C or C++ string.
Definition internal.hh:18
log
T max(T... args)
The Anklang C++ API namespace.
Definition api.hh:9
std::string string_format(const char *format, const Args &...args) __attribute__((__format__(__printf__
Format a string similar to sprintf(3) with support for std::string and std::ostringstream convertible...
CString website_url
Website of/about this AudioProcessor.
Definition processor.hh:35
uint64_t uint64
A 64-bit unsigned integer, use PRI*64 in format strings.
Definition cxxaux.hh:25
void floatfill(float *dst, float f, size_t n)
Fill n values of dst with f.
Definition datautils.hh:29
SpeakerArrangement
Definition transport.hh:11
OBusId
ID type for AudioProcessor output buses, buses are numbered with increasing index.
Definition processor.hh:25
CString version
Version identifier.
Definition processor.hh:31
float fast_exp2(float x)
Definition mathutils.hh:82
constexpr const char GUIONLY[]
GUI READABLE WRITABLE.
Definition api.hh:12
CString creator_name
Name of the creator.
Definition processor.hh:36
CString label
Preferred user interface name.
Definition processor.hh:30
std::string String
Convenience alias for std::string.
Definition cxxaux.hh:35
int irintf(float f)
Definition mathutils.hh:14
uint32_t uint
Provide 'uint' as convenience type.
Definition cxxaux.hh:18
CString category
Category to allow grouping for processors of similar function.
Definition processor.hh:32
float fast_log2(float x)
Definition mathutils.hh:101
Float db2voltage(Float x)
Convert Decibel to synthesizer value (Voltage).
Detailed information and common properties of AudioProcessor subclasses.
Definition processor.hh:29
T pow(T... args)
typedef uint32_t
Structured initializer for Parameter.
Definition parameter.hh:31
Parameter list construction helper.
Definition parameter.hh:93
String group
Group to be applied to all newly inserted Parameter objects.
Definition parameter.hh:95