Now file hint stashes the whole file in memory, so that any other threads reading the file at the same time will just grab the same memory block and read it, rather than opening the file repeatedly. Signed-off-by: Christopher Snowhill <kode54@gmail.com>
417 lines
8.7 KiB
Text
417 lines
8.7 KiB
Text
//
|
|
// SidDecoder.mm
|
|
// sidplay
|
|
//
|
|
// Created by Christopher Snowhill on 12/8/14.
|
|
// Copyright 2014 __NoWork, Inc__. All rights reserved.
|
|
//
|
|
|
|
#import "SidDecoder.h"
|
|
|
|
#import <sidplayfp/residfp.h>
|
|
|
|
#import "roms.hpp"
|
|
|
|
#import "Logging.h"
|
|
|
|
#import "PlaylistController.h"
|
|
|
|
#include <vector>
|
|
|
|
static const char *extListEmpty[] = { NULL };
|
|
static const char *extListStr[] = { ".str", NULL };
|
|
|
|
@interface sid_file_object : NSObject {
|
|
size_t refCount;
|
|
NSString *path;
|
|
NSData *data;
|
|
}
|
|
@property size_t refCount;
|
|
@property NSString *path;
|
|
@property NSData *data;
|
|
@end
|
|
|
|
@implementation sid_file_object
|
|
@synthesize refCount;
|
|
@synthesize path;
|
|
@synthesize data;
|
|
@end
|
|
|
|
@interface sid_file_container : NSObject {
|
|
NSLock *lock;
|
|
NSMutableDictionary *list;
|
|
}
|
|
+ (sid_file_container *)instance;
|
|
- (void)add_hint:(NSString *)path source:(id)source;
|
|
- (void)remove_hint:(NSString *)path;
|
|
- (BOOL)try_hint:(NSString *)path data:(NSData **)data;
|
|
@end
|
|
|
|
@implementation sid_file_container
|
|
+ (sid_file_container *)instance {
|
|
static sid_file_container *instance;
|
|
|
|
@synchronized(self) {
|
|
if(!instance) {
|
|
instance = [[self alloc] init];
|
|
}
|
|
}
|
|
|
|
return instance;
|
|
}
|
|
- (sid_file_container *)init {
|
|
if((self = [super init])) {
|
|
lock = [[NSLock alloc] init];
|
|
list = [[NSMutableDictionary alloc] initWithCapacity:0];
|
|
}
|
|
return self;
|
|
}
|
|
- (void)add_hint:(NSString *)path source:(id)source {
|
|
[lock lock];
|
|
sid_file_object *obj = [list objectForKey:path];
|
|
if(obj) {
|
|
obj.refCount += 1;
|
|
[lock unlock];
|
|
return;
|
|
}
|
|
[lock unlock];
|
|
|
|
obj = [[sid_file_object alloc] init];
|
|
|
|
obj.refCount = 1;
|
|
|
|
if(![source seekable])
|
|
return;
|
|
|
|
[source seek:0 whence:SEEK_END];
|
|
size_t fileSize = [source tell];
|
|
|
|
void *dataBytes = malloc(fileSize);
|
|
if(!dataBytes)
|
|
return;
|
|
|
|
[source seek:0 whence:SEEK_SET];
|
|
[source read:dataBytes amount:fileSize];
|
|
|
|
NSData *data = [NSData dataWithBytes:dataBytes length:fileSize];
|
|
free(dataBytes);
|
|
|
|
obj.path = path;
|
|
obj.data = data;
|
|
|
|
[lock lock];
|
|
[list setObject:obj forKey:path];
|
|
[lock unlock];
|
|
}
|
|
- (void)remove_hint:(NSString *)path {
|
|
[lock lock];
|
|
sid_file_object *obj = [list objectForKey:path];
|
|
if(obj.refCount <= 1) {
|
|
[list removeObjectForKey:path];
|
|
} else {
|
|
obj.refCount--;
|
|
}
|
|
[lock unlock];
|
|
}
|
|
- (BOOL)try_hint:(NSString *)path data:(NSData **)data {
|
|
sid_file_object *obj;
|
|
[lock lock];
|
|
obj = [list objectForKey:path];
|
|
[lock unlock];
|
|
if(obj) {
|
|
*data = obj.data;
|
|
return YES;
|
|
} else {
|
|
return NO;
|
|
}
|
|
}
|
|
@end
|
|
|
|
static void sidTuneLoader(const char *fileName, std::vector<uint8_t> &bufferRef) {
|
|
NSData *hintData = nil;
|
|
|
|
if(![[sid_file_container instance] try_hint:[NSString stringWithUTF8String:fileName] data:&hintData]) {
|
|
NSString *urlString = [NSString stringWithUTF8String:fileName];
|
|
NSURL *url = [NSURL URLWithDataRepresentation:[urlString dataUsingEncoding:NSUTF8StringEncoding] relativeToURL:nil];
|
|
|
|
id audioSourceClass = NSClassFromString(@"AudioSource");
|
|
id<CogSource> source = [audioSourceClass audioSourceForURL:url];
|
|
|
|
if(![source open:url])
|
|
return;
|
|
|
|
if(![source seekable])
|
|
return;
|
|
|
|
[source seek:0 whence:SEEK_END];
|
|
long fileSize = [source tell];
|
|
[source seek:0 whence:SEEK_SET];
|
|
|
|
bufferRef.resize(fileSize);
|
|
|
|
[source read:&bufferRef[0] amount:fileSize];
|
|
|
|
[source close];
|
|
} else {
|
|
bufferRef.resize([hintData length]);
|
|
memcpy(&bufferRef[0], [hintData bytes], [hintData length]);
|
|
}
|
|
}
|
|
|
|
@implementation SidDecoder
|
|
|
|
- (BOOL)open:(id<CogSource>)s {
|
|
if(![s seekable])
|
|
return NO;
|
|
|
|
[self setSource:s];
|
|
|
|
NSString *path = [[s url] absoluteString];
|
|
NSRange fragmentRange = [path rangeOfString:@"#" options:NSBackwardsSearch];
|
|
if(fragmentRange.location != NSNotFound) {
|
|
path = [path substringToIndex:fragmentRange.location];
|
|
}
|
|
|
|
currentUrl = [path stringByRemovingPercentEncoding];
|
|
|
|
[[sid_file_container instance] add_hint:currentUrl source:s];
|
|
hintAdded = YES;
|
|
|
|
NSString *extension = [[s url] pathExtension];
|
|
|
|
const char **extList = [extension isEqualToString:@"mus"] ? extListStr : extListEmpty;
|
|
|
|
tune = new SidTune(sidTuneLoader, [currentUrl UTF8String], extList, true);
|
|
|
|
if(!tune->getStatus())
|
|
return NO;
|
|
|
|
NSURL *url = [s url];
|
|
int track_num;
|
|
if([[url fragment] length] == 0)
|
|
track_num = 1;
|
|
else
|
|
track_num = [[url fragment] intValue];
|
|
|
|
n_channels = 1;
|
|
|
|
length = 3 * 60 * 44100;
|
|
|
|
tune->selectSong(track_num);
|
|
|
|
engine = new sidplayfp;
|
|
|
|
engine->setRoms(kernel, basic, chargen);
|
|
|
|
if(!engine->load(tune))
|
|
return NO;
|
|
|
|
ReSIDfpBuilder *_builder = new ReSIDfpBuilder("ReSIDfp");
|
|
builder = _builder;
|
|
|
|
if(_builder) {
|
|
_builder->create((engine->info()).maxsids());
|
|
if(_builder->getStatus()) {
|
|
_builder->filter(true);
|
|
_builder->filter6581Curve(0.5);
|
|
_builder->filter8580Curve(0.5);
|
|
}
|
|
if(!_builder->getStatus())
|
|
return NO;
|
|
} else
|
|
return NO;
|
|
|
|
const SidTuneInfo *tuneInfo = tune->getInfo();
|
|
|
|
SidConfig conf = engine->config();
|
|
conf.frequency = 44100;
|
|
conf.sidEmulation = builder;
|
|
conf.playback = SidConfig::MONO;
|
|
if(tuneInfo && (tuneInfo->sidChips() > 1))
|
|
conf.playback = SidConfig::STEREO;
|
|
if(!engine->config(conf))
|
|
return NO;
|
|
|
|
if(conf.playback == SidConfig::STEREO) {
|
|
n_channels = 2;
|
|
}
|
|
|
|
renderedTotal = 0;
|
|
fadeTotal = fadeRemain = 44100 * 8;
|
|
|
|
[self willChangeValueForKey:@"properties"];
|
|
[self didChangeValueForKey:@"properties"];
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (NSDictionary *)properties {
|
|
return @{@"bitrate": [NSNumber numberWithInt:0],
|
|
@"sampleRate": [NSNumber numberWithFloat:44100],
|
|
@"totalFrames": [NSNumber numberWithDouble:length],
|
|
@"bitsPerSample": [NSNumber numberWithInt:16],
|
|
@"floatingPoint": [NSNumber numberWithBool:NO],
|
|
@"channels": [NSNumber numberWithInt:n_channels],
|
|
@"seekable": [NSNumber numberWithBool:YES],
|
|
@"endian": @"host",
|
|
@"encoding": @"synthesized"};
|
|
}
|
|
|
|
- (NSDictionary *)metadata {
|
|
return @{};
|
|
}
|
|
|
|
- (int)readAudio:(void *)buf frames:(UInt32)frames {
|
|
int total = 0;
|
|
int16_t *sampleBuffer = (int16_t *)buf;
|
|
while(total < frames) {
|
|
int framesToRender = 1024;
|
|
if(framesToRender > frames)
|
|
framesToRender = frames;
|
|
int rendered = engine->play(sampleBuffer + total * n_channels, framesToRender * n_channels) / n_channels;
|
|
|
|
if(rendered <= 0)
|
|
break;
|
|
|
|
if(n_channels == 2) {
|
|
for(int i = 0, j = rendered * 2; i < j; i += 2) {
|
|
int16_t *sample = sampleBuffer + total * 2 + i;
|
|
int mid = (int)(sample[0] + sample[1]) / 2;
|
|
int side = (int)(sample[0] - sample[1]) / 4;
|
|
sample[0] = mid + side;
|
|
sample[1] = mid - side;
|
|
}
|
|
}
|
|
|
|
renderedTotal += rendered;
|
|
|
|
if(!IsRepeatOneSet() && renderedTotal >= length) {
|
|
int16_t *sampleBuf = (int16_t *)buf + total * n_channels;
|
|
long fadeEnd = fadeRemain - rendered;
|
|
if(fadeEnd < 0)
|
|
fadeEnd = 0;
|
|
float fadePosf = (float)fadeRemain / (float)fadeTotal;
|
|
const float fadeStep = 1.0f / (float)fadeTotal;
|
|
for(long fadePos = fadeRemain; fadePos > fadeEnd; --fadePos, fadePosf -= fadeStep) {
|
|
long offset = (fadeRemain - fadePos) * n_channels;
|
|
float sampleLeft = sampleBuf[offset + 0];
|
|
sampleLeft *= fadePosf;
|
|
sampleBuf[offset + 0] = (int16_t)sampleLeft;
|
|
if(n_channels == 2) {
|
|
float sampleRight = sampleBuf[offset + 1];
|
|
sampleRight *= fadePosf;
|
|
sampleBuf[offset + 1] = (int16_t)sampleRight;
|
|
}
|
|
}
|
|
rendered = (int)(fadeRemain - fadeEnd);
|
|
fadeRemain = fadeEnd;
|
|
}
|
|
|
|
total += rendered;
|
|
|
|
if(rendered < framesToRender)
|
|
break;
|
|
}
|
|
|
|
return total;
|
|
}
|
|
|
|
- (long)seek:(long)frame {
|
|
if(frame < renderedTotal) {
|
|
engine->load(tune);
|
|
renderedTotal = 0;
|
|
}
|
|
|
|
int16_t sampleBuffer[1024 * 2];
|
|
|
|
long remain = (frame - renderedTotal) % 32;
|
|
frame /= 32;
|
|
renderedTotal /= 32;
|
|
engine->fastForward(100 * 32);
|
|
|
|
while(renderedTotal < frame) {
|
|
long todo = frame - renderedTotal;
|
|
if(todo > 1024)
|
|
todo = 1024;
|
|
int done = engine->play(sampleBuffer, (uint_least32_t)(todo * n_channels)) / n_channels;
|
|
|
|
if(done < todo) {
|
|
if(engine->error())
|
|
return -1;
|
|
|
|
renderedTotal = length;
|
|
break;
|
|
}
|
|
|
|
renderedTotal += todo;
|
|
}
|
|
|
|
renderedTotal *= 32;
|
|
engine->fastForward(100);
|
|
|
|
if(remain)
|
|
renderedTotal += engine->play(sampleBuffer, (uint_least32_t)(remain * n_channels)) / n_channels;
|
|
|
|
return renderedTotal;
|
|
}
|
|
|
|
- (void)cleanUp {
|
|
if(builder) {
|
|
delete builder;
|
|
builder = NULL;
|
|
}
|
|
|
|
if(engine) {
|
|
delete engine;
|
|
engine = NULL;
|
|
}
|
|
|
|
if(tune) {
|
|
delete tune;
|
|
tune = NULL;
|
|
}
|
|
|
|
source = nil;
|
|
if(hintAdded) {
|
|
[[sid_file_container instance] remove_hint:currentUrl];
|
|
hintAdded = NO;
|
|
}
|
|
currentUrl = nil;
|
|
}
|
|
|
|
- (void)close {
|
|
[self cleanUp];
|
|
}
|
|
|
|
- (void)dealloc {
|
|
[self close];
|
|
}
|
|
|
|
- (void)setSource:(id<CogSource>)s {
|
|
source = s;
|
|
}
|
|
|
|
- (id<CogSource>)source {
|
|
return source;
|
|
}
|
|
|
|
+ (NSArray *)fileTypes {
|
|
return @[@"sid", @"mus"];
|
|
}
|
|
|
|
+ (NSArray *)mimeTypes {
|
|
return nil;
|
|
}
|
|
|
|
+ (float)priority {
|
|
return 0.5;
|
|
}
|
|
|
|
+ (NSArray *)fileTypeAssociations {
|
|
return @[
|
|
@[@"SID File", @"vg.icns", @"sid"],
|
|
@[@"SID MUS File", @"song.icns", @"mus"]
|
|
];
|
|
}
|
|
|
|
@end
|