// // modDecoder.m // modplay // // Created by Christopher Snowhill on 03/17/14. // Copyright 2014 __NoWork, Inc__. All rights reserved. // #import "modDecoder.h" #import "PlaylistController.h" @implementation modDecoder BOOL s3m_probe_length( unsigned long * intro_length, unsigned long * loop_length, const void * src, unsigned long size, unsigned int subsong ) { void * st3play = st3play_Alloc( 44100, 0, 0 ); if ( !st3play ) return NO; if ( !st3play_LoadModule( st3play, src, size ) ) { st3play_Free( st3play ); return NO; } st3play_PlaySong( st3play, subsong ); unsigned long length_total = 0; unsigned long length_saved; const long length_safety = 44100 * 60 * 30; while ( st3play_GetLoopCount( st3play ) < 1 && length_total < length_safety ) { st3play_RenderFloat( st3play, NULL, 512 ); length_total += 512; } if ( st3play_GetLoopCount( st3play ) < 1 ) { *loop_length = 0; *intro_length = 44100 * 60 * 3; st3play_Free( st3play ); return YES; } length_saved = length_total; while ( st3play_GetLoopCount( st3play ) < 2 ) { st3play_RenderFloat( st3play, NULL, 512 ); length_total += 512; } st3play_Free( st3play ); *loop_length = length_total - length_saved; if (length_saved > *loop_length) *intro_length = length_saved - *loop_length; else *intro_length = 0; return YES; } BOOL xm_probe_length( unsigned long * intro_length, unsigned long * loop_length, const void * src, unsigned long size, unsigned int subsong ) { void * ft2play = ft2play_Alloc( 44100, 0, 0 ); if ( !ft2play ) return NO; if ( !ft2play_LoadModule( ft2play, src, size ) ) { ft2play_Free( ft2play ); return NO; } ft2play_PlaySong( ft2play, subsong ); unsigned long length_total = 0; unsigned long length_saved; const long length_safety = 44100 * 60 * 30; while ( ft2play_GetLoopCount( ft2play ) < 1 && length_total < length_safety ) { ft2play_RenderFloat( ft2play, NULL, 512 ); length_total += 512; } if ( ft2play_GetLoopCount( ft2play ) < 1 ) { *loop_length = 0; *intro_length = 44100 * 60 * 3; ft2play_Free( ft2play ); return YES; } length_saved = length_total; while ( ft2play_GetLoopCount( ft2play ) < 2 ) { ft2play_RenderFloat( ft2play, NULL, 512 ); length_total += 512; } ft2play_Free( ft2play ); *loop_length = length_total - length_saved; if (length_saved > *loop_length) *intro_length = length_saved - *loop_length; else *intro_length = 0; return YES; } - (id)init { self = [super init]; if (self) { player = NULL; data = NULL; } return self; } - (BOOL)open:(id)s { [s seek:0 whence:SEEK_END]; size = [s tell]; [s seek:0 whence:SEEK_SET]; data = malloc(size); [s read:data amount:size]; type = TYPE_UNKNOWN; dataWasMo3 = 0; if ( size >= 3 && memcmp( data, "MO3", 3 ) == 0 ) { void * in_data = data; unsigned usize = (unsigned) size; if (UNMO3_Decode(&in_data, &usize, 0) == 0) { free( data ); data = in_data; size = usize; dataWasMo3 = 1; } } else if ( size >= 4 && memcmp( data, "\xC1\x83\x2A\x9E", 4 ) == 0 ) { long out_size = size; void * out_data = unpackUmx( data, &out_size ); if ( out_data ) { free( data ); data = out_data; size = out_size; } } if ( size >= (0x2C + 4) && memcmp( data + 0x2C, "SCRM", 4 ) == 0 ) type = TYPE_S3M; else if ( size >= 17 && memcmp( data, "Extended Module: ", 17 ) == 0 ) type = TYPE_XM; else return NO; if ([[[s url] fragment] length] == 0) track_num = 0; else track_num = [[[s url] fragment] intValue]; unsigned long intro_length = 0, loop_length = 0; if ( type == TYPE_S3M && !s3m_probe_length(&intro_length, &loop_length, data, size, track_num) ) return NO; else if ( type == TYPE_XM && !xm_probe_length(&intro_length, &loop_length, data, size, track_num) ) return NO; framesLength = intro_length + loop_length * 2; totalFrames = framesLength + 44100 * 8; [self willChangeValueForKey:@"properties"]; [self didChangeValueForKey:@"properties"]; return YES; } - (BOOL)decoderInitialize { 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; if ( type == TYPE_S3M ) { player = st3play_Alloc( 44100, resampling_int, 2 ); if ( !player ) return NO; if ( !st3play_LoadModule( player, data, size ) ) return NO; st3play_PlaySong( player, track_num ); } else if ( type == TYPE_XM ) { player = ft2play_Alloc( 44100, resampling_int, 2 ); if ( !player ) return NO; if ( !ft2play_LoadModule( player, data, size ) ) return NO; ft2play_PlaySong( player, track_num ); } framesRead = 0; return YES; } - (void)decoderShutdown { if ( player ) { if ( type == TYPE_S3M ) st3play_Free( player ); else if ( type == TYPE_XM ) ft2play_Free( player ); player = NULL; } } - (NSDictionary *)properties { return [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt:0], @"bitrate", [NSNumber numberWithFloat:44100], @"sampleRate", [NSNumber numberWithDouble:totalFrames], @"totalFrames", [NSNumber numberWithInt:32], @"bitsPerSample", [NSNumber numberWithBool:YES], @"floatingPoint", [NSNumber numberWithInt:2], @"channels", [NSNumber numberWithBool:YES], @"seekable", @"host", @"endian", nil]; } - (int)readAudio:(void *)buf frames:(UInt32)frames { BOOL repeat_one = IsRepeatOneSet(); if ( !repeat_one && framesRead >= totalFrames ) return 0; if ( !player ) { if ( ![self decoderInitialize] ) return 0; } int total = 0; while ( total < frames ) { int framesToRender = 512; if ( !repeat_one && framesToRender > totalFrames - framesRead ) framesToRender = (int)(totalFrames - framesRead); if ( framesToRender > frames - total ) framesToRender = frames - total; float * sampleBuf = ( float * ) buf + total * 2; if ( type == TYPE_S3M ) st3play_RenderFloat( player, sampleBuf, framesToRender ); else if ( type == TYPE_XM ) ft2play_RenderFloat( player, sampleBuf, framesToRender ); if ( !repeat_one && framesRead + framesToRender > framesLength ) { long fadeStart = ( framesLength > framesRead ) ? framesLength : framesRead; long fadeEnd = ( framesRead + framesToRender < totalFrames ) ? framesRead + framesToRender : totalFrames; const long fadeTotal = totalFrames - framesLength; for ( long fadePos = fadeStart; fadePos < fadeEnd; ++fadePos ) { const double scale = (double)( fadeTotal - ( fadePos - framesLength ) ) / (double)fadeTotal; const long offset = fadePos - framesRead; float * samples = sampleBuf + offset * 2; samples[ 0 ] = samples[ 0 ] * scale; samples[ 1 ] = samples[ 1 ] * scale; } framesToRender = (int)(fadeEnd - framesRead); } if ( !framesToRender ) break; total += framesToRender; framesRead += framesToRender; } return total; } - (long)seek:(long)frame { if ( frame < framesRead || !player ) { [self decoderShutdown]; if ( ![self decoderInitialize] ) return 0; } while ( framesRead < frame ) { int frames_todo = INT_MAX; if ( frames_todo > frame - framesRead ) frames_todo = (int)( frame - framesRead ); if ( type == TYPE_S3M ) st3play_RenderFloat( player, NULL, frames_todo ); else if ( type == TYPE_XM ) ft2play_RenderFloat( player, NULL, frames_todo ); framesRead += frames_todo; } framesRead = frame; return frame; } - (void)close { [self decoderShutdown]; if (data) { if ( dataWasMo3 ) UNMO3_Free( data ); else free( data ); data = NULL; } } - (void)dealloc { [self close]; } + (NSArray *)fileTypes { return [NSArray arrayWithObjects:@"s3m", @"s3z", @"xm", @"xmz", @"mo3", @"umx", nil]; } + (NSArray *)mimeTypes { return [NSArray arrayWithObjects:@"audio/x-s3m", @"audio/x-xm", nil]; } + (float)priority { return 1.5; } @end