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 unsigned numPlaybackChannels,
56 double sampleRate)
57{
58 // (Re)Set processor parameters
59 mRates.clear();
60 mGroups.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
68 scope.mInstances.push_back(state.Initialize(sampleRate));
69 });
70
71 // Leave suspended state
72 SetSuspended(false);
73
74 VisitGroup(MasterGroup, [&](RealtimeEffectState& state, bool) {
75 scope.mInstances.push_back(state.AddGroup(MasterGroup, numPlaybackChannels, sampleRate));
76 });
77}
78
81 const ChannelGroup &group, unsigned chans, float rate)
82{
83 mGroups.push_back(&group);
84 mRates.insert({&group, rate});
85
86 VisitGroup(&group,
87 [&](RealtimeEffectState & state, bool) {
88 scope.mInstances.push_back(state.AddGroup(&group, chans, rate));
89 }
90 );
91}
92
94{
95 // Reenter suspended state
96 SetSuspended(true);
97
98 VisitAll([](RealtimeEffectState &state, bool){ state.Finalize(); });
99
100 // Reset processor parameters
101 mGroups.clear();
102 mRates.clear();
103
104 // No longer active
105 mActive = false;
106}
107
108//
109// This will be called in a different thread than the main GUI thread.
110//
112{
113 // Can be suspended because of the audio stream being paused or because
114 // effects have been suspended.
115 VisitAll([suspended](RealtimeEffectState &state, bool listIsActive){
116 state.ProcessStart(!suspended && listIsActive);
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 // Allocate the in and out buffer arrays
133 const auto ibuf =
134 static_cast<float **>(alloca(nBuffers * sizeof(float *)));
135 const auto obuf =
136 static_cast<float **>(alloca(nBuffers * sizeof(float *)));
137
138 // And populate the input with the buffers we've been given while allocating
139 // NEW output buffers
140 for (unsigned int i = 0; i < nBuffers; i++) {
141 ibuf[i] = buffers[i];
142 obuf[i] = scratch[i];
143 }
144
145 // Now call each effect in the chain while swapping buffer pointers to feed
146 // the output of one effect as the input to the next effect
147 // Tracks how many processors were called
148 size_t called = 0;
149 size_t discardable = 0;
150 VisitGroup(group,
151 [&](RealtimeEffectState &state, bool)
152 {
153 discardable +=
154 state.Process(group, nBuffers, ibuf, obuf, dummy, numSamples);
155 for (auto i = 0; i < nBuffers; ++i)
156 std::swap(ibuf[i], obuf[i]);
157 called++;
158 }
159 );
160
161 // Once we're done, we might wind up with the last effect storing its results
162 // in the temporary buffers. If that's the case, we need to copy it over to
163 // the caller's buffers. This happens when the number of effects processed
164 // is odd.
165 if (called & 1)
166 for (unsigned int i = 0; i < nBuffers; i++)
167 memcpy(buffers[i], ibuf[i], numSamples * sizeof(float));
168
169 //
170 // This is wrong...needs to handle tails
171 //
172 return discardable;
173}
174
175//
176// This will be called in a different thread than the main GUI thread.
177//
178void RealtimeEffectManager::ProcessEnd(bool suspended) noexcept
179{
180 // Can be suspended because of the audio stream being paused or because
181 // effects have been suspended.
182 VisitAll([suspended](RealtimeEffectState &state, bool){
183 state.ProcessEnd();
184 });
185}
186
187std::shared_ptr<RealtimeEffectState>
190 ChannelGroup *pGroup, const PluginID &id)
191{
192 if (!pScope && mActive)
193 return nullptr;
194 auto pNewState = RealtimeEffectState::make_shared(id);
195 auto &state = *pNewState;
196 if (pScope && mActive) {
197 // Adding a state while playback is in-flight
198 auto pInstance = state.Initialize(pScope->mSampleRate);
199 pScope->mInstances.push_back(pInstance);
200
201 if(pGroup == MasterGroup)
202 {
203 auto pInstance2 = state.AddGroup(MasterGroup, pScope->mNumPlaybackChannels, pScope->mSampleRate);
204 if(pInstance2 != pInstance)
205 pScope->mInstances.push_back(pInstance2);
206 }
207 else
208 {
209 for (const auto group : mGroups) {
210 if (pGroup != group)
211 continue;
212 auto pInstance2 =
213 state.AddGroup(group, pScope->mNumPlaybackChannels, mRates[group]);
214 if (pInstance2 != pInstance)
215 pScope->mInstances.push_back(pInstance2);
216 }
217 }
218
219
220 }
221 return pNewState;
222}
223
224namespace {
227 return pGroup
228 ? RealtimeEffectList::Get(*pGroup)
230}
231}
232
233std::shared_ptr<RealtimeEffectState> RealtimeEffectManager::AddState(
235 ChannelGroup *pGroup, const PluginID & id)
236{
237 auto &states = FindStates(mProject, pGroup);
238 auto pState = MakeNewState(pScope, pGroup, id);
239 if (!pState)
240 return nullptr;
241
242 // Only now add the completed state to the list, under a lock guard
243 if (!states.AddState(pState))
244 return nullptr;
245 Publish({
247 pGroup ? pGroup : MasterGroup
248 });
249 return pState;
250}
251
252std::shared_ptr<RealtimeEffectState> RealtimeEffectManager::ReplaceState(
254 ChannelGroup *pGroup, size_t index, const PluginID & id)
255{
256 auto &states = FindStates(mProject, pGroup);
257 auto pOldState = states.GetStateAt(index);
258 if (!pOldState)
259 return nullptr;
260 auto pNewState = MakeNewState(pScope, pGroup, id);
261 if (!pNewState)
262 return nullptr;
263
264 // Only now swap the completed state into the list, under a lock guard
265 if (!states.ReplaceState(index, pNewState))
266 return nullptr;
267 if (mActive)
268 pOldState->Finalize();
269 Publish({
271 });
272 return pNewState;
273}
274
277 ChannelGroup *pGroup,
278 const std::shared_ptr<RealtimeEffectState> pState)
279{
280 auto &states = FindStates(mProject, pGroup);
281
282 // Remove the state from processing (under the lock guard) before finalizing
283 states.RemoveState(pState);
284 if (mActive)
285 pState->Finalize();
286 Publish({
288 pGroup ? pGroup : MasterGroup
289 });
290}
291
293 ChannelGroup *pGroup,
294 const std::shared_ptr<RealtimeEffectState> &pState) const
295{
296 auto &states = FindStates(mProject, pGroup);
297 return states.FindState(pState);
298}
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)
void Initialize(RealtimeEffects::InitializationScope &scope, unsigned numPlaybackChannels, double sampleRate)
Main thread begins to define a set of groups for playback.
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)
size_t Process(bool suspended, const ChannelGroup *group, float *const *buffers, float *const *scratch, float *dummy, unsigned nBuffers, size_t numSamples)
void VisitGroup(ChannelGroup *group, const StateVisitor &func)
Visit states for group or for the master when group is null.
void ProcessStart(bool suspended)
std::unordered_map< const ChannelGroup *, double > mRates
static constexpr ChannelGroup * MasterGroup
bool IsActive() const noexcept
To be called only from main thread.
void Finalize() noexcept
Main thread cleans up after playback.
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::vector< const ChannelGroup * > mGroups
all are non-null
std::shared_ptr< EffectInstance > AddGroup(const ChannelGroup *group, unsigned chans, float sampleRate)
Main thread sets up this state before adding it to lists.
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.
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 swap(std::unique_ptr< Alg_seq > &a, std::unique_ptr< Alg_seq > &b)
Definition: NoteTrack.cpp:634
RealtimeEffectList & FindStates(AudacityProject &project, ChannelGroup *pGroup)
static CommandContext::TargetFactory::SubstituteInUnique< InteractiveOutputTargets > scope
static std::shared_ptr< RealtimeEffectState > make_shared(Args &&...args)
Definition: MemoryX.h:300