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 mRates.clear();
59 mGroupLeaders.clear();
60
61 // RealtimeAdd/RemoveEffect() needs to know when we're active so it can
62 // initialize newly added effects
63 mActive = true;
64
65 // Tell each state to get ready for action
66 VisitAll([&scope, sampleRate](RealtimeEffectState &state, bool) {
67 scope.mInstances.push_back(state.Initialize(sampleRate));
68 });
69
70 // Leave suspended state
71 SetSuspended(false);
72}
73
76 Track &track, unsigned chans, float rate)
77{
78 auto leader = *track.GetOwner()->FindLeader(&track);
79 // This should never return a null
80 wxASSERT(leader);
81 mGroupLeaders.push_back(leader);
82 mRates.insert({leader, rate});
83
84 VisitGroup(*leader,
85 [&](RealtimeEffectState & state, bool) {
86 scope.mInstances.push_back(state.AddTrack(*leader, chans, rate));
87 }
88 );
89}
90
92{
93 // Reenter suspended state
94 SetSuspended(true);
95
96 // Assume it is now safe to clean up
97 mLatency = std::chrono::microseconds(0);
98
99 VisitAll([](RealtimeEffectState &state, bool){ state.Finalize(); });
100
101 // Reset processor parameters
102 mGroupLeaders.clear();
103 mRates.clear();
104
105 // No longer active
106 mActive = false;
107}
108
109//
110// This will be called in a different thread than the main GUI thread.
111//
113{
114 // Can be suspended because of the audio stream being paused or because effects
115 // have been suspended.
116 VisitAll([suspended](RealtimeEffectState &state, bool listIsActive){
117 state.ProcessStart(!suspended && listIsActive);
118 });
119}
120
121//
122
123// This will be called in a thread other than the main GUI thread.
124//
125size_t RealtimeEffectManager::Process(bool suspended, Track &track,
126 float *const *buffers, float *const *scratch, unsigned nBuffers,
127 size_t numSamples)
128{
129 // Can be suspended because of the audio stream being paused or because effects
130 // have been suspended, so allow the samples to pass as-is.
131 if (suspended)
132 return 0;
133
134 // Remember when we started so we can calculate the amount of latency we
135 // are introducing
136 auto start = std::chrono::steady_clock::now();
137
138 // Allocate the in and out buffer arrays
139 const auto ibuf =
140 static_cast<float **>(alloca(nBuffers * sizeof(float *)));
141 const auto obuf =
142 static_cast<float **>(alloca(nBuffers * sizeof(float *)));
143
144 // And populate the input with the buffers we've been given while allocating
145 // NEW output buffers
146 for (unsigned int i = 0; i < nBuffers; i++) {
147 ibuf[i] = buffers[i];
148 obuf[i] = scratch[i];
149 }
150
151 // Now call each effect in the chain while swapping buffer pointers to feed the
152 // output of one effect as the input to the next effect
153 // Tracks how many processors were called
154 size_t called = 0;
155 size_t discardable = 0;
156 VisitGroup(track,
157 [&](RealtimeEffectState &state, bool)
158 {
159 discardable += state.Process(track, nBuffers, ibuf, obuf, numSamples);
160 for (auto i = 0; i < nBuffers; ++i)
161 std::swap(ibuf[i], obuf[i]);
162 called++;
163 }
164 );
165
166 // Once we're done, we might wind up with the last effect storing its results
167 // in the temporary buffers. If that's the case, we need to copy it over to
168 // the caller's buffers. This happens when the number of effects processed
169 // is odd.
170 if (called & 1)
171 for (unsigned int i = 0; i < nBuffers; i++)
172 memcpy(buffers[i], ibuf[i], numSamples * sizeof(float));
173
174 // Remember the latency
175 auto end = std::chrono::steady_clock::now();
176 mLatency = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
177
178 //
179 // This is wrong...needs to handle tails
180 //
181 return discardable;
182}
183
184//
185// This will be called in a different thread than the main GUI thread.
186//
187void RealtimeEffectManager::ProcessEnd(bool suspended) noexcept
188{
189 // Can be suspended because of the audio stream being paused or because effects
190 // have been suspended.
191 VisitAll([suspended](RealtimeEffectState &state, bool){
192 state.ProcessEnd();
193 });
194}
195
198 : mpManager{ pManager }
199{
200 if (mpManager) {
201 // Paralleling VisitAll
203 // And all track lists
204 for (auto leader : mpManager->mGroupLeaders)
206 }
207}
208
210 : mpManager{ other.mpManager }
211{
212 other.mpManager = nullptr;
213}
214
217{
218 if (this != &other) {
219 Reset();
220 mpManager = other.mpManager;
221 other.mpManager = nullptr;
222 }
223 return *this;
224}
225
227{
228 if (mpManager) {
229 // Paralleling VisitAll
230 RealtimeEffectList::Get(mpManager->mProject).GetLock().unlock();
231 // And all track lists
232 for (auto leader : mpManager->mGroupLeaders)
234 mpManager = nullptr;
235 }
236}
237
238std::shared_ptr<RealtimeEffectState>
241 Track *pLeader, const PluginID &id)
242{
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 (auto &leader : mGroupLeaders) {
252 // Add all tracks to a per-project state, but add only the same track
253 // to a state in the per-track list
254 if (pLeader && pLeader != leader)
255 continue;
256 auto rate = mRates[leader];
257 auto pInstance2 =
258 state.AddTrack(*leader, pScope->mNumPlaybackChannels, rate);
259 if (pInstance2 != pInstance)
260 pScope->mInstances.push_back(pInstance2);
261 }
262 }
263 return pNewState;
264}
265
266namespace {
267std::pair<Track *, RealtimeEffectList &>
268FindStates(AudacityProject &project, Track *pTrack) {
269 auto pLeader = pTrack ? *TrackList::Channels(pTrack).begin() : nullptr;
270 return { pLeader,
271 pLeader
272 ? RealtimeEffectList::Get(*pLeader)
273 : RealtimeEffectList::Get(project)
274 };
275}
276}
277
278std::shared_ptr<RealtimeEffectState> RealtimeEffectManager::AddState(
280 Track *pTrack, const PluginID & id)
281{
282 auto [pLeader, states] = FindStates(mProject, pTrack);
283 auto pState = MakeNewState(pScope, pTrack, id);
284 if (!pState)
285 return nullptr;
286
287 // Only now add the completed state to the list, under a lock guard
288 if (!states.AddState(pState))
289 return nullptr;
290 Publish({
292 pLeader ? pLeader->shared_from_this() : nullptr
293 });
294 return pState;
295}
296
297std::shared_ptr<RealtimeEffectState> RealtimeEffectManager::ReplaceState(
299 Track *pTrack, size_t index, const PluginID & id)
300{
301 auto [pLeader, states] = FindStates(mProject, pTrack);
302 auto pOldState = states.GetStateAt(index);
303 if (!pOldState)
304 return nullptr;
305 auto pNewState = MakeNewState(pScope, pTrack, id);
306 if (!pNewState)
307 return nullptr;
308
309 // Only now swap the completed state into the list, under a lock guard
310 if (!states.ReplaceState(index, pNewState))
311 return nullptr;
312 if (mActive)
313 pOldState->Finalize();
314 Publish({
316 pLeader ? pLeader->shared_from_this() : nullptr
317 });
318 return pNewState;
319}
320
323 Track *pTrack, const std::shared_ptr<RealtimeEffectState> pState)
324{
325 auto [pLeader, states] = FindStates(mProject, pTrack);
326
327 // Remove the state from processing (under the lock guard) before finalizing
328 states.RemoveState(pState);
329 if (mActive)
330 pState->Finalize();
331 Publish({
333 pLeader ? pLeader->shared_from_this() : nullptr
334 });
335}
336
338 Track *pTrack, const std::shared_ptr<RealtimeEffectState> &pState) const
339{
340 auto [_, states] = FindStates(mProject, pTrack);
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 TransactionScope::Factory::Scope scope
wxString PluginID
Definition: EffectManager.h:30
#define _(s)
Definition: Internat.h:75
static const AttachedProjectObjects::RegisteredFactory manager
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
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
std::optional< size_t > FindState(Track *pTrack, const std::shared_ptr< RealtimeEffectState > &pState) const
Report the position of a state in the global or a per-track list.
void VisitAll(const StateVisitor &func)
Visit the per-project states first, then all tracks from AddTrack.
RealtimeEffectManager(AudacityProject &project)
size_t Process(bool suspended, Track &track, float *const *buffers, float *const *scratch, unsigned nBuffers, size_t numSamples)
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 tracks for playback.
void ProcessStart(bool suspended)
bool IsActive() const noexcept
To be called only from main thread.
void Finalize() noexcept
Main thread cleans up after playback.
std::shared_ptr< RealtimeEffectState > ReplaceState(RealtimeEffects::InitializationScope *pScope, Track *pTrack, size_t index, const PluginID &id)
Main thread replaces a global or per-track effect.
void RemoveState(RealtimeEffects::InitializationScope *pScope, Track *pTrack, std::shared_ptr< RealtimeEffectState > pState)
Main thread removes a global or per-track effect.
void ProcessEnd(bool suspended) noexcept
void AddTrack(RealtimeEffects::InitializationScope &scope, Track &track, unsigned chans, float rate)
std::shared_ptr< RealtimeEffectState > MakeNewState(RealtimeEffects::InitializationScope *pScope, Track *pLeader, const PluginID &id)
std::chrono::microseconds Latency
void VisitGroup(Track &leader, const StateVisitor &func)
Visit the per-project states first, then states for leader if not null.
size_t Process(Track &track, unsigned chans, const float *const *inbuf, float *const *outbuf, size_t numSamples)
Worker thread processes part of a batch of samples.
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 > AddTrack(Track &track, unsigned chans, float sampleRate)
Main thread sets up this state before adding it to lists.
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
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:409
static auto Channels(TrackType *pTrack) -> TrackIterRange< TrackType >
Definition: Track.h:1541
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
std::pair< Track *, RealtimeEffectList & > FindStates(AudacityProject &project, Track *pTrack)
AllListsLock(RealtimeEffectManager *pManager=nullptr)
AllListsLock & operator=(AllListsLock &&other)
static std::shared_ptr< RealtimeEffectState > make_shared(Args &&...args)
Definition: MemoryX.h:505