2014-02-26 04:50:54 -03:00
|
|
|
//
|
|
|
|
// VGMDecoder.m
|
|
|
|
// vgmstream
|
|
|
|
//
|
|
|
|
// Created by Christopher Snowhill on 02/25/14.
|
|
|
|
// Copyright 2014 __NoWork, Inc__. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
#import "VGMDecoder.h"
|
2017-09-17 01:24:57 -03:00
|
|
|
#import "VGMInterface.h"
|
2014-02-26 04:50:54 -03:00
|
|
|
|
|
|
|
#import "PlaylistController.h"
|
|
|
|
|
2019-10-19 00:21:27 -03:00
|
|
|
@implementation VGMInfoCache
|
|
|
|
|
|
|
|
+(id)sharedCache {
|
|
|
|
static VGMInfoCache *sharedMyCache = nil;
|
|
|
|
static dispatch_once_t onceToken;
|
|
|
|
dispatch_once(&onceToken, ^{
|
|
|
|
sharedMyCache = [[self alloc] init];
|
|
|
|
});
|
|
|
|
return sharedMyCache;
|
|
|
|
}
|
|
|
|
|
|
|
|
-(id)init {
|
|
|
|
if (self = [super init]) {
|
|
|
|
storage = [[NSMutableDictionary alloc] init];
|
|
|
|
}
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
|
|
|
-(void)stuffURL:(NSURL *)url stream:(VGMSTREAM *)stream {
|
|
|
|
int track_num = [[url fragment] intValue];
|
|
|
|
|
|
|
|
int sampleRate = stream->sample_rate;
|
|
|
|
int channels = stream->channels;
|
|
|
|
long totalFrames = get_vgmstream_play_samples( 2.0, 10.0, 10.0, stream );
|
|
|
|
long framesFade = stream->loop_flag ? sampleRate * 10 : 0;
|
|
|
|
long framesLength = totalFrames - framesFade;
|
|
|
|
|
|
|
|
int bitrate = get_vgmstream_average_bitrate(stream);
|
|
|
|
|
|
|
|
NSDictionary * properties =
|
|
|
|
[NSDictionary dictionaryWithObjectsAndKeys:
|
|
|
|
[NSNumber numberWithInt:bitrate / 1000], @"bitrate",
|
|
|
|
[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];
|
|
|
|
|
|
|
|
NSString * title;
|
|
|
|
|
|
|
|
if ( stream->num_streams > 1 ) {
|
|
|
|
title = [NSString stringWithFormat:@"%@ - %s", [[url URLByDeletingPathExtension] lastPathComponent], stream->stream_name];
|
|
|
|
} else {
|
|
|
|
title = [[url URLByDeletingPathExtension] lastPathComponent];
|
|
|
|
}
|
|
|
|
|
|
|
|
NSDictionary * metadata =
|
|
|
|
[NSDictionary dictionaryWithObjectsAndKeys:
|
|
|
|
title, @"title",
|
|
|
|
[NSNumber numberWithInt:track_num], @"track",
|
|
|
|
nil];
|
|
|
|
|
|
|
|
NSDictionary * package =
|
|
|
|
[NSDictionary dictionaryWithObjectsAndKeys:
|
|
|
|
properties, @"properties",
|
|
|
|
metadata, @"metadata",
|
|
|
|
nil];
|
|
|
|
|
|
|
|
@synchronized (self) {
|
|
|
|
[storage setValue:package forKey:[url absoluteString]];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
-(NSDictionary*)getPropertiesForURL:(NSURL *)url {
|
|
|
|
NSDictionary *properties = nil;
|
|
|
|
|
|
|
|
@synchronized (self) {
|
|
|
|
NSDictionary * package = [storage objectForKey:[url absoluteString]];
|
|
|
|
if (package) {
|
|
|
|
properties = [package objectForKey:@"properties"];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return properties;
|
|
|
|
}
|
|
|
|
|
|
|
|
-(NSDictionary*)getMetadataForURL:(NSURL *)url {
|
|
|
|
NSDictionary *metadata = nil;
|
|
|
|
|
|
|
|
@synchronized (self) {
|
|
|
|
NSDictionary * package = [storage objectForKey:[url absoluteString]];
|
|
|
|
if (package) {
|
|
|
|
metadata = [package objectForKey:@"metadata"];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return metadata;
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
2014-02-26 04:50:54 -03:00
|
|
|
@implementation VGMDecoder
|
|
|
|
|
|
|
|
- (BOOL)open:(id<CogSource>)s
|
|
|
|
{
|
2017-09-17 01:24:57 -03:00
|
|
|
int track_num = [[[s url] fragment] intValue];
|
2017-09-17 23:21:38 -03:00
|
|
|
|
|
|
|
NSString * path = [[s url] absoluteString];
|
|
|
|
NSRange fragmentRange = [path rangeOfString:@"#" options:NSBackwardsSearch];
|
|
|
|
if (fragmentRange.location != NSNotFound) {
|
|
|
|
path = [path substringToIndex:fragmentRange.location];
|
|
|
|
}
|
2019-10-19 00:21:27 -03:00
|
|
|
|
|
|
|
NSLog(@"Opening %@ subsong %d", path, track_num);
|
2017-09-17 01:24:57 -03:00
|
|
|
|
2017-09-17 23:21:38 -03:00
|
|
|
stream = init_vgmstream_from_cogfile([[path stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding] UTF8String], track_num);
|
2014-02-26 04:50:54 -03:00
|
|
|
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 02:03:44 -04:00
|
|
|
framesFade = stream->loop_flag ? sampleRate * 10 : 0;
|
2014-02-26 04:50:54 -03:00
|
|
|
framesLength = totalFrames - framesFade;
|
|
|
|
|
|
|
|
framesRead = 0;
|
|
|
|
|
2015-02-09 00:20:24 -03:00
|
|
|
bitrate = get_vgmstream_average_bitrate(stream);
|
2017-09-17 22:41:04 -03:00
|
|
|
|
2014-02-26 04:50:54 -03:00
|
|
|
[self willChangeValueForKey:@"properties"];
|
2017-09-17 22:41:04 -03:00
|
|
|
[self didChangeValueForKey:@"properties"];
|
2014-02-26 04:50:54 -03:00
|
|
|
|
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSDictionary *)properties
|
|
|
|
{
|
2019-10-19 00:21:27 -03:00
|
|
|
return [NSDictionary dictionaryWithObjectsAndKeys:
|
|
|
|
[NSNumber numberWithInt:bitrate / 1000], @"bitrate",
|
|
|
|
[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];
|
2014-02-26 04:50:54 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
- (int)readAudio:(void *)buf frames:(UInt32)frames
|
|
|
|
{
|
|
|
|
BOOL repeatone = IsRepeatOneSet();
|
|
|
|
|
2018-07-17 21:09:59 -04:00
|
|
|
BOOL loopokay = repeatone && stream->loop_flag;
|
|
|
|
|
|
|
|
if (!loopokay) {
|
2016-07-17 02:03:44 -04:00
|
|
|
if (framesRead >= totalFrames) return 0;
|
|
|
|
else if (framesRead + frames > totalFrames)
|
2017-09-17 23:21:38 -03:00
|
|
|
frames = (UInt32)(totalFrames - framesRead);
|
2016-07-17 02:03:44 -04:00
|
|
|
}
|
2014-02-26 04:50:54 -03:00
|
|
|
|
|
|
|
sample * sbuf = (sample *) buf;
|
|
|
|
|
|
|
|
render_vgmstream( sbuf, frames, stream );
|
|
|
|
|
2016-07-17 02:03:44 -04:00
|
|
|
if ( !repeatone && framesFade && framesRead + frames > framesLength ) {
|
2014-02-26 04:50:54 -03:00
|
|
|
long fadeStart = (framesLength > framesRead) ? framesLength : framesRead;
|
|
|
|
long fadeEnd = (framesRead + frames) > totalFrames ? totalFrames : (framesRead + frames);
|
|
|
|
long fadePos;
|
2018-07-18 02:34:25 -04:00
|
|
|
long i;
|
2014-02-26 04:50:54 -03:00
|
|
|
|
2014-09-28 22:11:44 -03:00
|
|
|
int64_t fadeScale = (int64_t)(totalFrames - fadeStart) * INT_MAX / framesFade;
|
|
|
|
int64_t fadeStep = INT_MAX / framesFade;
|
2018-07-18 02:34:25 -04:00
|
|
|
sbuf += (fadeStart - framesRead) * channels;
|
2014-02-26 04:50:54 -03:00
|
|
|
for (fadePos = fadeStart; fadePos < fadeEnd; ++fadePos) {
|
2018-07-18 02:34:25 -04:00
|
|
|
for (i = 0; i < channels; ++i) {
|
|
|
|
sbuf[ i ] = (int16_t)((int64_t)(sbuf[ i ]) * fadeScale / INT_MAX);
|
|
|
|
}
|
|
|
|
sbuf += channels;
|
2014-02-26 04:50:54 -03:00
|
|
|
fadeScale -= fadeStep;
|
|
|
|
if (fadeScale <= 0) break;
|
|
|
|
}
|
2014-09-30 20:57:14 -03:00
|
|
|
frames = (UInt32)(fadePos - framesRead);
|
2014-02-26 04:50:54 -03:00
|
|
|
}
|
|
|
|
|
2014-09-30 20:57:14 -03:00
|
|
|
framesRead += frames;
|
2014-02-26 04:50:54 -03:00
|
|
|
|
|
|
|
return frames;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (long)seek:(long)frame
|
|
|
|
{
|
2016-06-30 18:40:43 -04: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 04:50:54 -03: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 15:57:18 -04:00
|
|
|
stream = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)dealloc
|
|
|
|
{
|
|
|
|
[self close];
|
2014-02-26 04:50:54 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
+ (NSArray *)fileTypes
|
2017-07-09 18:00:59 -04:00
|
|
|
{
|
|
|
|
NSMutableArray *array = [[NSMutableArray alloc] init];
|
|
|
|
|
2017-12-17 01:17:41 -03:00
|
|
|
size_t count;
|
|
|
|
const char ** formats = vgmstream_get_formats(&count);
|
2017-07-09 18:00:59 -04:00
|
|
|
|
2017-12-17 01:17:41 -03:00
|
|
|
for (size_t i = 0; i < count; ++i)
|
2017-07-09 18:00:59 -04:00
|
|
|
{
|
|
|
|
[array addObject:[NSString stringWithUTF8String:formats[i]]];
|
|
|
|
}
|
|
|
|
|
|
|
|
return array;
|
2014-02-26 04:50:54 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
+ (NSArray *)mimeTypes
|
|
|
|
{
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
+ (float)priority
|
|
|
|
{
|
|
|
|
return 0.0;
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|