Music formats that loop without gaps?

Moderator
Posts: 434
Joined: 2002.09
Post: #1
It's been pointed out in these forums that the MP3 format introduces a slight gap in playback at the end of the track. This bit me last year with my game Asteroid Rally. Pity, because MP3 files sound great and are so much smaller than AIFF files.

This year I started with AIFF files and the music looping is seamless. But now my game is larger than contest rules permit. I could simply remove one of my tracks (I have three different tunes in the game just for variety.) Or I could downsample the tracks and hope it doesn't sound too crappy. But I was wondering if there was a format that Quicktime understands that is both compressed and does not introduce a gap at the end of the track. For example, does AAC have the same problem as MP3?

Thanks!

Measure twice, cut once, curse three or four times.
Quote this message in a reply
Moderator
Posts: 916
Joined: 2002.10
Post: #2
MattDiamond Wrote:For example, does AAC have the same problem as MP3?

Thanks!
not that I am aware of, I have noticed no problems with gaps between songs (though technically they are both the same song. I might have to listen more closely, but if you have a copy, check out Microbian: Fighter (with music). It has seamless music.
Quote this message in a reply
Member
Posts: 715
Joined: 2003.04
Post: #3
On technique I have been considering is fade the song on one channel at the end of the track, and fade in on another channel , repeat as necessary.
Quote this message in a reply
Moderator
Posts: 3,570
Joined: 2003.06
Post: #4
MattDiamond Wrote:This year I started with AIFF files and the music looping is seamless.
Using Quicktime for playback?
Quote this message in a reply
Moderator
Posts: 522
Joined: 2002.04
Post: #5
It's not the format the sounds are stored in, but the manner in which you play them. .. I get seamless looping with the Sound Manager.

-Jon
Quote this message in a reply
Moderator
Posts: 434
Joined: 2002.09
Post: #6
AnotherJake Wrote:Using Quicktime for playback?

Yes. (Specifically, Kelvin's Cocoa wrapper from his CocoaBlitz framework.)

aarku Wrote:It's not the format the sounds are stored in, but the manner in which you play them. .. I get seamless looping with the Sound Manager.

With mp3's? If so, that's interesting. I was told that MP3's quantize the time slices to something like 1/40th of a second. This leads to a discernable break unless (a) you r playback code works around it, (b) your music loop is in a style that makes breaks hard to detect (i.e. no sustained note where the loop occurs) or © the end of your track happens to align with the time slices. (People are saying that this is why iPod's don't play seamlessly between tracks.)

Measure twice, cut once, curse three or four times.
Quote this message in a reply
Moderator
Posts: 3,570
Joined: 2003.06
Post: #7
Yeah, okay, that's what I thought you meant. Just wanted to be sure. I thought Quicktime introduced a gap with any format.

At any rate, the format doesn't cause the gaps, Quicktime does. You need to uncompress your mp3 or whatever into memory and then loop it through OpenAL or CoreAudio or the Sound Manager or something else besides Quicktime to avoid those gaps. I guess you could still use Quicktime after it's been decompressed if it indeed does not gap with raw audio.
Quote this message in a reply
Moderator
Posts: 522
Joined: 2002.04
Post: #8
MattDiamond Wrote:With mp3's? If so, that's interesting. I was told that MP3's quantize the time slices to something like 1/40th of a second. This leads to a discernable break unless (a) you r playback code works around it, (b) your music loop is in a style that makes breaks hard to detect (i.e. no sustained note where the loop occurs) or © the end of your track happens to align with the time slices. (People are saying that this is why iPod's don't play seamlessly between tracks.)

Humph you're right! I made a little test sound, and the only one it loops absolutely flawlessly is with aiff. Both mp3 and m4a have a little jump. Crummy! Must not have noticed it before. I'll have to write something to look at the last 1/30th of the decompressed audio, and delete it the ending if there is no sound or something.

-Jon
Quote this message in a reply
Member
Posts: 156
Joined: 2002.11
Post: #9
You could use FLAC, which is about 50% smaller than AIFF and lossless, but it's a bit of pain to implement. I stopped at 80% done. Gotta try again.
Quote this message in a reply
_Kevlar
Unregistered
 
Post: #10
Quote this message in a reply
Moderator
Posts: 434
Joined: 2002.09
Post: #11
Looks interesting. I don't think I'll get a chance to try it before the contest deadline, but it's probably worth investigating Apple's extremely kludgey workaround.

The reason I think it might not be the same issue is that I believe the CocoaBlitz code I'm using uses an NSTimer to decide when to start playing the track again. It's not using Quicktime's native looping. And I believe people do that specifically because they get better playback. So I'm not sure this trick applies.

Measure twice, cut once, curse three or four times.
Quote this message in a reply
Moderator
Posts: 1,560
Joined: 2003.10
Post: #12
I was able to get around this problem in Water Tower by decompressing the MP3 file into memory, and playing it with the Sound Manager. If you have a lot of music, this will eat up a significant portion of memory, but if it's only a couple of minutes or so, you could probably get away with it.

Sorry for the ugly code:

Code:
static SndListHandle music = NULL;
static SndChannelPtr musicChannel = NULL;
static Boolean musicAvailable = 0, musicPlaying = 0, musicSuspended = 0;

void initWaterTowerMusic() {
  OSErr error;
  FSSpec file;
  short refNum;
  Track track;
  Movie movie;
  FSRef bundleFSRef;
  FSSpec appFSSpec;
  
  musicAvailable = 0;
  
  musicChannel = (SndChannelPtr) NewPtr(sizeof(SndChannel));
  musicChannel->qLength = 6;
  SndNewChannel(&musicChannel, sampledSynth, initMono, NULL);
  
  error = EnterMovies();
  if (error == noErr) {
    CFURLGetFSRef(CFBundleCopyBundleURL(CFBundleGetMainBundle()), &bundleFSRef);
    FSGetCatalogInfo(&bundleFSRef, kFSCatInfoNone, NULL, NULL, &appFSSpec, NULL);
    error = FSMakeFSSpec(appFSSpec.vRefNum, appFSSpec.parID, "\p:Music.mp3", &file);
    if (error == noErr) error = OpenMovieFile(&file, &refNum, fsRdPerm);
    if (error == noErr) {
      error = NewMovieFromFile(&movie, refNum, NULL, NULL, newMovieActive, NULL);
      if (error == noErr) track = GetMovieIndTrackType(movie, 1, AudioMediaCharacteristic, (movieTrackCharacteristic | movieTrackEnabledOnly));
      if (track != NULL) {
        music = (SndListHandle) NewHandle(0);
        if (music != NULL) {
          error = PutMovieIntoTypedHandle(movie, track, soundListRsrc, (Handle) music, 0, GetTrackDuration(track), 0, NULL);
          if (error == noErr) {
            musicAvailable = 1;
          } else {
            DisposeHandle((Handle) music);
          }
        }
        DisposeMovieTrack(track);
      }
      CloseMovieFile(refNum);
    }
    DisposeMovie(movie);
    ExitMovies();
  }
}

void runWaterTowerMusic() {
  SCStatus status;
  
  if (!musicPlaying) return;
  SndChannelStatus(musicChannel, sizeof(SCStatus), &status);
  if (!status.scChannelBusy) startBGMusic(1);
}

void shutDownWaterTowerMusic() {
  stopBGMusic();
  if (musicChannel != NULL) {
    SndCommand cmd;
    
    cmd.cmd = flushCmd;
    cmd.param1 = 0;
    cmd.param2 = 0;
    SndDoImmediate(musicChannel, &cmd);
    cmd.cmd = quietCmd;
    SndDoImmediate(musicChannel, &cmd);
    SndDisposeChannel(musicChannel, NULL);
    DisposePtr((Ptr) musicChannel);
    musicChannel = NULL;
  }
  if (music != NULL) {
    DisposeHandle((Handle) music);
    music = NULL;
  }
}

void suspendWaterTowerMusic() {
  if (musicPlaying) {
    stopBGMusic();
    musicSuspended = 1;
  }
}

void resumeWaterTowerMusic() {
  if (musicSuspended) {
    startBGMusic(0);
    musicSuspended = 0;
  }
}

void startBGMusic(Boolean fromBeginning) {
  SndCommand cmd;
  long offset;
  SCStatus status;
  
  if (!musicAvailable || musicChannel == NULL) return;
  
  SndChannelStatus(musicChannel, sizeof(SCStatus), &status);
  if (fromBeginning || !status.scChannelBusy) {
    cmd.cmd = flushCmd;
    cmd.param1 = 0;
    cmd.param2 = 0;
    SndDoImmediate(musicChannel, &cmd);
    cmd.cmd = quietCmd;
    SndDoImmediate(musicChannel, &cmd);
    cmd.cmd = rateMultiplierCmd;
    cmd.param1 = 0;
    cmd.param2 = 0x00010000;
    SndDoImmediate(musicChannel, &cmd);
    GetSoundHeaderOffset(music, &offset);
    cmd.cmd = bufferCmd;
    cmd.param1 = 0;
    cmd.param2 = (long) ((Ptr) *music + offset);
    SndDoCommand(musicChannel, &cmd, 0);
  } else {
    cmd.cmd = rateMultiplierCmd;
    cmd.param1 = 0;
    cmd.param2 = 0x00010000;
    SndDoImmediate(musicChannel, &cmd);
  }
  musicPlaying = 1;
}

void stopBGMusic() {
  SndCommand cmd;
  
  if (!musicAvailable) return;
  if (musicPlaying && musicChannel != NULL) {
    cmd.cmd = rateMultiplierCmd;
    cmd.param1 = 0;
    cmd.param2 = 0x00000000;
    SndDoImmediate(musicChannel, &cmd);
    musicPlaying = 0;
  }
}

Alex Diener
Quote this message in a reply
Moderator
Posts: 434
Joined: 2002.09
Post: #13
Thanks for the code. I'm fond of the nifty CocoaBlitz wrapper, but I'm also fond of having my game be smaller and download-friendly. If I have time I'll revisit my looping code. If you see your name in my credits then you'll know I did, and that it worked. :-)

Measure twice, cut once, curse three or four times.
Quote this message in a reply
Member
Posts: 469
Joined: 2002.10
Post: #14
The CBMovie class in CocoaBlitz can be used by itself without the entire framework. Just import CBMovie.h/m into your project and change #import <CBMovie.h/CocoaBlitz> to #import "CBMovie.h"

---Kelvin--
15.4" MacBook Pro revA
1.83GHz/2GB/250GB
Quote this message in a reply
Moderator
Posts: 434
Joined: 2002.09
Post: #15
kelvin Wrote:The CBMovie class in CocoaBlitz can be used by itself without the entire framework.

Yep, that's what I'm using.

Here's my little testimonial about CBMusic: to anyone who is already using Obj-C on their project, this class is a very painless way to add music to your project. It drops in easily, and the methods are very simple to use. I'm grateful to kelvin for lending it to me even before he'd Open Sourced CocoaBlitz.

The only downside is this gap-looping issue, apparently inherited from Quicktime. (Worth pointing out that I didn't get a single complaint about the "seams" in my loops last year, they seemed to bother me more than anyone else.) Since my game for uDG this year is ever-so-slightly above the size limit I have to take action on this, unfortunately. I'm going to have to switch from AIFF to MP3, and if I can hear gaps in these particular tracks (by no means certain due to their style) then I will have to replace CBMusic. I may try downsampling the tracks first.

Don'tcha just love last minute issues like this? :-)

Measure twice, cut once, curse three or four times.
Quote this message in a reply
Post Reply 

Possibly Related Threads...
Thread: Author Replies: Views: Last Post
  Dealing with OBJ vetices formats bronxbomber92 35 11,609 Jun 9, 2007 02:07 PM
Last Post: AnotherJake