Audio Chunks now have full timestamp accounting, including DSP playback speed ratio for the one DSP that can change play ratio, Rubber Band. Inputs which support looping and actually reporting the absolute play position now do so. Signed-off-by: Christopher Snowhill <kode54@gmail.com>
128 lines
2.5 KiB
Text
128 lines
2.5 KiB
Text
//
|
|
// ShnFile.mm
|
|
// Cog
|
|
//
|
|
// Created by Vincent Spader on 6/6/05.
|
|
// Copyright 2005 Vincent Spader All rights reserved.
|
|
//
|
|
|
|
#import "ShortenDecoder.h"
|
|
|
|
@implementation ShortenDecoder
|
|
|
|
- (BOOL)open:(id<CogSource>)source {
|
|
NSURL *url = [source url];
|
|
|
|
if(![[url scheme] isEqualToString:@"file"])
|
|
return NO;
|
|
|
|
decoder = new shn_reader;
|
|
|
|
if(!decoder) {
|
|
return NO;
|
|
}
|
|
|
|
decoder->open([[url path] UTF8String], true);
|
|
|
|
bufferSize = decoder->shn_get_buffer_block_size(NUM_DEFAULT_BUFFER_BLOCKS);
|
|
|
|
bool seekTable;
|
|
decoder->file_info(NULL, &channels, &frequency, NULL, &bitsPerSample, &seekTable);
|
|
|
|
seekable = seekTable == true ? YES : NO;
|
|
|
|
totalFrames = (decoder->shn_get_song_length() * frequency) / 1000.0;
|
|
|
|
seconds = 0.0;
|
|
|
|
decoder->go();
|
|
|
|
[self willChangeValueForKey:@"properties"];
|
|
[self didChangeValueForKey:@"properties"];
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (AudioChunk *)readAudio {
|
|
long frames = 1024;
|
|
long bytesPerFrame = channels * (bitsPerSample / 8);
|
|
long amountRead;
|
|
|
|
id audioChunkClass = NSClassFromString(@"AudioChunk");
|
|
AudioChunk *chunk = [[audioChunkClass alloc] initWithProperties:[self properties]];
|
|
|
|
uint8_t buffer[bytesPerFrame * 1024];
|
|
void *buf = (void *)buffer;
|
|
|
|
// For some reason a busy loop is causing pops when output is set to 48000. Probably CPU starvation, since the SHN decoder seems to use a multithreaded nonblocking approach.
|
|
do {
|
|
amountRead = decoder->read(buf, frames * bytesPerFrame);
|
|
} while(amountRead == -1);
|
|
|
|
[chunk setStreamTimestamp:seconds];
|
|
|
|
[chunk assignSamples:buf frameCount:amountRead / bytesPerFrame];
|
|
|
|
seconds += [chunk duration];
|
|
|
|
return chunk;
|
|
}
|
|
|
|
- (long)seek:(long)sample {
|
|
unsigned int sec = sample / frequency;
|
|
|
|
decoder->seek(sec);
|
|
seconds = sec;
|
|
|
|
return sample;
|
|
}
|
|
|
|
- (void)close {
|
|
if(decoder) {
|
|
decoder->exit();
|
|
delete decoder;
|
|
decoder = NULL;
|
|
}
|
|
|
|
/*if (shn_cleanup_decoder(handle))
|
|
shn_unload(handle);*/
|
|
}
|
|
|
|
- (void)dealloc {
|
|
[self close];
|
|
}
|
|
|
|
- (NSDictionary *)properties {
|
|
return @{ @"channels": @(channels),
|
|
@"bitsPerSample": @(bitsPerSample),
|
|
@"sampleRate": @(frequency),
|
|
@"totalFrames": @(totalFrames),
|
|
@"seekable": @(seekable),
|
|
@"codec": @"Shorten",
|
|
@"endian": @"little",
|
|
@"encoding": @"lossless" };
|
|
}
|
|
|
|
- (NSDictionary *)metadata {
|
|
return @{};
|
|
}
|
|
|
|
+ (NSArray *)fileTypes {
|
|
return @[@"shn"];
|
|
}
|
|
|
|
+ (NSArray *)mimeTypes {
|
|
return @[@"application/x-shorten"]; // This is basically useless, since we cant stream shorten yet
|
|
}
|
|
|
|
+ (float)priority {
|
|
return 1.0;
|
|
}
|
|
|
|
+ (NSArray *)fileTypeAssociations {
|
|
return @[
|
|
@[@"Shorten File", @"shn.icns", @"shn"]
|
|
];
|
|
}
|
|
|
|
@end
|