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 "EffectInterface.h"
14#include "MessageBuffer.h"
15#include "PluginManager.h"
16#include "SampleCount.h"
17
18#include <chrono>
19#include <thread>
20#include <condition_variable>
21
23class RealtimeEffectState::AccessState : public NonInterferingBase {
24public:
26 : mEffect{ effect }
27 , mState{ state }
28 {
29 // Clean initial state of the counter
30 state.mMainSettings.counter = 0;
31 Initialize(state.mMainSettings.settings,
32 state.mMessage.get(), state.mMovedOutputs.get());
33 }
34
36 const EffectInstance::Message *pMessage,
37 const EffectOutputs *pOutputs)
38 {
39 mLastSettings = { settings, 0 };
40 // Initialize each message buffer with two copies
41 mChannelToMain.Write(ToMainSlot{ { 0,
42 pOutputs ? pOutputs->Clone() : nullptr } });
43 mChannelToMain.Write(ToMainSlot{ { 0,
44 pOutputs ? pOutputs->Clone() : nullptr } });
45 mChannelFromMain.Write(FromMainSlot{ settings, pMessage });
46 mChannelFromMain.Write(FromMainSlot{ settings, pMessage });
47
48 mMainThreadId = std::this_thread::get_id();
49 }
50
51 void MainRead() {
53 mCounter);
54 }
56 std::unique_ptr<EffectInstance::Message> pMessage) {
57 // Main thread may simply swap new content into place
59 std::move(settings.settings), settings.counter, std::move(pMessage) });
60 }
62 std::unique_ptr<EffectInstance::Message> pMessage) {
64 counter, std::move(pMessage) });
65 }
66
70 };
71 void WorkerRead() {
72 // Worker thread avoids memory allocation. It copies the contents of any
74 }
75 void WorkerWrite() {
76
77 {
78 std::unique_lock lk(mLockForCV);
79
80 // Worker thread avoids memory allocation.
82 mState.mWorkerSettings.counter, mState.mOutputs.get() });
83 }
84 mCV.notify_one();
85 }
86
87 struct ToMainSlot {
88 // For initialization of the channel
89 ToMainSlot() = default;
90 explicit ToMainSlot(Response response)
91 : mResponse{ std::move(response) }
92 {}
94
95 // Worker thread writes the slot
97 // This happens during MessageBuffer's busying of the slot
98 mResponse.counter = arg.counter;
99 if (mResponse.pOutputs && arg.pOutputs)
100 mResponse.pOutputs->Assign(std::move(*arg.pOutputs));
101 return *this;
102 }
103
104 struct Reader { Reader(ToMainSlot &&slot, EffectOutputs *pOutputs,
105 Response::Counter &counter
106 ) {
107 // Main thread is not under the performance constraints of the
108 // worker, but Assign is still used so that
109 // members of underlying vectors or other containers do not
110 // relocate
111 if (pOutputs && slot.mResponse.pOutputs)
112 pOutputs->Assign(std::move(*slot.mResponse.pOutputs));
113 counter = slot.mResponse.counter;
114 } };
115
117 };
118
121 std::unique_ptr<EffectInstance::Message> pMessage;
122 };
125 std::unique_ptr<EffectInstance::Message> pMessage;
126 };
127
128 // For initialization of the channel
129 FromMainSlot() = default;
131 const EffectInstance::Message *pMessage)
132 // Copy std::any
133 : mMessage{ settings, 0, pMessage ? pMessage->Clone() : nullptr }
134 {}
136
137 // Main thread writes the slot
139 mMessage.SettingsAndCounter::swap(message);
140 if (message.pMessage && mMessage.pMessage)
141 // Merge the incoming message with any still unconsumed message
142 mMessage.pMessage->Merge(std::move(*message.pMessage));
143 return *this;
144 }
145
146 // Main thread writes the slot
148 mMessage.counter = message.counter;
149 if (message.pMessage && mMessage.pMessage)
150 // Merge the incoming message with any still unconsumed message
151 mMessage.pMessage->Merge(std::move(*message.pMessage));
152 return *this;
153 }
154
155 // Worker thread reads the slot
156 struct Reader { Reader(FromMainSlot &&slot,
157 const EffectSettingsManager &effect, RealtimeEffectState &state) {
158 auto &settings = state.mWorkerSettings;
159 if(slot.mMessage.counter == settings.counter)
160 return;//copy once
161 settings.counter = slot.mMessage.counter;
162
163 // This happens during MessageBuffer's busying of the slot
165 slot.mMessage.settings, settings.settings);
166 settings.settings.extra = slot.mMessage.settings.extra;
167 if (slot.mMessage.pMessage && state.mMovedMessage)
168 // Copy the message from the buffer (not a merge)
169 state.mMovedMessage->Assign(std::move(*slot.mMessage.pMessage));
170 } };
171
173 };
174
177
181
183
184 std::mutex mLockForCV;
185 std::condition_variable mCV;
186
188};
189
192 Access() = default;
194 : mwState{ state.weak_from_this() }
195 {
196 }
197 ~Access() override = default;
198
199
200 const EffectSettings &Get() override {
201 if (auto pState = mwState.lock()) {
202 if (auto pAccessState = pState->GetAccessState()) {
203 if (pAccessState->mState.mInitialized)
204 {
205 // try once
206 assert(pAccessState->mState.mInitialized);
207 auto& lastSettings = pAccessState->mLastSettings;
208 // Assigns to mCounter
209 pAccessState->MainRead();
210 }
211 else {
212 // Not yet waiting on the other thread's progress
213 // Not necessarily values yet in the state's Settings objects
214 }
215 return pAccessState->mLastSettings.settings;
216 }
217 }
218 // Non-modal dialog may have outlived the RealtimeEffectState
219 static EffectSettings empty;
220 return empty;
221 }
222 void Set(EffectSettings &&settings, std::unique_ptr<Message> pMessage)
223 override {
224 if (auto pState = mwState.lock()) {
225 if (auto pAccessState = pState->GetAccessState()) {
226 if (pMessage && !pAccessState->mState.mInitialized) {
227 // Other thread isn't processing.
228 // Let the instance consume the message directly.
229 if (auto pInstance = pState->mwInstance.lock()) {
230 auto &stateSettings = pState->mMainSettings.settings;
231 stateSettings = std::move(settings);
233 stateSettings, pMessage.get()
234 };
235 pInstance->RealtimeProcessStart(package);
236 pInstance->RealtimeProcessEnd(stateSettings);
237 pAccessState->mLastSettings.settings = stateSettings;
238 return;
239 }
240 }
241 auto &lastSettings = pAccessState->mLastSettings;
242 // move to remember values here
243 lastSettings.settings = std::move(settings);
244 ++lastSettings.counter;
245 // move a copy to there
246 pAccessState->MainWrite(
247 SettingsAndCounter{ lastSettings }, std::move(pMessage));
248 }
249 }
250 }
251 void Set(std::unique_ptr<Message> pMessage)
252 override {
253 if (auto pState = mwState.lock()) {
254 if (auto pAccessState = pState->GetAccessState()) {
255 if (pMessage && !pAccessState->mState.mInitialized) {
256 // Other thread isn't processing.
257 // Let the instance consume the message directly.
258 if (auto pInstance = pState->mwInstance.lock()) {
259 auto &stateSettings = pState->mMainSettings.settings;
261 stateSettings, pMessage.get()
262 };
263 pInstance->RealtimeProcessStart(package);
264 pInstance->RealtimeProcessEnd(stateSettings);
265 // Don't need to update pAccessState->mLastSettings
266 return;
267 }
268 }
269 auto &lastSettings = pAccessState->mLastSettings;
270 // Don't update settings, but do count
271 ++lastSettings.counter;
272 pAccessState->MainWrite(
273 lastSettings.counter, std::move(pMessage));
274 }
275 }
276 }
277 void Flush() override {
278 if (auto pState = mwState.lock()) {
279 if (auto pAccessState = pState->GetAccessState()) {
280 assert(pAccessState->mMainThreadId == std::this_thread::get_id());
281
282 if (pAccessState->mState.mInitialized)
283 {
284 std::unique_lock lk(pAccessState->mLockForCV);
285 pAccessState->mCV.wait(lk,
286 [&] {
287 auto& lastSettings = pAccessState->mLastSettings;
288 pAccessState->MainRead();
289 return pAccessState->mCounter == lastSettings.counter;
290 }
291 );
292 }
293
294 // Update what GetSettings() will return, during play and before
295 // Finalize(), but after it is confirmed that any worker thread has
296 // seen the values given to the last Set(). These values will also
297 // be returned by Get().
298 pState->mMainSettings.Set(pAccessState->mLastSettings);
299 }
300 }
301 }
302 bool IsSameAs(const EffectSettingsAccess &other) const override {
303 if (auto pOther = dynamic_cast<const Access*>(&other)) {
304 auto &mine = mwState;
305 auto &theirs = pOther->mwState;
306 auto less = std::owner_less{};
307 return !(less(mine, theirs) || less(theirs, mine));
308 }
309 return false;
310 }
312 std::weak_ptr<RealtimeEffectState> mwState;
313};
314
316{
317 SetID(id);
318 BuildAll();
319}
320
322{
323
324}
325
327{
328 bool empty = id.empty();
329 if (mID.empty() && !empty) {
330 mID = id;
331 GetEffect();
332 }
333 else
334 // Set mID to non-empty at most once
335 assert(empty);
336}
337
339{
340 return mID;
341}
342
344{
345 if (!mPlugin) {
347 if (mPlugin) {
348 // Also make EffectSettings, but preserve activation
349 auto wasActive = mMainSettings.settings.extra.GetActive();
350 mMainSettings.counter = 0;
351 mMainSettings.settings = mPlugin->MakeSettings();
352 mMainSettings.settings.extra.SetActive(wasActive);
355 }
356 }
357 return mPlugin;
358}
359
360std::shared_ptr<EffectInstance> RealtimeEffectState::MakeInstance()
361{
362 mMovedMessage.reset();
363 mMessage.reset();
364 auto result = mPlugin->MakeInstance();
365 if (result) {
366 // Allocate presized containers in messages, so later
367 // copies of contents might avoid free store operations
368 mMessage = result->MakeMessage();
369 mMovedMessage = result->MakeMessage();
370 if (auto state = GetAccessState())
371 state->Initialize(mMainSettings.settings,
372 mMessage.get(), mMovedOutputs.get());
373 }
374 return result;
375}
376
377std::shared_ptr<EffectInstance>
379{
380 if (!mPlugin)
381 return {};
382
383 auto pInstance = mwInstance.lock();
384 if (!mInitialized) {
388
390 if (!pInstance)
391 mwInstance = pInstance = MakeInstance();
392 if (!pInstance)
393 return {};
394
395 // PRL: conserving pre-3.2.0 behavior, but I don't know why this arbitrary
396 // number was important
397 pInstance->SetBlockSize(512);
398
399 if (!pInstance->RealtimeInitialize(mMainSettings.settings, sampleRate))
400 return {};
401 mInitialized = true;
402 return pInstance;
403 }
404 return pInstance;
405}
406
407std::shared_ptr<EffectInstance> RealtimeEffectState::GetInstance()
408{
410 auto pInstance = mwInstance.lock();
411 if (!pInstance && mPlugin)
412 mwInstance = pInstance = MakeInstance();
413 return pInstance;
414}
415
416std::shared_ptr<EffectInstance>
418{
419 if (!mPlugin)
420 return {};
421
423 mGroups.clear();
424 mLatency = {};
425 return EnsureInstance(sampleRate);
426}
427
428namespace {
429// The caller passes the number of channels to process and specifies
430// the number of input and output buffers. There will always be the
431// same number of output buffers as there are input buffers.
432//
433// Effects require a certain number of input and output buffers.
434// The number of channels we're currently processing may mismatch
435// the effect's requirements. Allocate some inputs repeatedly to a processor
436// that needs more, or allocate multiple processors if they accept too few.
437// Continue until the output buffers are all allocated.
438template<typename F>
440 unsigned chans, const unsigned numAudioIn, const unsigned numAudioOut,
441 const F &f)
442{
443 unsigned indx = 0;
444 for (unsigned ondx = 0; ondx < chans; ondx += numAudioOut) {
445 // Pass the function indices into the arrays of buffers
446 if (!f(indx, ondx))
447 return;
448 indx += numAudioIn;
449 indx %= chans;
450 }
451}
452}
453
455
456std::shared_ptr<EffectInstance>
458 const Track &track, unsigned chans, float sampleRate)
459{
460 auto pInstance = EnsureInstance(sampleRate);
461 if (!pInstance)
462 return {};
463 if (!mPlugin)
464 return {};
465 auto first = mCurrentProcessor;
466 const auto numAudioIn = pInstance->GetAudioInCount();
467 const auto numAudioOut = pInstance->GetAudioOutCount();
468 AllocateChannelsToProcessors(chans, numAudioIn, numAudioOut,
469 [&](unsigned, unsigned){
470 // Add a NEW processor
471 if (pInstance->RealtimeAddProcessor(
472 mWorkerSettings.settings, mOutputs.get(), numAudioIn, sampleRate)
473 ) {
474 mCurrentProcessor++;
475 return true;
476 }
477 else
478 return false;
479 });
480 if (mCurrentProcessor > first) {
481 // Remember the sampleRate of the track, so latency can be computed later
482 mGroups[&track] = { first, sampleRate };
483 return pInstance;
484 }
485 return {};
486}
487
489{
490 // Get state changes from the main thread
491 // Note that it is only here that the answer of IsActive() may be changed,
492 // and it is important that for each state the answer is unchanging in one
493 // processing scope.
494 if (auto pAccessState = TestAccessState())
495 pAccessState->WorkerRead();
496
497 // Detect transitions of activity state
498 auto pInstance = mwInstance.lock();
499 bool active = IsActive() && running;
500 if (active != mLastActive) {
501 if (pInstance) {
502 bool success = active
503 ? pInstance->RealtimeResume()
504 : pInstance->RealtimeSuspend();
505 if (!success)
506 return false;
507 }
508 mLastActive = active;
509 }
510
511 bool result = false;
512 if (pInstance) {
513 // Consume messages even if not processing
514 // (issue #3855: plain UI for VST 2 effects)
515
516 // Assuming we are in a processing scope, use the worker settings
518 mWorkerSettings.settings, mMovedMessage.get()
519 };
520 result = pInstance->RealtimeProcessStart(package);
521 }
522
523 if (!pInstance || !active)
524 return false;
525 else
526 return result;
527}
528
529#define stackAllocate(T, count) static_cast<T*>(alloca(count * sizeof(T)))
530
532
533size_t RealtimeEffectState::Process(const Track &track, unsigned chans,
534 const float *const *inbuf, float *const *outbuf, float *const dummybuf,
535 size_t numSamples)
536{
537 auto pInstance = mwInstance.lock();
538 if (!mPlugin || !pInstance || !mLastActive) {
539 // Process trivially
540 for (size_t ii = 0; ii < chans; ++ii)
541 memcpy(outbuf[ii], inbuf[ii], numSamples * sizeof(float));
542 return 0;
543 }
544 const auto numAudioIn = pInstance->GetAudioInCount();
545 const auto numAudioOut = pInstance->GetAudioOutCount();
546 const auto clientIn = stackAllocate(const float *, numAudioIn);
547 const auto clientOut = stackAllocate(float *, numAudioOut);
548 size_t len = 0;
549 const auto &pair = mGroups[&track];
550 auto processor = pair.first;
551 // Outer loop over processors
552 AllocateChannelsToProcessors(chans, numAudioIn, numAudioOut,
553 [&](unsigned indx, unsigned ondx){
554 // Point at the correct input buffers
555 unsigned copied = std::min(chans - indx, numAudioIn);
556 std::copy(inbuf + indx, inbuf + indx + copied, clientIn);
557 // If there are too few input channels for what the processor requires,
558 // re-use input channels from the beginning
559 while (auto need = numAudioIn - copied) {
560 auto moreCopied = std::min(chans, need);
561 std::copy(inbuf, inbuf + moreCopied, clientIn + copied);
562 copied += moreCopied;
563 }
564
565 // Point at the correct output buffers
566 copied = std::min(chans - ondx, numAudioOut);
567 std::copy(outbuf + ondx, outbuf + ondx + copied, clientOut);
568 if (copied < numAudioOut) {
569 // Make determinate pointers
570 std::fill(clientOut + copied, clientOut + numAudioOut, dummybuf);
571 }
572
573 // Inner loop over blocks
574 const auto blockSize = pInstance->GetBlockSize();
575 for (size_t block = 0; block < numSamples; block += blockSize) {
576 auto cnt = std::min(numSamples - block, blockSize);
577 // Assuming we are in a processing scope, use the worker settings
578 auto processed = pInstance->RealtimeProcess(processor,
579 mWorkerSettings.settings, clientIn, clientOut, cnt);
580 if (!mLatency)
581 // Find latency once only per initialization scope,
582 // after processing one block
583 mLatency.emplace(
584 pInstance->GetLatency(mWorkerSettings.settings, pair.second));
585 for (size_t i = 0 ; i < numAudioIn; i++)
586 if (clientIn[i])
587 clientIn[i] += cnt;
588 for (size_t i = 0 ; i < numAudioOut; i++)
589 if (clientOut[i])
590 clientOut[i] += cnt;
591 if (ondx == 0) {
592 // For the first processor only
593 len += processed;
594 auto discard = limitSampleBufferSize(len, *mLatency);
595 len -= discard;
596 *mLatency -= discard;
597 }
598 }
599 ++processor;
600 return true;
601 });
602 // Report the number discardable during the processing scope
603 // We are assuming len as calculated above is the same in case of multiple
604 // processors
605 return numSamples - len;
606}
607
609{
610 auto pInstance = mwInstance.lock();
611 bool result = pInstance &&
612 // Assuming we are in a processing scope, use the worker settings
613 pInstance->RealtimeProcessEnd(mWorkerSettings.settings) &&
615
616 if (auto pAccessState = TestAccessState())
617 // Always done, regardless of activity
618 // Some dialogs require communication back from the processor so that
619 // they can update their appearance in idle time, and some plug-in
620 // libraries (like lv2) require the host program to mediate the
621 // communication
622 pAccessState->WorkerWrite();
623
624 return result;
625}
626
628{
629 return mMainSettings.settings.extra.GetActive();
630}
631
632bool RealtimeEffectState::IsActive() const noexcept
633{
634 return mWorkerSettings.settings.extra.GetActive();
635}
636
638{
639 auto access = GetAccess();
640 access->ModifySettings([&](EffectSettings &settings) {
641 settings.extra.SetActive(active);
642 return nullptr;
643 });
644 access->Flush();
645
646 Publish(active
649}
650
652{
653 mGroups.clear();
655
656 auto pInstance = mwInstance.lock();
657 if (!pInstance)
658 return false;
659
660 if (!pInstance->UsesMessages()) {
661 // This is the main thread cleaning up a state not now used in processing
663 }
664
665 auto result = pInstance->RealtimeFinalize(mMainSettings.settings);
666 mLatency = {};
667 mInitialized = false;
668 return result;
669}
670
671const std::string &RealtimeEffectState::XMLTag()
672{
673 static const std::string result{"effect"};
674 return result;
675}
676
677static const auto idAttribute = "id";
678static const auto versionAttribute = "version";
679static const auto parametersAttribute = "parameters";
680static const auto parameterAttribute = "parameter";
681static const auto nameAttribute = "name";
682static const auto valueAttribute = "value";
683static constexpr auto activeAttribute = "active";
684
686 const std::string_view &tag, const AttributesList &attrs)
687{
688 if (tag == XMLTag()) {
689 mParameters.clear();
690 mPlugin = nullptr;
691 mID.clear();
692 for (auto &[attr, value] : attrs) {
693 if (attr == idAttribute) {
694 SetID(value.ToWString());
695 if (!mPlugin) {
696 // TODO - complain!!!!
697 }
698 }
699 else if (attr == versionAttribute) {
700 }
701 else if (attr == activeAttribute)
702 // Updating the EffectSettingsExtra although we haven't yet built
703 // the settings
704 mMainSettings.settings.extra.SetActive(value.Get<bool>());
705 }
706 return true;
707 }
708 else if (tag == parametersAttribute)
709 return true;
710 else if (tag == parameterAttribute) {
711 wxString n;
712 wxString v;
713 for (auto &[attr, value] : attrs) {
714 if (attr == nameAttribute)
715 n = value.ToWString();
716 else if (attr == valueAttribute)
717 v = value.ToWString();
718 }
719 mParameters += wxString::Format(wxT("\"%s=%s\" "), n, v);
720 return true;
721 }
722 else
723 return false;
724}
725
726void RealtimeEffectState::HandleXMLEndTag(const std::string_view &tag)
727{
728 if (tag == XMLTag()) {
729 if (mPlugin && !mParameters.empty()) {
731 mPlugin->LoadSettings(parms, mMainSettings.settings);
732 }
733 mParameters.clear();
734 }
735}
736
738{
739 // Tag may be for the state, or the list of parameters, or for one parameter.
740 // See the writing method below. All are handled by this
741 return this;
742}
743
745{
746 if (!mPlugin)
747 return;
748
749 xmlFile.StartTag(XMLTag());
750 const auto active = mMainSettings.settings.extra.GetActive();
751 xmlFile.WriteAttr(activeAttribute, active);
754
755 CommandParameters cmdParms;
756 if (mPlugin->SaveSettings(mMainSettings.settings, cmdParms)) {
758
759 wxString entryName;
760 long entryIndex;
761 bool entryKeepGoing;
762
763 entryKeepGoing = cmdParms.GetFirstEntry(entryName, entryIndex);
764 while (entryKeepGoing) {
765 wxString entryValue = cmdParms.Read(entryName, "");
766
768 xmlFile.WriteAttr(nameAttribute, entryName);
769 xmlFile.WriteAttr(valueAttribute, entryValue);
770 xmlFile.EndTag(parameterAttribute);
771
772 entryKeepGoing = cmdParms.GetNextEntry(entryName, entryIndex);
773 }
774
776 }
777
778 xmlFile.EndTag(XMLTag());
779}
780
781std::shared_ptr<EffectSettingsAccess> RealtimeEffectState::GetAccess()
782{
783 if (!GetEffect())
784 // Effect not found!
785 // Return a dummy
786 return std::make_shared<Access>();
787
788 // Only the main thread assigns to the atomic pointer, here and
789 // once only in the lifetime of the state
790 if (!GetAccessState())
791 {
792 MakeInstance();
793 mpAccessState.emplace(*mPlugin, *this);
794 }
795
796 return std::make_shared<Access>(*this);
797}
wxT("CloseDown"))
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:87
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: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)
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 > AddTrack(const Track &track, unsigned chans, float sampleRate)
Main thread sets up this state before adding it to lists.
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.
size_t Process(const Track &track, unsigned chans, const float *const *inbuf, float *const *outbuf, float *dummybuf, size_t numSamples)
Worker thread processes part of a batch of samples.
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.
std::unordered_map< const Track *, std::pair< size_t, double > > mGroups
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
Abstract base class for an object holding data associated with points on a time axis.
Definition: Track.h:226
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)
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