hammerspoon/Pods/MIKMIDI/Source/MIKMIDISystemExclusiveComma...

239 lines
7.4 KiB
Objective-C

//
// MIKMIDISystemExclusiveCommand.m
// MIDI Testbed
//
// Created by Andrew Madsen on 6/2/13.
// Copyright (c) 2013 Mixed In Key. All rights reserved.
//
#import "MIKMIDISystemExclusiveCommand.h"
#import "MIKMIDICommand_SubclassMethods.h"
#import "MIKMIDIUtilities.h"
#if !__has_feature(objc_arc)
#error MIKMIDISystemExclusiveCommand.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDISystemExclusiveCommand.m in the Build Phases for this target
#endif
uint32_t const kMIKMIDISysexNonRealtimeManufacturerID = 0x7E;
uint32_t const kMIKMIDISysexRealtimeManufacturerID = 0x7F;
uint8_t const kMIKMIDISysexChannelDisregard = 0x7F;
uint8_t const kMIKMIDISysexBeginDelimiter = 0xF0;
uint8_t const kMIKMIDISysexEndDelimiter = 0xF7;
@interface MIKMIDISystemExclusiveCommand ()
@property (nonatomic, readwrite) UInt32 manufacturerID;
@property (nonatomic, readwrite) UInt8 sysexChannel;
@property (nonatomic, strong, readwrite) NSData *sysexData;
@end
@implementation MIKMIDISystemExclusiveCommand
{
BOOL _has3ByteManufacturerID;
}
+ (void)load { [super load]; [MIKMIDICommand registerSubclass:self]; }
+ (NSArray *)supportedMIDICommandTypes { return @[@(MIKMIDICommandTypeSystemExclusive)]; }
+ (Class)immutableCounterpartClass; { return [MIKMIDISystemExclusiveCommand class]; }
+ (Class)mutableCounterpartClass; { return [MIKMutableMIDISystemExclusiveCommand class]; }
#pragma mark - Specialized Instances
+ (instancetype)identityRequestCommand
{
MIKMutableMIDISystemExclusiveCommand *identityRequest = [[self mutableCounterpartClass] commandForCommandType:MIKMIDICommandTypeSystemExclusive];
identityRequest.manufacturerID = kMIKMIDISysexNonRealtimeManufacturerID;
identityRequest.sysexChannel = kMIKMIDISysexChannelDisregard;
identityRequest.sysexData = [NSData dataWithBytes:(UInt8[]){0x06, 0x01} length:2];
return identityRequest;
}
#pragma mark - Private
#pragma mark - Properties
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key
{
NSSet *result = [super keyPathsForValuesAffectingValueForKey:key];
if ([key isEqualToString:@"sysexData"]
|| [key isEqualToString:@"sysexChannel"]
|| [key isEqualToString:@"manufacturerID"]) {
result = [result setByAddingObject:@"internalData"];
}
return result;
}
- (id)initWithMIDIPacket:(MIDIPacket *)packet
{
self = [super initWithMIDIPacket:packet];
if (self) {
if (packet) {
if ([self.internalData length] > 1) {
UInt8 firstByte = self.dataByte1;
if (firstByte == 0) {
_has3ByteManufacturerID = YES;
if ([self.internalData length] < 4) [self.internalData increaseLengthBy:4-[self.internalData length]];
}
}
} else {
UInt8 manufacturerID = kMIKMIDISysexNonRealtimeManufacturerID;
[self.internalData replaceBytesInRange:NSMakeRange(1, 1) withBytes:&manufacturerID length:1];
}
}
return self;
}
- (id)initWithRawData:(NSData *)data timeStamp:(MIDITimeStamp)timeStamp
{
self = [super initWithMIDIPacket:NULL];
if (self) {
self.midiTimestamp = timeStamp;
self.internalData = data.mutableCopy;
}
return self;
}
- (UInt32)manufacturerID
{
if ([self.internalData length] < 2) return 0;
NSUInteger manufacturerIDLength = _has3ByteManufacturerID ? 3 : 1;
NSData *idData = [self.internalData subdataWithRange:NSMakeRange(1, manufacturerIDLength)];
UInt8 *bytes = (UInt8 *)[idData bytes];
if (manufacturerIDLength == 1) { return bytes[0]; }
return (UInt32)(bytes[0] << 16 | bytes[1] << 8 | bytes[2]);
}
- (void)setManufacturerID:(UInt32)manufacturerID
{
if (![[self class] isMutable]) return MIKMIDI_RAISE_MUTATION_ATTEMPT_EXCEPTION;
NSUInteger numExistingBytes = _has3ByteManufacturerID ? 3 : 1;
NSUInteger numNewBytes = (manufacturerID & 0xFFFF00) != 0 ? 3 : 1;
manufacturerID = CFSwapInt32HostToBig(manufacturerID);
NSUInteger numRequiredBytes = MAX(numExistingBytes, numNewBytes) + 1;
if ([self.internalData length] < numRequiredBytes) [self.internalData increaseLengthBy:numRequiredBytes-[self.internalData length]];
UInt8 *replacementBytes = (UInt8 *)(&manufacturerID) + 4 - numNewBytes;
[self.internalData replaceBytesInRange:NSMakeRange(1, numExistingBytes) withBytes:replacementBytes length:numNewBytes];
_has3ByteManufacturerID = (numNewBytes == 3);
}
- (BOOL)isUniversal
{
UInt8 firstByte = self.dataByte1;
return (firstByte == kMIKMIDISysexRealtimeManufacturerID
|| firstByte == kMIKMIDISysexNonRealtimeManufacturerID);
}
- (NSUInteger)sysexChannelLocation
{
return _has3ByteManufacturerID ? 4 : 2;
}
- (UInt8)sysexChannel
{
if ([self.internalData length] < 3 || !self.isUniversal) return 0;
NSRange sysexChannelRange = NSMakeRange([self sysexChannelLocation], 1);
NSData *sysexChannelData = [self.internalData subdataWithRange:sysexChannelRange];
return *(UInt8 *)[sysexChannelData bytes];
}
- (void)setSysexChannel:(UInt8)sysexChannel
{
if (![[self class] isMutable]) return MIKMIDI_RAISE_MUTATION_ATTEMPT_EXCEPTION;
if (!self.isUniversal) { return; }
NSUInteger sysexChannelLocation = [self sysexChannelLocation];
NSUInteger requiredLength = MAX(sysexChannelLocation + 1, self.internalData.length);
[self.internalData setLength:requiredLength];
[self.internalData replaceBytesInRange:NSMakeRange(sysexChannelLocation, 1) withBytes:&sysexChannel length:1];
}
- (NSUInteger)sysexDataStartLocation
{
NSUInteger sysexStartLocation = _has3ByteManufacturerID ? 4 : 2;
if (self.isUniversal) {
sysexStartLocation++;
}
return sysexStartLocation;
}
- (NSData *)sysexData
{
NSUInteger sysexStartLocation = [self sysexDataStartLocation];
NSInteger length = MAX(0u, [self.data length]-sysexStartLocation-1);
if ([self.data length] < length + sysexStartLocation) { return [NSData data]; }
return [self.data subdataWithRange:NSMakeRange(sysexStartLocation, length)];
}
- (void)setSysexData:(NSData *)sysexData
{
if (![[self class] isMutable]) return MIKMIDI_RAISE_MUTATION_ATTEMPT_EXCEPTION;
NSUInteger sysexStartLocation = [self sysexDataStartLocation];
NSRange destinationRange = NSMakeRange(sysexStartLocation, [self.internalData length] - sysexStartLocation);
[self.internalData replaceBytesInRange:destinationRange withBytes:[sysexData bytes] length:[sysexData length]];
}
- (NSData *)data
{
NSMutableData *result = [[super data] mutableCopy];
UInt8 lastByte;
[result getBytes:&lastByte range:NSMakeRange([result length]-1, 1)];
if (lastByte != kMIKMIDISysexEndDelimiter) {
[result appendBytes:&(UInt8){kMIKMIDISysexEndDelimiter} length:1];
}
return result;
}
- (void)setData:(NSData *)data
{
if (![[self class] isMutable]) return MIKMIDI_RAISE_MUTATION_ATTEMPT_EXCEPTION;
if (![data length]) return [self setInternalData:[data mutableCopy]];
UInt8 *bytes = (UInt8 *)[data bytes];
UInt8 lastByte = bytes[[data length]-1];
if (lastByte == kMIKMIDISysexEndDelimiter) {
data = [data subdataWithRange:NSMakeRange(0, [data length]-1)];
}
self.internalData = [data mutableCopy];
}
- (NSString *)additionalCommandDescription
{
return [NSString stringWithFormat:@"universal: %@ sysexChannel: %u", @(self.isUniversal), self.sysexChannel];
}
@end
@implementation MIKMutableMIDISystemExclusiveCommand
+ (BOOL)isMutable { return YES; }
#pragma mark - Properties
// One of the super classes already implements a getter *and* setter for these. @dynamic keeps the compiler happy.
@dynamic manufacturerID;
@dynamic sysexChannel;
@dynamic sysexData;
@dynamic timestamp;
@dynamic commandType;
@dynamic dataByte1;
@dynamic dataByte2;
@dynamic midiTimestamp;
@dynamic data;
@end