Added support for gapless multitrack files.

Modified cue sheet plugin to use it.
CQTexperiment
vspader 2007-10-11 02:08:29 +00:00
parent 4bcf22ced2
commit b57bcc121c
10 changed files with 208 additions and 46 deletions

View File

@ -65,7 +65,7 @@
- (void)notifyStreamChanged:(id)userInfo; - (void)notifyStreamChanged:(id)userInfo;
- (void)notifyStreamChangedMainThread:(id)userInfo; - (void)notifyStreamChangedMainThread:(id)userInfo;
- (void)endOfInputReached:(BufferChain *)sender; - (BOOL)endOfInputReached:(BufferChain *)sender;
- (void)setShouldContinue:(BOOL)s; - (void)setShouldContinue:(BOOL)s;
- (BufferChain *)bufferChain; - (BufferChain *)bufferChain;
- (void)launchOutputThread; - (void)launchOutputThread;

View File

@ -134,6 +134,7 @@
[output setVolume:v]; [output setVolume:v];
} }
//Note: This is called
- (void)setNextStream:(NSURL *)url - (void)setNextStream:(NSURL *)url
{ {
[self setNextStream:url withUserInfo:nil]; [self setNextStream:url withUserInfo:nil];
@ -176,8 +177,6 @@
} }
} }
- (void)requestNextStream:(id)userInfo - (void)requestNextStream:(id)userInfo
{ {
[self sendDelegateMethod:@selector(audioPlayer:requestNextStream:) withObject:userInfo waitUntilDone:YES]; [self sendDelegateMethod:@selector(audioPlayer:requestNextStream:) withObject:userInfo waitUntilDone:YES];
@ -188,35 +187,68 @@
[self sendDelegateMethod:@selector(audioPlayer:streamChanged:) withObject:userInfo waitUntilDone:NO]; [self sendDelegateMethod:@selector(audioPlayer:streamChanged:) withObject:userInfo waitUntilDone:NO];
} }
- (void)addChainToQueue:(BufferChain *)newChain
- (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]]);
[newChain setUserInfo: nextStreamUserInfo]; [newChain setUserInfo: nextStreamUserInfo];
[newChain setShouldContinue:YES]; [newChain setShouldContinue:YES];
[newChain launchThreads]; [newChain launchThreads];
[chainQueue insertObject:newChain atIndex:[chainQueue count]]; [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]; [newChain release];
[self requestNextStream: nextStreamUserInfo];
newChain = [[BufferChain alloc] initWithController:self];
}
[self addChainToQueue:newChain];
[newChain release];
return YES;
} }
- (void)endOfInputPlayed - (void)endOfInputPlayed
@ -233,6 +265,8 @@
bufferChain = [chainQueue objectAtIndex:0]; bufferChain = [chainQueue objectAtIndex:0];
[bufferChain retain]; [bufferChain retain];
NSLog(@"New!!! %@ %@", bufferChain, [[bufferChain inputNode] decoder]);
[chainQueue removeObjectAtIndex:0]; [chainQueue removeObjectAtIndex:0];
[self notifyStreamChanged:[bufferChain userInfo]]; [self notifyStreamChanged:[bufferChain userInfo]];

View File

@ -26,11 +26,18 @@
- (id)initWithController:(id)c; - (id)initWithController:(id)c;
- (void)buildChain; - (void)buildChain;
- (BOOL)open:(NSURL *)url withOutputFormat:(AudioStreamBasicDescription)outputFormat; - (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)seek:(double)time;
- (void)launchThreads; - (void)launchThreads;
- (InputNode *)inputNode;
- (id)finalNode; - (id)finalNode;
- (id)userInfo; - (id)userInfo;
@ -43,7 +50,7 @@
- (void)initialBufferFilled; - (void)initialBufferFilled;
- (void)endOfInputReached; - (BOOL)endOfInputReached;
- (BOOL)setTrack:(NSURL *)track;
@end @end

View File

@ -55,7 +55,7 @@
} }
if (![inputNode openURL:url withSource:source outputFormat:outputFormat]) if (![inputNode openURL:url withSource:source])
return NO; return NO;
if (![converterNode setupWithInputFormat:propertiesToASBD([inputNode properties]) outputFormat:outputFormat]) if (![converterNode setupWithInputFormat:propertiesToASBD([inputNode properties]) outputFormat:outputFormat])
@ -64,8 +64,25 @@
return YES; 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 - (void)launchThreads
{ {
NSLog(@"Properties: %@", [inputNode properties]);
[inputNode launchThread]; [inputNode launchThread];
[converterNode launchThread]; [converterNode launchThread];
} }
@ -97,9 +114,14 @@
[inputNode seek:time]; [inputNode seek:time];
} }
- (void)endOfInputReached - (BOOL)endOfInputReached
{ {
[controller endOfInputReached:self]; return [controller endOfInputReached:self];
}
- (BOOL)setTrack: (NSURL *)track
{
return [inputNode setTrack:track];
} }
- (void)initialBufferFilled - (void)initialBufferFilled
@ -107,6 +129,11 @@
[controller launchOutputThread]; [controller launchOutputThread];
} }
- (InputNode *)inputNode
{
return inputNode;
}
- (id)finalNode - (id)finalNode
{ {
return finalNode; return finalNode;

View File

@ -134,6 +134,9 @@ static OSStatus ACInputProc(AudioConverterRef inAudioConverter, UInt32* ioNumber
} }
} }
PrintStreamDesc(&inf);
PrintStreamDesc(&outf);
return YES; return YES;
} }

View File

@ -19,17 +19,21 @@
@interface InputNode : Node { @interface InputNode : Node {
id<CogDecoder> decoder; id<CogDecoder> decoder;
AudioStreamBasicDescription outputFormat;
BOOL shouldSeek; BOOL shouldSeek;
double seekTime; double seekTime;
} }
- (BOOL)openURL:(NSURL *)url withSource:(id<CogSource>)source outputFormat:(AudioStreamBasicDescription)of; - (BOOL)openURL:(NSURL *)url withSource:(id<CogSource>)source;
- (BOOL)openWithDecoder:(id<CogDecoder>) d;
- (void)process; - (void)process;
- (NSDictionary *) properties; - (NSDictionary *) properties;
- (void)seek:(double)time; - (void)seek:(double)time;
- (void)registerObservers; - (void)registerObservers;
- (BOOL)setTrack:(NSURL *)track;
- (id<CogDecoder>) decoder;
@end @end

View File

@ -13,18 +13,16 @@
@implementation InputNode @implementation InputNode
- (BOOL)openURL:(NSURL *)url withSource:(id<CogSource>)source outputFormat:(AudioStreamBasicDescription)of - (BOOL)openURL:(NSURL *)url withSource:(id<CogSource>)source
{ {
outputFormat = of;
decoder = [AudioDecoder audioDecoderForURL:url]; decoder = [AudioDecoder audioDecoderForURL:url];
[decoder retain]; [decoder retain];
[self registerObservers];
if (decoder == nil) if (decoder == nil)
return NO; return NO;
[self registerObservers];
if (![decoder open:source]) if (![decoder open:source])
{ {
NSLog(@"Couldn't open decoder..."); NSLog(@"Couldn't open decoder...");
@ -37,6 +35,22 @@
return YES; return YES;
} }
- (BOOL)openWithDecoder:(id<CogDecoder>) d
{
NSLog(@"Opening with old decoder: %@", d);
decoder = d;
[decoder retain];
[self registerObservers];
shouldContinue = YES;
shouldSeek = NO;
NSLog(@"DONES: %@", decoder);
return YES;
}
- (void)registerObservers - (void)registerObservers
{ {
[decoder addObserver:self [decoder addObserver:self
@ -69,13 +83,14 @@
int amountRead = 0, amountInBuffer = 0; int amountRead = 0, amountInBuffer = 0;
void *inputBuffer = malloc(CHUNK_SIZE); void *inputBuffer = malloc(CHUNK_SIZE);
BOOL shouldClose = YES;
while ([self shouldContinue] == YES && [self endOfStream] == NO) while ([self shouldContinue] == YES && [self endOfStream] == NO)
{ {
if (shouldSeek == YES) if (shouldSeek == YES)
{ {
NSLog(@"SEEKING!"); NSLog(@"SEEKING!");
[decoder seekToTime:seekTime]; [decoder seekToTime:seekTime];
NSLog(@"Har");
shouldSeek = NO; shouldSeek = NO;
NSLog(@"Seeked! Resetting Buffer"); NSLog(@"Seeked! Resetting Buffer");
@ -95,16 +110,18 @@
[controller initialBufferFilled]; [controller initialBufferFilled];
} }
NSLog(@"End of stream?");
endOfStream = YES; endOfStream = YES;
[controller endOfInputReached]; shouldClose = [controller endOfInputReached]; //Lets us know if we should keep going or not (occassionally, for track changes within a file)
break; //eof NSLog(@"closing? is %i", shouldClose);
break;
} }
[self writeData:inputBuffer amount:amountInBuffer]; [self writeData:inputBuffer amount:amountInBuffer];
amountInBuffer = 0; amountInBuffer = 0;
} }
} }
if (shouldClose)
[decoder close]; [decoder close];
free(inputBuffer); free(inputBuffer);
@ -118,8 +135,21 @@
[semaphore signal]; [semaphore signal];
} }
- (BOOL)setTrack:(NSURL *)track
{
if ([decoder respondsToSelector:@selector(setTrack:)] && [decoder setTrack:track]) {
NSLog(@"SET TRACK!");
return YES;
}
return NO;
}
- (void)dealloc - (void)dealloc
{ {
NSLog(@"DEALLOCATING");
[decoder removeObserver:self forKeyPath:@"properties"]; [decoder removeObserver:self forKeyPath:@"properties"];
[decoder removeObserver:self forKeyPath:@"metadata"]; [decoder removeObserver:self forKeyPath:@"metadata"];
@ -133,4 +163,9 @@
return [decoder properties]; return [decoder properties];
} }
- (id<CogDecoder>) decoder
{
return decoder;
}
@end @end

View File

@ -284,14 +284,14 @@
isa = PBXContainerItemProxy; isa = PBXContainerItemProxy;
containerPortal = 17F3BB830CBC565100864489 /* CueSheet.xcodeproj */; containerPortal = 17F3BB830CBC565100864489 /* CueSheet.xcodeproj */;
proxyType = 2; proxyType = 2;
remoteGlobalIDString = 8D5B49B6048680CD000E48DA /* CueSheet.bundle */; remoteGlobalIDString = 8D5B49B6048680CD000E48DA;
remoteInfo = CueSheet; remoteInfo = CueSheet;
}; };
17F3BB8A0CBC566200864489 /* PBXContainerItemProxy */ = { 17F3BB8A0CBC566200864489 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy; isa = PBXContainerItemProxy;
containerPortal = 17F3BB830CBC565100864489 /* CueSheet.xcodeproj */; containerPortal = 17F3BB830CBC565100864489 /* CueSheet.xcodeproj */;
proxyType = 1; proxyType = 1;
remoteGlobalIDString = 8D5B49AC048680CD000E48DA /* CueSheet */; remoteGlobalIDString = 8D5B49AC048680CD000E48DA;
remoteInfo = CueSheet; remoteInfo = CueSheet;
}; };
17F561320C3BD4DC0019975C /* PBXContainerItemProxy */ = { 17F561320C3BD4DC0019975C /* PBXContainerItemProxy */ = {

View File

@ -10,6 +10,7 @@
#import "Plugin.h" #import "Plugin.h"
@class CueSheet;
@class CueSheetTrack; @class CueSheetTrack;
@interface CueSheetDecoder : NSObject <CogDecoder> { @interface CueSheetDecoder : NSObject <CogDecoder> {
@ -20,6 +21,7 @@
int bytePosition; int bytePosition;
double trackEnd; double trackEnd;
CueSheet *cuesheet;
CueSheetTrack *track; CueSheetTrack *track;
} }

View File

@ -38,7 +38,8 @@
NSURL *url = [s url]; NSURL *url = [s url];
[s close]; [s close];
CueSheet *cuesheet = [CueSheet cueSheetWithFile:[url path]]; cuesheet = [CueSheet cueSheetWithFile:[url path]];
[cuesheet retain];
NSArray *tracks = [cuesheet tracks]; NSArray *tracks = [cuesheet tracks];
int i; int i;
@ -46,6 +47,7 @@
{ {
if ([[[tracks objectAtIndex:i] track] isEqualToString:[url fragment]]){ if ([[[tracks objectAtIndex:i] track] isEqualToString:[url fragment]]){
track = [tracks objectAtIndex:i]; track = [tracks objectAtIndex:i];
[track retain];
//Kind of a hackish way of accessing outside classes. //Kind of a hackish way of accessing outside classes.
source = [NSClassFromString(@"AudioSource") audioSourceForURL:[track url]]; source = [NSClassFromString(@"AudioSource") audioSourceForURL:[track url]];
@ -103,6 +105,47 @@
[source release]; [source release];
source = nil; 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 - (double)seekToTime:(double)time //milliseconds
@ -119,6 +162,13 @@
bytePosition = (time/1000.0) * bytesPerSecond; 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]; return [decoder seekToTime:time];
} }