Audacity 3.2.0
RealtimeEffectManager.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 RealtimeEffectManager.cpp
6
7 Paul Licameli split from EffectManager.cpp
8
9 **********************************************************************/
11#include "RealtimeEffectState.h"
12#include "Channel.h"
13
14#include <memory>
15#include "Project.h"
16
17#include <atomic>
18#include <wx/time.h>
19
21{
23 {
24 return std::make_shared<RealtimeEffectManager>(project);
25 }
26};
27
29{
30 return project.AttachedObjects::Get<RealtimeEffectManager&>(manager);
31}
32
35{
36 return Get(const_cast<AudacityProject &>(project));
37}
38
40 : mProject(project)
41{
42}
43
45{
46}
47
49{
50 return mActive;
51}
52
55{
56 // (Re)Set processor parameters
57 mRates.clear();
58 mGroups.clear();
59
60 // RealtimeAdd/RemoveEffect() needs to know when we're active so it can
61 // initialize newly added effects
62 mActive = true;
63
64 // Tell each state to get ready for action
66 scope.mInstances.push_back(state.Initialize(sampleRate));
67 });
68
69 // Leave suspended state
70 SetSuspended(false);
71}
72
75 const ChannelGroup &group, unsigned chans, float rate)
76{
77 assert(group.IsLeader());
78 mGroups.push_back(&group);
79 mRates.insert({&group, rate});
80
81 VisitGroup(group,
82 [&](RealtimeEffectState & state, bool) {
83 scope.mInstances.push_back(state.AddGroup(group, chans, rate));
84 }
85 );
86}
87
89{
90 // Reenter suspended state
91 SetSuspended(true);
92
93 // Assume it is now safe to clean up
94 mLatency = std::chrono::microseconds(0);
95
96 VisitAll([](RealtimeEffectState &state, bool){ state.Finalize(); });
97
98 // Reset processor parameters
99 mGroups.clear();
100 mRates.clear();
101
102 // No longer active
103 mActive = false;
104}
105
106//
107// This will be called in a different thread than the main GUI thread.
108//
110{
111 // Can be suspended because of the audio stream being paused or because
112 // effects have been suspended.
113 VisitAll([suspended](RealtimeEffectState &state, bool listIsActive){
114 state.ProcessStart(!suspended && listIsActive);
115 });
116}
117
118//
119
120// This will be called in a thread other than the main GUI thread.
121//
122size_t RealtimeEffectManager::Process(bool suspended,
123 const ChannelGroup &group,
124 float *const *buffers, float *const *scratch, float *const dummy,
125 unsigned nBuffers, size_t numSamples)
126{
127 // Can be suspended because of the audio stream being paused or because
128 // effects have been suspended, so allow the samples to pass as-is.
129 if (suspended)
130 return 0;
131
132 // Remember when we started so we can calculate the amount of latency we
133 // are introducing
134 auto start = std::chrono::steady_clock::now();
135
136 // Allocate the in and out buffer arrays
137 const auto ibuf =
138 static_cast<float **>(alloca(nBuffers * sizeof(float *)));
139 const auto obuf =
140 static_cast<float **>(alloca(nBuffers * sizeof(float *)));
141
142 // And populate the input with the buffers we've been given while allocating
143 // NEW output buffers
144 for (unsigned int i = 0; i < nBuffers; i++) {
145 ibuf[i] = buffers[i];
146 obuf[i] = scratch[i];
147 }
148
149 // Now call each effect in the chain while swapping buffer pointers to feed
150 // the output of one effect as the input to the next effect
151 // Tracks how many processors were called
152 size_t called = 0;
153 size_t discardable = 0;
154 VisitGroup(group,
155 [&](RealtimeEffectState &state, bool)
156 {
157 discardable +=
158 state.Process(group, nBuffers, ibuf, obuf, dummy, numSamples);
159 for (auto i = 0; i < nBuffers; ++i)
160 std::swap(ibuf[i], obuf[i]);
161 called++;
162 }
163 );
164
165 // Once we're done, we might wind up with the last effect storing its results
166 // in the temporary buffers. If that's the case, we need to copy it over to
167 // the caller's buffers. This happens when the number of effects processed
168 // is odd.
169 if (called & 1)
170 for (unsigned int i = 0; i < nBuffers; i++)
171 memcpy(buffers[i], ibuf[i], numSamples * sizeof(float));
172
173 // Remember the latency
174 auto end = std::chrono::steady_clock::now();
175 mLatency = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
176
177 //
178 // This is wrong...needs to handle tails
179 //
180 return discardable;
181}
182
183//
184// This will be called in a different thread than the main GUI thread.
185//
186void RealtimeEffectManager::ProcessEnd(bool suspended) noexcept
187{
188 // Can be suspended because of the audio stream being paused or because
189 // effects have been suspended.
190 VisitAll([suspended](RealtimeEffectState &state, bool){
191 state.ProcessEnd();
192 });
193}
194
197 : mpManager{ pManager }
198{
199 if (mpManager) {
200 // Paralleling VisitAll
202 // And all group lists
203 for (auto group : mpManager->mGroups)
205 }
206}
207
209 : mpManager{ other.mpManager }
210{
211 other.mpManager = nullptr;
212}
213
216{
217 if (this != &other) {
218 Reset();
219 mpManager = other.mpManager;
220 other.mpManager = nullptr;
221 }
222 return *this;
223}
224
226{
227 if (mpManager) {
228 // Paralleling VisitAll
229 RealtimeEffectList::Get(mpManager->mProject).GetLock().unlock();
230 // And all group lists
231 for (auto group : mpManager->mGroups)
233 mpManager = nullptr;
234 }
235}
236
237std::shared_ptr<RealtimeEffectState>
240 ChannelGroup *pGroup, const PluginID &id)
241{
242 assert(!pGroup || pGroup->IsLeader());
243 if (!pScope && mActive)
244 return nullptr;
245 auto pNewState = RealtimeEffectState::make_shared(id);
246 auto &state = *pNewState;
247 if (pScope && mActive) {
248 // Adding a state while playback is in-flight
249 auto pInstance = state.Initialize(pScope->mSampleRate);
250 pScope->mInstances.push_back(pInstance);
251 for (const auto group : mGroups) {
252 // Add all groups to a per-project state, but add only the same
253 // group to a state in the per-group list
254 if (pGroup && pGroup != group)
255 continue;
256 auto rate = mRates[group];
257 auto pInstance2 =
258 state.AddGroup(*group, pScope->mNumPlaybackChannels, rate);
259 if (pInstance2 != pInstance)
260 pScope->mInstances.push_back(pInstance2);
261 }
262 }
263 return pNewState;
264}
265
266namespace {
269 return pGroup
270 ? RealtimeEffectList::Get(*pGroup)
272}
273}
274
275std::shared_ptr<RealtimeEffectState> RealtimeEffectManager::AddState(
277 ChannelGroup *pGroup, const PluginID & id)
278{
279 assert(!pGroup || pGroup->IsLeader());
280 auto &states = FindStates(mProject, pGroup);
281 auto pState = MakeNewState(pScope, pGroup, id);
282 if (!pState)
283 return nullptr;
284
285 // Only now add the completed state to the list, under a lock guard
286 if (!states.AddState(pState))
287 return nullptr;
288 Publish({
290 pGroup ? pGroup : nullptr
291 });
292 return pState;
293}
294
295std::shared_ptr<RealtimeEffectState> RealtimeEffectManager::ReplaceState(
297 ChannelGroup *pGroup, size_t index, const PluginID & id)
298{
299 assert(!pGroup || pGroup->IsLeader());
300 auto &states = FindStates(mProject, pGroup);
301 auto pOldState = states.GetStateAt(index);
302 if (!pOldState)
303 return nullptr;
304 auto pNewState = MakeNewState(pScope, pGroup, id);
305 if (!pNewState)
306 return nullptr;
307
308 // Only now swap the completed state into the list, under a lock guard
309 if (!states.ReplaceState(index, pNewState))
310 return nullptr;
311 if (mActive)
312 pOldState->Finalize();
313 Publish({
315 });
316 return pNewState;
317}
318
321 ChannelGroup *pGroup,
322 const std::shared_ptr<RealtimeEffectState> pState)
323{
324 auto &states = FindStates(mProject, pGroup);
325
326 // Remove the state from processing (under the lock guard) before finalizing
327 states.RemoveState(pState);
328 if (mActive)
329 pState->Finalize();
330 Publish({
332 pGroup ? pGroup : nullptr
333 });
334}
335
337 ChannelGroup *pGroup,
338 const std::shared_ptr<RealtimeEffectState> &pState) const
339{
340 auto &states = FindStates(mProject, pGroup);
341 return states.FindState(pState);
342}
343
344// Where is this used?
345#if 0
346auto RealtimeEffectManager::GetLatency() const -> Latency
347{
348 return mLatency; // should this be atomic?
349}
350#endif
static AudioUnitEffectsModule::Factory::SubstituteInUnique< AudioUnitEffect > scope
Abstract class ChannelGroup with two discrete iterable dimensions, channels and intervals; subclasses...
wxString PluginID
Definition: EffectManager.h:30
static const AttachedProjectObjects::RegisteredFactory manager
const auto project
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:90
virtual bool IsLeader() const =0
Client code makes static instance from a factory of attachments; passes it to Get or Find as a retrie...
Definition: ClientData.h:266
CallbackReturn Publish(const RealtimeEffectManagerMessage &message)
Send a message to connected callbacks.
Definition: Observer.h:207
static RealtimeEffectList & Get(AudacityProject &project)
Lock & GetLock() const
void VisitGroup(ChannelGroup &group, const StateVisitor &func)
Visit the per-project states first, then states for leader if not null.
void VisitAll(const StateVisitor &func)
Visit the per-project states first, then all groups from AddGroup.
RealtimeEffectManager(AudacityProject &project)
std::shared_ptr< RealtimeEffectState > MakeNewState(RealtimeEffects::InitializationScope *pScope, ChannelGroup *pGroup, const PluginID &id)
void SetSuspended(bool value)
To be called only from main thread.
static RealtimeEffectManager & Get(AudacityProject &project)
void Initialize(RealtimeEffects::InitializationScope &scope, double sampleRate)
Main thread begins to define a set of groups for playback.
void ProcessStart(bool suspended)
std::unordered_map< const ChannelGroup *, double > mRates
bool IsActive() const noexcept
To be called only from main thread.
void Finalize() noexcept
Main thread cleans up after playback.
size_t Process(bool suspended, const ChannelGroup &group, float *const *buffers, float *const *scratch, float *dummy, unsigned nBuffers, size_t numSamples)
void RemoveState(RealtimeEffects::InitializationScope *pScope, ChannelGroup *pGroup, std::shared_ptr< RealtimeEffectState > pState)
Main thread removes a global or per-group effect.
std::shared_ptr< RealtimeEffectState > ReplaceState(RealtimeEffects::InitializationScope *pScope, ChannelGroup *pGroup, size_t index, const PluginID &id)
Main thread replaces a global or per-group effect.
void AddGroup(RealtimeEffects::InitializationScope &scope, const ChannelGroup &group, unsigned chans, float rate)
std::optional< size_t > FindState(ChannelGroup *pGroup, const std::shared_ptr< RealtimeEffectState > &pState) const
Report the position of a state in the global or a per-group list.
std::shared_ptr< RealtimeEffectState > AddState(RealtimeEffects::InitializationScope *pScope, ChannelGroup *pGroup, const PluginID &id)
Main thread appends a global or per-group effect.
void ProcessEnd(bool suspended) noexcept
std::chrono::microseconds Latency
std::vector< const ChannelGroup * > mGroups
all are non-null
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.
std::shared_ptr< EffectInstance > AddGroup(const ChannelGroup &group, unsigned chans, float sampleRate)
Main thread sets up this state before adding it to lists.
bool ProcessEnd()
Worker thread finishes a batch of samples.
bool Finalize() noexcept
Main thread cleans up playback.
bool ProcessStart(bool running)
Worker thread begins a batch of samples.
std::shared_ptr< EffectInstance > Initialize(double rate)
Main thread sets up for playback.
Brackets processing setup and cleanup in the main thread.
std::vector< std::shared_ptr< EffectInstance > > mInstances
void unlock()
Definition: spinlock.h:33
void lock()
Definition: spinlock.h:27
auto end(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:159
void swap(std::unique_ptr< Alg_seq > &a, std::unique_ptr< Alg_seq > &b)
Definition: NoteTrack.cpp:770
RealtimeEffectList & FindStates(AudacityProject &project, ChannelGroup *pGroup)
AllListsLock(RealtimeEffectManager *pManager=nullptr)
AllListsLock & operator=(AllListsLock &&other)
static std::shared_ptr< RealtimeEffectState > make_shared(Args &&...args)
Definition: MemoryX.h:523