views:

83

answers:

3

Hi,

I've pretty much finished work on a white noise feature for one of my applications using NSSound to play a loop of 10 second AAC-encoded pre-recorded white noise.

[sound setLoops: YES]

should be all that's required, right?

It works like a charm but I've noticed that there is an audible pause between the sound file finishing and restarting.. a sort of "plop" sound. This isn't present when looping the original sound files and after an hour or so of trying to figure this out, I've come to the conclusion that NSSound sucks and that the audible pause is an artefact of the synchronisation of the private background thread playing the sound. It seems to be dependent on the main run loop somehow and this causes the audible gap between the end and restarting of the sound.

I know very little about sound stuff and this is a very minor feature, so I don't want to get into the depths of CoreAudio just to play a looping 10s sound fragment.. so I went chasing after a nice alternative, but nothing seems to quite fit:

  • Core Audio: total overkill, but at least a standard framework
  • AudioQueue: complicated, with C++ sample code!?
  • MusicKit/ SndKit: also huge learning curve, based on lots of open source stuff, etc.

I saw that AVFoundation on iOS 4 would be a nice way to play sounds, but that's only scheduled for Mac OS X 10.7..

Is there any easy-to-use way of reliably looping sound on Mac OS X 10.5+?

Is there any sample code for AudioQueue or Core Audio that takes the pain out of using them from an Objective-C application?

Any help would be very much appreciated..

Best regards,

Frank

A: 

Sadly, there is a lot of pain when developing audio applications on OS X. The learning curve is very steep because the documentation is fairly sparse.

If you don't mind Objective-C++ I've written a framework for this kind of thing: SFBAudioEngine. If you wanted to play a sound with my code here is how you could do it:

DSPAudioPlayer *player = new DSPAudioPlayer();
player->Enqueue((CFURLRef)audioURL);
player->Play();

Looping is also possible.

sbooth
Thanks for helping out. I'm going to put Mike's QTKit solution through its paces to see that it's really working properly, but unless something unexpected comes out, I'll probably be sticking with that for my humble needs.. but you never know :-)
Frank R.
+1  A: 

Use QTKit. Create a QTMovie for the sound, set it to loop, and leave it playing.

Mike Abdullah
Frank R.
Agreed. The hardest part about playing sounds with QTKit is including it in your project (which takes a few seconds).
Joshua Nozzi
Got it working with QTKit.. I thought as QuickTime can't run in either 64-bit or in a garbage collected mode it wasn't worth trying QTKit either.. but it seems to work just fine.Thanks.
Frank R.
The old QuickTime framework is 32-bit only. QTKit is the modern Objective-C framework that is 64-bit compatible, and should be GC-friendly too.
Mike Abdullah
+1  A: 

Just for the sake of the archives.

QTKit also suffers from a gap between the end of one play through and start of the next one. It seems to be linked with re-initializing the data (perhaps re-reading it from disk?) in some way. It's a lot more noticeable when using the much smaller but highly compressed m4a format than when playing uncompressed aiff files but it's still there even so.

The solution I've found is to use Audio Queue Services:

http://developer.apple.com/mac/library/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AQPlayback/PlayingAudio.html#//apple_ref/doc/uid/TP40005343-CH3-SW1

and

http://developer.apple.com/mac/library/samplecode/AudioQueueTools/Listings/aqplay_cpp.html#//apple_ref/doc/uid/DTS10004380-aqplay_cpp-DontLinkElementID_4

The Audio Queue calls a callback function which prepares and enqueues the next buffer, so when you reach the end of the current file you need to start again from the beginning. This gives completely gapless playback.

There's two gotchas in the sample code in the documentation.

The first is an actual bug (I'll contact DTS about this so they can correct it). Before allocating and priming the audio buffers, the custom structure must switch on playback otherwise the audio buffer never get primed and nothing is played:

    aqData.mIsRunning = 1;

The second gotcha is that the code doesn't run in Cocoa but as a standalone tool, so the code connects the audio queue to a new run loop and actually implements the run loop itself as the last step of the program.

Instead of passing CFRunLoopGetCurrent(), just pass NULL which causes the AudioQueue to run in its own run loop.

result = AudioQueueNewOutput (                                // 1
                     &aqData.mDataFormat,                             // 2
                     HandleOutputBuffer,                              // 3
                     &aqData,                                         // 4
                     NULL, //CFRunLoopGetCurrent (),                          // 5
                     kCFRunLoopCommonModes,                           // 6
                     0,                                               // 7
                     &aqData.mQueue                                   // 8
                     );

I hope this can save the poor wretches trying to do this same thing in the future a bit of time :-)

Best regards,

Frank

Frank R.