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...
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:69
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:449
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: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