cog/Plugins/BASSMODS/BASSMODS/BASSDecoder.mm

269 lines
6.1 KiB
Plaintext
Executable File

//
// BASSDecoder.m
// Cog
//
// Created by Christopher Snowhill on 7/03/14.
// Copyright 2014 __NoWork, Inc__. All rights reserved.
//
#include <mutex>
#import "BASSDecoder.h"
#import "umx.h"
#import "Logging.h"
#import "PlaylistController.h"
static class Bass_Initializer
{
std::mutex lock;
bool initialized;
public:
Bass_Initializer() : initialized(false)
{
}
~Bass_Initializer()
{
if ( initialized )
{
BASS_Free();
}
}
bool check_initialized()
{
std::lock_guard<std::mutex> lock( this->lock );
return initialized;
}
bool initialize()
{
std::lock_guard<std::mutex> lock( this->lock );
if ( !initialized )
{
BASS_SetConfig( BASS_CONFIG_UPDATEPERIOD, 0 );
initialized = !!BASS_Init( 0, 44100, 0, NULL, NULL );
}
return initialized;
}
} g_initializer;
static void SyncProc( HSYNC handle, DWORD channel, DWORD data, void *user )
{
BASSDecoder * decoder = (__bridge BASSDecoder *) user;
[decoder sync];
}
@implementation BASSDecoder
+ (void)initialize
{
if (self == [BASSDecoder class])
{
g_initializer.initialize();
}
}
- (void)sync
{
++loops;
}
- (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];
void * try_data = unpackUmx( data, &size );
if ( try_data ) {
free( data );
data = try_data;
}
NSURL * url = [s url];
int track_num;
if ([[url fragment] length] == 0)
track_num = 0;
else
track_num = [[url fragment] intValue];
int resampling_int = -1;
NSString * resampling = [[NSUserDefaults standardUserDefaults] stringForKey:@"resampling"];
if ([resampling isEqualToString:@"zoh"])
resampling_int = 0;
else if ([resampling isEqualToString:@"blep"])
resampling_int = 0;
else if ([resampling isEqualToString:@"linear"])
resampling_int = 1;
else if ([resampling isEqualToString:@"blam"])
resampling_int = 1;
else if ([resampling isEqualToString:@"cubic"])
resampling_int = 1;
else if ([resampling isEqualToString:@"sinc"])
resampling_int = 2;
music = BASS_MusicLoad( 1, data, 0, (DWORD) size, BASS_SAMPLE_FLOAT | BASS_SAMPLE_LOOP | BASS_MUSIC_STOPBACK | BASS_MUSIC_RAMP | BASS_MUSIC_PRESCAN | BASS_MUSIC_DECODE | (resampling_int == 0 ? BASS_MUSIC_NONINTER : ((resampling_int == 2) ? BASS_MUSIC_SINCINTER : 0)), 44100 );
if ( !music )
{
ALog(@"BASS MusicLoad error: %u", BASS_ErrorGetCode());
free(data);
return NO;
}
BASS_ChannelSetPosition( music, MAKELONG(track_num, 0), BASS_POS_MUSIC_ORDER );
length = BASS_ChannelGetLength( music, BASS_POS_BYTE ) / (sizeof(float) * 2);
BASS_ChannelSetSync( music, BASS_SYNC_END, 0, SyncProc, (__bridge void*) self );
loops = 0;
fadeTotal = fadeRemain = 44100 * 8;
[self willChangeValueForKey:@"properties"];
[self didChangeValueForKey:@"properties"];
return YES;
}
- (NSDictionary *)properties
{
return [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:0], @"bitrate",
[NSNumber numberWithFloat:44100], @"sampleRate",
[NSNumber numberWithDouble:length], @"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 ) {
float * fbuf = (float*)buf + total * 2;
int framesToRender = 1024;
if ( framesToRender > frames )
framesToRender = frames;
int rendered = BASS_ChannelGetData( music, fbuf, (framesToRender * 2 * sizeof(float)) | BASS_DATA_FLOAT );
if (rendered < 0) {
ALog(@"BASS ChannelGetData error: %u", BASS_ErrorGetCode());
return 0;
}
rendered /= sizeof(float) * 2;
if (rendered <= 0)
break;
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
{
long pos = BASS_ChannelGetPosition(music, BASS_POS_BYTE) / (sizeof(float) * 2);
if (frame < pos) {
//Reset. Dumb cannot seek backwards. It's dumb.
[self cleanUp];
[source seek:0 whence:SEEK_SET];
[self open:source];
pos = 0;
}
BASS_ChannelSetPosition( music, frame * (sizeof(float) * 2), BASS_POS_BYTE | BASS_POS_DECODETO );
return frame;
}
- (void)cleanUp
{
if (music) {
BASS_MusicFree( music );
music = 0;
}
}
- (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", @"umx", @"mo3", nil];
}
+ (NSArray *)mimeTypes
{
return [NSArray arrayWithObjects:@"audio/x-it", nil];
}
+ (float)priority
{
return 1.5;
}
@end