Cog/Plugins/sidplay/SidDecoder.mm
Christopher Snowhill 387dcb3453 [Synthesizers] Implement default overrides
Default time, fade, loop count, and sample rate may now be overridden.

Synchronized preferences strings tables. Spanish translation of new
options pending, new releases won't be pushed until they're complete.

Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-07-01 22:06:08 -07:00

431 lines
9.2 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];
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 @{};
}
- (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