357 lines
7.8 KiB
Objective-C
357 lines
7.8 KiB
Objective-C
//
|
|
// DumbFile.m
|
|
// Cog
|
|
//
|
|
// Created by Vincent Spader on 5/29/06.
|
|
// Copyright 2006 Vincent Spader. All rights reserved.
|
|
//
|
|
|
|
#define _USE_SSE 1
|
|
|
|
#import "DumbDecoder.h"
|
|
|
|
#import "j2b.h"
|
|
#import "mo3.h"
|
|
#import "umx.h"
|
|
|
|
#import "Logging.h"
|
|
|
|
#import "PlaylistController.h"
|
|
|
|
@implementation DumbDecoder
|
|
|
|
struct MEMANDFREEFILE {
|
|
char *ptr, *ptr_begin;
|
|
long left, size;
|
|
int is_mo3;
|
|
};
|
|
|
|
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;
|
|
}
|
|
|
|
static int dumb_maffile_getc(void *f) {
|
|
struct MEMANDFREEFILE *m = f;
|
|
if(m->left <= 0) return -1;
|
|
m->left--;
|
|
return *(const unsigned char *)m->ptr++;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
static void dumb_maffile_close(void *f) {
|
|
struct MEMANDFREEFILE *m = f;
|
|
if(m->is_mo3)
|
|
freeMo3(m->ptr_begin);
|
|
else
|
|
free(m->ptr_begin);
|
|
free(f);
|
|
}
|
|
|
|
static int dumb_maffile_seek(void *f, long n) {
|
|
struct MEMANDFREEFILE *m = f;
|
|
|
|
m->ptr = m->ptr_begin + n;
|
|
m->left = m->size - n;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static long dumb_maffile_get_size(void *f) {
|
|
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) {
|
|
int is_mo3 = 0;
|
|
char *try_data = unpackMo3(data, &size);
|
|
if(try_data) {
|
|
free(data);
|
|
data = try_data;
|
|
is_mo3 = 1;
|
|
} else {
|
|
try_data = unpackUmx(data, &size);
|
|
if(try_data) {
|
|
free(data);
|
|
data = try_data;
|
|
} else {
|
|
try_data = unpackJ2b(data, &size);
|
|
if(try_data) {
|
|
free(data);
|
|
data = try_data;
|
|
}
|
|
}
|
|
}
|
|
|
|
struct MEMANDFREEFILE *m = malloc(sizeof(*m));
|
|
if(!m) return NULL;
|
|
|
|
m->ptr_begin = data;
|
|
m->ptr = data;
|
|
m->left = size;
|
|
m->size = size;
|
|
m->is_mo3 = is_mo3;
|
|
|
|
return dumbfile_open_ex(m, &maffile_dfs);
|
|
}
|
|
|
|
+ (void)initialize {
|
|
if(self == [DumbDecoder class]) {
|
|
// do this here so we don't have to wait on it later
|
|
_dumb_init_cubic();
|
|
_dumb_init_sse();
|
|
}
|
|
}
|
|
|
|
- (id)init {
|
|
self = [super init];
|
|
if(self) {
|
|
sampptr = NULL;
|
|
dsr = NULL;
|
|
duh = NULL;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
int callbackLoop(void *data) {
|
|
long *loops = (long *)data;
|
|
++*loops;
|
|
return 0;
|
|
}
|
|
|
|
- (BOOL)open:(id<CogSource>)s {
|
|
[self setSource:s];
|
|
|
|
[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);
|
|
if(!df) {
|
|
ALog(@"Open failed for file: %@", [[s url] absoluteString]);
|
|
return NO;
|
|
}
|
|
|
|
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);
|
|
|
|
NSString *ext = [[[s url] pathExtension] lowercaseString];
|
|
duh = dumb_read_any(df, [ext isEqualToString:@"mod"] ? 0 : 1, subsong);
|
|
if(!duh) {
|
|
ALog(@"Failed to create duh");
|
|
dumbfile_close(df);
|
|
return NO;
|
|
}
|
|
dumbfile_close(df);
|
|
|
|
length = dumb_it_build_checkpoints(duh_get_it_sigdata(duh), startOrder);
|
|
|
|
dsr = duh_start_sigrenderer(duh, 0, 2 /* stereo */, startOrder);
|
|
if(!dsr) {
|
|
ALog(@"Failed to create dsr");
|
|
return NO;
|
|
}
|
|
|
|
DUMB_IT_SIGRENDERER *itsr = duh_get_it_sigrenderer(dsr);
|
|
|
|
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;
|
|
else if([resampling isEqualToString:@"blam"])
|
|
resampling_int = 3;
|
|
else if([resampling isEqualToString:@"cubic"])
|
|
resampling_int = 4;
|
|
else if([resampling isEqualToString:@"sinc"])
|
|
resampling_int = 5;
|
|
|
|
dumb_it_set_resampling_quality(itsr, resampling_int);
|
|
dumb_it_set_ramp_style(itsr, 2);
|
|
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;
|
|
|
|
sampptr = allocate_sample_buffer(2, 1024);
|
|
|
|
[self willChangeValueForKey:@"properties"];
|
|
[self didChangeValueForKey:@"properties"];
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (NSDictionary *)properties {
|
|
return [NSDictionary dictionaryWithObjectsAndKeys:
|
|
[NSNumber numberWithInt:0], @"bitrate",
|
|
[NSNumber numberWithFloat:44100], @"sampleRate",
|
|
[NSNumber numberWithDouble:((length / 65.536f) * 44.1000)], @"totalFrames",
|
|
[NSNumber numberWithInt:32], @"bitsPerSample", // Samples are short
|
|
[NSNumber numberWithBool:YES], @"floatingPoint",
|
|
[NSNumber numberWithInt:2], @"channels", // output from gme_play is in stereo
|
|
[NSNumber numberWithBool:[source seekable]], @"seekable",
|
|
@"host", @"endian",
|
|
nil];
|
|
}
|
|
|
|
- (int)readAudio:(void *)buf frames:(UInt32)frames {
|
|
int total = 0;
|
|
while(total < frames) {
|
|
int framesToRender = 1024;
|
|
if(framesToRender > frames)
|
|
framesToRender = frames;
|
|
dumb_silence(sampptr[0], framesToRender * 2);
|
|
int rendered = (int)duh_sigrenderer_generate_samples(dsr, 1.0, 65536.0f / 44100.0f, framesToRender, sampptr);
|
|
|
|
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;
|
|
}
|
|
|
|
if(!IsRepeatOneSet() && loops >= 2) {
|
|
float *sampleBuf = (float *)buf + total * 2;
|
|
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;
|
|
}
|
|
rendered = (int)(fadeRemain - fadeEnd);
|
|
fadeRemain = fadeEnd;
|
|
}
|
|
|
|
total += rendered;
|
|
|
|
if(rendered < framesToRender)
|
|
break;
|
|
}
|
|
|
|
return total;
|
|
}
|
|
|
|
- (long)seek:(long)frame {
|
|
double pos = (double)duh_sigrenderer_get_position(dsr) / 65.536f;
|
|
double seekPos = frame / 44.100;
|
|
|
|
if(seekPos < pos) {
|
|
// Reset. Dumb cannot seek backwards. It's dumb.
|
|
[self cleanUp];
|
|
|
|
[source seek:0 whence:SEEK_SET];
|
|
[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);
|
|
|
|
return frame;
|
|
}
|
|
|
|
- (void)cleanUp {
|
|
if(sampptr) {
|
|
destroy_sample_buffer(sampptr);
|
|
sampptr = NULL;
|
|
}
|
|
|
|
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];
|
|
}
|
|
}
|
|
|
|
- (void)dealloc {
|
|
[self close];
|
|
}
|
|
|
|
- (void)setSource:(id<CogSource>)s {
|
|
source = s;
|
|
}
|
|
|
|
- (id<CogSource>)source {
|
|
return source;
|
|
}
|
|
|
|
+ (NSArray *)fileTypes {
|
|
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];
|
|
}
|
|
|
|
+ (NSArray *)mimeTypes {
|
|
return [NSArray arrayWithObjects:@"audio/x-it", @"audio/x-xm", @"audio/x-s3m", @"audio/x-mod", nil];
|
|
}
|
|
|
|
+ (float)priority {
|
|
return 1.0;
|
|
}
|
|
|
|
@end
|