From b57bcc121ced955ce92ab3a9f24e5cd11f5db2fa Mon Sep 17 00:00:00 2001 From: vspader Date: Thu, 11 Oct 2007 02:08:29 +0000 Subject: [PATCH] Added support for gapless multitrack files. Modified cue sheet plugin to use it. --- Audio/AudioPlayer.h | 2 +- Audio/AudioPlayer.m | 80 +++++++++++++++++++++--------- Audio/Chain/BufferChain.h | 11 +++- Audio/Chain/BufferChain.m | 33 ++++++++++-- Audio/Chain/ConverterNode.m | 3 ++ Audio/Chain/InputNode.h | 10 ++-- Audio/Chain/InputNode.m | 55 ++++++++++++++++---- Cog.xcodeproj/project.pbxproj | 4 +- Plugins/CueSheet/CueSheetDecoder.h | 2 + Plugins/CueSheet/CueSheetDecoder.m | 54 +++++++++++++++++++- 10 files changed, 208 insertions(+), 46 deletions(-) diff --git a/Audio/AudioPlayer.h b/Audio/AudioPlayer.h index af08df3cc..0dcaf3f3c 100644 --- a/Audio/AudioPlayer.h +++ b/Audio/AudioPlayer.h @@ -65,7 +65,7 @@ - (void)notifyStreamChanged:(id)userInfo; - (void)notifyStreamChangedMainThread:(id)userInfo; -- (void)endOfInputReached:(BufferChain *)sender; +- (BOOL)endOfInputReached:(BufferChain *)sender; - (void)setShouldContinue:(BOOL)s; - (BufferChain *)bufferChain; - (void)launchOutputThread; diff --git a/Audio/AudioPlayer.m b/Audio/AudioPlayer.m index 9bd2e2ccc..5a48bced3 100644 --- a/Audio/AudioPlayer.m +++ b/Audio/AudioPlayer.m @@ -134,6 +134,7 @@ [output setVolume:v]; } +//Note: This is called - (void)setNextStream:(NSURL *)url { [self setNextStream:url withUserInfo:nil]; @@ -176,8 +177,6 @@ } } - - - (void)requestNextStream:(id)userInfo { [self sendDelegateMethod:@selector(audioPlayer:requestNextStream:) withObject:userInfo waitUntilDone:YES]; @@ -188,35 +187,68 @@ [self sendDelegateMethod:@selector(audioPlayer:streamChanged:) withObject:userInfo waitUntilDone:NO]; } - - -- (void)endOfInputReached:(BufferChain *)sender //Sender is a BufferChain -{ - BufferChain *newChain = nil; - - nextStreamUserInfo = [sender userInfo]; - [nextStreamUserInfo retain]; //Retained because when setNextStream is called, it will be released!!! - - do { - [newChain release]; - [self requestNextStream: nextStreamUserInfo]; - - if (nextStream == nil) - { - return; - } - - newChain = [[BufferChain alloc] initWithController:self]; - } while (![newChain open:nextStream withOutputFormat:[output format]]); - +- (void)addChainToQueue:(BufferChain *)newChain +{ [newChain setUserInfo: nextStreamUserInfo]; [newChain setShouldContinue:YES]; [newChain launchThreads]; [chainQueue insertObject:newChain atIndex:[chainQueue count]]; +} + +- (BOOL)endOfInputReached:(BufferChain *)sender //Sender is a BufferChain +{ + BufferChain *newChain = nil; + + nextStreamUserInfo = [sender userInfo]; + [nextStreamUserInfo retain]; //Retained because when setNextStream is called, it will be released!!! + + [self requestNextStream: nextStreamUserInfo]; + newChain = [[BufferChain alloc] initWithController:self]; + + BufferChain *lastChain = [chainQueue lastObject]; + if (lastChain == nil) { + lastChain = bufferChain; + } + + if ([[nextStream scheme] isEqualToString:[[lastChain streamURL] scheme]] + && [[nextStream host] isEqualToString:[[lastChain streamURL] host]] + && [[nextStream path] isEqualToString:[[lastChain streamURL] path]]) + { + if ([lastChain setTrack:nextStream]) { + [newChain openWithInput:[lastChain inputNode] withOutputFormat:[output format]]; + + [newChain setStreamURL:nextStream]; + [newChain setUserInfo:nextStreamUserInfo]; + + [self addChainToQueue:newChain]; + NSLog(@"TRACK SET!!! %@", newChain); + //Keep on-playin + [newChain release]; + + return NO; + } + } + + while (![newChain open:nextStream withOutputFormat:[output format]]) + { + if (nextStream == nil) + { + return YES; + } + + [newChain release]; + [self requestNextStream: nextStreamUserInfo]; + + newChain = [[BufferChain alloc] initWithController:self]; + } + + [self addChainToQueue:newChain]; [newChain release]; + + return YES; } - (void)endOfInputPlayed @@ -233,6 +265,8 @@ bufferChain = [chainQueue objectAtIndex:0]; [bufferChain retain]; + NSLog(@"New!!! %@ %@", bufferChain, [[bufferChain inputNode] decoder]); + [chainQueue removeObjectAtIndex:0]; [self notifyStreamChanged:[bufferChain userInfo]]; diff --git a/Audio/Chain/BufferChain.h b/Audio/Chain/BufferChain.h index e6cfdfd15..b46435b30 100644 --- a/Audio/Chain/BufferChain.h +++ b/Audio/Chain/BufferChain.h @@ -26,11 +26,18 @@ - (id)initWithController:(id)c; - (void)buildChain; + - (BOOL)open:(NSURL *)url withOutputFormat:(AudioStreamBasicDescription)outputFormat; + +//Used when changing tracks to reuse the same decoder +- (BOOL)openWithInput:(InputNode *)i withOutputFormat:(AudioStreamBasicDescription)outputFormat; + - (void)seek:(double)time; - (void)launchThreads; +- (InputNode *)inputNode; + - (id)finalNode; - (id)userInfo; @@ -43,7 +50,7 @@ - (void)initialBufferFilled; -- (void)endOfInputReached; - +- (BOOL)endOfInputReached; +- (BOOL)setTrack:(NSURL *)track; @end diff --git a/Audio/Chain/BufferChain.m b/Audio/Chain/BufferChain.m index 88a64eae2..159ff52bc 100644 --- a/Audio/Chain/BufferChain.m +++ b/Audio/Chain/BufferChain.m @@ -55,7 +55,7 @@ } - if (![inputNode openURL:url withSource:source outputFormat:outputFormat]) + if (![inputNode openURL:url withSource:source]) return NO; if (![converterNode setupWithInputFormat:propertiesToASBD([inputNode properties]) outputFormat:outputFormat]) @@ -64,8 +64,25 @@ return YES; } +- (BOOL)openWithInput:(InputNode *)i withOutputFormat:(AudioStreamBasicDescription)outputFormat +{ + NSLog(@"New buffer chain!"); + [self buildChain]; + + if (![inputNode openWithDecoder:[i decoder]]) + return NO; + + if (![converterNode setupWithInputFormat:propertiesToASBD([inputNode properties]) outputFormat:outputFormat]) + return NO; + + NSLog(@"Buffer chain made"); + return YES; +} + - (void)launchThreads { + NSLog(@"Properties: %@", [inputNode properties]); + [inputNode launchThread]; [converterNode launchThread]; } @@ -97,9 +114,14 @@ [inputNode seek:time]; } -- (void)endOfInputReached +- (BOOL)endOfInputReached { - [controller endOfInputReached:self]; + return [controller endOfInputReached:self]; +} + +- (BOOL)setTrack: (NSURL *)track +{ + return [inputNode setTrack:track]; } - (void)initialBufferFilled @@ -107,6 +129,11 @@ [controller launchOutputThread]; } +- (InputNode *)inputNode +{ + return inputNode; +} + - (id)finalNode { return finalNode; diff --git a/Audio/Chain/ConverterNode.m b/Audio/Chain/ConverterNode.m index 9134389ea..ea04dac7c 100644 --- a/Audio/Chain/ConverterNode.m +++ b/Audio/Chain/ConverterNode.m @@ -134,6 +134,9 @@ static OSStatus ACInputProc(AudioConverterRef inAudioConverter, UInt32* ioNumber } } + PrintStreamDesc(&inf); + PrintStreamDesc(&outf); + return YES; } diff --git a/Audio/Chain/InputNode.h b/Audio/Chain/InputNode.h index c4ca72c11..6e2581703 100644 --- a/Audio/Chain/InputNode.h +++ b/Audio/Chain/InputNode.h @@ -19,17 +19,21 @@ @interface InputNode : Node { id decoder; - AudioStreamBasicDescription outputFormat; - BOOL shouldSeek; double seekTime; } -- (BOOL)openURL:(NSURL *)url withSource:(id)source outputFormat:(AudioStreamBasicDescription)of; +- (BOOL)openURL:(NSURL *)url withSource:(id)source; +- (BOOL)openWithDecoder:(id) d; + - (void)process; - (NSDictionary *) properties; - (void)seek:(double)time; - (void)registerObservers; +- (BOOL)setTrack:(NSURL *)track; + +- (id) decoder; + @end diff --git a/Audio/Chain/InputNode.m b/Audio/Chain/InputNode.m index 9946af3b8..73c3bef08 100644 --- a/Audio/Chain/InputNode.m +++ b/Audio/Chain/InputNode.m @@ -13,18 +13,16 @@ @implementation InputNode -- (BOOL)openURL:(NSURL *)url withSource:(id)source outputFormat:(AudioStreamBasicDescription)of +- (BOOL)openURL:(NSURL *)url withSource:(id)source { - outputFormat = of; - decoder = [AudioDecoder audioDecoderForURL:url]; [decoder retain]; - [self registerObservers]; - if (decoder == nil) return NO; + [self registerObservers]; + if (![decoder open:source]) { NSLog(@"Couldn't open decoder..."); @@ -37,6 +35,22 @@ return YES; } +- (BOOL)openWithDecoder:(id) d +{ + NSLog(@"Opening with old decoder: %@", d); + decoder = d; + [decoder retain]; + + [self registerObservers]; + + shouldContinue = YES; + shouldSeek = NO; + + NSLog(@"DONES: %@", decoder); + return YES; +} + + - (void)registerObservers { [decoder addObserver:self @@ -69,13 +83,14 @@ int amountRead = 0, amountInBuffer = 0; void *inputBuffer = malloc(CHUNK_SIZE); + BOOL shouldClose = YES; + while ([self shouldContinue] == YES && [self endOfStream] == NO) { if (shouldSeek == YES) { NSLog(@"SEEKING!"); [decoder seekToTime:seekTime]; - NSLog(@"Har"); shouldSeek = NO; NSLog(@"Seeked! Resetting Buffer"); @@ -95,17 +110,19 @@ [controller initialBufferFilled]; } + NSLog(@"End of stream?"); endOfStream = YES; - [controller endOfInputReached]; - break; //eof + shouldClose = [controller endOfInputReached]; //Lets us know if we should keep going or not (occassionally, for track changes within a file) + NSLog(@"closing? is %i", shouldClose); + break; } [self writeData:inputBuffer amount:amountInBuffer]; amountInBuffer = 0; } } - - [decoder close]; + if (shouldClose) + [decoder close]; free(inputBuffer); } @@ -118,8 +135,21 @@ [semaphore signal]; } +- (BOOL)setTrack:(NSURL *)track +{ + if ([decoder respondsToSelector:@selector(setTrack:)] && [decoder setTrack:track]) { + NSLog(@"SET TRACK!"); + + return YES; + } + + return NO; +} + - (void)dealloc { + NSLog(@"DEALLOCATING"); + [decoder removeObserver:self forKeyPath:@"properties"]; [decoder removeObserver:self forKeyPath:@"metadata"]; @@ -133,4 +163,9 @@ return [decoder properties]; } +- (id) decoder +{ + return decoder; +} + @end diff --git a/Cog.xcodeproj/project.pbxproj b/Cog.xcodeproj/project.pbxproj index 00ea8f056..8e2876fc7 100644 --- a/Cog.xcodeproj/project.pbxproj +++ b/Cog.xcodeproj/project.pbxproj @@ -284,14 +284,14 @@ isa = PBXContainerItemProxy; containerPortal = 17F3BB830CBC565100864489 /* CueSheet.xcodeproj */; proxyType = 2; - remoteGlobalIDString = 8D5B49B6048680CD000E48DA /* CueSheet.bundle */; + remoteGlobalIDString = 8D5B49B6048680CD000E48DA; remoteInfo = CueSheet; }; 17F3BB8A0CBC566200864489 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 17F3BB830CBC565100864489 /* CueSheet.xcodeproj */; proxyType = 1; - remoteGlobalIDString = 8D5B49AC048680CD000E48DA /* CueSheet */; + remoteGlobalIDString = 8D5B49AC048680CD000E48DA; remoteInfo = CueSheet; }; 17F561320C3BD4DC0019975C /* PBXContainerItemProxy */ = { diff --git a/Plugins/CueSheet/CueSheetDecoder.h b/Plugins/CueSheet/CueSheetDecoder.h index 85675b5ea..40ffe965b 100644 --- a/Plugins/CueSheet/CueSheetDecoder.h +++ b/Plugins/CueSheet/CueSheetDecoder.h @@ -10,6 +10,7 @@ #import "Plugin.h" +@class CueSheet; @class CueSheetTrack; @interface CueSheetDecoder : NSObject { @@ -20,6 +21,7 @@ int bytePosition; double trackEnd; + CueSheet *cuesheet; CueSheetTrack *track; } diff --git a/Plugins/CueSheet/CueSheetDecoder.m b/Plugins/CueSheet/CueSheetDecoder.m index bb8bbcec5..a1e087ab9 100644 --- a/Plugins/CueSheet/CueSheetDecoder.m +++ b/Plugins/CueSheet/CueSheetDecoder.m @@ -38,14 +38,16 @@ NSURL *url = [s url]; [s close]; - CueSheet *cuesheet = [CueSheet cueSheetWithFile:[url path]]; - + 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]]; @@ -103,6 +105,47 @@ [source release]; source = nil; } + if (cuesheet) { + [cuesheet release]; + cuesheet = nil; + } + if (track) { + [track release]; + track = nil; + } +} + +- (BOOL)setTrack:(NSURL *)url +{ + if ([[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]; + + 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]; + } + else { + trackEnd = [[[decoder properties] objectForKey:@"length"] doubleValue]/1000.0; + } + + NSLog(@"CHANGING TRACK!"); + return YES; + } + } + } + + return NO; } - (double)seekToTime:(double)time //milliseconds @@ -119,6 +162,13 @@ bytePosition = (time/1000.0) * bytesPerSecond; + int bitsPerSample = [[[decoder properties] objectForKey:@"bitsPerSample"] intValue]; + int channels = [[[decoder properties] objectForKey:@"channels"] intValue]; + + NSLog(@"Before: %li", bytePosition); + bytePosition -= bytePosition % (bitsPerSample/8 * channels); + NSLog(@"After: %li", bytePosition); + return [decoder seekToTime:time]; }