Cog/Plugins/GME/GameDecoder.m
Christopher Snowhill 3c351f6968 [Input API] Change input readAudio method
readAudio now returns an AudioChunk object directly, and all inputs have
been changed to accomodate this. Also, input and converter processing
have been altered to better work with this.

Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-07-10 15:14:47 -07:00

257 lines
5.7 KiB
Objective-C

//
// GameFile.m
// Cog
//
// Created by Vincent Spader on 5/29/06.
// Copyright 2006 Vincent Spader. All rights reserved.
//
#import "GameDecoder.h"
#import "Logging.h"
#import "PlaylistController.h"
@implementation GameDecoder
gme_err_t readCallback(void *data, void *out, int count) {
id source = (__bridge id)data;
DLog(@"Amount: %i", count);
int n = (int)[source read:out amount:count];
DLog(@"Read: %i", n);
if(n <= 0) {
DLog(@"ERROR!");
return (gme_err_t)1; // Return non-zero for error
}
return 0; // Return 0 for no error
}
- (id)init {
self = [super init];
if(self) {
emu = NULL;
}
return self;
}
- (BOOL)open:(id<CogSource>)s {
[self setSource:s];
// We need file-size to use GME
if(![source seekable]) {
return NO;
}
gme_err_t error;
NSString *ext = [[[source url] pathExtension] lowercaseString];
gme_type_t type = gme_identify_extension([ext UTF8String]);
if(!type) {
ALog(@"GME: No type!");
return NO;
}
sampleRate = [[[[NSUserDefaultsController sharedUserDefaultsController] defaults] valueForKey:@"synthSampleRate"] doubleValue];
if(sampleRate < 8000.0) {
sampleRate = 44100.0;
} else if(sampleRate > 192000.0) {
sampleRate = 192000.0;
}
if(type == gme_spc_type || type == gme_sfm_type)
sampleRate = 32000.0;
emu = gme_new_emu(type, (int)sampleRate);
if(!emu) {
ALog(@"GME: No new emu!");
return NO;
}
[source seek:0 whence:SEEK_END];
long size = [source tell];
[source seek:0 whence:SEEK_SET];
DLog(@"Size: %li", size);
error = gme_load_custom(emu, readCallback, size, (__bridge void *)(s));
if(error) {
ALog(@"GME: ERROR Loding custom!");
return NO;
}
NSURL *m3uurl = [[source url] URLByDeletingPathExtension];
m3uurl = [m3uurl URLByAppendingPathExtension:@"m3u"];
id audioSourceClass = NSClassFromString(@"AudioSource");
id<CogSource> m3usrc = [audioSourceClass audioSourceForURL:m3uurl];
if([m3usrc open:m3uurl]) {
if([m3usrc seekable]) {
[m3usrc seek:0 whence:SEEK_END];
long size = [m3usrc tell];
[m3usrc seek:0 whence:SEEK_SET];
void *data = malloc(size);
[m3usrc read:data amount:size];
gme_load_m3u_data(emu, data, size);
free(data);
}
}
int track_num = [[[source url] fragment] intValue]; // What if theres no fragment? Assuming we get 0.
gme_info_t *info;
error = gme_track_info(emu, &info, track_num);
if(error) {
ALog(@"Unable to get track info");
return NO;
}
// As recommended
if(info->length > 0) {
DLog(@"Using length: %i", info->length);
length = info->length;
} else if(info->loop_length > 0) {
DLog(@"Using loop length: %i", info->loop_length);
int loopCount = [[[[NSUserDefaultsController sharedUserDefaultsController] defaults] valueForKey:@"synthDefaultLoopCount"] intValue];
if(loopCount < 0) {
loopCount = 1;
} else if(loopCount > 10) {
loopCount = 10;
}
length = info->intro_length + loopCount * info->loop_length;
} else {
double defaultLength = [[[[NSUserDefaultsController sharedUserDefaultsController] defaults] valueForKey:@"synthDefaultSeconds"] doubleValue];
if(defaultLength < 0) {
defaultLength = 150.0;
}
length = (int)ceil(defaultLength * 1000.0);
DLog(@"Setting default: %li", length);
}
if(info->fade_length >= 0) {
fade = info->fade_length;
} else {
double defaultFade = [[[[NSUserDefaultsController sharedUserDefaultsController] defaults] valueForKey:@"synthDefaultFadeSeconds"] doubleValue];
if(defaultFade < 0) {
defaultFade = 0;
}
fade = (int)ceil(defaultFade * 1000.0);
}
gme_free_info(info);
DLog(@"Length: %li", length);
DLog(@"Track num: %i", track_num);
error = gme_start_track(emu, track_num);
if(error) {
ALog(@"GME: Error starting track");
return NO;
}
length += fade;
[self willChangeValueForKey:@"properties"];
[self didChangeValueForKey:@"properties"];
return YES;
}
- (NSDictionary *)properties {
return @{ @"bitrate": @(0),
@"sampleRate": @(sampleRate),
@"totalFrames": @((long)(length * (sampleRate * 0.001))),
@"bitsPerSample": @(sizeof(short) * 8), // Samples are short
@"channels": @(2), // output from gme_play is in stereo
@"seekable": @(YES),
@"endian": @"host",
@"encoding": @"synthesized" };
}
- (NSDictionary *)metadata {
return @{};
}
- (AudioChunk *)readAudio {
int frames = 1024;
int16_t buffer[frames * 2];
void *buf = (void *)buffer;
id audioChunkClass = NSClassFromString(@"AudioChunk");
AudioChunk *chunk = [[audioChunkClass alloc] initWithProperties:[self properties]];
int numSamples = frames * 2; // channels = 2
if(gme_track_ended(emu)) {
return nil;
}
if(IsRepeatOneSet())
gme_set_fade(emu, -1, 0);
else
gme_set_fade(emu, (int)(length - fade), (int)fade);
gme_play(emu, numSamples, (short int *)buf);
// Some formats support length, but we'll add that in the future.
//(From gme.txt) If track length, then use it. If loop length, play for intro + loop * 2. Otherwise, default to 2.5 minutes
// GME will always generate samples. There's no real EOS.
[chunk assignSamples:buffer frameCount:numSamples];
return chunk;
}
- (long)seek:(long)frame {
gme_err_t error;
error = gme_seek(emu, frame * sampleRate * 0.001);
if(error) {
return -1;
}
return frame;
}
- (void)close {
if(emu) {
gme_delete(emu);
emu = NULL;
}
}
- (void)dealloc {
[self close];
}
+ (NSArray *)fileTypes {
return @[@"ay", @"gbs", @"hes", @"kss", @"nsf", @"nsfe", @"sap", @"sfm", @"sgc", @"spc"];
}
+ (NSArray *)mimeTypes {
return nil;
}
+ (float)priority {
return 1.0;
}
+ (NSArray *)fileTypeAssociations {
NSMutableArray *ret = [[NSMutableArray alloc] init];
[ret addObject:@"Game Music Emu Files"];
[ret addObject:@"vg.icns"];
[ret addObjectsFromArray:[self fileTypes]];
return @[ret];
}
- (void)setSource:(id<CogSource>)s {
source = s;
}
- (id<CogSource>)source {
return source;
}
@end