Skip to content

Commit

Permalink
AudioUnitEffectProcessor: Factor out DlgAudioUnit
Browse files Browse the repository at this point in the history
  • Loading branch information
fwcd committed Nov 15, 2024
1 parent 1cb3f51 commit 397649c
Show file tree
Hide file tree
Showing 5 changed files with 186 additions and 154 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1599,6 +1599,7 @@ if(APPLE)
src/effects/backends/audiounit/audiounitmanager.mm
src/effects/backends/audiounit/audiouniteffectprocessor.mm
src/effects/backends/audiounit/audiounitmanifest.mm
src/effects/backends/audiounit/dlgaudiounit.mm
)
target_link_libraries(mixxx-lib PRIVATE
"-weak_framework AudioToolbox"
Expand Down
2 changes: 0 additions & 2 deletions src/effects/backends/audiounit/audiouniteffectprocessor.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,4 @@ class AudioUnitEffectProcessor final : public EffectProcessorImpl<AudioUnitEffec

void syncParameters();
void syncStreamFormat(const mixxx::EngineParameters& engineParameters);

NSView* _Nullable createNativeUI(CGSize size, QString& outError);
};
155 changes: 3 additions & 152 deletions src/effects/backends/audiounit/audiouniteffectprocessor.mm
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#import <AVFAudio/AVFAudio.h>
#import <AppKit/AppKit.h>
#import <AudioToolbox/AudioToolbox.h>
#import <AudioUnit/AUCocoaUIView.h>
#import <CoreAudioKit/CoreAudioKit.h>
#import <CoreAudioTypes/CoreAudioBaseTypes.h>
#import <CoreAudioTypes/CoreAudioTypes.h>
Expand All @@ -14,6 +13,8 @@
#include <memory>

#include "effects/backends/audiounit/audiouniteffectprocessor.h"
#include "effects/backends/audiounit/audiounitmanager.h"
#include "effects/backends/audiounit/dlgaudiounit.h"
#include "engine/effects/engineeffectparameter.h"
#include "engine/engine.h"
#include "util/assert.h"
Expand Down Expand Up @@ -259,155 +260,5 @@
}

std::unique_ptr<QDialog> AudioUnitEffectProcessor::createUI() {
QString name = m_manager.getName();

std::unique_ptr<QDialog> dialog = std::make_unique<QDialog>();
dialog->setWindowTitle(name);

// See
// https://lists.qt-project.org/pipermail/interest/2014-January/010655.html
// for why we need this slightly convoluted cast
NSView* dialogView =
(__bridge NSView*)reinterpret_cast<void*>(dialog->winId());

// Style effect UI as a floating, but non-modal, HUD window
NSWindow* dialogWindow = [dialogView window];
[dialogWindow setStyleMask:NSWindowStyleMaskTitled |
NSWindowStyleMaskClosable | NSWindowStyleMaskResizable |
NSWindowStyleMaskUtilityWindow | NSWindowStyleMaskHUDWindow];
[dialogWindow setLevel:NSFloatingWindowLevel];

QString error = "Could not load UI of Audio Unit";
NSView* audioUnitView = createNativeUI(dialog->size().toCGSize(), error);

if (audioUnitView != nil) {
[dialogView addSubview:audioUnitView];

// Automatically resize the dialog to fit the view after layout
[audioUnitView setPostsFrameChangedNotifications:YES];
[[NSNotificationCenter defaultCenter]
addObserverForName:NSViewFrameDidChangeNotification
object:audioUnitView
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification* notification) {
NSView* audioUnitView =
(NSView*)notification.object;
NSWindow* dialogWindow = audioUnitView.window;
CGSize size = audioUnitView.frame.size;
[dialogWindow setContentSize:size];
}];
} else {
qWarning() << error;

// Fall back to a generic UI if possible
AudioUnit _Nullable audioUnit = m_manager.getAudioUnit();
if (audioUnit != nil) {
AUGenericView* genericView =
[[AUGenericView alloc] initWithAudioUnit:audioUnit];
genericView.showsExpertParameters = YES;
[dialogView addSubview:genericView];
}
}

return dialog;
}

NSView* _Nullable AudioUnitEffectProcessor::createNativeUI(
CGSize size, QString& outError) {
QString name = m_manager.getName();
qDebug() << "Loading UI of Audio Unit" << name << "with width" << size.width
<< "and height" << size.height;

AudioUnit _Nullable audioUnit = m_manager.getAudioUnit();

if (audioUnit == nil) {
outError = "Cannot create UI of Audio Unit " + name +
" without an instance";
return nil;
}

// See
// https://developer.apple.com/library/archive/samplecode/CocoaAUHost/Listings/Source_CAUHWindowController_mm.html

uint32_t dataSize;

OSStatus infoStatus = AudioUnitGetPropertyInfo(audioUnit,
kAudioUnitProperty_CocoaUI,
kAudioUnitScope_Global,
0,
&dataSize,
nullptr);
if (infoStatus != noErr) {
outError = "No Cocoa UI available for Audio Unit " + name;
return nil;
}

uint32_t numberOfClasses =
(dataSize - sizeof(CFURLRef)) / sizeof(CFStringRef);
if (numberOfClasses == 0) {
outError = "No view classes available for Audio Unit " + name;
return nil;
}

std::unique_ptr<AudioUnitCocoaViewInfo[]> cocoaViewInfo =
std::make_unique<AudioUnitCocoaViewInfo[]>(numberOfClasses);

OSStatus status = AudioUnitGetProperty(audioUnit,
kAudioUnitProperty_CocoaUI,
kAudioUnitScope_Global,
0,
cocoaViewInfo.get(),
&dataSize);
if (status != noErr) {
outError = "Could not fetch Cocoa UI for Audio Unit " + name;
return nil;
}

NSURL* viewBundleLocation =
(__bridge NSURL*)cocoaViewInfo.get()->mCocoaAUViewBundleLocation;
if (viewBundleLocation == nil) {
outError = "Cannot create UI of Audio Unit " + name +
" without view bundle path";
return nil;
}

// We only use the first view as in the Cocoa AU host example linked earlier
NSString* factoryClassName =
(__bridge NSString*)cocoaViewInfo.get()->mCocoaAUViewClass[0];
;
if (factoryClassName == nil) {
outError = "Cannot create UI of Audio Unit " + name +
" without factory class name";
return nil;
}

NSBundle* viewBundle = [NSBundle bundleWithURL:viewBundleLocation];
if (viewBundle == nil) {
outError = "Could not load view bundle of Audio Unit " + name;
return nil;
}

Class factoryClass = [viewBundle classNamed:factoryClassName];
if (factoryClass == nil) {
outError =
"Could not load view factory class from bundle of Audio Unit " +
name;
return nil;
}

id<AUCocoaUIBase> factoryInstance = [[factoryClass alloc] init];
if (factoryInstance == nil) {
outError =
"Could not instantiate factory for view of Audio Unit " + name;
return nil;
}

NSView* view = [factoryInstance uiViewForAudioUnit:audioUnit withSize:size];
if (view == nil) {
outError = "Could not instantiate view of Audio Unit " + name;
return nil;
}

qDebug() << "Successfully loaded UI of Audio Unit" << name;
return view;
return std::make_unique<DlgAudioUnit>(m_manager);
}
13 changes: 13 additions & 0 deletions src/effects/backends/audiounit/dlgaudiounit.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#pragma once

#include <QDialog>

class AudioUnitManager;

/// A dialog hosting the UI of an Audio Unit.
class DlgAudioUnit : public QDialog {
Q_OBJECT

public:
DlgAudioUnit(const AudioUnitManager& manager);
};
169 changes: 169 additions & 0 deletions src/effects/backends/audiounit/dlgaudiounit.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
#import <AVFAudio/AVFAudio.h>
#import <AppKit/AppKit.h>
#import <AudioToolbox/AudioToolbox.h>
#import <AudioUnit/AUCocoaUIView.h>
#import <CoreAudioKit/CoreAudioKit.h>
#import <CoreAudioTypes/CoreAudioBaseTypes.h>
#import <CoreAudioTypes/CoreAudioTypes.h>

#include "effects/backends/audiounit/audiounitmanager.h"
#include "effects/backends/audiounit/dlgaudiounit.h"

#include "moc_dlgaudiounit.cpp"

#include <QString>
#include <QtGlobal>

namespace {

NSView* _Nullable createNativeUI(
const AudioUnitManager& manager, CGSize size, QString& outError) {
QString name = manager.getName();
qDebug() << "Loading UI of Audio Unit" << name << "with width" << size.width
<< "and height" << size.height;

AudioUnit _Nullable audioUnit = manager.getAudioUnit();

if (audioUnit == nil) {
outError = "Cannot create UI of Audio Unit " + name +
" without an instance";
return nil;
}

// See
// https://developer.apple.com/library/archive/samplecode/CocoaAUHost/Listings/Source_CAUHWindowController_mm.html

uint32_t dataSize;

OSStatus infoStatus = AudioUnitGetPropertyInfo(audioUnit,
kAudioUnitProperty_CocoaUI,
kAudioUnitScope_Global,
0,
&dataSize,
nullptr);
if (infoStatus != noErr) {
outError = "No Cocoa UI available for Audio Unit " + name;
return nil;
}

uint32_t numberOfClasses =
(dataSize - sizeof(CFURLRef)) / sizeof(CFStringRef);
if (numberOfClasses == 0) {
outError = "No view classes available for Audio Unit " + name;
return nil;
}

std::unique_ptr<AudioUnitCocoaViewInfo[]> cocoaViewInfo =
std::make_unique<AudioUnitCocoaViewInfo[]>(numberOfClasses);

OSStatus status = AudioUnitGetProperty(audioUnit,
kAudioUnitProperty_CocoaUI,
kAudioUnitScope_Global,
0,
cocoaViewInfo.get(),
&dataSize);
if (status != noErr) {
outError = "Could not fetch Cocoa UI for Audio Unit " + name;
return nil;
}

NSURL* viewBundleLocation =
(__bridge NSURL*)cocoaViewInfo.get()->mCocoaAUViewBundleLocation;
if (viewBundleLocation == nil) {
outError = "Cannot create UI of Audio Unit " + name +
" without view bundle path";
return nil;
}

// We only use the first view as in the Cocoa AU host example linked earlier
NSString* factoryClassName =
(__bridge NSString*)cocoaViewInfo.get()->mCocoaAUViewClass[0];
;
if (factoryClassName == nil) {
outError = "Cannot create UI of Audio Unit " + name +
" without factory class name";
return nil;
}

NSBundle* viewBundle = [NSBundle bundleWithURL:viewBundleLocation];
if (viewBundle == nil) {
outError = "Could not load view bundle of Audio Unit " + name;
return nil;
}

Class factoryClass = [viewBundle classNamed:factoryClassName];
if (factoryClass == nil) {
outError =
"Could not load view factory class from bundle of Audio Unit " +
name;
return nil;
}

id<AUCocoaUIBase> factoryInstance = [[factoryClass alloc] init];
if (factoryInstance == nil) {
outError =
"Could not instantiate factory for view of Audio Unit " + name;
return nil;
}

NSView* view = [factoryInstance uiViewForAudioUnit:audioUnit withSize:size];
if (view == nil) {
outError = "Could not instantiate view of Audio Unit " + name;
return nil;
}

qDebug() << "Successfully loaded UI of Audio Unit" << name;
return view;
}

}; // anonymous namespace

DlgAudioUnit::DlgAudioUnit(const AudioUnitManager& manager) {
QString name = manager.getName();

setWindowTitle(name);

// See
// https://lists.qt-project.org/pipermail/interest/2014-January/010655.html
// for why we need this slightly convoluted cast
NSView* dialogView = (__bridge NSView*)reinterpret_cast<void*>(winId());

// Style effect UI as a floating, but non-modal, HUD window
NSWindow* dialogWindow = [dialogView window];
[dialogWindow setStyleMask:NSWindowStyleMaskTitled |
NSWindowStyleMaskClosable | NSWindowStyleMaskResizable |
NSWindowStyleMaskUtilityWindow | NSWindowStyleMaskHUDWindow];
[dialogWindow setLevel:NSFloatingWindowLevel];

QString error = "Could not load UI of Audio Unit";
NSView* audioUnitView = createNativeUI(manager, size().toCGSize(), error);

if (audioUnitView != nil) {
[dialogView addSubview:audioUnitView];

// Automatically resize the dialog to fit the view after layout
[audioUnitView setPostsFrameChangedNotifications:YES];
[[NSNotificationCenter defaultCenter]
addObserverForName:NSViewFrameDidChangeNotification
object:audioUnitView
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification* notification) {
NSView* audioUnitView =
(NSView*)notification.object;
NSWindow* dialogWindow = audioUnitView.window;
CGSize size = audioUnitView.frame.size;
[dialogWindow setContentSize:size];
}];
} else {
qWarning() << error;

// Fall back to a generic UI if possible
AudioUnit _Nullable audioUnit = manager.getAudioUnit();
if (audioUnit != nil) {
AUGenericView* genericView =
[[AUGenericView alloc] initWithAudioUnit:audioUnit];
genericView.showsExpertParameters = YES;
[dialogView addSubview:genericView];
}
}
}

0 comments on commit 397649c

Please sign in to comment.