hammerspoon/Pods/MIKMIDI/Source/MIKMIDIUtilities.m

223 lines
7.1 KiB
Objective-C

//
// MIKMIDIUtilities.m
// MIDI Testbed
//
// Created by Andrew Madsen on 3/7/13.
// Copyright (c) 2013 Mixed In Key. All rights reserved.
//
#import "MIKMIDIUtilities.h"
#import "MIKMIDIErrors.h"
#if !__has_feature(objc_arc)
#error MIKMIDIUtilities.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIUtilities.m in the Build Phases for this target
#endif
NSString *MIKStringPropertyFromMIDIObject(MIDIObjectRef object, CFStringRef propertyID, NSError *__autoreleasing*error)
{
error = error ? error : &(NSError *__autoreleasing){ nil };
CFStringRef result;
OSStatus err = MIDIObjectGetStringProperty(object, propertyID, &result);
if (err) {
*error = [NSError errorWithDomain:NSOSStatusErrorDomain code:err userInfo:nil];
return nil;
}
NSCharacterSet *controlCharacters = [NSCharacterSet controlCharacterSet];
return [CFBridgingRelease(result) stringByTrimmingCharactersInSet:controlCharacters];
}
BOOL MIKSetStringPropertyOnMIDIObject(MIDIObjectRef object, CFStringRef propertyID, NSString *string, NSError *__autoreleasing*error)
{
error = error ? error : &(NSError *__autoreleasing){ nil };
OSStatus err = MIDIObjectSetStringProperty(object, propertyID, (__bridge CFStringRef)string);
if (err) {
*error = [NSError errorWithDomain:NSOSStatusErrorDomain code:err userInfo:nil];
return NO;
}
return YES;
}
SInt32 MIKIntegerPropertyFromMIDIObject(MIDIObjectRef object, CFStringRef propertyID, NSError *__autoreleasing*error)
{
error = error ? error : &(NSError *__autoreleasing){ nil };
SInt32 result;
OSStatus err = MIDIObjectGetIntegerProperty(object, propertyID, &result);
if (err) {
*error = [NSError errorWithDomain:NSOSStatusErrorDomain code:err userInfo:nil];
return INT32_MIN;
}
return (SInt32)result;
}
BOOL MIKSetIntegerPropertyFromMIDIObject(MIDIObjectRef object, CFStringRef propertyID, SInt32 integerValue, NSError *__autoreleasing*error)
{
error = error ? error : &(NSError *__autoreleasing){ nil };
OSStatus err = MIDIObjectSetIntegerProperty(object, propertyID, integerValue);
if (err) {
*error = [NSError errorWithDomain:NSOSStatusErrorDomain code:err userInfo:nil];
return NO;
}
return YES;
}
MIDIObjectType MIKMIDIObjectTypeOfObject(MIDIObjectRef object, NSError *__autoreleasing*error)
{
error = error ? error : &(NSError *__autoreleasing){ nil };
MIDIUniqueID uniqueID = MIKIntegerPropertyFromMIDIObject(object, kMIDIPropertyUniqueID, error);
if (*error) return -2;
MIDIObjectRef resultObject;
MIDIObjectType objectType;
OSStatus err = MIDIObjectFindByUniqueID(uniqueID, &resultObject, &objectType);
if (err) {
*error = [NSError errorWithDomain:NSOSStatusErrorDomain code:err userInfo:nil];
return -2;
}
if (resultObject != object) {
*error = [NSError MIKMIDIErrorWithCode:MIKMIDIUnknownErrorCode userInfo:nil];
return -2;
}
return objectType;
}
NSString *MIKMIDIMappingAttributeStringForInteractionType(MIKMIDIResponderType type)
{
NSDictionary *map = @{@(MIKMIDIResponderTypePressReleaseButton) : @"Key",
@(MIKMIDIResponderTypePressButton) : @"Tap",
@(MIKMIDIResponderTypeAbsoluteSliderOrKnob) : @"KnobSlider",
@(MIKMIDIResponderTypeRelativeKnob) : @"JogWheel",
@(MIKMIDIResponderTypeTurntableKnob) : @"TurnTable",
@(MIKMIDIResponderTypeRelativeAbsoluteKnob) : @"RelativeAbsoluteKnob"};
return [map objectForKey:@(type)];
}
MIKMIDIResponderType MIKMIDIMappingInteractionTypeForAttributeString(NSString *string)
{
NSDictionary *map = @{@"Key" : @(MIKMIDIResponderTypePressReleaseButton),
@"Tap" : @(MIKMIDIResponderTypePressButton),
@"KnobSlider" : @(MIKMIDIResponderTypeAbsoluteSliderOrKnob),
@"JogWheel" : @(MIKMIDIResponderTypeRelativeKnob),
@"TurnTable" : @(MIKMIDIResponderTypeTurntableKnob),
@"RelativeAbsoluteKnob" : @(MIKMIDIResponderTypeRelativeAbsoluteKnob)};
return [[map objectForKey:string] integerValue];
}
NSInteger _MIKMIDIStandardLengthOfMessageForCommandType(MIKMIDICommandType commandType)
{
// Result includes status/command type byte
switch (commandType) {
case MIKMIDICommandTypeNoteOff:
case MIKMIDICommandTypeNoteOn:
case MIKMIDICommandTypePolyphonicKeyPressure:
case MIKMIDICommandTypeControlChange:
case MIKMIDICommandTypePitchWheelChange:
case MIKMIDICommandTypeSystemSongPositionPointer:
return 3;
break;
case MIKMIDICommandTypeProgramChange:
case MIKMIDICommandTypeChannelPressure:
case MIKMIDICommandTypeSystemTimecodeQuarterFrame:
case MIKMIDICommandTypeSystemSongSelect:
return 2;
break;
case MIKMIDICommandTypeSystemTuneRequest:
case MIKMIDICommandTypeSystemTimingClock:
case MIKMIDICommandTypeSystemStartSequence:
case MIKMIDICommandTypeSystemContinueSequence:
case MIKMIDICommandTypeSystemStopSequence:
case MIKMIDICommandTypeSystemKeepAlive:
return 1;
break;
case MIKMIDICommandTypeSystemMessage:
case MIKMIDICommandTypeSystemExclusive:
return -1; // No standard length
break;
default:
return NSIntegerMin;
break;
}
}
NSInteger MIKMIDIStandardLengthOfMessageForCommandType(MIKMIDICommandType commandType)
{
NSInteger result = _MIKMIDIStandardLengthOfMessageForCommandType(commandType);
if (result == NSIntegerMin) result = _MIKMIDIStandardLengthOfMessageForCommandType(commandType | 0x0F); // Mask out channel nibble
return result;
}
MIDITimeStamp MIKMIDIGetCurrentTimeStamp()
{
return mach_absolute_time();
}
MIDIPacket MIKMIDIPacketCreate(MIDITimeStamp timeStamp, UInt16 length, MIKArrayOf(NSNumber *) *data /*max length 256*/)
{
MIDIPacket result = {0};
if ([data count] > 256) {
[NSException raise:NSInvalidArgumentException format:@"MIKMIDIPacketCreate()'s data argument must contain 256 or fewer values"];
return result;
}
result.timeStamp = timeStamp;
result.length = length;
for (NSUInteger i=0; i<256; i++) {
if (i >= [data count]) {
result.data[i] = 0;
continue;
}
result.data[i] = [data[i] charValue];
}
return result;
}
MIDIPacket *MIKMIDIPacketCreateFromCommands(MIDITimeStamp timeStamp, MIKArrayOf(MIKMIDICommand *) *commands)
{
NSMutableData *allPacketData = [NSMutableData data];
for (MIKMIDICommand *command in commands) {
[allPacketData appendData:command.data];
}
MIDIPacket *result = malloc(sizeof(MIDIPacket) + allPacketData.length);
result->timeStamp = timeStamp;
result->length = allPacketData.length;
[allPacketData getBytes:result->data length:allPacketData.length];
return result;
}
void MIKMIDIPacketFree(MIDIPacket *packet)
{
free(packet);
}
#pragma mark - Note Utilities
BOOL MIKMIDINoteIsBlackKey(NSInteger noteNumber)
{
NSUInteger scaledNoteNumber = noteNumber % 12;
NSUInteger blackKeys[] = {1, 3, 6, 8, 10};
for (NSUInteger i=0; i < sizeof(blackKeys) / sizeof(NSUInteger); i++) {
if (blackKeys[i] == scaledNoteNumber) { return YES; }
}
return NO;
}
NSString *MIKMIDINoteLetterForMIDINoteNumber(UInt8 noteNumber)
{
NSArray *letters = @[@"C", @"C#", @"D", @"D#", @"E", @"F", @"F#", @"G", @"G#", @"A", @"A#", @"B"];
return [letters objectAtIndex:noteNumber % 12];
}
NSString *MIKMIDINoteLetterAndOctaveForMIDINote(UInt8 noteNumber)
{
NSInteger octave = noteNumber / 12;
return [MIKMIDINoteLetterForMIDINoteNumber(noteNumber) stringByAppendingFormat:@"%ld", (long)octave];
}