From ee7a77a63c889ae6f315180b2c8a1cf4eba4f69d Mon Sep 17 00:00:00 2001 From: Hugo Trippaers Date: Thu, 13 Dec 2018 22:50:01 +0100 Subject: [PATCH 1/5] Introduce the SMShowControlMessage --- Frameworks/SnoizeMIDI/SMMessageParser.m | 7 +- Frameworks/SnoizeMIDI/SMShowControlMessage.h | 19 ++ Frameworks/SnoizeMIDI/SMShowControlMessage.m | 210 ++++++++++++++++++ .../SnoizeMIDI/ShowControlCommandNames.plist | 74 ++++++ .../SnoizeMIDI.xcodeproj/project.pbxproj | 205 +++++++++++++++++ .../SnoizeMIDI/SnoizeMIDITests/Info.plist | 22 ++ .../SMShowControlMessageTest.m | 69 ++++++ 7 files changed, 605 insertions(+), 1 deletion(-) create mode 100644 Frameworks/SnoizeMIDI/SMShowControlMessage.h create mode 100644 Frameworks/SnoizeMIDI/SMShowControlMessage.m create mode 100644 Frameworks/SnoizeMIDI/ShowControlCommandNames.plist create mode 100644 Frameworks/SnoizeMIDI/SnoizeMIDITests/Info.plist create mode 100644 Frameworks/SnoizeMIDI/SnoizeMIDITests/SMShowControlMessageTest.m diff --git a/Frameworks/SnoizeMIDI/SMMessageParser.m b/Frameworks/SnoizeMIDI/SMMessageParser.m index 1eb05e36..d33445ef 100644 --- a/Frameworks/SnoizeMIDI/SMMessageParser.m +++ b/Frameworks/SnoizeMIDI/SMMessageParser.m @@ -18,6 +18,7 @@ #import "SMSystemCommonMessage.h" #import "SMSystemRealTimeMessage.h" #import "SMSystemExclusiveMessage.h" +#import "SMShowControlMessage.h" #import "SMInvalidMessage.h" @@ -341,7 +342,11 @@ - (SMSystemExclusiveMessage *)finishSysExMessageWithValidEnd:(BOOL)isEndValid; // 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]; + if (((uint8_t *)readingSysExData.mutableBytes)[2] == 0x02) { // MIDI Show Control Message + message = [SMShowControlMessage showControlMessageWithTimeStamp:startSysExTimeStamp data:readingSysExData]; + } else { + message = [SMSystemExclusiveMessage systemExclusiveMessageWithTimeStamp:startSysExTimeStamp data:readingSysExData]; + } [readingSysExData release]; readingSysExData = nil; diff --git a/Frameworks/SnoizeMIDI/SMShowControlMessage.h b/Frameworks/SnoizeMIDI/SMShowControlMessage.h new file mode 100644 index 00000000..ca8719cb --- /dev/null +++ b/Frameworks/SnoizeMIDI/SMShowControlMessage.h @@ -0,0 +1,19 @@ +// +// SMShowControlMessage.h +// SnoizeMIDI +// +// Created by Hugo Trippaers on 11/12/2018. +// + +#import +#import +#import + +@interface SMShowControlMessage : SMSystemExclusiveMessage +{ + Byte mscCommand; +} + ++ (SMShowControlMessage *)showControlMessageWithTimeStamp:(MIDITimeStamp)aTimeStamp data:(NSData *)aData; + +@end diff --git a/Frameworks/SnoizeMIDI/SMShowControlMessage.m b/Frameworks/SnoizeMIDI/SMShowControlMessage.m new file mode 100644 index 00000000..58d6fcac --- /dev/null +++ b/Frameworks/SnoizeMIDI/SMShowControlMessage.m @@ -0,0 +1,210 @@ +// +// SMShowControlMessage.m +// SnoizeMIDI +// +// Created by Hugo Trippaers on 11/12/2018. +// + +#import "SMShowControlMessage.h" + +#import "SMUtilities.h" + +typedef NS_ENUM(NSInteger, SMShowControlDataType) { + unknown = 1, // Yet undefined data + cue_path = 2, // optional Cue, optional List, optional Path + cue_path_with_timestamp = 3, // timestamp, optional Cue, optional List, optional Path + no_data = 4 // No additional data +}; + +typedef struct { + NSString *cueNumber; + NSString *cueList; + NSString *cuePath; +} Cue; + +@interface SMShowControlMessage (Private) + ++ (NSString *)nameForShowControlCommand:(Byte)mscCommand; + +@end + +@implementation SMShowControlMessage : SMSystemExclusiveMessage + ++ (SMShowControlMessage *)showControlMessageWithTimeStamp:(MIDITimeStamp)aTimeStamp data:(NSData *)aData +{ + SMShowControlMessage *message; + + message = [[[SMShowControlMessage alloc] initWithTimeStamp:aTimeStamp statusByte:0xF0] autorelease]; + [message setData:aData]; + [message parseShowControl:aData]; + + return message; +} + +- (void)encodeWithCoder:(NSCoder *)coder +{ + [super encodeWithCoder:coder]; + [coder encodeObject:data forKey:@"data"]; + [coder encodeBool:[self wasReceivedWithEOX] forKey:@"wasReceivedWithEOX"]; +} + +- (id)initWithCoder:(NSCoder *)decoder +{ + if ((self = [super initWithCoder:decoder])) { + id obj = [decoder decodeObjectForKey:@"data"]; + if (obj && [obj isKindOfClass:[NSData class]]) { + data = [obj retain]; + } else { + goto fail; + } + + [self setWasReceivedWithEOX:[decoder decodeBoolForKey:@"wasReceivedWithEOX"]]; + [self parseShowControl:data]; + } + + return self; + +fail: + [self release]; + return nil; +} + +- (NSString *)typeForDisplay; +{ + return NSLocalizedStringFromTableInBundle(@"Show Control", @"SnoizeMIDI", SMBundleForObject(self), "displayed type of System Exclusive Show Control event"); +} + +- (NSString *)dataForDisplay; +{ + NSString *dataString = [self expertDataForDisplay]; + + NSMutableString *result = [NSMutableString string]; + + NSString *command = [SMShowControlMessage nameForShowControlCommand:mscCommand]; + [result appendString:command]; + + SMShowControlDataType dataType = [SMShowControlMessage dataTypeForCommand:mscCommand]; + + if (dataType == cue_path) { + Cue cue = [SMShowControlMessage parseCue:[self otherData]]; + if (cue.cueNumber != nil) { + [result appendFormat:@" Cue %@", cue.cueNumber]; + } + if (cue.cueList != nil) { + [result appendFormat:@", List %@", cue.cueList]; + } + if (cue.cuePath != nil) { + [result appendFormat:@", Path %@", cue.cuePath]; + } + } else if (dataType == unknown){ + if (dataString) { + if (result.length > 0) { + [result appendString:@"\t"]; + } + [result appendString:dataString]; + } + } + + return result; +} + +- (void)setData:(NSData *)newData; +{ + if (data != newData) { + [data release]; + data = [newData retain]; + + [cachedDataWithEOX release]; + cachedDataWithEOX = nil; + } +} + +- (void)parseShowControl:(NSData *)newData; +{ + mscCommand = (((Byte *)newData.bytes)[4]); +} + ++ (NSString *)nameForShowControlCommand:(Byte)mscCommand; +{ + static NSDictionary *showControlCommands = nil; + NSString *identifierString, *name; + + if (!showControlCommands) { + NSString *path; + + path = [SMBundleForObject(self) pathForResource:@"ShowControlCommandNames" ofType:@"plist"]; + if (path) { + showControlCommands = [NSDictionary dictionaryWithContentsOfFile:path]; + if (!showControlCommands) + NSLog(@"Couldn't read ShowControlCommandNames.plist!"); + } else { + NSLog(@"Couldn't find ShowControlCommandNames.plist!"); + } + + if (!showControlCommands) + showControlCommands = [NSDictionary dictionary]; + [showControlCommands retain]; + } + + identifierString = [NSString stringWithFormat:@"%X", mscCommand]; + if ((name = [showControlCommands objectForKey:identifierString])) + return name; + else + return NSLocalizedStringFromTableInBundle(@"Unknown Command", @"SnoizeMIDI", SMBundleForObject(self), "unknown command name"); +} + ++ (SMShowControlDataType)dataTypeForCommand:(Byte)mscCommand; +{ + switch(mscCommand) { + case 0x01: + case 0x02: + case 0x03: + case 0x05: + case 0x0B: + case 0x10: + return cue_path; + case 0x04: + return cue_path_with_timestamp; + case 0x08: + case 0x09: + case 0x0A: + return no_data; + default: + return unknown; + } +} + ++ (Cue)parseCue:(NSData *)data; +{ + Cue parsedCue; + parsedCue.cueList = nil; + parsedCue.cueNumber = nil; + parsedCue.cuePath = nil; + + const Byte *cueData = data.bytes + 5; + if (*cueData == 0xF7) { + // All empty + return parsedCue; + } + + NSMutableString *cueItem = [[NSMutableString alloc] init]; + while (cueData != data.bytes + [data length]) { + Byte thingy = *cueData++; + if (thingy == 0x0 || (thingy == 0xF7 && [cueItem length] > 0)) { + if (parsedCue.cueNumber == nil) { + parsedCue.cueNumber = [[NSString alloc] initWithString:cueItem]; + } else if (parsedCue.cueList == nil) { + parsedCue.cueList = [[NSString alloc] initWithString:cueItem]; + } else { + parsedCue.cuePath = [[NSString alloc] initWithString:cueItem]; + } + cueItem = [[NSMutableString alloc] init]; + } else { + [cueItem appendFormat:@"%c", thingy]; + } + } + + return parsedCue; +} + +@end diff --git a/Frameworks/SnoizeMIDI/ShowControlCommandNames.plist b/Frameworks/SnoizeMIDI/ShowControlCommandNames.plist new file mode 100644 index 00000000..4ddff94a --- /dev/null +++ b/Frameworks/SnoizeMIDI/ShowControlCommandNames.plist @@ -0,0 +1,74 @@ + + + + + 0 + Reserved for extensions + 1 + GO + 2 + STOP + 3 + RESUME + 4 + TIMED_GO + 5 + LOAD + 6 + SET + 7 + FIRE + 8 + ALL_OFF + 9 + RESTORE + A + RESET + B + GO_OFF + 10 + GO/JAM_CLOCK + 11 + STANDBY_+ + 12 + STANDBY_- + 13 + SEQUENCE_+ + 14 + SEQUENCE_- + 15 + START_CLOCK + 16 + STOP_CLOCK + 17 + ZERO_CLOCK + 18 + SET_CLOCK + 19 + MTC_CHASE_ON + 1A + MTC_CHASE_OFF + 1B + OPEN_CUE_LIST + 1C + CLOSE_CUE_LIST + 1D + OPEN_CUE_PATH + 1E + CLOSE_CUE_PATH + 20 + STANDBY + 21 + STANDING_BY + 22 + GO_2PC + 23 + COMPLETE + 24 + CANCEL + 25 + CANCELLED + 26 + ABORT + + diff --git a/Frameworks/SnoizeMIDI/SnoizeMIDI.xcodeproj/project.pbxproj b/Frameworks/SnoizeMIDI/SnoizeMIDI.xcodeproj/project.pbxproj index 5b084db7..9354c893 100644 --- a/Frameworks/SnoizeMIDI/SnoizeMIDI.xcodeproj/project.pbxproj +++ b/Frameworks/SnoizeMIDI/SnoizeMIDI.xcodeproj/project.pbxproj @@ -74,6 +74,10 @@ 16B11DF20971D80F00DB1DB5 /* CoreMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 16B11DF00971D80F00DB1DB5 /* CoreMIDI.framework */; }; 16B11DF30971D81400DB1DB5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0867D69BFE84028FC02AAC07 /* Foundation.framework */; }; 8DC2EF530486A6940098B216 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C1666FE841158C02AAC07 /* InfoPlist.strings */; }; + 9A6D1B1621C0408500D9872C /* SMShowControlMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A6D1B1521C0408500D9872C /* SMShowControlMessage.m */; }; + 9A6D1B1821C0605C00D9872C /* ShowControlCommandNames.plist in Resources */ = {isa = PBXBuildFile; fileRef = 9A6D1B1721C0605C00D9872C /* ShowControlCommandNames.plist */; }; + 9A6D1B2521C2F65B00D9872C /* SnoizeMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8DC2EF5B0486A6940098B216 /* SnoizeMIDI.framework */; }; + 9A6D1B2B21C2F67100D9872C /* SMShowControlMessageTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A6D1B1A21C2F53000D9872C /* SMShowControlMessageTest.m */; }; A53DDB9A148F4219006D2FFD /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A53DDB99148F4219006D2FFD /* Foundation.framework */; }; A53DDBDF148F4349006D2FFD /* SMClient.m in Sources */ = {isa = PBXBuildFile; fileRef = 16B11B5E0971D40100DB1DB5 /* SMClient.m */; }; A53DDBE0148F434C006D2FFD /* SMHostTime.m in Sources */ = {isa = PBXBuildFile; fileRef = 16B11B660971D40100DB1DB5 /* SMHostTime.m */; }; @@ -201,6 +205,16 @@ A581999816AF699100325CF3 /* CoreMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A581999716AF699100325CF3 /* CoreMIDI.framework */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 9A6D1B2621C2F65B00D9872C /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8DC2EF4F0486A6940098B216; + remoteInfo = SnoizeMIDI; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXFileReference section */ 0867D69BFE84028FC02AAC07 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; 089C1667FE841158C02AAC07 /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -278,6 +292,12 @@ 32DBCF5E0370ADEE00C91783 /* SnoizeMIDI_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SnoizeMIDI_Prefix.pch; sourceTree = ""; }; 8DC2EF5A0486A6940098B216 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 8DC2EF5B0486A6940098B216 /* SnoizeMIDI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SnoizeMIDI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9A6D1B1421C03FE800D9872C /* SMShowControlMessage.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SMShowControlMessage.h; sourceTree = ""; }; + 9A6D1B1521C0408500D9872C /* SMShowControlMessage.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SMShowControlMessage.m; sourceTree = ""; }; + 9A6D1B1721C0605C00D9872C /* ShowControlCommandNames.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = ShowControlCommandNames.plist; sourceTree = ""; }; + 9A6D1B1A21C2F53000D9872C /* SMShowControlMessageTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SMShowControlMessageTest.m; sourceTree = ""; }; + 9A6D1B2021C2F65B00D9872C /* SnoizeMIDITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SnoizeMIDITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 9A6D1B2421C2F65B00D9872C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; A53DDB97148F4219006D2FFD /* libSnoizeMIDIiOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libSnoizeMIDIiOS.a; sourceTree = BUILT_PRODUCTS_DIR; }; A53DDB99148F4219006D2FFD /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; A53DDB9D148F4219006D2FFD /* SnoizeMIDIiOS-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SnoizeMIDIiOS-Prefix.pch"; sourceTree = ""; }; @@ -295,6 +315,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 9A6D1B1D21C2F65B00D9872C /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9A6D1B2521C2F65B00D9872C /* SnoizeMIDI.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; A53DDB94148F4219006D2FFD /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -321,6 +349,7 @@ 8DC2EF5B0486A6940098B216 /* SnoizeMIDI.framework */, A53DDB97148F4219006D2FFD /* libSnoizeMIDIiOS.a */, A581997216AF687400325CF3 /* libSnoizeMIDI.a */, + 9A6D1B2021C2F65B00D9872C /* SnoizeMIDITests.xctest */, ); name = Products; sourceTree = ""; @@ -332,6 +361,7 @@ 089C1665FE841158C02AAC07 /* Resources */, 0867D69AFE84028FC02AAC07 /* External Frameworks and Libraries */, 161D6C970972112200CA5276 /* Configurations */, + 9A6D1B2121C2F65B00D9872C /* SnoizeMIDITests */, A53DDB98148F4219006D2FFD /* Frameworks */, 034768DFFF38A50411DB9C8B /* Products */, ); @@ -355,6 +385,7 @@ 16B11BF40971D78500DB1DB5 /* SnoizeMIDI.strings */, 16B11BEE0971D77600DB1DB5 /* ControllerNames.plist */, 16B11BF00971D77600DB1DB5 /* ManufacturerNames.plist */, + 9A6D1B1721C0605C00D9872C /* ShowControlCommandNames.plist */, ); name = Resources; sourceTree = ""; @@ -455,6 +486,8 @@ 16B11B6C0971D40100DB1DB5 /* SMInvalidMessage.m */, 1648765E0E6D2A22002CF387 /* SMMessageTimeBase.h */, 1648765F0E6D2A22002CF387 /* SMMessageTimeBase.m */, + 9A6D1B1421C03FE800D9872C /* SMShowControlMessage.h */, + 9A6D1B1521C0408500D9872C /* SMShowControlMessage.m */, ); name = Messages; sourceTree = ""; @@ -514,6 +547,15 @@ name = Processors; sourceTree = ""; }; + 9A6D1B2121C2F65B00D9872C /* SnoizeMIDITests */ = { + isa = PBXGroup; + children = ( + 9A6D1B1A21C2F53000D9872C /* SMShowControlMessageTest.m */, + 9A6D1B2421C2F65B00D9872C /* Info.plist */, + ); + path = SnoizeMIDITests; + sourceTree = ""; + }; A53DDB98148F4219006D2FFD /* Frameworks */ = { isa = PBXGroup; children = ( @@ -665,6 +707,24 @@ productReference = 8DC2EF5B0486A6940098B216 /* SnoizeMIDI.framework */; productType = "com.apple.product-type.framework"; }; + 9A6D1B1F21C2F65B00D9872C /* SnoizeMIDITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 9A6D1B2821C2F65B00D9872C /* Build configuration list for PBXNativeTarget "SnoizeMIDITests" */; + buildPhases = ( + 9A6D1B1C21C2F65B00D9872C /* Sources */, + 9A6D1B1D21C2F65B00D9872C /* Frameworks */, + 9A6D1B1E21C2F65B00D9872C /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 9A6D1B2721C2F65B00D9872C /* PBXTargetDependency */, + ); + name = SnoizeMIDITests; + productName = SnoizeMIDITests; + productReference = 9A6D1B2021C2F65B00D9872C /* SnoizeMIDITests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; A53DDB96148F4219006D2FFD /* SnoizeMIDIiOS */ = { isa = PBXNativeTarget; buildConfigurationList = A53DDBA3148F4219006D2FFD /* Build configuration list for PBXNativeTarget "SnoizeMIDIiOS" */; @@ -706,6 +766,13 @@ isa = PBXProject; attributes = { LastUpgradeCheck = 1000; + TargetAttributes = { + 9A6D1B1F21C2F65B00D9872C = { + CreatedOnToolsVersion = 10.1; + DevelopmentTeam = 832NCTF3R5; + ProvisioningStyle = Automatic; + }; + }; }; buildConfigurationList = 1DEB91B108733DA50010E9CD /* Build configuration list for PBXProject "SnoizeMIDI" */; compatibilityVersion = "Xcode 3.2"; @@ -725,6 +792,7 @@ 8DC2EF4F0486A6940098B216 /* SnoizeMIDI */, A53DDB96148F4219006D2FFD /* SnoizeMIDIiOS */, A581992D16AF687400325CF3 /* SnoizeMIDIOSX */, + 9A6D1B1F21C2F65B00D9872C /* SnoizeMIDITests */, ); }; /* End PBXProject section */ @@ -738,6 +806,14 @@ 16B11BF20971D77600DB1DB5 /* ControllerNames.plist in Resources */, 16B11BF30971D77600DB1DB5 /* ManufacturerNames.plist in Resources */, 16B11BF60971D78500DB1DB5 /* SnoizeMIDI.strings in Resources */, + 9A6D1B1821C0605C00D9872C /* ShowControlCommandNames.plist in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9A6D1B1E21C2F65B00D9872C /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( ); runOnlyForDeploymentPostprocessing = 0; }; @@ -762,6 +838,7 @@ 16B11BAB0971D40100DB1DB5 /* SMMessage.m in Sources */, 16B11BAE0971D40100DB1DB5 /* SMMessageFilter.m in Sources */, 16B11BB00971D40100DB1DB5 /* SMMessageHistory.m in Sources */, + 9A6D1B1621C0408500D9872C /* SMShowControlMessage.m in Sources */, 16B11BB20971D40100DB1DB5 /* SMMessageMult.m in Sources */, 16B11BB40971D40100DB1DB5 /* SMMessageParser.m in Sources */, 16B11BB70971D40100DB1DB5 /* SMMIDIObject.m in Sources */, @@ -780,6 +857,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 9A6D1B1C21C2F65B00D9872C /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9A6D1B2B21C2F67100D9872C /* SMShowControlMessageTest.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; A53DDB93148F4219006D2FFD /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -854,6 +939,14 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 9A6D1B2721C2F65B00D9872C /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 8DC2EF4F0486A6940098B216 /* SnoizeMIDI */; + targetProxy = 9A6D1B2621C2F65B00D9872C /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 089C1666FE841158C02AAC07 /* InfoPlist.strings */ = { isa = PBXVariantGroup; @@ -962,6 +1055,109 @@ }; name = Release; }; + 9A6D1B2921C2F65B00D9872C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "Mac Developer"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = 832NCTF3R5; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_FILE = SnoizeMIDITests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.snoize.SnoizeMIDITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + }; + name = Debug; + }; + 9A6D1B2A21C2F65B00D9872C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "Mac Developer"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = 832NCTF3R5; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_FILE = SnoizeMIDITests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.snoize.SnoizeMIDITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + }; + name = Release; + }; A53DDBA1148F4219006D2FFD /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1095,6 +1291,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 9A6D1B2821C2F65B00D9872C /* Build configuration list for PBXNativeTarget "SnoizeMIDITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9A6D1B2921C2F65B00D9872C /* Debug */, + 9A6D1B2A21C2F65B00D9872C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; A53DDBA3148F4219006D2FFD /* Build configuration list for PBXNativeTarget "SnoizeMIDIiOS" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/Frameworks/SnoizeMIDI/SnoizeMIDITests/Info.plist b/Frameworks/SnoizeMIDI/SnoizeMIDITests/Info.plist new file mode 100644 index 00000000..6c40a6cd --- /dev/null +++ b/Frameworks/SnoizeMIDI/SnoizeMIDITests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/Frameworks/SnoizeMIDI/SnoizeMIDITests/SMShowControlMessageTest.m b/Frameworks/SnoizeMIDI/SnoizeMIDITests/SMShowControlMessageTest.m new file mode 100644 index 00000000..62fa911c --- /dev/null +++ b/Frameworks/SnoizeMIDI/SnoizeMIDITests/SMShowControlMessageTest.m @@ -0,0 +1,69 @@ +// +// SMShowControlMessageTest.m +// SnoizeMIDI +// +// Created by Hugo Trippaers on 13/12/2018. +// + +#import + +#import "SMShowControlMessage.h" + +@interface SMShowControlMessageTest : XCTestCase + +@end + +@implementation SMShowControlMessageTest + +- (void)setUp { + // Put setup code here. This method is called before the invocation of each test method in the class. +} + +- (void)tearDown { + // Put teardown code here. This method is called after the invocation of each test method in the class. +} + +- (void)testConstructor { + Byte bytes[] = { 0x7F, 0x00, 0x02, 0x7F, 0x01 }; + NSData *testData = [[NSData alloc] initWithBytes:bytes length:5]; + + SMShowControlMessage *message = [SMShowControlMessage showControlMessageWithTimeStamp:0 data:testData]; + [message setWasReceivedWithEOX:TRUE]; + + XCTAssertNotNil(message); + XCTAssert([message wasReceivedWithEOX] == TRUE); +} + +- (void)testDataForDisplay { + // MSC GO, no cue params + Byte bytes[] = { 0x7F, 0x00, 0x02, 0x7F, 0x01 }; + NSData *testData = [[NSData alloc] initWithBytes:bytes length:5]; + + SMShowControlMessage *message = [SMShowControlMessage showControlMessageWithTimeStamp:0 data:testData]; + + XCTAssertEqualObjects(@"GO", [message dataForDisplay]); +} + +- (void)testDataForDisplayWithCueData { + // MSC GO, all cue params + Byte bytes[] = { 0x7F, 0x00, 0x02, 0x7F, 0x01, 0x31, 0x32, 0x33, 0x00, 0x34, 0x35, 0x36, 0x00, 0x32, 0x33, 0x2E, 0x32 }; + + NSData *testData = [[NSData alloc] initWithBytes:bytes length:17]; + + SMShowControlMessage *message = [SMShowControlMessage showControlMessageWithTimeStamp:0 data:testData]; + + XCTAssertEqualObjects(@"GO Cue 123, List 456, Path 23.2", [message dataForDisplay]); +} + +- (void)testDataForDisplayWithPartialCueData { + // MSC GO, all cue params + Byte bytes[] = { 0x7F, 0x00, 0x02, 0x7F, 0x01, 0x31, 0x32, 0x33, 0x00, 0x32, 0x33, 0x2E, 0x32 }; + + NSData *testData = [[NSData alloc] initWithBytes:bytes length:sizeof bytes]; + + SMShowControlMessage *message = [SMShowControlMessage showControlMessageWithTimeStamp:0 data:testData]; + + XCTAssertEqualObjects(@"GO Cue 123, List 23.2", [message dataForDisplay]); +} + +@end From 9aa942381101d057b7c60a6bf70251e08805d841 Mon Sep 17 00:00:00 2001 From: Hugo Trippaers Date: Sat, 22 Dec 2018 21:40:02 +0100 Subject: [PATCH 2/5] Move parsers to a utility file and complete the parameter parsing for most common shocwontrol messages --- Frameworks/SnoizeMIDI/SMShowControlMessage.m | 203 +++++++++++++----- .../SnoizeMIDI/SMShowControlUtilities.h | 53 +++++ .../SnoizeMIDI/SMShowControlUtilities.m | 70 ++++++ .../SnoizeMIDI.xcodeproj/project.pbxproj | 12 +- .../SMShowControlMessageTest.m | 97 ++++++++- .../SMShowControlUtilitiesTest.m | 94 ++++++++ 6 files changed, 473 insertions(+), 56 deletions(-) create mode 100644 Frameworks/SnoizeMIDI/SMShowControlUtilities.h create mode 100644 Frameworks/SnoizeMIDI/SMShowControlUtilities.m create mode 100644 Frameworks/SnoizeMIDI/SnoizeMIDITests/SMShowControlUtilitiesTest.m diff --git a/Frameworks/SnoizeMIDI/SMShowControlMessage.m b/Frameworks/SnoizeMIDI/SMShowControlMessage.m index 58d6fcac..df864073 100644 --- a/Frameworks/SnoizeMIDI/SMShowControlMessage.m +++ b/Frameworks/SnoizeMIDI/SMShowControlMessage.m @@ -8,26 +8,20 @@ #import "SMShowControlMessage.h" #import "SMUtilities.h" +#import "SMShowControlUtilities.h" typedef NS_ENUM(NSInteger, SMShowControlDataType) { unknown = 1, // Yet undefined data - cue_path = 2, // optional Cue, optional List, optional Path - cue_path_with_timestamp = 3, // timestamp, optional Cue, optional List, optional Path - no_data = 4 // No additional data + cue = 2, // optional Cue, optional List, optional Path + cue_with_timecode = 3, // timecode, optional Cue, optional List, optional Path + no_data = 4, // No additional data + set_control = 5, // Specific for SET + fire_macro = 6, // Specific for FIRE + cue_list = 7, // Cue List + cue_path = 8, // Cue Path + cue_list_with_timecode = 9 // timecode optional Cue list }; -typedef struct { - NSString *cueNumber; - NSString *cueList; - NSString *cuePath; -} Cue; - -@interface SMShowControlMessage (Private) - -+ (NSString *)nameForShowControlCommand:(Byte)mscCommand; - -@end - @implementation SMShowControlMessage : SMSystemExclusiveMessage + (SMShowControlMessage *)showControlMessageWithTimeStamp:(MIDITimeStamp)aTimeStamp data:(NSData *)aData @@ -85,18 +79,62 @@ - (NSString *)dataForDisplay; SMShowControlDataType dataType = [SMShowControlMessage dataTypeForCommand:mscCommand]; - if (dataType == cue_path) { - Cue cue = [SMShowControlMessage parseCue:[self otherData]]; - if (cue.cueNumber != nil) { - [result appendFormat:@" Cue %@", cue.cueNumber]; + int hdrSize = 5; + NSData *data = [self otherData]; + NSData *parameterData = [NSData dataWithBytes:(data.bytes + hdrSize) length:data.length - hdrSize]; + + if (dataType == cue) { + NSString *cueList = [SMShowControlMessage parseCue:parameterData]; + if ([cueList length] > 0) { + [result appendFormat:@" %@", cueList]; } - if (cue.cueList != nil) { - [result appendFormat:@", List %@", cue.cueList]; + } else if (dataType == cue_with_timecode) { + NSData *cueBytes = [NSData dataWithBytes:(parameterData.bytes + 5) length:parameterData.length - 5]; + NSString *cueList = [SMShowControlMessage parseCue:cueBytes]; + if ([cueList length] > 0) { + [result appendFormat:@" %@", cueList]; } - if (cue.cuePath != nil) { - [result appendFormat:@", Path %@", cue.cuePath]; + + [result appendString:@" @ "]; + + NSData *timecodeBytes = [NSData dataWithBytes:parameterData.bytes length:5]; + [result appendString:[SMShowControlMessage parseTimecode:timecodeBytes]]; + } else if (dataType == cue_path) { + NSString *cueList = [SMShowControlMessage parseCuePath:parameterData]; + if ([cueList length] > 0) { + [result appendFormat:@" %@", cueList]; + } + } else if (dataType == cue_list) { + NSString *cueList = [SMShowControlMessage parseCueList:parameterData]; + if ([cueList length] > 0) { + [result appendFormat:@" %@", cueList]; } - } else if (dataType == unknown){ + } else if (dataType == cue_list_with_timecode) { + NSData *timecodeBytes = [NSData dataWithBytes:parameterData.bytes length:5]; + [result appendString:@" "]; + [result appendString:[SMShowControlMessage parseTimecode:timecodeBytes]]; + + NSData *cueBytes = [NSData dataWithBytes:(parameterData.bytes + 5) length:parameterData.length - 5]; + NSString *cueList = [SMShowControlMessage parseCueList:cueBytes]; + if ([cueList length] > 0) { + [result appendFormat:@" for %@", cueList]; + } + } else if (dataType == set_control) { + // 14 bit number (two bytes with 7 bit LSB first) + uint16 control = *(uint8 *)parameterData.bytes | *((uint8 *)parameterData.bytes + 1) << 7; + uint16 value = *((uint8 *)parameterData.bytes + 2) | *((uint8 *)parameterData.bytes + 3) << 7; + + [result appendFormat:@" Control %d to value %d", control, value]; + if ([parameterData length] == 10) { + // Timecode included + NSData *timecodeBytes = [NSData dataWithBytes:(parameterData.bytes + 4) length:5]; + [result appendString:@" @ "]; + [result appendString:[SMShowControlMessage parseTimecode:timecodeBytes]]; + } + } else if (dataType == fire_macro) { + // one 7 bit macro number + [result appendFormat:@" Macro %d", *(Byte *)parameterData.bytes]; + } else if (dataType == unknown){ if (dataString) { if (result.length > 0) { [result appendString:@"\t"]; @@ -162,49 +200,110 @@ + (SMShowControlDataType)dataTypeForCommand:(Byte)mscCommand; case 0x05: case 0x0B: case 0x10: - return cue_path; + return cue; case 0x04: - return cue_path_with_timestamp; + return cue_with_timecode; case 0x08: case 0x09: case 0x0A: return no_data; + case 0x06: + return set_control; + case 0x07: + return fire_macro; + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + case 0x19: + case 0x1A: + case 0x1B: + case 0x1C: + return cue_list; + case 0x1D: + case 0x1E: + return cue_path; + case 0x18: + return cue_list_with_timecode; default: return unknown; } } -+ (Cue)parseCue:(NSData *)data; ++ (NSString *)parseCue:(NSData *)data; { - Cue parsedCue; - parsedCue.cueList = nil; - parsedCue.cueNumber = nil; - parsedCue.cuePath = nil; - - const Byte *cueData = data.bytes + 5; - if (*cueData == 0xF7) { - // All empty - return parsedCue; + NSMutableString *result = [NSMutableString string]; + + NSArray *items = parseCueItemsBytes(data); + if ([items count] >= 1) { + [result appendFormat:@"Cue %@", [items objectAtIndex:0]]; + } + if ([items count] >= 2) { + [result appendFormat:@", List %@", [items objectAtIndex:1]]; + } + if ([items count] == 3) { + [result appendFormat:@", Path %@", [items objectAtIndex:2]]; } - NSMutableString *cueItem = [[NSMutableString alloc] init]; - while (cueData != data.bytes + [data length]) { - Byte thingy = *cueData++; - if (thingy == 0x0 || (thingy == 0xF7 && [cueItem length] > 0)) { - if (parsedCue.cueNumber == nil) { - parsedCue.cueNumber = [[NSString alloc] initWithString:cueItem]; - } else if (parsedCue.cueList == nil) { - parsedCue.cueList = [[NSString alloc] initWithString:cueItem]; - } else { - parsedCue.cuePath = [[NSString alloc] initWithString:cueItem]; - } - cueItem = [[NSMutableString alloc] init]; - } else { - [cueItem appendFormat:@"%c", thingy]; - } + return result; +} + ++ (NSString *)parseCueList:(NSData *)data; +{ + NSMutableString *result = [NSMutableString string]; + + NSArray *items = parseCueItemsBytes(data); + if ([items count] >= 1) { + [result appendFormat:@"Cue List %@", [items objectAtIndex:0]]; } - return parsedCue; + return result; +} + ++ (NSString *)parseCuePath:(NSData *)data; +{ + NSMutableString *result = [NSMutableString string]; + + NSArray *items = parseCueItemsBytes(data); + if ([items count] >= 1) { + [result appendFormat:@"Cue Path %@", [items objectAtIndex:0]]; + } + + return result; +} + ++ (NSString *)parseTimecode:(NSData *)data; +{ + NSMutableString *result = [NSMutableString string]; + + Timecode timecode = parseTimecodeBytes(data); + + [result appendFormat:@"%d:%02d:%02d:%02d", timecode.hours, timecode.minutes, timecode.seconds, timecode.frames]; + if (timecode.form == 0) { + [result appendFormat:@"/%02d", timecode.subframes]; + } + + switch (timecode.timecodeType) { + case 0: + [result appendString:@" (24 fps)"]; + break; + case 1: + [result appendString:@" (25 fps)"]; + break; + case 2: + [result appendString:@" (30 fps/drop)"]; + break; + case 3: + [result appendString:@" (30 fps/non-drop)"]; + break; + default: + [result appendString:@" (unknown)"]; + } + + return result; } @end diff --git a/Frameworks/SnoizeMIDI/SMShowControlUtilities.h b/Frameworks/SnoizeMIDI/SMShowControlUtilities.h new file mode 100644 index 00000000..133746b0 --- /dev/null +++ b/Frameworks/SnoizeMIDI/SMShowControlUtilities.h @@ -0,0 +1,53 @@ +// +// SMShowControlUtilities.h +// SnoizeMIDI +// +// Created by Hugo Trippaers on 22/12/2018. +// + +#ifndef SMShowControlUtilities_h +#define SMShowControlUtilities_h + +// Based on the structure described in +// MIDI Show Control 1.1 pg 6 +typedef struct { + int timecodeType; + int hours; + int minutes; + int seconds; + int frames; + int subframes; + int colorFrameBit; + int form; // 0 = fractional frames, 1 = status + int sign; // 0 = positive, 1 = negative + int statusEstimatedCodeFlag; + int statusInvalidCode; + int statusVideoFieldIndentification; +} Timecode; + +/* + * Parses the provided bytes into a timecode structure + * according to the Timecode format in the MIDI Show + * Control 1.1 specification + * + * Parameters + * NSData *timecodeBytes, object with 5 bytes of timecode data + * + * Returns + * Timecode, struct with all components contains in the timecode data + */ +extern Timecode parseTimecodeBytes(NSData *timecodeBytes); + +/* + * Parses the provided bytes into an array with one + * string per cue item in the list + * + * Parameters + * NSData *cueItemsBytes, object with a variable number of bytes terminated by 0xF7 + * + * Returns + * NSArray *, array containing an NSString * for every cueitem found in the data + */ +extern NSArray *parseCueItemsBytes(NSData *cueItemsBytes); + +#endif /* SMShowControlUtilities_h */ diff --git a/Frameworks/SnoizeMIDI/SMShowControlUtilities.m b/Frameworks/SnoizeMIDI/SMShowControlUtilities.m new file mode 100644 index 00000000..6b97650c --- /dev/null +++ b/Frameworks/SnoizeMIDI/SMShowControlUtilities.m @@ -0,0 +1,70 @@ +// +// SMShowControlUtilities.m +// SnoizeMIDI +// +// Created by Hugo Trippaers on 22/12/2018. +// + +#import + +#import "SMShowControlUtilities.h" + +#define BIT(n) ( 1<<(n) ) +#define BIT_MASK(len) ( BIT(len)-1 ) +#define BITFIELD_MASK(start, len) ( BIT_MASK(len)<<(start) ) +#define BITFIELD_GET(y, start, len) ( ((y)>>(start)) & BIT_MASK(len) ) + +Timecode parseTimecodeBytes(NSData *timecodeBytes) { + Timecode timecode; + const Byte *byte = timecodeBytes.bytes; + // Hours and type: 0 tt hhhhh + timecode.timecodeType = BITFIELD_GET(*byte, 5, 2); + timecode.hours = BITFIELD_GET(*byte++, 0, 5); + + // Minutes and color frame bit: 0 c mmmmmm + timecode.colorFrameBit = BITFIELD_GET(*byte, 6, 1); + timecode.minutes = BITFIELD_GET(*byte++, 0, 5); + + // Seconds: 0 k ssssss + timecode.seconds = BITFIELD_GET(*byte++, 0, 5); + + // Frames, byte 5 ident and sign: 0 g i fffff + timecode.sign = BITFIELD_GET(*byte, 6, 1); + timecode.form = BITFIELD_GET(*byte, 5, 1); + timecode.frames = BITFIELD_GET(*byte++, 0, 5); + + if (timecode.form) { + // code status bit map:0 e v d xxxx + timecode.statusEstimatedCodeFlag = BITFIELD_GET(*byte, 6, 1); + timecode.statusInvalidCode = BITFIELD_GET(*byte, 5, 1); + timecode.statusVideoFieldIndentification = BITFIELD_GET(*byte, 4, 1); + } else { + // fractional frames: 0 bbbbbbb + timecode.subframes = BITFIELD_GET(*byte, 0, 7); + } + + return timecode; +} + +NSArray *parseCueItemsBytes(NSData *cueItemsBytes) { + NSMutableArray *result = [[NSMutableArray alloc] init]; + + const Byte *cueData = cueItemsBytes.bytes; + if (*cueData == 0xF7) { + // All empty + return result; + } + + NSMutableString *cueItem = [[NSMutableString alloc] init]; + while (cueData != cueItemsBytes.bytes + [cueItemsBytes length]) { + Byte thingy = *cueData++; + if (thingy == 0x0 || (thingy == 0xF7 && [cueItem length] > 0)) { + [result addObject:[[NSString alloc] initWithString:cueItem]]; + cueItem = [[NSMutableString alloc] init]; + } else { + [cueItem appendFormat:@"%c", thingy]; + } + } + + return result; +} diff --git a/Frameworks/SnoizeMIDI/SnoizeMIDI.xcodeproj/project.pbxproj b/Frameworks/SnoizeMIDI/SnoizeMIDI.xcodeproj/project.pbxproj index 9354c893..6f721188 100644 --- a/Frameworks/SnoizeMIDI/SnoizeMIDI.xcodeproj/project.pbxproj +++ b/Frameworks/SnoizeMIDI/SnoizeMIDI.xcodeproj/project.pbxproj @@ -78,6 +78,8 @@ 9A6D1B1821C0605C00D9872C /* ShowControlCommandNames.plist in Resources */ = {isa = PBXBuildFile; fileRef = 9A6D1B1721C0605C00D9872C /* ShowControlCommandNames.plist */; }; 9A6D1B2521C2F65B00D9872C /* SnoizeMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8DC2EF5B0486A6940098B216 /* SnoizeMIDI.framework */; }; 9A6D1B2B21C2F67100D9872C /* SMShowControlMessageTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A6D1B1A21C2F53000D9872C /* SMShowControlMessageTest.m */; }; + 9AD4AE8021CE560100E7BBF0 /* SMShowControlUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 9AD4AE7F21CE560100E7BBF0 /* SMShowControlUtilities.m */; }; + 9AD4AE8321CE577800E7BBF0 /* SMShowControlUtilitiesTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 9AD4AE8221CE577800E7BBF0 /* SMShowControlUtilitiesTest.m */; }; A53DDB9A148F4219006D2FFD /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A53DDB99148F4219006D2FFD /* Foundation.framework */; }; A53DDBDF148F4349006D2FFD /* SMClient.m in Sources */ = {isa = PBXBuildFile; fileRef = 16B11B5E0971D40100DB1DB5 /* SMClient.m */; }; A53DDBE0148F434C006D2FFD /* SMHostTime.m in Sources */ = {isa = PBXBuildFile; fileRef = 16B11B660971D40100DB1DB5 /* SMHostTime.m */; }; @@ -298,6 +300,9 @@ 9A6D1B1A21C2F53000D9872C /* SMShowControlMessageTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SMShowControlMessageTest.m; sourceTree = ""; }; 9A6D1B2021C2F65B00D9872C /* SnoizeMIDITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SnoizeMIDITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 9A6D1B2421C2F65B00D9872C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 9AD4AE7F21CE560100E7BBF0 /* SMShowControlUtilities.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SMShowControlUtilities.m; sourceTree = ""; }; + 9AD4AE8121CE562900E7BBF0 /* SMShowControlUtilities.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SMShowControlUtilities.h; sourceTree = ""; }; + 9AD4AE8221CE577800E7BBF0 /* SMShowControlUtilitiesTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SMShowControlUtilitiesTest.m; sourceTree = ""; }; A53DDB97148F4219006D2FFD /* libSnoizeMIDIiOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libSnoizeMIDIiOS.a; sourceTree = BUILT_PRODUCTS_DIR; }; A53DDB99148F4219006D2FFD /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; A53DDB9D148F4219006D2FFD /* SnoizeMIDIiOS-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SnoizeMIDIiOS-Prefix.pch"; sourceTree = ""; }; @@ -426,6 +431,8 @@ 16B11B660971D40100DB1DB5 /* SMHostTime.m */, 16B11B890971D40100DB1DB5 /* SMUtilities.h */, 16B11B8A0971D40100DB1DB5 /* SMUtilities.m */, + 9AD4AE8121CE562900E7BBF0 /* SMShowControlUtilities.h */, + 9AD4AE7F21CE560100E7BBF0 /* SMShowControlUtilities.m */, ); name = "Global / Utilities"; sourceTree = ""; @@ -550,8 +557,9 @@ 9A6D1B2121C2F65B00D9872C /* SnoizeMIDITests */ = { isa = PBXGroup; children = ( - 9A6D1B1A21C2F53000D9872C /* SMShowControlMessageTest.m */, 9A6D1B2421C2F65B00D9872C /* Info.plist */, + 9A6D1B1A21C2F53000D9872C /* SMShowControlMessageTest.m */, + 9AD4AE8221CE577800E7BBF0 /* SMShowControlUtilitiesTest.m */, ); path = SnoizeMIDITests; sourceTree = ""; @@ -854,6 +862,7 @@ 16B11BCB0971D40100DB1DB5 /* SMVirtualOutputStream.m in Sources */, 16B11BCD0971D40100DB1DB5 /* SMVoiceMessage.m in Sources */, 164876610E6D2A22002CF387 /* SMMessageTimeBase.m in Sources */, + 9AD4AE8021CE560100E7BBF0 /* SMShowControlUtilities.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -862,6 +871,7 @@ buildActionMask = 2147483647; files = ( 9A6D1B2B21C2F67100D9872C /* SMShowControlMessageTest.m in Sources */, + 9AD4AE8321CE577800E7BBF0 /* SMShowControlUtilitiesTest.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Frameworks/SnoizeMIDI/SnoizeMIDITests/SMShowControlMessageTest.m b/Frameworks/SnoizeMIDI/SnoizeMIDITests/SMShowControlMessageTest.m index 62fa911c..97e58bf0 100644 --- a/Frameworks/SnoizeMIDI/SnoizeMIDITests/SMShowControlMessageTest.m +++ b/Frameworks/SnoizeMIDI/SnoizeMIDITests/SMShowControlMessageTest.m @@ -56,14 +56,105 @@ - (void)testDataForDisplayWithCueData { } - (void)testDataForDisplayWithPartialCueData { - // MSC GO, all cue params - Byte bytes[] = { 0x7F, 0x00, 0x02, 0x7F, 0x01, 0x31, 0x32, 0x33, 0x00, 0x32, 0x33, 0x2E, 0x32 }; + // MSC GO/JAMCLOCK, some cue params + Byte bytes[] = { 0x7F, 0x00, 0x02, 0x7F, 0x10, 0x31, 0x32, 0x33, 0x00, 0x32, 0x33, 0x2E, 0x32 }; + + NSData *testData = [[NSData alloc] initWithBytes:bytes length:sizeof bytes]; + + SMShowControlMessage *message = [SMShowControlMessage showControlMessageWithTimeStamp:0 data:testData]; + + XCTAssertEqualObjects(@"GO/JAM_CLOCK Cue 123, List 23.2", [message dataForDisplay]); +} + +- (void)testDataForDisplayWithTimedGo { + Byte bytes[] = { 0x7F, 0x00, 0x02, 0x7F, 0x04, 0x61, 0x02, 0x03, 0x04, 0x05, + 0x31, 0x00, 0x32 }; + + NSData *testData = [[NSData alloc] initWithBytes:bytes length:sizeof bytes]; + + SMShowControlMessage *message = [SMShowControlMessage showControlMessageWithTimeStamp:0 data:testData]; + + XCTAssertEqualObjects(@"TIMED_GO Cue 1, List 2 @ 1:02:03:04/05 (30 fps/non-drop)", [message dataForDisplay]); +} + +- (void)testDataForDisplayWithTimedGoWithoutCueList { + Byte bytes[] = { 0x7F, 0x00, 0x02, 0x7F, 0x04, 0x61, 0x02, 0x03, 0x04, 0x05 }; + + NSData *testData = [[NSData alloc] initWithBytes:bytes length:sizeof bytes]; + + SMShowControlMessage *message = [SMShowControlMessage showControlMessageWithTimeStamp:0 data:testData]; + + XCTAssertEqualObjects(@"TIMED_GO @ 1:02:03:04/05 (30 fps/non-drop)", [message dataForDisplay]); +} + +- (void)testDataForSet { + Byte bytes[] = { 0x7F, 0x00, 0x02, 0x7F, 0x06, 0x0C, 0x04, 0x29, 0x03 }; + + NSData *testData = [[NSData alloc] initWithBytes:bytes length:sizeof bytes]; + + SMShowControlMessage *message = [SMShowControlMessage showControlMessageWithTimeStamp:0 data:testData]; + + XCTAssertEqualObjects(@"SET Control 524 to value 425", [message dataForDisplay]); +} + +- (void)testDataForSetWithTimecode { + Byte bytes[] = { 0x7F, 0x00, 0x02, 0x7F, 0x06, 0x0C, 0x04, 0x29, 0x03, 0x61, 0x02, 0x03, 0x04, 0x05 }; + + NSData *testData = [[NSData alloc] initWithBytes:bytes length:sizeof bytes]; + + SMShowControlMessage *message = [SMShowControlMessage showControlMessageWithTimeStamp:0 data:testData]; + + XCTAssertEqualObjects(@"SET Control 524 to value 425 @ 1:02:03:04/05 (30 fps/non-drop)", [message dataForDisplay]); +} + +- (void)testDataForFire { + Byte bytes[] = { 0x7F, 0x00, 0x02, 0x7F, 0x07, 0x65 }; + + NSData *testData = [[NSData alloc] initWithBytes:bytes length:sizeof bytes]; + + SMShowControlMessage *message = [SMShowControlMessage showControlMessageWithTimeStamp:0 data:testData]; + + XCTAssertEqualObjects(@"FIRE Macro 101", [message dataForDisplay]); +} + +- (void)testDataForZeroClock { + Byte bytes[] = { 0x7F, 0x00, 0x02, 0x7F, 0x17, 0x32, 0x31 }; + + NSData *testData = [[NSData alloc] initWithBytes:bytes length:sizeof bytes]; + + SMShowControlMessage *message = [SMShowControlMessage showControlMessageWithTimeStamp:0 data:testData]; + + XCTAssertEqualObjects(@"ZERO_CLOCK Cue List 21", [message dataForDisplay]); +} + +- (void)testDataForOpenCuePath { + Byte bytes[] = { 0x7F, 0x00, 0x02, 0x7F, 0x1D, 0x32, 0x31 }; + + NSData *testData = [[NSData alloc] initWithBytes:bytes length:sizeof bytes]; + + SMShowControlMessage *message = [SMShowControlMessage showControlMessageWithTimeStamp:0 data:testData]; + + XCTAssertEqualObjects(@"OPEN_CUE_PATH Cue Path 21", [message dataForDisplay]); +} + +- (void)testDataForSetClockWithCue { + Byte bytes[] = { 0x7F, 0x00, 0x02, 0x7F, 0x18, 0x61, 0x02, 0x03, 0x04, 0x05, 0x32, 0x31 }; + + NSData *testData = [[NSData alloc] initWithBytes:bytes length:sizeof bytes]; + + SMShowControlMessage *message = [SMShowControlMessage showControlMessageWithTimeStamp:0 data:testData]; + + XCTAssertEqualObjects(@"SET_CLOCK 1:02:03:04/05 (30 fps/non-drop) for Cue List 21", [message dataForDisplay]); +} + +- (void)testDataForSetClockWithoutCue { + Byte bytes[] = { 0x7F, 0x00, 0x02, 0x7F, 0x18, 0x61, 0x02, 0x03, 0x04, 0x05 }; NSData *testData = [[NSData alloc] initWithBytes:bytes length:sizeof bytes]; SMShowControlMessage *message = [SMShowControlMessage showControlMessageWithTimeStamp:0 data:testData]; - XCTAssertEqualObjects(@"GO Cue 123, List 23.2", [message dataForDisplay]); + XCTAssertEqualObjects(@"SET_CLOCK 1:02:03:04/05 (30 fps/non-drop)", [message dataForDisplay]); } @end diff --git a/Frameworks/SnoizeMIDI/SnoizeMIDITests/SMShowControlUtilitiesTest.m b/Frameworks/SnoizeMIDI/SnoizeMIDITests/SMShowControlUtilitiesTest.m new file mode 100644 index 00000000..10ce6251 --- /dev/null +++ b/Frameworks/SnoizeMIDI/SnoizeMIDITests/SMShowControlUtilitiesTest.m @@ -0,0 +1,94 @@ +// +// SMShowControlUtilitiesTest.m +// SnoizeMIDITests +// +// Created by Hugo Trippaers on 22/12/2018. +// + +#import + +#import "SMShowControlUtilities.h" + +@interface SMShowControlUtilitiesTest : XCTestCase + +@end + +@implementation SMShowControlUtilitiesTest + +- (void)setUp { + // Put setup code here. This method is called before the invocation of each test method in the class. +} + +- (void)tearDown { + // Put teardown code here. This method is called after the invocation of each test method in the class. +} + +- (void)testParseTimecodeWithFractionalFrames { + Byte bytes[] = { 0x61, 0x02, 0x03, 0x04, 0x05 }; + NSData *testData = [[NSData alloc] initWithBytes:bytes length:sizeof bytes]; + + Timecode timecode = parseTimecodeBytes(testData); + + XCTAssertEqual(timecode.hours, 1); + XCTAssertEqual(timecode.timecodeType, 3); + XCTAssertEqual(timecode.minutes, 2); + XCTAssertEqual(timecode.seconds, 3); + XCTAssertEqual(timecode.frames, 4); + XCTAssertEqual(timecode.subframes, 5); + XCTAssertEqual(timecode.colorFrameBit, 0); + XCTAssertEqual(timecode.form, 0); + XCTAssertEqual(timecode.sign, 0); +} + +- (void)testParseTimecodeWithStatus { + Byte bytes[] = { 0x61, 0x02, 0x03, 0x24, 0x50 }; + NSData *testData = [[NSData alloc] initWithBytes:bytes length:sizeof bytes]; + + Timecode timecode = parseTimecodeBytes(testData); + + XCTAssertEqual(timecode.hours, 1); + XCTAssertEqual(timecode.timecodeType, 3); + XCTAssertEqual(timecode.minutes, 2); + XCTAssertEqual(timecode.seconds, 3); + XCTAssertEqual(timecode.frames, 4); + XCTAssertEqual(timecode.subframes, 0); + XCTAssertEqual(timecode.colorFrameBit, 0); + XCTAssertEqual(timecode.form, 1); + XCTAssertEqual(timecode.sign, 0); + XCTAssertEqual(timecode.statusVideoFieldIndentification, 1); + XCTAssertEqual(timecode.statusInvalidCode, 0); + XCTAssertEqual(timecode.statusEstimatedCodeFlag, 1); +} + +-(void)testParseCueListWithSingleCue { + Byte bytes[] = { 0x31, 0x31, 0xF7 }; + NSData *testData = [[NSData alloc] initWithBytes:bytes length:sizeof bytes]; + + NSArray *cues = parseCueItemsBytes(testData); + + XCTAssertEqual([cues count], 1); + XCTAssertEqualObjects([cues objectAtIndex:0], @"11"); +} + +-(void)testParseCueListWithFullPath { + Byte bytes[] = { 0x31, 0x31, 0x00, 0x32, 0x32, 0x00, 0x33, 0x33, 0xF7 }; + NSData *testData = [[NSData alloc] initWithBytes:bytes length:sizeof bytes]; + + NSArray *cues = parseCueItemsBytes(testData); + + XCTAssertEqual([cues count], 3); + XCTAssertEqualObjects([cues objectAtIndex:0], @"11"); + XCTAssertEqualObjects([cues objectAtIndex:1], @"22"); + XCTAssertEqualObjects([cues objectAtIndex:2], @"33"); +} + +-(void)testParseCueListWithoutEntries { + Byte bytes[] = { 0xF7 }; + NSData *testData = [[NSData alloc] initWithBytes:bytes length:sizeof bytes]; + + NSArray *cues = parseCueItemsBytes(testData); + + XCTAssertEqual([cues count], 0); +} + +@end From 9bb0fefb48a2333300c52d22a5159067723c8c43 Mon Sep 17 00:00:00 2001 From: Hugo Trippaers Date: Sat, 22 Dec 2018 22:54:38 +0100 Subject: [PATCH 3/5] Refactor cue item parser to not depend on the 0xF7 marker --- Frameworks/SnoizeMIDI/SMShowControlMessage.m | 3 +-- .../SnoizeMIDI/SMShowControlUtilities.m | 21 ++++++++----------- .../SMShowControlUtilitiesTest.m | 16 ++++++++++++-- 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/Frameworks/SnoizeMIDI/SMShowControlMessage.m b/Frameworks/SnoizeMIDI/SMShowControlMessage.m index df864073..041b6172 100644 --- a/Frameworks/SnoizeMIDI/SMShowControlMessage.m +++ b/Frameworks/SnoizeMIDI/SMShowControlMessage.m @@ -80,7 +80,6 @@ - (NSString *)dataForDisplay; SMShowControlDataType dataType = [SMShowControlMessage dataTypeForCommand:mscCommand]; int hdrSize = 5; - NSData *data = [self otherData]; NSData *parameterData = [NSData dataWithBytes:(data.bytes + hdrSize) length:data.length - hdrSize]; if (dataType == cue) { @@ -125,7 +124,7 @@ - (NSString *)dataForDisplay; uint16 value = *((uint8 *)parameterData.bytes + 2) | *((uint8 *)parameterData.bytes + 3) << 7; [result appendFormat:@" Control %d to value %d", control, value]; - if ([parameterData length] == 10) { + if ([parameterData length] == 9) { // Timecode included NSData *timecodeBytes = [NSData dataWithBytes:(parameterData.bytes + 4) length:5]; [result appendString:@" @ "]; diff --git a/Frameworks/SnoizeMIDI/SMShowControlUtilities.m b/Frameworks/SnoizeMIDI/SMShowControlUtilities.m index 6b97650c..6d26b493 100644 --- a/Frameworks/SnoizeMIDI/SMShowControlUtilities.m +++ b/Frameworks/SnoizeMIDI/SMShowControlUtilities.m @@ -48,21 +48,18 @@ Timecode parseTimecodeBytes(NSData *timecodeBytes) { NSArray *parseCueItemsBytes(NSData *cueItemsBytes) { NSMutableArray *result = [[NSMutableArray alloc] init]; + NSUInteger length = [cueItemsBytes length]; const Byte *cueData = cueItemsBytes.bytes; - if (*cueData == 0xF7) { - // All empty - return result; - } - NSMutableString *cueItem = [[NSMutableString alloc] init]; - while (cueData != cueItemsBytes.bytes + [cueItemsBytes length]) { - Byte thingy = *cueData++; - if (thingy == 0x0 || (thingy == 0xF7 && [cueItem length] > 0)) { - [result addObject:[[NSString alloc] initWithString:cueItem]]; - cueItem = [[NSMutableString alloc] init]; - } else { - [cueItem appendFormat:@"%c", thingy]; + NSUInteger startIndex = 0; + for (NSUInteger i = 0; i < [cueItemsBytes length]; i++) { + Byte currentByte = *(cueData + i); + NSUInteger copyLength = (currentByte != 0x00 ? i - startIndex + 1: i - startIndex); + + if ((currentByte == 0x0 || i == length - 1) && copyLength > 0) { + [result addObject:[[NSString alloc] initWithBytes:cueItemsBytes.bytes + startIndex length:copyLength encoding:NSASCIIStringEncoding]]; + startIndex = i + 1; } } diff --git a/Frameworks/SnoizeMIDI/SnoizeMIDITests/SMShowControlUtilitiesTest.m b/Frameworks/SnoizeMIDI/SnoizeMIDITests/SMShowControlUtilitiesTest.m index 10ce6251..de9c885b 100644 --- a/Frameworks/SnoizeMIDI/SnoizeMIDITests/SMShowControlUtilitiesTest.m +++ b/Frameworks/SnoizeMIDI/SnoizeMIDITests/SMShowControlUtilitiesTest.m @@ -61,7 +61,7 @@ - (void)testParseTimecodeWithStatus { } -(void)testParseCueListWithSingleCue { - Byte bytes[] = { 0x31, 0x31, 0xF7 }; + Byte bytes[] = { 0x31, 0x31 }; NSData *testData = [[NSData alloc] initWithBytes:bytes length:sizeof bytes]; NSArray *cues = parseCueItemsBytes(testData); @@ -71,7 +71,7 @@ -(void)testParseCueListWithSingleCue { } -(void)testParseCueListWithFullPath { - Byte bytes[] = { 0x31, 0x31, 0x00, 0x32, 0x32, 0x00, 0x33, 0x33, 0xF7 }; + Byte bytes[] = { 0x31, 0x31, 0x00, 0x32, 0x32, 0x00, 0x33, 0x33 }; NSData *testData = [[NSData alloc] initWithBytes:bytes length:sizeof bytes]; NSArray *cues = parseCueItemsBytes(testData); @@ -82,6 +82,18 @@ -(void)testParseCueListWithFullPath { XCTAssertEqualObjects([cues objectAtIndex:2], @"33"); } +-(void)testParseCueListWithFullPathOfOneNumberCues { + Byte bytes[] = { 0x31, 0x00, 0x32, 0x00, 0x33 }; + NSData *testData = [[NSData alloc] initWithBytes:bytes length:sizeof bytes]; + + NSArray *cues = parseCueItemsBytes(testData); + + XCTAssertEqual([cues count], 3); + XCTAssertEqualObjects([cues objectAtIndex:0], @"1"); + XCTAssertEqualObjects([cues objectAtIndex:1], @"2"); + XCTAssertEqualObjects([cues objectAtIndex:2], @"3"); +} + -(void)testParseCueListWithoutEntries { Byte bytes[] = { 0xF7 }; NSData *testData = [[NSData alloc] initWithBytes:bytes length:sizeof bytes]; From a2c93a3f2ab0a830313177b62a0ce8b6ecfc46ba Mon Sep 17 00:00:00 2001 From: Kurt Revis Date: Mon, 21 Jan 2019 02:05:51 -0800 Subject: [PATCH 4/5] WIP, untested: integrate more into SnoizeMIDI. - Don't use a separate show control message type, just put it all in sysex message - Cache dataForDisplay in sysex messages - rename some stuff - fix leak in parseCueItemsData TODO: - In tests target, use config files instead of ad-hoc settings - test failing in parseCueItemsData, clean that up probably - check this with MIDI Show Control spec, I suspect there is a lot more to show - new TODOs in sysex message, e.g. checking for message lengths --- .../MIDIMonitor/SMMMonitorWindowController.m | 2 + Frameworks/SnoizeMIDI/SMMessage.h | 2 + Frameworks/SnoizeMIDI/SMMessage.m | 5 + Frameworks/SnoizeMIDI/SMMessageParser.m | 7 +- Frameworks/SnoizeMIDI/SMShowControlMessage.h | 19 - Frameworks/SnoizeMIDI/SMShowControlMessage.m | 308 --------------- .../SnoizeMIDI/SMShowControlUtilities.h | 12 +- .../SnoizeMIDI/SMShowControlUtilities.m | 21 +- .../SnoizeMIDI/SMSystemExclusiveMessage.h | 10 - .../SnoizeMIDI/SMSystemExclusiveMessage.m | 365 +++++++++++++++++- .../SnoizeMIDI.xcodeproj/project.pbxproj | 18 +- .../xcschemes/SnoizeMIDI.xcscheme | 28 ++ .../SMShowControlMessageTest.m | 52 +-- .../SMShowControlUtilitiesTest.m | 23 +- 14 files changed, 446 insertions(+), 426 deletions(-) delete mode 100644 Frameworks/SnoizeMIDI/SMShowControlMessage.h delete mode 100644 Frameworks/SnoizeMIDI/SMShowControlMessage.m diff --git a/Applications/MIDIMonitor/SMMMonitorWindowController.m b/Applications/MIDIMonitor/SMMMonitorWindowController.m index 250ac761..fad7f34b 100644 --- a/Applications/MIDIMonitor/SMMMonitorWindowController.m +++ b/Applications/MIDIMonitor/SMMMonitorWindowController.m @@ -633,6 +633,8 @@ - (void)trivialWindowSettingsDidChange - (void)displayPreferencesDidChange:(NSNotification *)notification { + [self.displayedMessages makeObjectsPerformSelector:@selector(invalidateDisplayCache)]; + [self.messagesTableView reloadData]; } diff --git a/Frameworks/SnoizeMIDI/SMMessage.h b/Frameworks/SnoizeMIDI/SMMessage.h index 6ea7ca7f..2a3476da 100644 --- a/Frameworks/SnoizeMIDI/SMMessage.h +++ b/Frameworks/SnoizeMIDI/SMMessage.h @@ -151,4 +151,6 @@ extern NSString *SMProgramChangeBaseIndexPreferenceKey; - (NSString *)expertDataForDisplay; - (NSString *)originatingEndpointForDisplay; +- (void)invalidateDisplayCache; + @end diff --git a/Frameworks/SnoizeMIDI/SMMessage.m b/Frameworks/SnoizeMIDI/SMMessage.m index c98bee28..94ab31c7 100644 --- a/Frameworks/SnoizeMIDI/SMMessage.m +++ b/Frameworks/SnoizeMIDI/SMMessage.m @@ -530,6 +530,11 @@ - (NSString *)originatingEndpointForDisplay } } +- (void)invalidateDisplayCache +{ + // Do nothing by default. Subclasses that cache results that depend on display preferences should override to invalidate their cache. +} + @end diff --git a/Frameworks/SnoizeMIDI/SMMessageParser.m b/Frameworks/SnoizeMIDI/SMMessageParser.m index d33445ef..1eb05e36 100644 --- a/Frameworks/SnoizeMIDI/SMMessageParser.m +++ b/Frameworks/SnoizeMIDI/SMMessageParser.m @@ -18,7 +18,6 @@ #import "SMSystemCommonMessage.h" #import "SMSystemRealTimeMessage.h" #import "SMSystemExclusiveMessage.h" -#import "SMShowControlMessage.h" #import "SMInvalidMessage.h" @@ -342,11 +341,7 @@ - (SMSystemExclusiveMessage *)finishSysExMessageWithValidEnd:(BOOL)isEndValid; // The MIDI spec says that messages should end with this byte, but apparently that is not always the case in practice. if (readingSysExData) { - if (((uint8_t *)readingSysExData.mutableBytes)[2] == 0x02) { // MIDI Show Control Message - message = [SMShowControlMessage showControlMessageWithTimeStamp:startSysExTimeStamp data:readingSysExData]; - } else { - message = [SMSystemExclusiveMessage systemExclusiveMessageWithTimeStamp:startSysExTimeStamp data:readingSysExData]; - } + message = [SMSystemExclusiveMessage systemExclusiveMessageWithTimeStamp:startSysExTimeStamp data:readingSysExData]; [readingSysExData release]; readingSysExData = nil; diff --git a/Frameworks/SnoizeMIDI/SMShowControlMessage.h b/Frameworks/SnoizeMIDI/SMShowControlMessage.h deleted file mode 100644 index ca8719cb..00000000 --- a/Frameworks/SnoizeMIDI/SMShowControlMessage.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// SMShowControlMessage.h -// SnoizeMIDI -// -// Created by Hugo Trippaers on 11/12/2018. -// - -#import -#import -#import - -@interface SMShowControlMessage : SMSystemExclusiveMessage -{ - Byte mscCommand; -} - -+ (SMShowControlMessage *)showControlMessageWithTimeStamp:(MIDITimeStamp)aTimeStamp data:(NSData *)aData; - -@end diff --git a/Frameworks/SnoizeMIDI/SMShowControlMessage.m b/Frameworks/SnoizeMIDI/SMShowControlMessage.m deleted file mode 100644 index 041b6172..00000000 --- a/Frameworks/SnoizeMIDI/SMShowControlMessage.m +++ /dev/null @@ -1,308 +0,0 @@ -// -// SMShowControlMessage.m -// SnoizeMIDI -// -// Created by Hugo Trippaers on 11/12/2018. -// - -#import "SMShowControlMessage.h" - -#import "SMUtilities.h" -#import "SMShowControlUtilities.h" - -typedef NS_ENUM(NSInteger, SMShowControlDataType) { - unknown = 1, // Yet undefined data - cue = 2, // optional Cue, optional List, optional Path - cue_with_timecode = 3, // timecode, optional Cue, optional List, optional Path - no_data = 4, // No additional data - set_control = 5, // Specific for SET - fire_macro = 6, // Specific for FIRE - cue_list = 7, // Cue List - cue_path = 8, // Cue Path - cue_list_with_timecode = 9 // timecode optional Cue list -}; - -@implementation SMShowControlMessage : SMSystemExclusiveMessage - -+ (SMShowControlMessage *)showControlMessageWithTimeStamp:(MIDITimeStamp)aTimeStamp data:(NSData *)aData -{ - SMShowControlMessage *message; - - message = [[[SMShowControlMessage alloc] initWithTimeStamp:aTimeStamp statusByte:0xF0] autorelease]; - [message setData:aData]; - [message parseShowControl:aData]; - - return message; -} - -- (void)encodeWithCoder:(NSCoder *)coder -{ - [super encodeWithCoder:coder]; - [coder encodeObject:data forKey:@"data"]; - [coder encodeBool:[self wasReceivedWithEOX] forKey:@"wasReceivedWithEOX"]; -} - -- (id)initWithCoder:(NSCoder *)decoder -{ - if ((self = [super initWithCoder:decoder])) { - id obj = [decoder decodeObjectForKey:@"data"]; - if (obj && [obj isKindOfClass:[NSData class]]) { - data = [obj retain]; - } else { - goto fail; - } - - [self setWasReceivedWithEOX:[decoder decodeBoolForKey:@"wasReceivedWithEOX"]]; - [self parseShowControl:data]; - } - - return self; - -fail: - [self release]; - return nil; -} - -- (NSString *)typeForDisplay; -{ - return NSLocalizedStringFromTableInBundle(@"Show Control", @"SnoizeMIDI", SMBundleForObject(self), "displayed type of System Exclusive Show Control event"); -} - -- (NSString *)dataForDisplay; -{ - NSString *dataString = [self expertDataForDisplay]; - - NSMutableString *result = [NSMutableString string]; - - NSString *command = [SMShowControlMessage nameForShowControlCommand:mscCommand]; - [result appendString:command]; - - SMShowControlDataType dataType = [SMShowControlMessage dataTypeForCommand:mscCommand]; - - int hdrSize = 5; - NSData *parameterData = [NSData dataWithBytes:(data.bytes + hdrSize) length:data.length - hdrSize]; - - if (dataType == cue) { - NSString *cueList = [SMShowControlMessage parseCue:parameterData]; - if ([cueList length] > 0) { - [result appendFormat:@" %@", cueList]; - } - } else if (dataType == cue_with_timecode) { - NSData *cueBytes = [NSData dataWithBytes:(parameterData.bytes + 5) length:parameterData.length - 5]; - NSString *cueList = [SMShowControlMessage parseCue:cueBytes]; - if ([cueList length] > 0) { - [result appendFormat:@" %@", cueList]; - } - - [result appendString:@" @ "]; - - NSData *timecodeBytes = [NSData dataWithBytes:parameterData.bytes length:5]; - [result appendString:[SMShowControlMessage parseTimecode:timecodeBytes]]; - } else if (dataType == cue_path) { - NSString *cueList = [SMShowControlMessage parseCuePath:parameterData]; - if ([cueList length] > 0) { - [result appendFormat:@" %@", cueList]; - } - } else if (dataType == cue_list) { - NSString *cueList = [SMShowControlMessage parseCueList:parameterData]; - if ([cueList length] > 0) { - [result appendFormat:@" %@", cueList]; - } - } else if (dataType == cue_list_with_timecode) { - NSData *timecodeBytes = [NSData dataWithBytes:parameterData.bytes length:5]; - [result appendString:@" "]; - [result appendString:[SMShowControlMessage parseTimecode:timecodeBytes]]; - - NSData *cueBytes = [NSData dataWithBytes:(parameterData.bytes + 5) length:parameterData.length - 5]; - NSString *cueList = [SMShowControlMessage parseCueList:cueBytes]; - if ([cueList length] > 0) { - [result appendFormat:@" for %@", cueList]; - } - } else if (dataType == set_control) { - // 14 bit number (two bytes with 7 bit LSB first) - uint16 control = *(uint8 *)parameterData.bytes | *((uint8 *)parameterData.bytes + 1) << 7; - uint16 value = *((uint8 *)parameterData.bytes + 2) | *((uint8 *)parameterData.bytes + 3) << 7; - - [result appendFormat:@" Control %d to value %d", control, value]; - if ([parameterData length] == 9) { - // Timecode included - NSData *timecodeBytes = [NSData dataWithBytes:(parameterData.bytes + 4) length:5]; - [result appendString:@" @ "]; - [result appendString:[SMShowControlMessage parseTimecode:timecodeBytes]]; - } - } else if (dataType == fire_macro) { - // one 7 bit macro number - [result appendFormat:@" Macro %d", *(Byte *)parameterData.bytes]; - } else if (dataType == unknown){ - if (dataString) { - if (result.length > 0) { - [result appendString:@"\t"]; - } - [result appendString:dataString]; - } - } - - return result; -} - -- (void)setData:(NSData *)newData; -{ - if (data != newData) { - [data release]; - data = [newData retain]; - - [cachedDataWithEOX release]; - cachedDataWithEOX = nil; - } -} - -- (void)parseShowControl:(NSData *)newData; -{ - mscCommand = (((Byte *)newData.bytes)[4]); -} - -+ (NSString *)nameForShowControlCommand:(Byte)mscCommand; -{ - static NSDictionary *showControlCommands = nil; - NSString *identifierString, *name; - - if (!showControlCommands) { - NSString *path; - - path = [SMBundleForObject(self) pathForResource:@"ShowControlCommandNames" ofType:@"plist"]; - if (path) { - showControlCommands = [NSDictionary dictionaryWithContentsOfFile:path]; - if (!showControlCommands) - NSLog(@"Couldn't read ShowControlCommandNames.plist!"); - } else { - NSLog(@"Couldn't find ShowControlCommandNames.plist!"); - } - - if (!showControlCommands) - showControlCommands = [NSDictionary dictionary]; - [showControlCommands retain]; - } - - identifierString = [NSString stringWithFormat:@"%X", mscCommand]; - if ((name = [showControlCommands objectForKey:identifierString])) - return name; - else - return NSLocalizedStringFromTableInBundle(@"Unknown Command", @"SnoizeMIDI", SMBundleForObject(self), "unknown command name"); -} - -+ (SMShowControlDataType)dataTypeForCommand:(Byte)mscCommand; -{ - switch(mscCommand) { - case 0x01: - case 0x02: - case 0x03: - case 0x05: - case 0x0B: - case 0x10: - return cue; - case 0x04: - return cue_with_timecode; - case 0x08: - case 0x09: - case 0x0A: - return no_data; - case 0x06: - return set_control; - case 0x07: - return fire_macro; - case 0x11: - case 0x12: - case 0x13: - case 0x14: - case 0x15: - case 0x16: - case 0x17: - case 0x19: - case 0x1A: - case 0x1B: - case 0x1C: - return cue_list; - case 0x1D: - case 0x1E: - return cue_path; - case 0x18: - return cue_list_with_timecode; - default: - return unknown; - } -} - -+ (NSString *)parseCue:(NSData *)data; -{ - NSMutableString *result = [NSMutableString string]; - - NSArray *items = parseCueItemsBytes(data); - if ([items count] >= 1) { - [result appendFormat:@"Cue %@", [items objectAtIndex:0]]; - } - if ([items count] >= 2) { - [result appendFormat:@", List %@", [items objectAtIndex:1]]; - } - if ([items count] == 3) { - [result appendFormat:@", Path %@", [items objectAtIndex:2]]; - } - - return result; -} - -+ (NSString *)parseCueList:(NSData *)data; -{ - NSMutableString *result = [NSMutableString string]; - - NSArray *items = parseCueItemsBytes(data); - if ([items count] >= 1) { - [result appendFormat:@"Cue List %@", [items objectAtIndex:0]]; - } - - return result; -} - -+ (NSString *)parseCuePath:(NSData *)data; -{ - NSMutableString *result = [NSMutableString string]; - - NSArray *items = parseCueItemsBytes(data); - if ([items count] >= 1) { - [result appendFormat:@"Cue Path %@", [items objectAtIndex:0]]; - } - - return result; -} - -+ (NSString *)parseTimecode:(NSData *)data; -{ - NSMutableString *result = [NSMutableString string]; - - Timecode timecode = parseTimecodeBytes(data); - - [result appendFormat:@"%d:%02d:%02d:%02d", timecode.hours, timecode.minutes, timecode.seconds, timecode.frames]; - if (timecode.form == 0) { - [result appendFormat:@"/%02d", timecode.subframes]; - } - - switch (timecode.timecodeType) { - case 0: - [result appendString:@" (24 fps)"]; - break; - case 1: - [result appendString:@" (25 fps)"]; - break; - case 2: - [result appendString:@" (30 fps/drop)"]; - break; - case 3: - [result appendString:@" (30 fps/non-drop)"]; - break; - default: - [result appendString:@" (unknown)"]; - } - - return result; -} - -@end diff --git a/Frameworks/SnoizeMIDI/SMShowControlUtilities.h b/Frameworks/SnoizeMIDI/SMShowControlUtilities.h index 133746b0..f81c8b11 100644 --- a/Frameworks/SnoizeMIDI/SMShowControlUtilities.h +++ b/Frameworks/SnoizeMIDI/SMShowControlUtilities.h @@ -5,8 +5,7 @@ // Created by Hugo Trippaers on 22/12/2018. // -#ifndef SMShowControlUtilities_h -#define SMShowControlUtilities_h +#import // Based on the structure described in // MIDI Show Control 1.1 pg 6 @@ -23,7 +22,7 @@ typedef struct { int statusEstimatedCodeFlag; int statusInvalidCode; int statusVideoFieldIndentification; -} Timecode; +} SMTimecode; /* * Parses the provided bytes into a timecode structure @@ -36,18 +35,17 @@ typedef struct { * Returns * Timecode, struct with all components contains in the timecode data */ -extern Timecode parseTimecodeBytes(NSData *timecodeBytes); +extern SMTimecode parseTimecodeData(NSData *timecodeData); /* * Parses the provided bytes into an array with one * string per cue item in the list * * Parameters - * NSData *cueItemsBytes, object with a variable number of bytes terminated by 0xF7 + * NSData *cueItemsData, object with a variable number of bytes terminated by 0xF7 * * Returns * NSArray *, array containing an NSString * for every cueitem found in the data */ -extern NSArray *parseCueItemsBytes(NSData *cueItemsBytes); +extern NSArray *parseCueItemsData(NSData *cueItemsData); -#endif /* SMShowControlUtilities_h */ diff --git a/Frameworks/SnoizeMIDI/SMShowControlUtilities.m b/Frameworks/SnoizeMIDI/SMShowControlUtilities.m index 6d26b493..31028089 100644 --- a/Frameworks/SnoizeMIDI/SMShowControlUtilities.m +++ b/Frameworks/SnoizeMIDI/SMShowControlUtilities.m @@ -14,9 +14,9 @@ #define BITFIELD_MASK(start, len) ( BIT_MASK(len)<<(start) ) #define BITFIELD_GET(y, start, len) ( ((y)>>(start)) & BIT_MASK(len) ) -Timecode parseTimecodeBytes(NSData *timecodeBytes) { - Timecode timecode; - const Byte *byte = timecodeBytes.bytes; +SMTimecode parseTimecodeData(NSData *timecodeData) { + SMTimecode timecode; + const Byte *byte = timecodeData.bytes; // Hours and type: 0 tt hhhhh timecode.timecodeType = BITFIELD_GET(*byte, 5, 2); timecode.hours = BITFIELD_GET(*byte++, 0, 5); @@ -46,19 +46,22 @@ Timecode parseTimecodeBytes(NSData *timecodeBytes) { return timecode; } -NSArray *parseCueItemsBytes(NSData *cueItemsBytes) { +NSArray *parseCueItemsData(NSData *cueItemsData) { NSMutableArray *result = [[NSMutableArray alloc] init]; - NSUInteger length = [cueItemsBytes length]; + NSUInteger length = cueItemsData.length; - const Byte *cueData = cueItemsBytes.bytes; + const Byte *bytes = cueItemsData.bytes; NSUInteger startIndex = 0; - for (NSUInteger i = 0; i < [cueItemsBytes length]; i++) { - Byte currentByte = *(cueData + i); + for (NSUInteger i = 0; i < length; i++) { + Byte currentByte = *(bytes + i); NSUInteger copyLength = (currentByte != 0x00 ? i - startIndex + 1: i - startIndex); if ((currentByte == 0x0 || i == length - 1) && copyLength > 0) { - [result addObject:[[NSString alloc] initWithBytes:cueItemsBytes.bytes + startIndex length:copyLength encoding:NSASCIIStringEncoding]]; + NSString *cueItemString = [[NSString alloc] initWithBytes:bytes + startIndex length:copyLength encoding:NSASCIIStringEncoding]; + [result addObject:cueItemString]; + [cueItemString release]; + startIndex = i + 1; } } diff --git a/Frameworks/SnoizeMIDI/SMSystemExclusiveMessage.h b/Frameworks/SnoizeMIDI/SMSystemExclusiveMessage.h index 378e219e..065ac13c 100644 --- a/Frameworks/SnoizeMIDI/SMSystemExclusiveMessage.h +++ b/Frameworks/SnoizeMIDI/SMSystemExclusiveMessage.h @@ -18,16 +18,6 @@ @interface SMSystemExclusiveMessage : SMMessage // TODO Should this be a SMSystemCommonMessage too? Would we gain anything from that? -{ - NSData *data; - // data does not include the starting 0xF0 or the ending 0xF7 (EOX) - - struct { - unsigned int wasReceivedWithEOX:1; - } flags; - - NSMutableData *cachedDataWithEOX; -} + (SMSystemExclusiveMessage *)systemExclusiveMessageWithTimeStamp:(MIDITimeStamp)aTimeStamp data:(NSData *)aData; // data should NOT include the starting 0xF0 or the ending 0xF7 (EOX) diff --git a/Frameworks/SnoizeMIDI/SMSystemExclusiveMessage.m b/Frameworks/SnoizeMIDI/SMSystemExclusiveMessage.m index 9a765802..dda8efbd 100644 --- a/Frameworks/SnoizeMIDI/SMSystemExclusiveMessage.m +++ b/Frameworks/SnoizeMIDI/SMSystemExclusiveMessage.m @@ -14,7 +14,19 @@ #import "SMSystemExclusiveMessage.h" #import "SMUtilities.h" +#import "SMShowControlUtilities.h" +typedef NS_ENUM(NSInteger, SMShowControlDataType) { + SMShowControlDataTypeUnknown = 1, // Yet undefined data + SMShowControlDataTypeCue, // optional Cue, optional List, optional Path + SMShowControlDataTypeCueWithTimecode, // timecode, optional Cue, optional List, optional Path + SMShowControlDataTypeNoData, // No additional data + SMShowControlDataTypeSetControl, // Specific for SET + SMShowControlDataTypeFireMacro, // Specific for FIRE + SMShowControlDataTypeCueList, // Cue List + SMShowControlDataTypeCuePath, // Cue Path + SMShowControlDataTypeCueListWithTimecode, // timecode optional Cue list +}; @interface SMSystemExclusiveMessage (Private) @@ -30,10 +42,33 @@ + (NSArray *)systemExclusiveMessagesInDataBuffer:(const Byte *)buffer withLength - (NSData *)dataByAddingStartByte:(NSData *)someData; +- (BOOL)isShowControl; +- (NSString *)showControlDataForDisplay; ++ (NSString *)nameForShowControlCommand:(Byte)command; ++ (SMShowControlDataType)dataTypeForShowControlCommand:(Byte)command; ++ (NSString *)formatShowControlCue:(NSData *)cueData; ++ (NSString *)formatShowControlCueList:(NSData *)cuePathData; ++ (NSString *)formatShowControlCuePath:(NSData *)cuePathData; ++ (NSString *)formatShowControlTimecode:(NSData *)timecodeData; + + @end -@implementation SMSystemExclusiveMessage : SMMessage +@implementation SMSystemExclusiveMessage +{ + NSData *data; + // data does not include the starting 0xF0 or the ending 0xF7 (EOX) + + struct { + unsigned int wasReceivedWithEOX:1; + } flags; + + NSMutableData *cachedDataWithEOX; + + NSString *cachedDataForDisplay; +} + + (SMSystemExclusiveMessage *)systemExclusiveMessageWithTimeStamp:(MIDITimeStamp)aTimeStamp data:(NSData *)aData { @@ -227,33 +262,59 @@ - (NSData *)otherData - (NSString *)typeForDisplay; { - return NSLocalizedStringFromTableInBundle(@"SysEx", @"SnoizeMIDI", SMBundleForObject(self), "displayed type of System Exclusive event"); + if ([self isShowControl]) { + return NSLocalizedStringFromTableInBundle(@"Show Control", @"SnoizeMIDI", SMBundleForObject(self), "displayed type of System Exclusive Show Control event"); + } + else { + return NSLocalizedStringFromTableInBundle(@"SysEx", @"SnoizeMIDI", SMBundleForObject(self), "displayed type of System Exclusive event"); + } } - (NSString *)dataForDisplay; { - NSString *manufacturerName = [self manufacturerName]; - NSString *lengthString = [self sizeForDisplay]; - NSString *dataString = [self expertDataForDisplay]; + if (cachedDataForDisplay) { + return cachedDataForDisplay; + } - NSMutableString *result = [NSMutableString string]; - if (manufacturerName) { - [result appendString:manufacturerName]; + if ([self isShowControl]) { + // Show control + cachedDataForDisplay = [[self showControlDataForDisplay] copy]; } - if (lengthString) { - if (result.length > 0) { - [result appendString:@" "]; + else { + // Normal sysex + + NSMutableString *result = [NSMutableString string]; + + NSString *manufacturerName = [self manufacturerName]; + NSString *lengthString = [self sizeForDisplay]; + NSString *dataString = [self expertDataForDisplay]; + + if (manufacturerName) { + [result appendString:manufacturerName]; } - [result appendString:lengthString]; - } - if (dataString) { - if (result.length > 0) { - [result appendString:@"\t"]; + if (lengthString) { + if (result.length > 0) { + [result appendString:@" "]; + } + [result appendString:lengthString]; + } + if (dataString) { + if (result.length > 0) { + [result appendString:@"\t"]; + } + [result appendString:dataString]; } - [result appendString:dataString]; + + cachedDataForDisplay = [result copy]; } - return result; + return cachedDataForDisplay; +} + +- (void)invalidateDisplayCache +{ + [cachedDataForDisplay release]; + cachedDataForDisplay = nil; } // @@ -722,4 +783,272 @@ - (NSData *)dataByAddingStartByte:(NSData *)someData; return dataWithStartByte; } +// +// Show Control +// + +static const int showControlHeaderSize = 5; + +- (BOOL)isShowControl +{ + Byte *bytes = (Byte *)data.bytes; + return data.length >= showControlHeaderSize && bytes[0] == 0x7F && bytes[2] == 0x02; + // Byte 0: Manufacturer ID = 0x7F "Universal Real Time" + // Byte 1: Destination Device ID + // Byte 2: MIDI Show Control message = 0x02 + // Byte 3: command_format + // Byte 4: command + // Byte 5+: data +} + +- (NSString *)showControlDataForDisplay +{ + NSMutableString *result = [NSMutableString string]; + + Byte *bytes = (Byte *)data.bytes; +// Byte commandFormat = bytes[3]; // TODO is this really unused? + Byte command = bytes[4]; + + [result appendString:[SMSystemExclusiveMessage nameForShowControlCommand:command]]; + + const int showControlHeaderSize = 5; + NSData *parameterData = [data subdataWithRange:NSMakeRange(showControlHeaderSize, data.length - showControlHeaderSize)]; + + SMShowControlDataType dataType = [SMSystemExclusiveMessage dataTypeForShowControlCommand:command]; + switch (dataType) { + case SMShowControlDataTypeCue: { + NSString *cueString = [SMSystemExclusiveMessage formatShowControlCue:parameterData]; + if (cueString.length) { + [result appendFormat:@" %@", cueString]; + } + break; + } + + case SMShowControlDataTypeCueWithTimecode: { + // TODO Check that data is expected length for both of these + NSData *timecodeData = [parameterData subdataWithRange:NSMakeRange(0, 5)]; + NSString *timecodeString = [SMSystemExclusiveMessage formatShowControlTimecode:timecodeData]; + + NSData *cueData = [parameterData subdataWithRange:NSMakeRange(5, parameterData.length - 5)]; + NSString *cueString = [SMSystemExclusiveMessage formatShowControlCue:cueData]; + + if (cueString.length) { + [result appendFormat:@" %@", cueString]; + } + [result appendFormat:@" @ %@", timecodeString]; + break; + } + + case SMShowControlDataTypeCuePath: { + NSString *cuePathString = [SMSystemExclusiveMessage formatShowControlCuePath:parameterData]; + if (cuePathString.length) { + [result appendFormat:@" %@", cuePathString]; + } + break; + } + + case SMShowControlDataTypeCueList: { + NSString *cueListString = [SMSystemExclusiveMessage formatShowControlCueList:parameterData]; + if (cueListString.length) { + [result appendFormat:@" %@", cueListString]; + } + break; + } + + case SMShowControlDataTypeCueListWithTimecode: { + NSData *timecodeData = [parameterData subdataWithRange:NSMakeRange(0, 5)]; + NSString *timecodeString = [SMSystemExclusiveMessage formatShowControlTimecode:timecodeData]; + + NSData *cueListData = [parameterData subdataWithRange:NSMakeRange(5, parameterData.length - 5)]; + NSString *cueListString = [SMSystemExclusiveMessage formatShowControlCueList:cueListData]; + + [result appendFormat:@" %@", timecodeString]; + if (cueListString.length) { + [result appendFormat:@" for %@", cueListString]; + } + break; + } + + case SMShowControlDataTypeSetControl: { + // 14 bit number (two bytes with 7 bit LSB first) + uint16 control = *(uint8 *)parameterData.bytes | *((uint8 *)parameterData.bytes + 1) << 7; + uint16 value = *((uint8 *)parameterData.bytes + 2) | *((uint8 *)parameterData.bytes + 3) << 7; + + [result appendFormat:@" Control %d to value %d", control, value]; + if ([parameterData length] == 9) { + // Timecode included + NSData *timecodeBytes = [NSData dataWithBytes:(parameterData.bytes + 4) length:5]; + [result appendString:@" @ "]; + [result appendString:[SMSystemExclusiveMessage formatShowControlTimecode:timecodeBytes]]; + } + break; + } + + case SMShowControlDataTypeFireMacro: { + // one 7 bit macro number + [result appendFormat:@" Macro %d", *(Byte *)parameterData.bytes]; + break; + } + + case SMShowControlDataTypeUnknown: { + NSString *dataString = [self expertDataForDisplay]; + if (dataString) { + if (result.length > 0) { + [result appendString:@"\t"]; + } + [result appendString:dataString]; + } + break; + } + + case SMShowControlDataTypeNoData: { + // TODO What? + break; + } + } + + return result; +} + ++ (NSString *)nameForShowControlCommand:(Byte)command +{ + static NSDictionary *showControlCommands = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSString *path = [SMBundleForObject(self) pathForResource:@"ShowControlCommandNames" ofType:@"plist"]; + if (path) { + showControlCommands = [NSDictionary dictionaryWithContentsOfFile:path]; + if (!showControlCommands) { + NSLog(@"Couldn't read ShowControlCommandNames.plist!"); + } + } else { + NSLog(@"Couldn't find ShowControlCommandNames.plist!"); + } + + if (!showControlCommands) { + showControlCommands = [NSDictionary dictionary]; + } + [showControlCommands retain]; + }); + + NSString *identifierString = [NSString stringWithFormat:@"%X", command]; + NSString *name = showControlCommands[identifierString]; + return name ?: NSLocalizedStringFromTableInBundle(@"Unknown Command", @"SnoizeMIDI", SMBundleForObject(self), "unknown command name"); +} + ++ (SMShowControlDataType)dataTypeForShowControlCommand:(Byte)command; +{ + switch (command) { + case 0x01: + case 0x02: + case 0x03: + case 0x05: + case 0x0B: + case 0x10: + return SMShowControlDataTypeCue; + case 0x04: + return SMShowControlDataTypeCueWithTimecode; + case 0x08: + case 0x09: + case 0x0A: + return SMShowControlDataTypeNoData; + case 0x06: + return SMShowControlDataTypeSetControl; + case 0x07: + return SMShowControlDataTypeFireMacro; + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + case 0x19: + case 0x1A: + case 0x1B: + case 0x1C: + return SMShowControlDataTypeCueList; + case 0x1D: + case 0x1E: + return SMShowControlDataTypeCuePath; + case 0x18: + return SMShowControlDataTypeCueListWithTimecode; + default: + return SMShowControlDataTypeUnknown; + } +} + ++ (NSString *)formatShowControlCue:(NSData *)data +{ + NSMutableString *result = [NSMutableString string]; + + NSArray *items = parseCueItemsData(data); + if ([items count] >= 1) { + [result appendFormat:@"Cue %@", [items objectAtIndex:0]]; + } + if ([items count] >= 2) { + [result appendFormat:@", List %@", [items objectAtIndex:1]]; + } + if ([items count] == 3) { + [result appendFormat:@", Path %@", [items objectAtIndex:2]]; + } + + return result; +} + ++ (NSString *)formatShowControlCueList:(NSData *)data +{ + NSMutableString *result = [NSMutableString string]; + + NSArray *items = parseCueItemsData(data); + if ([items count] >= 1) { + [result appendFormat:@"Cue List %@", [items objectAtIndex:0]]; + } + + return result; +} + ++ (NSString *)formatShowControlCuePath:(NSData *)data +{ + NSMutableString *result = [NSMutableString string]; + + NSArray *items = parseCueItemsData(data); + if ([items count] >= 1) { + [result appendFormat:@"Cue Path %@", [items objectAtIndex:0]]; + } + + return result; +} + ++ (NSString *)formatShowControlTimecode:(NSData *)data +{ + NSMutableString *result = [NSMutableString string]; + + SMTimecode timecode = parseTimecodeData(data); + + [result appendFormat:@"%d:%02d:%02d:%02d", timecode.hours, timecode.minutes, timecode.seconds, timecode.frames]; + if (timecode.form == 0) { + [result appendFormat:@"/%02d", timecode.subframes]; + } + + switch (timecode.timecodeType) { + case 0: + [result appendString:@" (24 fps)"]; + break; + case 1: + [result appendString:@" (25 fps)"]; + break; + case 2: + [result appendString:@" (30 fps/drop)"]; + break; + case 3: + [result appendString:@" (30 fps/non-drop)"]; + break; + default: + [result appendString:@" (unknown)"]; + } + + return result; +} + @end diff --git a/Frameworks/SnoizeMIDI/SnoizeMIDI.xcodeproj/project.pbxproj b/Frameworks/SnoizeMIDI/SnoizeMIDI.xcodeproj/project.pbxproj index 6f721188..e899edbf 100644 --- a/Frameworks/SnoizeMIDI/SnoizeMIDI.xcodeproj/project.pbxproj +++ b/Frameworks/SnoizeMIDI/SnoizeMIDI.xcodeproj/project.pbxproj @@ -73,8 +73,10 @@ 16B11BF60971D78500DB1DB5 /* SnoizeMIDI.strings in Resources */ = {isa = PBXBuildFile; fileRef = 16B11BF40971D78500DB1DB5 /* SnoizeMIDI.strings */; }; 16B11DF20971D80F00DB1DB5 /* CoreMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 16B11DF00971D80F00DB1DB5 /* CoreMIDI.framework */; }; 16B11DF30971D81400DB1DB5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0867D69BFE84028FC02AAC07 /* Foundation.framework */; }; + 16B4B56321F5CE8C00C1AC09 /* SMShowControlUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 9AD4AE8121CE562900E7BBF0 /* SMShowControlUtilities.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 16B4B56421F5CE9200C1AC09 /* SMShowControlUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 9AD4AE8121CE562900E7BBF0 /* SMShowControlUtilities.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 16B4B56521F5CE9300C1AC09 /* SMShowControlUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 9AD4AE8121CE562900E7BBF0 /* SMShowControlUtilities.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8DC2EF530486A6940098B216 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C1666FE841158C02AAC07 /* InfoPlist.strings */; }; - 9A6D1B1621C0408500D9872C /* SMShowControlMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A6D1B1521C0408500D9872C /* SMShowControlMessage.m */; }; 9A6D1B1821C0605C00D9872C /* ShowControlCommandNames.plist in Resources */ = {isa = PBXBuildFile; fileRef = 9A6D1B1721C0605C00D9872C /* ShowControlCommandNames.plist */; }; 9A6D1B2521C2F65B00D9872C /* SnoizeMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8DC2EF5B0486A6940098B216 /* SnoizeMIDI.framework */; }; 9A6D1B2B21C2F67100D9872C /* SMShowControlMessageTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A6D1B1A21C2F53000D9872C /* SMShowControlMessageTest.m */; }; @@ -294,8 +296,6 @@ 32DBCF5E0370ADEE00C91783 /* SnoizeMIDI_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SnoizeMIDI_Prefix.pch; sourceTree = ""; }; 8DC2EF5A0486A6940098B216 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 8DC2EF5B0486A6940098B216 /* SnoizeMIDI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SnoizeMIDI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 9A6D1B1421C03FE800D9872C /* SMShowControlMessage.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SMShowControlMessage.h; sourceTree = ""; }; - 9A6D1B1521C0408500D9872C /* SMShowControlMessage.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SMShowControlMessage.m; sourceTree = ""; }; 9A6D1B1721C0605C00D9872C /* ShowControlCommandNames.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = ShowControlCommandNames.plist; sourceTree = ""; }; 9A6D1B1A21C2F53000D9872C /* SMShowControlMessageTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SMShowControlMessageTest.m; sourceTree = ""; }; 9A6D1B2021C2F65B00D9872C /* SnoizeMIDITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SnoizeMIDITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -493,8 +493,6 @@ 16B11B6C0971D40100DB1DB5 /* SMInvalidMessage.m */, 1648765E0E6D2A22002CF387 /* SMMessageTimeBase.h */, 1648765F0E6D2A22002CF387 /* SMMessageTimeBase.m */, - 9A6D1B1421C03FE800D9872C /* SMShowControlMessage.h */, - 9A6D1B1521C0408500D9872C /* SMShowControlMessage.m */, ); name = Messages; sourceTree = ""; @@ -590,6 +588,7 @@ 16B11BA20971D40100DB1DB5 /* SMHostTime.h in Headers */, 16B11BA40971D40100DB1DB5 /* SMInputStream.h in Headers */, 16B11BA60971D40100DB1DB5 /* SMInputStreamSource.h in Headers */, + 16B4B56321F5CE8C00C1AC09 /* SMShowControlUtilities.h in Headers */, 16B11BA80971D40100DB1DB5 /* SMInvalidMessage.h in Headers */, 16B11BAA0971D40100DB1DB5 /* SMMessage.h in Headers */, 16B11BAC0971D40100DB1DB5 /* SMMessageDestinationProtocol.h in Headers */, @@ -629,6 +628,7 @@ A53DDC1F148F4C89006D2FFD /* SMMIDIObject.h in Headers */, A53DDC20148F4C89006D2FFD /* SMMIDIObject-Private.h in Headers */, A53DDC21148F4C89006D2FFD /* SMDevice.h in Headers */, + 16B4B56521F5CE9300C1AC09 /* SMShowControlUtilities.h in Headers */, A53DDC22148F4C89006D2FFD /* SMEndpoint.h in Headers */, A53DDC23148F4C89006D2FFD /* SMExternalDevice.h in Headers */, A53DDC24148F4C94006D2FFD /* SMMessage.h in Headers */, @@ -668,6 +668,7 @@ A581995616AF687400325CF3 /* SMMIDIObject.h in Headers */, A581995716AF687400325CF3 /* SMMIDIObject-Private.h in Headers */, A581995816AF687400325CF3 /* SMDevice.h in Headers */, + 16B4B56421F5CE9200C1AC09 /* SMShowControlUtilities.h in Headers */, A581995916AF687400325CF3 /* SMEndpoint.h in Headers */, A581995A16AF687400325CF3 /* SMExternalDevice.h in Headers */, A581995B16AF687400325CF3 /* SMMessage.h in Headers */, @@ -846,7 +847,6 @@ 16B11BAB0971D40100DB1DB5 /* SMMessage.m in Sources */, 16B11BAE0971D40100DB1DB5 /* SMMessageFilter.m in Sources */, 16B11BB00971D40100DB1DB5 /* SMMessageHistory.m in Sources */, - 9A6D1B1621C0408500D9872C /* SMShowControlMessage.m in Sources */, 16B11BB20971D40100DB1DB5 /* SMMessageMult.m in Sources */, 16B11BB40971D40100DB1DB5 /* SMMessageParser.m in Sources */, 16B11BB70971D40100DB1DB5 /* SMMIDIObject.m in Sources */, @@ -1087,12 +1087,9 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "Mac Developer"; - CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; - DEVELOPMENT_TEAM = 832NCTF3R5; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; @@ -1141,12 +1138,9 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "Mac Developer"; - CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = 832NCTF3R5; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; diff --git a/Frameworks/SnoizeMIDI/SnoizeMIDI.xcodeproj/xcshareddata/xcschemes/SnoizeMIDI.xcscheme b/Frameworks/SnoizeMIDI/SnoizeMIDI.xcodeproj/xcshareddata/xcschemes/SnoizeMIDI.xcscheme index 6dba352c..6ce0d06b 100644 --- a/Frameworks/SnoizeMIDI/SnoizeMIDI.xcodeproj/xcshareddata/xcschemes/SnoizeMIDI.xcscheme +++ b/Frameworks/SnoizeMIDI/SnoizeMIDI.xcodeproj/xcshareddata/xcschemes/SnoizeMIDI.xcscheme @@ -28,7 +28,26 @@ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES"> + + + + + + + + @@ -64,6 +83,15 @@ savedToolIdentifier = "" useCustomWorkingDirectory = "NO" debugDocumentVersioning = "YES"> + + + + diff --git a/Frameworks/SnoizeMIDI/SnoizeMIDITests/SMShowControlMessageTest.m b/Frameworks/SnoizeMIDI/SnoizeMIDITests/SMShowControlMessageTest.m index 97e58bf0..e5072414 100644 --- a/Frameworks/SnoizeMIDI/SnoizeMIDITests/SMShowControlMessageTest.m +++ b/Frameworks/SnoizeMIDI/SnoizeMIDITests/SMShowControlMessageTest.m @@ -7,7 +7,7 @@ #import -#import "SMShowControlMessage.h" +#import @interface SMShowControlMessageTest : XCTestCase @@ -27,7 +27,7 @@ - (void)testConstructor { Byte bytes[] = { 0x7F, 0x00, 0x02, 0x7F, 0x01 }; NSData *testData = [[NSData alloc] initWithBytes:bytes length:5]; - SMShowControlMessage *message = [SMShowControlMessage showControlMessageWithTimeStamp:0 data:testData]; + SMSystemExclusiveMessage *message = [SMSystemExclusiveMessage systemExclusiveMessageWithTimeStamp:0 data:testData]; [message setWasReceivedWithEOX:TRUE]; XCTAssertNotNil(message); @@ -39,8 +39,8 @@ - (void)testDataForDisplay { Byte bytes[] = { 0x7F, 0x00, 0x02, 0x7F, 0x01 }; NSData *testData = [[NSData alloc] initWithBytes:bytes length:5]; - SMShowControlMessage *message = [SMShowControlMessage showControlMessageWithTimeStamp:0 data:testData]; - + SMSystemExclusiveMessage *message = [SMSystemExclusiveMessage systemExclusiveMessageWithTimeStamp:0 data:testData]; + XCTAssertEqualObjects(@"GO", [message dataForDisplay]); } @@ -50,8 +50,8 @@ - (void)testDataForDisplayWithCueData { NSData *testData = [[NSData alloc] initWithBytes:bytes length:17]; - SMShowControlMessage *message = [SMShowControlMessage showControlMessageWithTimeStamp:0 data:testData]; - + SMSystemExclusiveMessage *message = [SMSystemExclusiveMessage systemExclusiveMessageWithTimeStamp:0 data:testData]; + XCTAssertEqualObjects(@"GO Cue 123, List 456, Path 23.2", [message dataForDisplay]); } @@ -61,8 +61,8 @@ - (void)testDataForDisplayWithPartialCueData { NSData *testData = [[NSData alloc] initWithBytes:bytes length:sizeof bytes]; - SMShowControlMessage *message = [SMShowControlMessage showControlMessageWithTimeStamp:0 data:testData]; - + SMSystemExclusiveMessage *message = [SMSystemExclusiveMessage systemExclusiveMessageWithTimeStamp:0 data:testData]; + XCTAssertEqualObjects(@"GO/JAM_CLOCK Cue 123, List 23.2", [message dataForDisplay]); } @@ -72,8 +72,8 @@ - (void)testDataForDisplayWithTimedGo { NSData *testData = [[NSData alloc] initWithBytes:bytes length:sizeof bytes]; - SMShowControlMessage *message = [SMShowControlMessage showControlMessageWithTimeStamp:0 data:testData]; - + SMSystemExclusiveMessage *message = [SMSystemExclusiveMessage systemExclusiveMessageWithTimeStamp:0 data:testData]; + XCTAssertEqualObjects(@"TIMED_GO Cue 1, List 2 @ 1:02:03:04/05 (30 fps/non-drop)", [message dataForDisplay]); } @@ -82,8 +82,8 @@ - (void)testDataForDisplayWithTimedGoWithoutCueList { NSData *testData = [[NSData alloc] initWithBytes:bytes length:sizeof bytes]; - SMShowControlMessage *message = [SMShowControlMessage showControlMessageWithTimeStamp:0 data:testData]; - + SMSystemExclusiveMessage *message = [SMSystemExclusiveMessage systemExclusiveMessageWithTimeStamp:0 data:testData]; + XCTAssertEqualObjects(@"TIMED_GO @ 1:02:03:04/05 (30 fps/non-drop)", [message dataForDisplay]); } @@ -92,8 +92,8 @@ - (void)testDataForSet { NSData *testData = [[NSData alloc] initWithBytes:bytes length:sizeof bytes]; - SMShowControlMessage *message = [SMShowControlMessage showControlMessageWithTimeStamp:0 data:testData]; - + SMSystemExclusiveMessage *message = [SMSystemExclusiveMessage systemExclusiveMessageWithTimeStamp:0 data:testData]; + XCTAssertEqualObjects(@"SET Control 524 to value 425", [message dataForDisplay]); } @@ -102,8 +102,8 @@ - (void)testDataForSetWithTimecode { NSData *testData = [[NSData alloc] initWithBytes:bytes length:sizeof bytes]; - SMShowControlMessage *message = [SMShowControlMessage showControlMessageWithTimeStamp:0 data:testData]; - + SMSystemExclusiveMessage *message = [SMSystemExclusiveMessage systemExclusiveMessageWithTimeStamp:0 data:testData]; + XCTAssertEqualObjects(@"SET Control 524 to value 425 @ 1:02:03:04/05 (30 fps/non-drop)", [message dataForDisplay]); } @@ -112,8 +112,8 @@ - (void)testDataForFire { NSData *testData = [[NSData alloc] initWithBytes:bytes length:sizeof bytes]; - SMShowControlMessage *message = [SMShowControlMessage showControlMessageWithTimeStamp:0 data:testData]; - + SMSystemExclusiveMessage *message = [SMSystemExclusiveMessage systemExclusiveMessageWithTimeStamp:0 data:testData]; + XCTAssertEqualObjects(@"FIRE Macro 101", [message dataForDisplay]); } @@ -122,8 +122,8 @@ - (void)testDataForZeroClock { NSData *testData = [[NSData alloc] initWithBytes:bytes length:sizeof bytes]; - SMShowControlMessage *message = [SMShowControlMessage showControlMessageWithTimeStamp:0 data:testData]; - + SMSystemExclusiveMessage *message = [SMSystemExclusiveMessage systemExclusiveMessageWithTimeStamp:0 data:testData]; + XCTAssertEqualObjects(@"ZERO_CLOCK Cue List 21", [message dataForDisplay]); } @@ -132,8 +132,8 @@ - (void)testDataForOpenCuePath { NSData *testData = [[NSData alloc] initWithBytes:bytes length:sizeof bytes]; - SMShowControlMessage *message = [SMShowControlMessage showControlMessageWithTimeStamp:0 data:testData]; - + SMSystemExclusiveMessage *message = [SMSystemExclusiveMessage systemExclusiveMessageWithTimeStamp:0 data:testData]; + XCTAssertEqualObjects(@"OPEN_CUE_PATH Cue Path 21", [message dataForDisplay]); } @@ -142,8 +142,8 @@ - (void)testDataForSetClockWithCue { NSData *testData = [[NSData alloc] initWithBytes:bytes length:sizeof bytes]; - SMShowControlMessage *message = [SMShowControlMessage showControlMessageWithTimeStamp:0 data:testData]; - + SMSystemExclusiveMessage *message = [SMSystemExclusiveMessage systemExclusiveMessageWithTimeStamp:0 data:testData]; + XCTAssertEqualObjects(@"SET_CLOCK 1:02:03:04/05 (30 fps/non-drop) for Cue List 21", [message dataForDisplay]); } @@ -152,8 +152,8 @@ - (void)testDataForSetClockWithoutCue { NSData *testData = [[NSData alloc] initWithBytes:bytes length:sizeof bytes]; - SMShowControlMessage *message = [SMShowControlMessage showControlMessageWithTimeStamp:0 data:testData]; - + SMSystemExclusiveMessage *message = [SMSystemExclusiveMessage systemExclusiveMessageWithTimeStamp:0 data:testData]; + XCTAssertEqualObjects(@"SET_CLOCK 1:02:03:04/05 (30 fps/non-drop)", [message dataForDisplay]); } diff --git a/Frameworks/SnoizeMIDI/SnoizeMIDITests/SMShowControlUtilitiesTest.m b/Frameworks/SnoizeMIDI/SnoizeMIDITests/SMShowControlUtilitiesTest.m index de9c885b..3d4b01e7 100644 --- a/Frameworks/SnoizeMIDI/SnoizeMIDITests/SMShowControlUtilitiesTest.m +++ b/Frameworks/SnoizeMIDI/SnoizeMIDITests/SMShowControlUtilitiesTest.m @@ -7,7 +7,7 @@ #import -#import "SMShowControlUtilities.h" +#import @interface SMShowControlUtilitiesTest : XCTestCase @@ -27,7 +27,7 @@ - (void)testParseTimecodeWithFractionalFrames { Byte bytes[] = { 0x61, 0x02, 0x03, 0x04, 0x05 }; NSData *testData = [[NSData alloc] initWithBytes:bytes length:sizeof bytes]; - Timecode timecode = parseTimecodeBytes(testData); + SMTimecode timecode = parseTimecodeData(testData); XCTAssertEqual(timecode.hours, 1); XCTAssertEqual(timecode.timecodeType, 3); @@ -44,7 +44,7 @@ - (void)testParseTimecodeWithStatus { Byte bytes[] = { 0x61, 0x02, 0x03, 0x24, 0x50 }; NSData *testData = [[NSData alloc] initWithBytes:bytes length:sizeof bytes]; - Timecode timecode = parseTimecodeBytes(testData); + SMTimecode timecode = parseTimecodeData(testData); XCTAssertEqual(timecode.hours, 1); XCTAssertEqual(timecode.timecodeType, 3); @@ -60,21 +60,21 @@ - (void)testParseTimecodeWithStatus { XCTAssertEqual(timecode.statusEstimatedCodeFlag, 1); } --(void)testParseCueListWithSingleCue { +- (void)testParseCueListWithSingleCue { Byte bytes[] = { 0x31, 0x31 }; NSData *testData = [[NSData alloc] initWithBytes:bytes length:sizeof bytes]; - NSArray *cues = parseCueItemsBytes(testData); + NSArray *cues = parseCueItemsData(testData); XCTAssertEqual([cues count], 1); XCTAssertEqualObjects([cues objectAtIndex:0], @"11"); } --(void)testParseCueListWithFullPath { +- (void)testParseCueListWithFullPath { Byte bytes[] = { 0x31, 0x31, 0x00, 0x32, 0x32, 0x00, 0x33, 0x33 }; NSData *testData = [[NSData alloc] initWithBytes:bytes length:sizeof bytes]; - NSArray *cues = parseCueItemsBytes(testData); + NSArray *cues = parseCueItemsData(testData); XCTAssertEqual([cues count], 3); XCTAssertEqualObjects([cues objectAtIndex:0], @"11"); @@ -82,11 +82,11 @@ -(void)testParseCueListWithFullPath { XCTAssertEqualObjects([cues objectAtIndex:2], @"33"); } --(void)testParseCueListWithFullPathOfOneNumberCues { +- (void)testParseCueListWithFullPathOfOneNumberCues { Byte bytes[] = { 0x31, 0x00, 0x32, 0x00, 0x33 }; NSData *testData = [[NSData alloc] initWithBytes:bytes length:sizeof bytes]; - NSArray *cues = parseCueItemsBytes(testData); + NSArray *cues = parseCueItemsData(testData); XCTAssertEqual([cues count], 3); XCTAssertEqualObjects([cues objectAtIndex:0], @"1"); @@ -94,12 +94,13 @@ -(void)testParseCueListWithFullPathOfOneNumberCues { XCTAssertEqualObjects([cues objectAtIndex:2], @"3"); } --(void)testParseCueListWithoutEntries { +- (void)testParseCueListWithoutEntries { Byte bytes[] = { 0xF7 }; NSData *testData = [[NSData alloc] initWithBytes:bytes length:sizeof bytes]; - NSArray *cues = parseCueItemsBytes(testData); + NSArray *cues = parseCueItemsData(testData); + // TODO This is failing, returning 1 not 0 XCTAssertEqual([cues count], 0); } From 63d2345372b324e0a9a52946c33b58f172023c93 Mon Sep 17 00:00:00 2001 From: Kurt Revis Date: Mon, 21 Jan 2019 02:46:00 -0800 Subject: [PATCH 5/5] Fix testParseCueListWithoutEntries, simplify parseCueItemsData --- .../SnoizeMIDI/SMShowControlUtilities.m | 22 ++----------------- .../SMShowControlUtilitiesTest.m | 4 +--- 2 files changed, 3 insertions(+), 23 deletions(-) diff --git a/Frameworks/SnoizeMIDI/SMShowControlUtilities.m b/Frameworks/SnoizeMIDI/SMShowControlUtilities.m index 31028089..3a0402bc 100644 --- a/Frameworks/SnoizeMIDI/SMShowControlUtilities.m +++ b/Frameworks/SnoizeMIDI/SMShowControlUtilities.m @@ -47,24 +47,6 @@ SMTimecode parseTimecodeData(NSData *timecodeData) { } NSArray *parseCueItemsData(NSData *cueItemsData) { - NSMutableArray *result = [[NSMutableArray alloc] init]; - NSUInteger length = cueItemsData.length; - - const Byte *bytes = cueItemsData.bytes; - - NSUInteger startIndex = 0; - for (NSUInteger i = 0; i < length; i++) { - Byte currentByte = *(bytes + i); - NSUInteger copyLength = (currentByte != 0x00 ? i - startIndex + 1: i - startIndex); - - if ((currentByte == 0x0 || i == length - 1) && copyLength > 0) { - NSString *cueItemString = [[NSString alloc] initWithBytes:bytes + startIndex length:copyLength encoding:NSASCIIStringEncoding]; - [result addObject:cueItemString]; - [cueItemString release]; - - startIndex = i + 1; - } - } - - return result; + NSString *cueItemString = [[NSString alloc] initWithData:cueItemsData encoding:NSASCIIStringEncoding]; + return cueItemString.length > 0 ? [cueItemString componentsSeparatedByString:@"\0"] : @[]; } diff --git a/Frameworks/SnoizeMIDI/SnoizeMIDITests/SMShowControlUtilitiesTest.m b/Frameworks/SnoizeMIDI/SnoizeMIDITests/SMShowControlUtilitiesTest.m index 3d4b01e7..c3b9267a 100644 --- a/Frameworks/SnoizeMIDI/SnoizeMIDITests/SMShowControlUtilitiesTest.m +++ b/Frameworks/SnoizeMIDI/SnoizeMIDITests/SMShowControlUtilitiesTest.m @@ -95,12 +95,10 @@ - (void)testParseCueListWithFullPathOfOneNumberCues { } - (void)testParseCueListWithoutEntries { - Byte bytes[] = { 0xF7 }; - NSData *testData = [[NSData alloc] initWithBytes:bytes length:sizeof bytes]; + NSData *testData = [NSData data]; NSArray *cues = parseCueItemsData(testData); - // TODO This is failing, returning 1 not 0 XCTAssertEqual([cues count], 0); }