Last active
November 17, 2022 07:04
-
-
Save jemmons/847cdc494eb332ba1b9f50f18ee8616a to your computer and use it in GitHub Desktop.
Sample CoreMIDI code…
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// See: https://forums.developer.apple.com/thread/65997 | |
MIDIInputPortCreateWithBlock(midiClient, "Instrument" as CFString, &inPort, { | |
(unsafePacketList: UnsafePointer<MIDIPacketList>, pointer: UnsafeMutableRawPointer?) in | |
let packetList = unsafePacketList.pointee | |
if packetList.numPackets == 1 { | |
let packet = packetList.packet | |
if packet.length == 3 && packet.data.0 == 144 { | |
/* Note-On */ | |
let note = packet.data.1 | |
let velocity = packet.data.2 | |
if velocity > 0 { | |
DispatchQueue.main.async(execute: { | |
// use note | |
}) | |
} | |
} | |
} | |
}) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//See: https://developer.apple.com/library/archive/qa/qa1374/_index.html | |
// ____________________________________________________________________________ | |
// Obtain the name of an endpoint without regard for whether it has connections. | |
// The result should be released by the caller. | |
static CFStringRef GetEndpointDisplayName(MIDIEndpointRef endpoint) | |
{ | |
CFStringRef result = CFSTR(""); // default | |
MIDIObjectGetStringProperty(endpoint, kMIDIPropertyDisplayName, &result); | |
return result; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//See: https://developer.apple.com/library/archive/qa/qa1374/_index.html | |
// Obtain the name of an endpoint, following connections. | |
// The result should be released by the caller. | |
static CFStringRef CreateConnectedEndpointName(MIDIEndpointRef endpoint) | |
{ | |
CFMutableStringRef result = CFStringCreateMutable(NULL, 0); | |
CFStringRef str; | |
OSStatus err; | |
// Does the endpoint have connections? | |
CFDataRef connections = NULL; | |
int nConnected = 0; | |
bool anyStrings = false; | |
err = MIDIObjectGetDataProperty(endpoint, kMIDIPropertyConnectionUniqueID, &connections); | |
if (connections != NULL) { | |
// It has connections, follow them | |
// Concatenate the names of all connected devices | |
nConnected = CFDataGetLength(connections) / sizeof(MIDIUniqueID); | |
if (nConnected) { | |
const SInt32 *pid = reinterpret_cast<const SInt32 *>(CFDataGetBytePtr(connections)); | |
for (int i = 0; i < nConnected; ++i, ++pid) { | |
MIDIUniqueID id = EndianS32_BtoN(*pid); | |
MIDIObjectRef connObject; | |
MIDIObjectType connObjectType; | |
err = MIDIObjectFindByUniqueID(id, &connObject, &connObjectType); | |
if (err == noErr) { | |
if (connObjectType == kMIDIObjectType_ExternalSource || | |
connObjectType == kMIDIObjectType_ExternalDestination) { | |
// Connected to an external device's endpoint (10.3 and later). | |
str = EndpointName(static_cast<MIDIEndpointRef>(connObject), true); | |
} else { | |
// Connected to an external device (10.2) (or something else, catch-all) | |
str = NULL; | |
MIDIObjectGetStringProperty(connObject, kMIDIPropertyName, &str); | |
} | |
if (str != NULL) { | |
if (anyStrings) | |
CFStringAppend(result, CFSTR(", ")); | |
else anyStrings = true; | |
CFStringAppend(result, str); | |
CFRelease(str); | |
} | |
} | |
} | |
} | |
CFRelease(connections); | |
} | |
if (anyStrings) | |
return result; | |
else | |
CFRelease(result); | |
// Here, either the endpoint had no connections, or we failed to obtain names for any of them. | |
return CreateEndpointName(endpoint, false); | |
} | |
////////////////////////////////////// | |
// Obtain the name of an endpoint without regard for whether it has connections. | |
// The result should be released by the caller. | |
static CFStringRef CreateEndpointName(MIDIEndpointRef endpoint, bool isExternal) | |
{ | |
CFMutableStringRef result = CFStringCreateMutable(NULL, 0); | |
CFStringRef str; | |
// begin with the endpoint's name | |
str = NULL; | |
MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &str); | |
if (str != NULL) { | |
CFStringAppend(result, str); | |
CFRelease(str); | |
} | |
MIDIEntityRef entity = NULL; | |
MIDIEndpointGetEntity(endpoint, &entity); | |
if (entity == NULL) | |
// probably virtual | |
return result; | |
if (CFStringGetLength(result) == 0) { | |
// endpoint name has zero length -- try the entity | |
str = NULL; | |
MIDIObjectGetStringProperty(entity, kMIDIPropertyName, &str); | |
if (str != NULL) { | |
CFStringAppend(result, str); | |
CFRelease(str); | |
} | |
} | |
// now consider the device's name | |
MIDIDeviceRef device = NULL; | |
MIDIEntityGetDevice(entity, &device); | |
if (device == NULL) return result; | |
str = NULL; | |
MIDIObjectGetStringProperty(device, kMIDIPropertyName, &str); | |
if (str != NULL) { | |
// if an external device has only one entity, throw away | |
// the endpoint name and just use the device name | |
if (isExternal && MIDIDeviceGetNumberOfEntities(device) < 2) { | |
CFRelease(result); | |
return str; | |
} else { | |
// does the entity name already start with the device name? | |
// (some drivers do this though they shouldn't) | |
// if so, do not prepend | |
if (CFStringCompareWithOptions(str /* device name */, | |
result /* endpoint name */, | |
CFRangeMake(0, CFStringGetLength(str)), 0) != kCFCompareEqualTo) { | |
// prepend the device name to the entity name | |
if (CFStringGetLength(result) > 0) | |
CFStringInsert(result, 0, CFSTR(" ")); | |
CFStringInsert(result, 0, str); | |
} | |
CFRelease(str); | |
} | |
} | |
return result; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// See: http://mattg411.com/coremidi-swift-programming/ | |
// | |
// Swift MIDI Playground : Matt Grippaldi 10/17/2016 | |
// | |
// Sources & Destinations | |
// | |
// Updated for Swift 3 / XCode 8 | |
// | |
import Cocoa | |
import CoreMIDI | |
import PlaygroundSupport | |
func getDisplayName(_ obj: MIDIObjectRef) -> String | |
{ | |
var param: Unmanaged<CFString>? | |
var name: String = "Error" | |
let err: OSStatus = MIDIObjectGetStringProperty(obj, kMIDIPropertyDisplayName, ¶m) | |
if err == OSStatus(noErr) | |
{ | |
name = param!.takeRetainedValue() as String | |
} | |
return name | |
} | |
func getDestinationNames() -> [String] | |
{ | |
var names:[String] = []; | |
let count: Int = MIDIGetNumberOfDestinations(); | |
for i in 0..<count { | |
let endpoint:MIDIEndpointRef = MIDIGetDestination(i); | |
if (endpoint != 0) | |
{ | |
names.append(getDisplayName(endpoint)); | |
} | |
} | |
return names; | |
} | |
func getSourceNames() -> [String] | |
{ | |
var names:[String] = []; | |
let count: Int = MIDIGetNumberOfSources(); | |
for i in 0..<count { | |
let endpoint:MIDIEndpointRef = MIDIGetSource(i); | |
if (endpoint != 0) | |
{ | |
names.append(getDisplayName(endpoint)); | |
} | |
} | |
return names; | |
} | |
let destNames = getDestinationNames(); | |
print("Number of MIDI Destinations: \(destNames.count)"); | |
for destName in destNames | |
{ | |
print(" Destination: \(destName)"); | |
} | |
let sourceNames = getSourceNames(); | |
print("\nNumber of MIDI Sources: \(sourceNames.count)"); | |
for sourceName in sourceNames | |
{ | |
print(" Source: \(sourceName)"); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// See: https://github.com/krevis/MIDIApps/blob/master/Frameworks/SnoizeMIDI/SMMessageParser.m | |
/* | |
Copyright (c) 2001-2008, Kurt Revis. All rights reserved. | |
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: | |
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. | |
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. | |
* Neither the name of Kurt Revis, nor Snoize, nor the names of other contributors may be used to endorse or promote products derived from this software without specific prior written permission. | |
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
*/ | |
#import "SMMessageParser.h" | |
#import "SMMessage.h" | |
#import "SMVoiceMessage.h" | |
#import "SMSystemCommonMessage.h" | |
#import "SMSystemRealTimeMessage.h" | |
#import "SMSystemExclusiveMessage.h" | |
#import "SMInvalidMessage.h" | |
@interface SMMessageParser (Private) | |
- (NSArray *)messagesForPacket:(const MIDIPacket *)packet; | |
- (SMSystemExclusiveMessage *)finishSysExMessageWithValidEnd:(BOOL)isEndValid; | |
- (void)sysExTimedOut; | |
@end | |
@implementation SMMessageParser | |
- (id)init; | |
{ | |
if (!(self = [super init])) | |
return nil; | |
sysExTimeOut = 1.0; // seconds | |
ignoreInvalidData = NO; | |
return self; | |
} | |
- (void)dealloc; | |
{ | |
[readingSysExData release]; | |
readingSysExData = nil; | |
[sysExTimeOutTimer invalidate]; | |
[sysExTimeOutTimer release]; | |
sysExTimeOutTimer = nil; | |
[super dealloc]; | |
} | |
- (id)delegate; | |
{ | |
return nonretainedDelegate; | |
} | |
- (void)setDelegate:(id)value; | |
{ | |
nonretainedDelegate = value; | |
} | |
- (SMEndpoint *)originatingEndpoint; | |
{ | |
return nonretainedOriginatingEndpoint; | |
} | |
- (void)setOriginatingEndpoint:(SMEndpoint *)value; | |
{ | |
nonretainedOriginatingEndpoint = value; | |
} | |
- (NSTimeInterval)sysExTimeOut; | |
{ | |
return sysExTimeOut; | |
} | |
- (void)setSysExTimeOut:(NSTimeInterval)value; | |
{ | |
sysExTimeOut = value; | |
} | |
- (BOOL)ignoresInvalidData | |
{ | |
return ignoreInvalidData; | |
} | |
- (void)setIgnoresInvalidData:(BOOL)value | |
{ | |
ignoreInvalidData = value; | |
} | |
- (void)takePacketList:(const MIDIPacketList *)packetList; | |
{ | |
NSMutableArray *messages = nil; | |
UInt32 packetCount; | |
const MIDIPacket *packet; | |
packetCount = packetList->numPackets; | |
packet = packetList->packet; | |
while (packetCount--) { | |
NSArray *messagesForPacket; | |
messagesForPacket = [self messagesForPacket:packet]; | |
if (messagesForPacket) { | |
if (!messages) | |
messages = [NSMutableArray arrayWithArray:messagesForPacket]; | |
else | |
[messages addObjectsFromArray:messagesForPacket]; | |
} | |
packet = MIDIPacketNext(packet); | |
} | |
if (messages) | |
[nonretainedDelegate parser:self didReadMessages:messages]; | |
if (readingSysExData) { | |
if (!sysExTimeOutTimer) { | |
// Create a timer which will fire after we have received no sysex data for a while. | |
// This takes care of interruption in the data (devices being turned off or unplugged) as well as | |
// ill-behaved devices which don't terminate their sysex messages with 0xF7. | |
NSRunLoop *runLoop; | |
NSString *mode; | |
runLoop = [NSRunLoop currentRunLoop]; | |
mode = [runLoop currentMode]; | |
if (mode) { | |
sysExTimeOutTimer = [[NSTimer timerWithTimeInterval:sysExTimeOut target:self selector:@selector(sysExTimedOut) userInfo:nil repeats:NO] retain]; | |
[runLoop addTimer:sysExTimeOutTimer forMode:mode]; | |
} else { | |
#if DEBUG | |
NSLog(@"SMMessageParser trying to add timer but the run loop has no mode--giving up"); | |
#endif | |
} | |
} else { | |
// We already have a timer, so just bump its fire date forward. | |
[sysExTimeOutTimer setFireDate:[NSDate dateWithTimeIntervalSinceNow:sysExTimeOut]]; | |
} | |
} else { | |
// Not reading sysex, so if we have a timeout pending, forget about it | |
if (sysExTimeOutTimer) { | |
[sysExTimeOutTimer invalidate]; | |
[sysExTimeOutTimer release]; | |
sysExTimeOutTimer = nil; | |
} | |
} | |
} | |
- (BOOL)cancelReceivingSysExMessage; | |
{ | |
BOOL cancelled = NO; | |
if (readingSysExData) { | |
[readingSysExData release]; | |
readingSysExData = nil; | |
cancelled = YES; | |
} | |
return cancelled; | |
} | |
@end | |
@implementation SMMessageParser (Private) | |
- (NSArray *)messagesForPacket:(const MIDIPacket *)packet; | |
{ | |
// Split this packet into separate MIDI messages | |
NSMutableArray *messages = nil; | |
const Byte *data; | |
UInt16 length; | |
Byte byte; | |
Byte pendingMessageStatus; | |
Byte pendingData[2]; | |
UInt16 pendingDataIndex, pendingDataLength; | |
NSMutableData* readingInvalidData = nil; | |
pendingMessageStatus = 0; | |
pendingDataIndex = pendingDataLength = 0; | |
data = packet->data; | |
length = packet->length; | |
while (length--) { | |
SMMessage *message = nil; | |
BOOL byteIsInvalid = NO; | |
byte = *data++; | |
if (byte >= 0xF8) { | |
// Real Time message | |
switch (byte) { | |
case SMSystemRealTimeMessageTypeClock: | |
case SMSystemRealTimeMessageTypeStart: | |
case SMSystemRealTimeMessageTypeContinue: | |
case SMSystemRealTimeMessageTypeStop: | |
case SMSystemRealTimeMessageTypeActiveSense: | |
case SMSystemRealTimeMessageTypeReset: | |
message = [SMSystemRealTimeMessage systemRealTimeMessageWithTimeStamp:packet->timeStamp type:byte]; | |
break; | |
default: | |
// Byte is invalid | |
byteIsInvalid = YES; | |
break; | |
} | |
} else { | |
if (byte < 0x80) { | |
if (readingSysExData) { | |
NSUInteger length; | |
[readingSysExData appendBytes:&byte length:1]; | |
length = 1 + [readingSysExData length]; | |
// Tell the delegate we're still reading, every 256 bytes | |
if (length % 256 == 0) | |
[nonretainedDelegate parser:self isReadingSysExWithLength:length]; | |
} else if (pendingDataIndex < pendingDataLength) { | |
pendingData[pendingDataIndex] = byte; | |
pendingDataIndex++; | |
if (pendingDataIndex == pendingDataLength) { | |
// This message is now done--send it | |
if (pendingMessageStatus >= 0xF0) | |
message = [SMSystemCommonMessage systemCommonMessageWithTimeStamp:packet->timeStamp type:pendingMessageStatus data:pendingData length:pendingDataLength]; | |
else | |
message = [SMVoiceMessage voiceMessageWithTimeStamp:packet->timeStamp statusByte:pendingMessageStatus data:pendingData length:pendingDataLength]; | |
pendingDataLength = 0; | |
} | |
} else { | |
// Skip this byte -- it is invalid | |
byteIsInvalid = YES; | |
} | |
} else { | |
if (readingSysExData) | |
message = [self finishSysExMessageWithValidEnd:(byte == 0xF7)]; | |
pendingMessageStatus = byte; | |
pendingDataLength = 0; | |
pendingDataIndex = 0; | |
switch (byte & 0xF0) { | |
case 0x80: // Note off | |
case 0x90: // Note on | |
case 0xA0: // Aftertouch | |
case 0xB0: // Controller | |
case 0xE0: // Pitch wheel | |
pendingDataLength = 2; | |
break; | |
case 0xC0: // Program change | |
case 0xD0: // Channel pressure | |
pendingDataLength = 1; | |
break; | |
case 0xF0: { | |
// System common message | |
switch (byte) { | |
case 0xF0: | |
// System exclusive | |
readingSysExData = [[NSMutableData alloc] init]; // This is atomic, so there's no need to lock | |
startSysExTimeStamp = packet->timeStamp; | |
[nonretainedDelegate parser:self isReadingSysExWithLength:1]; | |
break; | |
case 0xF7: | |
// System exclusive ends--already handled above. | |
// But if this is showing up outside of sysex, it's invalid. | |
if (!message) | |
byteIsInvalid = YES; | |
break; | |
case SMSystemCommonMessageTypeTimeCodeQuarterFrame: | |
case SMSystemCommonMessageTypeSongSelect: | |
pendingDataLength = 1; | |
break; | |
case SMSystemCommonMessageTypeSongPositionPointer: | |
pendingDataLength = 2; | |
break; | |
case SMSystemCommonMessageTypeTuneRequest: | |
message = [SMSystemCommonMessage systemCommonMessageWithTimeStamp:packet->timeStamp type:byte data:NULL length:0]; | |
break; | |
default: | |
// Invalid message | |
byteIsInvalid = YES; | |
break; | |
} | |
break; | |
} | |
default: | |
// This can't happen, but handle it anyway | |
byteIsInvalid = YES; | |
break; | |
} | |
} | |
} | |
if (!ignoreInvalidData) { | |
if (byteIsInvalid) { | |
if (!readingInvalidData) | |
readingInvalidData = [NSMutableData data]; | |
[readingInvalidData appendBytes:&byte length:1]; | |
} | |
if (readingInvalidData && (!byteIsInvalid || length == 0)) { | |
// We hit the end of a stretch of invalid data. | |
message = [SMInvalidMessage invalidMessageWithTimeStamp:packet->timeStamp data:readingInvalidData]; | |
readingInvalidData = nil; | |
} | |
} | |
if (message) { | |
[message setOriginatingEndpoint:nonretainedOriginatingEndpoint]; | |
if (!messages) | |
messages = [NSMutableArray arrayWithObject:message]; | |
else | |
[messages addObject:message]; | |
} | |
} | |
return messages; | |
} | |
- (SMSystemExclusiveMessage *)finishSysExMessageWithValidEnd:(BOOL)isEndValid; | |
{ | |
SMSystemExclusiveMessage *message = nil; | |
// NOTE: If we want, we could refuse sysex messages that don't end in 0xF7. | |
// The MIDI spec says that messages should end with this byte, but apparently that is not always the case in practice. | |
if (readingSysExData) { | |
message = [SMSystemExclusiveMessage systemExclusiveMessageWithTimeStamp:startSysExTimeStamp data:readingSysExData]; | |
[readingSysExData release]; | |
readingSysExData = nil; | |
} | |
if (message) { | |
[message setOriginatingEndpoint:nonretainedOriginatingEndpoint]; | |
[message setWasReceivedWithEOX:isEndValid]; | |
[nonretainedDelegate parser:self finishedReadingSysExMessage:message]; | |
} | |
return message; | |
} | |
- (void)sysExTimedOut; | |
{ | |
SMSystemExclusiveMessage *message; | |
[sysExTimeOutTimer release]; | |
sysExTimeOutTimer = nil; | |
message = [self finishSysExMessageWithValidEnd:NO]; | |
if (message) | |
[nonretainedDelegate parser:self didReadMessages:[NSArray arrayWithObject:message]]; | |
} | |
@end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// See: http://mattg411.com/coremidi-swift-programming/ | |
// | |
// | |
// Swift MIDI Playground : Matt Grippaldi 10/17/2016 | |
// | |
// Sources & Destinations | |
// | |
// Updated for Swift 3 / XCode 8 | |
// Added code to display destinations (11/25/2016) | |
// | |
import Cocoa | |
import CoreMIDI | |
import PlaygroundSupport | |
func getDisplayName(_ obj: MIDIObjectRef) -> String | |
{ | |
var param: Unmanaged? | |
var name: String = "Error"; | |
let err: OSStatus = MIDIObjectGetStringProperty(obj, kMIDIPropertyDisplayName, ¶m) | |
if err == OSStatus(noErr) | |
{ | |
name = param!.takeRetainedValue() as String | |
} | |
return name; | |
} | |
func getDestinationNames() -> [String] | |
{ | |
var names:[String] = [String](); | |
let count: Int = MIDIGetNumberOfDestinations(); | |
for i in 0 ..< count | |
{ | |
let endpoint:MIDIEndpointRef = MIDIGetDestination(i); | |
if (endpoint != 0) | |
{ | |
names.append(getDisplayName(endpoint)); | |
} | |
} | |
return names; | |
} | |
var midiClient: MIDIClientRef = 0; | |
var outPort:MIDIPortRef = 0; | |
MIDIClientCreate("MidiTestClient" as CFString, nil, nil, &midiClient); | |
MIDIOutputPortCreate(midiClient, "MidiTest_OutPort" as CFString, &outPort); | |
var packet1:MIDIPacket = MIDIPacket(); | |
packet1.timeStamp = 0; | |
packet1.length = 3; | |
packet1.data.0 = 0x90 + 0; // Note On event channel 1 | |
packet1.data.1 = 0x3C; // Note C3 | |
packet1.data.2 = 100; // Velocity | |
var packetList:MIDIPacketList = MIDIPacketList(numPackets: 1, packet: packet1); | |
let destinationNames = getDestinationNames() | |
for (index,destName) in destinationNames.enumerated() | |
{ | |
print("Destination #\(index): \(destName)") | |
} | |
let destNum = 0 | |
print("Using destination #\(destNum)") | |
var dest:MIDIEndpointRef = MIDIGetDestination(destNum); | |
print("Playing note for 1 second on channel 1") | |
MIDISend(outPort, dest, &packetList); | |
packet1.data.0 = 0x80 + 0; // Note Off event channel 1 | |
packet1.data.2 = 0; // Velocity | |
sleep(1); | |
packetList = MIDIPacketList(numPackets: 1, packet: packet1); | |
MIDISend(outPort, dest, &packetList); | |
print("Note off sent") |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// See: http://mattg411.com/coremidi-swift-programming/ | |
// | |
// Swift MIDI Playground : Matt Grippaldi 10/17/2016 | |
// | |
// MIDI Callbacks | |
// | |
// Updated for Swift 3 / XCode 8 | |
// | |
import Cocoa | |
import CoreMIDI | |
import PlaygroundSupport | |
func getDisplayName(_ obj: MIDIObjectRef) -> String | |
{ | |
var param: Unmanaged<CFString>? | |
var name: String = "Error" | |
let err: OSStatus = MIDIObjectGetStringProperty(obj, kMIDIPropertyDisplayName, ¶m) | |
if err == OSStatus(noErr) | |
{ | |
name = param!.takeRetainedValue() as String | |
} | |
return name | |
} | |
func MyMIDIReadProc(pktList: UnsafePointer<MIDIPacketList>, | |
readProcRefCon: UnsafeMutableRawPointer?, srcConnRefCon: UnsafeMutableRawPointer?) -> Void | |
{ | |
let packetList:MIDIPacketList = pktList.pointee | |
let srcRef:MIDIEndpointRef = srcConnRefCon!.load(as: MIDIEndpointRef.self) | |
print("MIDI Received From Source: \(getDisplayName(srcRef))") | |
var packet:MIDIPacket = packetList.packet | |
for _ in 1...packetList.numPackets | |
{ | |
let bytes = Mirror(reflecting: packet.data).children | |
var dumpStr = "" | |
// bytes mirror contains all the zero values in the ridiulous packet data tuple | |
// so use the packet length to iterate. | |
var i = packet.length | |
for (_, attr) in bytes.enumerated() | |
{ | |
dumpStr += String(format:"$%02X ", attr.value as! UInt8) | |
i -= 1 | |
if (i <= 0) | |
{ | |
break | |
} | |
} | |
print(dumpStr) | |
packet = MIDIPacketNext(&packet).pointee | |
} | |
} | |
var midiClient: MIDIClientRef = 0 | |
var inPort:MIDIPortRef = 0 | |
var src:MIDIEndpointRef = MIDIGetSource(0) | |
MIDIClientCreate("MidiTestClient" as CFString, nil, nil, &midiClient) | |
MIDIInputPortCreate(midiClient, "MidiTest_InPort" as CFString, MyMIDIReadProc, nil, &inPort) | |
MIDIPortConnectSource(inPort, src, &src) | |
// Keep playground running | |
PlaygroundPage.current.needsIndefiniteExecution = true |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment