Audacity 3.2.0
AudioUnitEditor.cpp
Go to the documentation of this file.
1/*!********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 @file AudioUnitEditor.h
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 "AudioUnitEditor.h"
17#include "AudioUnitInstance.h"
18#include "AUControl.h"
19#include <wx/app.h>
20#include <wx/sizer.h>
21#include "ShuttleGui.h"
22#include "wxPanelWrapper.h"
23
25 const EffectUIServices &effect,
26 EffectSettingsAccess &access, AudioUnitInstance &instance,
27 AUControl *pControl, bool isGraphical
28) : EffectEditor{ effect, access }
29 , mInstance{ instance }
30 , mEventListenerRef{ MakeListener() }
31 , mpControl{ pControl }
32 , mIsGraphical{ isGraphical }
33{
34 // Make the settings of the instance up to date before using it to
35 // build a UI
37
38 wxTheApp->Bind(wxEVT_IDLE, &AudioUnitEditor::OnIdle, this);
39}
40
42{
43 if (mpControl)
45}
46
49{
50 const auto unit = mInstance.GetAudioUnit();
51 EventListenerPtr result;
52
53 // Register a callback with the audio unit
54 AUEventListenerRef eventListenerRef{};
55 if (AUEventListenerCreate(AudioUnitEditor::EventListenerCallback,
56 this,
57 static_cast<CFRunLoopRef>( const_cast<void*>(
58 GetCFRunLoopFromEventLoop(GetCurrentEventLoop()))),
59 kCFRunLoopDefaultMode, 0.0, 0.0, &eventListenerRef))
60 return nullptr;
61 result.reset(eventListenerRef);
62
63 // AudioUnitEvent is a struct with a discriminator field and a union
64 AudioUnitEvent event{ kAudioUnitEvent_ParameterValueChange };
65 // Initialize union member -- the ID (second field) reassigned later
66 auto &parameter = event.mArgument.mParameter;
67 parameter = AudioUnitUtils::Parameter{ unit, kAudioUnitScope_Global };
68
69 // Register each parameter as something we're interested in
70 if (auto &parameters = mInstance.GetParameters())
71 for (const auto &ID : parameters) {
72 parameter.mParameterID = ID;
73 if (AUEventListenerAddEventType(result.get(), this, &event))
74 return nullptr;
75 }
76
77 // Now set up the other union member
78 event = { kAudioUnitEvent_PropertyChange };
79 // And bind the listener function to certain property changes
80 for (auto type : {
81 kAudioUnitProperty_Latency,
82 kAudioUnitProperty_PresentPreset,
83 }) {
84 event.mArgument.mProperty = AudioUnitUtils::Property{
85 unit, type, kAudioUnitScope_Global };
86 if (AUEventListenerAddEventType(result.get(), this, &event))
87 return nullptr;
88 }
89
90 return result;
91}
92
94{
95 // Update parameter values in AudioUnit, and propagate to any listeners
97 // See AUView::viewWillDraw
98 if (mpControl)
100
101 // This will be the AudioUnit of a stateful instance, not of the effect
102 Notify();
103
104 return true;
105 }
106 return false;
107}
108
110{
112#if 0
113 // This analogy with other generators doesn't seem to fit AudioUnits
114 // How can we define the control mDuration?
115 if (GetType() == EffectTypeGenerate)
116 settings.extra.SetDuration(mDuration->GetValue());
117#endif
119 return nullptr;
120 });
121 return true;
122}
123
125{
126 return mIsGraphical;
127}
128
130{
131 return mInstance
133}
134
136{
139}
140
141std::unique_ptr<EffectEditor> AudioUnitEditor::Create(
142 const EffectUIServices &effect, ShuttleGui &S,
143 const wxString &uiType,
144 EffectInstance &instance, EffectSettingsAccess &access)
145{
146 const auto parent = S.GetParent();
147 bool isGraphical = (uiType == FullValue.MSGID().GET());
148 // Cast is assumed to succeed because only this effect's own instances
149 // are passed back by the framework
150 auto &myInstance = dynamic_cast<AudioUnitInstance&>(instance);
151
152 AUControl *pControl{};
153 wxPanel *container{};
154 {
155 auto mainSizer = std::make_unique<wxBoxSizer>(wxVERTICAL);
156 wxASSERT(parent); // To justify safenew
157 container = safenew wxPanelWrapper(parent, wxID_ANY);
158 mainSizer->Add(container, 1, wxEXPAND);
159 parent->SetSizer(mainSizer.release());
160 }
161
162#if defined(HAVE_AUDIOUNIT_BASIC_SUPPORT)
163 if (uiType == BasicValue.MSGID().GET()) {
164 if (!CreatePlain(mParent))
165 return nullptr;
166 }
167 else
168#endif
169 {
171 if (!uControl)
172 return nullptr;
173 pControl = uControl.get();
174
175 if (!pControl->Create(container, myInstance.GetComponent(),
176 myInstance.GetAudioUnit(), isGraphical))
177 return nullptr;
178
179 {
180 auto innerSizer = std::make_unique<wxBoxSizer>(wxVERTICAL);
181
182 innerSizer->Add(uControl.release(), 1, wxEXPAND);
183 container->SetSizer(innerSizer.release());
184 }
185
186 parent->SetMinSize(wxDefaultSize);
187
188#ifdef __WXMAC__
189#ifdef __WX_EVTLOOP_BUSY_WAITING__
190 wxEventLoop::SetBusyWaiting(true);
191#endif
192#endif
193 }
194
195 return std::make_unique<AudioUnitEditor>(
196 CreateToken{}, effect, access, myInstance, pControl, isGraphical);
197}
198
200{
201 AudioUnitParameter aup = {};
202 aup.mAudioUnit = mInstance.GetAudioUnit();
203 aup.mParameterID = kAUParameterListener_AnyParameter;
204 aup.mScope = kAudioUnitScope_Global;
205 aup.mElement = 0;
206 AUParameterListenerNotify(NULL, NULL, &aup);
207}
208
209void AudioUnitEditor::EventListener(const AudioUnitEvent *inEvent,
210 AudioUnitParameterValue inParameterValue)
211{
212 // Modify the instance and its workers
213 mInstance.EventListener(inEvent, inParameterValue);
214
215 if (inEvent->mEventType == kAudioUnitEvent_ParameterValueChange) {
216 constexpr AudioUnitParameterValue epsilon = 1e-6;
217
218 auto it = mParameterValues.find(inEvent->mArgument.mParameter.mParameterID);
219
220 // When the UI is opened - EventListener is called for each parameter
221 // with the current value.
222 if (it == mParameterValues.end())
223 mParameterValues.insert(std::make_pair(inEvent->mArgument.mParameter.mParameterID, inParameterValue));
224 else if (std::abs(it->second - inParameterValue) > epsilon)
225 {
226 it->second = inParameterValue;
227 Publish({inEvent->mArgument.mParameter.mParameterID, inParameterValue});
228 }
229
230 const auto ID = inEvent->mArgument.mParameter.mParameterID;
231 mToUpdate.emplace_back(ID, inParameterValue);
232 mAccess.Set(mInstance.MakeMessage(ID, inParameterValue));
233 }
234 else if (inEvent->mEventType == kAudioUnitEvent_PropertyChange &&
235 inEvent->mArgument.mProperty.mPropertyID ==
236 kAudioUnitProperty_PresentPreset
237 ) {
238 ValidateUI();
239 }
240}
241
242// static
243void AudioUnitEditor::EventListenerCallback(void *inCallbackRefCon,
244 void *inObject, const AudioUnitEvent *inEvent, UInt64 inEventHostTime,
245 AudioUnitParameterValue inParameterValue)
246{
247 static_cast<AudioUnitEditor *>(inCallbackRefCon)
248 ->EventListener(inEvent, inParameterValue);
249}
250
251void AudioUnitEditor::OnIdle(wxIdleEvent &evt)
252{
253 evt.Skip();
254 if (mToUpdate.size()) {
256 // Reassign settings, so that there is "stickiness" when dialog is
257 // closed and opened again
258 auto &mySettings = AudioUnitInstance::GetSettings(settings);
259 for (auto [ID, value] : mToUpdate)
260 if (auto &pair = mySettings.values[ID]; pair.has_value())
261 pair->second = value;
262 return nullptr;
263 });
264 mToUpdate.clear();
265 }
266}
267#endif
static const auto BasicValue
static const auto FullValue
@ EffectTypeGenerate
#define safenew
Definition: MemoryX.h:9
std::unique_ptr< T, Destroyer< T > > Destroy_ptr
a convenience for using Destroyer
Definition: MemoryX.h:161
#define S(N)
Definition: ToChars.cpp:64
static Settings & settings()
Definition: TrackInfo.cpp:69
void Close()
void ForceRedraw()
static std::unique_ptr< EffectEditor > Create(const EffectUIServices &effect, ShuttleGui &S, const wxString &uiType, EffectInstance &instance, EffectSettingsAccess &access)
void OnIdle(wxIdleEvent &evt)
AUControl *const mpControl
AudioUnitInstance & mInstance
std::unordered_map< AudioUnitParameterID, AudioUnitParameterValue > mParameterValues
bool IsGraphicalUI() override
static void EventListenerCallback(void *inCallbackRefCon, void *inObject, const AudioUnitEvent *inEvent, UInt64 inEventHostTime, AudioUnitParameterValue inParameterValue)
bool ValidateUI() override
Get settings data from the panel; may make error dialogs and return false.
bool UpdateUI() override
Update appearance of the panel for changes in settings.
const bool mIsGraphical
AudioUnitEditor(CreateToken, const EffectUIServices &effect, EffectSettingsAccess &access, AudioUnitInstance &instance, AUControl *pControl, bool isGraphical)
bool FetchSettingsFromInstance(EffectSettings &settings)
AudioUnitCleanup< AUEventListenerRef, AUListenerDispose > EventListenerPtr
EventListenerPtr MakeListener()
std::vector< std::pair< AudioUnitParameterID, AudioUnitParameterValue > > mToUpdate
void EventListener(const AudioUnitEvent *inEvent, AudioUnitParameterValue inParameterValue)
~AudioUnitEditor() override
bool StoreSettingsToInstance(const EffectSettings &settings)
std::unique_ptr< Message > MakeMessage() const override
Called on the main thread, in which the result may be cloned.
void EventListener(const AudioUnitEvent *inEvent, AudioUnitParameterValue inParameterValue)
EffectSettingsAccess & mAccess
Definition: EffectEditor.h:92
Performs effect computation.
void ModifySettings(Function &&function)
Do a correct read-modify-write of settings.
virtual const EffectSettings & Get()=0
virtual void Set(EffectSettings &&settings, std::unique_ptr< Message > pMessage=nullptr)=0
CallbackReturn Publish(const EffectSettingChanged &message)
Send a message to connected callbacks.
Definition: Observer.h:207
const PerTrackEffect & mProcessor
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI.
Definition: ShuttleGui.h:630
bool StoreSettings(const EffectDefinitionInterface &effect, const AudioUnitEffectSettings &settings) const
AudioUnit GetAudioUnit() const
static AudioUnitEffectSettings & GetSettings(EffectSettings &settings)
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.