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

« « « Anklang Documentation
Loading...
Searching...
No Matches
processor.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 "processor.hh"
3#include "combo.hh"
4#include "device.hh"
5#include "main.hh" // feature_toggle_find
6#include "utils.hh"
7#include "strings.hh"
8#include "engine.hh"
9#include "internal.hh"
10#include <shared_mutex>
11
12#define PDEBUG(...) Ase::debug ("processor", __VA_ARGS__)
13
14namespace Ase {
15
16// == PBus ==
17AudioProcessor::IOBus::IOBus (IOTag io_tag, const String &ident, const String &uilabel, SpeakerArrangement sa) :
18 BusInfo (ident, uilabel, "", "", sa), iotag (io_tag)
19{
20 switch (iotag)
21 {
22 case IBUS:
23 assert_return (ibus == IBUS);
24 obusid = {};
25 oproc = nullptr;
26 break;
27 case OBUS:
28 assert_return (obus == OBUS);
29 fbuffer_concounter = 0;
30 fbuffer_count = 0;
31 fbuffer_index = ~0;
32 break;
33 }
34 assert_return (ident != "");
35}
36
37// == AudioParams ==
38AudioParams::~AudioParams()
39{
40 clear();
41}
42
44void
45AudioParams::clear()
46{
47 changed = false;
48 count = 0;
49 delete[] ids;
50 ids = nullptr;
51 delete[] values;
52 values = nullptr;
53 delete[] bits;
54 bits = nullptr;
55 const ParameterC *old_parameters = nullptr;
56 std::swap (old_parameters, parameters);
57 std::weak_ptr<Property> *old_wprops = nullptr;
58 std::swap (old_wprops, wprops);
59 delete[] old_wprops;
60 delete[] old_parameters; // call ~ParameterC *after* *this is cleaned up
61}
62
64void
65AudioParams::install (const AudioParams::Map &params)
66{
67 assert_return (this_thread_is_ase());
68 clear();
69 count = params.size();
70 if (count == 0)
71 return;
72 // ids
73 uint32_t *new_ids = new uint32_t[count] ();
74 size_t i = 0;
75 for (auto [id,p] : params)
76 new_ids[i++] = id;
77 ids = new_ids;
78 assert_return (i == count);
79 // wprops
80 wprops = new std::weak_ptr<Property>[count] ();
81 // parameters
82 ParameterC *new_parameters = new ParameterC[count] ();
83 i = 0;
84 for (auto [id,p] : params)
85 new_parameters[i++] = p;
86 parameters = new_parameters;
87 assert_return (i == count);
88 // values
89 values = new double[count] (); // value-initialized per ISO C++03 5.3.4[expr.new]/15
90 for (size_t i = 0; i < count; i++)
91 values[i] = parameters[i]->initial().as_double();
92 // bits
93 const size_t u = (count + 64-1) / 64; // number of uint64_t bit fields
94 bits = new std::atomic<uint64_t>[u] (); // a bit array causes vastly fewer cache misses
95}
96
97// == AudioProcessor ==
98const String AudioProcessor::GUIONLY = ":G:r:w:";
99const String AudioProcessor::STANDARD = ":G:S:r:w:";
100const String AudioProcessor::STORAGEONLY = ":S:r:w:";
101uint64 __thread AudioProcessor::tls_timestamp = 0;
102
104AudioProcessor::AudioProcessor (const ProcessorSetup &psetup) :
105 engine_ (psetup.engine), aseid_ (psetup.aseid)
106{
107 engine_.processor_count_ += 1;
108}
109
112{
114 engine_.processor_count_ -= 1;
115 delete atomic_bits_;
116 MidiEventVector *t0events = nullptr;
117 t0events = t0events_.exchange (t0events);
118 delete t0events;
119}
120
122float
124{
125 return MidiNote::note_to_freq (MusicalTuning::OD_12_TET, note, 440);
126}
127
129DeviceP
131{
132 std::weak_ptr<Device> &wptr = const_cast<AudioProcessor*> (this)->device_;
133 DeviceP devicep = wptr.lock();
134 return devicep;
135}
136
138AudioProcessor::zero_buffer()
139{
140 static const FloatBuffer const_zero_float_buffer {};
141 return const_zero_float_buffer;
142}
143
145AudioProcessor::IOBus&
147{
148 static const uint64 dummy_iobusmem[sizeof (AudioProcessor::IOBus)] = { 0, };
149 auto &dummy_iobus = const_cast<IOBus&> (*reinterpret_cast<const IOBus*> (dummy_iobusmem));
150 const size_t busindex = size_t (obusid) - 1;
151 assert_return (busindex < n_obuses(), dummy_iobus);
152 IOBus &bus = iobuses_[output_offset_ + busindex];
153 assert_warn (bus.obus == IOBus::OBUS);
154 return bus;
155}
156
160{
161 static const uint64 dummy_iobusmem[sizeof (AudioProcessor::IOBus)] = { 0, };
162 auto &dummy_iobus = const_cast<IOBus&> (*reinterpret_cast<const IOBus*> (dummy_iobusmem));
163 const size_t busindex = size_t (ibusid) - 1;
164 assert_return (busindex < n_ibuses(), dummy_iobus);
165 IOBus &bus = iobuses_[busindex];
166 assert_warn (bus.ibus == IOBus::IBUS);
167 return bus;
168}
169
170// Release buffers allocated for input/output channels.
171void
172AudioProcessor::release_iobufs()
173{
176 for (OBusId ob = OBusId (1); size_t (ob) <= n_obuses(); ob = OBusId (size_t (ob) + 1))
177 {
178 IOBus &bus = iobus (ob);
179 bus.fbuffer_index = ~0;
180 bus.fbuffer_count = 0;
181 }
182 // trivial destructors (of FloatBuffer) need not be called
183 static_assert (std::is_trivially_destructible<decltype (fbuffers_[0])>::value);
184 fast_mem_free (fbuffers_);
185 fbuffers_ = nullptr;
186}
187
188// Allocate and assign output channel buffers.
189void
190AudioProcessor::assign_iobufs ()
191{
192 ssize_t ochannel_count = 0;
193 for (OBusId ob = OBusId (1); size_t (ob) <= n_obuses(); ob = OBusId (size_t (ob) + 1))
194 {
195 IOBus &bus = iobus (ob);
196 bus.fbuffer_index = ochannel_count;
197 bus.fbuffer_count = bus.n_channels();
198 ochannel_count += bus.fbuffer_count;
199 }
200 fast_mem_free (fbuffers_);
201 if (ochannel_count > 0)
202 {
203 fbuffers_ = (FloatBuffer*) fast_mem_alloc (ochannel_count * sizeof (FloatBuffer));
204 for (ssize_t i = 0; i < ochannel_count; i++)
205 new (fbuffers_ + i) FloatBuffer();
206 }
207 else
208 fbuffers_ = nullptr;
209}
210
212void
214{
215 assert_return (this_thread_is_ase());
216 params_.install (params);
217 modify_t0events ([&] (std::vector<MidiEvent> &t0events) {
218 for (size_t i = 0; i < params_.count; i++)
219 t0events.push_back (make_param_value (params_.ids[i], params_.parameters[i]->initial().as_double()));
220 });
221}
222
224auto
226{
227 auto ident = CString::lookup (identifier);
228 if (ident.empty())
229 return { ParamId (0), false };
230 for (size_t idx = 0; idx < params_.count; idx++)
231 if (params_.parameters[idx]->cident == ident)
232 return { ParamId (params_.ids[idx]), true };
233 return std::make_pair (ParamId (0), false);
234}
235
237bool
238AudioProcessor::send_param (Id32 paramid, const double value)
239{
240 assert_return (this_thread_is_ase(), false); // main_loop thread
241 const ssize_t idx = params_.index (paramid.id);
242 if (idx < 0) [[unlikely]]
243 return false;
244 ParameterC parameter = params_.parameters[idx];
245 double v = value;
246 if (parameter)
247 v = parameter->dconstrain (value);
248 modify_t0events ([&] (std::vector<MidiEvent> &t0events) {
249 for (auto &ev : t0events)
250 if (ev.type == MidiEvent::PARAM_VALUE &&
251 ev.param == params_.ids[idx]) {
252 ev.pvalue = v;
253 return; // re-assigned previous send_param event
254 }
255 t0events.push_back (make_param_value (params_.ids[idx], v));
256 });
257 return true;
258}
259
263{
264 const ssize_t idx = params_.index (paramid.id);
265 return idx < 0 ? nullptr : params_.parameters[idx];
266}
267
271double
273{
274 const ssize_t idx = params_.index (paramid.id);
275 if (idx < 0) [[unlikely]]
276 return false;
277 return params_.values[idx];
278}
279
282double
283AudioProcessor::param_peek_mt (const AudioProcessorP proc, Id32 paramid)
284{
285 assert_return (proc, FP_NAN);
286 assert_return (proc->is_initialized(), FP_NAN);
287 return proc->peek_param_mt (paramid);
288}
289
290double
291AudioProcessor::value_to_normalized (Id32 paramid, double value) const
292{
293 ParameterC parameterp = parameter (paramid);
294 assert_return (parameterp != nullptr, 0);
295 const auto [fmin, fmax, step] = parameterp->range();
296 const double normalized = (value - fmin) / (fmax - fmin);
297 assert_return (normalized >= 0.0 && normalized <= 1.0, CLAMP (normalized, 0.0, 1.0));
298 return normalized;
299}
300
301double
302AudioProcessor::value_from_normalized (Id32 paramid, double normalized) const
303{
304 ParameterC parameterp = parameter (paramid);
305 assert_return (parameterp != nullptr, 0);
306 const auto [fmin, fmax, step] = parameterp->range();
307 const double value = fmin + normalized * (fmax - fmin);
308 assert_return (normalized >= 0.0 && normalized <= 1.0, value);
309 return value;
310}
311
313double
315{
316 return value_to_normalized (paramid, get_param (paramid));
317}
318
320bool
321AudioProcessor::set_normalized (Id32 paramid, double normalized)
322{
323 if (!ASE_ISLIKELY (normalized >= 0.0))
324 normalized = 0;
325 else if (!ASE_ISLIKELY (normalized <= 1.0))
326 normalized = 1.0;
327 return send_param (paramid, value_from_normalized (paramid, normalized));
328}
329
335String
336AudioProcessor::param_value_to_text (uint32_t paramid, double value) const
337{
338 ParameterC parameterp = parameter (paramid);
339 return parameterp ? parameterp->value_to_text (value) : "";
340}
341
350double
351AudioProcessor::param_value_from_text (uint32_t paramid, const String &text) const
352{
353 ParameterC parameterp = parameter (paramid);
354 return parameterp ? parameterp->value_from_text (text).as_double() : 0.0;
355}
356
358uint
359AudioProcessor::text_param_to_quark (uint32_t paramid, const String &text)
360{
361 assert_return (this_thread_is_ase(), 0);
362 ParameterC parameterp = parameter (paramid);
363 if (!parameterp->is_text())
364 return 0;
365 const CString cstr = text; // uint mapping
366 cstrings0_.push_back (cstr); // keep alive during event queue references
367 return CString::temp_quark_impl (cstr);
368}
369
371String
373{
374 const CString cstr = CString::temp_quark_impl (vint);
375 return cstr;
376}
377
379void
381{
382 if (atomic_bits_)
383 delete atomic_bits_;
384 atomic_bits_ = new AtomicBits (count);
385}
386
388bool
390{
391 assert_return (atomic_bits_ && nth < atomic_bits_->size() * 64, false);
392 const bool last = atomic_bits_->iter (nth).set (1);
393 const bool need_wakeup = last == false;
394 return need_wakeup;
395}
396
400{
401 return_unless (atomic_bits_ && pos < atomic_bits_->size() * 64, {});
402 return atomic_bits_->iter (pos);
403}
404
406bool
408{
409 return flags_ & INITIALIZED;
410}
411
415{
416 ParameterC parameterc = parameter (paramid);
417 return_unless (parameterc, MinMax { FP_NAN, FP_NAN });
418 const auto [fmin, fmax, step] = parameterc->range();
419 return MinMax { fmin, fmax };
420}
421
422String
423AudioProcessor::debug_name () const
424{
425 return typeid_name (*this);
426}
427
431void
436
438void
440{
441 engine_.schedule_queue_update();
442}
443
445void
447{
448 if (onoff)
450 engine_.enable_output (*this, onoff);
451}
452
455void
457{
458 if (!estreams_)
459 estreams_ = new EventStreams();
460 assert_return (estreams_->has_event_input == false);
461 estreams_->has_event_input = true;
462}
463
466void
468{
469 if (!estreams_)
470 estreams_ = new EventStreams();
471 assert_return (estreams_->has_event_output == false);
472 estreams_->has_event_output = true;
473}
474
476void
478{
479 if (estreams_ && estreams_->oproc)
480 {
481 AudioProcessor &oproc = *estreams_->oproc;
482 assert_return (oproc.estreams_);
483 const bool backlink = Aux::erase_first (oproc.outputs_, [&] (auto &e) { return e.proc == this && e.ibusid == EventStreams::EVENT_ISTREAM; });
484 estreams_->oproc = nullptr;
485 reschedule();
486 assert_return (backlink == true);
487 enotify_enqueue_mt (BUSDISCONNECT);
488 oproc.enotify_enqueue_mt (BUSDISCONNECT);
489 }
490}
491
493void
495{
498 if (estreams_ && estreams_->oproc)
500 estreams_->oproc = &oproc;
501 // register backlink
502 oproc.outputs_.push_back ({ this, EventStreams::EVENT_ISTREAM });
503 reschedule();
504 enotify_enqueue_mt (BUSCONNECT);
505 oproc.enotify_enqueue_mt (BUSCONNECT);
506}
507
509IBusId
511 const String &hints, const String &blurb)
512{
514 assert_return (uilabel != "", {});
515 assert_return (uint64_t (speaker_arrangement_channels (speakerarrangement)) > 0, {});
516 assert_return (iobuses_.size() < 65535, {});
517 if (n_ibuses())
518 assert_return (uilabel != iobus (IBusId (n_ibuses())).label, {}); // easy CnP error
519 IOBus bus { IOBus::IBUS, string_to_identifier (uilabel), uilabel, speakerarrangement };
520 bus.hints = hints;
521 bus.blurb = blurb;
522 iobuses_.insert (iobuses_.begin() + output_offset_, bus);
523 output_offset_ += 1;
524 const IBusId id = IBusId (n_ibuses());
525 return id; // 1 + index
526}
527
529OBusId
531 const String &hints, const String &blurb)
532{
534 assert_return (uilabel != "", {});
535 assert_return (uint64_t (speaker_arrangement_channels (speakerarrangement)) > 0, {});
536 assert_return (iobuses_.size() < 65535, {});
537 if (n_obuses())
538 assert_return (uilabel != iobus (OBusId (n_obuses())).label, {}); // easy CnP error
539 IOBus bus { IOBus::OBUS, string_to_identifier (uilabel), uilabel, speakerarrangement };
540 bus.hints = hints;
541 bus.blurb = blurb;
542 iobuses_.push_back (bus);
543 const OBusId id = OBusId (n_obuses());
544 return id; // 1 + index
545}
546
548IBusId
549AudioProcessor::find_ibus (const String &uilabel) const
550{
551 auto ident = CString::lookup (uilabel);
552 if (!ident.empty())
553 for (IBusId ib = IBusId (1); size_t (ib) <= n_ibuses(); ib = IBusId (size_t (ib) + 1))
554 if (iobus (ib).ident == ident)
555 return ib;
556 return IBusId (0);
557}
558
560OBusId
561AudioProcessor::find_obus (const String &uilabel) const
562{
563 auto ident = CString::lookup (uilabel);
564 if (!ident.empty())
565 for (OBusId ob = OBusId (1); size_t (ob) <= n_obuses(); ob = OBusId (size_t (ob) + 1))
566 if (iobus (ob).ident == ident)
567 return ob;
568 return OBusId (0);
569}
570
575AudioProcessor::float_buffer (IBusId busid, uint channelindex) const
576{
577 const size_t ibusindex = size_t (busid) - 1;
578 assert_return (ibusindex < n_ibuses(), zero_buffer());
579 const IOBus &ibus = iobus (busid);
580 if (ibus.oproc)
581 {
582 const AudioProcessor &oproc = *ibus.oproc;
583 const IOBus &obus = oproc.iobus (ibus.obusid);
584 if (ASE_UNLIKELY (channelindex >= obus.fbuffer_count))
585 channelindex = obus.fbuffer_count - 1;
586 return oproc.fbuffers_[obus.fbuffer_index + channelindex];
587 }
588 return zero_buffer();
589}
590
595AudioProcessor::FloatBuffer&
596AudioProcessor::float_buffer (OBusId obusid, uint channelindex, bool resetptr)
597{
598 const size_t obusindex = size_t (obusid) - 1;
599 FloatBuffer *const fallback = const_cast<FloatBuffer*> (&zero_buffer());
600 assert_return (obusindex < n_obuses(), *fallback);
601 const IOBus &obus = iobus (obusid);
602 assert_return (channelindex < obus.fbuffer_count, *fallback);
603 FloatBuffer &fbuffer = fbuffers_[obus.fbuffer_index + channelindex];
604 if (resetptr)
605 fbuffer.buffer = &fbuffer.fblock[0];
606 return fbuffer;
607}
608
610void
611AudioProcessor::redirect_oblock (OBusId obusid, uint channelindex, const float *block)
612{
613 const size_t obusindex = size_t (obusid) - 1;
614 assert_return (obusindex < n_obuses());
615 const IOBus &obus = iobus (obusid);
616 assert_return (channelindex < obus.fbuffer_count);
617 FloatBuffer &fbuffer = fbuffers_[obus.fbuffer_index + channelindex];
618 if (block != nullptr)
619 fbuffer.buffer = const_cast<float*> (block);
620 else
621 fbuffer.buffer = const_float_zeros;
622 static_assert (sizeof (const_float_zeros) / sizeof (const_float_zeros[0]) >= AUDIO_BLOCK_MAX_RENDER_SIZE);
623}
624
626void
628{
629 float *const buffer = oblock (b, c);
630 floatfill (buffer, v, AUDIO_BLOCK_MAX_RENDER_SIZE);
631 // TODO: optimize assign_oblock() via redirect to const value blocks
632}
633
636bool
638{
639 const size_t obusindex = size_t (obusid) - 1;
640 assert_return (obusindex < n_obuses(), false);
641 return iobus (obusid).fbuffer_concounter;
642}
643
645void
647{
648 release_iobufs();
649 iobuses_.clear();
650 output_offset_ = 0;
651 if (estreams_)
652 {
653 assert_return (!estreams_->oproc && outputs_.empty());
654 delete estreams_; // must be disconnected beforehand
655 estreams_ = nullptr;
656 reschedule();
657 }
658}
659
661void
663{
664 disconnect (EventStreams::EVENT_ISTREAM);
665 if (n_ibuses())
666 reschedule();
667 for (size_t i = 0; i < n_ibuses(); i++)
668 disconnect (IBusId (1 + i));
669}
670
672void
674{
675 return_unless (fbuffers_);
676 if (outputs_.size())
677 reschedule();
678 while (outputs_.size())
679 {
680 const auto o = outputs_.back();
681 o.proc->disconnect (o.ibusid);
682 }
683}
684
686void
688{
689 if (EventStreams::EVENT_ISTREAM == ibusid)
690 return disconnect_event_input();
691 const size_t ibusindex = size_t (ibusid) - 1;
692 assert_return (ibusindex < n_ibuses());
693 IOBus &ibus = iobus (ibusid);
694 // disconnect
695 return_unless (ibus.oproc != nullptr);
696 AudioProcessor &oproc = *ibus.oproc;
697 const size_t obusindex = size_t (ibus.obusid) - 1;
698 assert_return (obusindex < oproc.n_obuses());
699 IOBus &obus = oproc.iobus (ibus.obusid);
700 assert_return (obus.fbuffer_concounter > 0);
701 obus.fbuffer_concounter -= 1; // connection counter
702 const bool backlink = Aux::erase_first (oproc.outputs_, [&] (auto &e) { return e.proc == this && e.ibusid == ibusid; });
703 ibus.oproc = nullptr;
704 ibus.obusid = {};
705 reschedule();
706 assert_return (backlink == true);
707 enotify_enqueue_mt (BUSDISCONNECT);
708 oproc.enotify_enqueue_mt (BUSDISCONNECT);
709}
710
712void
714{
715 const size_t ibusindex = size_t (ibusid) - 1;
716 assert_return (ibusindex < n_ibuses());
717 const size_t obusindex = size_t (obusid) - 1;
718 assert_return (obusindex < oproc.n_obuses());
719 disconnect (ibusid);
720 IOBus &ibus = iobus (ibusid);
721 const uint n_ichannels = ibus.n_channels();
722 IOBus &obus = oproc.iobus (obusid);
723 const uint n_ochannels = obus.n_channels();
724 // match channels
726 (ibus.speakers == SpeakerArrangement::STEREO &&
728 // connect
729 ibus.oproc = &oproc;
730 ibus.obusid = obusid;
731 // register backlink
732 obus.fbuffer_concounter += 1; // conection counter
733 oproc.outputs_.push_back ({ this, ibusid });
734 reschedule();
735 enotify_enqueue_mt (BUSCONNECT);
736 oproc.enotify_enqueue_mt (BUSCONNECT);
737}
738
740void
741AudioProcessor::ensure_initialized (DeviceP devicep)
742{
743 if (!is_initialized())
744 {
745 assert_return (n_ibuses() + n_obuses() == 0);
746 assert_return (get_device() == nullptr);
747 device_ = devicep;
748 initialize (engine_.speaker_arrangement());
749 flags_ |= INITIALIZED;
750 if (n_ibuses() + n_obuses() == 0 &&
751 (!estreams_ || (!estreams_->has_event_input && !estreams_->has_event_output)))
752 warning ("AudioProcessor::%s: initialize() failed to add input/output busses for: %s", __func__, debug_name());
753 assign_iobufs();
754 reset_state (engine_.frame_counter());
755 }
756 const bool has_event_input = estreams_ && estreams_->has_event_input;
757 const bool has_event_output = estreams_ && estreams_->has_event_output;
759}
760
762void
763AudioProcessor::reset_state (uint64 target_stamp)
764{
765 if (render_stamp_ != target_stamp)
766 {
767 if (estreams_)
768 estreams_->midi_event_output.clear();
769 reset (target_stamp);
770 render_stamp_ = target_stamp;
771 }
772}
773
775void
776AudioProcessor::reset (uint64 target_stamp)
777{}
778
780uint
782{
783 uint level = 0;
784 if (estreams_ && estreams_->oproc)
785 {
786 const uint l = schedule_processor (*estreams_->oproc);
787 level = std::max (level, l);
788 }
789 for (size_t i = 0; i < n_ibuses(); i++)
790 {
791 IOBus &ibus = iobus (IBusId (1 + i));
792 if (ibus.oproc)
793 {
794 const uint l = schedule_processor (*ibus.oproc);
795 level = std::max (level, l);
796 }
797 }
798 const uint l = schedule_children();
799 level = std::max (level, l);
800 engine_.schedule_add (*this, level);
801 return level + 1;
802}
803
805 MidiEventVector *render_events = nullptr;
806};
807
817void
818AudioProcessor::render (uint32 n_frames)
819{}
820
821void
822AudioProcessor::render_block (uint64 target_stamp)
823{
824 return_unless (render_stamp_ < target_stamp);
825 return_unless (target_stamp - render_stamp_ <= AUDIO_BLOCK_MAX_RENDER_SIZE);
826 RenderContext rc;
827 if (ASE_UNLIKELY (estreams_))
828 estreams_->midi_event_output.clear();
829 rc.render_events = t0events_.exchange (rc.render_events); // fetch t0events_ for rendering
830 render_context_ = &rc;
831 render (target_stamp - render_stamp_);
832 render_context_ = nullptr;
833 render_stamp_ = target_stamp;
834 if (rc.render_events) // delete in main_thread
835 main_rt_jobs += RtCall (call_delete<MidiEventVector>, rc.render_events);
836 if (params_.changed) {
837 params_.changed = false;
838 enotify_enqueue_mt (PARAMCHANGE);
839 }
840}
841
843AudioProcessor::MidiEventInput
845{
846 MidiEventInput::VectorArray mev_array{};
847 size_t n = 0;
848 if (estreams_ && estreams_->oproc && estreams_->oproc->estreams_)
849 mev_array[n++] = &estreams_->oproc->estreams_->midi_event_output.vector();
850 if (render_context_->render_events)
851 mev_array[n++] = render_context_->render_events;
852 return MidiEventInput (mev_array);
853}
854
855static const MidiEventOutput empty_event_output; // dummy
856
860{
861 assert_return (estreams_ != nullptr, const_cast<MidiEventOutput&> (empty_event_output));
862 return estreams_->midi_event_output;
863}
864
865// == Registry ==
868 void (*static_info) (AudioProcessorInfo&) = nullptr;
869 AudioProcessor::MakeProcessorP make_shared = nullptr;
870 const AudioProcessorRegistry* next = nullptr;
871};
872
874
878void
879AudioProcessor::registry_add (CString aseid, StaticInfo static_info, MakeProcessorP makeproc)
880{
881 assert_return (string_startswith (aseid, "Ase::"));
883 entry->aseid = aseid;
884 entry->static_info = static_info;
885 entry->make_shared = makeproc;
886 // push_front via compare_exchange (*obj, *expected, desired)
887 while (!std::atomic_compare_exchange_strong (&registry_first, &entry->next, entry))
888 ; // if (*obj==*expected) *obj=desired; else *expected=*obj;
889}
890
891DeviceP
892AudioProcessor::registry_create (CString aseid, AudioEngine &engine, const MakeDeviceP &makedevice)
893{
894 for (const AudioProcessorRegistry *entry = registry_first; entry; entry = entry->next)
895 if (entry->aseid == aseid) {
896 AudioProcessorP aproc = entry->make_shared (aseid, engine);
897 if (aproc) {
898 DeviceP devicep = makedevice (aseid, entry->static_info, aproc);
899 aproc->ensure_initialized (devicep);
900 return devicep;
901 }
902 }
903 warning ("AudioProcessor::registry_create: failed to create processor: %s", aseid);
904 return nullptr;
905}
906
908void
909AudioProcessor::registry_foreach (const std::function<void (const String &aseid, StaticInfo)> &fun)
910{
911 for (const AudioProcessorRegistry *entry = registry_first; entry; entry = entry->next)
912 fun (entry->aseid, entry->static_info);
913}
914
915// == FloatBuffer ==
917void
918AudioProcessor::FloatBuffer::check ()
919{
920#pragma GCC diagnostic push
921#pragma GCC diagnostic ignored "-Winvalid-offsetof"
922 // verify cache-line aligned struct layout
923 static_assert (0 == (offsetof (FloatBuffer, fblock[0]) & 63));
924 static_assert (0 == (offsetof (FloatBuffer, canary0_) & 63));
925 static_assert (0 == ((sizeof (buffer) + offsetof (FloatBuffer, buffer)) & 63));
926#pragma GCC diagnostic pop
927 // verify cache-line aligned runtime layout
928 assert_return (0 == (uintptr_t (&buffer[0]) & 63));
929 // failing canaries indicate end-of-buffer overwrites
930 assert_return (canary0_ == const_canary);
931 assert_return (canary1_ == const_canary);
932}
933
934// == AudioPropertyImpl ==
935class AudioPropertyImpl : public Property, public virtual EmittableImpl {
936 DeviceP device_;
937 ParameterC parameter_;
938 const uint32_t id_;
939 double inflight_value_ = 0;
940 uint64_t inflight_stamp_ = 0;
941public:
942 String ident () const override { return parameter_->ident(); }
943 String label () const override { return parameter_->label(); }
944 String nick () const override { return parameter_->nick(); }
945 String unit () const override { return parameter_->unit(); }
946 String hints () const { return parameter_->hints(); }
947 String group () const { return parameter_->group(); }
948 String blurb () const { return parameter_->blurb(); }
949 String descr () const { return parameter_->descr(); }
950 double get_min () const override { const auto [fmin, fmax, step] = parameter_->range(); return fmin; }
951 double get_max () const override { const auto [fmin, fmax, step] = parameter_->range(); return fmax; }
952 double get_step () const override { const auto [fmin, fmax, step] = parameter_->range(); return step; }
953 explicit
954 AudioPropertyImpl (DeviceP devp, uint32_t id, ParameterC parameter) :
955 device_ (devp), parameter_ (parameter), id_ (id)
956 {}
957 void
958 proc_paramchange()
959 {
960 const AudioProcessorP proc = device_->_audio_processor();
961 const double value = inflight_stamp_ > proc->engine().frame_counter() ? inflight_value_ : AudioProcessor::param_peek_mt (proc, id_);
962 ValueR vfields;
963 vfields["value"] = value;
964 this->value.notify();
965 emit_event ("notify", parameter_->ident(), vfields);
966 }
967 void
968 reset () override
969 {
970 set_value (parameter_->initial());
971 }
972 Value
973 get_value () const override
974 {
975 const AudioProcessorP proc = device_->_audio_processor();
976 const double value = inflight_stamp_ > proc->engine().frame_counter() ? inflight_value_ : AudioProcessor::param_peek_mt (proc, id_);
977 if (parameter_->is_text())
978 return proc->text_param_from_quark (id_, value);
979 else if (parameter_->is_choice())
980 return proc->param_value_to_text (id_, value);
981 else
982 return value;
983 }
984 bool
985 set_value (const Value &value) override
986 {
987 PropertyP thisp = shared_ptr_cast<Property> (this); // thisp keeps this alive during lambda
988 const AudioProcessorP proc = device_->_audio_processor();
989 double v;
990 if (parameter_->is_text())
991 v = proc->text_param_to_quark (id_, value.as_string());
992 else if (value.index() == Value::STRING && parameter_->is_choice())
993 v = proc->param_value_from_text (id_, value.as_string());
994 else
995 v = value.as_double();
996 proc->send_param (id_, v);
997 inflight_value_ = v;
998 inflight_stamp_ = proc->engine().frame_counter();
999 inflight_stamp_ += 2 * proc->engine().block_size(); // wait until after the *next* job queue has been processed
1000 emit_notify (parameter_->ident());
1001 return true;
1002 }
1003 double
1004 get_normalized () const override
1005 {
1006 const AudioProcessorP proc = device_->_audio_processor();
1007 const double value = inflight_stamp_ > proc->engine().frame_counter() ? inflight_value_ : AudioProcessor::param_peek_mt (proc, id_);
1008 const double v = proc->value_to_normalized (id_, value);
1009 return v;
1010 }
1011 bool
1012 set_normalized (double normalized) override
1013 {
1014 const AudioProcessorP proc = device_->_audio_processor();
1015 const auto [fmin, fmax, step] = parameter_->range();
1016 const double value = fmin + CLAMP (normalized, 0, 1) * (fmax - fmin);
1017 return set_value (value);
1018 }
1019 String
1020 get_text () const override
1021 {
1022 const AudioProcessorP proc = device_->_audio_processor();
1023 const double value = inflight_stamp_ > proc->engine().frame_counter() ? inflight_value_ : AudioProcessor::param_peek_mt (proc, id_);
1024 return proc->param_value_to_text (id_, value);
1025 }
1026 bool
1027 set_text (String vstr) override
1028 {
1029 const AudioProcessorP proc = device_->_audio_processor();
1030 const double v = proc->param_value_from_text (id_, vstr);
1031 return set_value (v);
1032 }
1033 bool
1034 is_numeric () const override
1035 {
1036 // TODO: we have yet to implement non-numeric AudioProcessor parameters
1037 return true;
1038 }
1039 ChoiceS
1040 choices () const override
1041 {
1042 return parameter_->choices();
1043 }
1044 StringS
1045 get_metadata () const override
1046 {
1047 return parameter_->metadata();
1048 }
1049};
1050
1051// == AudioProcessor::access_properties ==
1054PropertyS
1055AudioProcessor::access_properties () const
1056{
1057 DeviceP devp = get_device();
1058 assert_return (devp, {});
1059 PropertyS props;
1060 props.reserve (params_.count);
1061 for (size_t idx = 0; idx < params_.count; idx++) {
1062 const uint32_t id = params_.ids[idx];
1063 ParameterC parameterp = params_.parameters[idx];
1064 assert_return (parameterp != nullptr, {});
1065 PropertyP prop = weak_ptr_fetch_or_create<Property> (params_.wprops[idx], [&] () {
1066 return std::make_shared<AudioPropertyImpl> (devp, id, parameterp);
1067 });
1068 props.push_back (prop);
1069 }
1070 return props;
1071}
1072
1073// == enotify_queue ==
1074static AudioProcessor *const enotify_queue_tail = (AudioProcessor*) ptrdiff_t (-1);
1075static std::atomic<AudioProcessor*> enotify_queue_head { enotify_queue_tail };
1076
1079void
1081{
1082 return_unless (!device_.expired()); // need a means to report notifications
1083 assert_return (enotify_queue_head != nullptr); // paranoid
1084 const uint32 prev = flags_.fetch_or (pushmask & NOTIFYMASK);
1085 return_unless (prev != (prev | pushmask)); // nothing new
1086 AudioProcessor *expected = nullptr;
1087 if (nqueue_next_.compare_exchange_strong (expected, enotify_queue_tail))
1088 {
1089 // nqueue_next_ was nullptr, need to insert into queue now
1090 assert_warn (nqueue_guard_ == nullptr);
1091 nqueue_guard_ = shared_from_this();
1092 expected = enotify_queue_head.load(); // must never be nullptr
1093 do
1094 nqueue_next_.store (expected);
1095 while (!enotify_queue_head.compare_exchange_strong (expected, this));
1096 }
1097}
1098
1100void
1101AudioProcessor::enotify_dispatch ()
1102{
1103 assert_return (this_thread_is_ase());
1104 AudioProcessor *head = enotify_queue_head.exchange (enotify_queue_tail);
1105 while (head != enotify_queue_tail)
1106 {
1107 AudioProcessor *current = std::exchange (head, head->nqueue_next_);
1108 AudioProcessorP procp = std::exchange (current->nqueue_guard_, nullptr);
1109 AudioProcessor *old_nqueue_next = current->nqueue_next_.exchange (nullptr);
1110 assert_warn (old_nqueue_next != nullptr);
1111 const uint32 nflags = NOTIFYMASK & current->flags_.fetch_and (~NOTIFYMASK);
1112 assert_warn (procp != nullptr);
1113 DeviceP devicep = current->get_device();
1114 if (devicep)
1115 {
1116 if (nflags & BUSCONNECT)
1117 devicep->emit_event ("bus", "connect");
1118 if (nflags & BUSDISCONNECT)
1119 devicep->emit_event ("bus", "disconnect");
1120 if (nflags & INSERTION)
1121 devicep->emit_event ("sub", "insert");
1122 if (nflags & REMOVAL)
1123 devicep->emit_event ("sub", "remove");
1124 if (nflags & PARAMCHANGE)
1125 for (size_t blockoffset = 0; blockoffset < current->params_.count; blockoffset += 64)
1126 if (current->params_.bits[blockoffset >> 6]) {
1127 const uint64_t bitmask = std::atomic_fetch_and (&current->params_.bits[blockoffset >> 6], 0);
1128 const size_t bound = std::min (uint64_t (current->params_.count), blockoffset | uint64_t (64-1));
1129 for (size_t idx = blockoffset; idx < bound; idx++)
1130 if (bitmask & uint64_t (1) << (idx & (64-1))) {
1131 PropertyP propi = current->params_.wprops[idx].lock();
1132 AudioPropertyImpl *aprop = dynamic_cast<AudioPropertyImpl*> (propi.get());
1133 if (aprop)
1134 aprop->proc_paramchange();
1135 }
1136 }
1137 if (nflags & PARAMCHANGE) {
1138 devicep->emit_event ("params", "change");
1139 // cleanup temporary CStrings
1140 std::swap (current->cstrings0_, current->cstrings1_); // aging of CString instances from last round
1141 current->cstrings0_.clear(); // CString instances to be freed
1142 }
1143 }
1144 }
1145}
1146
1148bool
1149AudioProcessor::enotify_pending ()
1150{
1151 return enotify_queue_head != enotify_queue_tail;
1152}
1153
1154} // Ase
T atomic_compare_exchange_strong(T... args)
T atomic_fetch_and(T... args)
T back(T... args)
Vector of atomic bits, operates in blocks of 64 bits.
Definition atomics.hh:232
Audio signal AudioProcessor base class, implemented by all effects and instruments.
Definition processor.hh:76
bool set_normalized(Id32 paramid, double normalized)
Set param value normalized into 0…1.
Definition processor.cc:321
void redirect_oblock(OBusId b, uint c, const float *block)
Redirect output buffer of bus b, channel c to point to block, or zeros if block==nullptr.
Definition processor.cc:611
void enotify_enqueue_mt(uint32 pushmask)
void remove_all_buses()
Remove existing bus configurations, useful at the start of configure().
Definition processor.cc:646
float note_to_freq(int note) const
Convert MIDI note to Hertz according to the current MusicalTuning.
Definition processor.cc:123
IBusId find_ibus(const String &name) const
Return the IBusId for input bus uilabel or else 0.
Definition processor.cc:549
double get_param(Id32 paramid)
Fetch value of parameter id.
Definition processor.hh:525
IOBus & iobus(OBusId busid)
Get internal output bus handle.
Definition processor.cc:146
virtual String param_value_to_text(uint32_t paramid, double value) const
Definition processor.cc:336
bool send_param(Id32 paramid, double value)
Set parameter id to value within ParamInfo.get_minmax().
Definition processor.cc:238
MidiEventInput midi_event_input()
Access the current MidiEvent inputs during render(), needs prepare_event_input().
Definition processor.cc:844
MidiEventOutput & midi_event_output()
Access the current output EventStream during render(), needs prepare_event_output().
Definition processor.cc:859
uint n_ibuses() const
Number of input buses configured for this AudioProcessor.
Definition processor.hh:473
uint n_ochannels(OBusId busid) const
Number of channels of output bus busid configured for this AudioProcessor.
Definition processor.hh:509
virtual double param_value_from_text(uint32_t paramid, const String &text) const
Definition processor.cc:351
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
bool has_event_input() const
Returns true if this AudioProcessor has an event input stream.
Definition processor.hh:459
bool is_initialized() const
Check if AudioProcessor has been properly intiialized (so the parameter set is fixed).
Definition processor.cc:407
void atomic_bits_resize(size_t count)
Prepare count bits for atomic notifications.
Definition processor.cc:380
void reschedule()
Request recreation of the audio engine rendering schedule.
Definition processor.cc:439
void prepare_event_output()
Definition processor.cc:467
AtomicBits::Iter atomic_bits_iter(size_t pos=0) const
Allow iterations over the atomic bits.
Definition processor.cc:399
MaybeParamId find_param(const String &identifier) const
Return the ParamId for parameter identifier or else 0.
Definition processor.cc:225
void connect_event_input(AudioProcessor &oproc)
Connect event input to event output of AudioProcessor oproc.
Definition processor.cc:494
uint text_param_to_quark(uint32_t paramid, const String &text)
Ase main-thread helper for temporary string<->uint conversions.
Definition processor.cc:359
double get_normalized(Id32 paramid)
Get param value normalized into 0…1.
Definition processor.cc:314
static void registry_foreach(const std::function< void(const String &aseid, StaticInfo)> &fun)
Iterate over the known AudioProcessor types.
Definition processor.cc:909
void disconnect_obuses()
Disconnect inputs of all Processors that are connected to outputs of this.
Definition processor.cc:673
bool atomic_bit_notify(size_t nth)
Set the nth atomic notification bit, return if enotify_enqueue_mt() is needed.
Definition processor.cc:389
ParameterC parameter(Id32 paramid) const
Retrieve supplemental information for parameters, usually to enhance the user interface.
Definition processor.cc:262
String text_param_from_quark(uint32_t paramid, uint vint)
Helper for temporary uint<->string conversions.
Definition processor.cc:372
double peek_param_mt(Id32 paramid) const
Definition processor.cc:272
AudioEngine & engine() const
Retrieve AudioEngine handle for this AudioProcessor.
Definition processor.hh:425
void disconnect(IBusId ibus)
Disconnect input ibusid.
Definition processor.cc:687
virtual void initialize(SpeakerArrangement busses)=0
Definition processor.cc:432
bool connected(OBusId obusid) const
Definition processor.cc:637
MinMax param_range(Id32 paramid) const
Retrieve the minimum / maximum values for a parameter.
Definition processor.cc:414
void enable_engine_output(bool onoff)
Configure if the main output of this module is mixed into the engine output.
Definition processor.cc:446
IBusId add_input_bus(CString uilabel, SpeakerArrangement speakerarrangement, const String &hints="", const String &blurb="")
Add an input bus with uilabel and channels configured via speakerarrangement.
Definition processor.cc:510
virtual ~AudioProcessor()
The destructor is called when the last std::shared_ptr<> reference drops.
Definition processor.cc:111
uint n_obuses() const
Number of output buses configured for this AudioProcessor.
Definition processor.hh:480
uint schedule_processor()
Schedule this node and its dependencies for engine rendering.
Definition processor.cc:781
static double param_peek_mt(const AudioProcessorP proc, Id32 paramid)
Definition processor.cc:283
bool has_event_output() const
Returns true if this AudioProcessor has an event output stream.
Definition processor.hh:466
void disconnect_ibuses()
Reset input bus buffer data.
Definition processor.cc:662
static void registry_add(CString aseid, StaticInfo, MakeProcessorP)
Definition processor.cc:879
uint n_ichannels(IBusId busid) const
Number of channels of input bus busid configured for this AudioProcessor.
Definition processor.hh:501
void disconnect_event_input()
Disconnect event input if a connection is present.
Definition processor.cc:477
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
OBusId find_obus(const String &name) const
Return the OBusId for output bus uilabel or else 0.
Definition processor.cc:561
void assign_oblock(OBusId b, uint c, float val)
Fill the output buffer of bus b, channel c with v.
Definition processor.cc:627
void connect(IBusId ibus, AudioProcessor &oproc, OBusId obus)
Connect input ibusid to output obusid of AudioProcessor prev.
Definition processor.cc:713
DeviceP get_device() const
Gain access to the Device handle of this AudioProcessor.
Definition processor.cc:130
String unit() const override
Units of the values within range.
Definition processor.cc:945
double get_max() const override
Get the maximum property value, converted to double.
Definition processor.cc:951
void reset() override
Assign default as normalized property value.
Definition processor.cc:968
bool is_numeric() const override
Whether the property settings can be represented as a floating point number.
double get_step() const override
Get the property value stepping, converted to double.
Definition processor.cc:952
ChoiceS choices() const override
Enumerate choices for choosable properties.
bool set_value(const Value &value) override
Set the native property value.
Definition processor.cc:985
Value get_value() const override
Get the native property value.
Definition processor.cc:973
bool set_text(String vstr) override
Set the current property value as a text String.
bool set_normalized(double normalized) override
Set the normalized property value as double.
String nick() const override
Abbreviated user interface name, usually not more than 6 characters.
Definition processor.cc:944
double get_min() const override
Get the minimum property value, converted to double.
Definition processor.cc:950
double get_normalized() const override
Get the normalized property value, converted to double.
String label() const override
Preferred user interface name.
Definition processor.cc:943
String get_text() const override
Get the current property value, converted to a text String.
String ident() const override
Unique name (per owner) of this Property.
Definition processor.cc:942
Compact, deduplicating string variant for constant strings.
Definition memory.hh:138
static CString lookup(const std::string &s)
Definition memory.cc:631
Implementation type for classes with Event subscription.
Definition object.hh:11
A stream of writable MidiEvent structures.
Definition midievent.hh:96
An in-order MidiEvent reader for multiple MidiEvent sources.
Definition midievent.hh:117
A Property allows querying, setting and monitoring of an object property.
Definition api.hh:135
T compare_exchange_strong(T... args)
#define ASE_UNLIKELY(expr)
Compiler hint to optimize for expr evaluating to false.
Definition cxxaux.hh:46
#define ASE_ISLIKELY(expr)
Compiler hint to optimize for expr evaluating to true.
Definition cxxaux.hh:45
T empty(T... args)
T exchange(T... args)
T fetch_and(T... args)
T fetch_or(T... args)
fmax
fmin
#define assert_return(expr,...)
Return from the current function if expr is unmet and issue an assertion warning.
Definition internal.hh:29
#define return_unless(cond,...)
Return silently if cond does not evaluate to true with return value ...
Definition internal.hh:71
#define CLAMP(v, mi, ma)
Yield v clamped to [mi … ma].
Definition internal.hh:58
#define assert_warn(expr)
Issue an assertion warning if expr evaluates to false.
Definition internal.hh:33
T load(T... args)
T make_pair(T... args)
T max(T... args)
T min(T... args)
size_t erase_first(C &container, const std::function< bool(typename C::value_type const &value)> &pred)
Erase first element for which pred() is true in vector or list.
Definition utils.hh:337
The Anklang C++ API namespace.
Definition api.hh:9
String string_to_identifier(const String &input)
Force lower case, alphanumerics + underscore and non-digit start.
Definition strings.cc:54
uint64_t uint64
A 64-bit unsigned integer, use PRI*64 in format strings.
Definition cxxaux.hh:25
IBusId
ID type for AudioProcessor input buses, buses are numbered with increasing index.
Definition processor.hh:21
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
@ MONO
Single Channel (M)
OBusId
ID type for AudioProcessor output buses, buses are numbered with increasing index.
Definition processor.hh:25
float const_float_zeros[AUDIO_BLOCK_FLOAT_ZEROS_SIZE]
Block of const floats allof value 0.
Definition datautils.cc:7
ParamId
ID type for AudioProcessor parameters, the ID numbers are user assignable.
Definition processor.hh:17
std::string String
Convenience alias for std::string.
Definition cxxaux.hh:35
uint32_t uint32
A 32-bit unsigned integer.
Definition cxxaux.hh:24
uint32_t uint
Provide 'uint' as convenience type.
Definition cxxaux.hh:18
RtJobQueue main_rt_jobs
Queue a callback for the main_loop without invoking malloc(), addition is obstruction free.
Definition main.cc:65
bool string_startswith(const String &string, const String &fragment)
Returns whether string starts with fragment.
Definition strings.cc:846
Detailed information and common properties of AudioProcessor subclasses.
Definition processor.hh:29
T push_back(T... args)
T size(T... args)
typedef uint32_t
T store(T... args)
void install(const Map &params)
Clear and install a new set of parameters.
Definition processor.cc:65
ssize_t index(uint32_t id) const
Find index of parameter identified by id or return -1.
Definition processor.hh:318
CString aseid
Unique identifier for de-/serialization.
Definition processor.cc:867
uint n_channels() const
Number of channels described by speakers.
Definition processor.hh:418
SpeakerArrangement speakers
Channel to speaker arrangement.
Definition processor.hh:49
CString ident
Identifier used for serialization.
Definition processor.hh:45
Helper class for integer IDs up to 32 Bit, possibly of enum type.
Definition cxxaux.hh:403
static float note_to_freq(MusicalTuning tuning, int note, float kammer_freq)
Convert MIDI note to Hertz for a MusicalTuning and kammer_freq.
Definition midievent.hh:155
Wrap simple callback pointers, without using malloc (obstruction free).
Definition callback.hh:91
Value type used to interface with various property types.
Definition value.hh:54
double as_double() const
Convert Value to double or return 0.
Definition value.cc:77
String as_string() const
Convert Value to a string, not very useful for RECORD or ARRAY.
Definition value.cc:95
T swap(T... args)
typedef size_t