// // ptmodDecoder.m // playptmod // // Created by Christopher Snowhill on 10/22/13. // Copyright 2013 __NoWork, Inc__. All rights reserved. // #import "ptmodDecoder.h" #import "mo3.h" #import "umx.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