cog/Plugins/MIDI/MIDI/MIDIDecoder.mm

367 lines
9.7 KiB
Plaintext

//
// MIDIDecoder.mm
// MIDI
//
// Created by Christopher Snowhill on 10/15/13.
// Copyright 2013 __NoWork, Inc__. All rights reserved.
//
#import "MIDIDecoder.h"
#import "AUPlayer.h"
#import "BMPlayer.h"
#import "MSPlayer.h"
#import "Logging.h"
#import <midi_processing/midi_processor.h>
#import "PlaylistController.h"
#import <dlfcn.h>
static OSType getOSType(const char *in_) {
const unsigned char *in = (const unsigned char *)in_;
OSType v = (in[0] << 24) + (in[1] << 16) + (in[2] << 8) + in[3];
return v;
}
@implementation MIDIDecoder
+ (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;
}
- (BOOL)open:(id<CogSource>)s {
// We need file-size to use midi_processing
if(![s seekable]) {
return NO;
}
source = s;
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];
if(!midi_processor::process_file(file_data, [[[s url] pathExtension] UTF8String], midi_file))
return NO;
if(!midi_file.get_timestamp_end(track_num))
return NO;
track_num = [[[s url] fragment] intValue]; // What if theres no fragment? Assuming we get 0.
midi_file.scan_for_loops(true, true, true, true);
framesLength = midi_file.get_timestamp_end(track_num, true);
unsigned long loopStart = midi_file.get_timestamp_loop_start(track_num, true);
unsigned long loopEnd = midi_file.get_timestamp_loop_end(track_num, true);
if(loopStart == ~0UL) loopStart = 0;
if(loopEnd == ~0UL) loopEnd = framesLength;
if(loopStart != 0 || loopEnd != framesLength) {
// two loops and a fade
framesLength = loopStart + (loopEnd - loopStart) * 2;
framesFade = 8000;
isLooped = YES;
} else {
framesLength += 1000;
framesFade = 0;
isLooped = NO;
}
framesLength = framesLength * 441 / 10;
framesFade = framesFade * 441 / 10;
totalFrames = framesLength + framesFade;
framesRead = 0;
[self willChangeValueForKey:@"properties"];
[self didChangeValueForKey:@"properties"];
return YES;
}
- (NSDictionary *)properties {
return @{ @"bitrate": @(0),
@"sampleRate": @(44100),
@"totalFrames": @(totalFrames),
@"bitsPerSample": @(32),
@"floatingPoint": @(YES),
@"channels": @(2),
@"seekable": @(YES),
@"codec": @"MIDI",
@"endian": @"host",
@"encoding": @"synthesized" };
}
- (NSDictionary *)metadata {
return @{};
}
- (BOOL)initDecoder {
NSString *soundFontPath = @"";
if([[source url] isFileURL]) {
// Let's check for a SoundFont
NSArray *extensions = @[@"sflist", @"sf2pack", @"sf2"];
NSString *filePath = [[source 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 = @"";
}
DLog(@"Length: %li", totalFrames);
DLog(@"Track num: %i", track_num);
MIDIPlayer::filter_mode mode = MIDIPlayer::filter_sc55;
NSString *flavor = [[NSUserDefaults standardUserDefaults] stringForKey:@"midi.flavor"];
if([flavor isEqualToString:@"default"])
mode = MIDIPlayer::filter_default;
else if([flavor isEqualToString:@"gm"])
mode = MIDIPlayer::filter_gm;
else if([flavor isEqualToString:@"gm2"])
mode = MIDIPlayer::filter_gm2;
else if([flavor isEqualToString:@"sc55"])
mode = MIDIPlayer::filter_sc55;
else if([flavor isEqualToString:@"sc88"])
mode = MIDIPlayer::filter_sc88;
else if([flavor isEqualToString:@"sc88pro"])
mode = MIDIPlayer::filter_sc88pro;
else if([flavor isEqualToString:@"sc8850"])
mode = MIDIPlayer::filter_sc8850;
else if([flavor isEqualToString:@"xg"])
mode = MIDIPlayer::filter_xg;
globalSoundFontPath = [[NSUserDefaults standardUserDefaults] stringForKey:@"soundFontPath"];
// First detect if soundfont has gone AWOL
if(![[NSFileManager defaultManager] fileExistsAtPath:globalSoundFontPath]) {
globalSoundFontPath = nil;
[[NSUserDefaults standardUserDefaults] setValue:globalSoundFontPath forKey:@"soundFontPath"];
}
NSString *plugin = [[NSUserDefaults standardUserDefaults] stringForKey:@"midiPlugin"];
// Then detect if we should force the DLSMusicSynth, which has its own bank
if(!plugin || [plugin isEqualToString:@"BASSMIDI"]) {
if(!globalSoundFontPath || [globalSoundFontPath isEqualToString:@""]) {
plugin = @"dls appl"; // Apple DLSMusicSynth if soundfont doesn't exist
[[NSUserDefaults standardUserDefaults] setValue:plugin forKey:@"midiPlugin"];
}
}
if(!plugin || [plugin isEqualToString:@"BASSMIDI"]) {
bmplayer = new BMPlayer;
bool resamplingSinc = false;
NSString *resampling = [[NSUserDefaults standardUserDefaults] stringForKey:@"resampling"];
if([resampling isEqualToString:@"sinc"])
resamplingSinc = true;
bmplayer->setSincInterpolation(resamplingSinc);
bmplayer->setSampleRate(44100);
if([soundFontPath length])
bmplayer->setFileSoundFont([soundFontPath UTF8String]);
player = bmplayer;
} else if([[plugin substringToIndex:4] isEqualToString:@"DOOM"]) {
MSPlayer *msplayer = new MSPlayer;
player = msplayer;
msplayer->set_synth(0);
msplayer->set_bank([[plugin substringFromIndex:4] intValue]);
msplayer->set_extp(1);
msplayer->setSampleRate(44100);
} else if([[plugin substringToIndex:5] isEqualToString:@"OPL3W"]) {
MSPlayer *msplayer = new MSPlayer;
player = msplayer;
msplayer->set_synth(1);
msplayer->set_bank([[plugin substringFromIndex:5] intValue]);
msplayer->set_extp(1);
msplayer->setSampleRate(44100);
} else {
const char *cplugin = [plugin UTF8String];
OSType componentSubType;
OSType componentManufacturer;
componentSubType = getOSType(cplugin);
componentManufacturer = getOSType(cplugin + 4);
{
auplayer = new AUPlayer;
auplayer->setComponent(componentSubType, componentManufacturer);
auplayer->setSampleRate(44100);
if([soundFontPath length]) {
auplayer->setSoundFont([soundFontPath UTF8String]);
soundFontsAssigned = YES;
}
player = auplayer;
}
}
player->setFilterMode(mode, false);
unsigned int loop_mode = framesFade ? MIDIPlayer::loop_mode_enable | MIDIPlayer::loop_mode_force : 0;
unsigned int clean_flags = midi_container::clean_flag_emidi;
if(!player->Load(midi_file, track_num, loop_mode, clean_flags))
return NO;
return YES;
}
- (int)readAudio:(void *)buf frames:(UInt32)frames {
BOOL repeatone = IsRepeatOneSet();
long localFramesLength = framesLength;
long localTotalFrames = totalFrames;
if(!player) {
if(![self initDecoder])
return -1;
}
player->setLoopMode((repeatone || isLooped) ? (MIDIPlayer::loop_mode_enable | MIDIPlayer::loop_mode_force) : 0);
if(!repeatone && framesRead >= localTotalFrames)
return 0;
if((bmplayer || auplayer) && !soundFontsAssigned) {
if(globalSoundFontPath != nil) {
if(bmplayer)
bmplayer->setSoundFont([globalSoundFontPath UTF8String]);
else if(auplayer)
auplayer->setSoundFont([globalSoundFontPath UTF8String]);
}
soundFontsAssigned = YES;
}
UInt32 frames_done = player->Play((float *)buf, frames);
if(!frames_done)
return 0;
frames = frames_done;
if(!repeatone && framesRead + frames > localFramesLength) {
if(framesFade) {
long fadeStart = (localFramesLength > framesRead) ? localFramesLength : framesRead;
long fadeEnd = (framesRead + frames > localTotalFrames) ? localTotalFrames : (framesRead + frames);
long fadePos;
float *buff = (float *)buf;
float fadeScale = (float)(framesFade - (fadeStart - localFramesLength)) / framesFade;
float fadeStep = 1.0 / (float)framesFade;
for(fadePos = fadeStart; fadePos < fadeEnd; ++fadePos) {
buff[0] *= fadeScale;
buff[1] *= fadeScale;
buff += 2;
fadeScale -= fadeStep;
if(fadeScale < 0) {
fadeScale = 0;
fadeStep = 0;
}
}
frames = (int)(fadeEnd - framesRead);
} else {
frames = (int)(localTotalFrames - framesRead);
}
}
framesRead += frames;
return frames;
}
- (long)seek:(long)frame {
if(!player) {
float temp[2];
if([self readAudio:temp frames:1] < 1)
return -1;
}
player->Seek(frame);
framesRead = frame;
return frame;
}
- (void)close {
delete player;
player = NULL;
}
- (void)dealloc {
[self close];
}
+ (NSArray *)fileTypes {
return @[@"mid", @"midi", @"kar", @"rmi", @"mids", @"mds", @"hmi", @"hmp", @"hmq", @"mus", @"xmi", @"lds"];
}
+ (NSArray *)mimeTypes {
return @[@"audio/midi", @"audio/x-midi"];
}
+ (float)priority {
return 1.0;
}
+ (NSArray *)fileTypeAssociations {
return @[
@[@"General MIDI File", @"song.icns", @"mid", @"midi", @"kar"],
@[@"RIFF MIDI File", @"song.icns", @"rmi"],
@[@"MIDS MIDI File", @"song.icns", @"mids", @"mds"],
@[@"HMI MIDI File", @"song.icns", @"hmi", @"hmp", @"hmq"],
@[@"id Software MUS MIDI File", @"song.icns", @"mus"],
@[@"XMI MIDI File", @"song.icns", @"xmi"],
@[@"Loudness MIDI File", @"song.icns", @"lds"]
];
}
@end