2007-10-12 01:03:12 +00:00
|
|
|
//
|
|
|
|
// DumbFile.m
|
|
|
|
// Cog
|
|
|
|
//
|
|
|
|
// Created by Vincent Spader on 5/29/06.
|
|
|
|
// Copyright 2006 Vincent Spader. All rights reserved.
|
|
|
|
//
|
|
|
|
|
2013-11-24 02:57:46 +00:00
|
|
|
#define _USE_SSE 1
|
|
|
|
|
2007-10-12 01:03:12 +00:00
|
|
|
#import "DumbDecoder.h"
|
|
|
|
|
2013-10-05 02:25:45 +00:00
|
|
|
#import "umx.h"
|
|
|
|
#import "j2b.h"
|
2013-11-01 19:24:59 +00:00
|
|
|
#import "mo3.h"
|
2013-10-05 02:25:45 +00:00
|
|
|
|
2013-10-11 12:03:55 +00:00
|
|
|
#import "Logging.h"
|
|
|
|
|
2014-02-14 05:16:18 +00:00
|
|
|
#import "PlaylistController.h"
|
|
|
|
|
2007-10-12 01:03:12 +00:00
|
|
|
@implementation DumbDecoder
|
|
|
|
|
2013-10-05 02:25:45 +00:00
|
|
|
struct MEMANDFREEFILE
|
2007-10-12 01:03:12 +00:00
|
|
|
{
|
2013-10-05 02:25:45 +00:00
|
|
|
char *ptr, *ptr_begin;
|
|
|
|
long left, size;
|
2013-11-01 19:24:59 +00:00
|
|
|
int is_mo3;
|
2013-10-05 02:25:45 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static int dumb_maffile_skip(void *f, long n)
|
|
|
|
{
|
|
|
|
struct MEMANDFREEFILE *m = f;
|
|
|
|
if (n > m->left) return -1;
|
|
|
|
m->ptr += n;
|
|
|
|
m->left -= n;
|
|
|
|
return 0;
|
2007-10-12 01:03:12 +00:00
|
|
|
}
|
|
|
|
|
2013-10-05 02:25:45 +00:00
|
|
|
static int dumb_maffile_getc(void *f)
|
2007-10-12 01:03:12 +00:00
|
|
|
{
|
2013-10-05 02:25:45 +00:00
|
|
|
struct MEMANDFREEFILE *m = f;
|
|
|
|
if (m->left <= 0) return -1;
|
|
|
|
m->left--;
|
|
|
|
return *(const unsigned char *)m->ptr++;
|
|
|
|
}
|
2007-10-12 01:03:12 +00:00
|
|
|
|
2013-10-05 02:25:45 +00:00
|
|
|
static long dumb_maffile_getnc(char *ptr, long n, void *f)
|
|
|
|
{
|
|
|
|
struct MEMANDFREEFILE *m = f;
|
|
|
|
if (n > m->left) n = m->left;
|
|
|
|
memcpy(ptr, m->ptr, n);
|
|
|
|
m->ptr += n;
|
|
|
|
m->left -= n;
|
|
|
|
return n;
|
2007-10-12 01:03:12 +00:00
|
|
|
}
|
|
|
|
|
2013-10-05 02:25:45 +00:00
|
|
|
static void dumb_maffile_close(void *f)
|
2007-10-12 01:03:12 +00:00
|
|
|
{
|
2013-10-05 02:25:45 +00:00
|
|
|
struct MEMANDFREEFILE *m = f;
|
2013-11-01 19:24:59 +00:00
|
|
|
if (m->is_mo3) freeMo3(m->ptr_begin);
|
|
|
|
else free(m->ptr_begin);
|
2013-10-05 02:25:45 +00:00
|
|
|
free(f);
|
2007-10-12 01:03:12 +00:00
|
|
|
}
|
|
|
|
|
2013-10-05 02:25:45 +00:00
|
|
|
static int dumb_maffile_seek(void *f, long n)
|
2013-09-28 03:24:23 +00:00
|
|
|
{
|
2013-10-05 02:25:45 +00:00
|
|
|
struct MEMANDFREEFILE *m = f;
|
2013-09-28 03:24:23 +00:00
|
|
|
|
2013-10-05 02:25:45 +00:00
|
|
|
m->ptr = m->ptr_begin + n;
|
|
|
|
m->left = m->size - n;
|
2013-09-28 03:24:23 +00:00
|
|
|
|
2013-10-05 02:25:45 +00:00
|
|
|
return 0;
|
2013-09-28 03:24:23 +00:00
|
|
|
}
|
|
|
|
|
2013-10-05 02:25:45 +00:00
|
|
|
static long dumb_maffile_get_size(void *f)
|
2013-09-28 03:24:23 +00:00
|
|
|
{
|
2013-10-05 02:25:45 +00:00
|
|
|
struct MEMANDFREEFILE *m = f;
|
|
|
|
return m->size;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const DUMBFILE_SYSTEM maffile_dfs = {
|
|
|
|
NULL,
|
|
|
|
&dumb_maffile_skip,
|
|
|
|
&dumb_maffile_getc,
|
|
|
|
&dumb_maffile_getnc,
|
|
|
|
&dumb_maffile_close,
|
|
|
|
&dumb_maffile_seek,
|
|
|
|
&dumb_maffile_get_size
|
|
|
|
};
|
|
|
|
|
|
|
|
DUMBFILE *dumbfile_open_memory_and_free(char *data, long size)
|
|
|
|
{
|
2013-11-01 19:24:59 +00:00
|
|
|
int is_mo3 = 0;
|
|
|
|
char * try_data = unpackMo3( data, &size );
|
2013-10-05 02:25:45 +00:00
|
|
|
if ( try_data ) {
|
|
|
|
free( data );
|
|
|
|
data = try_data;
|
2013-11-01 19:24:59 +00:00
|
|
|
is_mo3 = 1;
|
2013-10-05 02:25:45 +00:00
|
|
|
}
|
|
|
|
else {
|
2013-11-01 19:24:59 +00:00
|
|
|
try_data = unpackUmx( data, &size );
|
2013-10-05 02:25:45 +00:00
|
|
|
if ( try_data ) {
|
|
|
|
free( data );
|
|
|
|
data = try_data;
|
|
|
|
}
|
2013-11-01 19:24:59 +00:00
|
|
|
else {
|
|
|
|
try_data = unpackJ2b( data, &size );
|
|
|
|
if ( try_data ) {
|
|
|
|
free( data );
|
|
|
|
data = try_data;
|
|
|
|
}
|
|
|
|
}
|
2013-10-05 02:25:45 +00:00
|
|
|
}
|
2013-09-28 03:24:23 +00:00
|
|
|
|
2013-10-05 02:25:45 +00:00
|
|
|
struct MEMANDFREEFILE *m = malloc(sizeof(*m));
|
|
|
|
if (!m) return NULL;
|
2013-09-28 03:24:23 +00:00
|
|
|
|
2013-10-05 02:25:45 +00:00
|
|
|
m->ptr_begin = data;
|
|
|
|
m->ptr = data;
|
|
|
|
m->left = size;
|
|
|
|
m->size = size;
|
2013-11-01 19:24:59 +00:00
|
|
|
m->is_mo3 = is_mo3;
|
2013-09-28 03:24:23 +00:00
|
|
|
|
2013-10-05 02:25:45 +00:00
|
|
|
return dumbfile_open_ex(m, &maffile_dfs);
|
2013-09-28 03:24:23 +00:00
|
|
|
}
|
|
|
|
|
2013-11-24 02:57:46 +00:00
|
|
|
+ (void)initialize
|
2013-09-30 00:28:58 +00:00
|
|
|
{
|
2013-11-24 02:57:46 +00:00
|
|
|
if (self == [DumbDecoder class])
|
2013-09-30 00:28:58 +00:00
|
|
|
{
|
2013-11-24 02:57:46 +00:00
|
|
|
// do this here so we don't have to wait on it later
|
|
|
|
_dumb_init_cubic();
|
|
|
|
_dumb_init_sse();
|
2013-09-30 00:28:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-19 19:57:18 +00:00
|
|
|
- (id)init
|
|
|
|
{
|
|
|
|
self = [super init];
|
|
|
|
if (self) {
|
|
|
|
sampptr = NULL;
|
|
|
|
dsr = NULL;
|
|
|
|
duh = NULL;
|
|
|
|
}
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
2013-10-05 00:23:11 +00:00
|
|
|
int callbackLoop(void *data)
|
|
|
|
{
|
|
|
|
long * loops = (long *) data;
|
|
|
|
++ *loops;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-10-12 01:03:12 +00:00
|
|
|
- (BOOL)open:(id<CogSource>)s
|
|
|
|
{
|
|
|
|
[self setSource:s];
|
|
|
|
|
2013-10-05 02:25:45 +00:00
|
|
|
[source seek:0 whence:SEEK_END];
|
|
|
|
long size = [source tell];
|
|
|
|
[source seek:0 whence:SEEK_SET];
|
|
|
|
|
|
|
|
void * data = malloc(size);
|
|
|
|
[source read:data amount:size];
|
|
|
|
|
|
|
|
DUMBFILE * df = dumbfile_open_memory_and_free( data, size );
|
2007-10-12 01:03:12 +00:00
|
|
|
if (!df)
|
|
|
|
{
|
2013-10-11 12:03:55 +00:00
|
|
|
ALog(@"Open failed for file: %@", [[s url] absoluteString]);
|
2007-10-12 01:03:12 +00:00
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
|
2013-10-04 23:34:29 +00:00
|
|
|
int subsong = 0;
|
|
|
|
int startOrder = 0;
|
|
|
|
|
|
|
|
NSURL * url = [s url];
|
|
|
|
int track_num;
|
|
|
|
if ([[url fragment] length] == 0)
|
|
|
|
track_num = 0;
|
|
|
|
else
|
|
|
|
track_num = [[url fragment] intValue];
|
|
|
|
|
|
|
|
if ( dumb_get_psm_subsong_count( df ) )
|
|
|
|
subsong = track_num;
|
|
|
|
else
|
|
|
|
startOrder = track_num;
|
|
|
|
|
|
|
|
dumbfile_seek( df, 0, SEEK_SET );
|
|
|
|
|
2016-06-19 19:57:18 +00:00
|
|
|
NSString *ext = [[[s url] pathExtension] lowercaseString];
|
2013-10-04 23:34:29 +00:00
|
|
|
duh = dumb_read_any(df, [ext isEqualToString:@"mod"] ? 0 : 1, subsong);
|
2007-10-12 01:03:12 +00:00
|
|
|
if (!duh)
|
|
|
|
{
|
2013-10-11 12:03:55 +00:00
|
|
|
ALog(@"Failed to create duh");
|
2013-09-30 00:28:58 +00:00
|
|
|
dumbfile_close(df);
|
2007-10-12 01:03:12 +00:00
|
|
|
return NO;
|
|
|
|
}
|
2013-09-30 00:28:58 +00:00
|
|
|
dumbfile_close(df);
|
2007-10-12 01:03:12 +00:00
|
|
|
|
2013-10-04 23:34:29 +00:00
|
|
|
length = dumb_it_build_checkpoints( duh_get_it_sigdata( duh ), startOrder );
|
2007-10-12 01:03:12 +00:00
|
|
|
|
2013-10-04 23:34:29 +00:00
|
|
|
dsr = duh_start_sigrenderer(duh, 0, 2 /* stereo */, startOrder);
|
2007-10-12 01:03:12 +00:00
|
|
|
if (!dsr)
|
|
|
|
{
|
2013-10-11 12:03:55 +00:00
|
|
|
ALog(@"Failed to create dsr");
|
2007-10-12 01:03:12 +00:00
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
|
2013-09-28 03:24:23 +00:00
|
|
|
DUMB_IT_SIGRENDERER * itsr = duh_get_it_sigrenderer( dsr );
|
2014-03-27 04:49:31 +00:00
|
|
|
|
|
|
|
int resampling_int = -1;
|
|
|
|
NSString * resampling = [[NSUserDefaults standardUserDefaults] stringForKey:@"resampling"];
|
|
|
|
if ([resampling isEqualToString:@"zoh"])
|
|
|
|
resampling_int = 0;
|
|
|
|
else if ([resampling isEqualToString:@"blep"])
|
|
|
|
resampling_int = 1;
|
|
|
|
else if ([resampling isEqualToString:@"linear"])
|
|
|
|
resampling_int = 2;
|
2015-01-11 07:07:51 +00:00
|
|
|
else if ([resampling isEqualToString:@"blam"])
|
2014-03-27 04:49:31 +00:00
|
|
|
resampling_int = 3;
|
2015-01-11 07:07:51 +00:00
|
|
|
else if ([resampling isEqualToString:@"cubic"])
|
2014-03-27 04:49:31 +00:00
|
|
|
resampling_int = 4;
|
2015-01-11 07:07:51 +00:00
|
|
|
else if ([resampling isEqualToString:@"sinc"])
|
|
|
|
resampling_int = 5;
|
2014-03-27 04:49:31 +00:00
|
|
|
|
|
|
|
dumb_it_set_resampling_quality( itsr, resampling_int );
|
2014-03-26 09:27:28 +00:00
|
|
|
dumb_it_set_ramp_style(itsr, 2);
|
2013-10-05 00:23:11 +00:00
|
|
|
dumb_it_set_loop_callback( itsr, callbackLoop, &loops);
|
|
|
|
dumb_it_set_xm_speed_zero_callback( itsr, dumb_it_callback_terminate, 0);
|
|
|
|
dumb_it_set_global_volume_zero_callback( itsr, dumb_it_callback_terminate, 0);
|
|
|
|
|
|
|
|
loops = 0;
|
|
|
|
fadeTotal = fadeRemain = 44100 * 8;
|
|
|
|
|
2013-10-05 21:15:09 +00:00
|
|
|
sampptr = allocate_sample_buffer(2, 1024);
|
|
|
|
|
2013-09-28 03:24:23 +00:00
|
|
|
[self willChangeValueForKey:@"properties"];
|
2007-10-12 02:55:59 +00:00
|
|
|
[self didChangeValueForKey:@"properties"];
|
|
|
|
|
2007-10-12 01:03:12 +00:00
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSDictionary *)properties
|
|
|
|
{
|
|
|
|
return [NSDictionary dictionaryWithObjectsAndKeys:
|
|
|
|
[NSNumber numberWithInt:0], @"bitrate",
|
|
|
|
[NSNumber numberWithFloat:44100], @"sampleRate",
|
2008-02-10 15:56:18 +00:00
|
|
|
[NSNumber numberWithDouble:((length / 65.536f)*44.1000)], @"totalFrames",
|
2013-10-05 21:15:09 +00:00
|
|
|
[NSNumber numberWithInt:32], @"bitsPerSample", //Samples are short
|
|
|
|
[NSNumber numberWithBool:YES], @"floatingPoint",
|
2007-10-12 01:03:12 +00:00
|
|
|
[NSNumber numberWithInt:2], @"channels", //output from gme_play is in stereo
|
|
|
|
[NSNumber numberWithBool:[source seekable]], @"seekable",
|
|
|
|
@"host", @"endian",
|
|
|
|
nil];
|
|
|
|
}
|
|
|
|
|
2008-02-10 15:56:18 +00:00
|
|
|
- (int)readAudio:(void *)buf frames:(UInt32)frames
|
2007-10-12 01:03:12 +00:00
|
|
|
{
|
2013-10-05 21:15:09 +00:00
|
|
|
int total = 0;
|
|
|
|
while ( total < frames ) {
|
|
|
|
int framesToRender = 1024;
|
|
|
|
if ( framesToRender > frames )
|
|
|
|
framesToRender = frames;
|
|
|
|
dumb_silence( sampptr[0], framesToRender * 2 );
|
2016-06-30 05:10:29 +00:00
|
|
|
int rendered = (int) duh_sigrenderer_generate_samples( dsr, 1.0, 65536.0f / 44100.0f, framesToRender, sampptr );
|
2013-10-05 21:15:09 +00:00
|
|
|
|
|
|
|
if (rendered <= 0)
|
|
|
|
break;
|
|
|
|
|
|
|
|
for ( int i = 0; i < rendered * 2; i++ ) {
|
|
|
|
const float scale = 1.0 / 0x800000;
|
|
|
|
((float *)buf)[(total * 2) + i] = (float)sampptr[0][i] * scale;
|
|
|
|
}
|
2013-10-05 00:23:11 +00:00
|
|
|
|
2014-02-14 05:16:18 +00:00
|
|
|
if ( !IsRepeatOneSet() && loops >= 2 ) {
|
2013-10-05 22:47:35 +00:00
|
|
|
float * sampleBuf = ( float * ) buf + total * 2;
|
2013-10-05 21:15:09 +00:00
|
|
|
long fadeEnd = fadeRemain - rendered;
|
|
|
|
if ( fadeEnd < 0 )
|
|
|
|
fadeEnd = 0;
|
|
|
|
float fadePosf = (float)fadeRemain / fadeTotal;
|
|
|
|
const float fadeStep = 1.0 / fadeTotal;
|
|
|
|
for ( long fadePos = fadeRemain; fadePos > fadeEnd; --fadePos, fadePosf -= fadeStep ) {
|
|
|
|
long offset = (fadeRemain - fadePos) * 2;
|
|
|
|
float sampleLeft = sampleBuf[ offset + 0 ];
|
|
|
|
float sampleRight = sampleBuf[ offset + 1 ];
|
|
|
|
sampleLeft *= fadePosf;
|
|
|
|
sampleRight *= fadePosf;
|
|
|
|
sampleBuf[ offset + 0 ] = sampleLeft;
|
|
|
|
sampleBuf[ offset + 1 ] = sampleRight;
|
|
|
|
}
|
2016-06-30 05:10:29 +00:00
|
|
|
rendered = (int)(fadeRemain - fadeEnd);
|
2013-10-05 21:15:09 +00:00
|
|
|
fadeRemain = fadeEnd;
|
2013-10-05 00:23:11 +00:00
|
|
|
}
|
2013-10-05 21:15:09 +00:00
|
|
|
|
|
|
|
total += rendered;
|
|
|
|
|
|
|
|
if ( rendered < framesToRender )
|
|
|
|
break;
|
2013-10-05 00:23:11 +00:00
|
|
|
}
|
|
|
|
|
2013-10-05 21:15:09 +00:00
|
|
|
return total;
|
2007-10-12 01:03:12 +00:00
|
|
|
}
|
|
|
|
|
2008-02-10 15:56:18 +00:00
|
|
|
- (long)seek:(long)frame
|
2007-10-12 01:03:12 +00:00
|
|
|
{
|
|
|
|
double pos = (double)duh_sigrenderer_get_position(dsr) / 65.536f;
|
2008-02-10 15:56:18 +00:00
|
|
|
double seekPos = frame/44.100;
|
2007-10-12 01:03:12 +00:00
|
|
|
|
|
|
|
if (seekPos < pos) {
|
|
|
|
//Reset. Dumb cannot seek backwards. It's dumb.
|
|
|
|
[self cleanUp];
|
2007-10-12 01:49:36 +00:00
|
|
|
|
|
|
|
[source seek:0 whence:SEEK_SET];
|
2007-10-12 01:03:12 +00:00
|
|
|
[self open:source];
|
|
|
|
|
|
|
|
pos = 0.0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int numSamples = (seekPos - pos)/1000 * 44100;
|
|
|
|
|
|
|
|
duh_sigrenderer_generate_samples(dsr, 1.0f, 65536.0f / 44100.0f, numSamples, NULL);
|
|
|
|
|
2008-02-10 15:56:18 +00:00
|
|
|
return frame;
|
2007-10-12 01:03:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)cleanUp
|
|
|
|
{
|
2013-10-05 21:15:09 +00:00
|
|
|
if (sampptr) {
|
|
|
|
destroy_sample_buffer(sampptr);
|
|
|
|
sampptr = NULL;
|
|
|
|
}
|
|
|
|
|
2007-10-12 01:03:12 +00:00
|
|
|
if (dsr) {
|
|
|
|
duh_end_sigrenderer(dsr);
|
|
|
|
dsr = NULL;
|
|
|
|
}
|
|
|
|
if (duh) {
|
|
|
|
unload_duh(duh);
|
|
|
|
duh = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)close
|
|
|
|
{
|
|
|
|
[self cleanUp];
|
|
|
|
|
|
|
|
if (source) {
|
|
|
|
[source close];
|
|
|
|
[self setSource:nil];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-19 19:57:18 +00:00
|
|
|
- (void)dealloc
|
|
|
|
{
|
|
|
|
[self close];
|
|
|
|
}
|
|
|
|
|
2007-10-12 01:03:12 +00:00
|
|
|
- (void)setSource:(id<CogSource>)s
|
|
|
|
{
|
|
|
|
source = s;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (id<CogSource>)source
|
|
|
|
{
|
|
|
|
return source;
|
|
|
|
}
|
|
|
|
|
2007-10-14 18:39:58 +00:00
|
|
|
+ (NSArray *)fileTypes
|
|
|
|
{
|
2013-11-01 19:24:59 +00:00
|
|
|
return [NSArray arrayWithObjects:@"it", @"itz", @"xm", @"xmz", @"s3m", @"s3z", @"mod", @"mdz", @"stm", @"stz", @"ptm", @"mtm", @"669", @"psm", @"am", @"j2b", @"dsm", @"amf", @"okt", @"okta", @"umx", @"mo3", nil];
|
2007-10-14 18:39:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
+ (NSArray *)mimeTypes
|
|
|
|
{
|
|
|
|
return [NSArray arrayWithObjects:@"audio/x-it", @"audio/x-xm", @"audio/x-s3m", @"audio/x-mod", nil];
|
|
|
|
}
|
|
|
|
|
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-12 01:03:12 +00:00
|
|
|
@end
|