Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AppleSampler crash on DSPBase.mm #20

Closed
NickCulbertson opened this issue Oct 4, 2022 · 3 comments
Closed

AppleSampler crash on DSPBase.mm #20

NickCulbertson opened this issue Oct 4, 2022 · 3 comments
Labels
bug Something isn't working

Comments

@NickCulbertson
Copy link
Member

NickCulbertson commented Oct 4, 2022

macOS Version(s) Used to Build

macOS 12 Monterey

Xcode Version(s)

Xcode 13.4.1

Description

If noteOn events are called while loading AudioKit's AppleSampler() it can occasionally crash on line 121 of DSPBase.mm. Here is a test project to illustrate the crash: https://github.com/NickCulbertson/TestRenderBlock

This is the current AUInternalRenderBlock:

AUInternalRenderBlock DSPBase::internalRenderBlock()
{
    return ^AUAudioUnitStatus(
        AudioUnitRenderActionFlags *actionFlags,
        const AudioTimeStamp       *timestamp,
        AUAudioFrameCount           frameCount,
        NSInteger                   outputBusNumber,
        AudioBufferList            *outputData,
        const AURenderEvent        *realtimeEventListHead,
        AURenderPullInputBlock __unsafe_unretained pullInputBlock)
    {

        assert( (outputBusNumber == 0) && "We don't yet support multiple output busses" );
        if (pullInputBlock) {
            if (bCanProcessInPlace && inputBufferLists.size() == 1) {
                // pull input directly to output buffer
                inputBufferLists[0] = outputData;
                AudioUnitRenderActionFlags inputFlags = 0;
                pullInputBlock(&inputFlags, timestamp, frameCount, 0, inputBufferLists[0]);
            }
            else {
                // pull input to internal buffer
                for (size_t i = 0; i < inputBufferLists.size(); i++) {
                    inputBufferLists[i] = internalBufferLists[i];
                    
                    UInt32 byteSize = frameCount * sizeof(float);
                    for (UInt32 ch = 0; ch < inputBufferLists[i]->mNumberBuffers; ch++) {
                        inputBufferLists[i]->mBuffers[ch].mDataByteSize = byteSize;
                    }
                    
                    AudioUnitRenderActionFlags inputFlags = 0;
                    pullInputBlock(&inputFlags, timestamp, frameCount, i, inputBufferLists[i]);  //CRASH HERE
                }
            }
        }
        
        outputBufferList = outputData;
        
        processWithEvents(timestamp, frameCount, realtimeEventListHead);
        
        return noErr;
    };
}

Just a thought, but this is Apple's generated render block in DSPKernel when making an AUv3. It might be useful for comparison:

// MARK: -  AUAudioUnit (AUAudioUnitImplementation)

// Subclassers must provide a AUInternalRenderBlock (via a getter) to implement rendering.
- (AUInternalRenderBlock)internalRenderBlock {
    /*
     Capture in locals to avoid ObjC member lookups. If "self" is captured in
     render, we're doing it wrong.
     */
    // Specify captured objects are mutable.
    __block OverdriveSynthDSPKernel *state = &_kernel;
    __block BufferedInputBus *input = &_inputBus;

    return ^AUAudioUnitStatus(AudioUnitRenderActionFlags 				*actionFlags,
                              const AudioTimeStamp       				*timestamp,
                              AVAudioFrameCount           				frameCount,
                              NSInteger                   				outputBusNumber,
                              AudioBufferList            				*outputData,
                              const AURenderEvent        				*realtimeEventListHead,
                              AURenderPullInputBlock __unsafe_unretained pullInputBlock) {

        AudioUnitRenderActionFlags pullFlags = 0;

        if (frameCount > state->maximumFramesToRender()) {
            return kAudioUnitErr_TooManyFramesToProcess;
        }

        AUAudioUnitStatus err = input->pullInput(&pullFlags, timestamp, frameCount, 0, pullInputBlock);

        if (err != noErr) { return err; }

        AudioBufferList *inAudioBufferList = input->mutableAudioBufferList;

        /*
         Important:
         If the caller passed non-null output pointers (outputData->mBuffers[x].mData), use those.

         If the caller passed null output buffer pointers, process in memory owned by the Audio Unit
         and modify the (outputData->mBuffers[x].mData) pointers to point to this owned memory.
         The Audio Unit is responsible for preserving the validity of this memory until the next call to render,
         or deallocateRenderResources is called.

         If your algorithm cannot process in-place, you will need to preallocate an output buffer
         and use it here.

         See the description of the canProcessInPlace property.
         */

        // If passed null output buffer pointers, process in-place in the input buffer.
        AudioBufferList *outAudioBufferList = outputData;
        if (outAudioBufferList->mBuffers[0].mData == nullptr) {
            for (UInt32 i = 0; i < outAudioBufferList->mNumberBuffers; ++i) {
                outAudioBufferList->mBuffers[i].mData = inAudioBufferList->mBuffers[i].mData;
            }
        }

        state->setBuffers(inAudioBufferList, outAudioBufferList);
        state->processWithEvents(timestamp, frameCount, realtimeEventListHead, nil /* MIDIOutEventBlock */);

        return noErr;
    };
}
@NickCulbertson NickCulbertson added the bug Something isn't working label Oct 4, 2022
@NickCulbertson
Copy link
Member Author

This render block is doing a lot so I'll leave this fix to someone with more experience in DSP.

@emurray2
Copy link
Member

This is an AVAudioUnitSampler bug which should be added to AppleBugDemos. The error in the debugger isn't coming from anything AudioKit related.

2022-11-17 12:00:09.266429-0500 TestRenderBlock[12635:250156]             VoiceZone.cpp:1244  VoiceZone::GetControlSource: invalid component ID: 0x7
2022-11-17 12:00:09.266748-0500 TestRenderBlock[12635:250156] [default]             VoiceZone.cpp:1245  about to throw -10879: VoiceZone::GetControlSource: Invalid connection source

The only info we get is the error code -10879, kAudioUnitErr_InvalidProperty. However, it's difficult to know what causes the connection source in the component to be invalid because we don't know what the sampler does in its internal method, loadInstrument(at instrumentURL: URL).

@NickCulbertson
Copy link
Member Author

Yeah, I just didn't know if we needed to add a return somewhere in the render block to avoid trying to render a null object. Unfortunately, it is difficult to recreate the specific errors I was seeing from it in production but it usually centered around trying to play notes right before or soon after changing AVAudioUnitSampler instruments. I'll reopen it is I get more info.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants