hammerspoon/Pods/MIKMIDI/Source/MIKMIDIEvent.m

297 lines
9.8 KiB
Objective-C

//
// MIKMIDIEvent.m
// MIDI Files Testbed
//
// Created by Jake Gundersen on 5/21/14.
// Copyright (c) 2014 Mixed In Key. All rights reserved.
//
#import "MIKMIDIEvent.h"
#import "MIKMIDIEvent_SubclassMethods.h"
#import "MIKMIDIMetaEvent.h"
#import "MIKMIDIUtilities.h"
#if !__has_feature(objc_arc)
#error MIKMIDIEvent.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIEvent.m in the Build Phases for this target
#endif
static NSMutableSet *registeredMIKMIDIEventSubclasses;
@implementation MIKMIDIEvent
+ (BOOL)supportsMIKMIDIEventType:(MIKMIDIEventType)type { return [[self supportedMIDIEventTypes] containsObject:@(type)]; }
+ (NSArray *)supportedMIDIEventTypes { return @[]; }
+ (Class)immutableCounterpartClass; { return [MIKMIDIEvent class]; }
+ (Class)mutableCounterpartClass; { return [MIKMutableMIDIEvent class]; }
+ (BOOL)isMutable { return NO; }
+ (NSData *)initialData { return [NSData data]; }
+ (instancetype)midiEventWithTimeStamp:(MusicTimeStamp)timeStamp eventType:(MusicEventType)eventType data:(NSData *)data
{
MIKMIDIEventType midiEventType = [[self class] mikEventTypeForMusicEventType:eventType andData:data];
// -initWithTimeStamp:midiEventType:data: will do subclass lookup too, but this way we avoid a second alloc
Class subclass = [[self class] subclassForEventType:midiEventType];
if (!subclass) subclass = self;
if ([self isMutable]) subclass = [subclass mutableCounterpartClass];
return [[subclass alloc] initWithTimeStamp:timeStamp midiEventType:midiEventType data:data];
}
- (id)init
{
MIKMIDIEventType eventType = (MIKMIDIEventType)[[[[self class] supportedMIDIEventTypes] firstObject] unsignedIntegerValue];
return [self initWithTimeStamp:0 midiEventType:eventType data:nil];
}
- (instancetype)initWithTimeStamp:(MusicTimeStamp)timeStamp midiEventType:(MIKMIDIEventType)eventType data:(NSData *)data
{
// If we don't directly support eventType, return an instance of an MIKMIDIEvent subclass that does.
if (![[[self class] supportedMIDIEventTypes] containsObject:@(eventType)]) {
BOOL isMutable = [[self class] isMutable];
Class subclass = [[self class] subclassForEventType:eventType];
if (!subclass) subclass = [MIKMIDIEvent class];
if (isMutable) subclass = [subclass mutableCounterpartClass];
self = [subclass alloc];
}
self = [super init];
if (self) {
_timeStamp = timeStamp;
_eventType = eventType;
if (!data) data = [[self class] initialData];
_internalData = [NSMutableData dataWithData:data];
}
return self;
}
- (instancetype)initWithTimeStamp:(MusicTimeStamp)timeStamp midiEventType:(MIKMIDIEventType)eventType
{
return [self initWithTimeStamp:timeStamp midiEventType:eventType data:nil];
}
- (NSString *)additionalEventDescription
{
return @"";
}
- (NSString *)description
{
NSString *additionalDescription = [self additionalEventDescription];
if ([additionalDescription length] > 0) {
additionalDescription = [NSString stringWithFormat:@"%@ ", additionalDescription];
}
return [NSString stringWithFormat:@"%@ Timestamp: %f Type: %u, %@", [super description], self.timeStamp, (unsigned int)self.eventType, additionalDescription];
}
#pragma mark - Equality
- (BOOL)isEqual:(id)object
{
if (object == self) return YES;
if (![object isKindOfClass:[MIKMIDIEvent class]]) return NO;
MIKMIDIEvent *otherEvent = (MIKMIDIEvent *)object;
if (otherEvent.eventType != self.eventType) return NO;
return (self.timeStamp == otherEvent.timeStamp && [self.internalData isEqualToData:otherEvent.internalData]);
}
- (NSUInteger)hash
{
MusicTimeStamp timestamp = self.timeStamp;
if (timestamp == 0) return [self.data hash];
return (NSUInteger)(timestamp * [self.data hash]);
}
#pragma mark - Private
#pragma mark Subclass Management
+ (void)registerSubclass:(Class)subclass;
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
registeredMIKMIDIEventSubclasses = [[NSMutableSet alloc] init];
});
[registeredMIKMIDIEventSubclasses addObject:subclass];
[self cacheSubclassesByEvent];
}
+ (void)unregisterSubclass:(Class)subclass;
{
[registeredMIKMIDIEventSubclasses removeObject:subclass];
[self cacheSubclassesByEvent];
}
+ (NSMutableDictionary *)subclassesByEventCache
{
static NSMutableDictionary *subclassesByEventCache = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{ subclassesByEventCache = [[NSMutableDictionary alloc] init]; });
return subclassesByEventCache;
}
+ (void)cacheSubclassesByEvent
{
[[self subclassesByEventCache] removeAllObjects];
// Regenerate cache
for (Class eachSubclass in registeredMIKMIDIEventSubclasses) {
for (NSNumber *eventType in [eachSubclass supportedMIDIEventTypes]) {
[[self subclassesByEventCache] setObject:eachSubclass forKey:eventType];
}
}
}
+ (MIKMIDIEventType)mikEventTypeForMusicEventType:(MusicEventType)musicEventType andData:(NSData *)data
{
static NSDictionary *channelEventTypeToMIDITypeMap = nil;
static NSDictionary *musicEventToMIDITypeMap = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
channelEventTypeToMIDITypeMap = @{@(MIKMIDIChannelEventTypePolyphonicKeyPressure) : @(MIKMIDIEventTypeMIDIPolyphonicKeyPressureMessage),
@(MIKMIDIChannelEventTypeControlChange) : @(MIKMIDIEventTypeMIDIControlChangeMessage),
@(MIKMIDIChannelEventTypeProgramChange) : @(MIKMIDIEventTypeMIDIProgramChangeMessage),
@(MIKMIDIChannelEventTypeChannelPressure) : @(MIKMIDIEventTypeMIDIChannelPressureMessage),
@(MIKMIDIChannelEventTypePitchBendChange) : @(MIKMIDIEventTypeMIDIPitchBendChangeMessage)};
musicEventToMIDITypeMap = @{@(kMusicEventType_NULL) : @(MIKMIDIEventTypeNULL),
@(kMusicEventType_ExtendedNote) : @(MIKMIDIEventTypeExtendedNote),
@(kMusicEventType_ExtendedTempo) : @(MIKMIDIEventTypeExtendedTempo),
@(kMusicEventType_User) : @(MIKMIDIEventTypeUser),
@(kMusicEventType_Meta) : @(MIKMIDIEventTypeMeta),
@(kMusicEventType_MIDINoteMessage) : @(MIKMIDIEventTypeMIDINoteMessage),
@(kMusicEventType_MIDIChannelMessage) : @(MIKMIDIEventTypeMIDIChannelMessage),
@(kMusicEventType_MIDIRawData) : @(MIKMIDIEventTypeMIDIRawData),
@(kMusicEventType_Parameter) : @(MIKMIDIEventTypeParameter),
@(kMusicEventType_AUPreset) : @(MIKMIDIEventTypeAUPreset),};
});
if (musicEventType == kMusicEventType_Meta) {
MIKMIDIMetaEventType metaEventType = *(MIKMIDIMetaEventType *)[data bytes];
return [MIKMIDIMetaEvent eventTypeForMetaSubtype:metaEventType];
} else if (musicEventType == kMusicEventType_MIDIChannelMessage) {
UInt8 channelEventType = *(UInt8 *)[data bytes] & 0xF0;
return [channelEventTypeToMIDITypeMap[@(channelEventType)] unsignedIntegerValue];
} else {
return [musicEventToMIDITypeMap[@(musicEventType)] unsignedIntegerValue];
}
}
+ (Class)subclassForEventType:(MIKMIDIEventType)eventType
{
Class result = [[self subclassesByEventCache] objectForKey:@(eventType)];
if (result) return result;
for (Class subclass in registeredMIKMIDIEventSubclasses) {
if ([[subclass supportedMIDIEventTypes] containsObject:@(eventType)]) {
result = subclass;
break;
}
}
return result;
}
+ (Class)subclassForMusicEventType:(MusicEventType)eventType andData:(NSData *)data
{
MIKMIDIEventType midiEventType = [[self class] mikEventTypeForMusicEventType:eventType andData:data];
return [self subclassForEventType:midiEventType];
}
#pragma mark - NSCopying
- (id)copyWithZone:(NSZone *)zone
{
Class copyClass = [[self class] immutableCounterpartClass];
MIKMIDIEvent *result = [[copyClass alloc] init];
result.internalData = self.internalData;
result->_eventType = self.eventType;
result->_timeStamp = self.timeStamp;
return result;
}
- (id)mutableCopy
{
Class copyClass = [[self class] mutableCounterpartClass];
MIKMutableMIDIEvent *result = [[copyClass alloc] init];
result.internalData = self.internalData;
result.eventType = self.eventType;
result.timeStamp = self.timeStamp;
return result;
}
#pragma mark - Properties
+ (NSSet *)keyPathsForValuesAffectingInternalData
{
return [NSSet set];
}
+ (NSSet *)keyPathsForValuesAffectingData
{
return [NSSet setWithObject:@"internalData"];
}
- (NSData *)data { return [self.internalData copy]; }
- (void)setData:(NSData *)data
{
if (![[self class] isMutable]) return MIKMIDI_RAISE_MUTATION_ATTEMPT_EXCEPTION;
self.internalData = data ? [data mutableCopy] : [NSMutableData data];
}
- (void)setTimeStamp:(MusicTimeStamp)timeStamp
{
if (![[self class] isMutable]) return MIKMIDI_RAISE_MUTATION_ATTEMPT_EXCEPTION;
_timeStamp = timeStamp;
}
- (void)setInternalData:(NSMutableData *)internalData
{
if (internalData != _internalData) {
_internalData = internalData ? [internalData mutableCopy] : [NSMutableData data];
}
}
@end
@implementation MIKMutableMIDIEvent
+ (BOOL)isMutable { return YES; }
@dynamic eventType;
@dynamic data;
@dynamic timeStamp;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-implementations"
+ (BOOL)supportsMIKMIDIEventType:(MIKMIDIEventType)type { return [[self immutableCounterpartClass] supportsMIKMIDIEventType:type]; }
#pragma clang diagnostic pop
@end
#pragma mark - MIKMIDICommand+MIKMIDIEventToCommands
#import "MIKMIDIClock.h"
#import "MIKMIDINoteEvent.h"
#import "MIKMIDIChannelEvent.h"
@implementation MIKMIDICommand (MIKMIDIEventToCommands)
+ (NSArray *)commandsFromMIDIEvent:(MIKMIDIEvent *)event clock:(MIKMIDIClock *)clock
{
NSMutableArray *result = [NSMutableArray array];
if ([event isKindOfClass:[MIKMIDINoteEvent class]]) {
NSArray *commands = [MIKMIDICommand commandsFromNoteEvent:(MIKMIDINoteEvent *)event clock:clock];
if (commands) [result addObjectsFromArray:commands];
} else if ([event isKindOfClass:[MIKMIDIChannelEvent class]]) {
MIKMIDICommand *command = [MIKMIDICommand commandFromChannelEvent:(MIKMIDIChannelEvent *)event clock:clock];
if (command) [result addObject:command];
}
return result;
}
@end