Skip to content

Commit 1c277b1

Browse files
committed
Initial Amuse AudioUnit frontend architecture
1 parent 3f0ea23 commit 1c277b1

10 files changed

+651
-1
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.DS_Store

AudioUnit/Amuse.entitlements.in

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>com.apple.application-identifier</key>
6+
<string>@APPLE_TEAM_ID@.@APPLE_BUNDLE_ID@</string>
7+
<key>com.apple.developer.team-identifier</key>
8+
<string>@APPLE_TEAM_ID@</string>
9+
<key>com.apple.security.app-sandbox</key>
10+
<true/>
11+
</dict>
12+
</plist>

AudioUnit/AmuseContainingApp.mm

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
#import <AppKit/AppKit.h>
2+
#import <AudioUnit/AudioUnit.h>
3+
#import <CoreAudioKit/AUViewController.h>
4+
#import "AudioUnitViewController.hpp"
5+
6+
@interface AppDelegate : NSObject <NSApplicationDelegate>
7+
{}
8+
@end
9+
10+
@interface ViewController : NSViewController
11+
{
12+
NSButton* playButton;
13+
AudioUnitViewController* amuseVC;
14+
}
15+
16+
@property (weak) IBOutlet NSView *containerView;
17+
-(IBAction)togglePlay:(id)sender;
18+
19+
@end
20+
21+
@implementation ViewController
22+
23+
- (void)viewDidLoad {
24+
[super viewDidLoad];
25+
#if 0
26+
AudioComponentDescription desc;
27+
/* Supply the correct AudioComponentDescription based on your AudioUnit type, manufacturer and creator.
28+
29+
You need to supply matching settings in the AUAppExtension info.plist under:
30+
31+
NSExtension
32+
NSExtensionAttributes
33+
AudioComponents
34+
Item 0
35+
type
36+
subtype
37+
manufacturer
38+
39+
If you do not do this step, your AudioUnit will not work!!!
40+
*/
41+
desc.componentType = kAudioUnitType_MusicDevice;
42+
desc.componentSubType = 'sin3';
43+
desc.componentManufacturer = 'Demo';
44+
desc.componentFlags = 0;
45+
desc.componentFlagsMask = 0;
46+
47+
[AUAudioUnit registerSubclass: AUv3InstrumentDemo.class asComponentDescription:desc name:@"Local InstrumentDemo" version: UINT32_MAX];
48+
49+
playEngine = [[SimplePlayEngine alloc] initWithComponentType: desc.componentType componentsFoundCallback: nil];
50+
[playEngine selectAudioUnitWithComponentDescription2:desc completionHandler:^{
51+
[self connectParametersToControls];
52+
}];
53+
#endif
54+
}
55+
56+
-(void)loadView {
57+
amuseVC = [[AudioUnitViewController alloc] initWithNibName:nil bundle:nil];
58+
self.view = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 200, 300)];
59+
[self.view addSubview:amuseVC.view];
60+
61+
self.view.translatesAutoresizingMaskIntoConstraints = NO;
62+
63+
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat: @"H:|-[view]-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(self.view)];
64+
[self.view addConstraints: constraints];
65+
66+
constraints = [NSLayoutConstraint constraintsWithVisualFormat: @"V:|-[view]-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(self.view)];
67+
[self.view addConstraints: constraints];
68+
}
69+
70+
@end
71+
72+
@implementation AppDelegate
73+
74+
@end
75+
76+
int main(int argc, const char * argv[]) {
77+
NSApplication* app = [NSApplication sharedApplication];
78+
[app setActivationPolicy:NSApplicationActivationPolicyRegular];
79+
80+
/* Delegate (OS X callbacks) */
81+
AppDelegate* delegate = [AppDelegate new];
82+
[app setDelegate:delegate];
83+
[app run];
84+
return 0;
85+
}

AudioUnit/AudioUnitBackend.hpp

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#ifndef __AMUSE_AUDIOUNIT_BACKEND_HPP__
2+
#define __AMUSE_AUDIOUNIT_BACKEND_HPP__
3+
#ifdef __APPLE__
4+
5+
#include <Availability.h>
6+
7+
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101100
8+
#define AMUSE_HAS_AUDIO_UNIT 1
9+
10+
#include <AudioUnit/AudioUnit.h>
11+
12+
#include "optional.hpp"
13+
14+
#include "amuse/BooBackend.hpp"
15+
#include "amuse/Engine.hpp"
16+
#include "amuse/IBackendVoice.hpp"
17+
#include "amuse/IBackendSubmix.hpp"
18+
#include "amuse/IBackendVoiceAllocator.hpp"
19+
20+
namespace amuse
21+
{
22+
23+
/** Backend MIDI event reader for controlling sequencer with external hardware / software */
24+
class AudioUnitBackendMIDIReader : public BooBackendMIDIReader
25+
{
26+
friend class AudioUnitBackendVoiceAllocator;
27+
public:
28+
AudioUnitBackendMIDIReader(Engine& engine)
29+
: BooBackendMIDIReader(engine, "AudioUnit MIDI") {}
30+
};
31+
32+
/** Backend voice allocator implementation for AudioUnit mixer */
33+
class AudioUnitBackendVoiceAllocator : public BooBackendVoiceAllocator
34+
{
35+
friend class AudioUnitBackendMIDIReader;
36+
public:
37+
AudioUnitBackendVoiceAllocator(boo::IAudioVoiceEngine& booEngine)
38+
: BooBackendVoiceAllocator(booEngine) {}
39+
};
40+
41+
void RegisterAudioUnit();
42+
43+
}
44+
45+
@interface AmuseAudioUnit : AUAudioUnit
46+
{
47+
std::unique_ptr<boo::IAudioVoiceEngine> m_booBackend;
48+
std::experimental::optional<amuse::AudioUnitBackendVoiceAllocator> m_voxAlloc;
49+
std::experimental::optional<amuse::Engine> m_engine;
50+
AUAudioUnitBusArray* m_outs;
51+
}
52+
@end
53+
54+
#endif
55+
#endif
56+
#endif // __AMUSE_AUDIOUNIT_BACKEND_HPP__

AudioUnit/AudioUnitBackend.mm

+230
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
#include "AudioUnitBackend.hpp"
2+
#ifdef __APPLE__
3+
#include <Availability.h>
4+
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101100
5+
#import <AudioUnit/AudioUnit.h>
6+
#import <CoreAudioKit/CoreAudioKit.h>
7+
#import <AVFoundation/AVFoundation.h>
8+
9+
#if !__has_feature(objc_arc)
10+
#error ARC Required
11+
#endif
12+
13+
#include "logvisor/logvisor.hpp"
14+
#include "audiodev/AudioVoiceEngine.hpp"
15+
16+
static logvisor::Module Log("amuse::AudioUnitBackend");
17+
18+
struct AudioUnitVoiceEngine : boo::BaseAudioVoiceEngine
19+
{
20+
std::vector<std::unique_ptr<float[]>> m_renderBufs;
21+
size_t m_frameBytes;
22+
23+
void render(AudioBufferList* outputData)
24+
{
25+
if (m_renderBufs.size() < outputData->mNumberBuffers)
26+
m_renderBufs.resize(outputData->mNumberBuffers);
27+
28+
for (int i=0 ; i<outputData->mNumberBuffers ; ++i)
29+
{
30+
std::unique_ptr<float[]>& buf = m_renderBufs[i];
31+
AudioBuffer& auBuf = outputData->mBuffers[i];
32+
if (!auBuf.mData)
33+
{
34+
buf.reset(new float[auBuf.mDataByteSize]);
35+
auBuf.mData = buf.get();
36+
}
37+
38+
_pumpAndMixVoices(auBuf.mDataByteSize / 2 / 4, reinterpret_cast<float*>(auBuf.mData));
39+
}
40+
}
41+
42+
boo::AudioChannelSet _getAvailableSet()
43+
{
44+
return boo::AudioChannelSet::Stereo;
45+
}
46+
47+
std::vector<std::pair<std::string, std::string>> enumerateMIDIDevices() const
48+
{
49+
return {};
50+
}
51+
52+
boo::ReceiveFunctor m_midiReceiver = nullptr;
53+
54+
std::unique_ptr<boo::IMIDIIn> newVirtualMIDIIn(boo::ReceiveFunctor&& receiver)
55+
{
56+
m_midiReceiver = std::move(receiver);
57+
return {};
58+
}
59+
60+
std::unique_ptr<boo::IMIDIOut> newVirtualMIDIOut()
61+
{
62+
return {};
63+
}
64+
65+
std::unique_ptr<boo::IMIDIInOut> newVirtualMIDIInOut(boo::ReceiveFunctor&& receiver)
66+
{
67+
return {};
68+
}
69+
70+
std::unique_ptr<boo::IMIDIIn> newRealMIDIIn(const char* name, boo::ReceiveFunctor&& receiver)
71+
{
72+
return {};
73+
}
74+
75+
std::unique_ptr<boo::IMIDIOut> newRealMIDIOut(const char* name)
76+
{
77+
return {};
78+
}
79+
80+
std::unique_ptr<boo::IMIDIInOut> newRealMIDIInOut(const char* name, boo::ReceiveFunctor&& receiver)
81+
{
82+
return {};
83+
}
84+
85+
AudioUnitVoiceEngine()
86+
{
87+
m_mixInfo.m_channels = _getAvailableSet();
88+
unsigned chCount = ChannelCount(m_mixInfo.m_channels);
89+
90+
m_mixInfo.m_sampleRate = 96000.0;
91+
m_mixInfo.m_sampleFormat = SOXR_FLOAT32_I;
92+
m_mixInfo.m_bitsPerSample = 32;
93+
m_5msFrames = 96000 * 5 / 1000;
94+
95+
boo::ChannelMap& chMapOut = m_mixInfo.m_channelMap;
96+
chMapOut.m_channelCount = 2;
97+
chMapOut.m_channels[0] = boo::AudioChannel::FrontLeft;
98+
chMapOut.m_channels[1] = boo::AudioChannel::FrontRight;
99+
100+
while (chMapOut.m_channelCount < chCount)
101+
chMapOut.m_channels[chMapOut.m_channelCount++] = boo::AudioChannel::Unknown;
102+
103+
m_mixInfo.m_periodFrames = 2400;
104+
105+
m_frameBytes = m_mixInfo.m_periodFrames * m_mixInfo.m_channelMap.m_channelCount * 4;
106+
}
107+
108+
void pumpAndMixVoices()
109+
{
110+
}
111+
};
112+
113+
@implementation AmuseAudioUnit
114+
- (id)initWithComponentDescription:(AudioComponentDescription)componentDescription
115+
options:(AudioComponentInstantiationOptions)options
116+
error:(NSError * _Nullable *)outError;
117+
{
118+
self = [super initWithComponentDescription:componentDescription options:options error:outError];
119+
if (!self)
120+
return nil;
121+
122+
AUAudioUnitBus* outBus = [[AUAudioUnitBus alloc] initWithFormat:
123+
[[AVAudioFormat alloc] initWithCommonFormat:AVAudioPCMFormatFloat32
124+
sampleRate:96000.0
125+
channels:2
126+
interleaved:TRUE]
127+
error:outError];
128+
if (!outBus)
129+
return nil;
130+
131+
m_outs = [[AUAudioUnitBusArray alloc] initWithAudioUnit:self busType:AUAudioUnitBusTypeOutput busses:@[outBus]];
132+
133+
self.maximumFramesToRender = 2400;
134+
return self;
135+
}
136+
137+
- (BOOL)allocateRenderResourcesAndReturnError:(NSError * _Nullable *)outError
138+
{
139+
if (![super allocateRenderResourcesAndReturnError:outError])
140+
return FALSE;
141+
142+
m_booBackend = std::make_unique<AudioUnitVoiceEngine>();
143+
if (!m_booBackend)
144+
{
145+
*outError = [NSError errorWithDomain:@"amuse" code:-1
146+
userInfo:@{NSLocalizedDescriptionKey:@"Unable to construct boo mixer"}];
147+
return FALSE;
148+
}
149+
150+
m_voxAlloc.emplace(*m_booBackend);
151+
m_engine.emplace(*m_voxAlloc);
152+
*outError = nil;
153+
return TRUE;
154+
}
155+
156+
- (void)deallocateRenderResources
157+
{
158+
m_engine = std::experimental::nullopt;
159+
m_voxAlloc = std::experimental::nullopt;
160+
m_booBackend.reset();
161+
[super deallocateRenderResources];
162+
}
163+
164+
- (BOOL)renderResourcesAllocated
165+
{
166+
if (m_engine)
167+
return TRUE;
168+
return FALSE;
169+
}
170+
171+
- (AUAudioUnitBusArray*)outputBusses
172+
{
173+
return m_outs;
174+
}
175+
176+
- (BOOL)musicDeviceOrEffect
177+
{
178+
return TRUE;
179+
}
180+
181+
- (NSInteger)virtualMIDICableCount
182+
{
183+
return 1;
184+
}
185+
186+
- (AUInternalRenderBlock)internalRenderBlock
187+
{
188+
AudioUnitVoiceEngine& voxEngine = static_cast<AudioUnitVoiceEngine&>(*m_booBackend);
189+
190+
return ^AUAudioUnitStatus(AudioUnitRenderActionFlags* actionFlags, const AudioTimeStamp* timestamp,
191+
AUAudioFrameCount frameCount, NSInteger outputBusNumber, AudioBufferList* outputData,
192+
const AURenderEvent* realtimeEventListHead, AURenderPullInputBlock pullInputBlock)
193+
{
194+
/* Process MIDI events first */
195+
if (voxEngine.m_midiReceiver)
196+
{
197+
for (const AUMIDIEvent* event = &realtimeEventListHead->MIDI ;
198+
event != nullptr ; event = &event->next->MIDI)
199+
{
200+
if (event->eventType == AURenderEventMIDI)
201+
{
202+
voxEngine.m_midiReceiver(std::vector<uint8_t>(std::cbegin(event->data),
203+
std::cbegin(event->data) + event->length));
204+
}
205+
}
206+
}
207+
208+
/* Output buffers */
209+
voxEngine.render(outputData);
210+
return noErr;
211+
};
212+
}
213+
@end
214+
215+
namespace amuse
216+
{
217+
218+
void RegisterAudioUnit()
219+
{
220+
AudioComponentDescription desc = {};
221+
desc.componentType = 'aumu';
222+
desc.componentSubType = 'amus';
223+
desc.componentManufacturer = 'AXDL';
224+
[AUAudioUnit registerSubclass:[AmuseAudioUnit class] asComponentDescription:desc name:@"Amuse" version:0100];
225+
}
226+
227+
}
228+
229+
#endif
230+
#endif

AudioUnit/AudioUnitViewController.hpp

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#ifndef __AMUSE_AUDIOUNIT_VIEWCONTROLLER_HPP__
2+
#define __AMUSE_AUDIOUNIT_VIEWCONTROLLER_HPP__
3+
4+
#import <CoreAudioKit/CoreAudioKit.h>
5+
6+
@interface AudioUnitViewController : AUViewController <AUAudioUnitFactory>
7+
8+
@end
9+
10+
#endif // __AMUSE_AUDIOUNIT_VIEWCONTROLLER_HPP__

0 commit comments

Comments
 (0)