2007-10-11 23:11:58 +00:00
|
|
|
//
|
|
|
|
// GameFile.m
|
|
|
|
// Cog
|
|
|
|
//
|
|
|
|
// Created by Vincent Spader on 5/29/06.
|
|
|
|
// Copyright 2006 Vincent Spader. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
#import "GameDecoder.h"
|
|
|
|
|
2013-10-11 12:03:55 +00:00
|
|
|
#import "Logging.h"
|
|
|
|
|
2014-02-14 05:16:18 +00:00
|
|
|
#import "PlaylistController.h"
|
|
|
|
|
2007-10-11 23:11:58 +00:00
|
|
|
@implementation GameDecoder
|
|
|
|
|
|
|
|
gme_err_t readCallback( void* data, void* out, long count )
|
|
|
|
{
|
2013-10-04 15:14:47 +00:00
|
|
|
id source = (id)data;
|
2013-10-11 12:03:55 +00:00
|
|
|
DLog(@"Amount: %li", count);
|
2013-10-04 15:14:47 +00:00
|
|
|
int n = [source read:out amount:count];
|
2013-10-11 12:03:55 +00:00
|
|
|
DLog(@"Read: %i", n);
|
2007-10-11 23:11:58 +00:00
|
|
|
if (n <= 0) {
|
|
|
|
|
2013-10-11 12:03:55 +00:00
|
|
|
DLog(@"ERROR!");
|
2007-10-11 23:11:58 +00:00
|
|
|
return (gme_err_t)1; //Return non-zero for error
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0; //Return 0 for no error
|
|
|
|
}
|
|
|
|
|
|
|
|
- (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] path] pathExtension] lowercaseString];
|
|
|
|
|
|
|
|
gme_type_t type = gme_identify_extension([ext UTF8String]);
|
|
|
|
if (!type)
|
|
|
|
{
|
2013-10-11 12:03:55 +00:00
|
|
|
ALog(@"GME: No type!");
|
2007-10-11 23:11:58 +00:00
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
|
|
|
|
emu = gme_new_emu(type, 44100);
|
|
|
|
if (!emu)
|
|
|
|
{
|
2013-10-11 12:03:55 +00:00
|
|
|
ALog(@"GME: No new emu!");
|
2007-10-11 23:11:58 +00:00
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
|
|
|
|
[source seek:0 whence:SEEK_END];
|
|
|
|
long size = [source tell];
|
|
|
|
[source seek:0 whence:SEEK_SET];
|
|
|
|
|
2013-10-11 12:03:55 +00:00
|
|
|
DLog(@"Size: %li", size);
|
2007-10-11 23:11:58 +00:00
|
|
|
|
2013-10-04 15:14:47 +00:00
|
|
|
error = gme_load_custom(emu, readCallback, size, s);
|
2007-10-11 23:11:58 +00:00
|
|
|
if (error)
|
|
|
|
{
|
2013-10-11 12:03:55 +00:00
|
|
|
ALog(@"GME: ERROR Loding custom!");
|
2007-10-11 23:11:58 +00:00
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
|
|
|
|
int track_num = [[[source url] fragment] intValue]; //What if theres no fragment? Assuming we get 0.
|
|
|
|
|
2013-09-28 03:24:23 +00:00
|
|
|
gme_info_t * info;
|
2007-10-11 23:11:58 +00:00
|
|
|
error = gme_track_info( emu, &info, track_num );
|
|
|
|
if (error)
|
|
|
|
{
|
2013-10-11 12:03:55 +00:00
|
|
|
ALog(@"Unable to get track info");
|
|
|
|
return NO;
|
2007-10-11 23:11:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//As recommended
|
2013-09-28 03:24:23 +00:00
|
|
|
if (info->length > 0) {
|
2013-10-11 12:03:55 +00:00
|
|
|
DLog(@"Using length: %i", info->length);
|
2013-09-28 03:24:23 +00:00
|
|
|
length = info->length;
|
2007-10-11 23:11:58 +00:00
|
|
|
}
|
2013-09-28 03:24:23 +00:00
|
|
|
else if (info->loop_length > 0) {
|
2013-10-11 12:03:55 +00:00
|
|
|
DLog(@"Using loop length: %i", info->loop_length);
|
2013-09-28 03:24:23 +00:00
|
|
|
length = info->intro_length + 2*info->loop_length;
|
2007-10-11 23:11:58 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
length = 150000;
|
2013-10-11 12:03:55 +00:00
|
|
|
DLog(@"Setting default: %li", length);
|
2007-10-11 23:11:58 +00:00
|
|
|
}
|
2013-09-28 03:24:23 +00:00
|
|
|
|
|
|
|
gme_free_info( info );
|
2007-10-11 23:11:58 +00:00
|
|
|
|
2013-10-11 12:03:55 +00:00
|
|
|
DLog(@"Length: %li", length);
|
2007-10-11 23:11:58 +00:00
|
|
|
|
2013-10-11 12:03:55 +00:00
|
|
|
DLog(@"Track num: %i", track_num);
|
2007-10-11 23:11:58 +00:00
|
|
|
error = gme_start_track(emu, track_num);
|
|
|
|
if (error)
|
|
|
|
{
|
2013-10-11 12:03:55 +00:00
|
|
|
ALog(@"GME: Error starting track");
|
2007-10-11 23:11:58 +00:00
|
|
|
return NO;
|
|
|
|
}
|
2013-10-04 20:33:39 +00:00
|
|
|
|
|
|
|
length += 8000;
|
|
|
|
|
2007-10-12 02:55:59 +00:00
|
|
|
|
|
|
|
[self willChangeValueForKey:@"properties"];
|
|
|
|
[self didChangeValueForKey:@"properties"];
|
2007-10-11 23:11:58 +00:00
|
|
|
|
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSDictionary *)properties
|
|
|
|
{
|
|
|
|
return [NSDictionary dictionaryWithObjectsAndKeys:
|
|
|
|
[NSNumber numberWithInt:0], @"bitrate",
|
|
|
|
[NSNumber numberWithFloat:44100], @"sampleRate",
|
2008-02-10 16:04:28 +00:00
|
|
|
[NSNumber numberWithLong:length*44.1], @"totalFrames",
|
2007-10-11 23:11:58 +00:00
|
|
|
[NSNumber numberWithInt:sizeof(short)*8], @"bitsPerSample", //Samples are short
|
|
|
|
[NSNumber numberWithInt:2], @"channels", //output from gme_play is in stereo
|
|
|
|
[NSNumber numberWithBool:[source seekable]], @"seekable",
|
|
|
|
@"host", @"endian",
|
|
|
|
nil];
|
|
|
|
}
|
|
|
|
|
2008-02-10 16:04:28 +00:00
|
|
|
- (int)readAudio:(void *)buf frames:(UInt32)frames
|
2007-10-11 23:11:58 +00:00
|
|
|
{
|
2008-02-10 16:04:28 +00:00
|
|
|
int numSamples = frames * 2; //channels = 2
|
2007-10-11 23:11:58 +00:00
|
|
|
|
2013-10-04 20:33:39 +00:00
|
|
|
if (gme_track_ended(emu)) {
|
2007-10-11 23:11:58 +00:00
|
|
|
return 0;
|
|
|
|
}
|
2014-02-14 05:16:18 +00:00
|
|
|
|
|
|
|
if ( IsRepeatOneSet() )
|
|
|
|
gme_set_fade( emu, INT_MAX, 0 );
|
|
|
|
else
|
|
|
|
gme_set_fade( emu, length - 8000, 8000 );
|
2007-10-11 23:11:58 +00:00
|
|
|
|
|
|
|
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
|
2008-02-10 16:04:28 +00:00
|
|
|
return frames; //GME will always generate samples. There's no real EOS.
|
2007-10-11 23:11:58 +00:00
|
|
|
}
|
|
|
|
|
2008-02-10 16:04:28 +00:00
|
|
|
- (long)seek:(long)frame
|
2007-10-11 23:11:58 +00:00
|
|
|
{
|
|
|
|
gme_err_t error;
|
2008-02-10 16:04:28 +00:00
|
|
|
error = gme_seek(emu, frame/44.1);
|
2007-10-11 23:11:58 +00:00
|
|
|
if (error) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2008-02-10 16:04:28 +00:00
|
|
|
return frame;
|
2007-10-11 23:11:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)close
|
|
|
|
{
|
|
|
|
if (emu) {
|
|
|
|
gme_delete(emu);
|
|
|
|
emu = NULL;
|
|
|
|
}
|
|
|
|
if (source) {
|
|
|
|
[source close];
|
|
|
|
[self setSource:nil];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
+ (NSArray *)fileTypes
|
|
|
|
{
|
2013-10-26 08:54:06 +00:00
|
|
|
return [NSArray arrayWithObjects:@"ay", @"gbs", @"hes", @"kss", @"nsf", @"nsfe", @"sap", @"sfm", @"sgc", @"spc", @"vgm", @"vgz", nil];
|
2007-10-11 23:11:58 +00:00
|
|
|
}
|
|
|
|
|
2007-10-14 18:39:58 +00:00
|
|
|
+ (NSArray *)mimeTypes
|
|
|
|
{
|
2007-10-20 03:17:43 +00:00
|
|
|
return nil;
|
2007-10-14 18:39:58 +00:00
|
|
|
}
|
|
|
|
|
Implemented support for multiple decoders per file name extension, with a floating point priority control per interface. In the event that more than one input is registered to a given extension, and we match that extension, it will be passed off to an instance of the multi-decoder wrapper, which will try opening the file with all of the decoders in order of priority, until either one of them accepts it, or all of them have failed. This paves the way for adding a VGMSTREAM input, so I can give it a very low priority, since it has several formats that are verified by file name extension only. All current inputs have been given a priority of 1.0, except for CoreAudio, which was given a priority of 0.5, because it contains an MP3 and AC3 decoders that I'd rather not use if I don't have to.
2013-10-21 17:54:11 +00:00
|
|
|
+ (float)priority
|
|
|
|
{
|
|
|
|
return 1.0;
|
|
|
|
}
|
|
|
|
|
2007-10-11 23:11:58 +00:00
|
|
|
- (void)setSource:(id<CogSource>)s
|
|
|
|
{
|
|
|
|
[s retain];
|
|
|
|
[source release];
|
|
|
|
source = s;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (id<CogSource>)source
|
|
|
|
{
|
|
|
|
return source;
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|