diff --git a/Cog.xcodeproj/project.pbxproj b/Cog.xcodeproj/project.pbxproj index 840cd9ef6..0a323097d 100644 --- a/Cog.xcodeproj/project.pbxproj +++ b/Cog.xcodeproj/project.pbxproj @@ -206,6 +206,8 @@ 83E5E54C18087CA5001F3284 /* miniModeOffTemplate.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 83E5E54A18087CA5001F3284 /* miniModeOffTemplate.pdf */; }; 83E5E54D18087CA5001F3284 /* miniModeOnTemplate.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 83E5E54B18087CA5001F3284 /* miniModeOnTemplate.pdf */; }; 83ED3AD1279A91C000904199 /* hdcdLogoTemplate.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 83ED3AC7279A91C000904199 /* hdcdLogoTemplate.pdf */; }; + 83F7AADA290B682400951B61 /* scpipe in Resources */ = {isa = PBXBuildFile; fileRef = 83F7AAD8290B682400951B61 /* scpipe */; }; + 83F7AADE290B8DDF00951B61 /* IIAM.bin in Resources */ = {isa = PBXBuildFile; fileRef = 83F7AAD7290B682400951B61 /* IIAM.bin */; }; 83F9D8071A884C54007ABEC2 /* SilenceDecoder.bundle in CopyFiles */ = {isa = PBXBuildFile; fileRef = 83F9D7F61A884B46007ABEC2 /* SilenceDecoder.bundle */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */; }; 8D11072D0486CEB800E47090 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; settings = {ATTRIBUTES = (); }; }; @@ -1092,6 +1094,8 @@ 83F0E8AD287CD48800D84594 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = ""; }; 83F0E8B1287CD50700D84594 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = ""; }; 83F0E8B2287CD52500D84594 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.html; name = pl; path = pl.lproj/Credits.html; sourceTree = ""; }; + 83F7AAD7290B682400951B61 /* IIAM.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; path = IIAM.bin; sourceTree = ""; }; + 83F7AAD8290B682400951B61 /* scpipe */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = scpipe; sourceTree = ""; }; 83F9D7F11A884B44007ABEC2 /* SilenceDecoder.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = SilenceDecoder.xcodeproj; path = Plugins/SilenceDecoder/SilenceDecoder.xcodeproj; sourceTree = ""; }; 8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 8E07AB760AAC930B00A4B32F /* PreferencesController.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = PreferencesController.h; path = Preferences/PreferencesController.h; sourceTree = ""; }; @@ -1570,6 +1574,8 @@ 29B97317FDCFA39411CA2CEA /* Resources */ = { isa = PBXGroup; children = ( + 83F7AAD7290B682400951B61 /* IIAM.bin */, + 83F7AAD8290B682400951B61 /* scpipe */, 839E56F12879625100DFB5F4 /* SADIE_D02-96000.mhr */, 837DC92F285B3F790005C58A /* DataModel.xcdatamodeld */, 8316B3922839FFD5004CC392 /* Scenes.scnassets */, @@ -2549,6 +2555,8 @@ 17D1B2800CF8B2830028F5B5 /* s3m.icns in Resources */, 8384916718083EAB00E7332D /* shuffleAlbumTemplate.pdf in Resources */, 17D1B2810CF8B2830028F5B5 /* song.icns in Resources */, + 83F7AADA290B682400951B61 /* scpipe in Resources */, + 83F7AADE290B8DDF00951B61 /* IIAM.bin in Resources */, 831B99BF27C23E88005A969B /* Cog.sdef in Resources */, 832923AF279FAC400048201E /* Cog.q1.json in Resources */, 836F462A28207FA4005B9B87 /* PauseColorful.png in Resources */, diff --git a/IIAM.bin b/IIAM.bin new file mode 100644 index 000000000..22421ec1f Binary files /dev/null and b/IIAM.bin differ diff --git a/Plugins/MIDI/MIDI.xcodeproj/project.pbxproj b/Plugins/MIDI/MIDI.xcodeproj/project.pbxproj index 611851ec7..9028157a1 100644 --- a/Plugins/MIDI/MIDI.xcodeproj/project.pbxproj +++ b/Plugins/MIDI/MIDI.xcodeproj/project.pbxproj @@ -40,6 +40,7 @@ 83C35702180EDB74007E9DF0 /* MIDIContainer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 83C35700180EDB74007E9DF0 /* MIDIContainer.mm */; }; 83C35705180EDD1C007E9DF0 /* MIDIMetadataReader.mm in Sources */ = {isa = PBXBuildFile; fileRef = 83C35703180EDD1C007E9DF0 /* MIDIMetadataReader.mm */; }; 83E973471C4378880007F413 /* AUPlayer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 83E973451C4378880007F413 /* AUPlayer.mm */; }; + 83F7AADD290B691900951B61 /* SCPlayer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 83F7AADB290B691900951B61 /* SCPlayer.mm */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -156,6 +157,8 @@ 83E973451C4378880007F413 /* AUPlayer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AUPlayer.mm; sourceTree = ""; }; 83E973461C4378880007F413 /* AUPlayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AUPlayer.h; sourceTree = ""; }; 83F0E6C4287CAB4300D84594 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = ""; }; + 83F7AADB290B691900951B61 /* SCPlayer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = SCPlayer.mm; sourceTree = ""; }; + 83F7AADC290B691900951B61 /* SCPlayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SCPlayer.h; sourceTree = ""; }; 83FAF8A618ADD60100057CAF /* PlaylistController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PlaylistController.h; path = ../../../Playlist/PlaylistController.h; sourceTree = ""; }; /* End PBXFileReference section */ @@ -308,6 +311,8 @@ 83B06690180D5668008E3612 /* MIDI */ = { isa = PBXGroup; children = ( + 83F7AADC290B691900951B61 /* SCPlayer.h */, + 83F7AADB290B691900951B61 /* SCPlayer.mm */, 834A42BC287AFC7F00EB9D9B /* AudioChunk.h */, 8307D31E28607377000FF8EB /* SandboxBroker.h */, 831E2A9127B4B2FA006F1C86 /* json */, @@ -466,6 +471,7 @@ 83A09F651CFA83F2001E7D2D /* opl3class.cpp in Sources */, 83C35705180EDD1C007E9DF0 /* MIDIMetadataReader.mm in Sources */, 834BE91B1DE407CB00A07DCD /* resampler.c in Sources */, + 83F7AADD290B691900951B61 /* SCPlayer.mm in Sources */, 83A09F641CFA83F2001E7D2D /* opl3.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Plugins/MIDI/MIDI/MIDIDecoder.mm b/Plugins/MIDI/MIDI/MIDIDecoder.mm index 2e7b01a60..78bf6be12 100644 --- a/Plugins/MIDI/MIDI/MIDIDecoder.mm +++ b/Plugins/MIDI/MIDI/MIDIDecoder.mm @@ -11,6 +11,7 @@ #import "AUPlayer.h" #import "BMPlayer.h" #import "MSPlayer.h" +#import "SCPlayer.h" #import "Logging.h" @@ -220,6 +221,11 @@ static OSType getOSType(const char *in_) { bmplayer->setFileSoundFont([soundFontPath UTF8String]); player = bmplayer; + } else if([plugin isEqualToString:@"sauce"]) { + SCPlayer *scplayer = new SCPlayer; + player = scplayer; + + scplayer->setSampleRate(sampleRate); } else if([[plugin substringToIndex:4] isEqualToString:@"DOOM"]) { MSPlayer *msplayer = new MSPlayer; player = msplayer; diff --git a/Plugins/MIDI/MIDI/SCPlayer.cpp b/Plugins/MIDI/MIDI/SCPlayer.cpp deleted file mode 100644 index 4294fdaee..000000000 --- a/Plugins/MIDI/MIDI/SCPlayer.cpp +++ /dev/null @@ -1,161 +0,0 @@ -#include -#include - -#include -#include -#include - -#include "SCPlayer.h" - -#define BLOCK_SIZE (512) - -// YAY! OS X doesn't unload dylibs on dlclose, so we cache up to two sets of instances here - -static pthread_mutex_t g_lock = PTHREAD_MUTEX_INITIALIZER; -static const unsigned int g_max_instances = 2; -static std::vector g_instances_open; -static SCCore g_sampler[3 * g_max_instances]; - -SCPlayer::SCPlayer() -: MIDIPlayer(), initialized(false), sccore_path(0) { - pthread_mutex_lock(&g_lock); - while(g_instances_open.size() >= g_max_instances) { - pthread_mutex_unlock(&g_lock); - usleep(10000); - pthread_mutex_lock(&g_lock); - } - unsigned int i; - for(i = 0; i < g_max_instances; ++i) { - if(std::find(g_instances_open.begin(), g_instances_open.end(), i) == g_instances_open.end()) - break; - } - g_instances_open.push_back(i); - instance_id = i; - sampler = &g_sampler[i * 3]; - pthread_mutex_unlock(&g_lock); -} - -SCPlayer::~SCPlayer() { - shutdown(); - if(sccore_path) - free(sccore_path); - if(sampler) { - pthread_mutex_lock(&g_lock); - auto it = std::find(g_instances_open.begin(), g_instances_open.end(), instance_id); - it = g_instances_open.erase(it); - pthread_mutex_unlock(&g_lock); - } -} - -void SCPlayer::set_sccore_path(const char *path) { - size_t len; - if(sccore_path) free(sccore_path); - len = strlen(path); - sccore_path = (char *)malloc(len + 1); - if(sccore_path) - memcpy(sccore_path, path, len + 1); -} - -void SCPlayer::send_event(uint32_t b) { - send_event_time(b, 0); -} - -void SCPlayer::send_sysex(const uint8_t *data, size_t size, size_t port) { - send_sysex_time(data, size, port, 0); -} - -void SCPlayer::send_event_time(uint32_t b, unsigned int time) { - unsigned port = (b >> 24) & 0x7F; - if(port > 2) port = 0; - sampler[port].TG_ShortMidiIn(b, time); -} - -void SCPlayer::send_sysex_time(const uint8_t *data, size_t size, size_t port, unsigned int time) { - if(port > 2) port = 0; - sampler[port].TG_LongMidiIn(data, time); - if(port == 0) { - sampler[1].TG_LongMidiIn(data, time); - sampler[2].TG_LongMidiIn(data, time); - } -} - -void SCPlayer::render(float *out, unsigned long count) { - memset(out, 0, count * sizeof(float) * 2); - while(count) { - float buffer[2][BLOCK_SIZE]; - unsigned long todo = count > BLOCK_SIZE ? BLOCK_SIZE : count; - for(unsigned long i = 0; i < 3; ++i) { - memset(buffer[0], 0, todo * sizeof(float)); - memset(buffer[1], 0, todo * sizeof(float)); - - sampler[i].TG_setInterruptThreadIdAtThisTime(); - sampler[i].TG_Process(buffer[0], buffer[1], (unsigned int)todo); - - for(unsigned long j = 0; j < todo; ++j) { - out[j * 2 + 0] += buffer[0][j]; - out[j * 2 + 1] += buffer[1][j]; - } - } - out += todo * 2; - count -= todo; - } -} - -void SCPlayer::shutdown() { - for(int i = 0; i < 3; i++) { - if(sampler[i].TG_deactivate) { - sampler[i].TG_flushMidi(); - sampler[i].TG_deactivate(); - } - } - initialized = false; -} - -bool SCPlayer::startup() { - int i; - - if(initialized) return true; - - if(!sccore_path) return false; - - for(i = 0; i < 3; i++) { - if(!sampler[i].TG_initialize) { - if(!sampler[i].Load(sccore_path, true)) - return false; - - if(sampler[i].TG_initialize(0) < 0) - return false; - } - - sampler[i].TG_activate(44100.0, 1024); - sampler[i].TG_setMaxBlockSize(256); - sampler[i].TG_setSampleRate((float)uSampleRate); - sampler[i].TG_setSampleRate((float)uSampleRate); - sampler[i].TG_setMaxBlockSize(BLOCK_SIZE); - } - - initialized = true; - - for(int i = 0; i < 3; i++) { - reset(i, 0); - } - - return true; -} - -unsigned int SCPlayer::get_playing_note_count() { - unsigned int total = 0; - unsigned int i; - - if(!initialized) - return 0; - - for(i = 0; i < 3; i++) - total += sampler[i].TG_XPgetCurTotalRunningVoices(); - - return total; -} - -unsigned int SCPlayer::send_event_needs_time() { - return BLOCK_SIZE; -} diff --git a/Plugins/MIDI/MIDI/SCPlayer.h b/Plugins/MIDI/MIDI/SCPlayer.h index f9d9cf42c..e594bdd99 100644 --- a/Plugins/MIDI/MIDI/SCPlayer.h +++ b/Plugins/MIDI/MIDI/SCPlayer.h @@ -3,7 +3,7 @@ #include "MIDIPlayer.h" -#include "SCCore.h" +#import class SCPlayer : public MIDIPlayer { public: @@ -13,28 +13,42 @@ class SCPlayer : public MIDIPlayer { // close, unload virtual ~SCPlayer(); - unsigned int get_playing_note_count(); - - void set_sccore_path(const char* path); - protected: virtual unsigned int send_event_needs_time(); virtual void send_event(uint32_t b); - virtual void send_sysex(const uint8_t* data, size_t size, size_t port); + virtual void send_sysex(const uint8_t* event, size_t size, size_t port); virtual void render(float* out, unsigned long count); virtual void shutdown(); virtual bool startup(); virtual void send_event_time(uint32_t b, unsigned int time); - virtual void send_sysex_time(const uint8_t* data, size_t size, size_t port, unsigned int time); + virtual void send_sysex_time(const uint8_t* event, size_t size, size_t port, unsigned int time); private: - unsigned int instance_id; - bool initialized; - SCCore* sampler; + bool LoadCore(); - char* sccore_path; + void send_command(uint32_t port, uint32_t command); + + void render_port(uint32_t port, float* out, uint32_t count); + + void reset(uint32_t port); + + void junk(uint32_t port, unsigned long count); + + bool process_create(uint32_t port); + void process_terminate(uint32_t port); + bool process_running(uint32_t port); + uint32_t process_read_code(uint32_t port); + void process_read_bytes(uint32_t port, void* buffer, uint32_t size); + uint32_t process_read_bytes_pass(uint32_t port, void* buffer, uint32_t size); + void process_write_code(uint32_t port, uint32_t code); + void process_write_bytes(uint32_t port, const void* buffer, uint32_t size); + + bool bTerminating[3]; + NSTask* hProcess[3]; + NSPipe* hChildStd_IN[3]; + NSPipe* hChildStd_OUT[3]; }; #endif diff --git a/Plugins/MIDI/MIDI/SCPlayer.mm b/Plugins/MIDI/MIDI/SCPlayer.mm new file mode 100644 index 000000000..19add2b8f --- /dev/null +++ b/Plugins/MIDI/MIDI/SCPlayer.mm @@ -0,0 +1,243 @@ +#include "SCPlayer.h" + +#include + +static uint16_t getwordle(uint8_t *pData) { + return (uint16_t)(pData[0] | (((uint16_t)pData[1]) << 8)); +} + +static uint32_t getdwordle(uint8_t *pData) { + return pData[0] | (((uint32_t)pData[1]) << 8) | (((uint32_t)pData[2]) << 16) | (((uint32_t)pData[3]) << 24); +} + +bool SCPlayer::LoadCore() { + bool rval = process_create(0); + if(rval) rval = process_create(1); + if(rval) rval = process_create(2); + + return rval; +} + +bool SCPlayer::process_create(uint32_t port) { + bTerminating[port] = false; + + hChildStd_IN[port] = [[NSPipe alloc] init]; + hChildStd_OUT[port] = [[NSPipe alloc] init]; + + NSURL *launcherUrl = [[NSBundle mainBundle] URLForResource:@"scpipe" withExtension:@""]; + NSURL *coreUrl = [[NSBundle mainBundle] URLForResource:@"IIAM" withExtension:@"bin"]; + + hProcess[port] = [[NSTask alloc] init]; + [hProcess[port] setExecutableURL:launcherUrl]; + [hProcess[port] setArguments:@[[coreUrl path]]]; + [hProcess[port] setStandardInput:hChildStd_IN[port]]; + [hProcess[port] setStandardOutput:hChildStd_OUT[port]]; + + NSError *error = nil; + if(![hProcess[port] launchAndReturnError:&error] || error != nil) { + process_terminate(port); + return false; + } + + uint32_t code = process_read_code(port); + + if(code != 0) { + process_terminate(port); + return false; + } + + return true; +} + +void SCPlayer::process_terminate(uint32_t port) { + if(bTerminating[port]) return; + + bTerminating[port] = true; + + if(hProcess[port]) { + process_write_code(port, 0); + [hProcess[port] interrupt]; + [hProcess[port] waitUntilExit]; + [hProcess[port] terminate]; + hProcess[port] = nil; + } + if(hChildStd_IN[port]) { + hChildStd_IN[port] = nil; + } + if(hChildStd_OUT[port]) { + hChildStd_OUT[port] = nil; + } + + bTerminating[port] = false; +} + +bool SCPlayer::process_running(uint32_t port) { + if(hProcess[port] && [hProcess[port] isRunning]) return true; + return false; +} + +uint32_t SCPlayer::process_read_bytes_pass(uint32_t port, void *out, uint32_t size) { + NSError *error = nil; + NSData *data = [[hChildStd_OUT[port] fileHandleForReading] readDataUpToLength:size error:&error]; + if(!data || error) return 0; + NSUInteger bytesDone = [data length]; + memcpy(out, [data bytes], bytesDone); + return bytesDone; +} + +void SCPlayer::process_read_bytes(uint32_t port, void *out, uint32_t size) { + if(process_running(port) && size) { + uint8_t *ptr = (uint8_t *)out; + uint32_t done = 0; + while(done < size) { + uint32_t delta = process_read_bytes_pass(port, ptr + done, size - done); + if(delta == 0) { + memset(out, 0xFF, size); + break; + } + done += delta; + } + } else + memset(out, 0xFF, size); +} + +uint32_t SCPlayer::process_read_code(uint32_t port) { + uint32_t code; + process_read_bytes(port, &code, sizeof(code)); + return code; +} + +void SCPlayer::process_write_bytes(uint32_t port, const void *in, uint32_t size) { + if(process_running(port)) { + if(size == 0) return; + NSError *error = nil; + NSData *data = [NSData dataWithBytes:in length:size]; + if(![[hChildStd_IN[port] fileHandleForWriting] writeData:data error:&error] || error) { + process_terminate(port); + } + } +} + +void SCPlayer::process_write_code(uint32_t port, uint32_t code) { + process_write_bytes(port, &code, sizeof(code)); +} + +SCPlayer::SCPlayer() +: MIDIPlayer() { + initialized = false; + for(unsigned int i = 0; i < 3; ++i) { + bTerminating[i] = false; + hProcess[i] = nil; + hChildStd_IN[i] = nil; + hChildStd_OUT[i] = nil; + } +} + +SCPlayer::~SCPlayer() { + shutdown(); +} + +void SCPlayer::send_event(uint32_t b) { + uint32_t port = (b >> 24) & 0xFF; + if(port > 2) port = 0; + process_write_code(port, 2); + process_write_code(port, b & 0xFFFFFF); + if(process_read_code(port) != 0) + process_terminate(port); +} + +void SCPlayer::send_sysex(const uint8_t *event, size_t size, size_t port) { + process_write_code(port, 3); + process_write_code(port, (uint32_t)size); + process_write_bytes(port, event, size); + if(process_read_code(port) != 0) + process_terminate(port); + if(port == 0) { + send_sysex(event, size, 1); + send_sysex(event, size, 2); + } +} + +void SCPlayer::send_event_time(uint32_t b, unsigned int time) { + uint32_t port = (b >> 24) & 0xFF; + if(port > 2) port = 0; + process_write_code(port, 6); + process_write_code(port, b & 0xFFFFFF); + process_write_code(port, time); + if(process_read_code(port) != 0) + process_terminate(port); +} + +void SCPlayer::send_sysex_time(const uint8_t *event, size_t size, size_t port, unsigned int time) { + process_write_code(port, 7); + process_write_code(port, size); + process_write_code(port, time); + process_write_bytes(port, event, size); + if(process_read_code(port) != 0) + process_terminate(port); + if(port == 0) { + send_sysex_time(event, size, 1, time); + send_sysex_time(event, size, 2, time); + } +} + +void SCPlayer::render_port(uint32_t port, float *out, uint32_t count) { + process_write_code(port, 4); + process_write_code(port, count); + if(process_read_code(port) != 0) { + process_terminate(port); + memset(out, 0, count * sizeof(float) * 2); + return; + } + process_read_bytes(port, out, count * sizeof(float) * 2); +} + +void SCPlayer::render(float *out, unsigned long count) { + memset(out, 0, count * sizeof(float) * 2); + while(count) { + unsigned long todo = count > 4096 ? 4096 : count; + float buffer[4096 * 2]; + for(unsigned long i = 0; i < 3; ++i) { + render_port(i, buffer, todo); + + for(unsigned long j = 0; j < todo; ++j) { + out[j * 2 + 0] += buffer[j * 2 + 0]; + out[j * 2 + 1] += buffer[j * 2 + 1]; + } + } + out += todo * 2; + count -= todo; + } +} + +void SCPlayer::shutdown() { + for(int i = 0; i < 3; i++) { + process_terminate(i); + } + initialized = false; +} + +bool SCPlayer::startup() { + if(initialized) return true; + + if(!LoadCore()) + return false; + + for(int i = 0; i < 3; i++) { + process_write_code(i, 1); + process_write_code(i, sizeof(uint32_t)); + process_write_code(i, uSampleRate); + if(process_read_code(i) != 0) + return false; + } + + initialized = true; + + setFilterMode(mode, reverb_chorus_disabled); + + return true; +} + +unsigned int SCPlayer::send_event_needs_time() { + return 0; // 4096; This doesn't work for some reason +} diff --git a/Preferences/Preferences/MIDIPluginBehaviorArrayController.m b/Preferences/Preferences/MIDIPluginBehaviorArrayController.m index cfd0e9cd0..4323d730c 100644 --- a/Preferences/Preferences/MIDIPluginBehaviorArrayController.m +++ b/Preferences/Preferences/MIDIPluginBehaviorArrayController.m @@ -68,6 +68,8 @@ static void enumCallback(void *context, OSType uSubType, OSType uManufacturer, c [self addObject:@{@"name": @"BASSMIDI", @"preference": @"BASSMIDI"}]; + [self addObject:@{ @"name": @"Secret Sauce", @"preference": @"sauce" }]; + [self addObject:@{@"name": @"DMX Generic", @"preference": @"DOOM0000"}]; [self addObject:@{@"name": @"DMX Doom 1", @"preference": @"DOOM0001"}]; [self addObject:@{@"name": @"DMX Doom 2", @"preference": @"DOOM0002"}]; diff --git a/scpipe b/scpipe new file mode 100755 index 000000000..42b7a90cd Binary files /dev/null and b/scpipe differ