cog/Plugins/playptmod/playptmod/ptmodDecoder.m

291 lines
7.2 KiB
Objective-C
Executable File

//
// 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<CogSource>)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