From fbe089dda62e69e8ab18d3bd049334f3a934cfaf Mon Sep 17 00:00:00 2001 From: Tim Janik Date: Mon, 13 Nov 2023 21:02:24 +0100 Subject: [PATCH 01/25] ASE: queuemux: add QueueMultiplexer.assign() and support iterator template arg Signed-off-by: Tim Janik --- ase/queuemux.cc | 3 ++- ase/queuemux.hh | 15 +++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/ase/queuemux.cc b/ase/queuemux.cc index f02f29b7..c1ac705e 100644 --- a/ase/queuemux.cc +++ b/ase/queuemux.cc @@ -46,7 +46,8 @@ queuemux_test() std::vector queue_ptrs; for (Queue &queue : queues) queue_ptrs.push_back (&queue); - QueueMultiplexer mux (queue_ptrs.size(), &queue_ptrs[0]); + QueueMultiplexer mux; + mux.assign (queue_ptrs.size(), &queue_ptrs[0]); int last = -2147483648; size_t sc = 0; while (mux.more()) diff --git a/ase/queuemux.hh b/ase/queuemux.hh index 8918664d..a54aabde 100644 --- a/ase/queuemux.hh +++ b/ase/queuemux.hh @@ -9,19 +9,18 @@ namespace Ase { /// Multiplexer to pop from multiple Queues, while preserving priorities. /// Order for values at the same priority is unstable. /// Relies on unqualified calls to `Priority QueueMultiplexer_priority (const ValueType&)`. -template +template + requires std::forward_iterator struct QueueMultiplexer { + using ValueType = decltype (*std::declval()); using Priority = decltype (QueueMultiplexer_priority (std::declval())); - struct Ptr { typename Queue::const_iterator it, end; }; + struct Ptr { ForwardIterator it, end; }; ssize_t n_queues = 0, current = -1; Priority first = {}, next = {}; std::array ptrs; - QueueMultiplexer (size_t nqueues, Queue **queues) - { - assign (nqueues, queues); - } - bool - assign (size_t nqueues, Queue **queues) + QueueMultiplexer () {} + template bool + assign (size_t nqueues, IterableContainer **queues) { n_queues = 0; ASE_ASSERT_RETURN (nqueues <= MAXQUEUES, false); From 46efa6661366ad6461372608f66750c6e06255e8 Mon Sep 17 00:00:00 2001 From: Tim Janik Date: Tue, 14 Nov 2023 01:19:24 +0100 Subject: [PATCH 02/25] ASE: queuemux: support iterators and assign via std::array Signed-off-by: Tim Janik --- ase/queuemux.cc | 13 +++++++---- ase/queuemux.hh | 61 +++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 64 insertions(+), 10 deletions(-) diff --git a/ase/queuemux.cc b/ase/queuemux.cc index c1ac705e..661852e6 100644 --- a/ase/queuemux.cc +++ b/ase/queuemux.cc @@ -30,8 +30,9 @@ queuemux_test() ascending_counter++; return ascending_counter; }; + const size_t TOTAL = 39; std::vector samples; - for (size_t i = 0; i < 39; i++) + for (size_t i = 0; i < TOTAL; i++) samples.push_back (ascending_values()); // setting: N queues contain ascending (sorted) values @@ -43,11 +44,12 @@ queuemux_test() queues[random_int64() % N].push_back (SomeValue { v }); // task: fetch values from all queues in sorted order - std::vector queue_ptrs; - for (Queue &queue : queues) - queue_ptrs.push_back (&queue); + std::array queue_ptrs{}; + for (size_t i = 0; i < queues.size(); i++) + queue_ptrs[i] = &queues[i]; QueueMultiplexer mux; - mux.assign (queue_ptrs.size(), &queue_ptrs[0]); + mux.assign (queue_ptrs); + TASSERT (mux.count_pending() == TOTAL); int last = -2147483648; size_t sc = 0; while (mux.more()) @@ -59,6 +61,7 @@ queuemux_test() TASSERT (sc < samples.size() && samples[sc++] == current.i); } TASSERT (sc == samples.size()); + TASSERT (mux.count_pending() == 0); } } // Anon diff --git a/ase/queuemux.hh b/ase/queuemux.hh index a54aabde..6a72828b 100644 --- a/ase/queuemux.hh +++ b/ase/queuemux.hh @@ -12,7 +12,7 @@ namespace Ase { template requires std::forward_iterator struct QueueMultiplexer { - using ValueType = decltype (*std::declval()); + using ValueType = std::remove_reference_t())>; using Priority = decltype (QueueMultiplexer_priority (std::declval())); struct Ptr { ForwardIterator it, end; }; ssize_t n_queues = 0, current = -1; @@ -20,11 +20,14 @@ struct QueueMultiplexer { std::array ptrs; QueueMultiplexer () {} template bool - assign (size_t nqueues, IterableContainer **queues) + assign (const std::array &queues) { + static_assert (std::is_same< + ForwardIterator, + decltype (std::begin (std::declval())) + >::value); n_queues = 0; - ASE_ASSERT_RETURN (nqueues <= MAXQUEUES, false); - for (size_t i = 0; i < nqueues; i++) + for (size_t i = 0; i < queues.size(); i++) if (queues[i]) [[likely]] { ptrs[n_queues].it = std::begin (*queues[i]); @@ -35,15 +38,30 @@ struct QueueMultiplexer { seek(); return more(); } + size_t + count_pending() const + { + size_t c = 0; + for (ssize_t i = 0; i < n_queues; i++) + c += ptrs[i].end - ptrs[i].it; + return c; + } bool more() const { return n_queues > 0; } const ValueType& + peek () + { + if (!more()) [[unlikely]] + return empty(); + return *ptrs[current].it; + } + const ValueType& pop () { - ASE_ASSERT_RETURN (more(), []() -> const ValueType& { static const ValueType empty {}; return empty; }()); + ASE_ASSERT_RETURN (more(), empty()); const ValueType &result = *ptrs[current].it++; if (ptrs[current].it == ptrs[current].end) [[unlikely]] { // remove emptied queue @@ -57,6 +75,33 @@ struct QueueMultiplexer { seek(); return result; } + class Iter { + QueueMultiplexer *mux_ = nullptr; + public: + using difference_type = ssize_t; + using value_type = const ValueType; + using pointer = const value_type*; + using reference = const value_type&; + using iterator_category = std::input_iterator_tag; + /*ctor*/ Iter (QueueMultiplexer *u = nullptr) : mux_ (u && u->more() ? u : nullptr) {} + bool oob () const { return !mux_; } + friend bool operator== (const Iter &a, const Iter &b) { return a.mux_ == b.mux_; } + value_type& operator* () const { return mux_ ? mux_->peek() : empty(); } + Iter operator++ (int) { Iter copy (*this); this->operator++(); return copy; } + Iter& operator++ () + { + if (mux_) [[likely]] { + if (mux_->more()) [[likely]] + mux_->pop(); + else + mux_ = nullptr; + } + return *this; + } + }; + using iterator = Iter; + iterator begin () { return Iter (this); } + iterator end () { return {}; } private: void seek() @@ -80,6 +125,12 @@ private: } // dprintf (2, "%s: n_queues=%zd current=%zd first=%ld next=%ld\n", __func__, n_queues, current, long (first), long (next)); } + static const ValueType& + empty() + { + static const ValueType empty_ {}; + return empty_; + } }; } // Ase From 8088583cf9498f7e5ccac250903666e1868414a3 Mon Sep 17 00:00:00 2001 From: Tim Janik Date: Fri, 10 Nov 2023 01:26:58 +0100 Subject: [PATCH 03/25] ASE: parameter: add ParameterMap and support groups * Make `ident` the first Param() field, adjust callers * Add ParameterMap helper type * Support Param.MinMaxStep as double min,max pair * Catch simple cases of duplicate IDs, ident, label * Assign group to new ParameterMap Params Signed-off-by: Tim Janik --- ase/engine.cc | 55 ++++++++++++++++++++++++++--------------------- ase/gadget.cc | 2 +- ase/parameter.cc | 37 +++++++++++++++++++++++++++++++ ase/parameter.hh | 17 ++++++++++++++- ase/project.cc | 10 ++++----- ase/properties.cc | 13 +++++------ ase/properties.hh | 5 ++--- 7 files changed, 96 insertions(+), 43 deletions(-) diff --git a/ase/engine.cc b/ase/engine.cc index b8ac4937..108fb999 100644 --- a/ase/engine.cc +++ b/ase/engine.cc @@ -880,38 +880,43 @@ midi_driver_pref_list_choices (const CString &ident) } static Preference pcm_driver_pref = - Preference ("driver.pcm.devid", - { _("PCM Driver"), "", "auto", "ms", { pcm_driver_pref_list_choices }, - STANDARD, "", _("Driver and device to be used for PCM input and output"), }, - [] (const CString&,const Value&) { apply_driver_preferences(); }); + Preference ({ + "driver.pcm.devid", _("PCM Driver"), "", "auto", "ms", + { pcm_driver_pref_list_choices }, STANDARD, "", + _("Driver and device to be used for PCM input and output"), }, + [] (const CString&,const Value&) { apply_driver_preferences(); }); static Preference synth_latency_pref = - Preference ("driver.pcm.synth_latency", - { _("Synth Latency"), "", 15, "ms", MinMaxStep { 0, 3000, 5 }, STANDARD + String ("step=5"), - "", _("Processing duration between input and output of a single sample, smaller values increase CPU load") }, - [] (const CString&,const Value&) { apply_driver_preferences(); }); + Preference ({ + "driver.pcm.synth_latency", _("Synth Latency"), "", 15, "ms", + MinMaxStep { 0, 3000, 5 }, STANDARD + String ("step=5"), "", + _("Processing duration between input and output of a single sample, smaller values increase CPU load") }, + [] (const CString&,const Value&) { apply_driver_preferences(); }); static Preference midi1_driver_pref = - Preference ("driver.midi1.devid", - { _("MIDI Controller (1)"), "", "auto", "ms", { midi_driver_pref_list_choices }, - STANDARD, "", _("MIDI controller device to be used for MIDI input"), }, - [] (const CString&,const Value&) { apply_driver_preferences(); }); + Preference ({ + "driver.midi1.devid", _("MIDI Controller (1)"), "", "auto", "ms", + { midi_driver_pref_list_choices }, STANDARD, "", + _("MIDI controller device to be used for MIDI input"), }, + [] (const CString&,const Value&) { apply_driver_preferences(); }); static Preference midi2_driver_pref = - Preference ("driver.midi2.devid", - { _("MIDI Controller (2)"), "", "auto", "ms", { midi_driver_pref_list_choices }, - STANDARD, "", _("MIDI controller device to be used for MIDI input"), }, - [] (const CString&,const Value&) { apply_driver_preferences(); }); + Preference ({ + "driver.midi2.devid", _("MIDI Controller (2)"), "", "auto", "ms", + { midi_driver_pref_list_choices }, STANDARD, "", + _("MIDI controller device to be used for MIDI input"), }, + [] (const CString&,const Value&) { apply_driver_preferences(); }); static Preference midi3_driver_pref = - Preference ("driver.midi3.devid", - { _("MIDI Controller (3)"), "", "auto", "ms", { midi_driver_pref_list_choices }, - STANDARD, "", _("MIDI controller device to be used for MIDI input"), }, - [] (const CString&,const Value&) { apply_driver_preferences(); }); + Preference ({ + "driver.midi3.devid", _("MIDI Controller (3)"), "", "auto", "ms", + { midi_driver_pref_list_choices }, STANDARD, "", + _("MIDI controller device to be used for MIDI input"), }, + [] (const CString&,const Value&) { apply_driver_preferences(); }); static Preference midi4_driver_pref = - Preference ("driver.midi4.devid", - { _("MIDI Controller (4)"), "", "auto", "ms", { midi_driver_pref_list_choices }, - STANDARD, "", _("MIDI controller device to be used for MIDI input"), }, - [] (const CString&,const Value&) { apply_driver_preferences(); }); - + Preference ({ + "driver.midi4.devid", _("MIDI Controller (4)"), "", "auto", "ms", + { midi_driver_pref_list_choices }, STANDARD, "", + _("MIDI controller device to be used for MIDI input"), }, + [] (const CString&,const Value&) { apply_driver_preferences(); }); static void apply_driver_preferences () diff --git a/ase/gadget.cc b/ase/gadget.cc index e23043a6..b3993f7b 100644 --- a/ase/gadget.cc +++ b/ase/gadget.cc @@ -210,7 +210,7 @@ GadgetImpl::property_bag () Param param = prop.param; if (param.group.empty() && !group.empty()) param.group = group; - this->props_.push_back (PropertyImpl::make_shared (prop.ident, param, prop.getter, prop.setter, prop.lister)); + this->props_.push_back (PropertyImpl::make_shared (param, prop.getter, prop.setter, prop.lister)); // PropertyImpl &property = *gadget_.props_.back(); }; return PropertyBag (add_prop); diff --git a/ase/parameter.cc b/ase/parameter.cc index 2d776752..04d9a928 100644 --- a/ase/parameter.cc +++ b/ase/parameter.cc @@ -9,6 +9,12 @@ namespace Ase { // == Param == +Param::ExtraVals::ExtraVals (double vmin, double vmax, double step) +{ + using Base = std::variant; + Base::operator= (MinMaxStep { vmin, vmax, step }); +} + Param::ExtraVals::ExtraVals (const MinMaxStep &range) { using Base = std::variant; @@ -395,6 +401,37 @@ Parameter::value_from_text (const String &text) const return constrain (string_to_double (text)); } +// == ParameterMap == +ParameterMap::Entry +ParameterMap::operator[] (uint32_t id) +{ + return { *this, id }; +} + +void +ParameterMap::Entry::operator= (const Param ¶m) +{ + const char *const FUNC = "ParameterMap::Entry::operator="; + std::map &pmap = map; + ParameterC old = pmap[id]; + if (old) + warning ("%s: reassign parameter '%s' with: %s\n", FUNC, old->ident(), param.ident); + else if (id > 0) { + auto it = pmap.find (id - 1); + if (it != pmap.end() && it->second) { + // catch erroneous CnP reuse + if (it->second->ident() == param.ident) + warning ("%s: duplicate %s: '%s' (param_id=%u)", FUNC, "ident", param.ident, id); + else if (it->second->label() == param.label) + warning ("%s: duplicate %s: '%s' (param_id=%u)", FUNC, "label", param.label, id); + } + } + Param mparam = param; + if (mparam.group.empty()) + mparam.group = map.group; + pmap[id] = std::make_shared (mparam); +} + // == guess_nick == using String3 = std::tuple; diff --git a/ase/parameter.hh b/ase/parameter.hh index 9cccbf6c..583ce937 100644 --- a/ase/parameter.hh +++ b/ase/parameter.hh @@ -17,11 +17,13 @@ struct Param { using InitialVal = std::variant; struct ExtraVals : std::variant { ExtraVals () = default; + ExtraVals (double vmin, double vmax, double step = 0); ExtraVals (const MinMaxStep&); ExtraVals (const std::initializer_list&); ExtraVals (const ChoiceS&); ExtraVals (const ChoicesFunc&); }; + String ident; ///< Identifier used for serialization (can be derived from untranslated label). String label; ///< Preferred user interface name. String nick; ///< Abbreviated user interface name, usually not more than 6 characters. InitialVal initial = 0; ///< Initial value for float, int, choice types. @@ -31,7 +33,6 @@ struct Param { String blurb; ///< Short description for user interface tooltips. String descr; ///< Elaborate description for help dialogs. String group; ///< Group for parameters of similar function. - String ident; ///< Identifier used for serialization (can be derived from untranslated label). StringS details; ///< Array of "key=value" pairs. static inline const String STORAGE = ":r:w:S:"; static inline const String STANDARD = ":r:w:S:G:"; @@ -80,6 +81,20 @@ private: }; using ParameterC = std::shared_ptr; +/// Parameter list construction helper. +struct ParameterMap final : std::map { + /// Group to be applied to all newly inserted Parameter objects. + String group; + /// Helper for new Parameter creation from Param initializer. + struct Entry { + ParameterMap ↦ + const uint32_t id; + void operator= (const Param&); + }; + /// Slot subscription for new Parameter creation. + Entry operator[] (uint32_t id); +}; + /// Abstract base type for Property implementations with Parameter meta data. class ParameterProperty : public EmittableImpl, public virtual Property { protected: diff --git a/ase/project.cc b/ase/project.cc index 720783d1..efd1ceaa 100644 --- a/ase/project.cc +++ b/ase/project.cc @@ -803,12 +803,12 @@ ProjectImpl::create_properties () // TODO: bag += Bool ("dirty", &dirty_, _("Modification Flag"), _("Dirty"), false, ":r:G:", _("Flag indicating modified project state")); // struct Prop { CString ident; ValueGetter getter; ValueSetter setter; Param param; ValueLister lister; }; bag.group = _("Timing"); - bag += Prop ("numerator", getbpb, setbpb, { _("Signature Numerator"), _("Numerator"), 4., "", MinMaxStep { 1., 63., 0 }, STANDARD }); - bag += Prop ("denominator", getunt, setunt, { _("Signature Denominator"), _("Denominator"), 4, "", MinMaxStep { 1, 16, 0 }, STANDARD }); - bag += Prop ("bpm", getbpm, setbpm, { _("Beats Per Minute"), _("BPM"), 90., "", MinMaxStep { 10., 1776., 0 }, STANDARD }); + bag += Prop (getbpb, setbpb, { "numerator", _("Signature Numerator"), _("Numerator"), 4., "", MinMaxStep { 1., 63., 0 }, STANDARD }); + bag += Prop (getunt, setunt, { "denominator", _("Signature Denominator"), _("Denominator"), 4, "", MinMaxStep { 1, 16, 0 }, STANDARD }); + bag += Prop (getbpm, setbpm, { "bpm", _("Beats Per Minute"), _("BPM"), 90., "", MinMaxStep { 10., 1776., 0 }, STANDARD }); bag.group = _("Tuning"); - bag += Prop ("musical_tuning", make_enum_getter (&musical_tuning_), make_enum_setter (&musical_tuning_), - { _("Musical Tuning"), _("Tuning"), uint32_t (MusicalTuning::OD_12_TET), "", {}, STANDARD, "", + bag += Prop (make_enum_getter (&musical_tuning_), make_enum_setter (&musical_tuning_), + { "musical_tuning", _("Musical Tuning"), _("Tuning"), uint32_t (MusicalTuning::OD_12_TET), "", {}, STANDARD, "", _("The tuning system which specifies the tones or pitches to be used. " "Due to the psychoacoustic properties of tones, various pitch combinations can " "sound \"natural\" or \"pleasing\" when used in combination, the musical " diff --git a/ase/properties.cc b/ase/properties.cc index fa0e0c1c..d608ab4f 100644 --- a/ase/properties.cc +++ b/ase/properties.cc @@ -17,13 +17,11 @@ Property::~Property() {} // == PropertyImpl == -PropertyImpl::PropertyImpl (CString ident, const Param ¶m, const PropertyGetter &getter, +PropertyImpl::PropertyImpl (const Param ¶m, const PropertyGetter &getter, const PropertySetter &setter, const PropertyLister &lister) : getter_ (getter), setter_ (setter), lister_ (lister) { - Param iparam = param; - iparam.ident = ident; - parameter_ = std::make_shared (iparam); + parameter_ = std::make_shared (param); } // == PropertyBag == @@ -107,11 +105,10 @@ Preference::Preference (ParameterC parameter) sigh_ = pv.callbacks->add_delcb ([this] (const String &ident, const Value &value) { emit_event ("notify", ident); }); } -Preference::Preference (const CString &ident, const Param ¶m, const StringValueF &cb) +Preference::Preference (const Param ¶m, const StringValueF &cb) { - Param iparam = param; - iparam.ident = ident; - parameter_ = std::make_shared (iparam); + assert_return (param.ident.empty() == false); + parameter_ = std::make_shared (param); PrefsMap &prefsmap = prefs_map(); PrefsValue &pv = prefsmap[parameter_->cident]; assert_return (pv.parameter == nullptr); // catch duplicate registration diff --git a/ase/properties.hh b/ase/properties.hh index 20155c39..c25791e4 100644 --- a/ase/properties.hh +++ b/ase/properties.hh @@ -15,7 +15,7 @@ public: using DelCb = std::function; using StringValueF = std::function; virtual ~Preference (); - /*ctor*/ Preference (const CString &ident, const Param&, const StringValueF& = nullptr); + /*ctor*/ Preference (const Param&, const StringValueF& = nullptr); String gets () const { return const_cast (this)->get_value().as_string(); } bool getb () const { return const_cast (this)->get_value().as_int(); } int64 getn () const { return const_cast (this)->get_value().as_int(); } @@ -48,7 +48,6 @@ using PropertyLister = std::function; /// Structured initializer for PropertyImpl struct Prop { - CString ident; ///< Valid NCName identifier. PropertyGetter getter; ///< Lambda implementing the Property value getter. PropertySetter setter; ///< Lambda implementing the Property value setter. Param param; ///< Parameter meta data for this Property. @@ -58,7 +57,7 @@ struct Prop { /// Property implementation for GadgetImpl, using lambdas as accessors. class PropertyImpl : public ParameterProperty { PropertyGetter getter_; PropertySetter setter_; PropertyLister lister_; - PropertyImpl (CString, const Param&, const PropertyGetter&, const PropertySetter&, const PropertyLister&); + PropertyImpl (const Param&, const PropertyGetter&, const PropertySetter&, const PropertyLister&); public: ASE_DEFINE_MAKE_SHARED (PropertyImpl); Value get_value () override { Value v; getter_ (v); return v; } From 93eb5eea4098c03db4eea2b749804ef5bea704b0 Mon Sep 17 00:00:00 2001 From: Tim Janik Date: Fri, 27 Oct 2023 19:32:33 +0200 Subject: [PATCH 04/25] ASE: midievent: add PARAM_VALUE and make_param_value() Signed-off-by: Tim Janik --- ase/midievent.cc | 16 ++++++++++++++-- ase/midievent.hh | 45 +++++++++++++++++++++++++-------------------- 2 files changed, 39 insertions(+), 22 deletions(-) diff --git a/ase/midievent.cc b/ase/midievent.cc index 8462929e..167d936b 100644 --- a/ase/midievent.cc +++ b/ase/midievent.cc @@ -49,6 +49,9 @@ MidiEvent::to_string () const const char *et = nullptr; switch (type) { + case PARAM_VALUE: + return string_format ("%+4d ch=%-2u %s param=%d pvalue=%.5f", + frame, channel, "PARAM_VALUE", param, pvalue); case NOTE_OFF: if (!et) et = "NOTE_OFF"; /* fall-through */ case NOTE_ON: if (!et) et = "NOTE_ON"; @@ -69,9 +72,8 @@ MidiEvent::to_string () const frame, channel, et, value); case SYSEX: if (!et) et = "SYSEX"; return string_format ("%+4d %s (unhandled)", frame, et); - default: - return string_format ("%+4d MidiEvent-%u (unhandled)", frame, type); } + return string_format ("%+4d MidiEvent-%u (unhandled)", frame, type); } MidiEvent @@ -159,6 +161,16 @@ make_pitch_bend (uint16 chnl, float val) return ev; } +MidiEvent +make_param_value (uint param, double pvalue) +{ + MidiEvent ev (MidiEvent::PARAM_VALUE); + ev.channel = 0xf; + ev.param = param; + ev.pvalue = pvalue; + return ev; +} + // == MidiEventStream == MidiEventStream::MidiEventStream () {} diff --git a/ase/midievent.hh b/ase/midievent.hh index 4d805bd0..c9033cf3 100644 --- a/ase/midievent.hh +++ b/ase/midievent.hh @@ -11,7 +11,17 @@ namespace Ase { enum class MusicalTuning : uint8; /// Type of MIDI Events. -enum class MidiEventType : uint8_t {}; +enum class MidiEventType : uint8_t { + PARAM_VALUE = 0x70, /// Ase internal Parameter update. + NOTE_OFF = 0x80, + NOTE_ON = 0x90, + AFTERTOUCH = 0xA0, ///< Key Pressure, polyphonic aftertouch + CONTROL_CHANGE = 0xB0, ///< Control Change + PROGRAM_CHANGE = 0xC0, + CHANNEL_PRESSURE = 0xD0, ///< Channel Aftertouch + PITCH_BEND = 0xE0, + SYSEX = 0xF0, +}; /// Extended type information for MidiEvent. enum class MidiMessage : int32_t { @@ -36,28 +46,23 @@ enum class MidiMessage : int32_t { /// MidiEvent data structure. struct MidiEvent { - constexpr static MidiEventType NOTE_OFF = MidiEventType (0x80); - constexpr static MidiEventType NOTE_ON = MidiEventType (0x90); - constexpr static MidiEventType AFTERTOUCH = MidiEventType (0xA0); ///< Key Pressure, polyphonic aftertouch - constexpr static MidiEventType CONTROL_CHANGE = MidiEventType (0xB0); ///< Control Change - constexpr static MidiEventType PROGRAM_CHANGE = MidiEventType (0xC0); - constexpr static MidiEventType CHANNEL_PRESSURE = MidiEventType (0xD0); ///< Channel Aftertouch - constexpr static MidiEventType PITCH_BEND = MidiEventType (0xE0); - constexpr static MidiEventType SYSEX = MidiEventType (0xF0); - MidiEventType type; ///< MidiEvent type, one of the MidiEventType members + using enum MidiEventType; + static_assert (AUDIO_BLOCK_MAX_RENDER_SIZE <= 2048); // -2048…+2047 fits frame int frame : 12; ///< Offset into current block, delayed if negative uint channel : 4; ///< 0…15 for standard events + MidiEventType type; ///< MidiEvent type, one of the MidiEventType members union { uint8 key; ///< NOTE, KEY_PRESSURE MIDI note, 0…0x7f, 60 = middle C at 261.63 Hz. uint8 fragment; ///< Flag for multi-part control change mesages. }; union { uint length; ///< Data event length of byte array. - uint param; ///< PROGRAM_CHANGE program, CONTROL_CHANGE controller, 0…0x7f + uint param; ///< PROGRAM_CHANGE (program), CONTROL_CHANGE (controller):0…0x7f; PARAM_VALUE:uint32_t uint noteid; ///< NOTE, identifier for note expression handling or 0xffffffff. }; union { char *data; ///< Data event byte array. + double pvalue; ///< Numeric parameter value, PARAM_VALUE. struct { float value; ///< CONTROL_CHANGE 0…+1, CHANNEL_PRESSURE, 0…+1, PITCH_BEND -1…+1 uint cval; ///< CONTROL_CHANGE control value, 0…0x7f @@ -73,17 +78,17 @@ struct MidiEvent { /*des*/ ~MidiEvent () {} MidiMessage message () const; std::string to_string () const; - static_assert (AUDIO_BLOCK_MAX_RENDER_SIZE <= 2048); // -2048…+2047 fits frame }; -MidiEvent make_note_on (uint16 chnl, uint8 mkey, float velo, float tune = 0, uint nid = 0xffffff); -MidiEvent make_note_off (uint16 chnl, uint8 mkey, float velo, float tune = 0, uint nid = 0xffffff); -MidiEvent make_aftertouch (uint16 chnl, uint8 mkey, float velo, float tune = 0, uint nid = 0xffffff); -MidiEvent make_pressure (uint16 chnl, float velo); -MidiEvent make_control (uint16 chnl, uint prm, float val); -MidiEvent make_control8 (uint16 chnl, uint prm, uint8 cval); -MidiEvent make_program (uint16 chnl, uint prgrm); -MidiEvent make_pitch_bend (uint16 chnl, float val); +MidiEvent make_note_on (uint16 chnl, uint8 mkey, float velo, float tune = 0, uint nid = 0xffffff); +MidiEvent make_note_off (uint16 chnl, uint8 mkey, float velo, float tune = 0, uint nid = 0xffffff); +MidiEvent make_aftertouch (uint16 chnl, uint8 mkey, float velo, float tune = 0, uint nid = 0xffffff); +MidiEvent make_pressure (uint16 chnl, float velo); +MidiEvent make_control (uint16 chnl, uint prm, float val); +MidiEvent make_control8 (uint16 chnl, uint prm, uint8 cval); +MidiEvent make_program (uint16 chnl, uint prgrm); +MidiEvent make_pitch_bend (uint16 chnl, float val); +MidiEvent make_param_value (uint param, double pvalue); /// A stream of writable MidiEvent structures. class MidiEventStream { From e7dc05ef7c80d79edac78f86bffaa4c9a0e8683a Mon Sep 17 00:00:00 2001 From: Tim Janik Date: Sun, 12 Nov 2023 23:27:39 +0100 Subject: [PATCH 05/25] ASE: midievent: support MidiMessage::PARAM_VALUE Signed-off-by: Tim Janik --- ase/midievent.hh | 1 + 1 file changed, 1 insertion(+) diff --git a/ase/midievent.hh b/ase/midievent.hh index c9033cf3..239742d7 100644 --- a/ase/midievent.hh +++ b/ase/midievent.hh @@ -34,6 +34,7 @@ enum class MidiMessage : int32_t { OMNI_MODE_ON = 125, MONO_MODE_ON = 126, POLY_MODE_ON = 127, + PARAM_VALUE = 0x70, NOTE_OFF = 0x80, NOTE_ON = 0x90, AFTERTOUCH = 0xA0, From 80eb4012ca3a6c0406f1f62e8086b49b592dfcd0 Mon Sep 17 00:00:00 2001 From: Tim Janik Date: Fri, 27 Oct 2023 19:34:12 +0200 Subject: [PATCH 06/25] ASE: processor: implement parameter change events * Introduce AudioParams, t0events, install_params(), apply_event() * Implement AudioParams::dirty() * Implement AudioProcessor.access_properties() * Store aseid_ in each AudioProcessor * Add property weak pointers to AudioParams * Support install_params() with an ordinary map<> * Initialize parameter cache with intial value * Disable Property inflight handling code * Rename MidiEventOutput (former MidiEventStream) * Implement multiplexing MIDI event handling * Atomically modify and swap t0events to pass between threads * Leave new/delete of t0events to the main-thread * Add modify_t0events<>() helper to care about atomic swapping * Rename and rewrite get_event_input() and get_event_output() * Introduce RenderContext for rendering specific variables * midi_event_input(): setup multiplexing event queue reader * midi_event_output(): provide writable MIDI event vector * Implement send_param() * Only set dirty and changed for params with property object * Enqueue PARAMCHANGE notification if params changed * Simplify event processing * Provide apply_input_events() to assign PARAM_CHANGE events * Use uint32_t as parameter id type for processors * Provide adjust_all_params() to call adjust_param() for all params Signed-off-by: Tim Janik --- ase/processor.cc | 492 +++++++++++++++++++---------------------------- ase/processor.hh | 278 +++++++++++++++----------- 2 files changed, 372 insertions(+), 398 deletions(-) diff --git a/ase/processor.cc b/ase/processor.cc index 3efca601..215d2720 100644 --- a/ase/processor.cc +++ b/ase/processor.cc @@ -38,6 +38,66 @@ AudioProcessor::OBus::OBus (const String &identifier, const String &uilabel, Spe assert_return (ident != ""); } +// == AudioParams == +AudioParams::~AudioParams() +{ + clear(); +} + +/// Clear all fields. +void +AudioParams::clear() +{ + changed = false; + count = 0; + delete[] ids; + ids = nullptr; + delete[] values; + values = nullptr; + delete[] bits; + bits = nullptr; + const ParameterC *old_parameters = nullptr; + std::swap (old_parameters, parameters); + std::weak_ptr *old_wprops = nullptr; + std::swap (old_wprops, wprops); + delete[] old_wprops; + delete[] old_parameters; // call ~ParameterC *after* *this is cleaned up +} + +/// Clear and install a new set of parameters. +void +AudioParams::install (const AudioParams::Map ¶ms) +{ + assert_return (this_thread_is_ase()); + clear(); + count = params.size(); + if (count == 0) + return; + // ids + uint32_t *new_ids = new uint32_t[count] (); + size_t i = 0; + for (auto [id,p] : params) + new_ids[i++] = id; + ids = new_ids; + assert_return (i == count); + // wprops + wprops = new std::weak_ptr[count] (); + // parameters + ParameterC *new_parameters = new ParameterC[count] (); + i = 0; + for (auto [id,p] : params) + new_parameters[i++] = p; + assert_return (i == count); + parameters = new_parameters; + // values + values = new double[count] (); // value-initialized per ISO C++03 5.3.4[expr.new]/15 + for (size_t i = 0; i < count; i++) + values[i] = parameters[i]->initial().as_double(); + // bits + const size_t u = (count + 64-1) / 64; // number of uint64_t bit fields + bits = new std::atomic[u] (); // a bit array causes vastly fewer cache misses +} + // == AudioProcessor == const String AudioProcessor::GUIONLY = ":G:r:w:"; ///< GUI READABLE WRITABLE const String AudioProcessor::STANDARD = ":G:S:r:w:"; ///< GUI STORAGE READABLE WRITABLE @@ -45,8 +105,8 @@ const String AudioProcessor::STORAGEONLY = ":S:r:w:"; ///< STORAGE READABLE WRIT uint64 __thread AudioProcessor::tls_timestamp = 0; /// Constructor for AudioProcessor -AudioProcessor::AudioProcessor (AudioEngine &engine) : - engine_ (engine) +AudioProcessor::AudioProcessor (const ProcessorSetup &psetup) : + engine_ (psetup.engine), aseid_ (psetup.aseid) { engine_.processor_count_ += 1; } @@ -57,6 +117,9 @@ AudioProcessor::~AudioProcessor () remove_all_buses(); engine_.processor_count_ -= 1; delete atomic_bits_; + MidiEventVector *t0events = nullptr; + t0events = t0events_.exchange (t0events); + delete t0events; } /// Convert MIDI note to Hertz according to the current MusicalTuning. @@ -147,144 +210,16 @@ AudioProcessor::assign_iobufs () fbuffers_ = nullptr; } -static __thread CString tls_param_group; - -/// Introduce a `ParamInfo.group` to be used for the following add_param() calls. +/// Reset list of parameters, enqueues parameter value initializaiton events. void -AudioProcessor::start_group (const String &groupname) const -{ - tls_param_group = groupname; -} - -/// Helper for add_param() to generate the sequentially next ParamId. -ParamId -AudioProcessor::nextid () const -{ - const uint pmax = pparams_.size(); - const uint last = pparams_.empty() ? 0 : uint (pparams_.back().id); - return ParamId (MAX (pmax, last) + 1); -} - -/// Add a new parameter with unique `ParamInfo.identifier`. -/// The returned `ParamId` is forced to match `id` (and must be unique). -ParamId -AudioProcessor::add_param (Id32 id, const Param &initparam, double value) +AudioProcessor::install_params (const AudioParams::Map ¶ms) { - assert_return (uint (id) > 0, ParamId (0)); - assert_return (!is_initialized(), {}); - assert_return (initparam.label != "", {}); - if (pparams_.size()) - assert_return (initparam.label != pparams_.back().parameter->label(), {}); // easy CnP error - Param iparam = initparam; - if (iparam.group.empty()) - iparam.group = tls_param_group; - ParameterP parameterp = std::make_shared (iparam); - ParameterC parameterc = parameterp; - if (pparams_.size()) - assert_return (parameterc->ident() != pparams_.back().parameter->ident(), {}); // easy CnP error - PParam pparam { ParamId (id.id), uint (1 + pparams_.size()), parameterc }; - using P = decltype (pparams_); - std::pair existing_parameter_position = - Aux::binary_lookup_insertion_pos (pparams_.begin(), pparams_.end(), PParam::cmp, pparam); - assert_return (existing_parameter_position.second == false, {}); - pparams_.insert (existing_parameter_position.first, std::move (pparam)); - set_param (pparam.id, value); // forces dirty - parameterp->initialsync (peek_param_mt (pparam.id)); - return pparam.id; -} - -/// Add new range parameter with most `ParamInfo` fields as inlined arguments. -/// The returned `ParamId` is newly assigned and increases with the number of parameters added. -/// The `clabel` is the canonical, non-translated name for this parameter, its -/// hyphenated lower case version is used for serialization. -ParamId -AudioProcessor::add_param (Id32 id, const String &clabel, const String &nickname, - double pmin, double pmax, double value, - const String &unit, String hints, - const String &blurb, const String &description) -{ - assert_return (uint (id) > 0, ParamId (0)); - return add_param (id, Param { - .label = clabel, - .nick = nickname, - .initial = value, - .unit = unit, - .extras = MinMaxStep { pmin, pmax, 0 }, - .hints = Parameter::construct_hints (hints, "", pmin, pmax), - .blurb = blurb, - .descr = description, - }, value); -} - -/// Variant of AudioProcessor::add_param() with sequential `id` generation. -ParamId -AudioProcessor::add_param (const String &clabel, const String &nickname, - double pmin, double pmax, double value, - const String &unit, String hints, - const String &blurb, const String &description) -{ - return add_param (nextid(), clabel, nickname, pmin, pmax, value, unit, hints, blurb, description); -} - -/// Add new choice parameter with most `ParamInfo` fields as inlined arguments. -/// The returned `ParamId` is newly assigned and increases with the number of parameters added. -/// The `clabel` is the canonical, non-translated name for this parameter, its -/// hyphenated lower case version is used for serialization. -ParamId -AudioProcessor::add_param (Id32 id, const String &clabel, const String &nickname, - ChoiceS &¢ries, double value, String hints, - const String &blurb, const String &description) -{ - assert_return (uint (id) > 0, ParamId (0)); - const double pmax = centries.size(); - return add_param (id, Param { - .label = clabel, - .nick = nickname, - .initial = value, - .extras = centries, - .hints = Parameter::construct_hints (hints, "choice", 0, pmax), - .blurb = blurb, - .descr = description, - }, value); -} - -/// Variant of AudioProcessor::add_param() with sequential `id` generation. -ParamId -AudioProcessor::add_param (const String &clabel, const String &nickname, - ChoiceS &¢ries, double value, String hints, - const String &blurb, const String &description) -{ - return add_param (nextid(), clabel, nickname, std::forward (centries), value, hints, blurb, description); -} - -/// Add new toggle parameter with most `ParamInfo` fields as inlined arguments. -/// The returned `ParamId` is newly assigned and increases with the number of parameters added. -/// The `clabel` is the canonical, non-translated name for this parameter, its -/// hyphenated lower case version is used for serialization. -ParamId -AudioProcessor::add_param (Id32 id, const String &clabel, const String &nickname, - bool boolvalue, String hints, - const String &blurb, const String &description) -{ - assert_return (uint (id) > 0, ParamId (0)); - using namespace MakeIcon; - return add_param (id, Param { - .label = clabel, - .nick = nickname, - .initial = boolvalue, - .hints = Parameter::construct_hints (hints, "toggle", false, true), - .blurb = blurb, - .descr = description, - }, boolvalue); -} - -/// Variant of AudioProcessor::add_param() with sequential `id` generation. -ParamId -AudioProcessor::add_param (const String &clabel, const String &nickname, - bool boolvalue, String hints, - const String &blurb, const String &description) -{ - return add_param (nextid(), clabel, nickname, boolvalue, hints, blurb, description); + assert_return (this_thread_is_ase()); + params_.install (params); + modify_t0events ([&] (std::vector &t0events) { + for (size_t i = 0; i < params_.count; i++) + t0events.push_back (make_param_value (params_.ids[i], params_.parameters[i]->initial().as_double())); + }); } /// Return the ParamId for parameter `identifier` or else 0. @@ -292,59 +227,56 @@ auto AudioProcessor::find_param (const String &identifier) const -> MaybeParamId { auto ident = CString::lookup (identifier); - if (!ident.empty()) - for (const PParam &pp : pparams_) - if (pp.parameter->cident == ident) - return std::make_pair (pp.id, true); + if (ident.empty()) + return { ParamId (0), false }; + for (size_t idx = 0; idx < params_.count; idx++) + if (params_.parameters[idx]->cident == ident) + return { ParamId (params_.ids[idx]), true }; return std::make_pair (ParamId (0), false); } -// Non-fastpath implementation of find_param(). -const AudioProcessor::PParam* -AudioProcessor::find_pparam_ (ParamId paramid) const -{ - // lookup id with gaps - const PParam key { paramid }; - auto iter = Aux::binary_lookup (pparams_.begin(), pparams_.end(), PParam::cmp, key); - const bool found_paramid = iter != pparams_.end(); - if (ISLIKELY (found_paramid)) - return &*iter; - assert_return (found_paramid == true, nullptr); - return nullptr; -} - /// Set parameter `id` to `value` within `ParamInfo.get_minmax()`. bool -AudioProcessor::set_param (Id32 paramid, const double value, bool sendnotify) +AudioProcessor::send_param (Id32 paramid, const double value) { - const PParam *pparam = find_pparam (ParamId (paramid.id)); - return_unless (pparam, false); - ParameterC parameter = pparam->parameter; + assert_return (this_thread_is_ase(), false); // main_loop thread + const ssize_t idx = params_.index (paramid.id); + if (idx < 0) [[unlikely]] + return false; + ParameterC parameter = params_.parameters[idx]; double v = value; if (parameter) v = parameter->dconstrain (value); - const bool need_notify = const_cast (pparam)->assign (v); - if (need_notify && sendnotify && !pparam->aprop_.expired()) - enotify_enqueue_mt (PARAMCHANGE); - return need_notify; + modify_t0events ([&] (std::vector &t0events) { + for (auto &ev : t0events) + if (ev.type == MidiEvent::PARAM_VALUE && + ev.param == params_.ids[idx]) { + ev.pvalue = v; + return; // re-assigned previous send_param event + } + t0events.push_back (make_param_value (params_.ids[idx], v)); + }); + return true; } /// Retrieve supplemental information for parameters, usually to enhance the user interface. ParameterC AudioProcessor::parameter (Id32 paramid) const { - const PParam *pparam = this->find_pparam (ParamId (paramid.id)); - return ASE_ISLIKELY (pparam) ? pparam->parameter : nullptr; + const ssize_t idx = params_.index (paramid.id); + return idx < 0 ? nullptr : params_.parameters[idx]; } -/// Fetch the current parameter value of a AudioProcessor. +/// Fetch the current parameter value of an AudioProcessor. /// This function does not modify the parameter `dirty` flag. /// This function is MT-Safe after proper AudioProcessor initialization. double AudioProcessor::peek_param_mt (Id32 paramid) const { - const PParam *param = find_pparam (ParamId (paramid.id)); - return ASE_ISLIKELY (param) ? param->peek() : FP_NAN; + const ssize_t idx = params_.index (paramid.id); + if (idx < 0) [[unlikely]] + return false; + return params_.values[idx]; } /// Fetch the current parameter value of a AudioProcessor from any thread. @@ -360,9 +292,9 @@ AudioProcessor::param_peek_mt (const AudioProcessorP proc, Id32 paramid) double AudioProcessor::value_to_normalized (Id32 paramid, double value) const { - const PParam *const pparam = find_pparam (paramid); - assert_return (pparam != nullptr, 0); - const auto [fmin, fmax, step] = pparam->parameter->range(); + ParameterC parameterp = parameter (paramid); + assert_return (parameterp != nullptr, 0); + const auto [fmin, fmax, step] = parameterp->range(); const double normalized = (value - fmin) / (fmax - fmin); assert_return (normalized >= 0.0 && normalized <= 1.0, CLAMP (normalized, 0.0, 1.0)); return normalized; @@ -371,9 +303,9 @@ AudioProcessor::value_to_normalized (Id32 paramid, double value) const double AudioProcessor::value_from_normalized (Id32 paramid, double normalized) const { - const PParam *const pparam = find_pparam (paramid); - assert_return (pparam != nullptr, 0); - const auto [fmin, fmax, step] = pparam->parameter->range(); + ParameterC parameterp = parameter (paramid); + assert_return (parameterp != nullptr, 0); + const auto [fmin, fmax, step] = parameterp->range(); const double value = fmin + normalized * (fmax - fmin); assert_return (normalized >= 0.0 && normalized <= 1.0, value); return value; @@ -388,13 +320,13 @@ AudioProcessor::get_normalized (Id32 paramid) /// Set param value normalized into 0…1. bool -AudioProcessor::set_normalized (Id32 paramid, double normalized, bool sendnotify) +AudioProcessor::set_normalized (Id32 paramid, double normalized) { if (!ASE_ISLIKELY (normalized >= 0.0)) normalized = 0; else if (!ASE_ISLIKELY (normalized <= 1.0)) normalized = 1.0; - return set_param (paramid, value_from_normalized (paramid, normalized), sendnotify); + return send_param (paramid, value_from_normalized (paramid, normalized)); } /** Format a parameter `paramid` value as text string. @@ -403,10 +335,10 @@ AudioProcessor::set_normalized (Id32 paramid, double normalized, bool sendnotify * concurrently by a different thread). */ String -AudioProcessor::param_value_to_text (Id32 paramid, double value) const +AudioProcessor::param_value_to_text (uint32_t paramid, double value) const { - const PParam *pparam = find_pparam (ParamId (paramid.id)); - return pparam && pparam->parameter ? pparam->parameter->value_to_text (value) : ""; + ParameterC parameterp = parameter (paramid); + return parameterp ? parameterp->value_to_text (value) : ""; } /** Extract a parameter `paramid` value from a text string. @@ -418,10 +350,10 @@ AudioProcessor::param_value_to_text (Id32 paramid, double value) const * concurrently by a different thread). */ double -AudioProcessor::param_value_from_text (Id32 paramid, const String &text) const +AudioProcessor::param_value_from_text (uint32_t paramid, const String &text) const { - const PParam *pparam = find_pparam (ParamId (paramid.id)); - return pparam && pparam->parameter ? pparam->parameter->value_from_text (text).as_double() : 0.0; + ParameterC parameterp = parameter (paramid); + return parameterp ? parameterp->value_from_text (text).as_double() : 0.0; } /// Prepare `count` bits for atomic notifications. @@ -510,18 +442,6 @@ AudioProcessor::prepare_event_input () estreams_->has_event_input = true; } -static const MidiEventStream empty_event_stream; // dummy - -/// Access the current input EventRange during render(), needs prepare_event_input(). -MidiEventRange -AudioProcessor::get_event_input () -{ - assert_return (estreams_ && estreams_->has_event_input, MidiEventRange (empty_event_stream)); - if (estreams_ && estreams_->oproc && estreams_->oproc->estreams_) - return MidiEventRange (estreams_->oproc->estreams_->estream); - return MidiEventRange (empty_event_stream); -} - /// Prepare the AudioProcessor to produce Event objects during render() via get_event_output(). /// Note, remove_all_buses() will remove the Event output created by this function. void @@ -533,14 +453,6 @@ AudioProcessor::prepare_event_output () estreams_->has_event_output = true; } -/// Access the current output EventStream during render(), needs prepare_event_input(). -MidiEventStream& -AudioProcessor::get_event_output () -{ - assert_return (estreams_ != nullptr, const_cast (empty_event_stream)); - return estreams_->estream; -} - /// Disconnect event input if a connection is present. void AudioProcessor::disconnect_event_input() @@ -814,9 +726,7 @@ AudioProcessor::ensure_initialized (DeviceP devicep) assert_return (n_ibuses() + n_obuses() == 0); assert_return (get_device() == nullptr); device_ = devicep; - tls_param_group = ""; initialize (engine_.speaker_arrangement()); - tls_param_group = ""; flags_ |= INITIALIZED; if (n_ibuses() + n_obuses() == 0 && (!estreams_ || (!estreams_->has_event_input && !estreams_->has_event_output))) @@ -836,7 +746,7 @@ AudioProcessor::reset_state (uint64 target_stamp) if (render_stamp_ != target_stamp) { if (estreams_) - estreams_->estream.clear(); + estreams_->midi_event_output.clear(); reset (target_stamp); render_stamp_ = target_stamp; } @@ -872,6 +782,10 @@ AudioProcessor::schedule_processor() return level + 1; } +struct AudioProcessor::RenderContext { + MidiEventVector *render_events = nullptr; +}; + /** Method called for every audio buffer to be processed. * Each connected output bus needs to be filled with `n_frames`, * i.e. `n_frames` many floating point samples per channel. @@ -890,17 +804,50 @@ AudioProcessor::render_block (uint64 target_stamp) { return_unless (render_stamp_ < target_stamp); return_unless (target_stamp - render_stamp_ <= AUDIO_BLOCK_MAX_RENDER_SIZE); - if (ASE_UNLIKELY (estreams_) && !ASE_ISLIKELY (estreams_->estream.empty())) - estreams_->estream.clear(); + RenderContext rc; + if (ASE_UNLIKELY (estreams_)) + estreams_->midi_event_output.clear(); + rc.render_events = t0events_.exchange (rc.render_events); // fetch t0events_ for rendering + render_context_ = &rc; render (target_stamp - render_stamp_); + render_context_ = nullptr; render_stamp_ = target_stamp; + if (rc.render_events) // delete in main_thread + main_rt_jobs += RtCall (call_delete, rc.render_events); + if (params_.changed) { + params_.changed = false; + enotify_enqueue_mt (PARAMCHANGE); + } +} + +/// Access the current MidiEvent inputs during render(), needs prepare_event_input(). +AudioProcessor::MidiEventInput +AudioProcessor::midi_event_input() +{ + MidiEventInput::VectorArray mev_array{}; + size_t n = 0; + if (estreams_ && estreams_->oproc && estreams_->oproc->estreams_) + mev_array[n++] = &estreams_->oproc->estreams_->midi_event_output.vector(); + if (render_context_->render_events) + mev_array[n++] = render_context_->render_events; + return MidiEventInput (mev_array); +} + +static const MidiEventOutput empty_event_output; // dummy + +/// Access the current output EventStream during render(), needs prepare_event_output(). +MidiEventOutput& +AudioProcessor::midi_event_output() +{ + assert_return (estreams_ != nullptr, const_cast (empty_event_output)); + return estreams_->midi_event_output; } // == Registry == struct AudioProcessorRegistry { - CString aseid; ///< Unique identifier for de-/serialization. - void (*static_info) (AudioProcessorInfo&) = nullptr; - AudioProcessorP (*make_shared) (AudioEngine&) = nullptr; + CString aseid; ///< Unique identifier for de-/serialization. + void (*static_info) (AudioProcessorInfo&) = nullptr; + AudioProcessor::MakeProcessorP make_shared = nullptr; const AudioProcessorRegistry* next = nullptr; }; @@ -923,11 +870,11 @@ AudioProcessor::registry_add (CString aseid, StaticInfo static_info, MakeProcess } DeviceP -AudioProcessor::registry_create (const String &aseid, AudioEngine &engine, const MakeDeviceP &makedevice) +AudioProcessor::registry_create (CString aseid, AudioEngine &engine, const MakeDeviceP &makedevice) { for (const AudioProcessorRegistry *entry = registry_first; entry; entry = entry->next) if (entry->aseid == aseid) { - AudioProcessorP aproc = entry->make_shared (engine); + AudioProcessorP aproc = entry->make_shared (aseid, engine); if (aproc) { DeviceP devicep = makedevice (aseid, entry->static_info, aproc); aproc->ensure_initialized (devicep); @@ -946,35 +893,6 @@ AudioProcessor::registry_foreach (const std::functionaseid, entry->static_info); } -// == AudioProcessor::PParam == -AudioProcessor::PParam::PParam (ParamId _id, uint order, ParameterC ¶meterc) : - id (_id), order_ (order), parameter (parameterc) -{ - assert_return (parameterc != nullptr); -} - -AudioProcessor::PParam::PParam (ParamId _id) : - id (_id) -{} - -AudioProcessor::PParam::PParam (const PParam &src) : - id (src.id) -{ - *this = src; -} - -AudioProcessor::PParam& -AudioProcessor::PParam::operator= (const PParam &src) -{ - id = src.id; - order_ = src.order_; - flags_ = src.flags_.load(); - value_ = src.value_.load(); - aprop_ = src.aprop_; - parameter = src.parameter; - return *this; -} - // == FloatBuffer == /// Check for end-of-buffer overwrites void @@ -996,11 +914,11 @@ AudioProcessor::FloatBuffer::check () // == AudioPropertyImpl == class AudioPropertyImpl : public Property, public virtual EmittableImpl { - DeviceP device_; - ParameterC parameter_; - const ParamId id_; - double inflight_value_ = 0; - std::atomic inflight_counter_ = 0; + DeviceP device_; + ParameterC parameter_; + const uint32_t id_; + double inflight_value_ = 0; + uint64_t inflight_stamp_ = 0; public: String ident () override { return parameter_->ident(); } String label () override { return parameter_->label(); } @@ -1014,14 +932,14 @@ class AudioPropertyImpl : public Property, public virtual EmittableImpl { double get_max () override { const auto [fmin, fmax, step] = parameter_->range(); return fmax; } double get_step () override { const auto [fmin, fmax, step] = parameter_->range(); return step; } explicit - AudioPropertyImpl (DeviceP devp, ParamId id, ParameterC parameter) : + AudioPropertyImpl (DeviceP devp, uint32_t id, ParameterC parameter) : device_ (devp), parameter_ (parameter), id_ (id) {} void proc_paramchange() { const AudioProcessorP proc = device_->_audio_processor(); - const double value = inflight_counter_ ? inflight_value_ : AudioProcessor::param_peek_mt (proc, id_); + const double value = inflight_stamp_ > proc->engine().frame_counter() ? inflight_value_ : AudioProcessor::param_peek_mt (proc, id_); ValueR vfields; vfields["value"] = value; emit_event ("notify", parameter_->ident(), vfields); @@ -1035,7 +953,7 @@ class AudioPropertyImpl : public Property, public virtual EmittableImpl { get_value () override { const AudioProcessorP proc = device_->_audio_processor(); - const double value = inflight_counter_ ? inflight_value_ : AudioProcessor::param_peek_mt (proc, id_); + const double value = inflight_stamp_ > proc->engine().frame_counter() ? inflight_value_ : AudioProcessor::param_peek_mt (proc, id_); const bool ischoice = strstr (parameter_->hints().c_str(), ":choice:") != nullptr; if (ischoice) return proc->param_value_to_text (id_, value); @@ -1047,20 +965,15 @@ class AudioPropertyImpl : public Property, public virtual EmittableImpl { { PropertyP thisp = shared_ptr_cast (this); // thisp keeps this alive during lambda const AudioProcessorP proc = device_->_audio_processor(); - const bool ischoice = strstr (parameter_->hints().c_str(), ":choice:") != nullptr; double v; - if (ischoice && value.index() == Value::STRING) + if (value.index() == Value::STRING && parameter_->is_choice()) v = proc->param_value_from_text (id_, value.as_string()); else v = value.as_double(); - const ParamId pid = id_; + proc->send_param (id_, v); inflight_value_ = v; - inflight_counter_++; - auto lambda = [proc, pid, v, thisp, this] () { - proc->set_param (pid, v, false); - inflight_counter_--; - }; - proc->engine().async_jobs += lambda; + inflight_stamp_ = proc->engine().frame_counter(); + inflight_stamp_ += 2 * proc->engine().block_size(); // wait until after the *next* job queue has been processed emit_notify (parameter_->ident()); return true; } @@ -1068,7 +981,7 @@ class AudioPropertyImpl : public Property, public virtual EmittableImpl { get_normalized () override { const AudioProcessorP proc = device_->_audio_processor(); - const double value = inflight_counter_ ? inflight_value_ : AudioProcessor::param_peek_mt (proc, id_); + const double value = inflight_stamp_ > proc->engine().frame_counter() ? inflight_value_ : AudioProcessor::param_peek_mt (proc, id_); const double v = proc->value_to_normalized (id_, value); return v; } @@ -1084,7 +997,7 @@ class AudioPropertyImpl : public Property, public virtual EmittableImpl { get_text () override { const AudioProcessorP proc = device_->_audio_processor(); - const double value = inflight_counter_ ? inflight_value_ : AudioProcessor::param_peek_mt (proc, id_); + const double value = inflight_stamp_ > proc->engine().frame_counter() ? inflight_value_ : AudioProcessor::param_peek_mt (proc, id_); return proc->param_value_to_text (id_, value); } bool @@ -1092,13 +1005,7 @@ class AudioPropertyImpl : public Property, public virtual EmittableImpl { { const AudioProcessorP proc = device_->_audio_processor(); const double v = proc->param_value_from_text (id_, vstr); - const ParamId pid = id_; - auto lambda = [proc, pid, v] () { - proc->set_param (pid, v, false); - }; - proc->engine().async_jobs += lambda; - emit_notify (parameter_->ident()); - return true; + return set_value (v); } bool is_numeric () override @@ -1114,24 +1021,25 @@ class AudioPropertyImpl : public Property, public virtual EmittableImpl { }; // == AudioProcessor::access_properties == -/// Retrieve/create Property handle from `id`. -/// This function is MT-Safe after proper AudioProcessor initialization. -PropertyP -AudioProcessor::access_property (ParamId id) const +/// Retrieve (or create) Property handles for all properties. +/// This function must be called from the main-thread. +PropertyS +AudioProcessor::access_properties () const { - assert_return (is_initialized(), {}); - const PParam *pparam = find_pparam (id); - assert_return (pparam, {}); DeviceP devp = get_device(); assert_return (devp, {}); - PropertyP newptr; - PropertyP prop = weak_ptr_fetch_or_create (const_cast (pparam)->aprop_, [&] () { - newptr = std::make_shared (devp, pparam->id, pparam->parameter); - return newptr; + PropertyS props; + props.reserve (params_.count); + for (size_t idx = 0; idx < params_.count; idx++) { + const uint32_t id = params_.ids[idx]; + ParameterC parameterp = params_.parameters[idx]; + assert_return (parameterp != nullptr, {}); + PropertyP prop = weak_ptr_fetch_or_create (params_.wprops[idx], [&] () { + return std::make_shared (devp, id, parameterp); }); - if (newptr.get() == prop.get()) - const_cast (pparam)->changed (false); // skip initial change notification - return prop; + props.push_back (prop); + } + return props; } // == enotify_queue == @@ -1186,14 +1094,18 @@ AudioProcessor::enotify_dispatch () if (nflags & REMOVAL) devicep->emit_event ("sub", "remove"); if (nflags & PARAMCHANGE) - for (PParam &pparam : current->pparams_) - if (ASE_UNLIKELY (pparam.changed()) && pparam.changed (false)) - { - PropertyP propi = pparam.aprop_.lock(); - AudioPropertyImpl *aprop = dynamic_cast (propi.get()); - if (aprop) - aprop->proc_paramchange(); - } + for (size_t blockoffset = 0; blockoffset < current->params_.count; blockoffset += 64) + if (current->params_.bits[blockoffset >> 6]) { + const uint64_t bitmask = std::atomic_fetch_and (¤t->params_.bits[blockoffset >> 6], 0); + const size_t bound = std::min (uint64_t (current->params_.count), blockoffset | uint64_t (64-1)); + for (size_t idx = blockoffset; idx < bound; idx++) + if (bitmask & uint64_t (1) << (idx & (64-1))) { + PropertyP propi = current->params_.wprops[idx].lock(); + AudioPropertyImpl *aprop = dynamic_cast (propi.get()); + if (aprop) + aprop->proc_paramchange(); + } + } if (nflags & PARAMCHANGE) devicep->emit_event ("params", "change"); } diff --git a/ase/processor.hh b/ase/processor.hh index 73647496..e0a9b247 100644 --- a/ase/processor.hh +++ b/ase/processor.hh @@ -50,13 +50,35 @@ struct BusInfo { uint n_channels () const; }; +/// Audio parameter handling, internal to AudioProcessor. +struct AudioParams final { + using Map = std::map; + const uint32_t *ids = nullptr; + double *values = nullptr; + std::atomic *bits = nullptr; + const ParameterC *parameters = nullptr; + std::weak_ptr *wprops = nullptr; + uint32 count = 0; + bool changed = false; + void clear (); + void install (const Map ¶ms); + ssize_t index (uint32_t id) const; + double value (uint32_t id) const; + double value (uint32_t id, double newval); + bool dirty (uint32_t id) const { return dirty_index (index (id)); } + void dirty (uint32_t id, bool d) { return dirty_index (index (id), d); } + /*dtor*/ ~AudioParams(); + bool dirty_index (uint32_t idx) const; + void dirty_index (uint32_t idx, bool d); +}; + /// Audio signal AudioProcessor base class, implemented by all effects and instruments. class AudioProcessor : public std::enable_shared_from_this, public FastMemory::NewDeleteBase { struct IBus; struct OBus; struct EventStreams; + struct RenderContext; union PBus; - struct PParam; class FloatBuffer; friend class ProcessorManager; friend class DeviceImpl; @@ -73,6 +95,7 @@ protected: // Inherit `AudioSignal` concepts in derived classes from other namespaces using MinMax = std::pair; #endif + using MidiEventInput = MidiEventReader<2>; enum { INITIALIZED = 1 << 0, // = 1 << 1, SCHEDULED = 1 << 2, @@ -88,13 +111,16 @@ private: uint32 output_offset_ = 0; FloatBuffer *fbuffers_ = nullptr; std::vector iobuses_; - std::vector pparams_; // const once is_initialized() + AudioParams params_; std::vector outputs_; EventStreams *estreams_ = nullptr; AtomicBits *atomic_bits_ = nullptr; uint64_t render_stamp_ = 0; - const PParam* find_pparam (Id32 paramid) const; - const PParam* find_pparam_ (ParamId paramid) const; + using MidiEventVector = std::vector; + using MidiEventVectorAP = std::atomic; + MidiEventVectorAP t0events_ = nullptr; + RenderContext *render_context_ = nullptr; + template void modify_t0events (const F&); void assign_iobufs (); void release_iobufs (); void ensure_initialized (DeviceP devicep); @@ -107,10 +133,16 @@ private: /*copy*/ AudioProcessor (const AudioProcessor&) = delete; virtual void render (uint n_frames) = 0; virtual void reset (uint64 target_stamp) = 0; - PropertyP access_property (ParamId id) const; + PropertyS access_properties () const; +public: + struct ProcessorSetup { + CString aseid; + AudioEngine &engine; + }; protected: AudioEngine &engine_; - explicit AudioProcessor (AudioEngine &engine); + CString aseid_; + explicit AudioProcessor (const ProcessorSetup&); virtual ~AudioProcessor (); virtual void initialize (SpeakerArrangement busses) = 0; void enotify_enqueue_mt (uint32 pushmask); @@ -119,30 +151,10 @@ protected: virtual uint schedule_children () { return 0; } static uint schedule_processor (AudioProcessor &p) { return p.schedule_processor(); } // Parameters - virtual void adjust_param (Id32 tag) {} - ParamId nextid () const; - ParamId add_param (Id32 id, const Param &initparam, double value); - ParamId add_param (Id32 id, const String &clabel, const String &nickname, - double pmin, double pmax, double value, - const String &unit = "", String hints = "", - const String &blurb = "", const String &description = ""); - ParamId add_param (Id32 id, const String &clabel, const String &nickname, - ChoiceS &¢ries, double value, String hints = "", - const String &blurb = "", const String &description = ""); - ParamId add_param (Id32 id, const String &clabel, const String &nickname, - bool boolvalue, String hints = "", - const String &blurb = "", const String &description = ""); - void start_group (const String &groupname) const; - ParamId add_param (const String &clabel, const String &nickname, - double pmin, double pmax, double value, - const String &unit = "", String hints = "", - const String &blurb = "", const String &description = ""); - ParamId add_param (const String &clabel, const String &nickname, - ChoiceS &¢ries, double value, String hints = "", - const String &blurb = "", const String &description = ""); - ParamId add_param (const String &clabel, const String &nickname, - bool boolvalue, String hints = "", - const String &blurb = "", const String &description = ""); + void install_params (const AudioParams::Map ¶ms); + void apply_event (const MidiEvent &event); + void apply_input_events (); + virtual void adjust_param (uint32_t paramid) {} double peek_param_mt (Id32 paramid) const; // Buses IBusId add_input_bus (CString uilabel, SpeakerArrangement speakerarrangement, @@ -163,9 +175,9 @@ protected: void redirect_oblock (OBusId b, uint c, const float *block); // event stream handling void prepare_event_input (); - MidiEventRange get_event_input (); void prepare_event_output (); - MidiEventStream& get_event_output (); + MidiEventInput midi_event_input (); + MidiEventOutput& midi_event_output (); // Atomic notification bits void atomic_bits_resize (size_t count); bool atomic_bit_notify (size_t nth); @@ -185,18 +197,18 @@ public: double inyquist () const ASE_CONST; // Parameters double get_param (Id32 paramid); - bool set_param (Id32 paramid, double value, bool sendnotify = true); + bool send_param (Id32 paramid, double value); ParameterC parameter (Id32 paramid) const; MaybeParamId find_param (const String &identifier) const; MinMax param_range (Id32 paramid) const; bool check_dirty (Id32 paramid) const; - void adjust_params (bool include_nondirty = false); - virtual String param_value_to_text (Id32 paramid, double value) const; - virtual double param_value_from_text (Id32 paramid, const String &text) const; + void adjust_all_params (); + virtual String param_value_to_text (uint32_t paramid, double value) const; + virtual double param_value_from_text (uint32_t paramid, const String &text) const; virtual double value_to_normalized (Id32 paramid, double value) const; virtual double value_from_normalized (Id32 paramid, double normalized) const; double get_normalized (Id32 paramid); - bool set_normalized (Id32 paramid, double normalized, bool sendnotify = true); + bool set_normalized (Id32 paramid, double normalized); bool is_initialized () const; // Buses IBusId find_ibus (const String &name) const; @@ -222,9 +234,9 @@ public: // AudioProcessor Registry using StaticInfo = void (*) (AudioProcessorInfo&); using MakeDeviceP = std::function; - using MakeProcessorP = AudioProcessorP (*) (AudioEngine&); + using MakeProcessorP = AudioProcessorP (*) (CString,AudioEngine&); static void registry_add (CString aseid, StaticInfo, MakeProcessorP); - static DeviceP registry_create (const String &aseid, AudioEngine &engine, const MakeDeviceP&); + static DeviceP registry_create (CString aseid, AudioEngine &engine, const MakeDeviceP&); static void registry_foreach (const std::function &fun); template std::shared_ptr static create_processor (AudioEngine &engine, const Args &...args); @@ -292,56 +304,111 @@ union AudioProcessor::PBus { // AudioProcessor internal input/output event stream book keeping struct AudioProcessor::EventStreams { static constexpr auto EVENT_ISTREAM = IBusId (0xff01); // *not* an input bus, ID is used for OConnection - AudioProcessor *oproc = nullptr; - MidiEventStream estream; + AudioProcessor *oproc = nullptr; + MidiEventOutput midi_event_output; bool has_event_input = false; bool has_event_output = false; }; -// AudioProcessor internal parameter book keeping -struct AudioProcessor::PParam { - explicit PParam (ParamId id); - explicit PParam (ParamId id, uint order, ParameterC ¶meter); - /*copy*/ PParam (const PParam &); - PParam& operator= (const PParam &); - double fetch_and_clean () { dirty (false); return value_; } - double peek () const { return value_; } - bool dirty () const { return flags_ & DIRTY; } - void dirty (bool b) { if (b) flags_ |= DIRTY; else flags_ &= ~uint32 (DIRTY); } - bool changed () const { return flags_ & CHANGED; } - bool changed (bool b) { return CHANGED & (b ? flags_.fetch_or (CHANGED) : - flags_.fetch_and (~uint32 (CHANGED))); } - static int // Helper to keep PParam structures sorted. - cmp (const PParam &a, const PParam &b) - { - return a.id < b.id ? -1 : a.id > b.id; +/// Find index of parameter identified by `id` or return -1. +inline ssize_t +AudioParams::index (const uint32_t id) const +{ + if (id - 1 < count && ids[id - 1] == id) [[likely]] + return id - 1; // fast path for consecutive IDs + if (id < count && ids[id] == id) [[likely]] + return id - 1; // fast handling of 0-based IDs + auto [it,found] = Aux::binary_lookup_insertion_pos (ids, ids + count, + [] (auto a, auto b) { return a < b ? -1 : a > b; }, + id); + return found ? it - ids : -1; +} + +/// Read current value of parameter identified by `id`. +inline double +AudioParams::value (uint32_t id) const +{ + const auto idx = index (id); + return idx < 0 ? 0.0 : values[idx]; +} + +/// Write new value into parameter identified by `id`, return old value. +inline double +AudioParams::value (uint32_t id, double newval) +{ + const auto idx = index (id); + if (idx < 0) return 0.0; + const double old = values[idx]; + values[idx] = newval; + if (!wprops[idx].expired()) { + dirty_index (idx, true); + changed = true; } -public: - ParamId id = {}; ///< Tag to identify parameter in APIs. -private: - enum { DIRTY = 1, CHANGED = 2, }; - std::atomic flags_ = 1; - std::atomic value_ = NAN; - std::weak_ptr aprop_; - friend class AudioProcessor; - uint order_ = 0; -public: - ParameterC parameter; - uint order() const { return order_; } - bool - assign (double f) - { - const double old = value_; - value_ = f; - if (ASE_ISLIKELY (old != value_)) + return old; +} + +/// Check if parameter is dirty (changed). +inline bool +AudioParams::dirty_index (uint32_t idx) const +{ + return bits[idx >> 6] & uint64_t (1) << (idx & (64-1)); +} + +/// Set or clear parameter dirty flag. +inline void +AudioParams::dirty_index (uint32_t idx, bool d) +{ + if (d) + bits[idx >> 6] |= uint64_t (1) << (idx & (64-1)); + else + bits[idx >> 6] &= ~(uint64_t (1) << (idx & (64-1))); +} + +/// Handle atomic swapping of t0events around modifications by `F` [main-thread]. +template void +AudioProcessor::modify_t0events (const F &mod) +{ + ASE_ASSERT_RETURN (this_thread_is_ase()); + MidiEventVector *t0events = nullptr; + t0events = t0events_.exchange (t0events); + if (!t0events) + t0events = new std::vector; + mod (*t0events); + if (!t0events->empty()) + t0events = t0events_.exchange (t0events); + delete t0events; +} + +/// Assign MidiEvent::PARAM_VALUE event values to parameters. +inline void +AudioProcessor::apply_event (const MidiEvent &event) +{ + switch (event.type) + { + case MidiEvent::PARAM_VALUE: + params_.value (event.param, event.pvalue); + break; + default: ; + } +} + +/// Process all input events via apply_event() and adjust_param(). +/// This applies all incoming parameter changes, +/// events like NOTE_ON are not handled. +inline void +AudioProcessor::apply_input_events() +{ + MidiEventInput evinput = midi_event_input(); + for (const auto &event : evinput) + switch (event.type) // event.message() { - const uint32 prev = flags_.fetch_or (DIRTY | CHANGED); - if (!ASE_ISLIKELY (prev & CHANGED)) - return true; // need notify + case MidiEvent::PARAM_VALUE: + apply_event (event); + adjust_param (event.param); + break; + default: ; } - return false; // no notify needed - } -}; +} /// Number of channels described by `speakers`. inline uint @@ -442,41 +509,33 @@ AudioProcessor::n_ochannels (OBusId busid) const return obus.n_channels(); } -// Call adjust_param() for all or just dirty parameters. +// Call adjust_param() for all parameters. inline void -AudioProcessor::adjust_params (bool include_nondirty) -{ - for (const PParam &p : pparams_) - if (include_nondirty || p.dirty()) - adjust_param (p.id); -} - -// Find parameter for internal access. -inline const AudioProcessor::PParam* -AudioProcessor::find_pparam (Id32 paramid) const +AudioProcessor::adjust_all_params() { - // fast path via sequential ids - const size_t idx = paramid.id - 1; - if (ASE_ISLIKELY (idx < pparams_.size()) && ASE_ISLIKELY (pparams_[idx].id == ParamId (paramid.id))) - return &pparams_[idx]; - return find_pparam_ (ParamId (paramid.id)); + for (size_t idx = 0; idx < params_.count; idx++) + adjust_param (params_.ids[idx]); } -/// Fetch `value` of parameter `id` and clear its `dirty` flag. +/// Fetch `value` of parameter `id`. inline double AudioProcessor::get_param (Id32 paramid) { - const PParam *pparam = find_pparam (ParamId (paramid.id)); - return ASE_ISLIKELY (pparam) ? const_cast (pparam)->fetch_and_clean() : FP_NAN; + const ssize_t idx = params_.index (paramid.id); + if (idx < 0) [[unlikely]] + return 0; + return params_.values[idx]; } /// Check if the parameter `dirty` flag is set. -/// Return `true` if set_param() changed the parameter value since the last get_param() call. +/// Return `true` if the parameter value changed during render(). inline bool AudioProcessor::check_dirty (Id32 paramid) const { - const PParam *param = this->find_pparam (ParamId (paramid.id)); - return ASE_ISLIKELY (param) ? param->dirty() : false; + const ssize_t idx = params_.index (paramid.id); + if (idx < 0) [[unlikely]] + return false; + return params_.dirty_index (idx); } /// Access readonly float buffer of input bus `b`, channel `c`, see also ofloats(). @@ -520,10 +579,11 @@ template extern inline CString register_audio_processor (const char *caseid) { CString aseid = caseid ? caseid : typeid_name(); - if constexpr (std::is_constructible::value) + if constexpr (std::is_constructible::value) { - auto make_shared = [] (AudioEngine &engine) -> AudioProcessorP { - return std::make_shared (engine); + auto make_shared = [] (CString aseid, AudioEngine &engine) -> AudioProcessorP { + const AudioProcessor::ProcessorSetup psetup { aseid, engine }; + return std::make_shared (psetup); }; AudioProcessor::registry_add (aseid, &T::static_info, make_shared); } @@ -535,7 +595,9 @@ register_audio_processor (const char *caseid) template std::shared_ptr AudioProcessor::create_processor (AudioEngine &engine, const Args &...args) { - std::shared_ptr proc = std::make_shared (engine, args...); + String aseid = typeid_name(); + const ProcessorSetup psetup { aseid, engine }; + std::shared_ptr proc = std::make_shared (psetup, args...); if (proc) { AudioProcessorP aproc = proc; aproc->ensure_initialized (nullptr); From 9bbe620a0307c3668fae75c2ea00e14a3aca4a83 Mon Sep 17 00:00:00 2001 From: Tim Janik Date: Sun, 12 Nov 2023 21:27:05 +0100 Subject: [PATCH 07/25] ASE: combo: construct AudioProcessor with ProcessorSetup& Signed-off-by: Tim Janik --- ase/combo.cc | 12 ++++++------ ase/combo.hh | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ase/combo.cc b/ase/combo.cc index 56660335..b018dd1c 100644 --- a/ase/combo.cc +++ b/ase/combo.cc @@ -14,8 +14,8 @@ static constexpr OBusId OUT1 = OBusId (1); class AudioChain::Inlet : public AudioProcessor { AudioChain &audio_chain_; public: - Inlet (AudioEngine &engine, AudioChain *audiochain) : - AudioProcessor (engine), + Inlet (const ProcessorSetup &psetup, AudioChain *audiochain) : + AudioProcessor (psetup), audio_chain_ (*audiochain) { assert_return (nullptr != audiochain); @@ -42,8 +42,8 @@ class AudioChain::Inlet : public AudioProcessor { }; // == AudioCombo == -AudioCombo::AudioCombo (AudioEngine &engine) : - AudioProcessor (engine) +AudioCombo::AudioCombo (const ProcessorSetup &psetup) : + AudioProcessor (psetup) {} AudioCombo::~AudioCombo () @@ -133,8 +133,8 @@ AudioCombo::set_event_source (AudioProcessorP eproc) } // == AudioChain == -AudioChain::AudioChain (AudioEngine &engine, SpeakerArrangement iobuses) : - AudioCombo (engine), +AudioChain::AudioChain (const ProcessorSetup &psetup, SpeakerArrangement iobuses) : + AudioCombo (psetup), ispeakers_ (iobuses), ospeakers_ (iobuses) { assert_return (speaker_arrangement_count_channels (iobuses) > 0); diff --git a/ase/combo.hh b/ase/combo.hh index 364e4507..3c4f9425 100644 --- a/ase/combo.hh +++ b/ase/combo.hh @@ -11,7 +11,7 @@ protected: AudioProcessorS processors_; AudioProcessorP eproc_; virtual void reconnect (size_t index, bool insertion) = 0; - explicit AudioCombo (AudioEngine &engine); + explicit AudioCombo (const ProcessorSetup&); virtual ~AudioCombo (); public: void insert (AudioProcessorP proc, ssize_t pos = ~size_t (0)); @@ -37,7 +37,7 @@ protected: void reconnect (size_t index, bool insertion) override; uint chain_up (AudioProcessor &pfirst, AudioProcessor &psecond); public: - explicit AudioChain (AudioEngine &engine, SpeakerArrangement iobuses = SpeakerArrangement::STEREO); + explicit AudioChain (const ProcessorSetup&, SpeakerArrangement iobuses = SpeakerArrangement::STEREO); virtual ~AudioChain (); struct Probe { float dbspl = -192; }; using ProbeArray = std::array; From 01fea449801c5278f00d159ae22cdc95c5f1b257 Mon Sep 17 00:00:00 2001 From: Tim Janik Date: Fri, 27 Oct 2023 19:33:49 +0200 Subject: [PATCH 08/25] ASE: clapplugin: remove unused flags fiels from params Signed-off-by: Tim Janik --- ase/clapplugin.cc | 5 +---- ase/clapplugin.hh | 1 - 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/ase/clapplugin.cc b/ase/clapplugin.cc index aa6516a5..1df7ecaf 100644 --- a/ase/clapplugin.cc +++ b/ase/clapplugin.cc @@ -136,7 +136,6 @@ struct ClapParamInfoImpl : ClapParamInfo { { unset(); param_id = cinfo.id; - flags = cinfo.flags; ident = string_format ("%08x", param_id); name = cinfo.name; module = cinfo.module; @@ -159,7 +158,6 @@ void ClapParamInfo::unset() { param_id = CLAP_INVALID_ID; - flags = 0; ident = ""; name = ""; module = ""; @@ -798,7 +796,6 @@ class ClapPluginHandleImpl : public ClapPluginHandle { ClapParamUpdate update = { .steady_time = 0, // NOW .param_id = param_id, - .flags = 0, .value = CLAMP (v, info->min_value, info->max_value), }; updates.push_back (update); @@ -818,7 +815,7 @@ class ClapPluginHandleImpl : public ClapPluginHandle { loader_updates_ = new ClapParamUpdateS; for (const auto &[id, value] : params) loader_updates_->push_back ({ - .steady_time = 0, .param_id = id, .flags = 0, .value = value, + .steady_time = 0, .param_id = id, .value = value, }); // TODO: flush loader_updates_ right away } diff --git a/ase/clapplugin.hh b/ase/clapplugin.hh index ce9c7f5d..8fe14239 100644 --- a/ase/clapplugin.hh +++ b/ase/clapplugin.hh @@ -30,7 +30,6 @@ public: struct ClapParamUpdate { int64_t steady_time = 0; // unimplemented clap_id param_id = CLAP_INVALID_ID; - uint32_t flags = 0; double value = NAN; }; using ClapParamUpdateS = std::vector; From 108909605de710bb96a1164b9e75fbc4ec014b2a Mon Sep 17 00:00:00 2001 From: Tim Janik Date: Sun, 12 Nov 2023 21:26:40 +0100 Subject: [PATCH 09/25] ASE: clapplugin.cc: construct AudioProcessor with ProcessorSetup& Signed-off-by: Tim Janik --- ase/clapplugin.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ase/clapplugin.cc b/ase/clapplugin.cc index 1df7ecaf..a75f5ddc 100644 --- a/ase/clapplugin.cc +++ b/ase/clapplugin.cc @@ -236,8 +236,8 @@ class ClapAudioProcessor : public AudioProcessor { { info.label = "Anklang.Devices.ClapAudioProcessor"; } - ClapAudioProcessor (AudioEngine &engine) : - AudioProcessor (engine) + ClapAudioProcessor (const ProcessorSetup &psetup) : + AudioProcessor (psetup) {} ~ClapAudioProcessor() { From f68f4f61513188961255f04d275b84adbc44b9cf Mon Sep 17 00:00:00 2001 From: Tim Janik Date: Sun, 12 Nov 2023 21:27:46 +0100 Subject: [PATCH 10/25] ASE: engine: add AudioEngine.engine_stats() Signed-off-by: Tim Janik --- ase/engine.cc | 32 +++++++++++++++++++++++++++++--- ase/engine.hh | 1 + 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/ase/engine.cc b/ase/engine.cc index 108fb999..808c2a1f 100644 --- a/ase/engine.cc +++ b/ase/engine.cc @@ -102,6 +102,7 @@ class AudioEngineThread : public AudioEngine { void start_threads_ml (); void stop_threads_ml (); void create_processors_ml (); + String engine_stats_string (uint64_t stats) const; }; static std::thread::id audio_engine_thread_id = {}; @@ -363,7 +364,7 @@ AudioEngineThread::driver_dispatcher (const LoopState &state) if (schedule_invalid_) { schedule_clear(); - for (AudioProcessorP &proc : oprocs_) + for (AudioProcessorP &proc : oprocs_) proc->schedule_processor(); schedule_invalid_ = false; } @@ -515,6 +516,22 @@ AudioEngineThread::set_project (ProjectImplP project) // dtor of old runs here } +String +AudioEngineThread::engine_stats_string (uint64_t stats) const +{ + String s; + for (size_t i = 0; i < oprocs_.size(); i++) { + AudioProcessorInfo pinfo; + pinfo.label = "INTERNAL"; + AudioProcessor::registry_foreach ([&] (const String &aseid, AudioProcessor::StaticInfo static_info) { + if (aseid == oprocs_[i]->aseid_) + static_info (pinfo); // TODO: this is a bit awkward to fetch AudioProcessorInfo for an AudioProcessor + }); + s += string_format ("%s: %s (MUST_SCHEDULE)\n", pinfo.label, oprocs_[i]->debug_name()); + } + return s; +} + ProjectImplP AudioEngineThread::get_project () { @@ -556,6 +573,15 @@ AudioEngine::~AudioEngine() fatal_error ("AudioEngine must not be destroyed"); } +String +AudioEngine::engine_stats (uint64_t stats) const +{ + String strstats; + const AudioEngineThread &engine_thread = static_cast (*this); + const_cast (this)->synchronized_jobs += [&] () { strstats = engine_thread.engine_stats_string (stats); }; + return strstats; +} + uint64 AudioEngine::frame_counter () const { @@ -779,8 +805,8 @@ class EngineMidiInput : public AudioProcessor { } public: MidiDriverS midi_drivers_; - EngineMidiInput (AudioEngine &engine) : - AudioProcessor (engine) + EngineMidiInput (const ProcessorSetup &psetup) : + AudioProcessor (psetup) {} }; diff --git a/ase/engine.hh b/ase/engine.hh index 683e9c56..f7eb4d63 100644 --- a/ase/engine.hh +++ b/ase/engine.hh @@ -49,6 +49,7 @@ public: void queue_capture_start (CallbackS&, const String &filename, bool needsrunning); void queue_capture_stop (CallbackS&); bool update_drivers (const String &pcm, uint latency_ms, const StringS &midis); + String engine_stats (uint64_t stats) const; static bool thread_is_engine () { return std::this_thread::get_id() == thread_id; } static const ThreadId &thread_id; // JobQueues From 20e95b834ec81b37018022927a811cc30ca0cf24 Mon Sep 17 00:00:00 2001 From: Tim Janik Date: Sun, 12 Nov 2023 21:28:25 +0100 Subject: [PATCH 11/25] ASE: api.hh, server.cc: add Server.engine_stats() Signed-off-by: Tim Janik --- ase/api.hh | 1 + ase/server.cc | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/ase/api.hh b/ase/api.hh index 8e0c64cf..a9cc4f38 100644 --- a/ase/api.hh +++ b/ase/api.hh @@ -372,6 +372,7 @@ public: int32 interval_ms) = 0; ///< Broadcast telemetry memory segments to the current Jsonipc connection. virtual StringS list_preferences () = 0; ///< Retrieve a list of all preference identifiers. virtual PropertyP access_preference (const String &ident) = 0; ///< Retrieve property handle for a Preference identifier. + String engine_stats (); ///< Print engine state. // projects virtual ProjectP last_project () = 0; ///< Retrieve the last created project. virtual ProjectP create_project (String projectname) = 0; ///< Create a new project (name is modified to be unique if necessary. diff --git a/ase/server.cc b/ase/server.cc index 51fd47cd..92419f9d 100644 --- a/ase/server.cc +++ b/ase/server.cc @@ -176,6 +176,14 @@ Server::url_crawler (const String &url) return nullptr; } +String +Server::engine_stats () +{ + const String s = main_config.engine->engine_stats (0); + printerr ("Server::engine_stats:\n%s\n", s); + return s; +} + // == Choice == Choice::Choice (String ident_, String label_, String blurb_, String notice_, String warning_) : ident (ident_.empty() ? string_to_identifier (label_) : ident_), From d15f2e95b1b8621dd48d3ad3158b8a5b8b8e4f69 Mon Sep 17 00:00:00 2001 From: Tim Janik Date: Sun, 12 Nov 2023 21:29:02 +0100 Subject: [PATCH 12/25] ASE: midilib: construct AudioProcessor with ProcessorSetup& Signed-off-by: Tim Janik --- ase/midilib.cc | 4 ++-- ase/midilib.hh | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ase/midilib.cc b/ase/midilib.cc index 8719221b..851f52f4 100644 --- a/ase/midilib.cc +++ b/ase/midilib.cc @@ -38,8 +38,8 @@ class MidiProducerImpl : public MidiProducerIface { std::vector future_stack; // newest events at back() FastMemory::Block position_block_; public: - MidiProducerImpl (AudioEngine &engine) : - MidiProducerIface (engine) + MidiProducerImpl (const ProcessorSetup &psetup) : + MidiProducerIface (psetup) { position_block_ = SERVER->telemem_allocate (sizeof (Position)); position_ = new (position_block_.block_start) Position {}; diff --git a/ase/midilib.hh b/ase/midilib.hh index a0c35fc9..de280347 100644 --- a/ase/midilib.hh +++ b/ase/midilib.hh @@ -29,7 +29,7 @@ public: virtual Position* position () const = 0; // MT-Safe virtual void start () = 0; virtual void stop (bool restart = false) = 0; - MidiProducerIface (AudioEngine &engine) : AudioProcessor (engine) {} + MidiProducerIface (const ProcessorSetup &psetup) : AudioProcessor (psetup) {} }; using MidiProducerIfaceP = std::shared_ptr; From 853f38e6aa9320c419c9788262d34b7b94b0aaee Mon Sep 17 00:00:00 2001 From: Tim Janik Date: Sun, 12 Nov 2023 21:29:25 +0100 Subject: [PATCH 13/25] ASE: nativedevice.cc: use AudioProcessor.access_properties() Signed-off-by: Tim Janik --- ase/nativedevice.cc | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/ase/nativedevice.cc b/ase/nativedevice.cc index ae78eb25..206ec906 100644 --- a/ase/nativedevice.cc +++ b/ase/nativedevice.cc @@ -48,16 +48,7 @@ NativeDeviceImpl::serialize (WritNode &xs) PropertyS NativeDeviceImpl::access_properties () { - std::vector pparams; - pparams.reserve (proc_->pparams_.size()); - for (const AudioProcessor::PParam &p : proc_->pparams_) - pparams.push_back (&p); - std::sort (pparams.begin(), pparams.end(), [] (auto a, auto b) { return a->order() < b->order(); }); - PropertyS pseq; - pseq.reserve (pparams.size()); - for (const AudioProcessor::PParam *p : pparams) - pseq.push_back (proc_->access_property (p->id)); - return pseq; + return proc_->access_properties(); } void From 2b880292e026ddb022e5539a87899303c7007f49 Mon Sep 17 00:00:00 2001 From: Tim Janik Date: Mon, 13 Nov 2023 21:05:18 +0100 Subject: [PATCH 14/25] ASE: rename MidiEventOutput (former MidiEventStream) Signed-off-by: Tim Janik --- ase/driver-alsa.cc | 4 ++-- ase/driver.cc | 2 +- ase/driver.hh | 2 +- ase/engine.cc | 4 ++-- ase/midievent.cc | 14 +++++++------- ase/midievent.hh | 10 +++++----- ase/midilib.cc | 2 +- 7 files changed, 19 insertions(+), 19 deletions(-) diff --git a/ase/driver-alsa.cc b/ase/driver-alsa.cc index aecc7a18..b184869f 100644 --- a/ase/driver-alsa.cc +++ b/ase/driver-alsa.cc @@ -1136,7 +1136,7 @@ class AlsaSeqMidiDriver : public MidiDriver { return snd_seq_event_input_pending (seq_, pull_fifo) > 0; } uint - fetch_events (MidiEventStream &estream, double samplerate) override + fetch_events (MidiEventOutput &estream, double samplerate) override { assert_return (!!evparser_, 0); const size_t old_size = estream.size(); @@ -1147,7 +1147,7 @@ class AlsaSeqMidiDriver : public MidiDriver { return (channel + 1) * 128 + note; }; bool must_sort = false; - const auto add = [&] (MidiEventStream &estream, const snd_seq_event_t *ev, const MidiEvent &event) { + const auto add = [&] (MidiEventOutput &estream, const snd_seq_event_t *ev, const MidiEvent &event) { const double t = ev->time.time.tv_sec + 1e-9 * ev->time.time.tv_nsec; const double diff = t - now; int64_t frames = diff * samplerate; diff --git a/ase/driver.cc b/ase/driver.cc index 2ef82131..3d4c81f7 100644 --- a/ase/driver.cc +++ b/ase/driver.cc @@ -400,7 +400,7 @@ class NullMidiDriver : public MidiDriver { return false; } uint - fetch_events (MidiEventStream&, double) override + fetch_events (MidiEventOutput&, double) override { return 0; // FIXME: needed? } diff --git a/ase/driver.hh b/ase/driver.hh index ee4ba10b..fe56f094 100644 --- a/ase/driver.hh +++ b/ase/driver.hh @@ -77,7 +77,7 @@ public: typedef std::shared_ptr MidiDriverP; static MidiDriverP open (const String &devid, IODir iodir, Ase::Error *ep); virtual bool has_events () = 0; - virtual uint fetch_events (MidiEventStream &estream, double samplerate) = 0; + virtual uint fetch_events (MidiEventOutput &estream, double samplerate) = 0; static EntryVec list_drivers (); static String register_driver (const String &driverid, const std::function &create, diff --git a/ase/engine.cc b/ase/engine.cc index 808c2a1f..0dbd3347 100644 --- a/ase/engine.cc +++ b/ase/engine.cc @@ -790,14 +790,14 @@ class EngineMidiInput : public AudioProcessor { void reset (uint64 target_stamp) override { - MidiEventStream &estream = get_event_output(); + MidiEventOutput &estream = get_event_output(); estream.clear(); estream.reserve (256); } void render (uint n_frames) override { - MidiEventStream &estream = get_event_output(); + MidiEventOutput &estream = get_event_output(); estream.clear(); for (size_t i = 0; i < midi_drivers_.size(); i++) if (midi_drivers_[i]) diff --git a/ase/midievent.cc b/ase/midievent.cc index 167d936b..6991423e 100644 --- a/ase/midievent.cc +++ b/ase/midievent.cc @@ -171,13 +171,13 @@ make_param_value (uint param, double pvalue) return ev; } -// == MidiEventStream == -MidiEventStream::MidiEventStream () +// == MidiEventOutput == +MidiEventOutput::MidiEventOutput () {} /// Append an MidiEvent with conscutive `frame` time stamp. void -MidiEventStream::append (int16_t frame, const MidiEvent &event) +MidiEventOutput::append (int16_t frame, const MidiEvent &event) { const bool out_of_order_event = append_unsorted (frame, event); assert_return (!out_of_order_event); @@ -186,7 +186,7 @@ MidiEventStream::append (int16_t frame, const MidiEvent &event) /// Dangerous! Append a MidiEvent while ignoring sort order, violates constraints. /// Returns if ensure_order() must be called due to adding an out-of-order event. bool -MidiEventStream::append_unsorted (int16_t frame, const MidiEvent &event) +MidiEventOutput::append_unsorted (int16_t frame, const MidiEvent &event) { const int64_t last_event_stamp = !events_.empty() ? events_.back().frame : -2048; events_.push_back (event); @@ -196,7 +196,7 @@ MidiEventStream::append_unsorted (int16_t frame, const MidiEvent &event) /// Fix event order after append_unsorted() returned `true`. void -MidiEventStream::ensure_order () +MidiEventOutput::ensure_order () { fixed_sort (events_.begin(), events_.end(), [] (const MidiEvent &a, const MidiEvent &b) -> bool { return a.frame < b.frame; @@ -205,13 +205,13 @@ MidiEventStream::ensure_order () /// Fetch the latest event stamp, can be used to enforce order. int64_t -MidiEventStream::last_frame () const +MidiEventOutput::last_frame () const { return !events_.empty() ? events_.back().frame : -2048; } // == MidiEventRange == -MidiEventRange::MidiEventRange (const MidiEventStream &estream) : +MidiEventRange::MidiEventRange (const MidiEventOutput &estream) : estream_ (estream) {} diff --git a/ase/midievent.hh b/ase/midievent.hh index 239742d7..31871536 100644 --- a/ase/midievent.hh +++ b/ase/midievent.hh @@ -92,11 +92,11 @@ MidiEvent make_pitch_bend (uint16 chnl, float val); MidiEvent make_param_value (uint param, double pvalue); /// A stream of writable MidiEvent structures. -class MidiEventStream { +class MidiEventOutput { std::vector events_; // TODO: use O(1) allocator friend class MidiEventRange; public: - explicit MidiEventStream (); + explicit MidiEventOutput (); void append (int16_t frame, const MidiEvent &event); const MidiEvent* begin () const noexcept { return &*events_.begin(); } const MidiEvent* end () const noexcept { return &*events_.end(); } @@ -110,14 +110,14 @@ public: void reserve (size_t n) { events_.reserve (n); } }; -/// A readonly view and iterator into an MidiEventStream. +/// A readonly view and iterator into a MidiEventOutput. class MidiEventRange { - const MidiEventStream &estream_; + const MidiEventOutput &estream_; public: const MidiEvent* begin () const { return &*estream_.begin(); } const MidiEvent* end () const { return &*estream_.end(); } size_t events_pending () const { return estream_.size(); } - explicit MidiEventRange (const MidiEventStream &estream); + explicit MidiEventRange (const MidiEventOutput &estream); }; /// Components of a MIDI note. diff --git a/ase/midilib.cc b/ase/midilib.cc index 851f52f4..a79d6e21 100644 --- a/ase/midilib.cc +++ b/ase/midilib.cc @@ -120,7 +120,7 @@ class MidiProducerImpl : public MidiProducerIface { { const AudioTransport &transport = this->transport(); MidiEventRange evinp = get_event_input(); // treat MIDI input as MIDI through - MidiEventStream &evout = get_event_output(); // needs prepare_event_output() + MidiEventOutput &evout = get_event_output(); // needs prepare_event_output() const int64 begin_tick = transport.current_tick; const int64 end_tick = transport.current_tick + transport.sample_to_tick (n_frames); const int64 bpm = transport.current_bpm; From 2f584174ab48a28d1757e029fb97346c89612bce Mon Sep 17 00:00:00 2001 From: Tim Janik Date: Tue, 14 Nov 2023 02:36:32 +0100 Subject: [PATCH 15/25] ASE: cxxaux.hh: add call_delete<> to quickly create delete callbacks Signed-off-by: Tim Janik --- ase/cxxaux.hh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ase/cxxaux.hh b/ase/cxxaux.hh index 060f29e4..c4feca0d 100644 --- a/ase/cxxaux.hh +++ b/ase/cxxaux.hh @@ -233,6 +233,13 @@ delete_inplace (Type &typemem) typemem.~Type(); } +/// Simple way to create a standalone callback to delete an object of type `T`. +template void +call_delete (T *o) +{ + delete o; // automatically handles nullptr +} + /// REQUIRES - Simplified version of std::enable_if::type to use SFINAE in function templates. template using REQUIRES = typename ::std::enable_if::type; From f42044c0019b0843182cb4215d99e3c40007f236 Mon Sep 17 00:00:00 2001 From: Tim Janik Date: Tue, 14 Nov 2023 02:36:56 +0100 Subject: [PATCH 16/25] ASE: clapplugin.cc: use call_delete<>() Signed-off-by: Tim Janik --- ase/clapplugin.cc | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/ase/clapplugin.cc b/ase/clapplugin.cc index a75f5ddc..5a1267f6 100644 --- a/ase/clapplugin.cc +++ b/ase/clapplugin.cc @@ -244,7 +244,7 @@ class ClapAudioProcessor : public AudioProcessor { while (enqueued_events_.size()) { ClapEventParamS *pevents = enqueued_events_.back(); enqueued_events_.pop_back(); - main_rt_jobs += RtCall (delete_clap_event_params, pevents); // delete in main_thread + main_rt_jobs += RtCall (call_delete, pevents); // delete in main_thread } } void @@ -489,16 +489,10 @@ class ClapAudioProcessor : public AudioProcessor { enqueued_events_.erase (enqueued_events_.begin()); for (const auto &e : *pevents) need_wakeup |= apply_param_value_event (e); - main_rt_jobs += RtCall (delete_clap_event_params, pevents); // delete in main_thread + main_rt_jobs += RtCall (call_delete, pevents); // delete in main_thread } return need_wakeup; } - static void - delete_clap_event_params (ClapEventParamS *p) - { - assert_return (this_thread_is_ase()); - delete p; - } }; static CString clap_audio_wrapper_aseid = register_audio_processor(); From 135a420ad7d60d8c65b554ef19028a8965415b66 Mon Sep 17 00:00:00 2001 From: Tim Janik Date: Tue, 14 Nov 2023 02:41:20 +0100 Subject: [PATCH 17/25] ASE: midievent: rename + rewrite MidiEventReader to read many queues * Base MidiEventReader on QueueMultiplexer<> to turn it into a multiplexing event queue reader * Rename MidiEventReader (former MidiEventRange) Signed-off-by: Tim Janik --- ase/midievent.cc | 5 ----- ase/midievent.hh | 36 ++++++++++++++++++++++++++++-------- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/ase/midievent.cc b/ase/midievent.cc index 6991423e..ba6ddd4b 100644 --- a/ase/midievent.cc +++ b/ase/midievent.cc @@ -210,9 +210,4 @@ MidiEventOutput::last_frame () const return !events_.empty() ? events_.back().frame : -2048; } -// == MidiEventRange == -MidiEventRange::MidiEventRange (const MidiEventOutput &estream) : - estream_ (estream) -{} - } // Ase diff --git a/ase/midievent.hh b/ase/midievent.hh index 31871536..6d684215 100644 --- a/ase/midievent.hh +++ b/ase/midievent.hh @@ -3,6 +3,7 @@ #define __ASE_MIDI_EVENT_HH__ #include +#include #include namespace Ase { @@ -94,7 +95,6 @@ MidiEvent make_param_value (uint param, double pvalue); /// A stream of writable MidiEvent structures. class MidiEventOutput { std::vector events_; // TODO: use O(1) allocator - friend class MidiEventRange; public: explicit MidiEventOutput (); void append (int16_t frame, const MidiEvent &event); @@ -108,18 +108,38 @@ public: int64_t last_frame () const ASE_PURE; size_t capacity () const noexcept { return events_.capacity(); } void reserve (size_t n) { events_.reserve (n); } + std::vector + const& vector () { return events_; } }; -/// A readonly view and iterator into a MidiEventOutput. -class MidiEventRange { - const MidiEventOutput &estream_; +/// An in-order MidiEvent reader for multiple MidiEvent sources. +template +class MidiEventReader : QueueMultiplexer::const_iterator> { + using Base = QueueMultiplexer::const_iterator>; + ASE_CLASS_NON_COPYABLE (MidiEventReader); public: - const MidiEvent* begin () const { return &*estream_.begin(); } - const MidiEvent* end () const { return &*estream_.end(); } - size_t events_pending () const { return estream_.size(); } - explicit MidiEventRange (const MidiEventOutput &estream); + using iterator = Base::iterator; + using Base::assign; + size_t events_pending () const { return this->count_pending(); } + iterator begin () { return this->Base::begin(); } + iterator end () { return this->Base::end(); } + using VectorArray = std::array*, MAXQUEUES>; + /*ctor*/ MidiEventReader (const VectorArray &midi_event_vector_array = VectorArray()); }; +// == MidiEventReader == +template +MidiEventReader::MidiEventReader (const VectorArray &midi_event_vector_array) +{ + assign (midi_event_vector_array); +} + +inline int +QueueMultiplexer_priority (const MidiEvent &e) +{ + return e.frame; +} + /// Components of a MIDI note. struct MidiNote { static constexpr const int NMIN = 0; From cbd17604e489289f06d6e16b34b0566d14ba12ff Mon Sep 17 00:00:00 2001 From: Tim Janik Date: Tue, 14 Nov 2023 02:42:05 +0100 Subject: [PATCH 18/25] ASE: engine.cc: use midi_event_output() Signed-off-by: Tim Janik --- ase/engine.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ase/engine.cc b/ase/engine.cc index 0dbd3347..61936249 100644 --- a/ase/engine.cc +++ b/ase/engine.cc @@ -790,14 +790,14 @@ class EngineMidiInput : public AudioProcessor { void reset (uint64 target_stamp) override { - MidiEventOutput &estream = get_event_output(); + MidiEventOutput &estream = midi_event_output(); estream.clear(); estream.reserve (256); } void render (uint n_frames) override { - MidiEventOutput &estream = get_event_output(); + MidiEventOutput &estream = midi_event_output(); estream.clear(); for (size_t i = 0; i < midi_drivers_.size(); i++) if (midi_drivers_[i]) From be4f95f09ce93daef89597bc67134ca427fe26d0 Mon Sep 17 00:00:00 2001 From: Tim Janik Date: Wed, 15 Nov 2023 16:45:04 +0100 Subject: [PATCH 19/25] ASE: engine: allow atomic frame_counter and block_size access Signed-off-by: Tim Janik --- ase/engine.cc | 10 +++++----- ase/engine.hh | 4 +++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/ase/engine.cc b/ase/engine.cc index 61936249..ad4c2707 100644 --- a/ase/engine.cc +++ b/ase/engine.cc @@ -53,9 +53,9 @@ class AudioEngineThread : public AudioEngine { static constexpr uint fixed_n_channels = 2; PcmDriverP null_pcm_driver_, pcm_driver_; constexpr static size_t MAX_BUFFER_SIZE = AUDIO_BLOCK_MAX_RENDER_SIZE; - size_t buffer_size_ = MAX_BUFFER_SIZE; // mono buffer size + std::atomic buffer_size_ = MAX_BUFFER_SIZE; // mono buffer size float chbuffer_data_[MAX_BUFFER_SIZE * fixed_n_channels] = { 0, }; - uint64 write_stamp_ = 0, render_stamp_ = MAX_BUFFER_SIZE; + uint64 write_stamp_ = 0; std::vector schedule_; EngineMidiInputP midi_proc_; bool schedule_invalid_ = true; @@ -81,7 +81,6 @@ class AudioEngineThread : public AudioEngine { void schedule_clear (); void schedule_add (AudioProcessor &aproc, uint level); void schedule_queue_update (); - uint64 frame_counter () const { return render_stamp_; } void schedule_render (uint64 frames); void enable_output (AudioProcessor &aproc, bool onoff); void wakeup_thread_mt (); @@ -549,6 +548,7 @@ AudioEngineThread::AudioEngineThread (const VoidF &owner_wakeup, uint sample_rat AudioEngine (*this, *new (transport_block.block_start) AudioTransport (speakerarrangement, sample_rate)), owner_wakeup_ (owner_wakeup), transport_block_ (transport_block) { + render_stamp_ = MAX_BUFFER_SIZE; // enforce non-0 start offset for all modules oprocs_.reserve (16); assert_return (transport_.samplerate == 48000); } @@ -583,10 +583,10 @@ AudioEngine::engine_stats (uint64_t stats) const } uint64 -AudioEngine::frame_counter () const +AudioEngine::block_size() const { const AudioEngineThread &impl = static_cast (*this); - return impl.frame_counter(); + return impl.buffer_size_; } void diff --git a/ase/engine.hh b/ase/engine.hh index f7eb4d63..bb4615e3 100644 --- a/ase/engine.hh +++ b/ase/engine.hh @@ -22,6 +22,7 @@ class AudioEngine : VirtualBase { protected: friend class AudioProcessor; std::atomic processor_count_ alignas (64) = 0; + std::atomic render_stamp_ = 0; AudioTransport &transport_; explicit AudioEngine (AudioEngineThread&, AudioTransport&); virtual ~AudioEngine (); @@ -39,7 +40,8 @@ public: void set_project (ProjectImplP project); ProjectImplP get_project (); // MT-Safe API - uint64_t frame_counter () const; + uint64_t frame_counter () const { return render_stamp_; } + uint64_t block_size () const; const AudioTransport& transport () const { return transport_; } uint sample_rate () const ASE_CONST { return transport().samplerate; } uint nyquist () const ASE_CONST { return transport().nyquist; } From 753a718d343e3703ce7880a64d753804692c4d1d Mon Sep 17 00:00:00 2001 From: Tim Janik Date: Tue, 14 Nov 2023 02:42:32 +0100 Subject: [PATCH 20/25] ASE: clapplugin.cc: use midi_event_input() Signed-off-by: Tim Janik --- ase/clapplugin.cc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ase/clapplugin.cc b/ase/clapplugin.cc index 5a1267f6..3a30e5bf 100644 --- a/ase/clapplugin.cc +++ b/ase/clapplugin.cc @@ -538,12 +538,12 @@ setup_expression (ClapEventUnion *evunion, uint32_t time, uint16_t port_index) void ClapAudioProcessor::convert_clap_events (const clap_process_t &process, const bool as_clapnotes) { - MidiEventRange erange = get_event_input(); - if (input_events_.capacity() < erange.events_pending()) - input_events_.reserve (erange.events_pending() + 128); - input_events_.resize (erange.events_pending()); + MidiEventInput evinput = midi_event_input(); + if (input_events_.capacity() < evinput.events_pending()) + input_events_.reserve (evinput.events_pending() + 128); + input_events_.resize (evinput.events_pending()); uint j = 0; - for (const auto &ev : erange) + for (const auto &ev : evinput) switch (ev.message()) { clap_event_note_expression *expr; From 159bd27851799d0559b9af5e0352a04d6c5b5f35 Mon Sep 17 00:00:00 2001 From: Tim Janik Date: Tue, 14 Nov 2023 02:43:22 +0100 Subject: [PATCH 21/25] ASE: midilib.cc: use midi_event_input() and midi_event_output() Signed-off-by: Tim Janik --- ase/midilib.cc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ase/midilib.cc b/ase/midilib.cc index a79d6e21..ad47ca97 100644 --- a/ase/midilib.cc +++ b/ase/midilib.cc @@ -119,8 +119,8 @@ class MidiProducerImpl : public MidiProducerIface { render (uint n_frames) override { const AudioTransport &transport = this->transport(); - MidiEventRange evinp = get_event_input(); // treat MIDI input as MIDI through - MidiEventOutput &evout = get_event_output(); // needs prepare_event_output() + MidiEventInput evinput = midi_event_input(); // treat MIDI input as MIDI through + MidiEventOutput &evout = midi_event_output(); // needs prepare_event_output() const int64 begin_tick = transport.current_tick; const int64 end_tick = transport.current_tick + transport.sample_to_tick (n_frames); const int64 bpm = transport.current_bpm; @@ -151,10 +151,10 @@ class MidiProducerImpl : public MidiProducerIface { evout.append_unsorted (frame, tnote.event); } // enqueue pending MIDI input events - for (const MidiEvent *midi_through = evinp.begin(); ASE_UNLIKELY (midi_through < evinp.end()); midi_through++) + for (const MidiEvent &mevent : evinput) { - MDEBUG ("THROUGH: f=%+3d ev=%s\n", midi_through->frame, midi_through->to_string()); - evout.append (midi_through->frame, *midi_through); + MDEBUG ("THROUGH: f=%+3d ev=%s\n", mevent.frame, mevent.to_string()); + evout.append (mevent.frame, mevent); } // enqueue new events, keep queue of future events generated on the fly (NOTE-OFF) if (ASE_ISLIKELY (feed_ && feed_->generators.size() && From 10a2f7eec6e3264d119a6424f541511feb5eaeb0 Mon Sep 17 00:00:00 2001 From: Tim Janik Date: Sat, 11 Nov 2023 21:45:37 +0100 Subject: [PATCH 22/25] DEVICES: Makefile.mk: include colorednoise.cc in the build Signed-off-by: Tim Janik --- devices/Makefile.mk | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/devices/Makefile.mk b/devices/Makefile.mk index a14f9487..b1a734e6 100644 --- a/devices/Makefile.mk +++ b/devices/Makefile.mk @@ -7,6 +7,11 @@ devices/4ase.ccfiles ::= include devices/blepsynth/Makefile.mk include devices/freeverb/Makefile.mk +# local sources +devices/4ase.ccfiles += $(strip \ + devices/colorednoise.cc \ +) + # derive object files devices/4ase.objects ::= $(call BUILDDIR_O, $(devices/4ase.ccfiles)) From e78a22680c714c854b2b307349b16ed0a6285f8a Mon Sep 17 00:00:00 2001 From: Tim Janik Date: Sat, 11 Nov 2023 21:46:04 +0100 Subject: [PATCH 23/25] DEVICES: colorednoise.cc: use adjust_all_params() and apply_input_events() * Port to install_params(ParameterMap) * Construct AudioProcessor with ProcessorSetup& Signed-off-by: Tim Janik --- devices/colorednoise.cc | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/devices/colorednoise.cc b/devices/colorednoise.cc index d9eeb7cf..a0396f8d 100644 --- a/devices/colorednoise.cc +++ b/devices/colorednoise.cc @@ -85,8 +85,8 @@ class ColoredNoise : public AudioProcessor { bool pink_ = true; enum Params { GAIN = 1, MONO, PINK }; public: - ColoredNoise (AudioEngine &engine) : - AudioProcessor (engine) + ColoredNoise (const ProcessorSetup &psetup) : + AudioProcessor (psetup) {} static void static_info (AudioProcessorInfo &info) @@ -100,17 +100,22 @@ class ColoredNoise : public AudioProcessor { void initialize (SpeakerArrangement busses) override { - start_group ("Noise Settings"); - add_param (GAIN, "Gain", "Gain", -96, 24, 0, "dB"); - add_param (MONO, "Mono", "Monophonic", false); - add_param (PINK, "Pink", "Pink Noise", true); remove_all_buses(); stereout_ = add_output_bus ("Stereo Out", SpeakerArrangement::STEREO); + + ParameterMap pmap; + + pmap.group = _("Noise Settings"); + pmap[GAIN] = Param { "gain", _("Gain"), _("Gain"), 0, "dB", { -96, 24, }, }; + pmap[MONO] = Param { "mono", _("Monophonic"), _("Mono"), false }; + pmap[PINK] = Param { "pink", _("Pink Noise"), _("Pink"), true }; + + install_params (pmap); } void - adjust_param (Id32 tag) override + adjust_param (uint32_t tag) override { - switch (Params (tag.id)) + switch (Params (tag)) { case GAIN: gain_factor_ = db2amp (get_param (tag)); break; case MONO: mono_ = get_param (tag); break; @@ -122,7 +127,7 @@ class ColoredNoise : public AudioProcessor { { pink0.reset(); pink1.reset(); - adjust_params (true); + adjust_all_params(); } void render (uint n_frames) override; // optimize rendering variants via template argument @@ -180,7 +185,7 @@ static const auto render_table = make_case_table ([] (auto C void ColoredNoise::render (uint n_frames) { - adjust_params (false); + apply_input_events(); float *out0 = oblock (stereout_, 0); float *out1 = oblock (stereout_, 1); const float gain = gain_factor_; From 56e3efd025f31b48f705b2320cced9cd59f21156 Mon Sep 17 00:00:00 2001 From: Tim Janik Date: Sat, 11 Nov 2023 03:25:26 +0100 Subject: [PATCH 24/25] DEVICES: freeverb/freeverb.cc: support new param events * Use install_params(ParameterMap) * Construct AudioProcessor with ProcessorSetup& * Use adjust_all_params() and apply_input_events() * Fix MODE hints and param position Signed-off-by: Tim Janik --- devices/freeverb/freeverb.cc | 47 ++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/devices/freeverb/freeverb.cc b/devices/freeverb/freeverb.cc index 351f155b..c38f3c0d 100644 --- a/devices/freeverb/freeverb.cc +++ b/devices/freeverb/freeverb.cc @@ -16,8 +16,8 @@ class Freeverb : public AudioProcessor { OBusId stereout; revmodel model; public: - Freeverb (AudioEngine &engine) : - AudioProcessor (engine) + Freeverb (const ProcessorSetup &psetup) : + AudioProcessor (psetup) {} static void static_info (AudioProcessorInfo &info) @@ -37,33 +37,34 @@ class Freeverb : public AudioProcessor { stereoin = add_input_bus ("Stereo In", SpeakerArrangement::STEREO); stereout = add_output_bus ("Stereo Out", SpeakerArrangement::STEREO); - start_group ("Reverb Settings"); - add_param (DRY, "Dry level", "Dry", 0, scaledry, scaledry * initialdry, "dB"); + ParameterMap pmap; - add_param (WET, "Wet level", "Wet", 0, scalewet, scalewet * initialwet, "dB"); + pmap.group = _("Reverb Settings"); + pmap[DRY] = Param ("drylevel", _("Dry level"), _("Dry"), scaledry * initialdry, "dB", { 0, scaledry }); + pmap[WET] = Param ("wetlevel", _("Wet level"), _("Wet"), scalewet * initialwet, "dB", { 0, scalewet }); - start_group ("Room Settings"); - add_param (ROOMSIZE, "Room size", "RS", offsetroom, offsetroom + scaleroom, offsetroom + scaleroom * initialroom, "size"); - - add_param (WIDTH, "Width", "W", 0, 100, 100 * initialwidth, "%"); + ChoiceS centries; + centries += { "Signflip 2000", _("Preserve May 2000 Freeverb damping sign flip") }; + centries += { "VLC Damping", _("The VLC Freeverb version disables one damping feedback chain") }; + centries += { "Normal Damping", _("Damping with sign correction as implemented in STK Freeverb") }; + pmap[MODE] = Param ("mode", _("Mode"), _("Mode"), 2, "", std::move (centries), "", _("Damping mode found in different Freeverb variants")); - add_param (DAMPING, "Damping", "D", 0, 100, 100 * initialdamp, "%"); + pmap.group = _("Room Settings"); + pmap[ROOMSIZE] = Param ("roomsize", _("Room size"), _("RS"), offsetroom + scaleroom * initialroom, _("size"), { offsetroom, offsetroom + scaleroom }); + pmap[WIDTH] = Param ("width", _("Width"), _("W"), 100 * initialwidth, "%", { 0, 100 }); + pmap[DAMPING] = Param ("damping", _("Damping"), _("D"), 100 * initialdamp, "%", { 0, 100 }); - ChoiceS centries; - centries += { "Signflip 2000", "Preserve May 2000 Freeverb damping sign flip" }; - centries += { "VLC Damping", "The VLC Freeverb version disables one damping feedback chain" }; - centries += { "Normal Damping", "Damping with sign correction as implemented in STK Freeverb" }; - add_param (MODE, "Mode", "M", std::move (centries), 2, "", "Damping mode found in different Freeverb variants"); + install_params (pmap); } void - adjust_param (Id32 tag) override + adjust_param (uint32_t paramid) override { - switch (Params (tag.id)) + switch (Params (paramid)) { - case WET: return model.setwet (get_param (tag) / scalewet); - case DRY: return model.setdry (get_param (tag) / scaledry); - case ROOMSIZE: return model.setroomsize ((get_param (tag) - offsetroom) / scaleroom); - case WIDTH: return model.setwidth (0.01 * get_param (tag)); + case WET: return model.setwet (get_param (paramid) / scalewet); + case DRY: return model.setdry (get_param (paramid) / scaledry); + case ROOMSIZE: return model.setroomsize ((get_param (paramid) - offsetroom) / scaleroom); + case WIDTH: return model.setwidth (0.01 * get_param (paramid)); case MODE: case DAMPING: return model.setdamp (0.01 * get_param (ParamId (DAMPING)), 1 - irintf (get_param (ParamId (MODE)))); @@ -74,12 +75,12 @@ class Freeverb : public AudioProcessor { { model.setmode (0); // no-freeze, allow mute model.mute(); // silence internal buffers - adjust_params (true); + adjust_all_params(); } void render (uint n_frames) override { - adjust_params (false); + apply_input_events(); float *input0 = const_cast (ifloats (stereoin, 0)); float *input1 = const_cast (ifloats (stereoin, 1)); float *output0 = oblock (stereout, 0); From 8e1185ca30c654f96b9e78c50aa60857522ba318 Mon Sep 17 00:00:00 2001 From: Tim Janik Date: Sat, 11 Nov 2023 19:16:12 +0100 Subject: [PATCH 25/25] DEVICES: blepsynth/blepsynth.cc: port to new Parameter API * Start using install_params() * Rename pid_key_[abcdefg]_, add key prefix * Declare all parameter IDs in an enum * Use ase_gettext() multi arg formatting * Construct AudioProcessor with ProcessorSetup& * Add "toggle" hint for piano key properties * Handle PARAM_VALUE events via apply_event() * Start using midi_event_input() * Port remaining OSC1 and OSC2 parameter references * Adjust and check parameter state *after* processing MIDI events * Adjust parameter ID types Signed-off-by: Tim Janik --- devices/blepsynth/blepsynth.cc | 299 +++++++++++++++------------------ 1 file changed, 140 insertions(+), 159 deletions(-) diff --git a/devices/blepsynth/blepsynth.cc b/devices/blepsynth/blepsynth.cc index f3232e01..dc65d9b7 100644 --- a/devices/blepsynth/blepsynth.cc +++ b/devices/blepsynth/blepsynth.cc @@ -335,55 +335,25 @@ class FlexADSR // - aliasing-free square/saw and similar sounds including hard sync class BlepSynth : public AudioProcessor { OBusId stereout_; - ParamId pid_c_, pid_d_, pid_e_, pid_f_, pid_g_; - bool old_c_, old_d_, old_e_, old_f_, old_g_; - - struct OscParams { - ParamId shape; - ParamId pulse_width; - ParamId sub; - ParamId sub_width; - ParamId sync; - ParamId octave; - ParamId pitch; - - ParamId unison_voices; - ParamId unison_detune; - ParamId unison_stereo; + bool old_c_, old_d_, old_e_, old_f_, old_g_; + + enum ParamType : uint32_t { + 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, + 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, + VE_MODEL, ATTACK, DECAY, SUSTAIN, RELEASE, ATTACK_SLOPE, DECAY_SLOPE, RELEASE_SLOPE, + CUTOFF, RESONANCE, DRIVE, KEY_TRACK, FILTER_TYPE, LADDER_MODE, SKFILTER_MODE, + FIL_ATTACK, FIL_DECAY, FIL_SUSTAIN, FIL_RELEASE, FIL_CUT_MOD, + MIX, VEL_TRACK, POST_GAIN, + KEY_C, KEY_D, KEY_E, KEY_F, KEY_G, }; - OscParams osc_params[2]; - ParamId pid_mix_; - ParamId pid_vel_track_; - ParamId pid_post_gain_; - - ParamId pid_cutoff_; - ParamId pid_resonance_; - ParamId pid_drive_; - ParamId pid_key_track_; - ParamId pid_filter_type_; - ParamId pid_ladder_mode_; - ParamId pid_skfilter_mode_; enum { FILTER_TYPE_BYPASS, FILTER_TYPE_LADDER, FILTER_TYPE_SKFILTER }; static constexpr int CUTOFF_MIN_MIDI = 15; static constexpr int CUTOFF_MAX_MIDI = 144; - ParamId pid_attack_; - ParamId pid_decay_; - ParamId pid_sustain_; - ParamId pid_release_; - ParamId pid_ve_model_; - ParamId pid_attack_slope_; - ParamId pid_decay_slope_; - ParamId pid_release_slope_; bool need_update_volume_envelope_; - ParamId pid_fil_attack_; - ParamId pid_fil_decay_; - ParamId pid_fil_sustain_; - ParamId pid_fil_release_; - ParamId pid_fil_cut_mod_; bool need_update_filter_envelope_; class Voice @@ -436,66 +406,71 @@ class BlepSynth : public AudioProcessor { using namespace MakeIcon; set_max_voices (32); - auto oscparams = [&] (int o) { - start_group (string_format ("Oscillator %d", o + 1)); - osc_params[o].shape = add_param (string_format ("Osc %d Shape", o + 1), "Shape", -100, 100, 0, "%"); - osc_params[o].pulse_width = add_param (string_format ("Osc %d Pulse Width", o + 1), "P.W", 0, 100, 50, "%"); - osc_params[o].sub = add_param (string_format ("Osc %d Subharmonic", o + 1), "Sub", 0, 100, 0, "%"); - osc_params[o].sub_width = add_param (string_format ("Osc %d Subharmonic Width", o + 1), "Sub.W", 0, 100, 50, "%"); - osc_params[o].sync = add_param (string_format ("Osc %d Sync Slave", o + 1), "Sync", 0, 60, 0, "semitones"); + ParameterMap pmap; - osc_params[o].pitch = add_param (string_format ("Osc %d Pitch", o + 1), "Pitch", -7, 7, 0, "semitones"); - osc_params[o].octave = add_param (string_format ("Osc %d Octave", o + 1), "Octave", -2, 3, 0, "octaves"); + auto oscparams = [&] (int oscnum) { + const uint I = oscnum + 1; + const uint O = oscnum * (OSC2_SHAPE - OSC1_SHAPE); + const String o = string_format ("osc_%u_", I); + pmap.group = _("Oscillator %u", I); + pmap[O+OSC1_SHAPE] = Param { o+"shape", _("Osc %u Shape", I), _("Shp%u", I), 0, "%", { -100, 100, }, }; + pmap[O+OSC1_PULSE_WIDTH] = Param { o+"pulse_width", _("Osc %u Pulse Width", I), _("PW%u", I), 50, "%", { 0, 100, }, }; + pmap[O+OSC1_SUB] = Param { o+"subharmonic", _("Osc %u Subharmonic", I), _("Sub%u", I), 0, "%", { 0, 100, }, }; + pmap[O+OSC1_SUB_WIDTH] = Param { o+"subharmonic_width", _("Osc %u Subharmonic Width", I), _("SbW%u", I), 50, "%", { 0, 100, }, }; + pmap[O+OSC1_SYNC] = Param { o+"sync_slave", _("Osc %u Sync Slave", I), _("Syn%u", I), 0, "Semitones", { 0, 60, }, }; + + pmap[O+OSC1_PITCH] = Param { o+"pitch", _("Osc %u Pitch", I), _("Pit%u", I), 0, "semitones", { -7, 7, }, }; + pmap[O+OSC1_OCTAVE] = Param { o+"octave", _("Osc %u Octave", I), _("Oct%u", I), 0, "octaves", { -2, 3, }, }; /* TODO: unison_voices property should have stepping set to 1 */ - osc_params[o].unison_voices = add_param (string_format ("Osc %d Unison Voices", o + 1), "Voices", 1, 16, 1, "voices"); - osc_params[o].unison_detune = add_param (string_format ("Osc %d Unison Detune", o + 1), "Detune", 0.5, 50, 6, "%"); - osc_params[o].unison_stereo = add_param (string_format ("Osc %d Unison Stereo", o + 1), "Stereo", 0, 100, 0, "%"); + pmap[O+OSC1_UNISON_VOICES] = Param { o+"unison_voices", _("Osc %u Unison Voices", I), _("Voi%u", I), 1, "Voices", { 1, 16, }, }; + pmap[O+OSC1_UNISON_DETUNE] = Param { o+"unison_detune", _("Osc %u Unison Detune", I), _("Dtu%u", I), 6, "%", { 0.5, 50, }, }; + pmap[O+OSC1_UNISON_STEREO] = Param { o+"unison_stereo", _("Osc %u Unison Stereo", I), _("Ste%u", I), 0, "%", { 0, 100, }, }; }; oscparams (0); - start_group ("Mix"); - pid_mix_ = add_param ("Mix", "Mix", 0, 100, 0, "%"); - pid_vel_track_ = add_param ("Velocity Tracking", "VelTr", 0, 100, 50, "%"); - // TODO: this probably should default to 0dB once we have track/mixer volumes - pid_post_gain_ = add_param ("Post Gain", "Gain", -24, 24, -12, "dB"); + pmap.group = _("Mix"); + pmap[MIX] = Param { "mix", _("Mix"), _("Mix"), 0, "%", { 0, 100 }, }; + pmap[VEL_TRACK] = Param { "vel_track", _("Velocity Tracking"), _("VelTr"), 50, "%", { 0, 100, }, }; + // TODO: post_gain probably should default to 0dB once we have track/mixer volumes + pmap[POST_GAIN] = Param { "post_gain", _("Post Gain"), _("Gain"), -12, "dB", { -24, 24, }, }; oscparams (1); - start_group ("Volume Envelope"); + pmap.group = _("Volume Envelope"); ChoiceS ve_model_cs; ve_model_cs += { "A", "Analog" }; ve_model_cs += { "F", "Flexible" }; - pid_ve_model_ = add_param ("Envelope Model", "Model", std::move (ve_model_cs), 0, "", "ADSR Model to be used"); + pmap[VE_MODEL] = Param { "ve_model", _("Envelope Model"), _("Model"), 0, "", std::move (ve_model_cs), "", "ADSR Model to be used" }; - pid_attack_ = add_param ("Attack", "A", 0, 100, 20.0, "%"); - pid_decay_ = add_param ("Decay", "D", 0, 100, 30.0, "%"); - pid_sustain_ = add_param ("Sustain", "S", 0, 100, 50.0, "%"); - pid_release_ = add_param ("Release", "R", 0, 100, 30.0, "%"); + pmap[ATTACK] = Param { "attack", _("Attack"), _("A"), 20.0, "%", { 0, 100, }, }; + pmap[DECAY] = Param { "decay", _("Decay"), _("D"), 30.0, "%", { 0, 100, }, }; + pmap[SUSTAIN] = Param { "sustain", _("Sustain"), _("S"), 50.0, "%", { 0, 100, }, }; + pmap[RELEASE] = Param { "release", _("Release"), _("R"), 30.0, "%", { 0, 100, }, }; - pid_attack_slope_ = add_param ("Attack Slope", "AS", -100, 100, 50, "%"); - pid_decay_slope_ = add_param ("Decay Slope", "DS", -100, 100, -100, "%"); - pid_release_slope_ = add_param ("Release Slope", "RS", -100, 100, -100, "%"); + pmap[ATTACK_SLOPE] = Param { "attack_slope", _("Attack Slope"), _("AS"), 50, "%", { -100, 100, }, }; + pmap[DECAY_SLOPE] = Param { "decay_slope", _("Decay Slope"), _("DS"), -100, "%", { -100, 100, }, }; + pmap[RELEASE_SLOPE] = Param { "release_slope", _("Release Slope"), _("RS"), -100, "%", { -100, 100, }, }; - start_group ("Filter"); + pmap.group = _("Filter"); - pid_cutoff_ = add_param ("Cutoff", "Cutoff", CUTOFF_MIN_MIDI, CUTOFF_MAX_MIDI, 60); // cutoff as midi notes - pid_resonance_ = add_param ("Resonance", "Reso", 0, 100, 25.0, "%"); - pid_drive_ = add_param ("Drive", "Drive", -24, 36, 0, "dB"); - pid_key_track_ = add_param ("Key Tracking", "KeyTr", 0, 100, 50, "%"); + pmap[CUTOFF] = Param { "cutoff", _("Cutoff"), _("Cutoff"), 60, "", { CUTOFF_MIN_MIDI, CUTOFF_MAX_MIDI, }, }; // cutoff as midi notes + pmap[RESONANCE] = Param { "resonance", _("Resonance"), _("Reso"), 25.0, "%", { 0, 100, }, }; + pmap[DRIVE] = Param { "drive", _("Drive"), _("Drive"), 0, "dB", { -24, 36, }, }; + pmap[KEY_TRACK] = Param { "key_tracking", _("Key Tracking"), _("KeyTr"), 50, "%", { 0, 100, }, }; ChoiceS filter_type_choices; filter_type_choices += { "—"_uc, "Bypass Filter" }; filter_type_choices += { "LD"_uc, "Ladder Filter" }; filter_type_choices += { "SKF"_uc, "Sallen-Key Filter" }; - pid_filter_type_ = add_param ("Filter Type", "Type", std::move (filter_type_choices), FILTER_TYPE_LADDER, "", "Filter Type to be used"); + pmap[FILTER_TYPE] = Param { "filter_type", _("Filter Type"), _("Type"), FILTER_TYPE_LADDER, "", std::move (filter_type_choices), "", _("Filter Type to be used") }; ChoiceS ladder_mode_choices; ladder_mode_choices += { "LP1"_uc, "1 Pole Lowpass, 6dB/Octave" }; ladder_mode_choices += { "LP2"_uc, "2 Pole Lowpass, 12dB/Octave" }; ladder_mode_choices += { "LP3"_uc, "3 Pole Lowpass, 18dB/Octave" }; ladder_mode_choices += { "LP4"_uc, "4 Pole Lowpass, 24dB/Octave" }; - pid_ladder_mode_ = add_param ("Filter Mode", "Mode", std::move (ladder_mode_choices), 1, "", "Ladder Filter Mode to be used"); + pmap[LADDER_MODE] = Param { "ladder_mode", _("Filter Mode"), _("Mode"), 1, "", std::move (ladder_mode_choices), "", "Ladder Filter Mode to be used" }; ChoiceS skfilter_mode_choices; skfilter_mode_choices += { "LP1"_uc, "1 Pole Lowpass, 6dB/Octave" }; @@ -514,23 +489,25 @@ class BlepSynth : public AudioProcessor { skfilter_mode_choices += { "HP4"_uc, "4 Pole Highpass, 24dB/Octave" }; skfilter_mode_choices += { "HP6"_uc, "6 Pole Highpass, 36dB/Octave" }; skfilter_mode_choices += { "HP8"_uc, "8 Pole Highpass, 48dB/Octave" }; - pid_skfilter_mode_ = add_param ("SKFilter Mode", "Mode", std::move (skfilter_mode_choices), 2, "", "Sallen-Key Filter Mode to be used"); - - start_group ("Filter Envelope"); - pid_fil_attack_ = add_param ("Attack", "A", 0, 100, 40, "%"); - pid_fil_decay_ = add_param ("Decay", "D", 0, 100, 55, "%"); - pid_fil_sustain_ = add_param ("Sustain", "S", 0, 100, 30, "%"); - pid_fil_release_ = add_param ("Release", "R", 0, 100, 30, "%"); - pid_fil_cut_mod_ = add_param ("Env Cutoff Modulation", "CutMod", -96, 96, 36, "semitones"); /* 8 octaves range */ - - start_group ("Keyboard Input"); - pid_c_ = add_param ("Main Input 1", "C", false, GUIONLY); - pid_d_ = add_param ("Main Input 2", "D", false, GUIONLY); - pid_e_ = add_param ("Main Input 3", "E", false, GUIONLY); - pid_f_ = add_param ("Main Input 4", "F", false, GUIONLY); - pid_g_ = add_param ("Main Input 5", "G", false, GUIONLY); + pmap[SKFILTER_MODE] = Param { "skfilter_mode", _("SKFilter Mode"), _("Mode"), 2, "", std::move (skfilter_mode_choices), "", "Sallen-Key Filter Mode to be used" }; + + pmap.group = _("Filter Envelope"); + pmap[FIL_ATTACK] = Param { "fil_attack", _("Attack"), _("A"), 40, "%", { 0, 100, }, }; + pmap[FIL_DECAY] = Param { "fil_decay", _("Decay"), _("D"), 55, "%", { 0, 100, }, }; + pmap[FIL_SUSTAIN] = Param { "fil_sustain", _("Sustain"), _("S"), 30, "%", { 0, 100, }, }; + pmap[FIL_RELEASE] = Param { "fil_release", _("Release"), _("R"), 30, "%", { 0, 100, }, }; + pmap[FIL_CUT_MOD] = Param { "fil_cut_mod", _("Env Cutoff Modulation"), _("CutMod"), 36, "semitones", { -96, 96, }, }; /* 8 octaves range */ + + pmap.group = _("Keyboard Input"); + pmap[KEY_C] = Param { "c", _("Main Input 1"), _("C"), false, "", {}, GUIONLY + ":toggle" }; + pmap[KEY_D] = Param { "d", _("Main Input 2"), _("D"), false, "", {}, GUIONLY + ":toggle" }; + pmap[KEY_E] = Param { "e", _("Main Input 3"), _("E"), false, "", {}, GUIONLY + ":toggle" }; + pmap[KEY_F] = Param { "f", _("Main Input 4"), _("F"), false, "", {}, GUIONLY + ":toggle" }; + pmap[KEY_G] = Param { "g", _("Main Input 5"), _("G"), false, "", {}, GUIONLY + ":toggle" }; old_c_ = old_d_ = old_e_ = old_f_ = old_g_ = false; + install_params (pmap); + prepare_event_input(); stereout_ = add_output_bus ("Stereo Out", SpeakerArrangement::STEREO); assert_return (bus_info (stereout_).ident == "stereo_out"); @@ -588,7 +565,7 @@ class BlepSynth : public AudioProcessor { { set_max_voices (0); set_max_voices (32); - adjust_params (true); + adjust_all_params(); } void init_osc (BlepUtils::OscImpl& osc, float freq) @@ -600,9 +577,9 @@ class BlepSynth : public AudioProcessor { #endif } void - adjust_param (Id32 tag) override + adjust_param (uint32_t tag) override { - if (tag == pid_filter_type_) + if (tag == FILTER_TYPE) { for (Voice *voice : active_voices_) { @@ -610,40 +587,41 @@ class BlepSynth : public AudioProcessor { voice->skfilter_.reset(); } } - if (tag == pid_attack_ || tag == pid_decay_ || tag == pid_sustain_ || tag == pid_release_ || - tag == pid_attack_slope_ || tag == pid_decay_slope_ || tag == pid_release_slope_) + if (tag == ATTACK || tag == DECAY || tag == SUSTAIN || tag == RELEASE || + tag == ATTACK_SLOPE || tag == DECAY_SLOPE || tag == RELEASE_SLOPE) { need_update_volume_envelope_ = true; } - if (tag == pid_fil_attack_ || tag == pid_fil_decay_ || tag == pid_fil_sustain_ || tag == pid_fil_release_) + if (tag == FIL_ATTACK || tag == FIL_DECAY || tag == FIL_SUSTAIN || tag == FIL_RELEASE) { need_update_filter_envelope_ = true; } - if (tag == pid_ve_model_) + if (tag == VE_MODEL) { - bool ve_has_slope = irintf (get_param (pid_ve_model_)) > 0; // exponential envelope has no slope parameters + bool ve_has_slope = irintf (get_param (VE_MODEL)) > 0; // exponential envelope has no slope parameters - set_parameter_used (pid_attack_slope_, ve_has_slope); - set_parameter_used (pid_decay_slope_, ve_has_slope); - set_parameter_used (pid_release_slope_, ve_has_slope); + set_parameter_used (ATTACK_SLOPE, ve_has_slope); + set_parameter_used (DECAY_SLOPE, ve_has_slope); + set_parameter_used (RELEASE_SLOPE, ve_has_slope); } } void - update_osc (BlepUtils::OscImpl& osc, const OscParams& params) + update_osc (BlepUtils::OscImpl& osc, int oscnum) { - osc.shape_base = get_param (params.shape) * 0.01; - osc.pulse_width_base = get_param (params.pulse_width) * 0.01; - osc.sub_base = get_param (params.sub) * 0.01; - osc.sub_width_base = get_param (params.sub_width) * 0.01; - osc.sync_base = get_param (params.sync); - - int octave = irintf (get_param (params.octave)); + const uint O = oscnum * (OSC2_SHAPE - OSC1_SHAPE); + osc.shape_base = get_param (O+OSC1_SHAPE) * 0.01; + osc.pulse_width_base = get_param (O+OSC1_PULSE_WIDTH) * 0.01; + osc.sub_base = get_param (O+OSC1_SUB) * 0.01; + osc.sub_width_base = get_param (O+OSC1_SUB_WIDTH) * 0.01; + osc.sync_base = get_param (O+OSC1_SYNC); + + int octave = irintf (get_param (O+OSC1_OCTAVE)); octave = CLAMP (octave, -2, 3); - osc.frequency_factor = fast_exp2 (octave + get_param (params.pitch) / 12.); + osc.frequency_factor = fast_exp2 (octave + get_param (O+OSC1_PITCH) / 12.); - int unison_voices = irintf (get_param (params.unison_voices)); + int unison_voices = irintf (get_param (O+OSC1_UNISON_VOICES)); unison_voices = CLAMP (unison_voices, 1, 16); - osc.set_unison (unison_voices, get_param (params.unison_detune), get_param (params.unison_stereo) * 0.01); + osc.set_unison (unison_voices, get_param (O+OSC1_UNISON_DETUNE), get_param (O+OSC1_UNISON_STEREO) * 0.01); } static double perc_to_s (double perc) @@ -702,11 +680,11 @@ class BlepSynth : public AudioProcessor { voice->state_ = Voice::ON; voice->channel_ = channel; voice->midi_note_ = midi_note; - voice->vel_gain_ = velocity_to_gain (vel, get_param (pid_vel_track_) * 0.01); + voice->vel_gain_ = velocity_to_gain (vel, get_param (VEL_TRACK) * 0.01); // Volume Envelope /* TODO: maybe use non-linear translation between level and sustain % */ - switch (irintf (get_param (pid_ve_model_))) + switch (irintf (get_param (VE_MODEL))) { case 0: voice->envelope_.set_shape (FlexADSR::Shape::EXPONENTIAL); break; @@ -769,7 +747,7 @@ class BlepSynth : public AudioProcessor { } } void - check_note (ParamId pid, bool& old_value, int note) + check_note (ParamType pid, bool& old_value, int note) { const bool value = get_param (pid) > 0.5; if (value != old_value) @@ -790,13 +768,13 @@ class BlepSynth : public AudioProcessor { float osc2_left_out[n_frames]; float osc2_right_out[n_frames]; - update_osc (voice->osc1_, osc_params[0]); - update_osc (voice->osc2_, osc_params[1]); + update_osc (voice->osc1_, 0); + update_osc (voice->osc2_, 1); voice->osc1_.process_sample_stereo (osc1_left_out, osc1_right_out, n_frames); voice->osc2_.process_sample_stereo (osc2_left_out, osc2_right_out, n_frames); // apply volume envelope & mix - const float mix_norm = get_param (pid_mix_) * 0.01; + const float mix_norm = get_param (MIX) * 0.01; const float v1 = voice->vel_gain_ * (1 - mix_norm); const float v2 = voice->vel_gain_ * mix_norm; for (uint i = 0; i < n_frames; i++) @@ -805,8 +783,8 @@ class BlepSynth : public AudioProcessor { mix_right_out[i] = osc1_right_out[i] * v1 + osc2_right_out[i] * v2; } /* --------- run filter - processing in place is ok --------- */ - double cutoff = convert_cutoff (get_param (pid_cutoff_)); - double key_track = get_param (pid_key_track_) * 0.01; + double cutoff = convert_cutoff (get_param (CUTOFF)); + double key_track = get_param (KEY_TRACK) * 0.01; if (fabs (voice->last_cutoff_ - cutoff) > 1e-7 || fabs (voice->last_key_track_ - key_track) > 1e-7) { @@ -819,7 +797,7 @@ class BlepSynth : public AudioProcessor { voice->last_cutoff_ = cutoff; voice->last_key_track_ = key_track; } - double cut_mod = get_param (pid_fil_cut_mod_) / 12.; /* convert semitones to octaves */ + double cut_mod = get_param (FIL_CUT_MOD) / 12.; /* convert semitones to octaves */ if (fabs (voice->last_cut_mod_ - cut_mod) > 1e-7) { const bool reset = voice->last_cut_mod_ < -1000; @@ -827,7 +805,7 @@ class BlepSynth : public AudioProcessor { voice->cut_mod_smooth_.set (cut_mod, reset); voice->last_cut_mod_ = cut_mod; } - double resonance = get_param (pid_resonance_) * 0.01; + double resonance = get_param (RESONANCE) * 0.01; if (fabs (voice->last_reso_ - resonance) > 1e-7) { const bool reset = voice->last_reso_ < -1000; @@ -835,7 +813,7 @@ class BlepSynth : public AudioProcessor { voice->reso_smooth_.set (resonance, reset); voice->last_reso_ = resonance; } - double drive = get_param (pid_drive_); + double drive = get_param (DRIVE); if (fabs (voice->last_drive_ - drive) > 1e-7) { const bool reset = voice->last_drive_ < -1000; @@ -886,20 +864,20 @@ class BlepSynth : public AudioProcessor { } }; - int filter_type = irintf (get_param (pid_filter_type_)); + int filter_type = irintf (get_param (FILTER_TYPE)); if (filter_type == FILTER_TYPE_LADDER) { - voice->ladder_filter_.set_mode (LadderVCF::Mode (irintf (get_param (pid_ladder_mode_)))); + voice->ladder_filter_.set_mode (LadderVCF::Mode (irintf (get_param (LADDER_MODE)))); filter_process_block (voice->ladder_filter_); } else if (filter_type == FILTER_TYPE_SKFILTER) { - voice->skfilter_.set_mode (SKFilter::Mode (irintf (get_param (pid_skfilter_mode_)))); + voice->skfilter_.set_mode (SKFilter::Mode (irintf (get_param (SKFILTER_MODE)))); filter_process_block (voice->skfilter_); } } void - set_parameter_used (ParamId id, bool used) + set_parameter_used (uint32_t id, bool used) { // TODO: implement this function to enable/disable parameters in the gui // printf ("TODO: set parameter %d used flag to %s\n", int (id), used ? "true" : "false"); @@ -907,36 +885,27 @@ class BlepSynth : public AudioProcessor { void update_volume_envelope (Voice *voice) { - voice->envelope_.set_attack (perc_to_s (get_param (pid_attack_))); - voice->envelope_.set_decay (perc_to_s (get_param (pid_decay_))); - voice->envelope_.set_sustain (get_param (pid_sustain_)); /* percent */ - voice->envelope_.set_release (perc_to_s (get_param (pid_release_))); - voice->envelope_.set_attack_slope (get_param (pid_attack_slope_) * 0.01); - voice->envelope_.set_decay_slope (get_param (pid_decay_slope_) * 0.01); - voice->envelope_.set_release_slope (get_param (pid_release_slope_) * 0.01); + voice->envelope_.set_attack (perc_to_s (get_param (ATTACK))); + voice->envelope_.set_decay (perc_to_s (get_param (DECAY))); + voice->envelope_.set_sustain (get_param (SUSTAIN)); /* percent */ + voice->envelope_.set_release (perc_to_s (get_param (RELEASE))); + voice->envelope_.set_attack_slope (get_param (ATTACK_SLOPE) * 0.01); + voice->envelope_.set_decay_slope (get_param (DECAY_SLOPE) * 0.01); + voice->envelope_.set_release_slope (get_param (RELEASE_SLOPE) * 0.01); } void update_filter_envelope (Voice *voice) { - voice->fil_envelope_.set_attack (perc_to_s (get_param (pid_fil_attack_))); - voice->fil_envelope_.set_decay (perc_to_s (get_param (pid_fil_decay_))); - voice->fil_envelope_.set_sustain (get_param (pid_fil_sustain_)); /* percent */ - voice->fil_envelope_.set_release (perc_to_s (get_param (pid_fil_release_))); + voice->fil_envelope_.set_attack (perc_to_s (get_param (FIL_ATTACK))); + voice->fil_envelope_.set_decay (perc_to_s (get_param (FIL_DECAY))); + voice->fil_envelope_.set_sustain (get_param (FIL_SUSTAIN)); /* percent */ + voice->fil_envelope_.set_release (perc_to_s (get_param (FIL_RELEASE))); } void render (uint n_frames) override { - adjust_params (false); - - /* TODO: replace this with true midi input */ - check_note (pid_c_, old_c_, 60); - check_note (pid_d_, old_d_, 62); - check_note (pid_e_, old_e_, 64); - check_note (pid_f_, old_f_, 65); - check_note (pid_g_, old_g_, 67); - - MidiEventRange erange = get_event_input(); - for (const auto &ev : erange) + MidiEventInput evinput = midi_event_input(); + for (const auto &ev : evinput) switch (ev.message()) { case MidiMessage::NOTE_OFF: @@ -950,9 +919,20 @@ class BlepSynth : public AudioProcessor { if (voice->state_ == Voice::ON && voice->channel_ == ev.channel) note_off (voice->channel_, voice->midi_note_); break; + case MidiMessage::PARAM_VALUE: + apply_event (ev); + adjust_param (ev.param); + break; default: ; } + /* TODO: replace this with true midi input */ + check_note (KEY_C, old_c_, 60); + check_note (KEY_D, old_d_, 62); + check_note (KEY_E, old_e_, 64); + check_note (KEY_F, old_f_, 65); + check_note (KEY_G, old_g_, 67); + assert_return (n_ochannels (stereout_) == 2); bool need_free = false; float *left_out = oblock (stereout_, 0); @@ -965,7 +945,7 @@ class BlepSynth : public AudioProcessor { { if (voice->new_voice_) { - int filter_type = irintf (get_param (pid_filter_type_)); + int filter_type = irintf (get_param (FILTER_TYPE)); int idelay = 0; if (filter_type == FILTER_TYPE_LADDER) idelay = voice->ladder_filter_.delay(); @@ -989,7 +969,7 @@ class BlepSynth : public AudioProcessor { if (need_update_volume_envelope_) update_volume_envelope (voice); voice->envelope_.process (volume_env, n_frames); - float post_gain_factor = db2voltage (get_param (pid_post_gain_)); + float post_gain_factor = db2voltage (get_param (POST_GAIN)); for (uint i = 0; i < n_frames; i++) { float amp = post_gain_factor * volume_env[i]; @@ -1013,27 +993,28 @@ class BlepSynth : public AudioProcessor { return 440 * std::pow (2, (midi_note - 69) / 12.); } std::string - param_value_to_text (Id32 paramid, double value) const override + param_value_to_text (uint32_t paramid, double value) const override { /* fake step=1 */ - for (int o = 0; o < 2; o++) + for (int oscnum = 0; oscnum < 2; oscnum++) { - if (paramid == osc_params[o].unison_voices) - return string_format ("%d voices", irintf (value)); - if (paramid == osc_params[o].octave) - return string_format ("%d octaves", irintf (value)); + const uint O = oscnum * (OSC2_SHAPE - OSC1_SHAPE); + if (paramid == O+OSC1_UNISON_VOICES) + return string_format ("%d Voices", irintf (value)); + if (paramid == O+OSC1_OCTAVE) + return string_format ("%d Octaves", irintf (value)); } - for (auto p : { pid_attack_, pid_decay_, pid_release_, pid_fil_attack_, pid_fil_decay_, pid_fil_release_ }) + for (auto p : { ATTACK, DECAY, RELEASE, FIL_ATTACK, FIL_DECAY, FIL_RELEASE }) if (paramid == p) return perc_to_str (value); - if (paramid == pid_cutoff_) + if (paramid == CUTOFF) return hz_to_str (convert_cutoff (value)); return AudioProcessor::param_value_to_text (paramid, value); } public: - BlepSynth (AudioEngine &engine) : - AudioProcessor (engine) + BlepSynth (const ProcessorSetup &psetup) : + AudioProcessor (psetup) {} static void static_info (AudioProcessorInfo &info)