Audacity 3.2.0
AudioUnitInstance.cpp
Go to the documentation of this file.
1/*!********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 @file AudioUnitInstance.cpp
6
7 Dominic Mazzoni
8 Leland Lucius
9
10 Paul Licameli split from AudioUnitEffect.cpp
11
12**********************************************************************/
13
14#if USE_AUDIO_UNITS
15#include "AudioUnitInstance.h"
16
17#include <AudioToolbox/AudioUnitUtilities.h>
18#include "AudacityException.h"
19#include <wx/log.h>
20
21namespace {
24 : settings{ std::move(settings) }
25 {}
27 std::unique_ptr<Message> Clone() const override;
28 void Assign(Message &&src) override;
29 void Merge(Message &&src) override;
30
32};
33}
34
35AudioUnitMessage::~AudioUnitMessage() = default;
36
37auto AudioUnitMessage::Clone() const -> std::unique_ptr<Message>
38{
39 return std::make_unique<AudioUnitMessage>(*this);
40}
41
42void AudioUnitMessage::Assign(Message &&src)
43{
44 auto &dstSettings = this->settings;
45 auto &srcSettings = static_cast<AudioUnitMessage&>(src).settings;
47 std::move(srcSettings), dstSettings, false);
48}
49
50void AudioUnitMessage::Merge(Message &&src)
51{
52 auto &dstSettings = this->settings;
53 auto &srcSettings = static_cast<AudioUnitMessage&>(src).settings;
55 std::move(srcSettings), dstSettings, true);
56}
57
59 AudioComponent component, Parameters &parameters,
60 const wxString &identifier,
61 unsigned audioIns, unsigned audioOuts, bool useLatency
62) : PerTrackEffect::Instance{ effect }
63 , AudioUnitWrapper{ component, &parameters }
64 , mIdentifier{ identifier }
65 , mBlockSize{ InitialBlockSize() }
66 , mUseLatency{ useLatency }
67{
68 mAudioIns = audioIns;
69 mAudioOuts = audioOuts;
71}
72
74{
75 // Retrieve the desired number of frames per slice
76 UInt32 blockSize{};
78 kAudioUnitProperty_MaximumFramesPerSlice, blockSize))
79 // Call failed? Then supply a default:
80 return 512;
81 else
82 return blockSize;
83}
84
86{
87 // Ignore the argument! Too-large block sizes won't work
88 return mBlockSize;
89}
90
92{
93 return mBlockSize;
94}
95
97{
98 return mAudioIns;
99}
100
102{
103 return mAudioOuts;
104}
105
107 const EffectSettings &, double sampleRate) const -> SampleCount
108{
109 // Retrieve the latency (can be updated via an event)
110 if (mUseLatency) {
111 Float64 latency = 0.0;
112 if (!GetFixedSizeProperty(kAudioUnitProperty_Latency, latency))
113 return latency * sampleRate;
114 }
115 return 0;
116}
117
118#if 0
120{
121 // Retrieve the tail time
122 Float64 tailTime = 0.0;
123 if (!GetFixedSizeProperty(kAudioUnitProperty_TailTime, tailTime))
124 return tailTime * mSampleRate;
125 return 0;
126}
127#endif
128
130 double sampleRate, ChannelNames chanMap)
131{
133 return false;
134
135 mInputList =
136 PackedArray::AllocateCount<AudioBufferList>(mAudioIns)(mAudioIns);
138 PackedArray::AllocateCount<AudioBufferList>(mAudioOuts)(mAudioOuts);
139
140 memset(&mTimeStamp, 0, sizeof(AudioTimeStamp));
141 mTimeStamp.mSampleTime = 0; // This is a double-precision number that should
142 // accumulate the number of frames processed so far
143 mTimeStamp.mFlags = kAudioTimeStampSampleTimeValid;
144
145 mInitialization.reset();
146 // Redo this with the correct sample rate, not the arbirary 44100 that the
147 // effect used
148 auto ins = mAudioIns;
149 auto outs = mAudioOuts;
150 if (!SetRateAndChannels(sampleRate, mIdentifier))
151 return false;
152 if (AudioUnitInitialize(mUnit.get())) {
153 wxLogError("Couldn't initialize audio unit\n");
154 return false;
155 }
156 if (ins != mAudioIns || outs != mAudioOuts) {
157 // A change of channels with changing rate? This is unexpected!
158 ins = mAudioIns;
159 outs = mAudioOuts;
160 return false;
161 }
162 mInitialization.reset(mUnit.get());
163
164 if (SetProperty(kAudioUnitProperty_SetRenderCallback,
166 kAudioUnitScope_Input)) {
167 wxLogError("Setting input render callback failed.\n");
168 return false;
169 }
170
171 if (AudioUnitReset(mUnit.get(), kAudioUnitScope_Global, 0))
172 return false;
173
174 if (!BypassEffect(false))
175 // Ignore bad return value. Some (like Xfer OTT) give a bad status.
176 ;
177
178 return true;
179}
180
182{
183 mOutputList.reset();
184 mInputList.reset();
185 return true;
186}
187
189 const float *const *inBlock, float *const *outBlock, size_t blockLen)
190{
191 // mAudioIns and mAudioOuts don't change after plugin initialization,
192 // so ProcessInitialize() made sufficient allocations
193 assert(Count(mInputList) >= mAudioIns);
194 for (size_t i = 0; i < mAudioIns; ++i)
195 mInputList[i] = { 1, static_cast<UInt32>(sizeof(float) * blockLen),
196 const_cast<float*>(inBlock[i]) };
197
198 // See previous comment
199 assert(Count(mOutputList) >= mAudioOuts);
200 for (size_t i = 0; i < mAudioOuts; ++i)
201 mOutputList[i] = { 1, static_cast<UInt32>(sizeof(float) * blockLen),
202 outBlock[i] };
203
204 AudioUnitRenderActionFlags flags = 0;
205 OSStatus result;
206
207 result = AudioUnitRender(mUnit.get(),
208 &flags,
209 &mTimeStamp,
210 0,
211 blockLen,
212 mOutputList.get());
213 if (result != noErr) {
214 wxLogError("Render failed: %d %4.4s\n",
215 static_cast<int>(result), reinterpret_cast<char *>(&result));
216 return 0;
217 }
218
219 mTimeStamp.mSampleTime += blockLen;
220 return blockLen;
221}
222
224 EffectSettings &settings, double sampleRate)
225{
226 return ProcessInitialize(settings, sampleRate, nullptr);
227}
228
230 EffectSettings &settings, EffectOutputs *, unsigned, float sampleRate)
231{
232 if (!mRecruited) {
233 // Assign self to the first processor
234 mRecruited = true;
235 return true;
236 }
237
238 // Assign another instance with independent state to other processors
239 auto &effect = static_cast<const PerTrackEffect&>(mProcessor);
240 auto uProcessor = std::make_unique<AudioUnitInstance>(effect,
243 uProcessor->SetBlockSize(mBlockSize);
244 if (!uProcessor->ProcessInitialize(settings, sampleRate, nullptr))
245 return false;
246 mSlaves.push_back(move(uProcessor));
247 return true;
248}
249
251{
252return GuardedCall<bool>([&]{
253 for (auto &pSlave : mSlaves)
254 pSlave->ProcessFinalize();
255 mSlaves.clear();
256 mRecruited = false;
257 return ProcessFinalize();
258});
259}
260
262{
263 if (!BypassEffect(true))
264 //return false
265 ;
266 for (auto &pSlave : mSlaves)
267 if (!pSlave->BypassEffect(true))
268 //return false
269 ;
270 return true;
271}
272
274{
275 if (!BypassEffect(false))
276 //return false
277 ;
278 for (auto &pSlave: mSlaves)
279 if (!pSlave->BypassEffect(false))
280 //return false
281 ;
282 return true;
283}
284
285auto AudioUnitInstance::MakeMessage() const -> std::unique_ptr<Message>
286{
287 // Like AudioUnitEffect::MakeSettings, except it only allocates map entries
288 // containing nullopt
290 FetchSettings(settings, false);
291 return std::make_unique<AudioUnitMessage>(std::move(settings));
292}
293
295MakeMessage(AudioUnitParameterID id, AudioUnitParameterValue value) const
296 -> std::unique_ptr<Message>
297{
299 settings.values[id].emplace(wxString{}, value);
300 return std::make_unique<AudioUnitMessage>(std::move(settings));
301}
302
304{
305 return true;
306}
307
309{
310 if (!package.pMessage)
311 return true;
312 auto &values = static_cast<AudioUnitMessage*>(package.pMessage)
313 ->settings.values;
314 auto storeSettings = [&](AudioUnitInstance &instance){
315 for (auto &[ID, oPair] : values)
316 if (oPair.has_value()) {
317 auto value = oPair->second;
318 if (AudioUnitSetParameter(mUnit.get(), ID,
319 kAudioUnitScope_Global, 0, value, 0)) {
320 // Probably failed because of an invalid parameter when
321 // a plug-in is in a certain mode that doesn't contain
322 // the parameter. Ignore the failure
323 }
324 }
325 };
326 storeSettings(*this);
327 for (auto &pSlave : mSlaves)
328 storeSettings(*pSlave);
329
330 // Consume the settings change so we don't repeat setting of parameters
331 // until more inter-thread messages arrive
332 for (auto &[_, oPair] : values)
333 oPair.reset();
334
335 return true;
336}
337
338size_t
340 const float *const *inbuf, float *const *outbuf, size_t numSamples)
341{
342 wxASSERT(numSamples <= mBlockSize);
343 // Interpret the group number consistently with RealtimeAddProcessor
344 if (!mRecruited)
345 return 0;
346 decltype(this) pSlave{};
347 if (group == 0)
348 pSlave = this;
349 else if (--group < mSlaves.size())
350 pSlave = mSlaves[group].get();
351 if (pSlave)
352 return pSlave->ProcessBlock(settings, inbuf, outbuf, numSamples);
353 else
354 return 0;
355}
356
358{
359 return true;
360}
361
363 AudioUnitRenderActionFlags *inActionFlags,
364 const AudioTimeStamp *inTimeStamp,
365 UInt32 inBusNumber, UInt32 inNumFrames, AudioBufferList *ioData)
366{
367 size_t i = 0;
368 auto size = std::min<size_t>(ioData->mNumberBuffers, Count(mInputList));
369 for (; i < size; ++i)
370 ioData->mBuffers[i].mData = mInputList[i].mData;
371 // Some defensive code here just in case SDK requests from us an unexpectedly
372 // large number of buffers:
373 for (; i < ioData->mNumberBuffers; ++i)
374 ioData->mBuffers[i].mData = nullptr;
375 return 0;
376}
377
378// static
379OSStatus AudioUnitInstance::RenderCallback(void *inRefCon,
380 AudioUnitRenderActionFlags *inActionFlags,
381 const AudioTimeStamp *inTimeStamp,
382 UInt32 inBusNumber, UInt32 inNumFrames, AudioBufferList *ioData)
383{
384 return static_cast<AudioUnitInstance*>(inRefCon)->Render(inActionFlags,
385 inTimeStamp, inBusNumber, inNumFrames, ioData);
386}
387
388void AudioUnitInstance::EventListener(const AudioUnitEvent *inEvent,
389 AudioUnitParameterValue inParameterValue)
390{
391 // Handle property changes
392 if (inEvent->mEventType == kAudioUnitEvent_PropertyChange) {
393 // Handle latency changes
394 if (inEvent->mArgument.mProperty.mPropertyID ==
395 kAudioUnitProperty_Latency) {
396 // Do what?
397 }
398 return;
399 }
400
401 if (inEvent->mEventType != kAudioUnitEvent_ParameterValueChange)
402 return;
403
404 // Only parameter changes at this point
405 const auto parameterStorer = [inParameterValue,
406 ID = inEvent->mArgument.mParameter.mParameterID
407 ](AudioUnit pUnit){
408 AudioUnitSetParameter(pUnit, ID,
409 kAudioUnitScope_Global, 0, inParameterValue, 0);
410 };
411
412 // Save the parameter change in the instance, so it can be
413 // fetched into Settings, used to initialize any new slave's state
414 // This is like StoreSettings but for just one setting
415 parameterStorer(GetAudioUnit());
416
417 // Propagate the parameter
418 for (auto &worker : mSlaves)
419 parameterStorer(worker->GetAudioUnit());
420}
421
423{
424 UInt32 value = (bypass ? 1 : 0);
425 if (bypass && AudioUnitReset(mUnit.get(), kAudioUnitScope_Global, 0))
426 return false;
427 return !SetProperty(kAudioUnitProperty_BypassEffect, value);
428}
429
430#endif
Declare abstract class AudacityException, some often-used subclasses, and GuardedCall.
ChannelName
const wxChar * values
#define _(s)
Definition: Internat.h:75
for(int ii=0, nn=names.size();ii< nn;++ii)
static Settings & settings()
Definition: TrackInfo.cpp:87
int id
std::vector< std::unique_ptr< AudioUnitInstance > > mSlaves
bool mRecruited
Whether the master instance is now allocated to a group number.
size_t SetBlockSize(size_t maxBlockSize) override
PackedArray::Ptr< AudioBufferList > mInputList
AudioUnitCleanup< AudioUnit, AudioUnitUninitialize > mInitialization
size_t RealtimeProcess(size_t group, EffectSettings &settings, const float *const *inbuf, float *const *outbuf, size_t numSamples) override
bool RealtimeInitialize(EffectSettings &settings, double sampleRate) override
bool RealtimeSuspend() override
bool RealtimeFinalize(EffectSettings &settings) noexcept override
const size_t mBlockSize
bool RealtimeResume() override
std::unique_ptr< Message > MakeMessage() const override
Called on the main thread, in which the result may be cloned.
bool ProcessInitialize(EffectSettings &settings, double sampleRate, ChannelNames chanMap) override
bool RealtimeProcessEnd(EffectSettings &settings) noexcept override
settings can be updated to let a dialog change appearance at idle
AudioTimeStamp mTimeStamp
bool UsesMessages() const noexcept override
OSStatus Render(AudioUnitRenderActionFlags *inActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumFrames, AudioBufferList *ioData)
const wxString & mIdentifier
void EventListener(const AudioUnitEvent *inEvent, AudioUnitParameterValue inParameterValue)
bool ProcessFinalize() noexcept override
bool BypassEffect(bool bypass)
bool RealtimeAddProcessor(EffectSettings &settings, EffectOutputs *pOutputs, unsigned numChannels, float sampleRate) override
PackedArray::Ptr< AudioBufferList > mOutputList
bool RealtimeProcessStart(MessagePackage &package) override
settings are possibly changed, since last call, by an asynchronous dialog
AudioUnitInstance(const PerTrackEffect &effect, AudioComponent component, Parameters &parameters, const wxString &identifier, unsigned audioIns, unsigned audioOuts, bool useLatency)
size_t InitialBlockSize() const
SampleCount GetLatency(const EffectSettings &settings, double sampleRate) const override
static OSStatus RenderCallback(void *inRefCon, AudioUnitRenderActionFlags *inActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumFrames, AudioBufferList *ioData)
size_t GetBlockSize() const override
size_t ProcessBlock(EffectSettings &settings, const float *const *inBlock, float *const *outBlock, size_t blockLen) override
Called for destructive effect computation.
unsigned GetAudioOutCount() const override
How many output buffers to allocate at once.
unsigned GetAudioInCount() const override
How many input buffers to allocate at once.
virtual size_t GetTailSize() const
uint64_t SampleCount
Hold values to send to effect output meters.
Type of messages to send from main thread to processing.
const PerTrackEffect & mProcessor
Base class for many of the effects in Audacity.
OSStatus GetFixedSizeProperty(AudioUnit unit, AudioUnitPropertyID inID, T &property, AudioUnitScope inScope=kAudioUnitScope_Global, AudioUnitElement inElement=0)
size_t Count(const Ptr< Type, BaseDeleter > &p)
Find out how many elements were allocated with a Ptr.
Definition: PackedArray.h:143
TranslatableString Message(unsigned trackCount)
STL namespace.
Common base class for AudioUnitEffect and its Instance.
Parameters & mParameters
bool StoreSettings(const EffectDefinitionInterface &effect, const AudioUnitEffectSettings &settings) const
OSStatus GetFixedSizeProperty(AudioUnitPropertyID inID, T &property, AudioUnitScope inScope=kAudioUnitScope_Global, AudioUnitElement inElement=0) const
static bool MoveSettingsContents(AudioUnitEffectSettings &&src, AudioUnitEffectSettings &dst, bool merge)
Copy, then clear the optionals in src.
AudioUnit GetAudioUnit() const
bool SetRateAndChannels(double sampleRate, const wxString &identifier)
AudioUnitCleanup< AudioUnit, AudioComponentInstanceDispose > mUnit
OSStatus SetProperty(AudioUnitPropertyID inID, const T &property, AudioUnitScope inScope=kAudioUnitScope_Global, AudioUnitElement inElement=0) const
static AudioUnitEffectSettings & GetSettings(EffectSettings &settings)
const AudioComponent mComponent
bool FetchSettings(AudioUnitEffectSettings &settings, bool fetchValues, bool fetchPreset=false) const
May allocate memory, so should be called only in the main thread.
Externalized state of a plug-in.