Audacity 3.2.0
AudioUnitWrapper.cpp
Go to the documentation of this file.
1/*!********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 @file AudioUnitWrapper.cpp
6
7 Dominic Mazzoni
8 Leland Lucius
9
10 Paul Licameli split from AudioUnitEffect.cpp
11
12**********************************************************************/
13
14
15#if USE_AUDIO_UNITS
16#include "AudioUnitWrapper.h"
17#include "ConfigInterface.h"
18#include "EffectInterface.h"
19#include "Internat.h"
20#include "ModuleManager.h"
21#include "PluginProvider.h"
22
23#include <wx/osx/core/private.h>
24#include <wx/log.h>
25
28 auto pSettings = settings.cast<AudioUnitEffectSettings>();
29 // Assume the settings object ultimately came from AudioUnitEffect's
30 // MakeSettings or copying of that
31 assert(pSettings);
32 return *pSettings;
33}
34
37 return GetSettings(const_cast<EffectSettings &>(settings));
38}
39
40//
41// When a plug-in's state is saved to the settings file (as a preset),
42// it is in binary and gets base64 encoded before storing.
43//
44// When exporting, save as XML without base64 encoding.
45//
46// The advantages of XML format is less chance of failures occurring
47// when exporting. But, it can take a bit more space per preset int
48// the Audacity settings file.
49//
50// Using binary for now. Use kCFPropertyListXMLFormat_v1_0 if XML
51// format is desired.
52//
53#define PRESET_FORMAT kCFPropertyListBinaryFormat_v1_0
54
55// Name of the settings key to use for the above value
56#define PRESET_KEY wxT("Data")
57
58// Where the presets are located
59#define PRESET_LOCAL_PATH wxT("/Library/Audio/Presets")
60#define PRESET_USER_PATH wxT("~/Library/Audio/Presets")
61
62// Uncomment to include parameter IDs in the final name. Only needed if it's
63// discovered that many effects have duplicate names. It could even be done
64// at runtime by scanning an effects parameters to determine if dups are present
65// and, if so, enable the clump and parameter IDs.
66#define USE_EXTENDED_NAMES
67
69 AudioUnit mUnit, AudioUnitParameterID parmID)
70{
71 UInt32 dataSize;
72
73 mInfo = {};
74 // Note non-default element parameter, parmID
76 kAudioUnitProperty_ParameterInfo, mInfo,
77 kAudioUnitScope_Global, parmID))
78 return;
79
80 wxString &name = mName.emplace();
81 if (mInfo.flags & kAudioUnitParameterFlag_HasCFNameString)
82 name = wxCFStringRef::AsString(mInfo.cfNameString);
83 else
84 name = wxString(mInfo.name);
85
86#if defined(USE_EXTENDED_NAMES)
87 // Parameter name may or may not be present. The modified name will be:
88 //
89 // <[ParameterName,]parmID>
90 //
91 // (where the [ ] meta-characters denote optionality)
92 // (And any of the characters < , > in ParameterName are replaced with _)
93 if (!name.empty()) {
94 name.Replace(idBeg, wxT('_'));
95 name.Replace(idSep, wxT('_'));
96 name.Replace(idEnd, wxT('_'));
97 name.Append(idSep);
98 }
99 name = wxString::Format(wxT("%c%s%x%c"), idBeg, name, parmID, idEnd);
100
101 // If the parameter has a clumpID, then the final modified name will be:
102 //
103 // <[clumpName,]clumpId><[ParameterName,]parmID>
104 //
105 // (And any of the characters < , > in clumpName are replaced with _)
106 if (mInfo.flags & kAudioUnitParameterFlag_HasClump) {
107 wxString clumpName;
109 mInfo.clumpID, kAudioUnitParameterName_Full
110 };
111
113 kAudioUnitProperty_ParameterClumpName, clumpInfo)) {
114 clumpName = wxCFStringRef::AsString(clumpInfo.outName);
115 clumpName.Replace(idBeg, wxT('_'));
116 clumpName.Replace(idSep, wxT('_'));
117 clumpName.Replace(idEnd, wxT('_'));
118 clumpName.Append(idSep);
119 }
120 name = wxString::Format(wxT("%c%s%x%c%s"),
121 idBeg, clumpName, mInfo.clumpID, idEnd, name);
122 }
123#endif
124}
125
126std::optional<AudioUnitParameterID>
128{
129 // This is not a complete validation of the format of the key
130 // Scan left for , or <
131 if (const auto rend = key.rend(), riter = std::find_if(key.rbegin(), rend,
132 [](wxChar c) { return c == idBeg || c == idSep; })
133 ; riter != rend
134 ) {
135 // Scan right for >
136 if (const auto end = key.end(), left = riter.base(), // one right of riter
137 right = std::find(left, end, idEnd)
138 ; left != right && right != end
139 ){
140 // Interpret character range as hex
141 if (long value{}
142 ; wxString{left, right}.ToLong(&value, 16))
143 return value;
144 }
145 }
146 return {};
147}
148
150 AudioUnitEffectSettings &settings, bool fetchValues, bool fetchPreset) const
151{
152 settings.mPresetNumber = {};
153 if (fetchPreset) {
154 AUPreset preset{};
155 if (!GetFixedSizeProperty(kAudioUnitProperty_PresentPreset, preset))
156 // Only want factory preset number, not a user preset (<0)
157 if (preset.presetNumber >= 0)
158 settings.mPresetNumber = { preset.presetNumber };
159 }
160
161 // Fetch values from the AudioUnit into AudioUnitEffectSettings,
162 // keeping the cache up-to-date␓ after state changes in the AudioUnit
164 [this, &settings, fetchValues]
165 (const ParameterInfo &pi, AudioUnitParameterID ID) {
166 // Always make a slot, even for parameter IDs that are known but
167 // not gettable from the instance now. Example: AUGraphicEQ when
168 // you choose 10 bands; the values for bands 10 ... 30 are undefined
169 // but the parameter IDs are known
170 auto &slot = settings.values[ID];
171 slot.reset();
172 if (fetchValues) {
173 AudioUnitParameterValue value;
174 if (!pi.mName ||
175 AudioUnitGetParameter(
176 mUnit.get(), ID, kAudioUnitScope_Global, 0, &value)) {
177 // Probably failed because of invalid parameter which can happen
178 // if a plug-in is in a certain mode that doesn't contain the
179 // parameter. In any case, just ignore it.
180 }
181 else
182 slot.emplace(settings.Intern(*pi.mName), value);
183 }
184 return true;
185 });
186 return true;
187}
188
191{
192 // This is a const member function inherited by AudioUnitEffect, though it
193 // mutates the AudioUnit object (mUnit.get()). This is necessary for the
194 // AudioUnitEffect (an EffectPlugin) to compute the "blob" of settings state
195 // for export or to save settings in the config file, which the SDK later
196 // reinterprets.
197 // So consider mUnit a mutable scratch pad object. This doesn't really make
198 // the AudioUnitEffect stateful.
199
200 // First restore factory preset if it applies
201 if (settings.mPresetNumber) {
202 // Mutate the scratch AudioUnit, don't pass settings
203 LoadFactoryPreset(effect, *settings.mPresetNumber, nullptr);
204 // Then go on to reapply some slider changes that might have been done
205 // after a change of preset
206 }
207
208 // Update parameter values in the AudioUnit from const
209 // AudioUnitEffectSettings
210 // Allow two passes; because sometimes the re-assignability of one parameter
211 // depends on first doing it for another parameter, but the other is later
212 // in the iteration. For instance, for AUGraphicEQ, parameter ID 10000
213 // (decimal) determines 10 or 31 bands, but while the effect is still set
214 // for 10, then assignment to the last 21 sliders fails.
215 for (auto pass : {0, 1}) {
217 ] (const ParameterInfo &pi, AudioUnitParameterID ID) {
218 if (pi.mName) {
219 if (auto iter = settings.values.find(ID);
220 iter != settings.values.end() && iter->second.has_value()
221 ){
222 if (AudioUnitSetParameter(mUnit.get(), ID,
223 kAudioUnitScope_Global, 0, iter->second->second, 0)) {
224 // Probably failed because of an invalid parameter when
225 // a plug-in is in a certain mode that doesn't contain
226 // the parameter.
227 }
228 }
229 else {
230 // Leave parameters that are in the AudioUnit, but not known in
231 // settings, unchanged
232 }
233 }
234 return true;
235 });
236 }
237 return true;
238}
239
241 AudioUnitEffectSettings &&src, AudioUnitEffectSettings &dst, bool merge)
242{
243 // Do an in-place rewrite of dst, avoiding allocations
244 auto &dstMap = dst.values;
245 auto dstIter = dstMap.begin(), dstEnd = dstMap.end();
246 auto &srcMap = src.values;
247 for (auto &[key, oValue] : srcMap) {
248 while (dstIter != dstEnd && dstIter->first != key)
249 ++dstIter;
250 if (dstIter == dstEnd)
251 break;
252 auto &[dstKey, dstOValue] = *dstIter;
253 assert(dstKey == key);
254 if (oValue) {
255 dstOValue.emplace(*oValue);
256 oValue.reset();
257 }
258 else if (!merge)
259 // Don't accumulate non-nulls only, but copy the nulls
260 dstOValue.reset();
261 }
262 return true;
263}
264
266{
267 AudioUnit unit{};
268 auto result = AudioComponentInstanceNew(mComponent, &unit);
269 if (!result) {
270 mUnit.reset(unit);
273 kAudioUnitProperty_ParameterList, mOwnParameters);
274 }
275
276 return (!result && unit != nullptr);
277}
278
281 const RegistryPath &group, const wxMemoryBuffer &buf) const
282{
283 size_t bufLen = buf.GetDataLen();
284 if (!bufLen)
285 return XO("Failed to decode \"%s\" preset").Format(group);
286
287 // Create a CFData object that references the decoded preset
288 const auto bufPtr = static_cast<const uint8_t *>(buf.GetData());
289 CF_ptr<CFDataRef> data{ CFDataCreateWithBytesNoCopy(kCFAllocatorDefault,
290 bufPtr, bufLen, kCFAllocatorNull)
291 };
292 if (!data)
293 return XO("Failed to convert \"%s\" preset to internal format")
294 .Format(group);
295
296 // Convert it back to a property list
297 CF_ptr<CFPropertyListRef> content{
298 CFPropertyListCreateWithData(kCFAllocatorDefault,
299 data.get(), kCFPropertyListImmutable, nullptr,
300 // TODO might retrieve more error information
301 nullptr)
302 };
303 if (!content)
304 return XO("Failed to create property list for \"%s\" preset")
305 .Format(group);
306
307 // Finally, update the properties and parameters
308 if (SetProperty(kAudioUnitProperty_ClassInfo, content.get()))
309 return XO("Failed to set class info for \"%s\" preset").Format(group);
310
311 // Repopulate the AudioUnitEffectSettings from the change of state in
312 // the AudioUnit, and include the preset
313 FetchSettings(settings, true, true);
314 return {};
315}
316
318{
319 if (!mParameters)
320 return;
321 for (const auto &ID : mParameters)
322 if (ParameterInfo pi{ mUnit.get(), ID };
323 !visitor(pi, ID))
324 break;
325}
326
328 const RegistryPath & group, EffectSettings &settings) const
329{
330 // Retrieve the preset
331 wxString parms;
332 if (!GetConfig(effect, PluginSettings::Private, group, PRESET_KEY, parms,
333 wxEmptyString)) {
334 // Commented "CurrentSettings" gets tried a lot and useless messages appear
335 // in the log
336 //wxLogError(wxT("Preset key \"%s\" not found in group \"%s\""), PRESET_KEY, group);
337 return false;
338 }
339
340 // Decode it, complementary to what SaveBlobToConfig did
341 auto error =
342 InterpretBlob(GetSettings(settings), group, wxBase64Decode(parms));
343 if (!error.empty()) {
344 wxLogError(error.Debug());
345 return false;
346 }
347
348 return true;
349}
350
352 const EffectDefinitionInterface &effect, int id, EffectSettings *pSettings)
353const
354{
355 if (pSettings) {
356 // Issue 3441: Some factory presets of some effects do not reassign all
357 // controls. So first put controls into a default state, not contaminated
358 // by previous importing or other loading of settings into this wrapper.
359 if (!LoadPreset(effect, FactoryDefaultsGroup(), *pSettings))
360 return false;
361 }
362
363 // Retrieve the list of factory presets
364 CF_ptr<CFArrayRef> array;
365 if (GetFixedSizeProperty(kAudioUnitProperty_FactoryPresets, array) ||
366 id < 0 || id >= CFArrayGetCount(array.get()))
367 return false;
368
369 // Mutate the scratch pad AudioUnit in this wrapper
370 if (SetProperty(kAudioUnitProperty_PresentPreset,
371 *static_cast<const AUPreset*>(CFArrayGetValueAtIndex(array.get(), id))))
372 return false;
373
374 if (pSettings) {
375 // Repopulate the AudioUnitEffectSettings from the change of state in
376 // the AudioUnit
377 if (!FetchSettings(GetSettings(*pSettings), true, true))
378 return false;
379 }
380
381 return true;
382}
383
384std::pair<CF_ptr<CFDataRef>, TranslatableString>
387 const wxCFStringRef &cfname, bool binary) const
388{
389 // This is a const function of AudioUnitEffect, but it mutates
390 // an AudioUnit object (mUnit.get()) to accomplish its work. But that
391 // should be the "scratchpad" unit only, not real instance state.
392
393 // Update state of the unit from settings
394 StoreSettings(effect, settings);
395
396 CF_ptr<CFDataRef> data;
397 TranslatableString message;
398
399 // Define the preset property and set it in the audio unit
400 if (SetProperty(
401 kAudioUnitProperty_PresentPreset, AudioUnitUtils::UserPreset{ cfname }))
402 message = XO("Failed to set preset name");
403
404 // Now retrieve the preset content
405 else if (CF_ptr<CFPropertyListRef> content;
406 GetFixedSizeProperty(kAudioUnitProperty_ClassInfo, content))
407 message = XO("Failed to retrieve preset content");
408
409 // And convert it to serialized XML data
410 else if (data.reset(CFPropertyListCreateData(kCFAllocatorDefault,
411 content.get(),
412 (binary ? PRESET_FORMAT : kCFPropertyListXMLFormat_v1_0), 0,
413 // TODO might retrieve more error information
414 nullptr));
415 !data)
416 message = XO("Failed to convert property list to XML data");
417
418 // Nothing to do if we don't have any data
419 else if (auto length = CFDataGetLength(data.get()); length == 0)
420 // Caller might not treat this as error, becauase data is non-null
421 message = XO("XML data is empty after conversion");
422
423 return { move(data), message };
424}
425
427 double sampleRate, const wxString &identifier)
428{
430 // Float64 mSampleRate;
432
433 // UInt32 mFormatID;
434 kAudioFormatLinearPCM,
435
436 // UInt32 mFormatFlags;
437 (kAudioFormatFlagsNativeFloatPacked |
438 kAudioFormatFlagIsNonInterleaved),
439
440 // UInt32 mBytesPerPacket;
441 sizeof(float),
442
443 // UInt32 mFramesPerPacket;
444 1,
445
446 // UInt32 mBytesPerFrame;
447 sizeof(float),
448
449 // UInt32 mChannelsPerFrame;
450 0,
451
452 // UInt32 mBitsPerChannel;
453 sizeof(float) * 8,
454 };
455
456 unsigned one = 1u;
457 const struct Info{
458 unsigned &nChannels;
459 AudioUnitScope scope;
460 const char *const msg; // used only in log messages
461 } infos[]{
462 { one, kAudioUnitScope_Global, "global" },
463 { mAudioIns, kAudioUnitScope_Input, "input" },
464 { mAudioOuts, kAudioUnitScope_Output, "output" },
465 };
466 for (const auto &[nChannels, scope, msg] : infos) {
467 if (nChannels) {
468 if (SetProperty(kAudioUnitProperty_SampleRate, sampleRate, scope)) {
469 wxLogError("%ls Didn't accept sample rate on %s\n",
470 // Exposing internal name only in logging
471 identifier.wx_str(), msg);
472 return false;
473 }
474 if (scope != kAudioUnitScope_Global) {
475 bool failed = true;
476 ++nChannels;
477 do {
478 --nChannels;
479 streamFormat.mChannelsPerFrame = nChannels;
480 failed = SetProperty(kAudioUnitProperty_StreamFormat,
481 streamFormat, scope);
482 } while(failed && nChannels > 0);
483 if (failed) {
484 wxLogError("%ls didn't accept stream format on %s\n",
485 // Exposing internal name only in logging
486 identifier.wx_str(), msg);
487 return false;
488 }
489 }
490 }
491 }
492 return true;
493}
494#endif
wxT("CloseDown"))
#define PRESET_KEY
#define PRESET_FORMAT
const TranslatableString name
Definition: Distortion.cpp:76
const RegistryPath & FactoryDefaultsGroup()
Component of a configuration key path, for default state of MakeSettings()
XO("Cut/Copy/Paste")
wxString RegistryPath
Definition: Identifier.h:218
static const AudacityProject::AttachedObjects::RegisteredFactory key
Generalized interface for discovery of plug-ins for one protocol.
EffectReverbSettings preset
Definition: Reverb.cpp:44
static Settings & settings()
Definition: TrackInfo.cpp:69
ParameterInfo(AudioUnit mUnit, AudioUnitParameterID parmID)
Make a structure holding a key for the config file and a value.
static std::optional< AudioUnitParameterID > ParseKey(const wxString &key)
Recover the parameter ID from the key, if well formed.
std::optional< wxString > mName
AudioUnitUtils::ParameterInfo mInfo
EffectDefinitionInterface is a ComponentInterface that adds some basic read-only information about ef...
Holds a msgid for the translation catalog; may also bind format arguments.
OSStatus GetFixedSizeProperty(AudioUnit unit, AudioUnitPropertyID inID, T &property, AudioUnitScope inScope=kAudioUnitScope_Global, AudioUnitElement inElement=0)
bool GetConfig(const EffectDefinitionInterface &ident, ConfigurationType type, const RegistryPath &group, const RegistryPath &key, Value &var, const Value &defval)
static CommandContext::TargetFactory::SubstituteInUnique< InteractiveOutputTargets > scope
const char * end(const char *str) noexcept
Definition: StringUtils.h:106
Parameters & mParameters
bool LoadPreset(const EffectDefinitionInterface &effect, const RegistryPath &group, EffectSettings &settings) const
bool StoreSettings(const EffectDefinitionInterface &effect, const AudioUnitEffectSettings &settings) const
bool LoadFactoryPreset(const EffectDefinitionInterface &effect, int id, EffectSettings *pSettings) const
OSStatus GetFixedSizeProperty(AudioUnitPropertyID inID, T &property, AudioUnitScope inScope=kAudioUnitScope_Global, AudioUnitElement inElement=0) const
std::function< bool(const ParameterInfo &pi, AudioUnitParameterID ID) > ParameterVisitor
Return value: if true, continue visiting.
static bool MoveSettingsContents(AudioUnitEffectSettings &&src, AudioUnitEffectSettings &dst, bool merge)
Copy, then clear the optionals in src.
OSStatus GetVariableSizeProperty(AudioUnitPropertyID inID, PackedArray::Ptr< T > &pObject, AudioUnitScope inScope=kAudioUnitScope_Global, AudioUnitElement inElement=0) const
TranslatableString InterpretBlob(AudioUnitEffectSettings &settings, const wxString &group, const wxMemoryBuffer &buf) const
Interpret the dump made before by MakeBlob.
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
Parameters mOwnParameters
std::pair< CF_ptr< CFDataRef >, TranslatableString > MakeBlob(const EffectDefinitionInterface &effect, const AudioUnitEffectSettings &settings, const wxCFStringRef &cfname, bool binary) const
Obtain dump of the setting state of an AudioUnit instance.
void ForEachParameter(ParameterVisitor visitor) const
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.