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 mGroups.push_back(&group);
78 mRates.insert({&group, rate});
79
80 VisitGroup(group,
81 [&](RealtimeEffectState & state, bool) {
82 scope.mInstances.push_back(state.AddGroup(group, chans, rate));
83 }
84 );
85}
86
88{
89 // Reenter suspended state
90 SetSuspended(true);
91
92 // Assume it is now safe to clean up
93 mLatency = std::chrono::microseconds(0);
94
95 VisitAll([](RealtimeEffectState &state, bool){ state.Finalize(); });
96
97 // Reset processor parameters
98 mGroups.clear();
99 mRates.clear();
100
101 // No longer active
102 mActive = false;
103}
104
105//
106// This will be called in a different thread than the main GUI thread.
107//
109{
110 // Can be suspended because of the audio stream being paused or because
111 // effects have been suspended.
112 VisitAll([suspended](RealtimeEffectState &state, bool listIsActive){
113 state.ProcessStart(!suspended && listIsActive);
114 });
115}
116
117//
118
119// This will be called in a thread other than the main GUI thread.
120//
121size_t RealtimeEffectManager::Process(bool suspended,
122 const ChannelGroup &group,
123 float *const *buffers, float *const *scratch, float *const dummy,
124 unsigned nBuffers, size_t numSamples)
125{
126 // Can be suspended because of the audio stream being paused or because
127 // effects have been suspended, so allow the samples to pass as-is.
128 if (suspended)
129 return 0;
130
131 // Remember when we started so we can calculate the amount of latency we
132 // are introducing
133 auto start = std::chrono::steady_clock::now();
134
135 // Allocate the in and out buffer arrays
136 const auto ibuf =
137 static_cast<float **>(alloca(nBuffers * sizeof(float *)));
138 const auto obuf =
139 static_cast<float **>(alloca(nBuffers * sizeof(float *)));
140
141 // And populate the input with the buffers we've been given while allocating
142 // NEW output buffers
143 for (unsigned int i = 0; i < nBuffers; i++) {
144 ibuf[i] = buffers[i];
145 obuf[i] = scratch[i];
146 }
147
148 // Now call each effect in the chain while swapping buffer pointers to feed
149 // the output of one effect as the input to the next effect
150 // Tracks how many processors were called
151 size_t called = 0;
152 size_t discardable = 0;
153 VisitGroup(group,
154 [&](RealtimeEffectState &state, bool)
155 {
156 discardable +=
157 state.Process(group, nBuffers, ibuf, obuf, dummy, numSamples);
158 for (auto i = 0; i < nBuffers; ++i)
159 std::swap(ibuf[i], obuf[i]);
160 called++;
161 }
162 );
163
164 // Once we're done, we might wind up with the last effect storing its results
165 // in the temporary buffers. If that's the case, we need to copy it over to
166 // the caller's buffers. This happens when the number of effects processed
167 // is odd.
168 if (called & 1)
169 for (unsigned int i = 0; i < nBuffers; i++)
170 memcpy(buffers[i], ibuf[i], numSamples * sizeof(float));
171
172 // Remember the latency
173 auto end = std::chrono::steady_clock::now();
174 mLatency = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
175
176 //
177 // This is wrong...needs to handle tails
178 //
179 return discardable;
180}
181
182//
183// This will be called in a different thread than the main GUI thread.
184//
185void RealtimeEffectManager::ProcessEnd(bool suspended) noexcept
186{
187 // Can be suspended because of the audio stream being paused or because
188 // effects have been suspended.
189 VisitAll([suspended](RealtimeEffectState &state, bool){
190 state.ProcessEnd();
191 });
192}
193
196 : mpManager{ pManager }
197{
198 if (mpManager) {
199 // Paralleling VisitAll
201 // And all group lists
202 for (auto group : mpManager->mGroups)
204 }
205}
206
208 : mpManager{ other.mpManager }
209{
210 other.mpManager = nullptr;
211}
212
215{
216 if (this != &other) {
217 Reset();
218 mpManager = other.mpManager;
219 other.mpManager = nullptr;
220 }
221 return *this;
222}
223
225{
226 if (mpManager) {
227 // Paralleling VisitAll
228 RealtimeEffectList::Get(mpManager->mProject).GetLock().unlock();
229 // And all group lists
230 for (auto group : mpManager->mGroups)
232 mpManager = nullptr;
233 }
234}
235
236std::shared_ptr<RealtimeEffectState>
239 ChannelGroup *pGroup, const PluginID &id)
240{
241 if (!pScope && mActive)
242 return nullptr;
243 auto pNewState = RealtimeEffectState::make_shared(id);
244 auto &state = *pNewState;
245 if (pScope && mActive) {
246 // Adding a state while playback is in-flight
247 auto pInstance = state.Initialize(pScope->mSampleRate);
248 pScope->mInstances.push_back(pInstance);
249 for (const auto group : mGroups) {
250 // Add all groups to a per-project state, but add only the same
251 // group to a state in the per-group list
252 if (pGroup && pGroup != group)
253 continue;
254 auto rate = mRates[group];
255 auto pInstance2 =
256 state.AddGroup(*group, pScope->mNumPlaybackChannels, rate);
257 if (pInstance2 != pInstance)
258 pScope->mInstances.push_back(pInstance2);
259 }
260 }
261 return pNewState;
262}
263
264namespace {
267 return pGroup
268 ? RealtimeEffectList::Get(*pGroup)
270}
271}
272
273std::shared_ptr<RealtimeEffectState> RealtimeEffectManager::AddState(
275 ChannelGroup *pGroup, const PluginID & id)
276{
277 auto &states = FindStates(mProject, pGroup);
278 auto pState = MakeNewState(pScope, pGroup, id);
279 if (!pState)
280 return nullptr;
281
282 // Only now add the completed state to the list, under a lock guard
283 if (!states.AddState(pState))
284 return nullptr;
285 Publish({
287 pGroup ? pGroup : nullptr
288 });
289 return pState;
290}
291
292std::shared_ptr<RealtimeEffectState> RealtimeEffectManager::ReplaceState(
294 ChannelGroup *pGroup, size_t index, const PluginID & id)
295{
296 auto &states = FindStates(mProject, pGroup);
297 auto pOldState = states.GetStateAt(index);
298 if (!pOldState)
299 return nullptr;
300 auto pNewState = MakeNewState(pScope, pGroup, id);
301 if (!pNewState)
302 return nullptr;
303
304 // Only now swap the completed state into the list, under a lock guard
305 if (!states.ReplaceState(index, pNewState))
306 return nullptr;
307 if (mActive)
308 pOldState->Finalize();
309 Publish({
311 });
312 return pNewState;
313}
314
317 ChannelGroup *pGroup,
318 const std::shared_ptr<RealtimeEffectState> pState)
319{
320 auto &states = FindStates(mProject, pGroup);
321
322 // Remove the state from processing (under the lock guard) before finalizing
323 states.RemoveState(pState);
324 if (mActive)
325 pState->Finalize();
326 Publish({
328 pGroup ? pGroup : nullptr
329 });
330}
331
333 ChannelGroup *pGroup,
334 const std::shared_ptr<RealtimeEffectState> &pState) const
335{
336 auto &states = FindStates(mProject, pGroup);
337 return states.FindState(pState);
338}
339
340// Where is this used?
341#if 0
342auto RealtimeEffectManager::GetLatency() const -> Latency
343{
344 return mLatency; // should this be atomic?
345}
346#endif
Abstract class ChannelGroup with two discrete iterable dimensions, channels and intervals; subclasses...
wxString PluginID
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
Client code makes static instance from a factory of attachments; passes it to Get or Find as a retrie...
Definition: ClientData.h:275
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 group.
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
void swap(std::unique_ptr< Alg_seq > &a, std::unique_ptr< Alg_seq > &b)
Definition: NoteTrack.cpp:628
RealtimeEffectList & FindStates(AudacityProject &project, ChannelGroup *pGroup)
static CommandContext::TargetFactory::SubstituteInUnique< InteractiveOutputTargets > scope
const char * end(const char *str) noexcept
Definition: StringUtils.h:106
AllListsLock(RealtimeEffectManager *pManager=nullptr)
AllListsLock & operator=(AllListsLock &&other)
static std::shared_ptr< RealtimeEffectState > make_shared(Args &&...args)
Definition: MemoryX.h:300