// // ptmodDecoder.m // playptmod // // Created by Christopher Snowhill on 10/22/13. // Copyright 2013 __NoWork, Inc__. All rights reserved. // #import "ptmodDecoder.h" #import "umx.h" #import "mo3.h" #import "Logging.h" #import "PlaylistController.h" @implementation ptmodDecoder BOOL probe_length( void * ptmod, unsigned long * intro_length, unsigned long * loop_length, int test_vblank, const void * src, unsigned long size, unsigned int subsong ) { playptmod_Config( ptmod, PTMOD_OPTION_VSYNC_TIMING, test_vblank ); playptmod_Play( ptmod, subsong ); unsigned long length_total = 0; unsigned long length_saved; const long length_safety = 44100 * 60 * 30; while ( playptmod_LoopCounter( ptmod ) < 1 && length_total < length_safety ) { playptmod_Render( ptmod, NULL, 512 ); length_total += 512; } if ( playptmod_LoopCounter( ptmod ) < 1 ) { *loop_length = 0; *intro_length = 44100 * 60 * 3; playptmod_Stop(ptmod); return YES; } length_saved = length_total; while ( playptmod_LoopCounter( ptmod ) < 2 ) { playptmod_Render( ptmod, NULL, 512 ); length_total += 512; } playptmod_Stop(ptmod); *loop_length = length_total - length_saved; *intro_length = length_saved - *loop_length; return YES; } - (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]; isMo3 = 0; char * try_data = unpackMo3( data, &size ); if ( try_data ) { free( data ); data = try_data; isMo3 = 1; } else { try_data = unpackUmx( data, &size ); if ( try_data ) { free( data ); data = try_data; } } if ([[[s url] fragment] length] == 0) track_num = 0; else track_num = [[[s url] fragment] intValue]; void * mod = playptmod_Create( 44100 ); if ( !mod ) return NO; if ( !playptmod_LoadMem(mod, data, size) ) { playptmod_Free(mod); return NO; } int format = playptmod_GetFormat(mod); BOOL can_be_vblank = (format <= FORMAT_MK2); unsigned long normal_intro_length, normal_loop_length, vblank_intro_length, vblank_loop_length; if ( !probe_length(mod, &normal_intro_length, &normal_loop_length, 0, data, size, track_num) ) return NO; if ( can_be_vblank ) { if ( !probe_length(mod, &vblank_intro_length, &vblank_loop_length, 1, data, size, track_num) ) return NO; if (vblank_loop_length == 0) can_be_vblank = NO; } else { vblank_intro_length = 0; vblank_loop_length = 0; } playptmod_Free(mod); isVblank = can_be_vblank && (( vblank_intro_length + vblank_loop_length ) < ( normal_intro_length + normal_loop_length )); unsigned long intro_length = isVblank ? vblank_intro_length : normal_intro_length; unsigned long loop_length = isVblank ? vblank_loop_length : normal_loop_length; framesLength = intro_length + loop_length * 2; totalFrames = framesLength + 44100 * 8; [self willChangeValueForKey:@"properties"]; [self didChangeValueForKey:@"properties"]; return YES; } - (BOOL)decoderInitialize { ptmod = playptmod_Create( 44100 ); if ( !ptmod ) return NO; playptmod_Config( ptmod, PTMOD_OPTION_CLAMP_PERIODS, 0 ); playptmod_Config( ptmod, PTMOD_OPTION_VSYNC_TIMING, isVblank ); if ( !playptmod_LoadMem( ptmod, data, size ) ) return NO; playptmod_Play( ptmod, track_num ); framesRead = 0; return YES; } - (void)decoderShutdown { if ( ptmod ) { playptmod_Stop( ptmod ); playptmod_Free( ptmod ); ptmod = 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 ( !ptmod ) { 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; int32_t * sampleBuf = ( int32_t * ) buf + total * 2; playptmod_Render( ptmod, 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 long scale = ( fadeTotal - ( fadePos - framesLength ) ); const long offset = fadePos - framesRead; int32_t * samples = sampleBuf + offset * 2; samples[ 0 ] = (int32_t)(samples[ 0 ] * scale / fadeTotal); samples[ 1 ] = (int32_t)(samples[ 1 ] * scale / fadeTotal); } framesToRender = (int)(fadeEnd - framesRead); } if ( !framesToRender ) break; total += framesToRender; framesRead += framesToRender; } for ( int i = 0; i < total; ++i ) { int32_t * sampleIn = ( int32_t * ) buf + i * 2; float * sampleOut = ( float * ) buf + i * 2; sampleOut[ 0 ] = sampleIn[ 0 ] * (1.0f / 16777216.0f); sampleOut[ 1 ] = sampleIn[ 1 ] * (1.0f / 16777216.0f); } return total; } - (long)seek:(long)frame { if ( frame < framesRead || !ptmod ) { [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 ); playptmod_Render( ptmod, NULL, frames_todo ); framesRead += frames_todo; } framesRead = frame; return frame; } - (void)close { [self decoderShutdown]; if (data) { if (isMo3) freeMo3( data ); else free( data ); data = NULL; } } - (void)dealloc { [self close]; } + (NSArray *)fileTypes { return [NSArray arrayWithObjects:@"mod", @"mdz", @"stk", @"m15", @"fst", @"mo3", @"umx", nil]; } + (NSArray *)mimeTypes { return [NSArray arrayWithObjects:@"audio/x-mod", nil]; } + (float)priority { return 1.5; } @end