Added support for gapless multitrack files.
Modified cue sheet plugin to use it.CQTexperiment
parent
4bcf22ced2
commit
b57bcc121c
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
- (void)addChainToQueue:(BufferChain *)newChain
|
||||
{
|
||||
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 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]];
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -134,6 +134,9 @@ static OSStatus ACInputProc(AudioConverterRef inAudioConverter, UInt32* ioNumber
|
|||
}
|
||||
}
|
||||
|
||||
PrintStreamDesc(&inf);
|
||||
PrintStreamDesc(&outf);
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
|
|
@ -19,17 +19,21 @@
|
|||
@interface InputNode : Node {
|
||||
id<CogDecoder> decoder;
|
||||
|
||||
AudioStreamBasicDescription outputFormat;
|
||||
|
||||
BOOL shouldSeek;
|
||||
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;
|
||||
- (NSDictionary *) properties;
|
||||
- (void)seek:(double)time;
|
||||
|
||||
- (void)registerObservers;
|
||||
|
||||
- (BOOL)setTrack:(NSURL *)track;
|
||||
|
||||
- (id<CogDecoder>) decoder;
|
||||
|
||||
@end
|
||||
|
|
|
@ -13,18 +13,16 @@
|
|||
|
||||
@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 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<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
|
||||
{
|
||||
[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<CogDecoder>) decoder
|
||||
{
|
||||
return decoder;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -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 */ = {
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
#import "Plugin.h"
|
||||
|
||||
@class CueSheet;
|
||||
@class CueSheetTrack;
|
||||
|
||||
@interface CueSheetDecoder : NSObject <CogDecoder> {
|
||||
|
@ -20,6 +21,7 @@
|
|||
int bytePosition;
|
||||
double trackEnd;
|
||||
|
||||
CueSheet *cuesheet;
|
||||
CueSheetTrack *track;
|
||||
}
|
||||
|
||||
|
|
|
@ -38,7 +38,8 @@
|
|||
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;
|
||||
|
@ -46,6 +47,7 @@
|
|||
{
|
||||
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];
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue