DPF

DISTRHO Plugin Framework
Log | Files | Refs | Submodules | README | LICENSE

DistrhoUIAU.mm (17338B)


      1 /*
      2  * DISTRHO Plugin Framework (DPF)
      3  * Copyright (C) 2012-2024 Filipe Coelho <falktx@falktx.com>
      4  *
      5  * Permission to use, copy, modify, and/or distribute this software for any purpose with
      6  * or without fee is hereby granted, provided that the above copyright notice and this
      7  * permission notice appear in all copies.
      8  *
      9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
     10  * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
     11  * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
     12  * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
     13  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
     14  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     15  */
     16 
     17 #include "DistrhoUIInternal.hpp"
     18 
     19 #define Point AudioUnitPoint
     20 #define Size AudioUnitSize
     21 
     22 #include <AudioUnit/AudioUnit.h>
     23 #include <AudioUnit/AUCocoaUIView.h>
     24 #include <Cocoa/Cocoa.h>
     25 
     26 #undef Point
     27 #undef Size
     28 
     29 #ifndef DISTRHO_PLUGIN_BRAND_ID
     30 # error DISTRHO_PLUGIN_BRAND_ID undefined!
     31 #endif
     32 
     33 #ifndef DISTRHO_PLUGIN_UNIQUE_ID
     34 # error DISTRHO_PLUGIN_UNIQUE_ID undefined!
     35 #endif
     36 
     37 START_NAMESPACE_DISTRHO
     38 
     39 // --------------------------------------------------------------------------------------------------------------------
     40 
     41 #if ! DISTRHO_PLUGIN_WANT_STATE
     42 static constexpr const setStateFunc setStateCallback = nullptr;
     43 #endif
     44 #if ! DISTRHO_PLUGIN_WANT_MIDI_INPUT
     45 static constexpr const sendNoteFunc sendNoteCallback = nullptr;
     46 #endif
     47 
     48 // unsupported in AU
     49 static constexpr const fileRequestFunc fileRequestCallback = nullptr;
     50 
     51 // --------------------------------------------------------------------------------------------------------------------
     52 // Static data, see DistrhoPlugin.cpp
     53 
     54 extern double d_nextSampleRate;
     55 extern const char* d_nextBundlePath;
     56 
     57 // --------------------------------------------------------------------------------------------------------------------
     58 
     59 class DPF_UI_AU
     60 {
     61 public:
     62     DPF_UI_AU(const AudioUnit component,
     63               NSView* const view,
     64               const double sampleRate,
     65               void* const instancePointer)
     66         : fComponent(component),
     67           fParentView(view),
     68           fTimerRef(nullptr),
     69           fUI(this,
     70               reinterpret_cast<uintptr_t>(view),
     71               sampleRate,
     72               editParameterCallback,
     73               setParameterCallback,
     74               setStateCallback,
     75               sendNoteCallback,
     76               setSizeCallback,
     77               fileRequestCallback,
     78               d_nextBundlePath,
     79               instancePointer)
     80     {
     81        #if DISTRHO_PLUGIN_WANT_STATE
     82         // create state keys
     83         {
     84             CFArrayRef keysRef = nullptr;
     85             UInt32 dataSize = sizeof(CFArrayRef);
     86             if (AudioUnitGetProperty(fComponent, 'DPFl', kAudioUnitScope_Global, 0, &keysRef, &dataSize) == noErr
     87                 && dataSize == sizeof(CFArrayRef))
     88             {
     89                 const CFIndex numStates = CFArrayGetCount(keysRef);
     90                 char* key = nullptr;
     91                 CFIndex keyLen = -1;
     92 
     93                 fStateKeys.resize(numStates);
     94 
     95                 for (CFIndex i=0; i<numStates; ++i)
     96                 {
     97                     const CFStringRef keyRef = static_cast<CFStringRef>(CFArrayGetValueAtIndex(keysRef, i));
     98                     DISTRHO_SAFE_ASSERT_BREAK(CFGetTypeID(keyRef) == CFStringGetTypeID());
     99 
    100                     const CFIndex keyRefLen = CFStringGetLength(keyRef);
    101                     if (keyLen < keyRefLen)
    102                     {
    103                         keyLen = keyRefLen;
    104                         key = static_cast<char*>(std::realloc(key, keyLen + 1));
    105                     }
    106                     DISTRHO_SAFE_ASSERT_BREAK(CFStringGetCString(keyRef, key, keyLen + 1, kCFStringEncodingASCII));
    107 
    108                     fStateKeys[i] = key;
    109                 }
    110 
    111                 CFRelease(keysRef);
    112                 std::free(key);
    113             }
    114         }
    115        #endif
    116 
    117         // setup idle timer
    118         constexpr const CFTimeInterval interval = 60 * 0.0001;
    119 
    120         CFRunLoopTimerContext context = {};
    121         context.info = this;
    122         fTimerRef = CFRunLoopTimerCreate(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + interval, interval, 0, 0,
    123                                          _idleCallback, &context);
    124         DISTRHO_SAFE_ASSERT_RETURN(fTimerRef != nullptr,);
    125 
    126         CFRunLoopAddTimer(CFRunLoopGetCurrent(), fTimerRef, kCFRunLoopCommonModes);
    127 
    128         AudioUnitAddPropertyListener(fComponent, kAudioUnitProperty_SampleRate, auPropertyChangedCallback, this);
    129         AudioUnitAddPropertyListener(fComponent, 'DPFp', auPropertyChangedCallback, this);
    130        #if DISTRHO_PLUGIN_WANT_PROGRAMS
    131         AudioUnitAddPropertyListener(fComponent, 'DPFo', auPropertyChangedCallback, this);
    132        #endif
    133        #if DISTRHO_PLUGIN_WANT_STATE
    134         AudioUnitAddPropertyListener(fComponent, 'DPFs', auPropertyChangedCallback, this);
    135        #endif
    136     }
    137 
    138     ~DPF_UI_AU()
    139     {
    140         AudioUnitRemovePropertyListenerWithUserData(fComponent, kAudioUnitProperty_SampleRate, auPropertyChangedCallback, this);
    141         AudioUnitRemovePropertyListenerWithUserData(fComponent, 'DPFp', auPropertyChangedCallback, this);
    142        #if DISTRHO_PLUGIN_WANT_PROGRAMS
    143         AudioUnitRemovePropertyListenerWithUserData(fComponent, 'DPFo', auPropertyChangedCallback, this);
    144        #endif
    145        #if DISTRHO_PLUGIN_WANT_STATE
    146         AudioUnitRemovePropertyListenerWithUserData(fComponent, 'DPFs', auPropertyChangedCallback, this);
    147        #endif
    148 
    149         if (fTimerRef != nullptr)
    150         {
    151             CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), fTimerRef, kCFRunLoopCommonModes);
    152             CFRelease(fTimerRef);
    153         }
    154     }
    155 
    156     void postSetup()
    157     {
    158         const double scaleFactor = fUI.getScaleFactor();
    159         const NSSize size = NSMakeSize(fUI.getWidth() / scaleFactor, fUI.getHeight() / scaleFactor);
    160 
    161         [fParentView setFrameSize:size];
    162         [fParentView setHidden:NO];
    163     }
    164 
    165 private:
    166     const AudioUnit fComponent;
    167     NSView* const fParentView;
    168     CFRunLoopTimerRef fTimerRef;
    169 
    170     UIExporter fUI;
    171 
    172    #if DISTRHO_PLUGIN_WANT_STATE
    173     std::vector<String> fStateKeys;
    174    #endif
    175 
    176     // ----------------------------------------------------------------------------------------------------------------
    177     // Idle setup
    178 
    179     void idleCallback()
    180     {
    181         fUI.idleFromNativeIdle();
    182     }
    183 
    184     static void _idleCallback(CFRunLoopTimerRef, void* const info)
    185     {
    186         static_cast<DPF_UI_AU*>(info)->idleCallback();
    187     }
    188 
    189     // ----------------------------------------------------------------------------------------------------------------
    190     // AU callbacks
    191 
    192     void auSampleRateChanged(const AudioUnitScope scope)
    193     {
    194         Float64 sampleRate = 0;
    195         UInt32 dataSize = sizeof(Float64);
    196         if (AudioUnitGetProperty(fComponent, kAudioUnitProperty_SampleRate, scope, 0, &sampleRate, &dataSize) == noErr
    197             && dataSize == sizeof(Float64))
    198         {
    199             fUI.setSampleRate(sampleRate, true);
    200         }
    201     }
    202 
    203     void auParameterChanged(const AudioUnitElement elem)
    204     {
    205         float value = 0;
    206         UInt32 dataSize = sizeof(float);
    207         if (AudioUnitGetProperty(fComponent, 'DPFp', kAudioUnitScope_Global, elem, &value, &dataSize) == noErr
    208             && dataSize == sizeof(float))
    209         {
    210             fUI.parameterChanged(elem, value);
    211         }
    212     }
    213 
    214    #if DISTRHO_PLUGIN_WANT_PROGRAMS
    215     void auProgramChanged()
    216     {
    217         uint32_t program = 0;
    218         UInt32 dataSize = sizeof(uint32_t);
    219         if (AudioUnitGetProperty(fComponent, 'DPFo', kAudioUnitScope_Global, 0, &program, &dataSize) == noErr
    220             && dataSize == sizeof(uint32_t))
    221         {
    222             fUI.programLoaded(program);
    223         }
    224     }
    225    #endif
    226 
    227    #if DISTRHO_PLUGIN_WANT_STATE
    228     void auStateChanged(const AudioUnitElement elem)
    229     {
    230         DISTRHO_SAFE_ASSERT_RETURN(elem < fStateKeys.size(),);
    231 
    232         CFStringRef valueRef = nullptr;
    233         UInt32 dataSize = sizeof(valueRef);
    234         if (AudioUnitGetProperty(fComponent, 'DPFs', kAudioUnitScope_Global, elem, &valueRef, &dataSize) == noErr
    235             && dataSize == sizeof(CFStringRef)
    236             && valueRef != nullptr
    237             && CFGetTypeID(valueRef) == CFStringGetTypeID())
    238         {
    239             const CFIndex valueLen = CFStringGetLength(valueRef);
    240             char* const value = static_cast<char*>(std::malloc(valueLen + 1));
    241             DISTRHO_SAFE_ASSERT_RETURN(value != nullptr,);
    242             DISTRHO_SAFE_ASSERT_RETURN(CFStringGetCString(valueRef, value, valueLen + 1, kCFStringEncodingUTF8),);
    243 
    244             fUI.stateChanged(fStateKeys[elem], value);
    245 
    246             CFRelease(valueRef);
    247             std::free(value);
    248         }
    249     }
    250    #endif
    251 
    252     static void auPropertyChangedCallback(void* const userData,
    253                                           const AudioUnit component,
    254                                           const AudioUnitPropertyID prop,
    255                                           const AudioUnitScope scope,
    256                                           const AudioUnitElement elem)
    257     {
    258         DPF_UI_AU* const self = static_cast<DPF_UI_AU*>(userData);
    259 
    260         DISTRHO_SAFE_ASSERT_RETURN(self != nullptr,);
    261         DISTRHO_SAFE_ASSERT_RETURN(self->fComponent == component,);
    262 
    263         switch (prop)
    264         {
    265         case kAudioUnitProperty_SampleRate:
    266             DISTRHO_SAFE_ASSERT_UINT_RETURN(scope == kAudioUnitScope_Input || scope == kAudioUnitScope_Output, scope,);
    267             self->auSampleRateChanged(scope);
    268             break;
    269         case 'DPFp':
    270             DISTRHO_SAFE_ASSERT_UINT_RETURN(scope == kAudioUnitScope_Global, scope,);
    271             self->auParameterChanged(elem);
    272             break;
    273        #if DISTRHO_PLUGIN_WANT_PROGRAMS
    274         case 'DPFo':
    275             DISTRHO_SAFE_ASSERT_UINT_RETURN(scope == kAudioUnitScope_Global, scope,);
    276             self->auProgramChanged();
    277             break;
    278        #endif
    279        #if DISTRHO_PLUGIN_WANT_STATE
    280         case 'DPFs':
    281             DISTRHO_SAFE_ASSERT_UINT_RETURN(scope == kAudioUnitScope_Global, scope,);
    282             self->auStateChanged(elem);
    283             break;
    284        #endif
    285         }
    286     }
    287 
    288     // ----------------------------------------------------------------------------------------------------------------
    289     // DPF callbacks
    290 
    291     void editParameter(const uint32_t rindex, const bool started) const
    292     {
    293         const uint8_t flag = started ? 1 : 2;
    294         AudioUnitSetProperty(fComponent, 'DPFe', kAudioUnitScope_Global, rindex, &flag, sizeof(uint8_t));
    295 
    296         if (! started)
    297         {
    298             const uint8_t cancel = 0;
    299             AudioUnitSetProperty(fComponent, 'DPFe', kAudioUnitScope_Global, rindex, &cancel, sizeof(uint8_t));
    300         }
    301     }
    302 
    303     static void editParameterCallback(void* const ptr, const uint32_t rindex, const bool started)
    304     {
    305         static_cast<DPF_UI_AU*>(ptr)->editParameter(rindex, started);
    306     }
    307 
    308     // ----------------------------------------------------------------------------------------------------------------
    309 
    310     void setParameter(const uint32_t rindex, const float value)
    311     {
    312         AudioUnitSetProperty(fComponent, 'DPFp', kAudioUnitScope_Global, rindex, &value, sizeof(float));
    313     }
    314 
    315     static void setParameterCallback(void* const ptr, const uint32_t rindex, const float value)
    316     {
    317         static_cast<DPF_UI_AU*>(ptr)->setParameter(rindex, value);
    318     }
    319 
    320     // ----------------------------------------------------------------------------------------------------------------
    321 
    322    #if DISTRHO_PLUGIN_WANT_STATE
    323     void setState(const char* const key, const char* const value)
    324     {
    325         const std::vector<String>::iterator it = std::find(fStateKeys.begin(), fStateKeys.end(), key);
    326         DISTRHO_SAFE_ASSERT_RETURN(it != fStateKeys.end(),);
    327 
    328         if (const CFStringRef valueRef = CFStringCreateWithCString(nullptr, value, kCFStringEncodingUTF8))
    329         {
    330             const uint32_t index = it - fStateKeys.begin();
    331             AudioUnitSetProperty(fComponent, 'DPFs', kAudioUnitScope_Global, index, &valueRef, sizeof(CFStringRef));
    332             CFRelease(valueRef);
    333         }
    334     }
    335 
    336     static void setStateCallback(void* const ptr, const char* const key, const char* const value)
    337     {
    338         static_cast<DPF_UI_AU*>(ptr)->setState(key, value);
    339     }
    340    #endif
    341 
    342     // ----------------------------------------------------------------------------------------------------------------
    343 
    344    #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
    345     void sendNote(const uint8_t channel, const uint8_t note, const uint8_t velocity)
    346     {
    347         const uint8_t data[3] = { static_cast<uint8_t>((velocity != 0 ? 0x90 : 0x80) | channel), note, velocity };
    348         AudioUnitSetProperty(fComponent, 'DPFn', kAudioUnitScope_Global, 0, data, sizeof(data));
    349 
    350         const uint8_t cancel[3] = { 0, 0, 0 };
    351         AudioUnitSetProperty(fComponent, 'DPFn', kAudioUnitScope_Global, 0, cancel, sizeof(cancel));
    352     }
    353 
    354     static void sendNoteCallback(void* const ptr, const uint8_t channel, const uint8_t note, const uint8_t velocity)
    355     {
    356         static_cast<DPF_UI_AU*>(ptr)->sendNote(channel, note, velocity);
    357     }
    358    #endif
    359 
    360     // ----------------------------------------------------------------------------------------------------------------
    361 
    362     void setSize(const uint width, const uint height)
    363     {
    364         const double scaleFactor = fUI.getScaleFactor();
    365         const NSSize size = NSMakeSize(width / scaleFactor, height / scaleFactor);
    366 
    367         [fParentView setFrameSize:size];
    368     }
    369 
    370     static void setSizeCallback(void* const ptr, const uint width, const uint height)
    371     {
    372         static_cast<DPF_UI_AU*>(ptr)->setSize(width, height);
    373     }
    374 };
    375 
    376 // --------------------------------------------------------------------------------------------------------------------
    377 
    378 END_NAMESPACE_DISTRHO
    379 
    380 // --------------------------------------------------------------------------------------------------------------------
    381 
    382 #define MACRO_NAME2(a, b, c, d, e, f) a ## b ## c ## d ## e ## f
    383 #define MACRO_NAME(a, b, c, d, e, f) MACRO_NAME2(a, b, c, d, e, f)
    384 
    385 // --------------------------------------------------------------------------------------------------------------------
    386 
    387 #define COCOA_VIEW_CLASS_NAME \
    388     MACRO_NAME(CocoaView_, DISTRHO_PLUGIN_AU_TYPE, _, DISTRHO_PLUGIN_UNIQUE_ID, _, DISTRHO_PLUGIN_BRAND_ID)
    389 
    390 @interface COCOA_VIEW_CLASS_NAME : NSView
    391 {
    392 @public
    393     DPF_UI_AU* ui;
    394 }
    395 @end
    396 
    397 @implementation COCOA_VIEW_CLASS_NAME
    398 
    399 - (id) initWithPreferredSize:(NSSize)size
    400 {
    401     ui = nullptr;
    402     self = [super initWithFrame: NSMakeRect(0, 0, size.width, size.height)];
    403     [self setHidden:YES];
    404     return self;
    405 }
    406 
    407 - (BOOL) acceptsFirstResponder
    408 {
    409   return YES;
    410 }
    411 
    412 - (void) dealloc
    413 {
    414     delete ui;
    415     ui = nullptr;
    416 
    417 	[super dealloc];
    418 }
    419 
    420 - (BOOL) isFlipped
    421 {
    422   return YES;
    423 }
    424 
    425 @end
    426 
    427 // --------------------------------------------------------------------------------------------------------------------
    428 
    429 #define COCOA_UI_CLASS_NAME \
    430     MACRO_NAME(CocoaAUView_, DISTRHO_PLUGIN_AU_TYPE, _, DISTRHO_PLUGIN_UNIQUE_ID, _, DISTRHO_PLUGIN_BRAND_ID)
    431 
    432 @interface COCOA_UI_CLASS_NAME : NSObject<AUCocoaUIBase>
    433 {
    434     COCOA_VIEW_CLASS_NAME* view;
    435 }
    436 @end
    437 
    438 @implementation COCOA_UI_CLASS_NAME
    439 
    440 - (NSString*) description
    441 {
    442     return @DISTRHO_PLUGIN_NAME;
    443 }
    444 
    445 - (unsigned) interfaceVersion
    446 {
    447     return 0;
    448 }
    449 
    450 - (NSView*) uiViewForAudioUnit:(AudioUnit)component withSize:(NSSize)inPreferredSize
    451 {
    452     Float64 sampleRate = d_nextSampleRate;
    453     void* instancePointer = nullptr;
    454     AudioUnitScope scope;
    455     UInt32 dataSize;
    456 
    457     // fetch direct access pointer
    458    #if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
    459     dataSize = sizeof(void*);
    460     AudioUnitGetProperty(component, 'DPFa', kAudioUnitScope_Global, 0, &instancePointer, &dataSize);
    461    #endif
    462 
    463     // fetch current sample rate
    464    #if DISTRHO_PLUGIN_NUM_INPUTS != 0
    465     dataSize = sizeof(Float64);
    466     AudioUnitGetProperty(component, kAudioUnitProperty_SampleRate, kAudioUnitScope_Input, 0, &sampleRate, &dataSize);
    467    #elif DISTRHO_PLUGIN_NUM_OUTPUTS != 0
    468     dataSize = sizeof(Float64);
    469     AudioUnitGetProperty(component, kAudioUnitProperty_SampleRate, kAudioUnitScope_Output, 0, &sampleRate, &dataSize);
    470    #endif
    471 
    472    #if defined(DISTRHO_UI_DEFAULT_WIDTH) && defined(DISTRHO_UI_DEFAULT_HEIGHT)
    473     inPreferredSize = NSMakeSize(DISTRHO_UI_DEFAULT_WIDTH, DISTRHO_UI_DEFAULT_HEIGHT);
    474    #endif
    475 
    476     // create view
    477     view = [[[COCOA_VIEW_CLASS_NAME alloc] initWithPreferredSize:inPreferredSize] autorelease];
    478     view->ui = new DPF_UI_AU(component, view, sampleRate, instancePointer);
    479     view->ui->postSetup();
    480 
    481     // request data from DSP side
    482     {
    483         const uint16_t magic = 1337;
    484         AudioUnitSetProperty(component, 'DPFi', kAudioUnitScope_Global, 0, &magic, sizeof(uint16_t));
    485         const uint16_t cancel = 0;
    486         AudioUnitSetProperty(component, 'DPFi', kAudioUnitScope_Global, 0, &cancel, sizeof(uint16_t));
    487     }
    488 
    489     return view;
    490 
    491     // maybe unused
    492     (void)scope;
    493 }
    494 
    495 @end
    496 
    497 // --------------------------------------------------------------------------------------------------------------------
    498 
    499 #undef MACRO_NAME
    500 #undef MACRO_NAME2
    501 
    502 // --------------------------------------------------------------------------------------------------------------------