Audacity 3.2.0
RealtimeEffectState.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 @file RealtimeEffectState.cpp
6
7 Paul Licameli split from RealtimeEffectManager.cpp
8
9 *********************************************************************/
10
11#include "RealtimeEffectState.h"
12
13#include "Channel.h"
14#include "EffectInterface.h"
15#include "MessageBuffer.h"
16#include "PluginManager.h"
17#include "SampleCount.h"
18
19#include <chrono>
20#include <thread>
21#include <condition_variable>
22
24class RealtimeEffectState::AccessState : public NonInterferingBase {
25public:
27 : mEffect{ effect }
28 , mState{ state }
29 {
30 // Clean initial state of the counter
31 state.mMainSettings.counter = 0;
32 Initialize(state.mMainSettings.settings,
33 state.mMessage.get(), state.mMovedOutputs.get());
34 }
35
37 const EffectInstance::Message *pMessage,
38 const EffectOutputs *pOutputs)
39 {
40 mLastSettings = { settings, 0 };
41 // Initialize each message buffer with two copies
42 mChannelToMain.Write(ToMainSlot{ { 0,
43 pOutputs ? pOutputs->Clone() : nullptr } });
44 mChannelToMain.Write(ToMainSlot{ { 0,
45 pOutputs ? pOutputs->Clone() : nullptr } });
46 mChannelFromMain.Write(FromMainSlot{ settings, pMessage });
47 mChannelFromMain.Write(FromMainSlot{ settings, pMessage });
48
49 mMainThreadId = std::this_thread::get_id();
50 }
51
52 void MainRead() {
54 mCounter);
55 }
57 std::unique_ptr<EffectInstance::Message> pMessage) {
58 // Main thread may simply swap new content into place
60 std::move(settings.settings), settings.counter, std::move(pMessage) });
61 }
63 std::unique_ptr<EffectInstance::Message> pMessage) {
65 counter, std::move(pMessage) });
66 }
67
71 };
72 void WorkerRead() {
73 // Worker thread avoids memory allocation. It copies the contents of any
75 }
76 void WorkerWrite() {
77
78 {
79 std::unique_lock lk(mLockForCV);
80
81 // Worker thread avoids memory allocation.
83 mState.mWorkerSettings.counter, mState.mOutputs.get() });
84 }
85 mCV.notify_one();
86 }
87
88 struct ToMainSlot {
89 // For initialization of the channel
90 ToMainSlot() = default;
91 explicit ToMainSlot(Response response)
92 : mResponse{ std::move(response) }
93 {}
95
96 // Worker thread writes the slot
98 // This happens during MessageBuffer's busying of the slot
99 mResponse.counter = arg.counter;
100 if (mResponse.pOutputs && arg.pOutputs)
101 mResponse.pOutputs->Assign(std::move(*arg.pOutputs));
102 return *this;
103 }
104
105 struct Reader { Reader(ToMainSlot &&slot, EffectOutputs *pOutputs,
106 Response::Counter &counter
107 ) {
108 // Main thread is not under the performance constraints of the
109 // worker, but Assign is still used so that
110 // members of underlying vectors or other containers do not
111 // relocate
112 if (pOutputs && slot.mResponse.pOutputs)
113 pOutputs->Assign(std::move(*slot.mResponse.pOutputs));
114 counter = slot.mResponse.counter;
115 } };
116
118 };
119
122 std::unique_ptr<EffectInstance::Message> pMessage;
123 };
126 std::unique_ptr<EffectInstance::Message> pMessage;
127 };
128
129 // For initialization of the channel
130 FromMainSlot() = default;
132 const EffectInstance::Message *pMessage)
133 // Copy std::any
134 : mMessage{ settings, 0, pMessage ? pMessage->Clone() : nullptr }
135 {}
137
138 // Main thread writes the slot
140 mMessage.SettingsAndCounter::swap(message);
141 if (message.pMessage && mMessage.pMessage)
142 // Merge the incoming message with any still unconsumed message
143 mMessage.pMessage->Merge(std::move(*message.pMessage));
144 return *this;
145 }
146
147 // Main thread writes the slot
149 mMessage.counter = message.counter;
150 if (message.pMessage && mMessage.pMessage)
151 // Merge the incoming message with any still unconsumed message
152 mMessage.pMessage->Merge(std::move(*message.pMessage));
153 return *this;
154 }
155
156 // Worker thread reads the slot
157 struct Reader { Reader(FromMainSlot &&slot,
158 const EffectSettingsManager &effect, RealtimeEffectState &state) {
159 auto &settings = state.mWorkerSettings;
160 if(slot.mMessage.counter == settings.counter)
161 return;//copy once
162 settings.counter = slot.mMessage.counter;
163
164 // This happens during MessageBuffer's busying of the slot
166 slot.mMessage.settings, settings.settings);
167 settings.settings.extra = slot.mMessage.settings.extra;
168 if (slot.mMessage.pMessage && state.mMovedMessage)
169 // Copy the message from the buffer (not a merge)
170 state.mMovedMessage->Assign(std::move(*slot.mMessage.pMessage));
171 } };
172
174 };
175
178
182
184
185 std::mutex mLockForCV;
186 std::condition_variable mCV;
187
189};
190
193 Access() = default;
195 : mwState{ state.weak_from_this() }
196 {
197 }
198 ~Access() override = default;
199
200
201 const EffectSettings &Get() override {
202 if (auto pState = mwState.lock()) {
203 if (auto pAccessState = pState->GetAccessState()) {
204 if (pAccessState->mState.mInitialized)
205 {
206 // try once
207 assert(pAccessState->mState.mInitialized);
208 auto& lastSettings = pAccessState->mLastSettings;
209 // Assigns to mCounter
210 pAccessState->MainRead();
211 }
212 else {
213 // Not yet waiting on the other thread's progress
214 // Not necessarily values yet in the state's Settings objects
215 }
216 return pAccessState->mLastSettings.settings;
217 }
218 }
219 // Non-modal dialog may have outlived the RealtimeEffectState
220 static EffectSettings empty;
221 return empty;
222 }
223 void Set(EffectSettings &&settings, std::unique_ptr<Message> pMessage)
224 override {
225 if (auto pState = mwState.lock()) {
226 if (auto pAccessState = pState->GetAccessState()) {
227 if (pMessage && !pAccessState->mState.mInitialized) {
228 // Other thread isn't processing.
229 // Let the instance consume the message directly.
230 if (auto pInstance = pState->mwInstance.lock()) {
231 auto &stateSettings = pState->mMainSettings.settings;
232 stateSettings = std::move(settings);
234 stateSettings, pMessage.get()
235 };
236 pInstance->RealtimeProcessStart(package);
237 pInstance->RealtimeProcessEnd(stateSettings);
238 pAccessState->mLastSettings.settings = stateSettings;
239 return;
240 }
241 }
242 auto &lastSettings = pAccessState->mLastSettings;
243 // move to remember values here
244 lastSettings.settings = std::move(settings);
245 ++lastSettings.counter;
246 // move a copy to there
247 pAccessState->MainWrite(
248 SettingsAndCounter{ lastSettings }, std::move(pMessage));
249 }
250 }
251 }
252 void Set(std::unique_ptr<Message> pMessage)
253 override {
254 if (auto pState = mwState.lock()) {
255 if (auto pAccessState = pState->GetAccessState()) {
256 if (pMessage && !pAccessState->mState.mInitialized) {
257 // Other thread isn't processing.
258 // Let the instance consume the message directly.
259 if (auto pInstance = pState->mwInstance.lock()) {
260 auto &stateSettings = pState->mMainSettings.settings;
262 stateSettings, pMessage.get()
263 };
264 pInstance->RealtimeProcessStart(package);
265 pInstance->RealtimeProcessEnd(stateSettings);
266 // Don't need to update pAccessState->mLastSettings
267 return;
268 }
269 }
270 auto &lastSettings = pAccessState->mLastSettings;
271 // Don't update settings, but do count
272 ++lastSettings.counter;
273 pAccessState->MainWrite(
274 lastSettings.counter, std::move(pMessage));
275 }
276 }
277 }
278 void Flush() override {
279 if (auto pState = mwState.lock()) {
280 if (auto pAccessState = pState->GetAccessState()) {
281 assert(pAccessState->mMainThreadId == std::this_thread::get_id());
282
283 if (pAccessState->mState.mInitialized)
284 {
285 std::unique_lock lk(pAccessState->mLockForCV);
286 pAccessState->mCV.wait(lk,
287 [&] {
288 auto& lastSettings = pAccessState->mLastSettings;
289 pAccessState->MainRead();
290 return pAccessState->mCounter == lastSettings.counter;
291 }
292 );
293 }
294
295 // Update what GetSettings() will return, during play and before
296 // Finalize(), but after it is confirmed that any worker thread has
297 // seen the values given to the last Set(). These values will also
298 // be returned by Get().
299 pState->mMainSettings.Set(pAccessState->mLastSettings);
300 }
301 }
302 }
303 bool IsSameAs(const EffectSettingsAccess &other) const override {
304 if (auto pOther = dynamic_cast<const Access*>(&other)) {
305 auto &mine = mwState;
306 auto &theirs = pOther->mwState;
307 auto less = std::owner_less{};
308 return !(less(mine, theirs) || less(theirs, mine));
309 }
310 return false;
311 }
313 std::weak_ptr<RealtimeEffectState> mwState;
314};
315
317{
318 SetID(id);
319 BuildAll();
320}
321
323{
324
325}
326
328{
329 bool empty = id.empty();
330 if (mID.empty() && !empty) {
331 mID = id;
332 GetEffect();
333 }
334 else
335 // Set mID to non-empty at most once
336 assert(empty);
337}
338
340{
341 return mID;
342}
343
345{
346 if (!mPlugin) {
348 if (mPlugin) {
349 // Also make EffectSettings, but preserve activation
350 auto wasActive = mMainSettings.settings.extra.GetActive();
351 mMainSettings.counter = 0;
352 mMainSettings.settings = mPlugin->MakeSettings();
353 mMainSettings.settings.extra.SetActive(wasActive);
356 }
357 }
358 return mPlugin;
359}
360
361std::shared_ptr<EffectInstance> RealtimeEffectState::MakeInstance()
362{
363 mMovedMessage.reset();
364 mMessage.reset();
365 auto result = mPlugin->MakeInstance();
366 if (result) {
367 // Allocate presized containers in messages, so later
368 // copies of contents might avoid free store operations
369 mMessage = result->MakeMessage();
370 mMovedMessage = result->MakeMessage();
371 if (auto state = GetAccessState())
372 state->Initialize(mMainSettings.settings,
373 mMessage.get(), mMovedOutputs.get());
374 }
375 return result;
376}
377
378std::shared_ptr<EffectInstance>
380{
381 if (!mPlugin)
382 return {};
383
384 auto pInstance = mwInstance.lock();
385 if (!mInitialized) {
389
391 if (!pInstance)
392 mwInstance = pInstance = MakeInstance();
393 if (!pInstance)
394 return {};
395
396 // PRL: conserving pre-3.2.0 behavior, but I don't know why this arbitrary
397 // number was important
398 pInstance->SetBlockSize(512);
399
400 if (!pInstance->RealtimeInitialize(mMainSettings.settings, sampleRate))
401 return {};
402 mInitialized = true;
403 return pInstance;
404 }
405 return pInstance;
406}
407
408std::shared_ptr<EffectInstance> RealtimeEffectState::GetInstance()
409{
411 auto pInstance = mwInstance.lock();
412 if (!pInstance && mPlugin)
413 mwInstance = pInstance = MakeInstance();
414 return pInstance;
415}
416
417std::shared_ptr<EffectInstance>
419{
420 if (!mPlugin)
421 return {};
422
424 mGroups.clear();
425 mLatency = {};
427}
428
429namespace {
430// The caller passes the number of channels to process and specifies
431// the number of input and output buffers. There will always be the
432// same number of output buffers as there are input buffers.
433//
434// Effects require a certain number of input and output buffers.
435// The number of channels we're currently processing may mismatch
436// the effect's requirements. Allocate some inputs repeatedly to a processor
437// that needs more, or allocate multiple processors if they accept too few.
438// Continue until the output buffers are all allocated.
439template<typename F>
441 unsigned chans, const unsigned numAudioIn, const unsigned numAudioOut,
442 const F &f)
443{
444 unsigned indx = 0;
445 for (unsigned ondx = 0; ondx < chans; ondx += numAudioOut) {
446 // Pass the function indices into the arrays of buffers
447 if (!f(indx, ondx))
448 return;
449 indx += numAudioIn;
450 indx %= chans;
451 }
452}
453}
454
456
457std::shared_ptr<EffectInstance>
459 const ChannelGroup *group, unsigned chans, float sampleRate)
460{
461 auto pInstance = EnsureInstance(sampleRate);
462 if (!pInstance)
463 return {};
464 if (!mPlugin)
465 return {};
466 auto first = mCurrentProcessor;
467 const auto numAudioIn = pInstance->GetAudioInCount();
468 const auto numAudioOut = pInstance->GetAudioOutCount();
469 AllocateChannelsToProcessors(chans, numAudioIn, numAudioOut,
470 [&](unsigned, unsigned){
471 // Add a NEW processor
472 if (pInstance->RealtimeAddProcessor(
473 mWorkerSettings.settings, mOutputs.get(), numAudioIn, sampleRate)
474 ) {
475 mCurrentProcessor++;
476 return true;
477 }
478 else
479 return false;
480 });
481 if (mCurrentProcessor > first) {
482 // Remember the sampleRate of the group, so latency can be computed
483 // later
484 mGroups[group] = { first, sampleRate };
485 return pInstance;
486 }
487 return {};
488}
489
491{
492 // Get state changes from the main thread
493 // Note that it is only here that the answer of IsActive() may be changed,
494 // and it is important that for each state the answer is unchanging in one
495 // processing scope.
496 if (auto pAccessState = TestAccessState())
497 pAccessState->WorkerRead();
498
499 // Detect transitions of activity state
500 auto pInstance = mwInstance.lock();
501 bool active = IsActive() && running;
502 if (active != mLastActive) {
503 if (pInstance) {
504 bool success = active
505 ? pInstance->RealtimeResume()
506 : pInstance->RealtimeSuspend();
507 if (!success)
508 return false;
509 }
510 mLastActive = active;
511 }
512
513 bool result = false;
514 if (pInstance) {
515 // Consume messages even if not processing
516 // (issue #3855: plain UI for VST 2 effects)
517
518 // Assuming we are in a processing scope, use the worker settings
520 mWorkerSettings.settings, mMovedMessage.get()
521 };
522 result = pInstance->RealtimeProcessStart(package);
523 }
524
525 if (!pInstance || !active)
526 return false;
527 else
528 return result;
529}
530
531#define stackAllocate(T, count) static_cast<T*>(alloca(count * sizeof(T)))
532
534
536 const ChannelGroup *group, unsigned chans,
537 const float *const *inbuf, float *const *outbuf, float *const dummybuf,
538 size_t numSamples)
539{
540
541 const auto pInstance = mwInstance.lock();
542 const auto& pair = mGroups[group];
543 const float** const clientIn =
544 pInstance ? stackAllocate(const float*, pInstance->GetAudioInCount()) :
545 nullptr;
546
547 const auto PointAtCorrectInputBuffers =
548 [&](unsigned numAudioIn, unsigned indx) {
549 // Point at the correct input buffers
550 unsigned copied = std::min(chans - indx, numAudioIn);
551 std::copy(inbuf + indx, inbuf + indx + copied, clientIn);
552 // If there are too few input channels for what the processor requires,
553 // re-use input channels from the beginning
554 while (auto need = numAudioIn - copied)
555 {
556 auto moreCopied = std::min(chans, need);
557 std::copy(inbuf, inbuf + moreCopied, clientIn + copied);
558 copied += moreCopied;
559 }
560 };
561
562 if (!mPlugin || !pInstance || !mLastActive)
563 {
564 // Process trivially
565 for (size_t ii = 0; ii < chans; ++ii)
566 memcpy(outbuf[ii], inbuf[ii], numSamples * sizeof(float));
567 if (pInstance)
568 {
569 auto processor = pair.first;
570 const auto numAudioIn = pInstance->GetAudioInCount();
571 const auto numAudioOut = pInstance->GetAudioOutCount();
573 chans, numAudioIn, numAudioOut, [&](unsigned indx, unsigned ondx) {
574
575 PointAtCorrectInputBuffers(numAudioIn, indx);
576
577 // Inner loop over blocks
578 const auto blockSize = pInstance->GetBlockSize();
579 for (size_t block = 0; block < numSamples; block += blockSize)
580 {
581 auto cnt = std::min(numSamples - block, blockSize);
582 pInstance->RealtimePassThrough(
583 processor, mWorkerSettings.settings, clientIn, cnt);
584 for (size_t i = 0; i < numAudioIn; i++)
585 if (clientIn[i])
586 clientIn[i] += cnt;
587 }
588 ++processor;
589 return true;
590 });
591 }
592 return 0;
593 }
594 const auto numAudioIn = pInstance->GetAudioInCount();
595 const auto numAudioOut = pInstance->GetAudioOutCount();
596 const auto clientOut = stackAllocate(float *, numAudioOut);
597 size_t len = 0;
598 auto processor = pair.first;
599 // Outer loop over processors
601 chans, numAudioIn, numAudioOut, [&](unsigned indx, unsigned ondx) {
602
603 PointAtCorrectInputBuffers(numAudioIn, indx);
604
605 // Point at the correct output buffers
606 unsigned copied = std::min(chans - ondx, numAudioOut);
607 std::copy(outbuf + ondx, outbuf + ondx + copied, clientOut);
608 if (copied < numAudioOut)
609 // Make determinate pointers
610 std::fill(clientOut + copied, clientOut + numAudioOut, dummybuf);
611
612 // Inner loop over blocks
613 const auto blockSize = pInstance->GetBlockSize();
614 for (size_t block = 0; block < numSamples; block += blockSize)
615 {
616 auto cnt = std::min(numSamples - block, blockSize);
617 // Assuming we are in a processing scope, use the worker settings
618 auto processed = pInstance->RealtimeProcess(
619 processor, mWorkerSettings.settings, clientIn, clientOut, cnt);
620 if (!mLatency)
621 // Find latency once only per initialization scope,
622 // after processing one block
623 mLatency.emplace(
624 pInstance->GetLatency(mWorkerSettings.settings, pair.second));
625 for (size_t i = 0; i < numAudioIn; i++)
626 if (clientIn[i])
627 clientIn[i] += cnt;
628 for (size_t i = 0; i < numAudioOut; i++)
629 if (clientOut[i])
630 clientOut[i] += cnt;
631 if (ondx == 0)
632 {
633 // For the first processor only
634 len += processed;
635 auto discard = limitSampleBufferSize(len, *mLatency);
636 len -= discard;
637 *mLatency -= discard;
638 }
639 }
640 ++processor;
641 return true;
642 });
643 // Report the number discardable during the processing scope
644 // We are assuming len as calculated above is the same in case of multiple
645 // processors
646 return numSamples - len;
647}
648
650{
651 auto pInstance = mwInstance.lock();
652 bool result = pInstance &&
653 // Assuming we are in a processing scope, use the worker settings
654 pInstance->RealtimeProcessEnd(mWorkerSettings.settings) &&
656
657 if (auto pAccessState = TestAccessState())
658 // Always done, regardless of activity
659 // Some dialogs require communication back from the processor so that
660 // they can update their appearance in idle time, and some plug-in
661 // libraries (like lv2) require the host program to mediate the
662 // communication
663 pAccessState->WorkerWrite();
664
665 return result;
666}
667
669{
670 return mMainSettings.settings.extra.GetActive();
671}
672
673bool RealtimeEffectState::IsActive() const noexcept
674{
675 return mWorkerSettings.settings.extra.GetActive();
676}
677
679{
680 auto access = GetAccess();
681 access->ModifySettings([&](EffectSettings &settings) {
682 settings.extra.SetActive(active);
683 return nullptr;
684 });
685 access->Flush();
686
687 Publish(active
690}
691
693{
694 mGroups.clear();
696
697 auto pInstance = mwInstance.lock();
698 if (!pInstance)
699 return false;
700
701 if (!pInstance->UsesMessages()) {
702 // This is the main thread cleaning up a state not now used in processing
704 }
705
706 auto result = pInstance->RealtimeFinalize(mMainSettings.settings);
707 mLatency = {};
708 mInitialized = false;
709 return result;
710}
711
712const std::string &RealtimeEffectState::XMLTag()
713{
714 static const std::string result{"effect"};
715 return result;
716}
717
718static const auto idAttribute = "id";
719static const auto versionAttribute = "version";
720static const auto parametersAttribute = "parameters";
721static const auto parameterAttribute = "parameter";
722static const auto nameAttribute = "name";
723static const auto valueAttribute = "value";
724static constexpr auto activeAttribute = "active";
725
727 const std::string_view &tag, const AttributesList &attrs)
728{
729 if (tag == XMLTag()) {
730 mParameters.clear();
731 mPlugin = nullptr;
732 mID.clear();
733 for (auto &[attr, value] : attrs) {
734 if (attr == idAttribute) {
735 SetID(value.ToWString());
736 if (!mPlugin) {
737 // TODO - complain!!!!
738 }
739 }
740 else if (attr == versionAttribute) {
741 }
742 else if (attr == activeAttribute)
743 // Updating the EffectSettingsExtra although we haven't yet built
744 // the settings
745 mMainSettings.settings.extra.SetActive(value.Get<bool>());
746 }
747 return true;
748 }
749 else if (tag == parametersAttribute)
750 return true;
751 else if (tag == parameterAttribute) {
752 wxString n;
753 wxString v;
754 for (auto &[attr, value] : attrs) {
755 if (attr == nameAttribute)
756 n = value.ToWString();
757 else if (attr == valueAttribute)
758 v = value.ToWString();
759 }
760 mParameters += wxString::Format(wxT("\"%s=%s\" "), n, v);
761 return true;
762 }
763 else
764 return false;
765}
766
767void RealtimeEffectState::HandleXMLEndTag(const std::string_view &tag)
768{
769 if (tag == XMLTag()) {
770 if (mPlugin && !mParameters.empty()) {
772 mPlugin->LoadSettings(parms, mMainSettings.settings);
773 }
774 mParameters.clear();
775 }
776}
777
779{
780 // Tag may be for the state, or the list of parameters, or for one parameter.
781 // See the writing method below. All are handled by this
782 return this;
783}
784
786{
787 if (!mPlugin)
788 return;
789
790 xmlFile.StartTag(XMLTag());
791 const auto active = mMainSettings.settings.extra.GetActive();
792 xmlFile.WriteAttr(activeAttribute, active);
795
796 CommandParameters cmdParms;
797 if (mPlugin->SaveSettings(mMainSettings.settings, cmdParms)) {
799
800 wxString entryName;
801 long entryIndex;
802 bool entryKeepGoing;
803
804 entryKeepGoing = cmdParms.GetFirstEntry(entryName, entryIndex);
805 while (entryKeepGoing) {
806 wxString entryValue = cmdParms.Read(entryName, "");
807
809 xmlFile.WriteAttr(nameAttribute, entryName);
810 xmlFile.WriteAttr(valueAttribute, entryValue);
811 xmlFile.EndTag(parameterAttribute);
812
813 entryKeepGoing = cmdParms.GetNextEntry(entryName, entryIndex);
814 }
815
817 }
818
819 xmlFile.EndTag(XMLTag());
820}
821
822std::shared_ptr<EffectSettingsAccess> RealtimeEffectState::GetAccess()
823{
824 if (!GetEffect())
825 // Effect not found!
826 // Return a dummy
827 return std::make_shared<Access>();
828
829 // Only the main thread assigns to the atomic pointer, here and
830 // once only in the lifetime of the state
831 if (!GetAccessState())
832 {
833 MakeInstance();
834 mpAccessState.emplace(*mPlugin, *this);
835 }
836
837 return std::make_shared<Access>(*this);
838}
wxT("CloseDown"))
Abstract class ChannelGroup with two discrete iterable dimensions, channels and intervals; subclasses...
wxString PluginID
int min(int a, int b)
static const auto parametersAttribute
static const auto versionAttribute
static const auto parameterAttribute
static const auto nameAttribute
static const auto idAttribute
static const auto valueAttribute
static constexpr auto activeAttribute
#define stackAllocate(T, count)
size_t limitSampleBufferSize(size_t bufferSize, sampleCount limit)
Definition: SampleCount.cpp:22
static Settings & settings()
Definition: TrackInfo.cpp:51
int id
std::vector< Attribute > AttributesList
Definition: XMLTagHandler.h:40
void BuildAll()
For each RegisteredFactory, if the corresponding attachment is absent in this, build and store it.
Definition: ClientData.h:546
CommandParameters, derived from wxFileConfig, is essentially doing the same things as the SettingsVis...
virtual wxString GetVersion() const =0
virtual std::shared_ptr< EffectInstance > MakeInstance() const =0
Make an object maintaining short-term state of an Effect.
Hold values to send to effect output meters.
virtual std::unique_ptr< EffectOutputs > Clone() const =0
virtual void Assign(EffectOutputs &&src)=0
Update one Outputs object from another.
Type of messages to send from main thread to processing.
EffectSettingsManager is an EffectDefinitionInterface that adds a factory function for EffectSettings...
virtual bool LoadSettings(const CommandParameters &parms, EffectSettings &settings) const =0
Restore settings from keys and values.
virtual bool SaveSettings(const EffectSettings &settings, CommandParameters &parms) const =0
Store settings as keys and values.
virtual bool CopySettingsContents(const EffectSettings &src, EffectSettings &dst) const
Update one settings object from another.
virtual std::unique_ptr< EffectOutputs > MakeOutputs() const
Produce an object to hold values to send to effect output meters.
virtual EffectSettings MakeSettings() const
static result_type Call(Arguments &&...arguments)
Null check of the installed function is done for you.
Communicate data atomically from one writer thread to one reader.
Definition: MessageBuffer.h:23
CallbackReturn Publish(const RealtimeEffectStateChange &message)
Send a message to connected callbacks.
Definition: Observer.h:207
static PluginID GetID(const PluginProvider *provider)
Mediator of two-way inter-thread communication of changes of settings.
void Initialize(const EffectSettings &settings, const EffectInstance::Message *pMessage, const EffectOutputs *pOutputs)
MessageBuffer< ToMainSlot > mChannelToMain
const EffectSettingsManager & mEffect
void MainWrite(SettingsAndCounter::Counter counter, std::unique_ptr< EffectInstance::Message > pMessage)
AccessState(const EffectSettingsManager &effect, RealtimeEffectState &state)
MessageBuffer< FromMainSlot > mChannelFromMain
void MainWrite(SettingsAndCounter &&settings, std::unique_ptr< EffectInstance::Message > pMessage)
void SetActive(bool active)
Set only in the main thread.
const EffectInstanceFactory * mPlugin
Stateless effect object.
std::shared_ptr< EffectInstance > AddGroup(const ChannelGroup *group, unsigned chans, float sampleRate)
Main thread sets up this state before adding it to lists.
NonInterfering< SettingsAndCounter > mWorkerSettings
NonInterfering< SettingsAndCounter > mMainSettings
Updated immediately by Access::Set in the main thread.
size_t Process(const ChannelGroup *group, unsigned chans, const float *const *inbuf, float *const *outbuf, float *dummybuf, size_t numSamples)
Worker thread processes part of a batch of samples.
std::optional< EffectInstance::SampleCount > mLatency
How many samples must be discarded.
void HandleXMLEndTag(const std::string_view &tag) override
std::shared_ptr< EffectInstance > GetInstance()
Expose a pointer to the state's instance (making one as needed).
std::unordered_map< const ChannelGroup *, std::pair< size_t, double > > mGroups
std::shared_ptr< EffectSettingsAccess > GetAccess()
std::unique_ptr< EffectOutputs > mOutputs
bool ProcessEnd()
Worker thread finishes a batch of samples.
AccessState * GetAccessState() const
AccessState * TestAccessState() const
static const std::string & XMLTag()
bool Finalize() noexcept
Main thread cleans up playback.
std::shared_ptr< EffectInstance > EnsureInstance(double rate)
bool ProcessStart(bool running)
Worker thread begins a batch of samples.
bool IsEnabled() const noexcept
Test only in the main thread.
const PluginID & GetID() const noexcept
bool mLastActive
Assigned in the worker thread at the start of each processing scope.
void WriteXML(XMLWriter &xmlFile)
RealtimeEffectState(const PluginID &id)
bool IsActive() const noexcept
Test only in the worker thread, or else when there is no processing.
std::shared_ptr< EffectInstance > Initialize(double rate)
Main thread sets up for playback.
std::shared_ptr< EffectInstance > MakeInstance()
std::unique_ptr< EffectInstance::Message > mMessage
const EffectInstanceFactory * GetEffect()
Initializes the effect on demand.
XMLTagHandler * HandleXMLChild(const std::string_view &tag) override
void SetID(const PluginID &id)
May be called with nonempty id at most once in the lifetime of a state.
std::unique_ptr< EffectInstance::Message > mMovedMessage
std::weak_ptr< EffectInstance > mwInstance
Stateful instance made by the plug-in.
AtomicUniquePointer< AccessState > mpAccessState
bool HandleXMLTag(const std::string_view &tag, const AttributesList &attrs) override
std::unique_ptr< EffectOutputs > mMovedOutputs
This class is an interface which should be implemented by classes which wish to be able to load and s...
Definition: XMLTagHandler.h:42
Base class for XMLFileWriter and XMLStringWriter that provides the general functionality for creating...
Definition: XMLWriter.h:25
virtual void StartTag(const wxString &name)
Definition: XMLWriter.cpp:79
void WriteAttr(const wxString &name, const Identifier &value)
Definition: XMLWriter.h:36
virtual void EndTag(const wxString &name)
Definition: XMLWriter.cpp:102
void AllocateChannelsToProcessors(unsigned chans, const unsigned numAudioIn, const unsigned numAudioOut, const F &f)
void copy(const T *src, T *dst, int32_t n)
Definition: VectorOps.h:40
STL namespace.
Externalized state of a plug-in.
Main thread's interface to inter-thread communication of changes of settings.
const EffectSettings & Get() override
void Set(std::unique_ptr< Message > pMessage) override
Message-only overload of Set(). In future, this should be the only one.
Access(RealtimeEffectState &state)
bool IsSameAs(const EffectSettingsAccess &other) const override
void Flush() override
Make the last Set changes "persistent" in underlying storage.
void Set(EffectSettings &&settings, std::unique_ptr< Message > pMessage) override
~Access() override=default
std::weak_ptr< RealtimeEffectState > mwState
Store no state here but this weak pointer, so IsSameAs isn't lying.
std::unique_ptr< EffectInstance::Message > pMessage
Reader(FromMainSlot &&slot, const EffectSettingsManager &effect, RealtimeEffectState &state)
FromMainSlot & operator=(FromMainSlot &&)=default
FromMainSlot(const EffectSettings &settings, const EffectInstance::Message *pMessage)
FromMainSlot & operator=(ShortMessage &&message)
Reader(ToMainSlot &&slot, EffectOutputs *pOutputs, Response::Counter &counter)
ToMainSlot & operator=(ToMainSlot &&)=default
ToMainSlot & operator=(CounterAndOutputs &&arg)
std::unique_ptr< EffectOutputs > pOutputs