Play and stop a sound sample on iOS (Novocaine) - ios

I'm trying to make a simple Drumpad app. The app needs to be super fast and play the sound with as little latency as possible. The premise is store audio samples in an array and each one is played when you hit a pad.
The catch is that some pads are chords (and not drums) so they need to stop right away On Touch Up.
Here are the approaches I have tried:
System Sound - Very simple to implement, super responsive, no way to stop sounds without destroying them, sounds cant be more than 30 seconds.
AV Framework - Way to slow.
The Amazing Audio Engine - Seems nice, but not sure what the advantage is over CoreAudio as setup was rather complicated I wasn't able to get a sound to play. Also not sure about latency.
Novocaine - I settled on this for now, it seems very fast, and I can play sounds, but I've seen no way to stop them once they start. I don't know how I can stop a single sound without stopping the whole audioManager?
- (void) playSoundN:(int)padNum
{
__weak ViewController * wself = self;
NSURL *inputFileURL = [[NSBundle mainBundle] URLForResource:#"Drum - Kick" withExtension:#"wav"];
self.fileReader = [[AudioFileReader alloc]
initWithAudioFileURL:inputFileURL
samplingRate:self.audioManager.samplingRate
numChannels:self.audioManager.numOutputChannels];
[self.fileReader play];
self.fileReader.currentTime = 0.0;
[self.audioManager setOutputBlock:^(float *data, UInt32 numFrames, UInt32 numChannels)
{
[wself.fileReader retrieveFreshAudio:data numFrames:numFrames numChannels:numChannels];
NSLog(#"Time: %f", wself.fileReader.currentTime);
}];
[self.audioManager play];
}
Seems like a simple task, starting and stoping a sound with super low latency.
Should I use one of the above approaches or just bite the bullet and dive into Core Audio Units.
Anyone have any ideas?

I just made an app that has similar functionality to what you are trying to accomplish. My recommendation:
Use AVAudioPlayer (and AVAudioRecorder for recording).
Use the prepareToPlay method of the class to preload the sound and reduce lag.
You can read more about the classes in the AVFoundation documentation:
https://developer.apple.com/library/ios/DOCUMENTATION/AVFoundation/Reference/AVFoundationFramework/_index.html

Surprisedly, alexbw accepted the patch quickly. If you updated to newest code, you may simply use following lines in output block:
[weakSelf.reader retrieveFreshAudio:data numFrames:numFrames numChannels:numChannels];
if (!weakSelf.reader.playing) {
weakSelf.audioManager.outputBlock = nil;
dispatch_async(dispatch_get_main_queue(), ^{
// UI stuff
});
}
Novocaine cannot tell EOF of the file, the last buffer is used repeat.
Considering the author think AudioFileReader is just a piece of sugar and not likely to fix it, here is a dirty solution.
__block float lastTimeLeft = 0.0f;
[self.audioManager setOutputBlock:^(float *data, UInt32 numFrames, UInt32 numChannels) {
float timeLeft = weakSelf.reader.duration - [weakSelf.reader getCurrentTime];
if ( timeLeft > 0.01 && timeLeft != lastTimeLeft ) {
lastTimeLeft = timeLeft;
[weakSelf.reader retrieveFreshAudio:data numFrames:numFrames numChannels:numChannels];
} else {
[weakSelf.reader retrieveFreshAudio:data numFrames:numFrames numChannels:numChannels]; //forget this line will cause a buzz at the end.
[weakSelf.reader pause];
weakSelf.audioManager.outputBlock = nil;
}
}];
A better solution would be to patch the AudioFileReader.

Related

Is it possible to play multiple sound files simultaneously (like a mix) on Apple Watch?

This is the code I am using to play a sound with AVAudioPlayerNode. It is just playing the last sound of the beats array. On iPhone, all sounds from the beats array are playing simultaneously with the same function.
-(void)playMix{
for (int i = 0; i< mix.beatsArray.count;i++) {
beat = [[WatchBeatObject alloc] initWithFileName:mix.beatsArray[i].fileName];
[beat.audioPlayerNode play];
[beat.audioPlayerNode scheduleBuffer:beat.buffer atTime:nil options:AVAudioPlayerNodeBufferLoops completionHandler:^{
}];
}
}
N.B: Method initWithFileName: handles initializing and creating AVAudioPlayerNode and everything needed.
Thank you in advance.
Does that WatchBeatObject has any category option in AVAudioSession to set AVAudioSessionCategoryAmbient? If yes, set it, they would mix different sound.

No audio in video recording (using GPUImage) after initializing The Amazing Audio Engine

I'm using two third party tools in my project. One is "The Amazing Audio Engine". I use this for audio filters. The other is GPUImage, or more specifically, GPUImageMovieWriter. When I record videos, I merge an audio recording with the video. This works fine. However, sometimes I do not use The Amazing Audio Engine and just record a normal video using GPUImageMovieWriter. The problem is, even just after initializing The Amazing Audio Engine, the video has only a fraction of a second of audio at the beginning, and then the audio is gone.
+ (STAudioManager *)sharedManager
{
static STAudioManager *manager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (!manager)
{
manager = [[STAudioManager alloc] init];
manager.audioController = [[AEAudioController alloc] initWithAudioDescription:[AEAudioController nonInterleaved16BitStereoAudioDescription] inputEnabled:YES];
manager.audioController.preferredBufferDuration = 0.005;
manager.audioController.voiceProcessingEnabled = YES;
manager.audioController.useMeasurementMode = YES;
}
});
return manager;
}
Something is happening when TAAE is initialized. My suspicion is it's something having to do with AVAudioSession as it's a sharedInstance. Any help would be amazing.
Naturally when I finally buckle and post a question, I find my problem.
Setting the preferredBufferDuration to 0.005 is a bit excessive. Removing this line solved my problem.
You can learn more about preferredBufferDuration here.

AVAudioPlayer currentTime lag issue

When I change the currentTime of AVAudioPlayer after pausing the player, it gives a lag (some time in positive and some time in negative).
[self.bookAudioPlayer pause];
[self.bookAudioPlayer setCurrentTime:[currentPage.audioStartTime doubleValue]];
[self.bookAudioPlayer prepareToPlay];
When I print the currentTime and audioStartTime it prints values with slight difference. For example,
audioStartTime : 2.203665, currentTime : 2.194286
audioStartTime : 137.521347, currentTime : 137.508571
I have tried to fix it using the following code but results stay the same.
- (void)fixSetCurentTime:(NSTimeInterval)newTime {
self.currentTime = newTime;
if (self.currentTime != newTime) {
[self prepareToPlay];
self.currentTime = newTime;
}
}
Has anyone experienced this issue? Any pointers for a possible fix?
Note that the MP3 audio frames are about 26 milliseconds long. So, block-based audio compression formats are only (re)startable on time-quantized block boundaries... And the nearest block start might be earlier (or later) in time.
Credit: Apple Developer Forum User (Reference)

AVCaptureSession restart recording performance optimization

I have following code which performs stop running (video) session, delete last segment of video, store video to directory and starts new video recording. There is (naturally) gap between these two video segments. Is there any way to optimize this code (maybe assync optimization if possible)? By optimization I mean elimination time gap as much as possible between these two video segments, thank you.
- (void) restartVideoRecording {
[captureSession removeOutput:captureMovieOutput];
[captureMovieOutput stopRecording];
if(lastPathWasOne){
captureMoviePath = [[URLPathProvider getUrlPathProvider]videoTwoPathString];
[URLPathProvider deleteFileAtStringPath:captureMoviePath];
lastPathWasOne = NO;
} else {
captureMoviePath = [[URLPathProvider getUrlPathProvider]videoOnePathString];
[URLPathProvider deleteFileAtStringPath:captureMoviePath];
lastPathWasOne = YES;
}
captureMovieURL = [[NSURL alloc] initFileURLWithPath:captureMoviePath];
[captureSession addOutput:captureMovieOutput];
[captureMovieOutput startRecordingToOutputFileURL:captureMovieURL recordingDelegate:self];
}
[NSTimer scheduledTimerWithTimeInterval:loopDuration target:self selector:#selector(restartVideoRecording) userInfo:nil repeats:NO];}
Thank you very much
Yes, you can. Store captured samples in NSTemporaryDirectory and use AVMutableComposition to merge assets at the end of recording session.
there is a Sample code please check this also

How do I audio crossfade using cocoalibspotify?

I'd like to crossfade from one track to the next in a Spotify enabled app. Both tracks are Spotify tracks, and since only one data stream at a time can come from Spotify, I suspect I need to buffer (I think I can read ahead 1.5 x playback speed) the last few seconds of the first track, start the stream for track two, fade out one and fade in two using an AudioUnit.
I've reviewed sample apps:
Viva - https://github.com/iKenndac/Viva SimplePlayer with EQ - https://github.com/iKenndac/SimplePlayer-with-EQ and tried to get my mind around the SPCircularBuffer, but I still need help. Could someone point me to another example or help bullet-point a track crossfade game plan?
Update: Thanks to iKenndac, I'm about 95% there. I'll post what I have so far:
in SPPlaybackManager.m: initWithPlaybackSession:(SPSession *)aSession {
added:
self.audioController2 = [[SPCoreAudioController alloc] init];
self.audioController2.delegate = self;
and in
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
...
self.audioController.audioOutputEnabled = self.playbackSession.isPlaying;
// for crossfade, add
self.audioController2.audioOutputEnabled = self.playbackSession.isPlaying;
and added a new method based on playTrack
-(void)crossfadeTrack:(SPTrack *)aTrack callback:(SPErrorableOperationCallback)block {
// switch audiocontroller from current to other
if (self.playbackSession.audioDeliveryDelegate == self.audioController)
{
self.playbackSession.audioDeliveryDelegate = self.audioController2;
self.audioController2.delegate = self;
self.audioController.delegate = nil;
}
else
{
self.playbackSession.audioDeliveryDelegate = self.audioController;
self.audioController.delegate = self;
self.audioController2.delegate = nil;
}
if (aTrack.availability != SP_TRACK_AVAILABILITY_AVAILABLE) {
if (block) block([NSError spotifyErrorWithCode:SP_ERROR_TRACK_NOT_PLAYABLE]);
self.currentTrack = nil;
}
self.currentTrack = aTrack;
self.trackPosition = 0.0;
[self.playbackSession playTrack:self.currentTrack callback:^(NSError *error) {
if (!error)
self.playbackSession.playing = YES;
else
self.currentTrack = nil;
if (block) {
block(error);
}
}];
}
this starts a timer for crossfade
crossfadeTimer = [NSTimer scheduledTimerWithTimeInterval: 0.5
target: self
selector: #selector ( crossfadeCountdown)
userInfo: nil
repeats: YES];
And in order to keep the first track playing after its data has loaded in SPCoreAudioController.m I changed target buffer length:
static NSTimeInterval const kTargetBufferLength = 20;
and in SPSession.m : end_of_track(sp_session *session) {
I removed
// sess.playing = NO;
I call preloadTrackForPlayback: about 15 seconds before end of track, then crossfadeTrack: at 10 seconds before.
Then set crossfadeCountdownTime = [how many seconds you want the crossfade]*2;
I fade volume over the crosssfade with:
- (void) crossfadeCountdown
{
[UIAppDelegate.playbackSPManager setVolume:(1- (((float)crossfadeCountdownTime/ (thisCrossfadeSeconds*2.0)) *0.2) )];
crossfadeCountdownTime -= 0.5;
if (crossfadeCountdownTime == 1.0)
{
NSLog(#"Crossfade countdown done");
crossfadeCountdownTime = 0;
[crossfadeTimer invalidate];
crossfadeTimer = nil;
[UIAppDelegate.playbackSPManager setVolume:1.0];
}
}
I'll keep working on it, and update if I can make it better. Thanks again to iKenndac for his always spot-on help!
There isn't a pre-written crossfade example that I'm aware of that uses CocoaLibSpotify. However, a (perhaps not ideal) game plan would be:
Make two separate audio queues. SPCoreAudioController is an encapsulation of an audio queue, so you should just be able to instantiate two of them.
Play music as normal to one queue. When you're approaching the end of the track, call SPSession's preloadTrackForPlayback:callback: method with the next track to get it ready.
When all audio data for the playing track has been delivered, SPSession will fire the audio delegate method sessionDidEndPlayback:. This means that all audio data has been delivered. However, since CocoaLibSpotify buffers the audio from libspotify, there's still some time before audio stops.
At this point, start playing the new track but divert the audio data to the second audio queue. Start ramping down the volume of the first queue while ramping up the volume of the next one. This should give a pleasing crossfade.
A few pointers:
In SPCoreAudioController.m, you'll find the following line, which defines how much audio CocoaLibSpotify buffers, in seconds. If you want a bigger crossfade, you'll need to increase it.
static NSTimeInterval const kTargetBufferLength = 0.5;
Since you get audio data at a maximum of 1.5x actual playback speed, be careful not to do, for example, a 5 second crossfade when the user has just skipped near to the end of the track. You might not have enough audio data available to pull it off.
Take a good look at SPPlaybackManager.m. This class is the interface between CocoaLibSpotify and Core Audio. It's not too complicated, and understanding it will get you a long way. SPCoreAudioController and SPCircularBuffer are pretty much implementation details of getting the audio into Core Audio, and you shouldn't need to understand their implementations to achieve what you want.
Also, make sure you understand the various delegates SPSession has. The audio delivery delegate only has one job - to receive audio data. The playback delegate gets all other playback events - when audio has finished being delivered to the audio delivery delegate, etc. There's nothing stopping one class being both, but in the current implementation, SPPlaybackManager is the playback delegate, which creates an instance of SPCoreAudioController to be the audio delivery delegate. If you modify SPPlaybackManager to have two Core Audio controllers and alternate which one is the audio delivery delegate, you should be golden.

Resources