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 // --------------------------------------------------------------------------------------------------------------------