// // CueSheetDecoder.m // CueSheet // // Created by Zaphod Beeblebrox on 10/8/07. // Copyright 2007 __MyCompanyName__. All rights reserved. // #import "CueSheetDecoder.h" #import "CueSheet.h" #import "CueSheetTrack.h" #import "CueSheetContainer.h" #import "Logging.h" @implementation CueSheetDecoder + (NSArray *)fileTypes { return [CueSheetContainer fileTypes]; } + (NSArray *)mimeTypes { return [CueSheetContainer mimeTypes]; } + (float)priority { return 1.0; } - (NSDictionary *)properties { NSMutableDictionary *properties = [[decoder properties] mutableCopy]; //Need to alter length [properties setObject:[NSNumber numberWithLong:(trackEnd - trackStart)] forKey:@"totalFrames"]; return [properties autorelease]; } - (BOOL)open:(id)s { if (![[s url] isFileURL]) { return NO; } NSURL *url = [s url]; [s close]; cuesheet = [CueSheet cueSheetWithFile:[url path]]; [cuesheet retain]; NSArray *tracks = [cuesheet tracks]; int i; for (i = 0; i < [tracks count]; i++) { if ([[[tracks objectAtIndex:i] track] isEqualToString:[url fragment]]){ track = [tracks objectAtIndex:i]; [track retain]; //Kind of a hackish way of accessing outside classes. source = [NSClassFromString(@"AudioSource") audioSourceForURL:[track url]]; [source retain]; if (![source open:[track url]]) { ALog(@"Could not open cuesheet source"); return NO; } decoder = [NSClassFromString(@"AudioDecoder") audioDecoderForSource:source]; [decoder retain]; if (![decoder open:source]) { ALog(@"Could not open cuesheet decoder"); return NO; } CueSheetTrack *nextTrack = nil; if (i + 1 < [tracks count]) { nextTrack = [tracks objectAtIndex:i + 1]; } NSDictionary *properties = [decoder properties]; int bitsPerSample = [[properties objectForKey:@"bitsPerSample"] intValue]; int channels = [[properties objectForKey:@"channels"] intValue]; float sampleRate = [[properties objectForKey:@"sampleRate"] floatValue]; bytesPerFrame = (bitsPerSample/8) * channels; trackStart = [track time] * sampleRate; if (nextTrack && [[[nextTrack url] absoluteString] isEqualToString:[[track url] absoluteString]]) { trackEnd = [nextTrack time] * sampleRate; } else { trackEnd = [[properties objectForKey:@"totalFrames"] doubleValue]; } [self seek: 0]; //Note: Should register for observations of the decoder [self willChangeValueForKey:@"properties"]; [self didChangeValueForKey:@"properties"]; return YES; } } return NO; } - (void)close { if (decoder) { [decoder close]; [decoder release]; decoder = nil; } if (source) { [source release]; source = nil; } if (cuesheet) { [cuesheet release]; cuesheet = nil; } if (track) { [track release]; track = nil; } } - (BOOL)setTrack:(NSURL *)url { //Same file, just next track...this may be unnecessary since frame-based decoding is done now... if ([[[track url] path] isEqualToString:[url path]] && [[[track url] host] isEqualToString:[url host]] && [[url fragment] intValue] == [[track track] intValue] + 1) { NSArray *tracks = [cuesheet tracks]; int i; for (i = 0; i < [tracks count]; i++) { if ([[[tracks objectAtIndex:i] track] isEqualToString:[url fragment]]){ [track release]; track = [tracks objectAtIndex:i]; [track retain]; float sampleRate = [[[decoder properties] objectForKey:@"sampleRate"] floatValue]; trackStart = [track time] * sampleRate; CueSheetTrack *nextTrack = nil; if (i + 1 < [tracks count]) { nextTrack = [tracks objectAtIndex:i + 1]; } if (nextTrack && [[[nextTrack url] absoluteString] isEqualToString:[[track url] absoluteString]]) { trackEnd = [nextTrack time] * sampleRate; } else { trackEnd = [[[decoder properties] objectForKey:@"totalFrames"] longValue]; } DLog(@"CHANGING TRACK!"); return YES; } } } return NO; } - (long)seek:(long)frame { if (frame > trackEnd - trackStart) { //need a better way of returning fail. return -1; } frame += trackStart; framePosition = [decoder seek:frame]; return framePosition; } - (int)readAudio:(void *)buf frames:(UInt32)frames { if (framePosition + frames > trackEnd) { frames = trackEnd - framePosition; } if (!frames) { DLog(@"Returning 0"); return 0; } int n = [decoder readAudio:buf frames:frames]; framePosition += n; return n; } @end