Playing a Sound in a Block Channel from a File

edited April 2014

Hi Guys:

a big Salute to @Michael and all of you TAAE Members :-)

I'm Struggling with an Issue for OVER a WEEK and Before I Lose the Little hair I Got Left I Decided to consult with you...

I'm Trying to create a Block Channel that Plays an Audio File, I'm On my way to Building a Metronome and I Can't Seem to Get my head Around this:

i Know I Need to Load a Sound with the AEAudioFileLoaderOperation Class and Once Loaded Start Using that Buffer and Add frames from the Sound to the Block Channel in Order for it to Play... But Once I Load the Sound with the Operation... WHAT DO I DO?

To put it in a Simpler Way: I'd Like to make a Block Channel like in the TAAE Sample, but Instead of Playing an Oscillator, I'd Like to Play a FILE...

The Original Sample Code for the Oscillator:
/////////

// Create a block-based channel, with an implementation of an oscillator
__block float oscillatorPosition = 0;
__block float oscillatorRate = 622.0/44100.0;
self.oscillator = [AEBlockChannel channelWithBlock:^(const AudioTimeStamp  *time,
                                                           UInt32           frames,
                                                           AudioBufferList *audio) {
    for ( int i=0; i<frames; i++ ) {
        // Quick sin-esque oscillator
        float x = oscillatorPosition;
        x *= x; x -= 1.0; x *= x;       // x now in the range 0...1
        x *= INT16_MAX;
        x -= INT16_MAX / 2;
        oscillatorPosition += oscillatorRate;
        if ( oscillatorPosition > 1.0 ) oscillatorPosition -= 2.0;

        ((SInt16*)audio->mBuffers[0].mData)[i] = x;
        ((SInt16*)audio->mBuffers[1].mData)[i] = x;
    }
}];
_oscillator.audioDescription = [AEAudioController nonInterleaved16BitStereoAudioDescription];

_oscillator.channelIsMuted = YES;

//////////////////////

And My Code would go Something Like:

//////////////////////

- (void) startMetronome:(int)BPM
{
    self.beatCount = 0;
    self.bpm = BPM;
    self.speed = SECONDS/self.bpm;
    
    // Load the Sounds:
    
    // Fill in your Help HERE :-)
    /////    // The total frames that have passed
    //
    static UInt64 total_frames = 0;
    // The next frame that the beat will play on
    static UInt64 next_beat_frame = 0;
    // YES if we are currently sounding a beat
    static BOOL making_beat = NO;
    
    __block float filePosition = 0; // this is outside the block since beats can span calls to the block
    
    // The block that is our metronome
    self.blockChannel = [AEBlockChannel channelWithBlock:^(const AudioTimeStamp *time, UInt32 frames, AudioBufferList *audio) {
        
        // How many frames pass between the start of each beat
        UInt64 frames_between_beats = 44100.0/(self.bpm/60.0);

        // For each frame, count and if we reach the frame that should start a beat, start the beat
        for (int i=0; imBuffers[0].mData)[i] = x;
                     ((SInt16*)audio->mBuffers[1].mData)[i] = x;
                     */
                }else
                {
                    [self playClick:NO]; /// I WANT TO PLAY THE LOADED FILE #2 (Downbeat Sound)
                    // Fill In Your HELP HERE :-)
                    if (self.beatCount == 4) {
                        self.beatCount = 0;
                    }
                }
            }
            
            // Increment the count
            total_frames++;
        }
    }];

    // Add the block channel to the audio controller
    [[LSAudioManager sharedAudioManager].audioController addChannels:[NSArray arrayWithObject:_blockChannel]];
    
}

/////////////////////////

THANKS To Anyone That Can Enlighten Me In this Dark Hour (Its literally 2:30 aM Here)

Hernan

PS: I've Had Great Help from @zobkiw Post on a SampleRate-Based Metronome: http://forum.theamazingaudioengine.com/discussion/comment/1147#Comment_1147

Comments

  • Hi Hernan,

    This is how I do it:

    //Load audio file:
    AEAudioFileLoaderOperation *operation = [[AEAudioFileLoaderOperation alloc] initWithFileURL:[[NSBundle mainBundle] URLForResource:@"guitar" withExtension:@"caf"]
                                                                         targetAudioDescription:audioController.audioDescription];
    [operation start];
    if ( operation.error ) {
        NSLog(@"load error: %@", operation.error);
        return;
    }
    
    AudioBufferList *audioSampleBufferList = operation.bufferList;
    
    //Play the audio file using an AEBlockChannel:
    audioFileChannel = [AEBlockChannel channelWithBlock:^(const AudioTimeStamp *time, UInt32 frames, AudioBufferList *audio) {
    
        static UInt32 playHead;
    
        for (int i=0; i<frames; i++) {
    
            for ( int j=0; j<audio->mNumberBuffers; j++ ) {
    
                bool shouldLoop = true;
    
                if (playHead < operation.lengthInFrames) {
                    ((float *)audio->mBuffers[j].mData)[i] = ((float *)audioSampleBufferList->mBuffers[j].mData)[playHead + i];
                }
                else if (shouldLoop) {
                    playHead = 0;
                }
                else {
                    ((float *)audio->mBuffers[j].mData)[i] = 0;
                }
            }
        }
    
        playHead += frames;
    
    }];
    

    Take a look at these too:
    http://theamazingaudioengine.com/doc/_other-_facilities.html#Reading-Audio
    http://forum.theamazingaudioengine.com/discussion/comment/259#Comment_259

  • Hi AriVocals:

    OK, I'll give this a Try and Get Back to you. Thank you very Much :-)

  • do you guys know how to stop executing the block?

  • For anyone looking at this thread: I just tried @AriVocals' solution and it was very helpful, but there was just a couple of fixes I had to make for it to sound without glitches and avoid crashes:

    1) make sure you don't try to access the audio buffer past the buffer's length
    2) cast to SInt16*

                if ((playHead + i) < operation.lengthInFrames) {
                    ((SInt16*)audio->mBuffers[j].mData)[i] = ((SInt16*)audioSampleBufferList->mBuffers[0].mData)[playHead + i];
                }
    
  • edited November 2015

    Here is what I did to get a metronome working correctly, combining bits from the other examples.

        self.audioController = [[AEAudioController alloc]
                                initWithAudioDescription:[AEAudioController nonInterleaved16BitStereoAudioDescription]
                                inputEnabled:NO];
        
        NSError *error = NULL;
        BOOL result = [_audioController start:&error];
        if (!result) {
            NSLog(@_audioController start error: %@", [error localizedDescription]);
            return;
        }
        
        _bpm = 130;
        
        // The total frames that have passed
        static UInt64 total_frames = 0;
        
        // The next frame that the beat will play on
        static UInt64 next_beat_frame = 0;
        
        // YES if we are currently sounding a beat
        static BOOL making_beat = NO;
        
        //Load audio file:
        AEAudioFileLoaderOperation *operation = [[AEAudioFileLoaderOperation alloc]
                                                 initWithFileURL:[[NSBundle mainBundle] URLForResource:@CLK_LOGIC2 withExtension:@wav]
                                                 targetAudioDescription:_audioController.audioDescription];
        [operation start];
        if ( operation.error ) {
            NSLog(@load error: %@", operation.error);
            return;
        }
        
        AudioBufferList *audioSampleBufferList = operation.bufferList;
    
        static UInt32 playHead;
        
        self.channel = [AEBlockChannel channelWithBlock:^(const AudioTimeStamp *time, UInt32 frames, AudioBufferList *audio) {
    
            UInt64 frames_between_beats = 44100/(_bpm/60);
            
            for (int i=0; i < frames; i++) {
                
                if (next_beat_frame == total_frames) {
                    making_beat = YES;
                    playHead = 0;
                    next_beat_frame += frames_between_beats;
                }
    
                if (making_beat) {
                    for ( int j=0; j < audio->mNumberBuffers; j++ ) {
                        if ( playHead < operation.lengthInFrames ) {
                            ((SInt16 *)audio->mBuffers[j].mData)[i] = ((SInt16 *)audioSampleBufferList->mBuffers[0].mData)[playHead+1];
                        } else {
                            making_beat = NO;
                            ((SInt16 *)audio->mBuffers[j].mData)[i] = 0;
                        }
                    }
                }
    
                total_frames++;
                playHead++;
            }
        }];
        
        [_audioController addChannels:[NSArray arrayWithObject:_channel]];
    
Sign In or Register to comment.