cog/Plugins/sidplay/SidDecoder.mm

443 lines
9.4 KiB
Plaintext

//
// 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] init];
}
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
// Need this static initializer to create the static global tables that sidplayfp doesn't really lock access to
+ (void)initialize {
ReSIDfpBuilder *builder = new ReSIDfpBuilder("ReSIDfp");
if(builder) {
builder->create(1);
if(builder->getStatus()) {
builder->filter(true);
builder->filter6581Curve(0.5);
builder->filter8580Curve(0.5);
}
delete builder;
}
}
- (BOOL)open:(id<CogSource>)s {
if(![s seekable])
return NO;
[self setSource:s];
sampleRate = [[[[NSUserDefaultsController sharedUserDefaultsController] defaults] valueForKey:@"synthSampleRate"] doubleValue];
if(sampleRate < 8000.0) {
sampleRate = 44100.0;
} else if(sampleRate > 192000.0) {
sampleRate = 192000.0;
}
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;
double defaultLength = [[[[NSUserDefaultsController sharedUserDefaultsController] defaults] valueForKey:@"synthDefaultSeconds"] doubleValue];
length = (int)ceil(sampleRate * defaultLength);
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 = (int)ceil(sampleRate);
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;
}
double defaultFade = [[[[NSUserDefaultsController sharedUserDefaultsController] defaults] valueForKey:@"synthDefaultFadeSeconds"] doubleValue];
if(defaultFade < 0.0) {
defaultFade = 0.0;
}
renderedTotal = 0;
fadeTotal = fadeRemain = (int)ceil(sampleRate * defaultFade);
[self willChangeValueForKey:@"properties"];
[self didChangeValueForKey:@"properties"];
return YES;
}
- (NSDictionary *)properties {
return @{ @"bitrate": @(0),
@"sampleRate": @(sampleRate),
@"totalFrames": @(length),
@"bitsPerSample": @(16),
@"floatingPoint": @(NO),
@"channels": @(n_channels),
@"seekable": @(YES),
@"endian": @"host",
@"encoding": @"synthesized" };
}
- (NSDictionary *)metadata {
return @{};
}
- (AudioChunk *)readAudio {
int total = 0;
id audioChunkClass = NSClassFromString(@"AudioChunk");
AudioChunk *chunk = [[audioChunkClass alloc] initWithProperties:[self properties]];
int16_t buffer[1024 * n_channels];
int framesToRender = 1024;
int rendered = engine->play(buffer, framesToRender * n_channels) / n_channels;
if(rendered <= 0)
return nil;
if(n_channels == 2) {
for(int i = 0, j = rendered * 2; i < j; i += 2) {
int16_t *sample = buffer + 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;
}
}
if(!IsRepeatOneSet() && renderedTotal >= length) {
int16_t *sampleBuf = buffer;
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;
}
[chunk assignSamples:buffer frameCount:rendered];
return chunk;
}
- (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