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 "AudioIOBase.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 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 // Don't need to update pAccessState->mLastSettings
265 return;
266 }
267 }
268 auto &lastSettings = pAccessState->mLastSettings;
269 // Don't update settings, but do count
270 ++lastSettings.counter;
271 pAccessState->MainWrite(
272 lastSettings.counter, std::move(pMessage));
273 }
274 }
275 }
276 void Flush() override {
277 if (auto pState = mwState.lock()) {
278 if (auto pAccessState = pState->GetAccessState()) {
279 assert(pAccessState->mMainThreadId == std::this_thread::get_id());
280
281 if (pAccessState->mState.mInitialized)
282 {
283 std::unique_lock lk(pAccessState->mLockForCV);
284 pAccessState->mCV.wait(lk,
285 [&] {
286 auto& lastSettings = pAccessState->mLastSettings;
287 pAccessState->MainRead();
288 return pAccessState->mCounter == lastSettings.counter;
289 }
290 );
291 }
292
293 // Update what GetSettings() will return, during play and before
294 // Finalize(), but after it is confirmed that any worker thread has
295 // seen the values given to the last Set(). These values will also
296 // be returned by Get().
297 pState->mMainSettings.Set(pAccessState->mLastSettings);
298 }
299 }
300 }
301 bool IsSameAs(const EffectSettingsAccess &other) const override {
302 if (auto pOther = dynamic_cast<const Access*>(&other)) {
303 auto &mine = mwState;
304 auto &theirs = pOther->mwState;
305 auto less = std::owner_less{};
306 return !(less(mine, theirs) || less(theirs, mine));
307 }
308 return false;
309 }
311 std::weak_ptr<RealtimeEffectState> mwState;
312};
313
315{
316 SetID(id);
317 BuildAll();
318}
319
321{
322
323}
324
326{
327 bool empty = id.empty();
328 if (mID.empty() && !empty) {
329 mID = id;
330 GetEffect();
331 }
332 else
333 // Set mID to non-empty at most once
334 assert(empty);
335}
336
338{
339 return mID;
340}
341
343{
344 if (!mPlugin) {
346 if (mPlugin) {
347 // Also make EffectSettings, but preserve activation
348 auto wasActive = mMainSettings.settings.extra.GetActive();
349 mMainSettings.counter = 0;
350 mMainSettings.settings = mPlugin->MakeSettings();
351 mMainSettings.settings.extra.SetActive(wasActive);
354 }
355 }
356 return mPlugin;
357}
358
359std::shared_ptr<EffectInstance> RealtimeEffectState::MakeInstance()
360{
361 mMovedMessage.reset();
362 mMessage.reset();
363 auto result = mPlugin->MakeInstance();
364 if (result) {
365 // Allocate presized containers in messages, so later
366 // copies of contents might avoid free store operations
367 mMessage = result->MakeMessage();
368 mMovedMessage = result->MakeMessage();
369 if (auto state = GetAccessState())
370 state->Initialize(mMainSettings.settings,
371 mMessage.get(), mMovedOutputs.get());
372 }
373 return result;
374}
375
376std::shared_ptr<EffectInstance>
378{
379 if (!mPlugin)
380 return {};
381
382 auto pInstance = mwInstance.lock();
383 if (!mInitialized) {
387
389 if (!pInstance)
390 mwInstance = pInstance = MakeInstance();
391 if (!pInstance)
392 return {};
393
394 // PRL: conserving pre-3.2.0 behavior, but I don't know why this arbitrary
395 // number was important
396 pInstance->SetBlockSize(512);
397
398 if (!pInstance->RealtimeInitialize(mMainSettings.settings, sampleRate))
399 return {};
400 mInitialized = true;
401 return pInstance;
402 }
403 return pInstance;
404}
405
406std::shared_ptr<EffectInstance> RealtimeEffectState::GetInstance()
407{
409 auto pInstance = mwInstance.lock();
410 if (!pInstance && mPlugin)
411 mwInstance = pInstance = MakeInstance();
412 return pInstance;
413}
414
415std::shared_ptr<EffectInstance>
417{
418 if (!mPlugin)
419 return {};
420
422 mGroups.clear();
423 mLatency = {};
424 return EnsureInstance(sampleRate);
425}
426
427namespace {
428// The caller passes the number of channels to process and specifies
429// the number of input and output buffers. There will always be the
430// same number of output buffers as there are input buffers.
431//
432// Effects require a certain number of input and output buffers.
433// The number of channels we're currently processing may mismatch
434// the effect's requirements. Allocate some inputs repeatedly to a processor
435// that needs more, or allocate multiple processors if they accept too few.
436// Continue until the output buffers are all allocated.
437template<typename F>
439 unsigned chans, const unsigned numAudioIn, const unsigned numAudioOut,
440 const F &f)
441{
442 unsigned indx = 0;
443 for (unsigned ondx = 0; ondx < chans; ondx += numAudioOut) {
444 // Pass the function indices into the arrays of buffers
445 if (!f(indx, ondx))
446 return;
447 indx += numAudioIn;
448 indx %= chans;
449 }
450}
451}
452
454
455std::shared_ptr<EffectInstance>
456RealtimeEffectState::AddTrack(Track &track, unsigned chans, float sampleRate)
457{
458 auto pInstance = EnsureInstance(sampleRate);
459 if (!pInstance)
460 return {};
461 if (!mPlugin)
462 return {};
463 auto first = mCurrentProcessor;
464 const auto numAudioIn = pInstance->GetAudioInCount();
465 const auto numAudioOut = pInstance->GetAudioOutCount();
466 AllocateChannelsToProcessors(chans, numAudioIn, numAudioOut,
467 [&](unsigned, unsigned){
468 // Add a NEW processor
469 if (pInstance->RealtimeAddProcessor(
470 mWorkerSettings.settings, mOutputs.get(), numAudioIn, sampleRate)
471 ) {
472 mCurrentProcessor++;
473 return true;
474 }
475 else
476 return false;
477 });
478 if (mCurrentProcessor > first) {
479 // Remember the sampleRate of the track, so latency can be computed later
480 mGroups[&track] = { first, sampleRate };
481 return pInstance;
482 }
483 return {};
484}
485
487{
488 // Get state changes from the main thread
489 // Note that it is only here that the answer of IsActive() may be changed,
490 // and it is important that for each state the answer is unchanging in one
491 // processing scope.
492 if (auto pAccessState = TestAccessState())
493 pAccessState->WorkerRead();
494
495 // Detect transitions of activity state
496 auto pInstance = mwInstance.lock();
497 bool active = IsActive() && running;
498 if (active != mLastActive) {
499 if (pInstance) {
500 bool success = active
501 ? pInstance->RealtimeResume()
502 : pInstance->RealtimeSuspend();
503 if (!success)
504 return false;
505 }
506 mLastActive = active;
507 }
508
509 bool result = false;
510 if (pInstance) {
511 // Consume messages even if not processing
512 // (issue #3855: plain UI for VST 2 effects)
513
514 // Assuming we are in a processing scope, use the worker settings
516 mWorkerSettings.settings, mMovedMessage.get()
517 };
518 result = pInstance->RealtimeProcessStart(package);
519 }
520
521 if (!pInstance || !active)
522 return false;
523 else
524 return result;
525}
526
527#define stackAllocate(T, count) static_cast<T*>(alloca(count * sizeof(T)))
528
530
531size_t RealtimeEffectState::Process(Track &track, unsigned chans,
532 const float *const *inbuf, float *const *outbuf, float *const dummybuf,
533 size_t numSamples)
534{
535 auto pInstance = mwInstance.lock();
536 if (!mPlugin || !pInstance || !mLastActive) {
537 // Process trivially
538 for (size_t ii = 0; ii < chans; ++ii)
539 memcpy(outbuf[ii], inbuf[ii], numSamples * sizeof(float));
540 return 0;
541 }
542 const auto numAudioIn = pInstance->GetAudioInCount();
543 const auto numAudioOut = pInstance->GetAudioOutCount();
544 const auto clientIn = stackAllocate(const float *, numAudioIn);
545 const auto clientOut = stackAllocate(float *, numAudioOut);
546 size_t len = 0;
547 const auto &pair = mGroups[&track];
548 auto processor = pair.first;
549 // Outer loop over processors
550 AllocateChannelsToProcessors(chans, numAudioIn, numAudioOut,
551 [&](unsigned indx, unsigned ondx){
552 // Point at the correct input buffers
553 unsigned copied = std::min(chans - indx, numAudioIn);
554 std::copy(inbuf + indx, inbuf + indx + copied, clientIn);
555 // If there are too few input channels for what the processor requires,
556 // re-use input channels from the beginning
557 while (auto need = numAudioIn - copied) {
558 auto moreCopied = std::min(chans, need);
559 std::copy(inbuf, inbuf + moreCopied, clientIn + copied);
560 copied += moreCopied;
561 }
562
563 // Point at the correct output buffers
564 copied = std::min(chans - ondx, numAudioOut);
565 std::copy(outbuf + ondx, outbuf + ondx + copied, clientOut);
566 if (copied < numAudioOut) {
567 // Make determinate pointers
568 std::fill(clientOut + copied, clientOut + numAudioOut, dummybuf);
569 }
570
571 // Inner loop over blocks
572 const auto blockSize = pInstance->GetBlockSize();
573 for (size_t block = 0; block < numSamples; block += blockSize) {
574 auto cnt = std::min(numSamples - block, blockSize);
575 // Assuming we are in a processing scope, use the worker settings
576 auto processed = pInstance->RealtimeProcess(processor,
577 mWorkerSettings.settings, clientIn, clientOut, cnt);
578 if (!mLatency)
579 // Find latency once only per initialization scope,
580 // after processing one block
581 mLatency.emplace(
582 pInstance->GetLatency(mWorkerSettings.settings, pair.second));
583 for (size_t i = 0 ; i < numAudioIn; i++)
584 if (clientIn[i])
585 clientIn[i] += cnt;
586 for (size_t i = 0 ; i < numAudioOut; i++)
587 if (clientOut[i])
588 clientOut[i] += cnt;
589 if (ondx == 0) {
590 // For the first processor only
591 len += processed;
592 auto discard = limitSampleBufferSize(len, *mLatency);
593 len -= discard;
594 *mLatency -= discard;
595 }
596 }
597 ++processor;
598 return true;
599 });
600 // Report the number discardable during the processing scope
601 // We are assuming len as calculated above is the same in case of multiple
602 // processors
603 return numSamples - len;
604}
605
607{
608 auto pInstance = mwInstance.lock();
609 bool result = pInstance && IsActive() && mLastActive &&
610 // Assuming we are in a processing scope, use the worker settings
611 pInstance->RealtimeProcessEnd(mWorkerSettings.settings);
612
613 if (auto pAccessState = TestAccessState())
614 // Always done, regardless of activity
615 // Some dialogs require communication back from the processor so that
616 // they can update their appearance in idle time, and some plug-in
617 // libraries (like lv2) require the host program to mediate the
618 // communication
619 pAccessState->WorkerWrite();
620
621 return result;
622}
623
625{
626 return mMainSettings.settings.extra.GetActive();
627}
628
629bool RealtimeEffectState::IsActive() const noexcept
630{
631 return mWorkerSettings.settings.extra.GetActive();
632}
633
635{
636 auto access = GetAccess();
637 access->ModifySettings([&](EffectSettings &settings) {
638 settings.extra.SetActive(active);
639 return nullptr;
640 });
641 access->Flush();
642
643 Publish(active
646}
647
649{
650 // This is the main thread cleaning up a state not now used in processing
652
653 mGroups.clear();
655
656 auto pInstance = mwInstance.lock();
657 if (!pInstance)
658 return false;
659
660 auto result = pInstance->RealtimeFinalize(mMainSettings.settings);
661 mLatency = {};
662 mInitialized = false;
663 return result;
664}
665
666const std::string &RealtimeEffectState::XMLTag()
667{
668 static const std::string result{"effect"};
669 return result;
670}
671
672static const auto idAttribute = "id";
673static const auto versionAttribute = "version";
674static const auto parametersAttribute = "parameters";
675static const auto parameterAttribute = "parameter";
676static const auto nameAttribute = "name";
677static const auto valueAttribute = "value";
678static constexpr auto activeAttribute = "active";
679
681 const std::string_view &tag, const AttributesList &attrs)
682{
683 if (tag == XMLTag()) {
684 mParameters.clear();
685 mPlugin = nullptr;
686 mID.clear();
687 for (auto &[attr, value] : attrs) {
688 if (attr == idAttribute) {
689 SetID(value.ToWString());
690 if (!mPlugin) {
691 // TODO - complain!!!!
692 }
693 }
694 else if (attr == versionAttribute) {
695 }
696 else if (attr == activeAttribute)
697 // Updating the EffectSettingsExtra although we haven't yet built
698 // the settings
699 mMainSettings.settings.extra.SetActive(value.Get<bool>());
700 }
701 return true;
702 }
703 else if (tag == parametersAttribute)
704 return true;
705 else if (tag == parameterAttribute) {
706 wxString n;
707 wxString v;
708 for (auto &[attr, value] : attrs) {
709 if (attr == nameAttribute)
710 n = value.ToWString();
711 else if (attr == valueAttribute)
712 v = value.ToWString();
713 }
714 mParameters += wxString::Format(wxT("\"%s=%s\" "), n, v);
715 return true;
716 }
717 else
718 return false;
719}
720
721void RealtimeEffectState::HandleXMLEndTag(const std::string_view &tag)
722{
723 if (tag == XMLTag()) {
724 if (mPlugin && !mParameters.empty()) {
726 mPlugin->LoadSettings(parms, mMainSettings.settings);
727 }
728 mParameters.clear();
729 }
730}
731
733{
734 // Tag may be for the state, or the list of parameters, or for one parameter.
735 // See the writing method below. All are handled by this
736 return this;
737}
738
740{
741 if (!mPlugin)
742 return;
743
744 xmlFile.StartTag(XMLTag());
745 const auto active = mMainSettings.settings.extra.GetActive();
746 xmlFile.WriteAttr(activeAttribute, active);
749
750 CommandParameters cmdParms;
751 if (mPlugin->SaveSettings(mMainSettings.settings, cmdParms)) {
753
754 wxString entryName;
755 long entryIndex;
756 bool entryKeepGoing;
757
758 entryKeepGoing = cmdParms.GetFirstEntry(entryName, entryIndex);
759 while (entryKeepGoing) {
760 wxString entryValue = cmdParms.Read(entryName, "");
761
763 xmlFile.WriteAttr(nameAttribute, entryName);
764 xmlFile.WriteAttr(valueAttribute, entryValue);
765 xmlFile.EndTag(parameterAttribute);
766
767 entryKeepGoing = cmdParms.GetNextEntry(entryName, entryIndex);
768 }
769
771 }
772
773 xmlFile.EndTag(XMLTag());
774}
775
776std::shared_ptr<EffectSettingsAccess> RealtimeEffectState::GetAccess()
777{
778 if (!GetEffect())
779 // Effect not found!
780 // Return a dummy
781 return std::make_shared<Access>();
782
783 // Only the main thread assigns to the atomic pointer, here and
784 // once only in the lifetime of the state
785 if (!GetAccessState())
786 {
787 MakeInstance();
788 mpAccessState.emplace(*mPlugin, *this);
789 }
790
791 return std::make_shared<Access>(*this);
792}
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< 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.
std::shared_ptr< EffectInstance > AddTrack(Track &track, unsigned chans, float sampleRate)
Main thread sets up this state before adding it to lists.
std::unordered_map< Track *, std::pair< size_t, double > > mGroups
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.
size_t Process(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.
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:225
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:26
virtual void StartTag(const wxString &name)
Definition: XMLWriter.cpp:80
void WriteAttr(const wxString &name, const Identifier &value)
Definition: XMLWriter.h:37
virtual void EndTag(const wxString &name)
Definition: XMLWriter.cpp:103
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