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 auto pInstance = mwInstance.lock();
541 if (!mPlugin || !pInstance || !mLastActive) {
542 // Process trivially
543 for (size_t ii = 0; ii < chans; ++ii)
544 memcpy(outbuf[ii], inbuf[ii], numSamples * sizeof(float));
545 return 0;
546 }
547 const auto numAudioIn = pInstance->GetAudioInCount();
548 const auto numAudioOut = pInstance->GetAudioOutCount();
549 const auto clientIn = stackAllocate(const float *, numAudioIn);
550 const auto clientOut = stackAllocate(float *, numAudioOut);
551 size_t len = 0;
552 const auto &pair = mGroups[&group];
553 auto processor = pair.first;
554 // Outer loop over processors
555 AllocateChannelsToProcessors(chans, numAudioIn, numAudioOut,
556 [&](unsigned indx, unsigned ondx){
557 // Point at the correct input buffers
558 unsigned copied = std::min(chans - indx, numAudioIn);
559 std::copy(inbuf + indx, inbuf + indx + copied, clientIn);
560 // If there are too few input channels for what the processor requires,
561 // re-use input channels from the beginning
562 while (auto need = numAudioIn - copied) {
563 auto moreCopied = std::min(chans, need);
564 std::copy(inbuf, inbuf + moreCopied, clientIn + copied);
565 copied += moreCopied;
566 }
567
568 // Point at the correct output buffers
569 copied = std::min(chans - ondx, numAudioOut);
570 std::copy(outbuf + ondx, outbuf + ondx + copied, clientOut);
571 if (copied < numAudioOut) {
572 // Make determinate pointers
573 std::fill(clientOut + copied, clientOut + numAudioOut, dummybuf);
574 }
575
576 // Inner loop over blocks
577 const auto blockSize = pInstance->GetBlockSize();
578 for (size_t block = 0; block < numSamples; block += blockSize) {
579 auto cnt = std::min(numSamples - block, blockSize);
580 // Assuming we are in a processing scope, use the worker settings
581 auto processed = pInstance->RealtimeProcess(processor,
582 mWorkerSettings.settings, clientIn, clientOut, cnt);
583 if (!mLatency)
584 // Find latency once only per initialization scope,
585 // after processing one block
586 mLatency.emplace(
587 pInstance->GetLatency(mWorkerSettings.settings, pair.second));
588 for (size_t i = 0 ; i < numAudioIn; i++)
589 if (clientIn[i])
590 clientIn[i] += cnt;
591 for (size_t i = 0 ; i < numAudioOut; i++)
592 if (clientOut[i])
593 clientOut[i] += cnt;
594 if (ondx == 0) {
595 // For the first processor only
596 len += processed;
597 auto discard = limitSampleBufferSize(len, *mLatency);
598 len -= discard;
599 *mLatency -= discard;
600 }
601 }
602 ++processor;
603 return true;
604 });
605 // Report the number discardable during the processing scope
606 // We are assuming len as calculated above is the same in case of multiple
607 // processors
608 return numSamples - len;
609}
610
612{
613 auto pInstance = mwInstance.lock();
614 bool result = pInstance &&
615 // Assuming we are in a processing scope, use the worker settings
616 pInstance->RealtimeProcessEnd(mWorkerSettings.settings) &&
618
619 if (auto pAccessState = TestAccessState())
620 // Always done, regardless of activity
621 // Some dialogs require communication back from the processor so that
622 // they can update their appearance in idle time, and some plug-in
623 // libraries (like lv2) require the host program to mediate the
624 // communication
625 pAccessState->WorkerWrite();
626
627 return result;
628}
629
631{
632 return mMainSettings.settings.extra.GetActive();
633}
634
635bool RealtimeEffectState::IsActive() const noexcept
636{
637 return mWorkerSettings.settings.extra.GetActive();
638}
639
641{
642 auto access = GetAccess();
643 access->ModifySettings([&](EffectSettings &settings) {
644 settings.extra.SetActive(active);
645 return nullptr;
646 });
647 access->Flush();
648
649 Publish(active
652}
653
655{
656 mGroups.clear();
658
659 auto pInstance = mwInstance.lock();
660 if (!pInstance)
661 return false;
662
663 if (!pInstance->UsesMessages()) {
664 // This is the main thread cleaning up a state not now used in processing
666 }
667
668 auto result = pInstance->RealtimeFinalize(mMainSettings.settings);
669 mLatency = {};
670 mInitialized = false;
671 return result;
672}
673
674const std::string &RealtimeEffectState::XMLTag()
675{
676 static const std::string result{"effect"};
677 return result;
678}
679
680static const auto idAttribute = "id";
681static const auto versionAttribute = "version";
682static const auto parametersAttribute = "parameters";
683static const auto parameterAttribute = "parameter";
684static const auto nameAttribute = "name";
685static const auto valueAttribute = "value";
686static constexpr auto activeAttribute = "active";
687
689 const std::string_view &tag, const AttributesList &attrs)
690{
691 if (tag == XMLTag()) {
692 mParameters.clear();
693 mPlugin = nullptr;
694 mID.clear();
695 for (auto &[attr, value] : attrs) {
696 if (attr == idAttribute) {
697 SetID(value.ToWString());
698 if (!mPlugin) {
699 // TODO - complain!!!!
700 }
701 }
702 else if (attr == versionAttribute) {
703 }
704 else if (attr == activeAttribute)
705 // Updating the EffectSettingsExtra although we haven't yet built
706 // the settings
707 mMainSettings.settings.extra.SetActive(value.Get<bool>());
708 }
709 return true;
710 }
711 else if (tag == parametersAttribute)
712 return true;
713 else if (tag == parameterAttribute) {
714 wxString n;
715 wxString v;
716 for (auto &[attr, value] : attrs) {
717 if (attr == nameAttribute)
718 n = value.ToWString();
719 else if (attr == valueAttribute)
720 v = value.ToWString();
721 }
722 mParameters += wxString::Format(wxT("\"%s=%s\" "), n, v);
723 return true;
724 }
725 else
726 return false;
727}
728
729void RealtimeEffectState::HandleXMLEndTag(const std::string_view &tag)
730{
731 if (tag == XMLTag()) {
732 if (mPlugin && !mParameters.empty()) {
734 mPlugin->LoadSettings(parms, mMainSettings.settings);
735 }
736 mParameters.clear();
737 }
738}
739
741{
742 // Tag may be for the state, or the list of parameters, or for one parameter.
743 // See the writing method below. All are handled by this
744 return this;
745}
746
748{
749 if (!mPlugin)
750 return;
751
752 xmlFile.StartTag(XMLTag());
753 const auto active = mMainSettings.settings.extra.GetActive();
754 xmlFile.WriteAttr(activeAttribute, active);
757
758 CommandParameters cmdParms;
759 if (mPlugin->SaveSettings(mMainSettings.settings, cmdParms)) {
761
762 wxString entryName;
763 long entryIndex;
764 bool entryKeepGoing;
765
766 entryKeepGoing = cmdParms.GetFirstEntry(entryName, entryIndex);
767 while (entryKeepGoing) {
768 wxString entryValue = cmdParms.Read(entryName, "");
769
771 xmlFile.WriteAttr(nameAttribute, entryName);
772 xmlFile.WriteAttr(valueAttribute, entryValue);
773 xmlFile.EndTag(parameterAttribute);
774
775 entryKeepGoing = cmdParms.GetNextEntry(entryName, entryIndex);
776 }
777
779 }
780
781 xmlFile.EndTag(XMLTag());
782}
783
784std::shared_ptr<EffectSettingsAccess> RealtimeEffectState::GetAccess()
785{
786 if (!GetEffect())
787 // Effect not found!
788 // Return a dummy
789 return std::make_shared<Access>();
790
791 // Only the main thread assigns to the atomic pointer, here and
792 // once only in the lifetime of the state
793 if (!GetAccessState())
794 {
795 MakeInstance();
796 mpAccessState.emplace(*mPlugin, *this);
797 }
798
799 return std::make_shared<Access>(*this);
800}
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
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(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