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 **********************************************************************/
10
11
13#include "RealtimeEffectState.h"
14
15#include <memory>
16#include "Project.h"
17#include "Track.h"
18
19#include <atomic>
20#include <wx/time.h>
21
23{
24 [](AudacityProject &project)
25 {
26 return std::make_shared<RealtimeEffectManager>(project);
27 }
28};
29
31{
32 return project.AttachedObjects::Get<RealtimeEffectManager&>(manager);
33}
34
36{
37 return Get(const_cast<AudacityProject &>(project));
38}
39
41 : mProject(project)
42{
43}
44
46{
47}
48
50{
51 return mActive;
52}
53
56{
57 // (Re)Set processor parameters
58 mChans.clear();
59 mRates.clear();
60 mGroupLeaders.clear();
61
62 // RealtimeAdd/RemoveEffect() needs to know when we're active so it can
63 // initialize newly added effects
64 mActive = true;
65
66 // Tell each state to get ready for action
67 VisitAll([&scope, sampleRate](RealtimeEffectState &state, bool) {
68 scope.mInstances.push_back(state.Initialize(sampleRate));
69 });
70
71 // Leave suspended state
72 Resume();
73}
74
77 Track &track, unsigned chans, float rate)
78{
79 auto leader = *track.GetOwner()->FindLeader(&track);
80 // This should never return a null
81 wxASSERT(leader);
82 mGroupLeaders.push_back(leader);
83 mChans.insert({leader, chans});
84 mRates.insert({leader, rate});
85
86 VisitGroup(*leader,
87 [&](RealtimeEffectState & state, bool) {
88 scope.mInstances.push_back(state.AddTrack(*leader, chans, rate));
89 }
90 );
91}
92
94{
95 // Reenter suspended state
96 Suspend();
97
98 // Assume it is now safe to clean up
99 mLatency = std::chrono::microseconds(0);
100
101 VisitAll([](RealtimeEffectState &state, bool){ state.Finalize(); });
102
103 // Reset processor parameters
104 mGroupLeaders.clear();
105 mChans.clear();
106 mRates.clear();
107
108 // No longer active
109 mActive = false;
110}
111
113{
114 // Already suspended...bail
115 if (GetSuspended())
116 return;
117
118 // Show that we aren't going to be doing anything
119 // (set atomically, before next ProcessingScope)
120 SetSuspended(true);
121}
122
124{
125 // Already running...bail
126 if (!GetSuspended())
127 return;
128
129 // Get ready for more action
130 // (set atomically, before next ProcessingScope)
131 SetSuspended(false);
132}
133
134//
135// This will be called in a different thread than the main GUI thread.
136//
138{
139 // Can be suspended because of the audio stream being paused or because effects
140 // have been suspended.
141 VisitAll([suspended](RealtimeEffectState &state, bool listIsActive){
142 state.ProcessStart(!suspended && listIsActive);
143 });
144}
145
146//
147
148// This will be called in a thread other than the main GUI thread.
149//
150size_t RealtimeEffectManager::Process(bool suspended, Track &track,
151 float *const *buffers, float *const *scratch,
152 size_t numSamples)
153{
154 // Can be suspended because of the audio stream being paused or because effects
155 // have been suspended, so allow the samples to pass as-is.
156 if (suspended)
157 return numSamples;
158
159 auto chans = mChans[&track];
160
161 // Remember when we started so we can calculate the amount of latency we
162 // are introducing
163 auto start = std::chrono::steady_clock::now();
164
165 // Allocate the in and out buffer arrays
166 const auto ibuf =
167 static_cast<float **>(alloca(chans * sizeof(float *)));
168 const auto obuf =
169 static_cast<float **>(alloca(chans * sizeof(float *)));
170
171 // And populate the input with the buffers we've been given while allocating
172 // NEW output buffers
173 for (unsigned int i = 0; i < chans; i++)
174 {
175 ibuf[i] = buffers[i];
176 obuf[i] = scratch[i];
177 }
178
179 // Now call each effect in the chain while swapping buffer pointers to feed the
180 // output of one effect as the input to the next effect
181 // Tracks how many processors were called
182 size_t called = 0;
183 VisitGroup(track,
184 [&](RealtimeEffectState &state, bool listIsActive)
185 {
186 if (!(listIsActive && state.IsActive()))
187 return;
188
189 state.Process(track, chans, ibuf, obuf, scratch[chans], numSamples);
190 for (auto i = 0; i < chans; ++i)
191 std::swap(ibuf[i], obuf[i]);
192 called++;
193 }
194 );
195
196 // Once we're done, we might wind up with the last effect storing its results
197 // in the temporary buffers. If that's the case, we need to copy it over to
198 // the caller's buffers. This happens when the number of effects processed
199 // is odd.
200 if (called & 1)
201 for (unsigned int i = 0; i < chans; i++)
202 memcpy(buffers[i], ibuf[i], numSamples * sizeof(float));
203
204 // Remember the latency
205 auto end = std::chrono::steady_clock::now();
206 mLatency = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
207
208 //
209 // This is wrong...needs to handle tails
210 //
211 return numSamples;
212}
213
214//
215// This will be called in a different thread than the main GUI thread.
216//
217void RealtimeEffectManager::ProcessEnd(bool suspended) noexcept
218{
219 // Can be suspended because of the audio stream being paused or because effects
220 // have been suspended.
221 VisitAll([suspended](RealtimeEffectState &state, bool listIsActive){
222 state.ProcessEnd(!suspended && listIsActive);
223 });
224}
225
228 : mpManager{ pManager }
229{
230 if (mpManager) {
231 // Paralleling VisitAll
233 // And all track lists
234 for (auto leader : mpManager->mGroupLeaders)
236 }
237}
238
240 : mpManager{ other.mpManager }
241{
242 other.mpManager = nullptr;
243}
244
247{
248 if (this != &other) {
249 Reset();
250 mpManager = other.mpManager;
251 other.mpManager = nullptr;
252 }
253 return *this;
254}
255
257{
258 if (mpManager) {
259 // Paralleling VisitAll
260 RealtimeEffectList::Get(mpManager->mProject).GetLock().unlock();
261 // And all track lists
262 for (auto leader : mpManager->mGroupLeaders)
264 mpManager = nullptr;
265 }
266}
267
268std::shared_ptr<RealtimeEffectState> RealtimeEffectManager::AddState(
270 Track *pTrack, const PluginID & id)
271{
272 auto pLeader = pTrack ? *TrackList::Channels(pTrack).begin() : nullptr;
273 RealtimeEffectList &states = pLeader
274 ? RealtimeEffectList::Get(*pLeader)
276
277 std::optional<RealtimeEffects::SuspensionScope> myScope;
278 if (mActive) {
279 if (pScope)
280 myScope.emplace(*pScope, mProject.weak_from_this());
281 else
282 return nullptr;
283 }
284
285 auto pState = RealtimeEffectState::make_shared(id);
286 auto &state = *pState;
287
288 if (pScope && mActive)
289 {
290 // Adding a state while playback is in-flight
291 auto pInstance = state.Initialize(pScope->mSampleRate);
292 pScope->mInstances.push_back(pInstance);
293
294 for (auto &leader : mGroupLeaders) {
295 // Add all tracks to a per-project state, but add only the same track
296 // to a state in the per-track list
297 if (pLeader && pLeader != leader)
298 continue;
299
300 auto chans = mChans[leader];
301 auto rate = mRates[leader];
302
303 auto pInstance2 = state.AddTrack(*leader, chans, rate);
304 if (pInstance2 != pInstance)
305 pScope->mInstances.push_back(pInstance2);
306 }
307 }
308
309 // Only now add the completed state to the list, under a lock guard
310 bool added = states.AddState(pState);
311 if (!added)
312 return nullptr;
313
314 Publish({
316 pLeader ? pLeader->shared_from_this() : nullptr
317 });
318
319 return pState;
320}
321
324 Track *pTrack, const std::shared_ptr<RealtimeEffectState> &pState)
325{
326 auto pLeader = pTrack ? *TrackList::Channels(pTrack).begin() : nullptr;
327 RealtimeEffectList &states = pLeader
328 ? RealtimeEffectList::Get(*pLeader)
330
331 std::optional<RealtimeEffects::SuspensionScope> myScope;
332 if (mActive) {
333 if (pScope)
334 myScope.emplace(*pScope, mProject.weak_from_this());
335 else
336 return;
337 }
338
339 // Remove the state from processing (under the lock guard) before finalizing
340 states.RemoveState(pState);
341
342 if (mActive)
343 pState->Finalize();
344
345 Publish({
347 pLeader ? pLeader->shared_from_this() : nullptr
348 });
349}
350
351// Where is this used?
352#if 0
353auto RealtimeEffectManager::GetLatency() const -> Latency
354{
355 return mLatency; // should this be atomic?
356}
357#endif
static TransactionScope::Factory::Scope scope
wxString PluginID
Definition: EffectManager.h:30
static const AttachedProjectObjects::RegisteredFactory manager
for(int ii=0, nn=names.size();ii< nn;++ii)
declares abstract base class Track, TrackList, and iterators over TrackList
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:89
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
bool AddState(std::shared_ptr< RealtimeEffectState > pState)
void RemoveState(const std::shared_ptr< RealtimeEffectState > &pState)
std::atomic< bool > mActive
std::shared_ptr< RealtimeEffectState > AddState(RealtimeEffects::InitializationScope *pScope, Track *pTrack, const PluginID &id)
Main thread appends a global or per-track effect.
std::unordered_map< Track *, double > mRates
std::vector< Track * > mGroupLeaders
all are non-null
void VisitAll(const StateVisitor &func)
Visit the per-project states first, then all tracks from AddTrack.
RealtimeEffectManager(AudacityProject &project)
static RealtimeEffectManager & Get(AudacityProject &project)
void Initialize(RealtimeEffects::InitializationScope &scope, double sampleRate)
Main thread begins to define a set of tracks for playback.
void ProcessStart(bool suspended)
bool IsActive() const noexcept
void Finalize() noexcept
Main thread cleans up after playback.
std::unordered_map< Track *, unsigned > mChans
void ProcessEnd(bool suspended) noexcept
void AddTrack(RealtimeEffects::InitializationScope &scope, Track &track, unsigned chans, float rate)
std::chrono::microseconds Latency
void RemoveState(RealtimeEffects::InitializationScope *pScope, Track *pTrack, const std::shared_ptr< RealtimeEffectState > &pState)
Main thread removes a global or per-track effect.
size_t Process(bool suspended, Track &track, float *const *buffers, float *const *scratch, size_t numSamples)
void VisitGroup(Track &leader, const StateVisitor &func)
Visit the per-project states first, then states for leader if not null.
bool Finalize() noexcept
Main thread cleans up playback.
bool ProcessStart(bool running)
Worker thread begins a batch of samples.
std::shared_ptr< EffectInstance > AddTrack(Track &track, unsigned chans, float sampleRate)
Main thread sets up this state before adding it to lists.
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.
bool ProcessEnd(bool running)
Worker thread finishes a batch of samples.
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.
Brackets processing setup and cleanup in the main thread.
std::vector< std::shared_ptr< EffectInstance > > mInstances
Abstract base class for an object holding data associated with points on a time axis.
Definition: Track.h:225
std::shared_ptr< TrackList > GetOwner() const
Definition: Track.h:402
static auto Channels(TrackType *pTrack) -> TrackIterRange< TrackType >
Definition: Track.h:1533
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:753
AllListsLock(RealtimeEffectManager *pManager=nullptr)
AllListsLock & operator=(AllListsLock &&other)
static std::shared_ptr< RealtimeEffectState > make_shared(Args &&...args)
Definition: MemoryX.h:497