Anklang-0.3.0.dev967+g08f67ae3 anklang-0.3.0.dev967+g08f67ae3
ASE — Anklang Sound Engine (C++)

« « « Anklang Documentation
Loading...
Searching...
No Matches
bleposc.hh
Go to the documentation of this file.
1 // This Source Code Form is licensed MPL-2.0: http://mozilla.org/MPL/2.0
2#pragma once
3
4#include <ase/randomhash.hh>
5#include <ase/datautils.hh>
6
7namespace Ase {
8namespace BlepUtils {
9
10using std::max;
11
13{
14 double rate_;
15 double leaky_a;
16
17 static const int WIDTH = 13;
18 static const int WSHIFT = 6; // delay to align saw and impulse part of the signal
19 static const int OVERSAMPLE = 64;
20
21 static const float blep_table[WIDTH * OVERSAMPLE + 1];
22
23public:
24 double frequency_base = 440;
25 double frequency_factor = 1;
26
27 double freq_mod_octaves = 0;
28
29 double shape_base = 0; // 0 = saw, range [-1:1]
30 double shape_mod = 1.0;
31
32 double pulse_width_base = 0.5;
33 double pulse_width_mod = 0.0;
34
35 double sync_base = 0;
36 double sync_mod = 0;
37
38 double sub_base = 0;
39 double sub_mod = 0;
40
41 double sub_width_base = 0.5;
42 double sub_width_mod = 0.0;
43
44 bool need_reset_voice_state;
45
46 enum class State {
47 A,
48 B,
49 C,
50 D
51 };
52
54 {
55 double freq_factor = 1;
56 double left_factor = 1;
57 double right_factor = 0;
58
59 double master_phase = 0;
60 double slave_phase = 0;
61
62 double last_value = 0; /* leaky integrator state */
63 double current_level = 0; /* current position of the wave form (saw + jumps) */
64
65 double last_dc = 0; /* dc of previous parameters */
66 double dc_delta = 0;
67 int dc_steps = 0;
68
69 int future_pos = 0;
70
71
72 State state = State::A;
73
74 // could be shared under certain conditions
76
77 void
78 init_future()
79 {
80 future.fill (0);
81 future_pos = 0;
82 }
83 float
84 pop_future()
85 {
86 const float f = future[future_pos++];
87 if (future_pos == WIDTH)
88 {
89 // this loop was slightly faster than std::copy_n + std::fill_n
90 for (int i = 0; i < WIDTH; i++)
91 {
92 future[i] = future[WIDTH + i];
93 future[WIDTH + i] = 0;
94 }
95 future_pos = 0;
96 }
97 return f;
98 }
99 };
100
101 std::vector<UnisonVoice> unison_voices;
102
103 OscImpl()
104 {
105 set_unison (1, 0, 0); // default
106 }
107 void
108 reset()
109 {
110 const bool randomize_phase = unison_voices.size() > 1;
111
112 for (auto& voice : unison_voices)
113 {
114 voice.init_future();
115 voice.dc_steps = 0;
116 voice.dc_delta = 0;
117
118 if (randomize_phase) // randomize start phase for true unison
119 {
120 reset_master (voice, random_frange (0, 1));
121 }
122 else
123 {
124 reset_master (voice, 0);
125 }
126 }
127 }
128 void
129 reset_master (UnisonVoice& voice, double master_phase)
130 {
131 voice.master_phase = master_phase;
132 need_reset_voice_state = true;
133 }
134 void
135 set_unison (size_t n_voices, float detune, float stereo)
136 {
137 const bool unison_voices_changed = unison_voices.size() != n_voices;
138
139 unison_voices.resize (n_voices);
140
141 bool left_channel = true; /* start spreading voices at the left channel */
142 for (size_t i = 0; i < unison_voices.size(); i++)
143 {
144 if (n_voices == 1)
145 unison_voices[i].freq_factor = 1;
146 else
147 {
148 const float detune_cent = -detune / 2.0 + i / float (n_voices - 1) * detune;
149 unison_voices[i].freq_factor = pow (2, detune_cent / 1200);
150 }
151 /* stereo spread factors */
152 double left_factor, right_factor;
153 bool odd_n_voices = unison_voices.size() & 1;
154 if (odd_n_voices && i == unison_voices.size() / 2) // odd number of voices: this voice is centered
155 {
156 left_factor = (1 - stereo) + stereo * 0.5;
157 right_factor = (1 - stereo) + stereo * 0.5;
158 }
159 else if (left_channel) // alternate beween left and right voices
160 {
161 left_factor = 0.5 + stereo / 2;
162 right_factor = 0.5 - stereo / 2;
163 left_channel = false;
164 }
165 else
166 {
167 left_factor = 0.5 - stereo / 2;
168 right_factor = 0.5 + stereo / 2;
169 left_channel = true;
170 }
171 /* ensure constant total energy of left + right channel combined
172 *
173 * also take into account the more unison voices we add up, the louder the result
174 * will be, so compensate for this:
175 *
176 * -> each time the number of voices is doubled, the signal level is increased by
177 * a factor of sqrt (2)
178 */
179 const double norm = sqrt (left_factor * left_factor + right_factor * right_factor) * sqrt (n_voices / 2.0);
180 unison_voices[i].left_factor = left_factor / norm;
181 unison_voices[i].right_factor = right_factor / norm;
182 }
183 if (unison_voices_changed)
184 reset();
185 }
186 void
187 set_rate (double rate)
188 {
189 rate_ = rate;
190
191 /* get leaky integrator constant for sample rate from ms (half-life time) */
192 const double leaky_ms = 10;
193 leaky_a = pow (2.0, -1000.0 / (rate_ * leaky_ms));
194 }
195 double
196 rate()
197 {
198 return rate_;
199 }
200 double
201 estimate_dc (double shape,
202 double pulse_width,
203 double sub,
204 double sub_width,
205 double sync_factor)
206 {
207 const double bound_a = sub_width * pulse_width;
208 const double bound_b = 2 * sub_width * pulse_width + 1 - sub_width - pulse_width;
209 const double bound_c = sub_width * pulse_width + (1 - sub_width);
210 const double bound_d = 1.0;
211
212 const double saw_slope = -4.0 * (shape + 1) * (1 - sub);
213
214 const double a1 = 1;
215 const double a2 = a1 + saw_slope * bound_a;
216
217 const double b1 = a2 + 2.0 * (shape * (1 - sub) - sub);
218 const double b2 = b1 + saw_slope * (bound_b - bound_a);
219
220 const double c1 = b2 + 2 * (1 - sub);
221 const double c2 = c1 + saw_slope * (bound_c - bound_b);
222
223 const double d1 = c2 + 2.0 * (shape * (1 - sub) + sub);
224 const double d2 = d1 + saw_slope * (bound_d - bound_c);
225
226 /* dc without sync */
227 const double dc_base = (a1 + a2) / 2 * bound_a
228 + (b1 + b2) / 2 * (bound_b - bound_a)
229 + (c1 + c2) / 2 * (bound_c - bound_b)
230 + (d1 + d2) / 2 * (bound_d - bound_c);
231
232 /* quick path: no sync, no sync related dc computation */
233 if (sync_factor < 1.01)
234 return dc_base;
235
236 /* dc offset introduced by sync */
237 const double sync_phase = sync_factor - int (sync_factor);
238
239 double a_avg = (a1 + a2) / 2;
240 double b_avg = (b1 + b2) / 2;
241 double c_avg = (c1 + c2) / 2;
242 double d_avg = (d1 + d2) / 2;
243
244 if (sync_phase < bound_a)
245 {
246 const double frac = (bound_a - sync_phase) / bound_a;
247 const double sync_a2 = a1 * frac + a2 * (1 - frac);
248
249 a_avg = (1 - frac) * (a1 + sync_a2) / 2;
250 b_avg = c_avg = d_avg = 0;
251 }
252 else if (sync_phase < bound_b)
253 {
254 const double frac = (bound_b - sync_phase) / (bound_b - bound_a);
255 const double sync_b2 = b1 * frac + b2 * (1 - frac);
256
257 b_avg = (1 - frac) * (b1 + sync_b2) / 2;
258 c_avg = d_avg = 0;
259 }
260 else if (sync_phase < bound_c)
261 {
262 const double frac = (bound_c - sync_phase) / (bound_c - bound_b);
263 const double sync_c2 = c1 * frac + c2 * (1 - frac);
264
265 c_avg = (1 - frac) * (c1 + sync_c2) / 2;
266 d_avg = 0;
267 }
268 else
269 {
270 const double frac = (bound_d - sync_phase) / (bound_d - bound_c);
271 const double sync_d2 = d1 * frac + d2 * (1 - frac);
272
273 d_avg = (1 - frac) * (d1 + sync_d2) / 2;
274 }
275
276 /* dc sync part of the signal */
277 const double dc_sync = a_avg * bound_a
278 + b_avg * (bound_b - bound_a)
279 + c_avg * (bound_c - bound_b)
280 + d_avg * (bound_d - bound_c);
281 const double dc = (dc_base * (int) sync_factor + dc_sync) / sync_factor;
282
283 return dc;
284 }
285
286 void
287 reset_voice_state (double shape,
288 double pulse_width,
289 double sub,
290 double sub_width,
291 double sync_factor)
292 {
293 const double bound_a = sub_width * pulse_width;
294 const double bound_b = 2 * sub_width * pulse_width + 1 - sub_width - pulse_width;
295 const double bound_c = sub_width * pulse_width + (1 - sub_width);
296 const double bound_d = 1.0;
297
298 const double saw_slope = -4.0 * (shape + 1) * (1 - sub);
299
300 const double a1 = 1;
301 const double a2 = a1 + saw_slope * bound_a;
302
303 const double b1 = a2 + 2.0 * (shape * (1 - sub) - sub);
304 const double b2 = b1 + saw_slope * (bound_b - bound_a);
305
306 const double c1 = b2 + 2 * (1 - sub);
307 const double c2 = c1 + saw_slope * (bound_c - bound_b);
308
309 const double d1 = c2 + 2.0 * (shape * (1 - sub) + sub);
310 const double d2 = d1 + saw_slope * (bound_d - bound_c);
311
312 /* dc without sync */
313 const double dc_base = (a1 + a2) / 2 * bound_a
314 + (b1 + b2) / 2 * (bound_b - bound_a)
315 + (c1 + c2) / 2 * (bound_c - bound_b)
316 + (d1 + d2) / 2 * (bound_d - bound_c);
317
318 /* dc offset introduced by sync */
319 const double sync_phase = sync_factor - int (sync_factor);
320
321 double a_avg = (a1 + a2) / 2;
322 double b_avg = (b1 + b2) / 2;
323 double c_avg = (c1 + c2) / 2;
324 double d_avg = (d1 + d2) / 2;
325
326 if (sync_phase < bound_a)
327 {
328 const double frac = (bound_a - sync_phase) / bound_a;
329 const double sync_a2 = a1 * frac + a2 * (1 - frac);
330
331 a_avg = (1 - frac) * (a1 + sync_a2) / 2;
332 b_avg = c_avg = d_avg = 0;
333 }
334 else if (sync_phase < bound_b)
335 {
336 const double frac = (bound_b - sync_phase) / (bound_b - bound_a);
337 const double sync_b2 = b1 * frac + b2 * (1 - frac);
338
339 b_avg = (1 - frac) * (b1 + sync_b2) / 2;
340 c_avg = d_avg = 0;
341 }
342 else if (sync_phase < bound_c)
343 {
344 const double frac = (bound_c - sync_phase) / (bound_c - bound_b);
345 const double sync_c2 = c1 * frac + c2 * (1 - frac);
346
347 c_avg = (1 - frac) * (c1 + sync_c2) / 2;
348 d_avg = 0;
349 }
350 else
351 {
352 const double frac = (bound_d - sync_phase) / (bound_d - bound_c);
353 const double sync_d2 = d1 * frac + d2 * (1 - frac);
354
355 d_avg = (1 - frac) * (d1 + sync_d2) / 2;
356 }
357
358 /* dc sync part of the signal */
359 double dc_sync = a_avg * bound_a
360 + b_avg * (bound_b - bound_a)
361 + c_avg * (bound_c - bound_b)
362 + d_avg * (bound_d - bound_c);
363
364 const double dc = (dc_base * (int) sync_factor + dc_sync) / sync_factor;
365
366 for (auto& voice : unison_voices)
367 {
368 double dest_phase = voice.master_phase;
369
370 double last_value; /* leaky integrator state */
371
372 dest_phase *= sync_factor;
373 dest_phase -= (int) dest_phase;
374
375 voice.slave_phase = dest_phase;
376
377 /* compute voice state and initial value without dc */
378 if (dest_phase < bound_a)
379 {
380 double frac = (bound_a - dest_phase) / bound_a;
381 last_value = a1 * frac + a2 * (1 - frac);
382
383 voice.state = State::A;
384 }
385 else if (dest_phase < bound_b)
386 {
387 double frac = (bound_b - dest_phase) / (bound_b - bound_a);
388 last_value = b1 * frac + b2 * (1 - frac);
389
390 voice.state = State::B;
391 }
392 else if (dest_phase < bound_c)
393 {
394 double frac = (bound_c - dest_phase) / (bound_c - bound_b);
395 last_value = c1 * frac + c2 * (1 - frac);
396
397 voice.state = State::C;
398 }
399 else
400 {
401 double frac = (bound_d - dest_phase) / (bound_d - bound_c);
402 last_value = d1 * frac + d2 * (1 - frac);
403
404 voice.state = State::D;
405 }
406 voice.last_value = last_value - dc;
407 voice.last_dc = dc;
408 voice.current_level = last_value - 1;
409 }
410 }
411 void
412 insert_blep (UnisonVoice& voice, double frac, double weight)
413 {
414 int pos = frac * OVERSAMPLE;
415 const float inter_frac = frac * OVERSAMPLE - pos;
416 const float weight_left = (1 - inter_frac) * weight;
417 const float weight_right = inter_frac * weight;
418
419 pos = std::max (pos, 0);
420 pos = std::min (pos, OVERSAMPLE - 1);
421
422 for (int i = 0; i < WIDTH; i++)
423 {
424 voice.future[i + voice.future_pos] += blep_table[pos] * weight_left + blep_table[pos + 1] * weight_right;
425
426 pos += OVERSAMPLE;
427 }
428 }
429 void
430 insert_future_delta (UnisonVoice& voice, double weight)
431 {
432 voice.future[voice.future_pos + WSHIFT] += weight;
433 }
434
435 double
436 clamp (double d, double min, double max)
437 {
438 return ASE_CLAMP (d, min, max);
439 }
440
441 /* check if slave oscillator has reached target_phase
442 *
443 * when master oscillator sync occurs, only return true if this point in time is
444 * before master oscillator sync
445 */
446 bool
447 check_slave_before_master (UnisonVoice& voice, double target_phase, double sync_factor)
448 {
449 if (voice.slave_phase > target_phase)
450 {
451 if (voice.master_phase > 1)
452 {
453 const double slave_frac = (voice.slave_phase - target_phase) / sync_factor;
454 const double master_frac = voice.master_phase - 1;
455
456 return master_frac < slave_frac;
457 }
458 else
459 return true;
460 }
461 return false;
462 }
463 void
464 process_sample_stereo (float *left_out, float *right_out, unsigned int n_values,
465 const float *freq_in = nullptr,
466 const float *freq_mod_in = nullptr,
467 const float *shape_mod_in = nullptr,
468 const float *sub_mod_in = nullptr,
469 const float *sync_mod_in = nullptr,
470 const float *pulse_mod_in = nullptr,
471 const float *sub_width_mod_in = nullptr)
472 {
473 floatfill (left_out, 0.0, n_values);
474 floatfill (right_out, 0.0, n_values);
475
476 double master_freq = frequency_factor * frequency_base;
477 double pulse_width = clamp (pulse_width_base, 0.01, 0.99);
478 double sub = clamp (sub_base, 0.0, 1.0);
479 double sub_width = clamp (sub_width_base, 0.01, 0.99);
480 double shape = clamp (shape_base, -1.0, 1.0);
481 double sync_factor = fast_exp2 (clamp (sync_base, 0.0, 60.0) / 12);
482
483 /* dc substampling according to control frequency (cpu/quality trade off) */
484 const int dc_steps = max (irintf (rate_ / 4000), 1);
485
486 for (auto& voice : unison_voices)
487 {
488 const double master_freq2inc = 0.5 / rate_ * voice.freq_factor;
489
490 for (unsigned int n = 0; n < n_values; n++)
491 {
492 if (freq_in)
493 master_freq = frequency_factor * fast_voltage2hz (freq_in[n]);
494
495 double master_inc = master_freq * master_freq2inc;
496 if (freq_mod_in)
497 master_inc *= fast_exp2 (freq_mod_in[n] * freq_mod_octaves);
498
499 if (shape_mod_in)
500 shape = clamp (shape_base + shape_mod * shape_mod_in[n], -1.0, 1.0);
501
502 if (sub_mod_in)
503 sub = clamp (sub_base + sub_mod * sub_mod_in[n], 0.0, 1.0);
504
505 if (sync_mod_in)
506 sync_factor = fast_exp2 (clamp (sync_base + sync_mod * sync_mod_in[n], 0.0, 60.0) / 12);
507
508 if (pulse_mod_in)
509 pulse_width = clamp (pulse_width_base + pulse_width_mod * pulse_mod_in[n], 0.01, 0.99);
510
511 if (sub_width_mod_in)
512 sub_width = clamp (sub_width_base + sub_width_mod * sub_width_mod_in[n], 0.01, 0.99);
513
514 /* reset needs parameters, so we need to do it here */
515 if (need_reset_voice_state)
516 {
517 reset_voice_state (shape, pulse_width, sub, sub_width, sync_factor);
518 need_reset_voice_state = false;
519 }
520
521 const double slave_inc = master_inc * sync_factor;
522 const double saw_delta = -4.0 * slave_inc * (shape + 1) * (1 - sub);
523
524 voice.master_phase += master_inc;
525 voice.slave_phase += slave_inc;
526
527 bool state_changed;
528 do
529 {
530 state_changed = false;
531
532 if (voice.state == State::A)
533 {
534 const double bound_a = sub_width * pulse_width;
535
536 if (check_slave_before_master (voice, bound_a, sync_factor))
537 {
538 const double slave_frac = (voice.slave_phase - bound_a) / slave_inc;
539
540 const double jump_a = 2.0 * (shape * (1 - sub) - sub);
541 const double saw = -4.0 * (shape + 1) * (1 - sub) * bound_a;
542 const double blep_height = jump_a + saw - (voice.current_level + (1 - slave_frac) * saw_delta);
543
544 insert_blep (voice, slave_frac, blep_height);
545 voice.current_level += blep_height;
546 voice.state = State::B;
547 state_changed = true;
548 }
549 }
550 if (voice.state == State::B)
551 {
552 const double bound_b = 2 * sub_width * pulse_width + 1 - sub_width - pulse_width;
553
554 if (check_slave_before_master (voice, bound_b, sync_factor))
555 {
556 const double slave_frac = (voice.slave_phase - bound_b) / slave_inc;
557
558 const double jump_ab = 2.0 * ((shape + 1) * (1 - sub) - sub);
559 const double saw = -4.0 * (shape + 1) * (1 - sub) * bound_b;
560 const double blep_height = jump_ab + saw - (voice.current_level + (1 - slave_frac) * saw_delta);
561
562 insert_blep (voice, slave_frac, blep_height);
563 voice.current_level += blep_height;
564 voice.state = State::C;
565 state_changed = true;
566 }
567 }
568 if (voice.state == State::C)
569 {
570 const double bound_c = sub_width * pulse_width + (1 - sub_width);
571
572 if (check_slave_before_master (voice, bound_c, sync_factor))
573 {
574 const double slave_frac = (voice.slave_phase - bound_c) / slave_inc;
575
576 const double jump_abc = 2.0 * (2 * shape + 1) * (1 - sub);
577 const double saw = -4.0 * (shape + 1) * (1 - sub) * bound_c;
578 const double blep_height = jump_abc + saw - (voice.current_level + (1 - slave_frac) * saw_delta);
579
580 insert_blep (voice, slave_frac, blep_height);
581 voice.current_level += blep_height;
582 voice.state = State::D;
583 state_changed = true;
584 }
585 }
586 if (voice.state == State::D)
587 {
588 if (check_slave_before_master (voice, 1, sync_factor))
589 {
590 voice.slave_phase -= 1;
591
592 const double slave_frac = voice.slave_phase / slave_inc;
593
594 voice.current_level += (1 - slave_frac) * saw_delta;
595
596 insert_blep (voice, slave_frac, -voice.current_level);
597
598 voice.current_level = saw_delta * slave_frac - saw_delta;
599 voice.state = State::A;
600 state_changed = true;
601 }
602 }
603 if (!state_changed && voice.master_phase > 1)
604 {
605 voice.master_phase -= 1;
606
607 const double master_frac = voice.master_phase / master_inc;
608
609 const double new_slave_phase = voice.master_phase * sync_factor;
610
611 voice.current_level += (1 - master_frac) * saw_delta;
612
613 insert_blep (voice, master_frac, -voice.current_level);
614
615 voice.current_level = saw_delta * master_frac - saw_delta;
616 voice.slave_phase = new_slave_phase;
617
618 voice.state = State::A;
619 state_changed = true;
620 }
621 }
622 while (state_changed); // rerun all state checks if state was modified
623
624 if (voice.dc_steps > 0)
625 {
626 voice.dc_steps--;
627 }
628 else
629 {
630 const double dc = estimate_dc (shape, pulse_width, sub, sub_width, sync_factor);
631
632 voice.dc_steps = dc_steps - 1;
633 voice.dc_delta = (voice.last_dc - dc) / dc_steps;
634 voice.last_dc = dc;
635 }
636
637 voice.current_level += saw_delta;
638 insert_future_delta (voice, saw_delta + voice.dc_delta); // align with the impulses
639
640 /* leaky integration */
641 double value = leaky_a * voice.last_value + voice.pop_future();
642 voice.last_value = value;
643
644 left_out[n] += value * voice.left_factor;
645 right_out[n] += value * voice.right_factor;
646 }
647 }
648 }
649};
650
651class Osc /* simple interface to OscImpl */
652{
653 double
654 to_sync (double sync_factor)
655 {
656 return 12 * log (sync_factor) / log (2);
657 }
658public:
659 OscImpl osc_impl;
660
661 double rate;
662 double freq;
663 double pulse_width = 0.5;
664 double shape = 0; // 0 = saw, range [-1:1]
665 double sub = 0;
666 double sub_width = 0.5;
667
668 double master_freq;
669
670 void
671 set_unison (size_t n_voices, float detune, float stereo)
672 {
673 osc_impl.set_unison (n_voices, detune, stereo);
674 }
675 double
676 test_seek_to (double phase)
677 {
678 ASE_ASSERT_RETURN (osc_impl.unison_voices.size() > 0, 0);
679 osc_impl.reset();
680 osc_impl.reset_master (osc_impl.unison_voices[0], phase); // jump to phase
681
682 process_sample(); // propagate parameters & perform reset
683 return osc_impl.unison_voices[0].last_value;
684 }
685 double
686 process_sample()
687 {
688 float left, right;
689
690 process_sample_stereo (&left, &right);
691
692 return (left + right) / 2;
693 }
694 void
695 process_sample_stereo (float *left_out, float *right_out)
696 {
697 osc_impl.set_rate (rate);
698 osc_impl.sync_base = to_sync (freq / master_freq);
699 osc_impl.pulse_width_base = pulse_width;
700 osc_impl.shape_base = shape;
701 osc_impl.sub_base = sub;
702 osc_impl.sub_width_base = sub_width;
703 osc_impl.frequency_base = master_freq;
704
705 return osc_impl.process_sample_stereo (left_out, right_out, 1);
706 }
707};
708
709} // BlepUtils
710} // Ase
711
#define ASE_CLAMP(v, mi, ma)
Yield v clamped to [ mi .. ma ].
Definition cxxaux.hh:49
#define ASE_ASSERT_RETURN(expr,...)
Return from the current function if expr evaluates to false and issue an assertion warning.
Definition cxxaux.hh:81
typedef int
log
typedef float
T max(T... args)
T min(T... args)
The Anklang C++ API namespace.
Definition api.hh:8
void floatfill(float *dst, float f, size_t n)
Fill n values of dst with f.
Definition datautils.hh:28
float fast_voltage2hz(float x)
Float precision variant of voltage2hz using fast_exp2().
Definition signalmath.hh:79
float fast_exp2(float x)
Fast approximation of 2 raised to the power of x.
Definition mathutils.hh:81
int irintf(float f)
Round float to int, using round-to-nearest Fast version of f < 0 ? int (f - 0.5) : int (f + 0....
Definition mathutils.hh:13
double random_frange(double begin, double end)
Generate uniformly distributed pseudo-random floating point number within a range.
pow
sqrt