2016-05-25 02:00:22 +00:00
|
|
|
#include "AudioUnitBackend.hpp"
|
|
|
|
#ifdef __APPLE__
|
|
|
|
#include <Availability.h>
|
|
|
|
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101100
|
|
|
|
#import <AudioUnit/AudioUnit.h>
|
|
|
|
#import <CoreAudioKit/CoreAudioKit.h>
|
|
|
|
#import <AVFoundation/AVFoundation.h>
|
|
|
|
|
|
|
|
#if !__has_feature(objc_arc)
|
|
|
|
#error ARC Required
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include "logvisor/logvisor.hpp"
|
|
|
|
#include "audiodev/AudioVoiceEngine.hpp"
|
|
|
|
|
|
|
|
static logvisor::Module Log("amuse::AudioUnitBackend");
|
|
|
|
|
|
|
|
struct AudioUnitVoiceEngine : boo::BaseAudioVoiceEngine
|
|
|
|
{
|
|
|
|
std::vector<std::unique_ptr<float[]>> m_renderBufs;
|
|
|
|
size_t m_frameBytes;
|
2016-05-26 01:28:50 +00:00
|
|
|
AudioBufferList* m_outputData = nullptr;
|
2016-05-25 02:00:22 +00:00
|
|
|
|
|
|
|
boo::AudioChannelSet _getAvailableSet()
|
|
|
|
{
|
|
|
|
return boo::AudioChannelSet::Stereo;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<std::pair<std::string, std::string>> enumerateMIDIDevices() const
|
|
|
|
{
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
boo::ReceiveFunctor m_midiReceiver = nullptr;
|
|
|
|
|
|
|
|
std::unique_ptr<boo::IMIDIIn> newVirtualMIDIIn(boo::ReceiveFunctor&& receiver)
|
|
|
|
{
|
|
|
|
m_midiReceiver = std::move(receiver);
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
std::unique_ptr<boo::IMIDIOut> newVirtualMIDIOut()
|
|
|
|
{
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
std::unique_ptr<boo::IMIDIInOut> newVirtualMIDIInOut(boo::ReceiveFunctor&& receiver)
|
|
|
|
{
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
std::unique_ptr<boo::IMIDIIn> newRealMIDIIn(const char* name, boo::ReceiveFunctor&& receiver)
|
|
|
|
{
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
std::unique_ptr<boo::IMIDIOut> newRealMIDIOut(const char* name)
|
|
|
|
{
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
std::unique_ptr<boo::IMIDIInOut> newRealMIDIInOut(const char* name, boo::ReceiveFunctor&& receiver)
|
|
|
|
{
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
AudioUnitVoiceEngine()
|
|
|
|
{
|
|
|
|
m_mixInfo.m_channels = _getAvailableSet();
|
|
|
|
unsigned chCount = ChannelCount(m_mixInfo.m_channels);
|
|
|
|
|
|
|
|
m_mixInfo.m_sampleRate = 96000.0;
|
|
|
|
m_mixInfo.m_sampleFormat = SOXR_FLOAT32_I;
|
|
|
|
m_mixInfo.m_bitsPerSample = 32;
|
|
|
|
m_5msFrames = 96000 * 5 / 1000;
|
|
|
|
|
|
|
|
boo::ChannelMap& chMapOut = m_mixInfo.m_channelMap;
|
|
|
|
chMapOut.m_channelCount = 2;
|
|
|
|
chMapOut.m_channels[0] = boo::AudioChannel::FrontLeft;
|
|
|
|
chMapOut.m_channels[1] = boo::AudioChannel::FrontRight;
|
|
|
|
|
|
|
|
while (chMapOut.m_channelCount < chCount)
|
|
|
|
chMapOut.m_channels[chMapOut.m_channelCount++] = boo::AudioChannel::Unknown;
|
|
|
|
|
|
|
|
m_mixInfo.m_periodFrames = 2400;
|
|
|
|
|
|
|
|
m_frameBytes = m_mixInfo.m_periodFrames * m_mixInfo.m_channelMap.m_channelCount * 4;
|
|
|
|
}
|
|
|
|
|
|
|
|
void pumpAndMixVoices()
|
|
|
|
{
|
2016-05-26 01:28:50 +00:00
|
|
|
if (m_renderBufs.size() < m_outputData->mNumberBuffers)
|
|
|
|
m_renderBufs.resize(m_outputData->mNumberBuffers);
|
|
|
|
|
|
|
|
for (int i=0 ; i<m_outputData->mNumberBuffers ; ++i)
|
|
|
|
{
|
|
|
|
std::unique_ptr<float[]>& buf = m_renderBufs[i];
|
|
|
|
AudioBuffer& auBuf = m_outputData->mBuffers[i];
|
|
|
|
if (!auBuf.mData)
|
|
|
|
{
|
|
|
|
buf.reset(new float[auBuf.mDataByteSize]);
|
|
|
|
auBuf.mData = buf.get();
|
|
|
|
}
|
|
|
|
|
|
|
|
_pumpAndMixVoices(auBuf.mDataByteSize / 2 / 4, reinterpret_cast<float*>(auBuf.mData));
|
|
|
|
}
|
2016-05-25 02:00:22 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
@implementation AmuseAudioUnit
|
|
|
|
- (id)initWithComponentDescription:(AudioComponentDescription)componentDescription
|
|
|
|
options:(AudioComponentInstantiationOptions)options
|
|
|
|
error:(NSError * _Nullable *)outError;
|
|
|
|
{
|
|
|
|
self = [super initWithComponentDescription:componentDescription options:options error:outError];
|
|
|
|
if (!self)
|
|
|
|
return nil;
|
|
|
|
|
|
|
|
AUAudioUnitBus* outBus = [[AUAudioUnitBus alloc] initWithFormat:
|
|
|
|
[[AVAudioFormat alloc] initWithCommonFormat:AVAudioPCMFormatFloat32
|
|
|
|
sampleRate:96000.0
|
|
|
|
channels:2
|
|
|
|
interleaved:TRUE]
|
|
|
|
error:outError];
|
|
|
|
if (!outBus)
|
|
|
|
return nil;
|
|
|
|
|
|
|
|
m_outs = [[AUAudioUnitBusArray alloc] initWithAudioUnit:self busType:AUAudioUnitBusTypeOutput busses:@[outBus]];
|
|
|
|
|
|
|
|
self.maximumFramesToRender = 2400;
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (BOOL)allocateRenderResourcesAndReturnError:(NSError * _Nullable *)outError
|
|
|
|
{
|
|
|
|
if (![super allocateRenderResourcesAndReturnError:outError])
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
m_booBackend = std::make_unique<AudioUnitVoiceEngine>();
|
|
|
|
if (!m_booBackend)
|
|
|
|
{
|
|
|
|
*outError = [NSError errorWithDomain:@"amuse" code:-1
|
|
|
|
userInfo:@{NSLocalizedDescriptionKey:@"Unable to construct boo mixer"}];
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_voxAlloc.emplace(*m_booBackend);
|
|
|
|
m_engine.emplace(*m_voxAlloc);
|
|
|
|
*outError = nil;
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)deallocateRenderResources
|
|
|
|
{
|
|
|
|
m_engine = std::experimental::nullopt;
|
|
|
|
m_voxAlloc = std::experimental::nullopt;
|
|
|
|
m_booBackend.reset();
|
|
|
|
[super deallocateRenderResources];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (BOOL)renderResourcesAllocated
|
|
|
|
{
|
|
|
|
if (m_engine)
|
|
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (AUAudioUnitBusArray*)outputBusses
|
|
|
|
{
|
|
|
|
return m_outs;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (BOOL)musicDeviceOrEffect
|
|
|
|
{
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSInteger)virtualMIDICableCount
|
|
|
|
{
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (AUInternalRenderBlock)internalRenderBlock
|
|
|
|
{
|
2016-05-26 01:28:50 +00:00
|
|
|
__block AudioUnitVoiceEngine& voxEngine = static_cast<AudioUnitVoiceEngine&>(*m_booBackend);
|
|
|
|
__block amuse::Engine& amuseEngine = *m_engine;
|
2016-05-25 02:00:22 +00:00
|
|
|
|
|
|
|
return ^AUAudioUnitStatus(AudioUnitRenderActionFlags* actionFlags, const AudioTimeStamp* timestamp,
|
|
|
|
AUAudioFrameCount frameCount, NSInteger outputBusNumber, AudioBufferList* outputData,
|
|
|
|
const AURenderEvent* realtimeEventListHead, AURenderPullInputBlock pullInputBlock)
|
|
|
|
{
|
|
|
|
/* Process MIDI events first */
|
|
|
|
if (voxEngine.m_midiReceiver)
|
|
|
|
{
|
|
|
|
for (const AUMIDIEvent* event = &realtimeEventListHead->MIDI ;
|
|
|
|
event != nullptr ; event = &event->next->MIDI)
|
|
|
|
{
|
|
|
|
if (event->eventType == AURenderEventMIDI)
|
|
|
|
{
|
|
|
|
voxEngine.m_midiReceiver(std::vector<uint8_t>(std::cbegin(event->data),
|
|
|
|
std::cbegin(event->data) + event->length));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Output buffers */
|
2016-05-26 01:28:50 +00:00
|
|
|
voxEngine.m_outputData = outputData;
|
|
|
|
amuseEngine.pumpEngine();
|
2016-05-25 02:00:22 +00:00
|
|
|
return noErr;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
|
|
namespace amuse
|
|
|
|
{
|
|
|
|
|
|
|
|
void RegisterAudioUnit()
|
|
|
|
{
|
|
|
|
AudioComponentDescription desc = {};
|
|
|
|
desc.componentType = 'aumu';
|
|
|
|
desc.componentSubType = 'amus';
|
|
|
|
desc.componentManufacturer = 'AXDL';
|
|
|
|
[AUAudioUnit registerSubclass:[AmuseAudioUnit class] asComponentDescription:desc name:@"Amuse" version:0100];
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|
|
|
|
#endif
|