2014-02-26 07:50:54 +00:00
|
|
|
//
|
|
|
|
// VGMDecoder.m
|
|
|
|
// vgmstream
|
|
|
|
//
|
|
|
|
// Created by Christopher Snowhill on 02/25/14.
|
|
|
|
// Copyright 2014 __NoWork, Inc__. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
#import "VGMDecoder.h"
|
|
|
|
|
|
|
|
#import "PlaylistController.h"
|
|
|
|
|
|
|
|
typedef struct _COGSTREAMFILE {
|
|
|
|
STREAMFILE sf;
|
2016-06-19 19:57:18 +00:00
|
|
|
void *file;
|
2014-02-26 07:50:54 +00:00
|
|
|
off_t offset;
|
|
|
|
char name[PATH_LIMIT];
|
|
|
|
} COGSTREAMFILE;
|
|
|
|
|
|
|
|
static void cogsf_seek(COGSTREAMFILE *this, off_t offset) {
|
2016-06-19 19:57:18 +00:00
|
|
|
NSObject* _file = (__bridge NSObject *)(this->file);
|
|
|
|
id<CogSource> __unsafe_unretained file = (id) _file;
|
|
|
|
if ([file seek:offset whence:SEEK_SET] != 0)
|
2014-02-26 07:50:54 +00:00
|
|
|
this->offset = offset;
|
|
|
|
else
|
2016-06-19 19:57:18 +00:00
|
|
|
this->offset = [file tell];
|
2014-02-26 07:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static off_t cogsf_get_size(COGSTREAMFILE *this) {
|
2016-06-19 19:57:18 +00:00
|
|
|
NSObject* _file = (__bridge NSObject *)(this->file);
|
|
|
|
id<CogSource> __unsafe_unretained file = (id) _file;
|
|
|
|
off_t offset = [file tell];
|
|
|
|
[file seek:0 whence:SEEK_END];
|
|
|
|
off_t size = [file tell];
|
|
|
|
[file seek:offset whence:SEEK_SET];
|
2014-02-26 07:50:54 +00:00
|
|
|
return size;
|
|
|
|
}
|
|
|
|
|
|
|
|
static off_t cogsf_get_offset(COGSTREAMFILE *this) {
|
|
|
|
return this->offset;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void cogsf_get_name(COGSTREAMFILE *this, char *buffer, size_t length) {
|
|
|
|
strncpy(buffer, this->name, length);
|
|
|
|
buffer[length-1]='\0';
|
|
|
|
}
|
|
|
|
|
|
|
|
static size_t cogsf_read(COGSTREAMFILE *this, uint8_t *dest, off_t offset, size_t length) {
|
2016-06-19 19:57:18 +00:00
|
|
|
NSObject* _file = (__bridge NSObject *)(this->file);
|
|
|
|
id<CogSource> __unsafe_unretained file = (id) _file;
|
2014-02-26 07:50:54 +00:00
|
|
|
size_t read;
|
|
|
|
if (this->offset != offset)
|
|
|
|
cogsf_seek(this, offset);
|
2016-06-19 19:57:18 +00:00
|
|
|
read = [file read:dest amount:length];
|
2014-02-26 07:50:54 +00:00
|
|
|
if (read > 0)
|
|
|
|
this->offset += read;
|
|
|
|
return read;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void cogsf_close(COGSTREAMFILE *this) {
|
2016-06-19 19:57:18 +00:00
|
|
|
CFBridgingRelease(this->file);
|
2014-02-26 07:50:54 +00:00
|
|
|
free(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
static STREAMFILE *cogsf_create_from_path(const char *path);
|
|
|
|
static STREAMFILE *cogsf_open(COGSTREAMFILE *this, const char *const filename,size_t buffersize) {
|
|
|
|
if (!filename) return NULL;
|
|
|
|
return cogsf_create_from_path(filename);
|
|
|
|
}
|
|
|
|
|
|
|
|
static STREAMFILE *cogsf_create(id file, const char *path) {
|
|
|
|
COGSTREAMFILE *streamfile = malloc(sizeof(COGSTREAMFILE));
|
|
|
|
|
|
|
|
if (!streamfile) return NULL;
|
|
|
|
|
|
|
|
memset(streamfile,0,sizeof(COGSTREAMFILE));
|
|
|
|
streamfile->sf.read = (void*)cogsf_read;
|
|
|
|
streamfile->sf.get_size = (void*)cogsf_get_size;
|
|
|
|
streamfile->sf.get_offset = (void*)cogsf_get_offset;
|
|
|
|
streamfile->sf.get_name = (void*)cogsf_get_name;
|
|
|
|
streamfile->sf.get_realname = (void*)cogsf_get_name;
|
|
|
|
streamfile->sf.open = (void*)cogsf_open;
|
|
|
|
streamfile->sf.close = (void*)cogsf_close;
|
2016-06-19 19:57:18 +00:00
|
|
|
streamfile->file = (void*)CFBridgingRetain(file);
|
2014-02-26 07:50:54 +00:00
|
|
|
streamfile->offset = 0;
|
|
|
|
strncpy(streamfile->name, path, sizeof(streamfile->name));
|
|
|
|
|
|
|
|
return &streamfile->sf;
|
|
|
|
}
|
|
|
|
|
|
|
|
STREAMFILE *cogsf_create_from_path(const char *path) {
|
|
|
|
id<CogSource> source;
|
|
|
|
NSString * urlString = [NSString stringWithUTF8String:path];
|
|
|
|
NSURL * url = [NSURL URLWithString:[urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
|
|
|
|
|
|
|
|
id audioSourceClass = NSClassFromString(@"AudioSource");
|
|
|
|
source = [audioSourceClass audioSourceForURL:url];
|
|
|
|
|
|
|
|
if (![source open:url])
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (![source seekable])
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
return cogsf_create(source, path);
|
|
|
|
}
|
|
|
|
|
|
|
|
VGMSTREAM *init_vgmstream_from_cogfile(const char *path) {
|
|
|
|
STREAMFILE *sf;
|
2017-07-09 22:00:59 +00:00
|
|
|
VGMSTREAM *vgm = NULL;
|
2014-02-26 07:50:54 +00:00
|
|
|
|
|
|
|
sf = cogsf_create_from_path(path);
|
|
|
|
|
2016-06-30 22:40:43 +00:00
|
|
|
if (sf) {
|
|
|
|
vgm = init_vgmstream_from_STREAMFILE(sf);
|
|
|
|
cogsf_close((COGSTREAMFILE *)sf);
|
|
|
|
}
|
2014-02-26 07:50:54 +00:00
|
|
|
|
|
|
|
return vgm;
|
|
|
|
}
|
|
|
|
|
|
|
|
@implementation VGMDecoder
|
|
|
|
|
|
|
|
- (BOOL)open:(id<CogSource>)s
|
|
|
|
{
|
|
|
|
stream = init_vgmstream_from_cogfile([[[[s url] absoluteString] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding] UTF8String]);
|
|
|
|
if ( !stream )
|
|
|
|
return NO;
|
|
|
|
|
|
|
|
sampleRate = stream->sample_rate;
|
|
|
|
channels = stream->channels;
|
|
|
|
totalFrames = get_vgmstream_play_samples( 2.0, 10.0, 10.0, stream );
|
2016-07-17 06:03:44 +00:00
|
|
|
framesFade = stream->loop_flag ? sampleRate * 10 : 0;
|
2014-02-26 07:50:54 +00:00
|
|
|
framesLength = totalFrames - framesFade;
|
|
|
|
|
|
|
|
framesRead = 0;
|
|
|
|
|
2015-02-09 03:20:24 +00:00
|
|
|
bitrate = get_vgmstream_average_bitrate(stream);
|
|
|
|
|
2014-02-26 07:50:54 +00:00
|
|
|
[self willChangeValueForKey:@"properties"];
|
|
|
|
[self didChangeValueForKey:@"properties"];
|
|
|
|
|
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSDictionary *)properties
|
|
|
|
{
|
|
|
|
return [NSDictionary dictionaryWithObjectsAndKeys:
|
2015-02-09 03:20:24 +00:00
|
|
|
[NSNumber numberWithInt:bitrate / 1000], @"bitrate",
|
2014-02-26 07:50:54 +00:00
|
|
|
[NSNumber numberWithInt:sampleRate], @"sampleRate",
|
|
|
|
[NSNumber numberWithDouble:totalFrames], @"totalFrames",
|
|
|
|
[NSNumber numberWithInt:16], @"bitsPerSample",
|
|
|
|
[NSNumber numberWithBool:NO], @"floatingPoint",
|
|
|
|
[NSNumber numberWithInt:channels], @"channels",
|
|
|
|
[NSNumber numberWithBool:YES], @"seekable",
|
|
|
|
@"host", @"endian",
|
|
|
|
nil];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (int)readAudio:(void *)buf frames:(UInt32)frames
|
|
|
|
{
|
|
|
|
BOOL repeatone = IsRepeatOneSet();
|
|
|
|
|
2016-07-17 06:03:44 +00:00
|
|
|
if (!repeatone) {
|
|
|
|
if (framesRead >= totalFrames) return 0;
|
|
|
|
else if (framesRead + frames > totalFrames)
|
|
|
|
frames = totalFrames - framesRead;
|
|
|
|
}
|
2014-02-26 07:50:54 +00:00
|
|
|
|
|
|
|
sample * sbuf = (sample *) buf;
|
|
|
|
|
|
|
|
render_vgmstream( sbuf, frames, stream );
|
|
|
|
|
2016-07-17 06:03:44 +00:00
|
|
|
if ( !repeatone && framesFade && framesRead + frames > framesLength ) {
|
2014-02-26 07:50:54 +00:00
|
|
|
long fadeStart = (framesLength > framesRead) ? framesLength : framesRead;
|
|
|
|
long fadeEnd = (framesRead + frames) > totalFrames ? totalFrames : (framesRead + frames);
|
|
|
|
long fadePos;
|
|
|
|
|
2014-09-29 01:11:44 +00:00
|
|
|
int64_t fadeScale = (int64_t)(totalFrames - fadeStart) * INT_MAX / framesFade;
|
|
|
|
int64_t fadeStep = INT_MAX / framesFade;
|
|
|
|
sbuf += (fadeStart - framesRead) * 2;
|
2014-02-26 07:50:54 +00:00
|
|
|
for (fadePos = fadeStart; fadePos < fadeEnd; ++fadePos) {
|
2014-09-29 01:11:44 +00:00
|
|
|
sbuf[ 0 ] = (int16_t)((int64_t)(sbuf[ 0 ]) * fadeScale / INT_MAX);
|
|
|
|
sbuf[ 1 ] = (int16_t)((int64_t)(sbuf[ 1 ]) * fadeScale / INT_MAX);
|
2014-02-26 07:50:54 +00:00
|
|
|
sbuf += 2;
|
|
|
|
fadeScale -= fadeStep;
|
|
|
|
if (fadeScale <= 0) break;
|
|
|
|
}
|
2014-09-30 23:57:14 +00:00
|
|
|
frames = (UInt32)(fadePos - framesRead);
|
2014-02-26 07:50:54 +00:00
|
|
|
}
|
|
|
|
|
2014-09-30 23:57:14 +00:00
|
|
|
framesRead += frames;
|
2014-02-26 07:50:54 +00:00
|
|
|
|
|
|
|
return frames;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (long)seek:(long)frame
|
|
|
|
{
|
2016-06-30 22:40:43 +00:00
|
|
|
// Constrain the seek offset to within the loop, if any
|
|
|
|
if(stream->loop_flag && (stream->loop_end_sample - stream->loop_start_sample) && frame >= stream->loop_end_sample) {
|
|
|
|
frame -= stream->loop_start_sample;
|
|
|
|
frame %= (stream->loop_end_sample - stream->loop_start_sample);
|
|
|
|
frame += stream->loop_start_sample;
|
|
|
|
}
|
|
|
|
|
2014-02-26 07:50:54 +00:00
|
|
|
if (frame < framesRead) {
|
|
|
|
reset_vgmstream( stream );
|
|
|
|
framesRead = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (framesRead < frame) {
|
|
|
|
sample buffer[1024];
|
|
|
|
long max_sample_count = 1024 / channels;
|
|
|
|
long samples_to_skip = frame - framesRead;
|
|
|
|
if ( samples_to_skip > max_sample_count )
|
|
|
|
samples_to_skip = max_sample_count;
|
|
|
|
render_vgmstream( buffer, (int)samples_to_skip, stream );
|
|
|
|
framesRead += samples_to_skip;
|
|
|
|
}
|
|
|
|
|
|
|
|
return framesRead;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)close
|
|
|
|
{
|
|
|
|
close_vgmstream( stream );
|
2016-06-19 19:57:18 +00:00
|
|
|
stream = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)dealloc
|
|
|
|
{
|
|
|
|
[self close];
|
2014-02-26 07:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
+ (NSArray *)fileTypes
|
2017-07-09 22:00:59 +00:00
|
|
|
{
|
|
|
|
NSMutableArray *array = [[NSMutableArray alloc] init];
|
|
|
|
|
|
|
|
int count = vgmstream_get_formats_length();
|
|
|
|
const char ** formats = vgmstream_get_formats();
|
|
|
|
|
|
|
|
for (int i = 0; i < count; ++i)
|
|
|
|
{
|
|
|
|
[array addObject:[NSString stringWithUTF8String:formats[i]]];
|
|
|
|
}
|
|
|
|
|
|
|
|
return array;
|
2014-02-26 07:50:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
+ (NSArray *)mimeTypes
|
|
|
|
{
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
+ (float)priority
|
|
|
|
{
|
|
|
|
return 0.0;
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|