Attention! For Japanese customers, my Japanese blog might help.

Aug 2, 2016

pass through input data to output on v3 effect AudioUnit

NOTE: This is the English version of my Japanese blog.

My v3 AudioUnit now got to be able to pass input data to output.

By the way, I use AUv3Host.app as a host application by building a sample code at AudioUnitV3Example: A Basic AudioUnit Extension and Host Implementation.

When I built an AudioUnit App Extension with codes, which were generated by a template target in Xcode without any modifications,
the AudioUnit did not work with an error in AUv3Host.app:
AVAudioNode.mm:747: AUSetFormat: error -10877
It was seemed that a custom AudioUnit class (MyAudioUnit) needs to override two methods: inputBusses and outputBusses.

For now,  I overrode these methods by reference to FilterDemo in AudioUnitv3Example as
- (AUAudioUnitBusArray *)inputBusses {
    AUAudioUnitBusArray *buses = [[AUAudioUnitBusArray allocinitWithAudioUnit:self
                                                                        busType:AUAudioUnitBusTypeInput
                                                                         busses:@[self.inputBus]];
    return buses;
}

- (AUAudioUnitBusArray *)outputBusses {
    AUAudioUnitBusArray *buses = [[AUAudioUnitBusArray allocinitWithAudioUnit:self
                                                                        busType:AUAudioUnitBusTypeOutput
                                                                         busses:@[self.outputBus]];
    return buses;
}
where,  inputBus and outputBus were defined as
@property (nonatomicAUAudioUnitBus *inputBus;
@property (nonatomicAUAudioUnitBus *outputBus;
and initialized in initWithComponentDescription:options:error: as follows.
const UInt32 numCh = 2;
    
AVAudioFormat *defaultFormat = [[AVAudioFormat allocinitStandardFormatWithSampleRate:44100.0 channels:numCh];
NSError *error = nil;
self.inputBus = [[AUAudioUnitBus allocinitWithFormat:defaultFormat
                                                     error:&error];
if (error) {
    NSLog(@"failed to make input bus. error:%@", error);
    return nil;
}
    
self.outputBus = [[AUAudioUnitBus allocinitWithFormat:defaultFormat
                                                     error:&error];
if (error) {
   NSLog(@"failed to make output bus. error:%@", error);
    return nil;
}
As a result, the error was got to not occured in AUv3Host.app.

However, no sound was output.
Next, it is seemed to need to set data to output buffer by overriding internalRenderBlock.

Because my first objective is to pass input data to output, I firstly implemented as
AUAudioUnitStatus status = pullInputBlock(actionFlags, timestamp, frameCount, 0, outputData);
But, my AU did not work as I expected.
I don't know the reason yet. (Because debugging an app exntension is to annoying!)

By referring FilterDemo again, I overrode internalRenderBlock as
- (AUInternalRenderBlock)internalRenderBlock {
    return ^AUAudioUnitStatus(AudioUnitRenderActionFlags *actionFlags, const AudioTimeStamp *timestamp, AVAudioFrameCount frameCount, NSInteger outputBusNumber, AudioBufferList *outputData, const AURenderEvent *realtimeEventListHead, AURenderPullInputBlock pullInputBlock) {
        
        AUAudioUnitStatus status = pullInputBlock(actionFlags, timestamp, frameCount, 0self.list);
        
        if (status != noErr)
            return status;
        
        for (UInt32 b = 0; b < outputData->mNumberBuffers; b++) {
            if (outputData->mBuffers[b].mDataByteSize < sizeof(float) * frameCount) {
                NSLog(@"invalid output data size");
                return kAudioUnitErr_TooManyFramesToProcess;
            }
        
            if (self.list->mBuffers[b].mDataByteSize < sizeof(float) * frameCount) {
                NSLog(@"invalid input data size");
                return kAudioUnitErr_TooManyFramesToProcess;
            }

            memcpy(outputData->mBuffers[b].mDataself.list->mBuffers[0].mDatasizeof(float) * frameCount);
        }
        return noErr;
    };
}
where, the definition of list is
@property (nonatomicAudioBufferList *list;
and the list is also initialized in initWithComponentDescription:options:error: as
self.list = malloc(sizeof(AudioBufferList) + sizeof(AudioBuffer) * (numCh - 1));
    
const UInt32 numMaxFrames = 1024;
for (UInt32 ch = 0; ch < numCh; ch++) {
        self.list->mNumberBuffers = numCh;
        self.list->mBuffers[ch].mNumberChannels = 1;
        self.list->mBuffers[ch].mDataByteSize = numMaxFrames * sizeof(float);
        self.list->mBuffers[ch].mData = malloc(numMaxFrames * sizeof(float));
}
which is to say allocating buffers.

Because I set default fomats(canonical, non-interleaved, float) to input/output busses,
I allocated extra AudioBuffers for the number of channel - 1, and I hard-coded the type of buffer as float.

Finaly, my AudioUnit got to be able to pass input audio data to output.

By the way, I could not figure out how/when to use allocateRenderResourcesAndReturnError: and deallocateRenderResources.
I could not the differences between init... and dealloc.

To figure out that, I will take a look at FilterDemo.