2014-12-08 06:26:31 +00:00
|
|
|
//
|
|
|
|
// SidDecoder.mm
|
|
|
|
// sidplay
|
|
|
|
//
|
|
|
|
// Created by Christopher Snowhill on 12/8/14.
|
|
|
|
// Copyright 2014 __NoWork, Inc__. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
#import "SidDecoder.h"
|
|
|
|
|
|
|
|
#import <sidplayfp/residfp.h>
|
|
|
|
|
|
|
|
#import "roms.hpp"
|
|
|
|
|
|
|
|
#import "Logging.h"
|
|
|
|
|
|
|
|
#import "PlaylistController.h"
|
|
|
|
|
|
|
|
@implementation SidDecoder
|
|
|
|
|
|
|
|
- (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];
|
|
|
|
|
|
|
|
tune = new SidTune((const uint_least8_t *)data, (uint_least32_t)size);
|
|
|
|
|
2015-04-13 07:42:48 +00:00
|
|
|
if (!tune->getStatus())
|
|
|
|
return NO;
|
|
|
|
|
2014-12-08 06:26:31 +00:00
|
|
|
NSURL * url = [s url];
|
|
|
|
int track_num;
|
|
|
|
if ([[url fragment] length] == 0)
|
|
|
|
track_num = 1;
|
|
|
|
else
|
|
|
|
track_num = [[url fragment] intValue];
|
|
|
|
|
2020-02-13 09:29:08 +00:00
|
|
|
n_channels = 2;
|
2014-12-08 06:26:31 +00:00
|
|
|
|
|
|
|
length = 3 * 60 * 44100;
|
|
|
|
|
|
|
|
tune->selectSong( track_num );
|
|
|
|
|
|
|
|
engine = new sidplayfp;
|
|
|
|
|
|
|
|
engine->setRoms( kernel, basic, chargen );
|
|
|
|
|
2015-04-13 07:42:48 +00:00
|
|
|
if ( !engine->load( tune ) )
|
2014-12-08 06:26:31 +00:00
|
|
|
return NO;
|
|
|
|
|
|
|
|
ReSIDfpBuilder * _builder = new ReSIDfpBuilder("ReSIDfp");
|
|
|
|
builder = _builder;
|
|
|
|
|
|
|
|
if (_builder)
|
|
|
|
{
|
|
|
|
_builder->create((engine->info()).maxsids());
|
|
|
|
if (_builder->getStatus())
|
|
|
|
{
|
|
|
|
_builder->filter(true);
|
|
|
|
_builder->filter6581Curve(0.5);
|
2018-05-02 02:31:55 +00:00
|
|
|
_builder->filter8580Curve(0.5);
|
2014-12-08 06:26:31 +00:00
|
|
|
}
|
|
|
|
if (!_builder->getStatus())
|
|
|
|
return NO;
|
|
|
|
}
|
2019-12-06 02:13:52 +00:00
|
|
|
else return NO;
|
2014-12-08 06:26:31 +00:00
|
|
|
|
|
|
|
SidConfig conf = engine->config();
|
|
|
|
conf.frequency = 44100;
|
2020-02-13 09:29:08 +00:00
|
|
|
conf.playback = SidConfig::STEREO;
|
2014-12-08 06:26:31 +00:00
|
|
|
conf.sidEmulation = builder;
|
2016-06-19 19:57:18 +00:00
|
|
|
if (!engine->config(conf))
|
2014-12-08 06:26:31 +00:00
|
|
|
return NO;
|
|
|
|
|
|
|
|
renderedTotal = 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:16], @"bitsPerSample", //Samples are short
|
|
|
|
[NSNumber numberWithBool:NO], @"floatingPoint",
|
|
|
|
[NSNumber numberWithInt:n_channels], @"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;
|
|
|
|
int16_t * sampleBuffer = (int16_t*)buf;
|
|
|
|
while ( total < frames ) {
|
|
|
|
int framesToRender = 1024;
|
|
|
|
if ( framesToRender > frames )
|
|
|
|
framesToRender = frames;
|
|
|
|
int rendered = engine->play( sampleBuffer + total * n_channels, framesToRender * n_channels ) / n_channels;
|
|
|
|
|
|
|
|
if (rendered <= 0)
|
|
|
|
break;
|
|
|
|
|
2020-02-13 09:29:08 +00:00
|
|
|
for (int i = 0; i < rendered; i += 2) {
|
|
|
|
int16_t * sample = sampleBuffer + total * 2 + i;
|
|
|
|
int mid = (sample[0] + sample[1]) / 2;
|
|
|
|
int side = (sample[0] - sample[1]) / 4;
|
|
|
|
sample[0] = mid + side;
|
|
|
|
sample[1] = mid - side;
|
|
|
|
}
|
|
|
|
|
2014-12-08 06:26:31 +00:00
|
|
|
renderedTotal += rendered;
|
|
|
|
|
|
|
|
if ( !IsRepeatOneSet() && renderedTotal >= length ) {
|
|
|
|
int16_t * sampleBuf = ( int16_t * ) buf + total * n_channels;
|
|
|
|
long fadeEnd = fadeRemain - rendered;
|
|
|
|
if ( fadeEnd < 0 )
|
|
|
|
fadeEnd = 0;
|
|
|
|
float fadePosf = (float)fadeRemain / (float)fadeTotal;
|
|
|
|
const float fadeStep = 1.0f / (float)fadeTotal;
|
|
|
|
for ( long fadePos = fadeRemain; fadePos > fadeEnd; --fadePos, fadePosf -= fadeStep ) {
|
|
|
|
long offset = (fadeRemain - fadePos) * n_channels;
|
|
|
|
float sampleLeft = sampleBuf[ offset + 0 ];
|
|
|
|
sampleLeft *= fadePosf;
|
|
|
|
sampleBuf[ offset + 0 ] = (int16_t)sampleLeft;
|
|
|
|
if (n_channels == 2)
|
|
|
|
{
|
|
|
|
float sampleRight = sampleBuf[ offset + 1 ];
|
|
|
|
sampleRight *= fadePosf;
|
|
|
|
sampleBuf[ offset + 1 ] = (int16_t)sampleRight;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
rendered = (int)(fadeRemain - fadeEnd);
|
|
|
|
fadeRemain = fadeEnd;
|
|
|
|
}
|
|
|
|
|
|
|
|
total += rendered;
|
|
|
|
|
|
|
|
if ( rendered < framesToRender )
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return total;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (long)seek:(long)frame
|
|
|
|
{
|
|
|
|
if (frame < renderedTotal) {
|
|
|
|
engine->load(tune);
|
|
|
|
renderedTotal = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int16_t sampleBuffer[1024 * 2];
|
|
|
|
|
|
|
|
long remain = ( frame - renderedTotal ) % 32;
|
|
|
|
frame /= 32;
|
|
|
|
renderedTotal /= 32;
|
|
|
|
engine->fastForward( 100 * 32 );
|
|
|
|
|
|
|
|
while ( renderedTotal < frame )
|
|
|
|
{
|
|
|
|
long todo = frame - renderedTotal;
|
|
|
|
if ( todo > 1024 )
|
|
|
|
todo = 1024;
|
|
|
|
int done = engine->play( sampleBuffer, (uint_least32_t)(todo * n_channels) ) / n_channels;
|
|
|
|
|
|
|
|
if ( done < todo )
|
|
|
|
{
|
|
|
|
if ( engine->error() )
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
renderedTotal = length;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
renderedTotal += todo;
|
|
|
|
}
|
|
|
|
|
|
|
|
renderedTotal *= 32;
|
|
|
|
engine->fastForward( 100 );
|
|
|
|
|
|
|
|
if ( remain )
|
|
|
|
renderedTotal += engine->play( sampleBuffer, (uint_least32_t)(remain * n_channels) ) / n_channels;
|
|
|
|
|
|
|
|
return renderedTotal;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)cleanUp
|
|
|
|
{
|
|
|
|
if (builder)
|
|
|
|
{
|
|
|
|
delete builder;
|
|
|
|
builder = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (engine)
|
|
|
|
{
|
|
|
|
delete engine;
|
|
|
|
engine = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (tune)
|
|
|
|
{
|
|
|
|
delete tune;
|
|
|
|
tune = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)close
|
|
|
|
{
|
|
|
|
[self cleanUp];
|
|
|
|
}
|
|
|
|
|
2016-06-19 19:57:18 +00:00
|
|
|
- (void)dealloc
|
|
|
|
{
|
|
|
|
[self close];
|
|
|
|
}
|
|
|
|
|
2014-12-08 06:26:31 +00:00
|
|
|
- (void)setSource:(id<CogSource>)s
|
|
|
|
{
|
|
|
|
source = s;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (id<CogSource>)source
|
|
|
|
{
|
|
|
|
return source;
|
|
|
|
}
|
|
|
|
|
|
|
|
+ (NSArray *)fileTypes
|
|
|
|
{
|
|
|
|
return [NSArray arrayWithObjects:@"sid", @"mus", nil];
|
|
|
|
}
|
|
|
|
|
|
|
|
+ (NSArray *)mimeTypes
|
|
|
|
{
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
+ (float)priority
|
|
|
|
{
|
2015-04-13 06:28:06 +00:00
|
|
|
return 0.5;
|
2014-12-08 06:26:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@end
|