// // SidDecoder.mm // sidplay // // Created by Christopher Snowhill on 12/8/14. // Copyright 2014 __NoWork, Inc__. All rights reserved. // #import "SidDecoder.h" #import #import "roms.hpp" #import "Logging.h" #import "PlaylistController.h" #include 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 &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 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)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)s { source = s; } - (id)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