2013-10-15 11:49:53 -03:00
|
|
|
//
|
|
|
|
// MIDIDecoder.mm
|
|
|
|
// MIDI
|
|
|
|
//
|
|
|
|
// Created by Christopher Snowhill on 10/15/13.
|
|
|
|
// Copyright 2013 __NoWork, Inc__. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
#import "MIDIDecoder.h"
|
|
|
|
|
|
|
|
#import "BMPlayer.h"
|
|
|
|
|
|
|
|
#import "Logging.h"
|
|
|
|
|
|
|
|
#import <midi_processing/midi_processor.h>
|
|
|
|
|
2014-02-14 02:16:18 -03:00
|
|
|
#import "PlaylistController.h"
|
|
|
|
|
2013-10-15 11:49:53 -03:00
|
|
|
@implementation MIDIDecoder
|
|
|
|
|
2013-10-24 02:53:39 -03:00
|
|
|
+ (NSInteger)testExtensions:(NSString *)pathMinusExtension extensions:(NSArray *)extensionsToTest
|
|
|
|
{
|
|
|
|
NSInteger i = 0;
|
|
|
|
for (NSString * extension in extensionsToTest)
|
|
|
|
{
|
|
|
|
if ([[NSFileManager defaultManager] fileExistsAtPath:[pathMinusExtension stringByAppendingPathExtension:extension]])
|
|
|
|
return i;
|
|
|
|
++i;
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2013-10-15 11:49:53 -03:00
|
|
|
- (BOOL)open:(id<CogSource>)s
|
|
|
|
{
|
|
|
|
//We need file-size to use midi_processing
|
|
|
|
if (![s seekable]) {
|
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<uint8_t> file_data;
|
|
|
|
|
|
|
|
[s seek:0 whence:SEEK_END];
|
|
|
|
size_t size = [s tell];
|
|
|
|
[s seek:0 whence:SEEK_SET];
|
|
|
|
file_data.resize( size );
|
|
|
|
[s read:&file_data[0] amount:size];
|
|
|
|
|
2014-03-14 21:57:48 -03:00
|
|
|
if ( !midi_processor::process_file(file_data, [[[s url] pathExtension] UTF8String], midi_file) )
|
2013-10-15 11:49:53 -03:00
|
|
|
return NO;
|
|
|
|
|
|
|
|
int track_num = [[[s url] fragment] intValue]; //What if theres no fragment? Assuming we get 0.
|
|
|
|
|
2013-12-15 00:56:59 -03:00
|
|
|
midi_file.scan_for_loops( true, true, true );
|
2013-10-15 11:49:53 -03:00
|
|
|
|
2013-10-15 15:52:20 -03:00
|
|
|
framesLength = midi_file.get_timestamp_end( track_num, true );
|
2013-10-15 11:49:53 -03:00
|
|
|
|
|
|
|
unsigned long loopStart = midi_file.get_timestamp_loop_start( track_num, true );
|
|
|
|
unsigned long loopEnd = midi_file.get_timestamp_loop_end( track_num, true );
|
|
|
|
|
2013-10-15 15:52:20 -03:00
|
|
|
if ( loopStart == ~0UL ) loopStart = 0;
|
|
|
|
if ( loopEnd == ~0UL ) loopEnd = framesLength;
|
|
|
|
|
|
|
|
if ( loopStart != 0 || loopEnd != framesLength )
|
2013-10-15 11:49:53 -03:00
|
|
|
{
|
|
|
|
// two loops and a fade
|
|
|
|
framesLength = loopStart + ( loopEnd - loopStart ) * 2;
|
|
|
|
framesFade = 8000;
|
2014-05-11 22:27:09 -04:00
|
|
|
isLooped = YES;
|
2013-10-15 11:49:53 -03:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2013-10-15 15:52:20 -03:00
|
|
|
framesLength += 1000;
|
2013-10-15 11:49:53 -03:00
|
|
|
framesFade = 0;
|
2014-05-11 22:27:09 -04:00
|
|
|
isLooped = NO;
|
2013-10-15 11:49:53 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
framesLength = framesLength * 441 / 10;
|
|
|
|
framesFade = framesFade * 441 / 10;
|
|
|
|
|
|
|
|
totalFrames = framesLength + framesFade;
|
2013-10-24 02:53:39 -03:00
|
|
|
|
|
|
|
NSString * soundFontPath = @"";
|
|
|
|
|
|
|
|
if ( [[s url] isFileURL] )
|
|
|
|
{
|
|
|
|
// Let's check for a SoundFont
|
|
|
|
NSArray * extensions = [NSArray arrayWithObjects:@"sflist", @"sf2pack", @"sf2", nil];
|
|
|
|
NSString * filePath = [[s url] path];
|
|
|
|
NSString * fileNameBase = [filePath lastPathComponent];
|
|
|
|
filePath = [filePath stringByDeletingLastPathComponent];
|
|
|
|
soundFontPath = [filePath stringByAppendingPathComponent:fileNameBase];
|
|
|
|
NSInteger extFound;
|
|
|
|
if ((extFound = [MIDIDecoder testExtensions:soundFontPath extensions:extensions]) < 0)
|
|
|
|
{
|
|
|
|
fileNameBase = [fileNameBase stringByDeletingPathExtension];
|
|
|
|
soundFontPath = [filePath stringByAppendingPathComponent:fileNameBase];
|
|
|
|
if ((extFound = [MIDIDecoder testExtensions:soundFontPath extensions:extensions]) < 0)
|
|
|
|
{
|
|
|
|
fileNameBase = [filePath lastPathComponent];
|
|
|
|
soundFontPath = [filePath stringByAppendingPathComponent:fileNameBase];
|
|
|
|
extFound = [MIDIDecoder testExtensions:soundFontPath extensions:extensions];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (extFound >= 0)
|
|
|
|
{
|
|
|
|
soundFontPath = [soundFontPath stringByAppendingPathExtension:[extensions objectAtIndex:extFound]];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
soundFontPath = @"";
|
|
|
|
}
|
2013-10-15 11:49:53 -03:00
|
|
|
|
|
|
|
DLog(@"Length: %li", totalFrames);
|
|
|
|
|
|
|
|
DLog(@"Track num: %i", track_num);
|
|
|
|
|
|
|
|
BMPlayer * bmplayer = new BMPlayer;
|
|
|
|
player = bmplayer;
|
|
|
|
|
2014-03-27 01:49:31 -03:00
|
|
|
bool resampling_sinc = false;
|
|
|
|
NSString * resampling = [[NSUserDefaults standardUserDefaults] stringForKey:@"resampling"];
|
|
|
|
if ([resampling isEqualToString:@"sinc"])
|
|
|
|
resampling_sinc = true;
|
|
|
|
|
|
|
|
bmplayer->setSincInterpolation( resampling_sinc );
|
2013-10-15 11:49:53 -03:00
|
|
|
bmplayer->setSampleRate( 44100 );
|
|
|
|
|
2013-10-24 02:53:39 -03:00
|
|
|
if ( [soundFontPath length] )
|
|
|
|
bmplayer->setFileSoundFont( [soundFontPath UTF8String] );
|
|
|
|
|
2013-10-15 11:49:53 -03:00
|
|
|
unsigned int loop_mode = framesFade ? MIDIPlayer::loop_mode_enable | MIDIPlayer::loop_mode_force : 0;
|
|
|
|
unsigned int clean_flags = midi_container::clean_flag_emidi;
|
|
|
|
|
|
|
|
if ( !bmplayer->Load( midi_file, track_num, loop_mode, clean_flags) )
|
|
|
|
return NO;
|
|
|
|
|
|
|
|
framesRead = 0;
|
|
|
|
|
|
|
|
[self willChangeValueForKey:@"properties"];
|
|
|
|
[self didChangeValueForKey:@"properties"];
|
|
|
|
|
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSDictionary *)properties
|
|
|
|
{
|
|
|
|
return [NSDictionary dictionaryWithObjectsAndKeys:
|
|
|
|
[NSNumber numberWithInt:0], @"bitrate",
|
|
|
|
[NSNumber numberWithFloat:44100], @"sampleRate",
|
|
|
|
[NSNumber numberWithLong:totalFrames], @"totalFrames",
|
|
|
|
[NSNumber numberWithInt:32], @"bitsPerSample",
|
|
|
|
[NSNumber numberWithBool:YES], @"floatingPoint",
|
|
|
|
[NSNumber numberWithInt:2], @"channels", //output from gme_play is in stereo
|
|
|
|
[NSNumber numberWithBool:YES], @"seekable",
|
|
|
|
@"host", @"endian",
|
|
|
|
nil];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (int)readAudio:(void *)buf frames:(UInt32)frames
|
|
|
|
{
|
2014-02-14 02:16:18 -03:00
|
|
|
BOOL repeatone = IsRepeatOneSet();
|
2014-05-11 22:27:09 -04:00
|
|
|
long localFramesLength = framesLength;
|
|
|
|
long localTotalFrames = totalFrames;
|
2014-02-14 02:16:18 -03:00
|
|
|
|
2014-05-11 22:27:09 -04:00
|
|
|
if ( repeatone && !isLooped )
|
|
|
|
{
|
|
|
|
localFramesLength -= 44100;
|
|
|
|
localTotalFrames -= 44100;
|
|
|
|
repeatone = NO;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !repeatone && framesRead >= localTotalFrames )
|
2013-10-15 16:58:45 -03:00
|
|
|
return 0;
|
|
|
|
|
2013-10-15 11:49:53 -03:00
|
|
|
if ( !soundFontsAssigned ) {
|
|
|
|
NSString * soundFontPath = [[NSUserDefaults standardUserDefaults] stringForKey:@"soundFontPath"];
|
|
|
|
if (soundFontPath == nil)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
((BMPlayer *)player)->setSoundFont( [soundFontPath UTF8String] );
|
|
|
|
|
|
|
|
soundFontsAssigned = YES;
|
|
|
|
}
|
|
|
|
|
|
|
|
player->Play( (float *) buf, frames );
|
|
|
|
|
2014-05-11 22:27:09 -04:00
|
|
|
if ( !repeatone && framesRead + frames > localFramesLength ) {
|
2013-10-15 11:49:53 -03:00
|
|
|
if ( framesFade ) {
|
2014-05-11 22:27:09 -04:00
|
|
|
long fadeStart = (localFramesLength > framesRead) ? localFramesLength : framesRead;
|
|
|
|
long fadeEnd = (framesRead + frames > localTotalFrames) ? localTotalFrames : (framesRead + frames);
|
2013-10-15 11:49:53 -03:00
|
|
|
long fadePos;
|
|
|
|
|
|
|
|
float * buff = ( float * ) buf;
|
|
|
|
|
2014-05-11 22:27:09 -04:00
|
|
|
float fadeScale = (float)(framesFade - (fadeStart - localFramesLength)) / framesFade;
|
2013-10-18 22:52:59 -03:00
|
|
|
float fadeStep = 1.0 / (float)framesFade;
|
2013-10-15 11:49:53 -03:00
|
|
|
for (fadePos = fadeStart; fadePos < fadeEnd; ++fadePos) {
|
|
|
|
buff[ 0 ] *= fadeScale;
|
|
|
|
buff[ 1 ] *= fadeScale;
|
|
|
|
buff += 2;
|
|
|
|
fadeScale -= fadeStep;
|
2013-10-18 22:52:59 -03:00
|
|
|
if (fadeScale < 0) {
|
|
|
|
fadeScale = 0;
|
|
|
|
fadeStep = 0;
|
|
|
|
}
|
2013-10-15 11:49:53 -03:00
|
|
|
}
|
2013-10-18 22:52:59 -03:00
|
|
|
|
|
|
|
frames = (int)(fadeEnd - framesRead);
|
2013-10-15 11:49:53 -03:00
|
|
|
}
|
|
|
|
else {
|
2014-05-11 22:27:09 -04:00
|
|
|
frames = (int)(localTotalFrames - framesRead);
|
2013-10-15 11:49:53 -03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
framesRead += frames;
|
|
|
|
return frames;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (long)seek:(long)frame
|
|
|
|
{
|
|
|
|
player->Seek( frame );
|
|
|
|
|
2013-10-15 15:30:18 -03:00
|
|
|
framesRead = frame;
|
|
|
|
|
2013-10-15 11:49:53 -03:00
|
|
|
return frame;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)close
|
|
|
|
{
|
|
|
|
delete player;
|
|
|
|
player = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
+ (NSArray *)fileTypes
|
|
|
|
{
|
|
|
|
return [NSArray arrayWithObjects:@"mid", @"midi", @"kar", @"rmi", @"mids", @"mds", @"hmi", @"hmp", @"mus", @"xmi", @"lds", nil];
|
|
|
|
}
|
|
|
|
|
|
|
|
+ (NSArray *)mimeTypes
|
|
|
|
{
|
|
|
|
return [NSArray arrayWithObjects:@"audio/midi", @"audio/x-midi", nil];
|
|
|
|
}
|
|
|
|
|
Implemented support for multiple decoders per file name extension, with a floating point priority control per interface. In the event that more than one input is registered to a given extension, and we match that extension, it will be passed off to an instance of the multi-decoder wrapper, which will try opening the file with all of the decoders in order of priority, until either one of them accepts it, or all of them have failed. This paves the way for adding a VGMSTREAM input, so I can give it a very low priority, since it has several formats that are verified by file name extension only. All current inputs have been given a priority of 1.0, except for CoreAudio, which was given a priority of 0.5, because it contains an MP3 and AC3 decoders that I'd rather not use if I don't have to.
2013-10-21 14:54:11 -03:00
|
|
|
+ (float)priority
|
|
|
|
{
|
|
|
|
return 1.0;
|
|
|
|
}
|
|
|
|
|
2013-10-15 11:49:53 -03:00
|
|
|
@end
|