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 assert(group.IsLeader());
462 auto pInstance = EnsureInstance(sampleRate);
463 if (!pInstance)
464 return {};
465 if (!mPlugin)
466 return {};
467 auto first = mCurrentProcessor;
468 const auto numAudioIn = pInstance->GetAudioInCount();
469 const auto numAudioOut = pInstance->GetAudioOutCount();
470 AllocateChannelsToProcessors(chans, numAudioIn, numAudioOut,
471 [&](unsigned, unsigned){
472 // Add a NEW processor
473 if (pInstance->RealtimeAddProcessor(
474 mWorkerSettings.settings, mOutputs.get(), numAudioIn, sampleRate)
475 ) {
476 mCurrentProcessor++;
477 return true;
478 }
479 else
480 return false;
481 });
482 if (mCurrentProcessor > first) {
483 // Remember the sampleRate of the group, so latency can be computed
484 // later
485 mGroups[&group] = { first, sampleRate };
486 return pInstance;
487 }
488 return {};
489}
490
492{
493 // Get state changes from the main thread
494 // Note that it is only here that the answer of IsActive() may be changed,
495 // and it is important that for each state the answer is unchanging in one
496 // processing scope.
497 if (auto pAccessState = TestAccessState())
498 pAccessState->WorkerRead();
499
500 // Detect transitions of activity state
501 auto pInstance = mwInstance.lock();
502 bool active = IsActive() && running;
503 if (active != mLastActive) {
504 if (pInstance) {
505 bool success = active
506 ? pInstance->RealtimeResume()
507 : pInstance->RealtimeSuspend();
508 if (!success)
509 return false;
510 }
511 mLastActive = active;
512 }
513
514 bool result = false;
515 if (pInstance) {
516 // Consume messages even if not processing
517 // (issue #3855: plain UI for VST 2 effects)
518
519 // Assuming we are in a processing scope, use the worker settings
521 mWorkerSettings.settings, mMovedMessage.get()
522 };
523 result = pInstance->RealtimeProcessStart(package);
524 }
525
526 if (!pInstance || !active)
527 return false;
528 else
529 return result;
530}
531
532#define stackAllocate(T, count) static_cast<T*>(alloca(count * sizeof(T)))
533
535
537 const ChannelGroup &group, unsigned chans,
538 const float *const *inbuf, float *const *outbuf, float *const dummybuf,
539 size_t numSamples)
540{
541 auto pInstance = mwInstance.lock();
542 if (!mPlugin || !pInstance || !mLastActive) {
543 // Process trivially
544 for (size_t ii = 0; ii < chans; ++ii)
545 memcpy(outbuf[ii], inbuf[ii], numSamples * sizeof(float));
546 return 0;
547 }
548 const auto numAudioIn = pInstance->GetAudioInCount();
549 const auto numAudioOut = pInstance->GetAudioOutCount();
550 const auto clientIn = stackAllocate(const float *, numAudioIn);
551 const auto clientOut = stackAllocate(float *, numAudioOut);
552 size_t len = 0;
553 const auto &pair = mGroups[&group];
554 auto processor = pair.first;
555 // Outer loop over processors
556 AllocateChannelsToProcessors(chans, numAudioIn, numAudioOut,
557 [&](unsigned indx, unsigned ondx){
558 // Point at the correct input buffers
559 unsigned copied = std::min(chans - indx, numAudioIn);
560 std::copy(inbuf + indx, inbuf + indx + copied, clientIn);
561 // If there are too few input channels for what the processor requires,
562 // re-use input channels from the beginning
563 while (auto need = numAudioIn - copied) {
564 auto moreCopied = std::min(chans, need);
565 std::copy(inbuf, inbuf + moreCopied, clientIn + copied);
566 copied += moreCopied;
567 }
568
569 // Point at the correct output buffers
570 copied = std::min(chans - ondx, numAudioOut);
571 std::copy(outbuf + ondx, outbuf + ondx + copied, clientOut);
572 if (copied < numAudioOut) {
573 // Make determinate pointers
574 std::fill(clientOut + copied, clientOut + numAudioOut, dummybuf);
575 }
576
577 // Inner loop over blocks
578 const auto blockSize = pInstance->GetBlockSize();
579 for (size_t block = 0; block < numSamples; block += blockSize) {
580 auto cnt = std::min(numSamples - block, blockSize);
581 // Assuming we are in a processing scope, use the worker settings
582 auto processed = pInstance->RealtimeProcess(processor,
583 mWorkerSettings.settings, clientIn, clientOut, cnt);
584 if (!mLatency)
585 // Find latency once only per initialization scope,
586 // after processing one block
587 mLatency.emplace(
588 pInstance->GetLatency(mWorkerSettings.settings, pair.second));
589 for (size_t i = 0 ; i < numAudioIn; i++)
590 if (clientIn[i])
591 clientIn[i] += cnt;
592 for (size_t i = 0 ; i < numAudioOut; i++)
593 if (clientOut[i])
594 clientOut[i] += cnt;
595 if (ondx == 0) {
596 // For the first processor only
597 len += processed;
598 auto discard = limitSampleBufferSize(len, *mLatency);
599 len -= discard;
600 *mLatency -= discard;
601 }
602 }
603 ++processor;
604 return true;
605 });
606 // Report the number discardable during the processing scope
607 // We are assuming len as calculated above is the same in case of multiple
608 // processors
609 return numSamples - len;
610}
611
613{
614 auto pInstance = mwInstance.lock();
615 bool result = pInstance &&
616 // Assuming we are in a processing scope, use the worker settings
617 pInstance->RealtimeProcessEnd(mWorkerSettings.settings) &&
619
620 if (auto pAccessState = TestAccessState())
621 // Always done, regardless of activity
622 // Some dialogs require communication back from the processor so that
623 // they can update their appearance in idle time, and some plug-in
624 // libraries (like lv2) require the host program to mediate the
625 // communication
626 pAccessState->WorkerWrite();
627
628 return result;
629}
630
632{
633 return mMainSettings.settings.extra.GetActive();
634}
635
636bool RealtimeEffectState::IsActive() const noexcept
637{
638 return mWorkerSettings.settings.extra.GetActive();
639}
640
642{
643 auto access = GetAccess();
644 access->ModifySettings([&](EffectSettings &settings) {
645 settings.extra.SetActive(active);
646 return nullptr;
647 });
648 access->Flush();
649
650 Publish(active
653}
654
656{
657 mGroups.clear();
659
660 auto pInstance = mwInstance.lock();
661 if (!pInstance)
662 return false;
663
664 if (!pInstance->UsesMessages()) {
665 // This is the main thread cleaning up a state not now used in processing
667 }
668
669 auto result = pInstance->RealtimeFinalize(mMainSettings.settings);
670 mLatency = {};
671 mInitialized = false;
672 return result;
673}
674
675const std::string &RealtimeEffectState::XMLTag()
676{
677 static const std::string result{"effect"};
678 return result;
679}
680
681static const auto idAttribute = "id";
682static const auto versionAttribute = "version";
683static const auto parametersAttribute = "parameters";
684static const auto parameterAttribute = "parameter";
685static const auto nameAttribute = "name";
686static const auto valueAttribute = "value";
687static constexpr auto activeAttribute = "active";
688
690 const std::string_view &tag, const AttributesList &attrs)
691{
692 if (tag == XMLTag()) {
693 mParameters.clear();
694 mPlugin = nullptr;
695 mID.clear();
696 for (auto &[attr, value] : attrs) {
697 if (attr == idAttribute) {
698 SetID(value.ToWString());
699 if (!mPlugin) {
700 // TODO - complain!!!!
701 }
702 }
703 else if (attr == versionAttribute) {
704 }
705 else if (attr == activeAttribute)
706 // Updating the EffectSettingsExtra although we haven't yet built
707 // the settings
708 mMainSettings.settings.extra.SetActive(value.Get<bool>());
709 }
710 return true;
711 }
712 else if (tag == parametersAttribute)
713 return true;
714 else if (tag == parameterAttribute) {
715 wxString n;
716 wxString v;
717 for (auto &[attr, value] : attrs) {
718 if (attr == nameAttribute)
719 n = value.ToWString();
720 else if (attr == valueAttribute)
721 v = value.ToWString();
722 }
723 mParameters += wxString::Format(wxT("\"%s=%s\" "), n, v);
724 return true;
725 }
726 else
727 return false;
728}
729
730void RealtimeEffectState::HandleXMLEndTag(const std::string_view &tag)
731{
732 if (tag == XMLTag()) {
733 if (mPlugin && !mParameters.empty()) {
735 mPlugin->LoadSettings(parms, mMainSettings.settings);
736 }
737 mParameters.clear();
738 }
739}
740
742{
743 // Tag may be for the state, or the list of parameters, or for one parameter.
744 // See the writing method below. All are handled by this
745 return this;
746}
747
749{
750 if (!mPlugin)
751 return;
752
753 xmlFile.StartTag(XMLTag());
754 const auto active = mMainSettings.settings.extra.GetActive();
755 xmlFile.WriteAttr(activeAttribute, active);
758
759 CommandParameters cmdParms;
760 if (mPlugin->SaveSettings(mMainSettings.settings, cmdParms)) {
762
763 wxString entryName;
764 long entryIndex;
765 bool entryKeepGoing;
766
767 entryKeepGoing = cmdParms.GetFirstEntry(entryName, entryIndex);
768 while (entryKeepGoing) {
769 wxString entryValue = cmdParms.Read(entryName, "");
770
772 xmlFile.WriteAttr(nameAttribute, entryName);
773 xmlFile.WriteAttr(valueAttribute, entryValue);
774 xmlFile.EndTag(parameterAttribute);
775
776 entryKeepGoing = cmdParms.GetNextEntry(entryName, entryIndex);
777 }
778
780 }
781
782 xmlFile.EndTag(XMLTag());
783}
784
785std::shared_ptr<EffectSettingsAccess> RealtimeEffectState::GetAccess()
786{
787 if (!GetEffect())
788 // Effect not found!
789 // Return a dummy
790 return std::make_shared<Access>();
791
792 // Only the main thread assigns to the atomic pointer, here and
793 // once only in the lifetime of the state
794 if (!GetAccessState())
795 {
796 MakeInstance();
797 mpAccessState.emplace(*mPlugin, *this);
798 }
799
800 return std::make_shared<Access>(*this);
801}
wxT("CloseDown"))
Abstract class ChannelGroup with two discrete iterable dimensions, channels and intervals; subclasses...
int min(int a, int b)
wxString PluginID
Definition: EffectManager.h:30
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:83
int id
std::vector< Attribute > AttributesList
Definition: XMLTagHandler.h:40
virtual bool IsLeader() const =0
void BuildAll()
For each RegisteredFactory, if the corresponding attachment is absent in this, build and store it.
Definition: ClientData.h:441
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(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)
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.
void SetActive(bool active)
Set only in the main thread.
const EffectInstanceFactory * mPlugin
Stateless effect object.
NonInterfering< SettingsAndCounter > mWorkerSettings
NonInterfering< SettingsAndCounter > mMainSettings
Updated immediately by Access::Set in the main thread.
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::shared_ptr< EffectInstance > AddGroup(const ChannelGroup &group, unsigned chans, float sampleRate)
Main thread sets up this state before adding it to lists.
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:31
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