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
18#include <chrono>
19#include <thread>
20
22class RealtimeEffectState::AccessState : public NonInterferingBase {
23public:
25 : mEffect{ effect }
26 , mState{ state }
27 {
28 // Clean initial state of the counter
29 auto &mainSettings = state.mMainSettings;
30 mainSettings.extra.SetCounter(0);
31 // Initialize each message buffer with two copies of settings
32 mChannelToMain.Write(ToMainSlot{ mainSettings });
33 mChannelToMain.Write(ToMainSlot{ mainSettings });
34 mChannelFromMain.Write(FromMainSlot{ mainSettings });
35 mChannelFromMain.Write(FromMainSlot{ mainSettings });
36 }
37
39 // Main thread clones the object in the std::any, then gives a reference
41 return mMainThreadCache;
42 }
44 // Main thread may simply swap new content into place
45 mChannelFromMain.Write(std::move(settings));
46 }
48 // Send a copy of settings for worker to update from later
50 // Main thread assumes worker isn't processing and bypasses the echo
51 return (mMainThreadCache = settings);
52 }
53
56 void WorkerRead() {
57 // Worker thread avoids memory allocation. It copies the contents of any
60 }
61 void WorkerWrite() {
62 // Worker thread avoids memory allocation. It copies the contents of any
65 }
66
68
69 struct ToMainSlot {
70 // For initialization of the channel
71 ToMainSlot() = default;
73 // Copy std::any
75 {}
77
78 // Worker thread writes the slot
80 // This happens during MessageBuffer's busying of the slot
81 arg.effect.CopySettingsContents(
82 arg.settings, mSettings,
84 mSettings.extra = arg.settings.extra;
85 return *this;
86 }
87
88 // Main thread doesn't move out of the slot, but copies std::any
89 // and extra fields
91 settings = slot.mSettings;
92 } };
93
95 };
96
97 struct FromMainSlot {
98 // For initialization of the channel
99 FromMainSlot() = default;
101 // Copy std::any
103 {}
105
106 // Main thread writes the slot
109 return *this;
110 }
111
112 // Worker thread reads the slot
113 struct Reader { Reader(FromMainSlot &&slot,
115 // This happens during MessageBuffer's busying of the slot
117 slot.mSettings, settings,
119 settings.extra = slot.mSettings.extra;
120 } };
121
123 };
124
127
131
133};
134
137 Access() = default;
139 : mwState{ state.weak_from_this() }
140 {
141 }
142 ~Access() override = default;
143 // Try once to detect that the worker thread has echoed the last write
145 auto &lastSettings = state.mLastSettings;
146 auto pResult = &state.MainRead();
147 if (!lastSettings.has_value() ||
148 pResult->extra.GetCounter() ==
149 lastSettings.extra.GetCounter()
150 ){
151 // First time test, or echo is completed
152 return pResult;
153 }
154 else if (!state.mState.mInitialized) {
155 // not relying on the other thread to make progress
156 // and no fear of data races
157 return &state.MainWriteThrough(lastSettings);
158 }
159 return nullptr;
160 }
161 const EffectSettings &Get() override {
162 if (auto pState = mwState.lock()) {
163 if (auto pAccessState = pState->GetAccessState()) {
164 FlushAttempt(*pAccessState); // try once, ignore success
165 auto &lastSettings = pAccessState->mLastSettings;
166 return lastSettings.has_value()
167 ? lastSettings
168 // If no value there yet, then FlushAttempt did MainRead which
169 // has copied the initial value given to the constructor
170 : pAccessState->MainThreadCache();
171 }
172 }
173 // Non-modal dialog may have outlived the RealtimeEffectState
174 static EffectSettings empty;
175 return empty;
176 }
177 void Set(EffectSettings &&settings) override {
178 if (auto pState = mwState.lock())
179 if (auto pAccessState = pState->GetAccessState()) {
180 auto &lastSettings = pAccessState->mLastSettings;
181 auto lastCounter = lastSettings.extra.GetCounter();
182 settings.extra.SetCounter(++lastCounter);
183 // move to remember values here
184 lastSettings = std::move(settings);
185 // move a copy to there
186 pAccessState->MainWrite(EffectSettings{ lastSettings });
187 }
188 }
189 void Flush() override {
190 if (auto pState = mwState.lock()) {
191 if (auto pAccessState = pState->GetAccessState()) {
192 const EffectSettings *pResult{};
193 while (!(pResult = FlushAttempt(*pAccessState))) {
194 // Wait for progress of audio thread
195 using namespace std::chrono;
196 std::this_thread::sleep_for(50ms);
197 }
198 pState->mMainSettings.Set(*pResult); // Update the state
199 pAccessState->mLastSettings = *pResult;
200 }
201 }
202 }
203 bool IsSameAs(const EffectSettingsAccess &other) const override {
204 if (auto pOther = dynamic_cast<const Access*>(&other)) {
205 auto &mine = mwState;
206 auto &theirs = pOther->mwState;
207 auto less = std::owner_less{};
208 return !(less(mine, theirs) || less(theirs, mine));
209 }
210 return false;
211 }
213 std::weak_ptr<RealtimeEffectState> mwState;
214};
215
217{
218 SetID(id);
219}
220
222 : mID{ other.mID }
223 , mPlugin{ other.mPlugin }
224 , mMainSettings{ other.mMainSettings }
225{
226 // Do not copy mWorkerSettings
227}
228
230
232{
233 bool empty = id.empty();
234 if (mID.empty() && !empty) {
235 mID = id;
236 GetEffect();
237 }
238 else
239 // Set mID to non-empty at most once
240 assert(empty);
241}
242
244{
245 return mID;
246}
247
248
250{
251 if (!mPlugin) {
253 if (mPlugin) {
254 // Also make EffectSettings, but preserve activation
255 auto wasActive = mMainSettings.extra.GetActive();
257 mMainSettings.extra.SetActive(wasActive);
258 }
259 }
260 return mPlugin;
261}
262
263std::shared_ptr<EffectInstance>
265{
266 if (!mPlugin)
267 return {};
268
269 auto pInstance = mwInstance.lock();
270 if (!mInitialized) {
274
276 if (!pInstance)
277 mwInstance = pInstance = mPlugin->MakeInstance();
278 if (!pInstance)
279 return {};
280
281 mInitialized = true;
282
283 // PRL: conserving pre-3.2.0 behavior, but I don't know why this arbitrary
284 // number was important
285 pInstance->SetBlockSize(512);
286
287 if (!pInstance->RealtimeInitialize(mMainSettings, sampleRate))
288 return {};
289 return pInstance;
290 }
291 return pInstance;
292}
293
294std::shared_ptr<EffectInstance> RealtimeEffectState::GetInstance()
295{
297 auto pInstance = mwInstance.lock();
298 if (!pInstance && mPlugin)
299 mwInstance = pInstance = mPlugin->MakeInstance();
300 return pInstance;
301}
302
303std::shared_ptr<EffectInstance>
305{
306 if (!mPlugin)
307 return {};
308
310 mGroups.clear();
311 return EnsureInstance(sampleRate);
312}
313
315
316std::shared_ptr<EffectInstance>
317RealtimeEffectState::AddTrack(Track &track, unsigned chans, float sampleRate)
318{
319 auto pInstance = EnsureInstance(sampleRate);
320 if (!pInstance)
321 return {};
322
323 if (!mPlugin)
324 return {};
325
326 auto ichans = chans;
327 auto ochans = chans;
328 auto gchans = chans;
329
330 auto first = mCurrentProcessor;
331
332 const auto numAudioIn = mPlugin->GetAudioInCount();
333 const auto numAudioOut = mPlugin->GetAudioOutCount();
334
335 // Call the client until we run out of input or output channels
336 while (ichans > 0 && ochans > 0)
337 {
338 // If we don't have enough input channels to accommodate the client's
339 // requirements, then we replicate the input channels until the
340 // client's needs are met.
341 if (ichans < numAudioIn)
342 {
343 // All input channels have been consumed
344 ichans = 0;
345 }
346 // Otherwise fulfill the client's needs with as many input channels as possible.
347 // After calling the client with this set, we will loop back up to process more
348 // of the input/output channels.
349 else if (ichans >= numAudioIn)
350 {
351 gchans = numAudioIn;
352 ichans -= gchans;
353 }
354
355 // If we don't have enough output channels to accommodate the client's
356 // requirements, then we provide all of the output channels and fulfill
357 // the client's needs with dummy buffers. These will just get tossed.
358 if (ochans < numAudioOut)
359 {
360 // All output channels have been consumed
361 ochans = 0;
362 }
363 // Otherwise fulfill the client's needs with as many output channels as possible.
364 // After calling the client with this set, we will loop back up to process more
365 // of the input/output channels.
366 else if (ochans >= numAudioOut)
367 {
368 ochans -= numAudioOut;
369 }
370
371 // Add a NEW processor
372 // Pass reference to worker settings, not main -- such as, for connecting
373 // Ladspa ports to the proper addresses.
374 if (pInstance->RealtimeAddProcessor(mWorkerSettings, gchans, sampleRate))
376 else
377 break;
378 }
379
380 if (mCurrentProcessor > first) {
381 mGroups[&track] = first;
382 return pInstance;
383 }
384 return {};
385}
386
388{
389 // Get state changes from the main thread
390 // Note that it is only here that the answer of IsActive() may be changed,
391 // and it is important that for each state the answer is unchanging in one
392 // processing scope.
393 if (auto pAccessState = TestAccessState())
394 pAccessState->WorkerRead();
395
396 // Detect transitions of activity state
397 auto pInstance = mwInstance.lock();
398 bool active = IsActive() && running;
399 if (active != mLastActive) {
400 if (pInstance) {
401 bool success = active
402 ? pInstance->RealtimeResume()
403 : pInstance->RealtimeSuspend();
404 if (!success)
405 return false;
406 }
407 mLastActive = active;
408 }
409
410 if (!pInstance || !active)
411 return false;
412
413 // Assuming we are in a processing scope, use the worker settings
414 return pInstance->RealtimeProcessStart(mWorkerSettings);
415}
416
418
419void RealtimeEffectState::Process(Track &track, unsigned chans,
420 const float *const *inbuf, float *const *outbuf, size_t numSamples)
421{
422 auto pInstance = mwInstance.lock();
423 if (!mPlugin || !pInstance || !mLastActive) {
424 // Process trivially
425 for (size_t ii = 0; ii < chans; ++ii)
426 memcpy(outbuf[ii], inbuf[ii], numSamples * sizeof(float));
427 return;
428 }
429
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 always require a certain number of input and output buffers,
435 // so if the number of channels we're currently processing are different
436 // than what the effect expects, then we use a few methods of satisfying
437 // the effects requirements.
438 const auto numAudioIn = mPlugin->GetAudioInCount();
439 const auto numAudioOut = mPlugin->GetAudioOutCount();
440
441 const auto clientIn =
442 static_cast<const float **>(alloca(numAudioIn * sizeof(float *)));
443 const auto clientOut =
444 static_cast<float **>(alloca(numAudioOut * sizeof(float *)));
445 decltype(numSamples) len = 0;
446 auto ichans = chans;
447 auto ochans = chans;
448 auto gchans = chans;
449 unsigned indx = 0;
450 unsigned ondx = 0;
451
452 auto processor = mGroups[&track];
453
454 // Call the client until we run out of input or output channels
455 while (ichans > 0 && ochans > 0)
456 {
457 // Assume sufficient buffers of zeroes given when the stored track
458 // did not have enough channels
459 if (ichans < numAudioIn)
460 {
461 for (size_t i = 0; i < numAudioIn; i++)
462 clientIn[i] = inbuf[indx++];
463
464 // All input channels have been consumed
465 ichans = 0;
466 }
467 // Otherwise fulfill the client's needs with as many input channels as possible.
468 // After calling the client with this set, we will loop back up to process more
469 // of the input/output channels.
470 else if (ichans >= numAudioIn)
471 {
472 gchans = 0;
473 for (size_t i = 0; i < numAudioIn; i++, ichans--, gchans++)
474 {
475 clientIn[i] = inbuf[indx++];
476 }
477 }
478
479 // If we don't have enough output channels to accommodate the client's
480 // requirements, then we provide all of the output channels and fulfill
481 // the client's needs with dummy buffers. These will just get tossed.
482 if (ochans < numAudioOut)
483 {
484 for (size_t i = 0; i < numAudioOut; i++)
485 clientOut[i] = outbuf[i];
486
487 // All output channels have been consumed
488 ochans = 0;
489 }
490 // Otherwise fulfill the client's needs with as many output channels as possible.
491 // After calling the client with this set, we will loop back up to process more
492 // of the input/output channels.
493 else if (ochans >= numAudioOut)
494 {
495 for (size_t i = 0; i < numAudioOut; i++, ochans--)
496 {
497 clientOut[i] = outbuf[ondx++];
498 }
499 }
500
501 // Finally call the plugin to process the block
502 len = 0;
503 const auto blockSize = pInstance->GetBlockSize();
504 for (decltype(numSamples) block = 0; block < numSamples; block += blockSize)
505 {
506 auto cnt = std::min(numSamples - block, blockSize);
507 // Assuming we are in a processing scope, use the worker settings
508 len += pInstance->RealtimeProcess(processor,
509 mWorkerSettings, clientIn, clientOut, cnt);
510
511 for (size_t i = 0 ; i < numAudioIn; i++)
512 {
513 clientIn[i] += cnt;
514 }
515
516 for (size_t i = 0 ; i < numAudioOut; i++)
517 {
518 clientOut[i] += cnt;
519 }
520 }
521 processor++;
522 }
523}
524
526{
527 auto pInstance = mwInstance.lock();
528 bool result = pInstance && IsActive() && mLastActive &&
529 // Assuming we are in a processing scope, use the worker settings
530 pInstance->RealtimeProcessEnd(mWorkerSettings);
531
532 if (auto pAccessState = TestAccessState())
533 // Always done, regardless of activity
534 // Some dialogs require communication back from the processor so that
535 // they can update their appearance in idle time, and some plug-in
536 // libraries (like lv2) require the host program to mediate the
537 // communication
538 pAccessState->WorkerWrite();
539
540 return result;
541}
542
544{
546}
547
548bool RealtimeEffectState::IsActive() const noexcept
549{
551}
552
554{
555 // This is the main thread cleaning up a state not now used in processing
557
558 mGroups.clear();
560
561 auto pInstance = mwInstance.lock();
562 if (!pInstance)
563 return false;
564
565 auto result = pInstance->RealtimeFinalize(mMainSettings);
566 mInitialized = false;
567 return result;
568}
569
570const std::string &RealtimeEffectState::XMLTag()
571{
572 static const std::string result{"effect"};
573 return result;
574}
575
576static const auto idAttribute = "id";
577static const auto versionAttribute = "version";
578static const auto parametersAttribute = "parameters";
579static const auto parameterAttribute = "parameter";
580static const auto nameAttribute = "name";
581static const auto valueAttribute = "value";
582static constexpr auto activeAttribute = "active";
583
585 const std::string_view &tag, const AttributesList &attrs)
586{
587 if (tag == XMLTag()) {
588 mParameters.clear();
589 mPlugin = nullptr;
590 mID.clear();
591 for (auto &[attr, value] : attrs) {
592 if (attr == idAttribute) {
593 SetID(value.ToWString());
594 if (!mPlugin) {
595 // TODO - complain!!!!
596 }
597 }
598 else if (attr == versionAttribute) {
599 }
600 else if (attr == activeAttribute)
601 // Updating the EffectSettingsExtra although we haven't yet built
602 // the settings
603 mMainSettings.extra.SetActive(value.Get<bool>());
604 }
605 return true;
606 }
607 else if (tag == parametersAttribute)
608 return true;
609 else if (tag == parameterAttribute) {
610 wxString n;
611 wxString v;
612 for (auto &[attr, value] : attrs) {
613 if (attr == nameAttribute)
614 n = value.ToWString();
615 else if (attr == valueAttribute)
616 v = value.ToWString();
617 }
618 mParameters += wxString::Format(wxT("\"%s=%s\" "), n, v);
619 return true;
620 }
621 else
622 return false;
623}
624
625void RealtimeEffectState::HandleXMLEndTag(const std::string_view &tag)
626{
627 if (tag == XMLTag()) {
628 if (mPlugin && !mParameters.empty()) {
631 }
632 mParameters.clear();
633 }
634}
635
637{
638 // Tag may be for the state, or the list of parameters, or for one parameter.
639 // See the writing method below. All are handled by this
640 return this;
641}
642
644{
645 if (!mPlugin)
646 return;
647
648 xmlFile.StartTag(XMLTag());
649 const auto active = mMainSettings.extra.GetActive();
650 xmlFile.WriteAttr(activeAttribute, active);
653
654 CommandParameters cmdParms;
655 if (mPlugin->SaveSettings(mMainSettings, cmdParms)) {
657
658 wxString entryName;
659 long entryIndex;
660 bool entryKeepGoing;
661
662 entryKeepGoing = cmdParms.GetFirstEntry(entryName, entryIndex);
663 while (entryKeepGoing) {
664 wxString entryValue = cmdParms.Read(entryName, "");
665
667 xmlFile.WriteAttr(nameAttribute, entryName);
668 xmlFile.WriteAttr(valueAttribute, entryValue);
669 xmlFile.EndTag(parameterAttribute);
670
671 entryKeepGoing = cmdParms.GetNextEntry(entryName, entryIndex);
672 }
673
675 }
676
677 xmlFile.EndTag(XMLTag());
678}
679
680std::shared_ptr<EffectSettingsAccess> RealtimeEffectState::GetAccess()
681{
682 if (!GetEffect())
683 // Effect not found! Return a dummy
684 return std::make_shared<Access>();
685
686 // Only the main thread assigns to the atomic pointer, here and
687 // once only in the lifetime of the state
688 if (!GetAccessState())
689 mpAccessState.emplace(*mPlugin, *this);
690 return std::make_shared<Access>(*this);
691}
int min(int a, int b)
@ WorkerToMain
Worker thread settings replicated to main thread.
@ MainToWorker
Main thread settings replicated to the worker thread.
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
static Settings & settings()
Definition: TrackInfo.cpp:87
int id
std::vector< Attribute > AttributesList
Definition: XMLTagHandler.h:40
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.
virtual unsigned GetAudioOutCount() const =0
How many output buffers to allocate at once.
virtual unsigned GetAudioInCount() const =0
How many input buffers to allocate at once.
bool GetActive() const
void SetActive(bool value)
void SetCounter(Counter value)
EffectSettingsManager is an EffectDefinitionInterface that adds a factory function for EffectSettings...
virtual bool CopySettingsContents(const EffectSettings &src, EffectSettings &dst, SettingsCopyDirection copyDirection) const
Update one settings object from another.
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 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
static PluginID GetID(PluginProvider *provider)
Mediator of two-way inter-thread communication of changes of settings.
const EffectSettings & MainThreadCache() const
MessageBuffer< ToMainSlot > mChannelToMain
const EffectSettingsManager & mEffect
const EffectSettings & MainWriteThrough(const EffectSettings &settings)
AccessState(const EffectSettingsManager &effect, RealtimeEffectState &state)
MessageBuffer< FromMainSlot > mChannelFromMain
void MainWrite(EffectSettings &&settings)
NonInterfering< EffectSettings > mMainSettings
Updated immediately by Access::Set in the main thread.
const EffectInstanceFactory * mPlugin
Stateless effect object.
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()
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.
NonInterfering< EffectSettings > mWorkerSettings
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.
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::unordered_map< Track *, size_t > mGroups
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::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
void Process(Track &track, unsigned chans, const float *const *inbuf, float *const *outbuf, size_t numSamples)
Worker thread processes part of a batch of samples.
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
Externalized state of a plug-in.
EffectSettingsExtra extra
void swap(EffectSettings &other)
void Set(const T &other)
Allow assignment from default-aligned base type.
Definition: MemoryX.h:520
Main thread's interface to inter-thread communication of changes of settings.
void Set(EffectSettings &&settings) override
const EffectSettings & Get() override
static const EffectSettings * FlushAttempt(AccessState &state)
Access(RealtimeEffectState &state)
bool IsSameAs(const EffectSettingsAccess &other) const override
void Flush() override
Make the last Set changes "persistent" in underlying storage.
~Access() override=default
std::weak_ptr< RealtimeEffectState > mwState
Store no state here but this weak pointer, so IsSameAs isn't lying.
Reader(FromMainSlot &&slot, const EffectSettingsManager &effect, EffectSettings &settings)
FromMainSlot & operator=(FromMainSlot &&)=default
FromMainSlot & operator=(EffectSettings &&settings)
Reader(ToMainSlot &&slot, EffectSettings &settings)
ToMainSlot & operator=(ToMainSlot &&)=default
ToMainSlot & operator=(EffectAndSettings &&arg)