Pause the audio file but let the delay effect continue

Hi there,

I have an audio file channel that loops something, and I have a delay filter. The loop is controlled by the user interactively.

What is the simplest way to achieve this: pause the loop but let the delay effect finish its job?

Here is what I currently do: I create a channel group and add my loop sound to it. Then I attach the delay effect to the channel group. The loop channel is controlled using the channelIsPlaying property. When I set it to NO the delay effect goes silent too, though theoretically it shouldn't. What am I doing wrong?

(There is another problem with this too, with the OS X port of the library: channel groups don't seem to work at all, but I'll try to diagnose it myself first. I'd appreciate though if anyone could give me a clue though)

Comments

  • Without modifying the library code, would feeding silence for the duration of kAudioUnitProperty_TailTime be a good solution?

  • Hmm, that sounds strange, @crontab. If I understand you correctly, this is very close to what the sample app does, and it's working as expected. Maybe take a look at that, and see if you can spot the difference?

  • @crontab,

    Without seeing your code, couldn't you just pause or mute the loop channel audio file, rather than the group channel that the loop channel and delay filter are added to?

  • @markjeschke, @Michael I do channelIsPlaying = NO on the channel, I suppose that's why the delay effect stops too: there is no data arriving. Mute sends silence, correct? So a proper way of doing this would be to mute the channel, wait a least kAudioUnitProperty_TailTime and only then set playing to NO.

  • @crontab I was suggesting to only mute the loop channel and not the group channel that the loop channel is added to. If the effect is applied to only the group channel, then the effect should continue while the loop channel is muted.

    For example, in TheEngineExample, provided with TAAE:

    @interface ViewController () {
        AEChannelGroupRef _group;
    }
    @property (nonatomic, retain) AEAudioFilePlayer *loop1;
    @property (nonatomic, retain) AEAudioUnitFilter *delay;
    @end
    
    ***
    
    - (id)initWithAudioController:(AEAudioController*)audioController {
        if ( !(self = [super initWithStyle:UITableViewStyleGrouped]) ) return nil;
        
        self.audioController = audioController;
        
        // Create the first loop player
        self.loop1 = [AEAudioFilePlayer audioFilePlayerWithURL:[[NSBundle mainBundle] URLForResource:@Southern Rock Drums withExtension:@m4a]
                                               audioController:_audioController
                                                         error:NULL];
        _loop1.volume = 1.0;
        _loop1.channelIsMuted = YES; // Loop is muted by default.
        _loop1.loop = YES;
    
        // Create a group channel and add _loop1 to the group.
        _group = [_audioController createChannelGroup];
        [_audioController addChannels:[NSArray arrayWithObjects:_loop1, nil] toChannelGroup:_group];
    
        // Create the delay effect filter
        _delay = [[[AEAudioUnitFilter alloc] initWithComponentDescription:AEAudioComponentDescriptionMake(kAudioUnitManufacturer_Apple, kAudioUnitType_Effect, kAudioUnitSubType_Delay) audioController:_audioController error:NULL] autorelease]; 
        // Remove autorelease, if using ARC.
    
        // Set its parameters
            
        // Wet/Dry Mix
        // Global, EqPow Crossfade, 0->100, default is 50
        AudioUnitSetParameter(_delay.audioUnit, kDelayParam_WetDryMix, kAudioUnitScope_Global, 0, 50, 0);
            
        // Delay Time
        // Global, Secs, 0->2, default is 1
        AudioUnitSetParameter(_delay.audioUnit, kDelayParam_DelayTime, kAudioUnitScope_Global, 0, 0.5, 2);
            
        // Delay Feedback
        // Global, Percent, -100->100, default is 50
        AudioUnitSetParameter(_delay.audioUnit, kDelayParam_Feedback, kAudioUnitScope_Global, 0, 35, 3);
            
        // Lopass Cutoff
        // Global, Hz, 10->(SampleRate/2), default is 15000
        AudioUnitSetParameter(_delay.audioUnit, kDelayParam_LopassCutoff, kAudioUnitScope_Global, 0, 1500, 4);
    
        // Add delay effect to the _group channel, not the _loop1 channel. 
        // That way, when you mute the _loop1, the delay feedback should complete its echo.
        [_audioController addFilter:_delay toChannelGroup:_group];
    
    }
    
    
  • @crontab, You may also want to change the loop to utilize channelIsMuted = NO, rather than channelIsPlaying = NO. Again, this should be applied to only the loop channel, and not the group channel.

    You probably caught this already, but I just wanted to be sure.

  • edited August 2015

    @markjeschke that's a looping sound, that's why it works. Try not to loop it but instead just let the channel finish and autostop, and the delay effect will stop receiving data. I still think feeding silence is the only solution here.

    Another thing that works is adding the filter to the top of the tree rather than to the channel or a group. That also works, as the root node is always receiving data.

  • edited August 2015

    @crontab Oh, I see what you're saying. Here's a solution for adding audio effects with tails to a specific group channel, rather than the entire _audioController output: Try adding an "empty" AEAudioUnitChannel object to the group channel array.

    For instance, in TheEngineSample replace the current code at line #110 with this:

        // Create an audio unit channel (a file player)
        self.audioUnitPlayer = [[AEAudioUnitChannel alloc] initWithComponentDescription:AEAudioComponentDescriptionMake(kAudioUnitManufacturer_Apple, kAudioUnitType_Generator, kAudioUnitSubType_AudioFilePlayer)];
        
        // Create an empty audio unit channel to keep the effect tail intact
        self.emptyAudioChannel = [[AEAudioUnitChannel alloc] initWithComponentDescription:AEAudioComponentDescriptionMake(kAudioUnitManufacturer_Apple, kAudioUnitType_Generator, kAudioUnitSubType_AudioFilePlayer)];
        
        // Create a group for loop1, loop2 and oscillator and add the _emptyAudioChannel to the _group
        _group = [_audioController createChannelGroup];
        [_audioController addChannels:@[_loop1, _loop2, _oscillator, _emptyAudioChannel] toChannelGroup:_group];
        
        // Create the delay effect filter
        _delay = [[AEAudioUnitFilter alloc] initWithComponentDescription:AEAudioComponentDescriptionMake(kAudioUnitManufacturer_Apple, kAudioUnitType_Effect, kAudioUnitSubType_Delay)];
        
        // Set its parameters
        
        // Wet/Dry Mix
        // Global, EqPow Crossfade, 0->100, default is 50
        AudioUnitSetParameter(_delay.audioUnit, kDelayParam_WetDryMix, kAudioUnitScope_Global, 0, 50, 0);
        
        // Delay Time
        // Global, Secs, 0->2, default is 1
        AudioUnitSetParameter(_delay.audioUnit, kDelayParam_DelayTime, kAudioUnitScope_Global, 0, 0.5, 2);
        
        // Delay Feedback
        // Global, Percent, -100->100, default is 50
        AudioUnitSetParameter(_delay.audioUnit, kDelayParam_Feedback, kAudioUnitScope_Global, 0, 35, 3);
        
        // Lopass Cutoff
        // Global, Hz, 10->(SampleRate/2), default is 15000
        AudioUnitSetParameter(_delay.audioUnit, kDelayParam_LopassCutoff, kAudioUnitScope_Global, 0, 1500, 4);
        
        // Add delay effect to the _group channel, not the _loop1 channel.
        // That way, when you mute the _loop1, the delay feedback should complete its echo.
        [_audioController addFilter:_delay toChannelGroup:_group];
        
        [_audioController addObserver:self forKeyPath:@numberOfInputChannels options:0 context:(void*)&kInputChannelsChangedContext];
        
        return self;
    
    


    Please be sure to add the following properties at the top of ViewController.m:

    @property (nonatomic, strong) AEAudioUnitChannel *emptyAudioChannel;
    @property (nonatomic, strong) AEAudioUnitFilter *delay;
    


    In the -(void) dealloc method, be sure to remove the _emptyAudioChannel:

    NSMutableArray *channelsToRemove = [NSMutableArray arrayWithObjects:_loop1, _loop2, _emptyAudioChannel, nil];
    


    If you'd rather not add a new _emptyAudioChannel object to TheEngineSample, you can simply use the existing _audioUnitPlayer to keep the _group channel open:

        // Create a group for loop1, loop2 and oscillator and add the _audioUnitPlayer to the _group
        _group = [_audioController createChannelGroup];
        [_audioController addChannels:@[_loop1, _loop2, _oscillator, _audioUnitPlayer] toChannelGroup:_group];
     


    This will also add the delay effect to the One Shot (Audio Unit) play trigger, since the delay has been added to the _group channel.

    I've attached the ViewController.m for your convenience. I hope it's not too hacky, but I believe that it does achieve the effect that you're looking for... Pun, intended. :)

    Good luck,

    Mark

  • @crontab, is that what you meant by "feeding silence?"

  • edited August 2015

    [Removed]

    Sorry, I posted something and realized there is a better way of doing that :)

  • @crontab said:
    [Removed]

    Sorry, I posted something and realized there is a better way of doing that :)

    @crontab Okay. I'm unsure which part you're referring to. What's the better way?

  • @markjeschke I posted a solution with AEBlockChannel, then realized there are better ways, including yours. Apparently though you can't delete comments here.

Sign In or Register to comment.