From 1701fa6190f81f9a8cbf4f083ca118e806bf196b Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Sun, 25 Aug 2019 13:17:33 -0600 Subject: [PATCH 01/53] Issue #269: Add API to MIKMIDISequence for converting between beats and seconds, along with tests. (cherry picked from commit 6e0c342ca02df5a61a7b15e94c6b208a29a73f24) --- .../MIKMIDI Tests/MIKMIDISequenceTests.m | 45 ++++++++++++++++++ Framework/MIKMIDI Tests/tempochanges.mid | Bin 0 -> 2154 bytes Framework/MIKMIDI.xcodeproj/project.pbxproj | 5 ++ .../xcshareddata/xcschemes/MIKMIDI.xcscheme | 26 +++++----- Source/MIKMIDISequence.h | 5 ++ Source/MIKMIDISequence.m | 16 +++++++ 6 files changed, 82 insertions(+), 15 deletions(-) create mode 100644 Framework/MIKMIDI Tests/tempochanges.mid diff --git a/Framework/MIKMIDI Tests/MIKMIDISequenceTests.m b/Framework/MIKMIDI Tests/MIKMIDISequenceTests.m index 49c3fc11..4bae766d 100644 --- a/Framework/MIKMIDI Tests/MIKMIDISequenceTests.m +++ b/Framework/MIKMIDI Tests/MIKMIDISequenceTests.m @@ -139,6 +139,51 @@ - (void)testLength XCTAssertTrue([self.receivedNotificationKeyPaths containsObject:@"durationInSeconds"], @"KVO notification for durationInSeconds failed after removing longest child track."); } +- (void)testConversionFromMusicTimeStampToSeconds +{ + NSBundle *bundle = [NSBundle bundleForClass:[self class]]; + NSURL *testMIDIFileURL = [bundle URLForResource:@"tempochanges" withExtension:@"mid"]; + NSError *error = nil; + MIKMIDISequence *sequence = [MIKMIDISequence sequenceWithFileAtURL:testMIDIFileURL convertMIDIChannelsToTracks:NO error:&error]; + XCTAssertNotNil(sequence); + + XCTAssertEqual([sequence timeInSecondsForMusicTimeStamp:0], 0.0); + + MIKMIDITempoEvent *firstTempo = [sequence.tempoEvents firstObject]; + XCTAssertNotNil(firstTempo); + NSTimeInterval expectedTimeAt3 = 60 * 3.0 / firstTempo.bpm; + XCTAssertEqualWithAccuracy([sequence timeInSecondsForMusicTimeStamp:3], expectedTimeAt3, 1e-6); + + MIKMIDITempoEvent *secondTempo = sequence.tempoEvents[1]; + NSTimeInterval expectedTimeAtSecondTempo = 60 * secondTempo.timeStamp / firstTempo.bpm; + MusicTimeStamp nextCheck = secondTempo.timeStamp+1; + NSTimeInterval expectedTimeAtNextCheck = 60 * (nextCheck - secondTempo.timeStamp) / secondTempo.bpm + expectedTimeAtSecondTempo; + XCTAssertEqualWithAccuracy([sequence timeInSecondsForMusicTimeStamp:nextCheck], expectedTimeAtNextCheck, 1e-6); +} + +- (void)testConversionFromSecondsToMusicTimeStamp +{ + NSBundle *bundle = [NSBundle bundleForClass:[self class]]; + NSURL *testMIDIFileURL = [bundle URLForResource:@"tempochanges" withExtension:@"mid"]; + NSError *error = nil; + MIKMIDISequence *sequence = [MIKMIDISequence sequenceWithFileAtURL:testMIDIFileURL convertMIDIChannelsToTracks:NO error:&error]; + XCTAssertNotNil(sequence); + + XCTAssertEqual([sequence musicTimeStampForTimeInSeconds:0.0], 0); + + MIKMIDITempoEvent *firstTempo = [sequence.tempoEvents firstObject]; + XCTAssertNotNil(firstTempo); + NSTimeInterval firstCheckTime = 1.5; + MusicTimeStamp firstExpectedTimeStamp = firstTempo.bpm * firstCheckTime / 60.0; + XCTAssertEqualWithAccuracy([sequence musicTimeStampForTimeInSeconds:firstCheckTime], firstExpectedTimeStamp, 1e-6); + + MIKMIDITempoEvent *secondTempo = sequence.tempoEvents[1]; + NSTimeInterval timeAtSecondTempo = 60 * secondTempo.timeStamp / firstTempo.bpm; + NSTimeInterval secondCheckTime = 4; + MusicTimeStamp expectedTimeStampAtNextCheck = secondTempo.bpm * (secondCheckTime - timeAtSecondTempo) / 60.0 + secondTempo.timeStamp; + XCTAssertEqualWithAccuracy([sequence musicTimeStampForTimeInSeconds:secondCheckTime], expectedTimeStampAtNextCheck, 1e-6); +} + - (void)testSetTimeSignature { [self.sequence setTimeSignature:MIKMIDITimeSignatureMake(2, 4) atTimeStamp:0]; diff --git a/Framework/MIKMIDI Tests/tempochanges.mid b/Framework/MIKMIDI Tests/tempochanges.mid new file mode 100644 index 0000000000000000000000000000000000000000..398857b7c8c626c98e915c2660ca3a9146381878 GIT binary patch literal 2154 zcmai$v2GJV5Qf*g=SwI+qKJe9iYy^IP;PJc?shxuBu*?PiVPR9dkQ3=fF23E;{~XA z0VEXh0+dK}RMaRh!ZEk=kCHT@+VRZ#+nL$@$3AXfzEDc#YM}Ik56A7hS4wR*s%wXj z)iwpXxw&zErn-DksBWnT z+-$skPZVv76x5cf+A>z##9eAbInXY~Ys-9X`2cOx37l!ZU6iBA>|iu`GCMeIN0a^W zX*-*q9*v%#9PK?Ca}txZnDK(oF%vd$(BMjK#>%b|#^6e|#!1{u{M!0a>9rOAuU3k4 zJGG6>E5R$#!4e%TIaghxcO`mPqIV_VjxNC~!E@j_@Emv!JO`cw&w=N_bKtqeTV#gb zPk)ELSe?szN}s{^;Ct{r_#S)@z6alf?~!kXd@Jx8#2dr|;sNo1^8%k|g5#$_1|COn zyZlOm+v|-*rnv^%vcLL7$q&g`lozJEFnuy<=d^$2_dbV^y)T zG$Ds!jK<*Lz002jfDcqkZLQr^Gfomq|8eCSV@_c^sy3hEw8DT z*HjC+7IH1*TFAAKYa!P{uIV>uNpA|xZ-n0nzY%^T{6_eV@EhSb!pF!l7Bk0K3>`9r zjIl!=Aw%Ef5i%4`9w9^H + shouldUseLaunchSchemeArgsEnv = "YES" + enableAddressSanitizer = "YES"> + + + + @@ -40,17 +49,6 @@ - - - - - - - - Date: Sun, 25 Aug 2019 22:28:58 -0600 Subject: [PATCH 02/53] Issue #269: Add method to convert from MusicTimeStamp to seconds to MIKMIDISequencer, plus tests. (cherry picked from commit 7d746a613a2194189dff3fe52669eaa7460b8507) --- .../MIKMIDI Tests/MIKMIDISequencerTests.m | 85 ++++++++++++++++++- Source/MIKMIDISequencer.h | 7 +- Source/MIKMIDISequencer.m | 71 ++++++++++++++++ 3 files changed, 160 insertions(+), 3 deletions(-) diff --git a/Framework/MIKMIDI Tests/MIKMIDISequencerTests.m b/Framework/MIKMIDI Tests/MIKMIDISequencerTests.m index 346ed788..1ab8e82e 100644 --- a/Framework/MIKMIDI Tests/MIKMIDISequencerTests.m +++ b/Framework/MIKMIDI Tests/MIKMIDISequencerTests.m @@ -20,14 +20,95 @@ @implementation MIKMIDISequencerTests - (void)setUp { - [super setUp]; + [super setUp]; self.sequencer = [MIKMIDISequencer sequencer]; } - (void)tearDown { - [super tearDown]; + [super tearDown]; +} + +- (void)testConversionFromMusicTimeStampToSeconds +{ + NSBundle *bundle = [NSBundle bundleForClass:[self class]]; + NSURL *testMIDIFileURL = [bundle URLForResource:@"tempochanges" withExtension:@"mid"]; + NSError *error = nil; + MIKMIDISequence *s = [MIKMIDISequence sequenceWithFileAtURL:testMIDIFileURL convertMIDIChannelsToTracks:NO error:&error]; + XCTAssertNotNil(s); + self.sequencer.sequence = s; + + XCTAssertEqual([self.sequencer timeInSecondsForMusicTimeStamp:0 ignoreLooping:NO], 0.0); + + XCTAssertEqualWithAccuracy([s timeInSecondsForMusicTimeStamp:3], [self.sequencer timeInSecondsForMusicTimeStamp:3 ignoreLooping:NO], 1e-6 ); + + MIKMIDITempoEvent *secondTempo = s.tempoEvents[1]; + MusicTimeStamp nextCheck = secondTempo.timeStamp+1; + XCTAssertEqualWithAccuracy([s timeInSecondsForMusicTimeStamp:nextCheck], [self.sequencer timeInSecondsForMusicTimeStamp:nextCheck ignoreLooping:NO], 1e-6); + + MusicTimeStamp testTimeStamp = 850; + NSTimeInterval expected = [s timeInSecondsForMusicTimeStamp:testTimeStamp]; + NSTimeInterval actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp ignoreLooping:NO]; + XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + + // Test when tempo is overridden + Float64 overrideTempo = 80.0; + self.sequencer.tempo = overrideTempo; + + expected = 60 * testTimeStamp / overrideTempo; + actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp ignoreLooping:NO]; + XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + + self.sequencer.tempo = 0; // Disable tempo override + + // Test with looping + self.sequencer.loop = YES; + [self.sequencer setLoopStartTimeStamp:10 endTimeStamp:15]; + + testTimeStamp = 5; // Test before loop region, should be unaffected + expected = [s timeInSecondsForMusicTimeStamp:testTimeStamp]; + actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp ignoreLooping:NO]; + XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + + testTimeStamp = 12; // Test inside loop region before first loop, should be unaffected + expected = [s timeInSecondsForMusicTimeStamp:testTimeStamp]; + actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp ignoreLooping:NO]; + XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + + testTimeStamp = 37.5; // Test inside loop region after first loop, should be affected + expected = 24.284925; + actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp ignoreLooping:NO]; + XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + + [self.sequencer setLoopStartTimeStamp:200 endTimeStamp:600]; + + testTimeStamp = 160; // Test before loop region, should be unaffected + expected = [s timeInSecondsForMusicTimeStamp:testTimeStamp]; + actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp ignoreLooping:NO]; + XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + + testTimeStamp = 550; // Test inside loop region before first loop, should be unaffected + expected = [s timeInSecondsForMusicTimeStamp:testTimeStamp]; + actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp ignoreLooping:NO]; + XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + + testTimeStamp = 850; // Test inside loop region after first loop, should be affected + expected = 427.46909; + actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp ignoreLooping:NO]; + XCTAssertEqualWithAccuracy(expected, actual, 1e-6); +} + +- (void)testConversionFromSecondsToMusicTimeStamp +{ + NSBundle *bundle = [NSBundle bundleForClass:[self class]]; + NSURL *testMIDIFileURL = [bundle URLForResource:@"tempochanges" withExtension:@"mid"]; + NSError *error = nil; + MIKMIDISequence *s = [MIKMIDISequence sequenceWithFileAtURL:testMIDIFileURL convertMIDIChannelsToTracks:NO error:&error]; + XCTAssertNotNil(s); + self.sequencer.sequence = s; + + XCTAssertEqual([self.sequencer musicTimeStampForTimeInSeconds:0.0], 0); } - (void)testBuiltinSynthesizers diff --git a/Source/MIKMIDISequencer.h b/Source/MIKMIDISequencer.h index 790212d3..e62a64b5 100644 --- a/Source/MIKMIDISequencer.h +++ b/Source/MIKMIDISequencer.h @@ -255,6 +255,11 @@ NS_ASSUME_NONNULL_BEGIN */ - (nullable MIKMIDISynthesizer *)builtinSynthesizerForTrack:(MIKMIDITrack *)track; +#pragma mark - Time Conversion + +- (NSTimeInterval)timeInSecondsForMusicTimeStamp:(MusicTimeStamp)musicTimeStamp ignoreLooping:(BOOL)ignoreLooping; +- (MusicTimeStamp)musicTimeStampForTimeInSeconds:(NSTimeInterval)timeInSeconds; + #pragma mark - Properties /** @@ -475,4 +480,4 @@ FOUNDATION_EXPORT NSString * const MIKMIDISequencerWillLoopNotification; */ FOUNDATION_EXPORT const MusicTimeStamp MIKMIDISequencerEndOfSequenceLoopEndTimeStamp; -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index b226efe2..3c13ea40 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -657,6 +657,77 @@ - (MIKMIDISynthesizer *)builtinSynthesizerForTrack:(MIKMIDITrack *)track return [self.tracksToDefaultSynthsMap objectForKey:track]; } +#pragma mark - Time Conversion + +- (NSTimeInterval)timeInSecondsForMusicTimeStamp:(MusicTimeStamp)musicTimeStamp ignoreLooping:(BOOL)ignoreLooping +{ + if (!ignoreLooping && self.shouldLoop && musicTimeStamp >= self.loopEndTimeStamp) { + MusicTimeStamp loopDuration = self.loopEndTimeStamp - self.loopStartTimeStamp; + NSTimeInterval loopStartTimeInSeconds = [self timeInSecondsForMusicTimeStamp:self.loopStartTimeStamp ignoreLooping:YES]; + NSTimeInterval loopEndTimeInSeconds = [self timeInSecondsForMusicTimeStamp:self.loopEndTimeStamp ignoreLooping:YES]; + NSTimeInterval loopDurationInSeconds = loopEndTimeInSeconds - loopStartTimeInSeconds; + + MusicTimeStamp scratch = musicTimeStamp; + scratch -= self.loopStartTimeStamp; // Subtract off time before the loop + NSTimeInterval result = loopStartTimeInSeconds; + // "Use up" the loops until we're down to a fraction of a loop + while (scratch >= loopDuration) { + result += loopDurationInSeconds; + scratch -= loopDuration; + } + // Add the remaining fraction of a loop + result += [self timeInSecondsForMusicTimeStamp:(self.loopStartTimeStamp + scratch) ignoreLooping:YES]; + result -= loopStartTimeInSeconds; + return result; + } + + // Calculate initial tempo, handling case where sequence doesn't specify one. + NSArray *tempoEvents = self.sequence.tempoEvents; + if (self.tempo != 0) { // Overridden tempo that should be used instead of events in the tempo track + tempoEvents = @[[MIKMIDITempoEvent tempoEventWithTimeStamp:0 tempo:self.tempo]]; + } else { + NSUInteger tempoAtZeroIndex = [tempoEvents indexOfObjectPassingTest:^BOOL(MIKMIDITempoEvent *event, NSUInteger i, BOOL *s) { + return event.timeStamp == 0; + }]; + if (tempoAtZeroIndex == NSNotFound) { + NSMutableArray *scratch = [tempoEvents mutableCopy]; + MIKMIDITempoEvent *initialTempo = [MIKMIDITempoEvent tempoEventWithTimeStamp:0 tempo:kDefaultTempo]; + [scratch insertObject:initialTempo atIndex:0]; + tempoEvents = [scratch copy]; + } + } + + // Get tempo events that affect the result (ie. come before musicTimeStamp) and sort them in ascending order + NSIndexSet *indexesOfTempoEventsAffectingResult = + [tempoEvents indexesOfObjectsPassingTest:^BOOL(MIKMIDITempoEvent *event, NSUInteger i, BOOL *s) { + if (!self.shouldLoop || musicTimeStamp < self.loopEndTimeStamp || ignoreLooping) { + return event.timeStamp <= musicTimeStamp; + } + // musicTimeStamp is within the loop region, so include all tempo events up to the end of the loop + return musicTimeStamp <= self.loopEndTimeStamp; + }]; + NSArray *tempoEventsAffectingResult = [tempoEvents objectsAtIndexes:indexesOfTempoEventsAffectingResult]; + tempoEvents = [tempoEventsAffectingResult sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"timeStamp" ascending:YES]]]; + + NSTimeInterval result = 0.0; + MIKMIDITempoEvent *lastTempoEvent = tempoEvents[0]; + for (MIKMIDITempoEvent *tempoEvent in tempoEvents) { + result += 60.0 * (tempoEvent.timeStamp - lastTempoEvent.timeStamp) / lastTempoEvent.bpm; + lastTempoEvent = tempoEvent; + } + result += 60.0 * (musicTimeStamp - lastTempoEvent.timeStamp) / lastTempoEvent.bpm; + return result; +} + +- (MusicTimeStamp)musicTimeStampForTimeInSeconds:(NSTimeInterval)timeInSeconds +{ + // if (self.tempo == 0 && !self.shouldLoop) { // If tempo is not overridden, and looping is not on + // // Just use MIKMIDISequence's simple implementation + // return [self.sequence musicTimeStampForTimeInSeconds:timeInSeconds]; + // } + return 0; +} + #pragma mark - Click Track - (NSMutableArray *)clickTrackEventsFromTimeStamp:(MusicTimeStamp)fromTimeStamp toTimeStamp:(MusicTimeStamp)toTimeStamp From 1002134b2660fa055ff42591cfd87be47b3d0c6b Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Sun, 1 Sep 2019 16:30:29 -0600 Subject: [PATCH 03/53] Issue #269: Add options to MIKMIDISequencer time conversion methods, along with more test coverage. --- .../MIKMIDI Tests/MIKMIDISequencerTests.m | 44 ++++++++++++++----- Source/MIKMIDISequencer.h | 23 +++++++++- Source/MIKMIDISequencer.m | 18 +++++--- 3 files changed, 65 insertions(+), 20 deletions(-) diff --git a/Framework/MIKMIDI Tests/MIKMIDISequencerTests.m b/Framework/MIKMIDI Tests/MIKMIDISequencerTests.m index 1ab8e82e..b9a65933 100644 --- a/Framework/MIKMIDI Tests/MIKMIDISequencerTests.m +++ b/Framework/MIKMIDI Tests/MIKMIDISequencerTests.m @@ -39,17 +39,17 @@ - (void)testConversionFromMusicTimeStampToSeconds XCTAssertNotNil(s); self.sequencer.sequence = s; - XCTAssertEqual([self.sequencer timeInSecondsForMusicTimeStamp:0 ignoreLooping:NO], 0.0); + XCTAssertEqual([self.sequencer timeInSecondsForMusicTimeStamp:0 options:MIKMIDISequencerTimeConversionOptionsNone], 0.0); - XCTAssertEqualWithAccuracy([s timeInSecondsForMusicTimeStamp:3], [self.sequencer timeInSecondsForMusicTimeStamp:3 ignoreLooping:NO], 1e-6 ); + XCTAssertEqualWithAccuracy([s timeInSecondsForMusicTimeStamp:3], [self.sequencer timeInSecondsForMusicTimeStamp:3 options:MIKMIDISequencerTimeConversionOptionsNone], 1e-6 ); MIKMIDITempoEvent *secondTempo = s.tempoEvents[1]; MusicTimeStamp nextCheck = secondTempo.timeStamp+1; - XCTAssertEqualWithAccuracy([s timeInSecondsForMusicTimeStamp:nextCheck], [self.sequencer timeInSecondsForMusicTimeStamp:nextCheck ignoreLooping:NO], 1e-6); + XCTAssertEqualWithAccuracy([s timeInSecondsForMusicTimeStamp:nextCheck], [self.sequencer timeInSecondsForMusicTimeStamp:nextCheck options:MIKMIDISequencerTimeConversionOptionsNone], 1e-6); MusicTimeStamp testTimeStamp = 850; NSTimeInterval expected = [s timeInSecondsForMusicTimeStamp:testTimeStamp]; - NSTimeInterval actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp ignoreLooping:NO]; + NSTimeInterval actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; XCTAssertEqualWithAccuracy(expected, actual, 1e-6); // Test when tempo is overridden @@ -57,7 +57,7 @@ - (void)testConversionFromMusicTimeStampToSeconds self.sequencer.tempo = overrideTempo; expected = 60 * testTimeStamp / overrideTempo; - actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp ignoreLooping:NO]; + actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; XCTAssertEqualWithAccuracy(expected, actual, 1e-6); self.sequencer.tempo = 0; // Disable tempo override @@ -68,35 +68,55 @@ - (void)testConversionFromMusicTimeStampToSeconds testTimeStamp = 5; // Test before loop region, should be unaffected expected = [s timeInSecondsForMusicTimeStamp:testTimeStamp]; - actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp ignoreLooping:NO]; + actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; XCTAssertEqualWithAccuracy(expected, actual, 1e-6); testTimeStamp = 12; // Test inside loop region before first loop, should be unaffected expected = [s timeInSecondsForMusicTimeStamp:testTimeStamp]; - actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp ignoreLooping:NO]; + actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; XCTAssertEqualWithAccuracy(expected, actual, 1e-6); testTimeStamp = 37.5; // Test inside loop region after first loop, should be affected expected = 24.284925; - actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp ignoreLooping:NO]; + actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; XCTAssertEqualWithAccuracy(expected, actual, 1e-6); [self.sequencer setLoopStartTimeStamp:200 endTimeStamp:600]; testTimeStamp = 160; // Test before loop region, should be unaffected expected = [s timeInSecondsForMusicTimeStamp:testTimeStamp]; - actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp ignoreLooping:NO]; + actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; XCTAssertEqualWithAccuracy(expected, actual, 1e-6); testTimeStamp = 550; // Test inside loop region before first loop, should be unaffected expected = [s timeInSecondsForMusicTimeStamp:testTimeStamp]; - actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp ignoreLooping:NO]; + actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; XCTAssertEqualWithAccuracy(expected, actual, 1e-6); testTimeStamp = 850; // Test inside loop region after first loop, should be affected expected = 427.46909; - actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp ignoreLooping:NO]; + actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + + testTimeStamp = 850; // Test inside loop region after first loop, but ignoring looping should be affected + NSTimeInterval withLooping = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; + NSTimeInterval ignoringLooping = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsIgnoreLooping]; + self.sequencer.loop = NO; + NSTimeInterval withoutLooping = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; + XCTAssertNotEqualWithAccuracy(withLooping, ignoringLooping, 1e-6); + XCTAssertEqualWithAccuracy(withoutLooping, ignoringLooping, 1e-6); + + // Test loop "unrolling" + self.sequencer.loop = YES; + [self.sequencer setLoopStartTimeStamp:10 endTimeStamp:15]; + testTimeStamp = 37.5; // Test inside loop region after first loop, should be affected + NSTimeInterval withoutUnrolling = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsDontUnrollLoop]; + NSTimeInterval withUnrolling = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; + NSTimeInterval expectedWithoutUnrolling = 8.094975; + ignoringLooping = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsIgnoreLooping]; + XCTAssertNotEqualWithAccuracy(withoutUnrolling, withUnrolling, 1e-6); + XCTAssertNotEqualWithAccuracy(withUnrolling, ignoringLooping, 1e-6); + XCTAssertEqualWithAccuracy(withoutUnrolling, expectedWithoutUnrolling, 1e-6); } - (void)testConversionFromSecondsToMusicTimeStamp @@ -108,7 +128,7 @@ - (void)testConversionFromSecondsToMusicTimeStamp XCTAssertNotNil(s); self.sequencer.sequence = s; - XCTAssertEqual([self.sequencer musicTimeStampForTimeInSeconds:0.0], 0); + XCTAssertEqual([self.sequencer musicTimeStampForTimeInSeconds:0.0 options:MIKMIDISequencerTimeConversionOptionsNone], 0); } - (void)testBuiltinSynthesizers diff --git a/Source/MIKMIDISequencer.h b/Source/MIKMIDISequencer.h index e62a64b5..09c1e6fb 100644 --- a/Source/MIKMIDISequencer.h +++ b/Source/MIKMIDISequencer.h @@ -35,6 +35,25 @@ typedef NS_ENUM(NSInteger, MIKMIDISequencerClickTrackStatus) { MIKMIDISequencerClickTrackStatusAlwaysEnabled }; +typedef NS_OPTIONS(NSInteger, MIKMIDISequencerTimeConversionOptions) { + /** Use default options (consider tempo override and looping, don't unroll loops) */ + MIKMIDISequencerTimeConversionOptionsNone = 0, + /** Use the sequence's tempo events to calculate conversion, even if the sequencer has a tempo override set. The default is to use the overridden tempo for calculation if one is set.*/ + MIKMIDISequencerTimeConversionOptionsIgnoreTempoOverride = 1 << 0, + /** Calculate conversion as if looping were disabled. The default is to take into account looping if it is enabled on the sequencer.*/ + MIKMIDISequencerTimeConversionOptionsIgnoreLooping = 1 << 1, + /** When this option is set, conversion will return the time of events currently being played relative to the start of the sequence, and the result will never been greater than the end of the loop. The default, where this option is not set, is to calculate and return the absolute time since the sequence start. + + For example, consider a sequence that is 16 beats long, the tempo is a constant 75 bpm and looping is enabled for first 8 beats. The sequence will be exactly 20 seconds long, and the loop will consist of the first 10 seconds. + + If this option is *set*, and a time of 25 seconds is passed in, the result will be 4 beats, because the sequencer will be at the half way point of the loop on its third time through. If this option is *not set*, the result will be 20 beats, because 20 beats total will have elapsed since the start of the sequence. + + Setting the option allows you to determine what part of the raw sequence is currently being played, while leaving it unset allows you to determine total playback time. + + The same concept applies for conversion from beats to seconds.*/ + MIKMIDISequencerTimeConversionOptionsDontUnrollLoop = 1 << 2, +}; + NS_ASSUME_NONNULL_BEGIN /** @@ -257,8 +276,8 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - Time Conversion -- (NSTimeInterval)timeInSecondsForMusicTimeStamp:(MusicTimeStamp)musicTimeStamp ignoreLooping:(BOOL)ignoreLooping; -- (MusicTimeStamp)musicTimeStampForTimeInSeconds:(NSTimeInterval)timeInSeconds; +- (NSTimeInterval)timeInSecondsForMusicTimeStamp:(MusicTimeStamp)musicTimeStamp options:(MIKMIDISequencerTimeConversionOptions)options; +- (MusicTimeStamp)musicTimeStampForTimeInSeconds:(NSTimeInterval)timeInSeconds options:(MIKMIDISequencerTimeConversionOptions)options; #pragma mark - Properties diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index 3c13ea40..b358900b 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -659,24 +659,30 @@ - (MIKMIDISynthesizer *)builtinSynthesizerForTrack:(MIKMIDITrack *)track #pragma mark - Time Conversion -- (NSTimeInterval)timeInSecondsForMusicTimeStamp:(MusicTimeStamp)musicTimeStamp ignoreLooping:(BOOL)ignoreLooping +- (NSTimeInterval)timeInSecondsForMusicTimeStamp:(MusicTimeStamp)musicTimeStamp options:(MIKMIDISequencerTimeConversionOptions)options { + BOOL ignoreLooping = options & MIKMIDISequencerTimeConversionOptionsIgnoreLooping; if (!ignoreLooping && self.shouldLoop && musicTimeStamp >= self.loopEndTimeStamp) { + options |= MIKMIDISequencerTimeConversionOptionsIgnoreLooping; MusicTimeStamp loopDuration = self.loopEndTimeStamp - self.loopStartTimeStamp; - NSTimeInterval loopStartTimeInSeconds = [self timeInSecondsForMusicTimeStamp:self.loopStartTimeStamp ignoreLooping:YES]; - NSTimeInterval loopEndTimeInSeconds = [self timeInSecondsForMusicTimeStamp:self.loopEndTimeStamp ignoreLooping:YES]; + NSTimeInterval loopStartTimeInSeconds = [self timeInSecondsForMusicTimeStamp:self.loopStartTimeStamp options:options]; + NSTimeInterval loopEndTimeInSeconds = [self timeInSecondsForMusicTimeStamp:self.loopEndTimeStamp options:options]; NSTimeInterval loopDurationInSeconds = loopEndTimeInSeconds - loopStartTimeInSeconds; MusicTimeStamp scratch = musicTimeStamp; scratch -= self.loopStartTimeStamp; // Subtract off time before the loop NSTimeInterval result = loopStartTimeInSeconds; + BOOL shouldUnroll = !(options & MIKMIDISequencerTimeConversionOptionsDontUnrollLoop); // "Use up" the loops until we're down to a fraction of a loop while (scratch >= loopDuration) { - result += loopDurationInSeconds; + // Only add time for full loops to result if we're unrolling loops + if (shouldUnroll) { + result += loopDurationInSeconds; + } scratch -= loopDuration; } // Add the remaining fraction of a loop - result += [self timeInSecondsForMusicTimeStamp:(self.loopStartTimeStamp + scratch) ignoreLooping:YES]; + result += [self timeInSecondsForMusicTimeStamp:(self.loopStartTimeStamp + scratch) options:options]; result -= loopStartTimeInSeconds; return result; } @@ -719,7 +725,7 @@ - (NSTimeInterval)timeInSecondsForMusicTimeStamp:(MusicTimeStamp)musicTimeStamp return result; } -- (MusicTimeStamp)musicTimeStampForTimeInSeconds:(NSTimeInterval)timeInSeconds +- (MusicTimeStamp)musicTimeStampForTimeInSeconds:(NSTimeInterval)timeInSeconds options:(MIKMIDISequencerTimeConversionOptions)options { // if (self.tempo == 0 && !self.shouldLoop) { // If tempo is not overridden, and looping is not on // // Just use MIKMIDISequence's simple implementation From aeec51b9cc4c71259a5fcdd252e6b82e23fa3eec Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Sun, 1 Sep 2019 18:00:20 -0600 Subject: [PATCH 04/53] Issue #269: Implement -[MIKMIDISequencer timeInSecondsForMusicTimeStamp:options:] and add tests for it --- .../MIKMIDI Tests/MIKMIDISequencerTests.m | 151 +++++++++++++++++- Source/MIKMIDISequencer.m | 90 +++++++++-- 2 files changed, 229 insertions(+), 12 deletions(-) diff --git a/Framework/MIKMIDI Tests/MIKMIDISequencerTests.m b/Framework/MIKMIDI Tests/MIKMIDISequencerTests.m index b9a65933..4987e988 100644 --- a/Framework/MIKMIDI Tests/MIKMIDISequencerTests.m +++ b/Framework/MIKMIDI Tests/MIKMIDISequencerTests.m @@ -40,8 +40,10 @@ - (void)testConversionFromMusicTimeStampToSeconds self.sequencer.sequence = s; XCTAssertEqual([self.sequencer timeInSecondsForMusicTimeStamp:0 options:MIKMIDISequencerTimeConversionOptionsNone], 0.0); + // Test with negative number + XCTAssertEqual([self.sequencer timeInSecondsForMusicTimeStamp:-1 options:MIKMIDISequencerTimeConversionOptionsNone], 0.0); - XCTAssertEqualWithAccuracy([s timeInSecondsForMusicTimeStamp:3], [self.sequencer timeInSecondsForMusicTimeStamp:3 options:MIKMIDISequencerTimeConversionOptionsNone], 1e-6 ); + XCTAssertEqualWithAccuracy([s timeInSecondsForMusicTimeStamp:3], [self.sequencer timeInSecondsForMusicTimeStamp:3 options:MIKMIDISequencerTimeConversionOptionsNone], 1e-6); MIKMIDITempoEvent *secondTempo = s.tempoEvents[1]; MusicTimeStamp nextCheck = secondTempo.timeStamp+1; @@ -60,6 +62,11 @@ - (void)testConversionFromMusicTimeStampToSeconds actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + // Test while ignoring tempo override + expected = [s timeInSecondsForMusicTimeStamp:testTimeStamp]; + actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsIgnoreTempoOverride]; + XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + self.sequencer.tempo = 0; // Disable tempo override // Test with looping @@ -129,6 +136,148 @@ - (void)testConversionFromSecondsToMusicTimeStamp self.sequencer.sequence = s; XCTAssertEqual([self.sequencer musicTimeStampForTimeInSeconds:0.0 options:MIKMIDISequencerTimeConversionOptionsNone], 0); + + XCTAssertEqualWithAccuracy([s musicTimeStampForTimeInSeconds:3], [self.sequencer musicTimeStampForTimeInSeconds:3 options:MIKMIDISequencerTimeConversionOptionsNone], 1e-6); + + MIKMIDITempoEvent *secondTempo = s.tempoEvents[1]; + MusicTimeStamp nextCheckTimeStamp = secondTempo.timeStamp+1; + NSTimeInterval nextCheck = [s timeInSecondsForMusicTimeStamp:nextCheckTimeStamp]; + XCTAssertEqualWithAccuracy(nextCheckTimeStamp, [self.sequencer musicTimeStampForTimeInSeconds:nextCheck options:MIKMIDISequencerTimeConversionOptionsNone], 1e-6); + + NSTimeInterval testTimeStamp = 400; + MusicTimeStamp expected = [s musicTimeStampForTimeInSeconds:testTimeStamp]; + MusicTimeStamp actual = [self.sequencer musicTimeStampForTimeInSeconds:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; + XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + + // Test when tempo is overridden + Float64 overrideTempo = 80.0; + self.sequencer.tempo = overrideTempo; + + expected = overrideTempo * testTimeStamp / 60.0; + actual = [self.sequencer musicTimeStampForTimeInSeconds:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; + XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + + // Test while ignoring tempo override + expected = [s musicTimeStampForTimeInSeconds:testTimeStamp]; + actual = [self.sequencer musicTimeStampForTimeInSeconds:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsIgnoreTempoOverride]; + XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + + self.sequencer.tempo = 0; // Disable tempo override + + // Test with looping + self.sequencer.loop = YES; + [self.sequencer setLoopStartTimeStamp:10 endTimeStamp:15]; + + testTimeStamp = 5; // Test before loop region, should be unaffected + expected = [s musicTimeStampForTimeInSeconds:testTimeStamp]; + actual = [self.sequencer musicTimeStampForTimeInSeconds:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; + XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + + testTimeStamp = 8; // Test inside loop region before first loop, should be unaffected + expected = [s musicTimeStampForTimeInSeconds:testTimeStamp]; + actual = [self.sequencer musicTimeStampForTimeInSeconds:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; + XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + + testTimeStamp = 37.5; // Test inside loop region after first loop, should be affected + expected = 57.906294; + actual = [self.sequencer musicTimeStampForTimeInSeconds:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; + XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + + [self.sequencer setLoopStartTimeStamp:200 endTimeStamp:600]; + + testTimeStamp = 160; // Test before loop region, should be unaffected + expected = [s musicTimeStampForTimeInSeconds:testTimeStamp]; + actual = [self.sequencer musicTimeStampForTimeInSeconds:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; + XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + + testTimeStamp = 200; // Test inside loop region before first loop, should be unaffected + expected = [s musicTimeStampForTimeInSeconds:testTimeStamp]; + actual = [self.sequencer musicTimeStampForTimeInSeconds:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; + XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + + testTimeStamp = 450; // Test inside loop region after first loop, should be affected + expected = 896.188353; + actual = [self.sequencer musicTimeStampForTimeInSeconds:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; + XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + + testTimeStamp = 450; // Test inside loop region after first loop, but ignoring looping should be affected + NSTimeInterval withLooping = [self.sequencer musicTimeStampForTimeInSeconds:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; + NSTimeInterval ignoringLooping = [self.sequencer musicTimeStampForTimeInSeconds:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsIgnoreLooping]; + self.sequencer.loop = NO; + NSTimeInterval withoutLooping = [self.sequencer musicTimeStampForTimeInSeconds:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; + XCTAssertNotEqualWithAccuracy(withLooping, ignoringLooping, 1e-6); + XCTAssertEqualWithAccuracy(withoutLooping, ignoringLooping, 1e-6); + + // Test loop "unrolling" + self.sequencer.loop = YES; + [self.sequencer setLoopStartTimeStamp:10 endTimeStamp:15]; + testTimeStamp = 24.284925; // Test inside loop region after first loop, should be affected + NSTimeInterval withoutUnrolling = [self.sequencer musicTimeStampForTimeInSeconds:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsDontUnrollLoop]; + NSTimeInterval withUnrolling = [self.sequencer musicTimeStampForTimeInSeconds:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; + NSTimeInterval expectedWithoutUnrolling = 12.5; + ignoringLooping = [self.sequencer musicTimeStampForTimeInSeconds:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsIgnoreLooping]; + XCTAssertNotEqualWithAccuracy(withoutUnrolling, withUnrolling, 1e-6); + XCTAssertNotEqualWithAccuracy(withUnrolling, ignoringLooping, 1e-6); + XCTAssertEqualWithAccuracy(withoutUnrolling, expectedWithoutUnrolling, 1e-6); +} + +- (void)testTwoWayConversionBetweenMusicTimeStampAndSeconds +{ + NSBundle *bundle = [NSBundle bundleForClass:[self class]]; + NSURL *testMIDIFileURL = [bundle URLForResource:@"tempochanges" withExtension:@"mid"]; + NSError *error = nil; + MIKMIDISequence *s = [MIKMIDISequence sequenceWithFileAtURL:testMIDIFileURL convertMIDIChannelsToTracks:NO error:&error]; + XCTAssertNotNil(s); + self.sequencer.sequence = s; + + NSArray *testMusicTimeStamps = @[@3, @5, @12, @24.284925, @37.5, @57.906294, @160, @550, @850, @896.188353]; + + // Test with default conversion options + MIKMIDISequencerTimeConversionOptions options = MIKMIDISequencerTimeConversionOptionsNone; + for (NSNumber *number in testMusicTimeStamps) { + MusicTimeStamp timeStamp = [number doubleValue]; + NSTimeInterval timeInSeconds = [self.sequencer timeInSecondsForMusicTimeStamp:timeStamp options:options]; + MusicTimeStamp convertedTimeStamp = [self.sequencer musicTimeStampForTimeInSeconds:timeInSeconds options:options]; + NSTimeInterval convertedTimeInSeconds = [self.sequencer timeInSecondsForMusicTimeStamp:convertedTimeStamp options:options]; + XCTAssertEqualWithAccuracy(timeStamp, convertedTimeStamp, 2e-6); + XCTAssertEqualWithAccuracy(timeInSeconds, convertedTimeInSeconds, 2e-6); + } + + // Test with tempo override on + self.sequencer.tempo = 80.0; + for (NSNumber *number in testMusicTimeStamps) { + MusicTimeStamp timeStamp = [number doubleValue]; + NSTimeInterval timeInSeconds = [self.sequencer timeInSecondsForMusicTimeStamp:timeStamp options:options]; + MusicTimeStamp convertedTimeStamp = [self.sequencer musicTimeStampForTimeInSeconds:timeInSeconds options:options]; + NSTimeInterval convertedTimeInSeconds = [self.sequencer timeInSecondsForMusicTimeStamp:convertedTimeStamp options:options]; + XCTAssertEqualWithAccuracy(timeStamp, convertedTimeStamp, 2e-6); + XCTAssertEqualWithAccuracy(timeInSeconds, convertedTimeInSeconds, 2e-6); + } + + // Test ignoring temp override + options = MIKMIDISequencerTimeConversionOptionsIgnoreTempoOverride; + for (NSNumber *number in testMusicTimeStamps) { + MusicTimeStamp timeStamp = [number doubleValue]; + NSTimeInterval timeInSeconds = [self.sequencer timeInSecondsForMusicTimeStamp:timeStamp options:options]; + MusicTimeStamp convertedTimeStamp = [self.sequencer musicTimeStampForTimeInSeconds:timeInSeconds options:options]; + NSTimeInterval convertedTimeInSeconds = [self.sequencer timeInSecondsForMusicTimeStamp:convertedTimeStamp options:options]; + XCTAssertEqualWithAccuracy(timeStamp, convertedTimeStamp, 2e-6); + XCTAssertEqualWithAccuracy(timeInSeconds, convertedTimeInSeconds, 2e-6); + } + + // Test with looping + self.sequencer.tempo = 0; + self.sequencer.loop = YES; + [self.sequencer setLoopStartTimeStamp:200 endTimeStamp:600]; + options = MIKMIDISequencerTimeConversionOptionsNone; + for (NSNumber *number in testMusicTimeStamps) { + MusicTimeStamp timeStamp = [number doubleValue]; + NSTimeInterval timeInSeconds = [self.sequencer timeInSecondsForMusicTimeStamp:timeStamp options:options]; + MusicTimeStamp convertedTimeStamp = [self.sequencer musicTimeStampForTimeInSeconds:timeInSeconds options:options]; + NSTimeInterval convertedTimeInSeconds = [self.sequencer timeInSecondsForMusicTimeStamp:convertedTimeStamp options:options]; + XCTAssertEqualWithAccuracy(timeStamp, convertedTimeStamp, 2e-6); + XCTAssertEqualWithAccuracy(timeInSeconds, convertedTimeInSeconds, 2e-6); + } } - (void)testBuiltinSynthesizers diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index b358900b..c422dd8f 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -661,7 +661,12 @@ - (MIKMIDISynthesizer *)builtinSynthesizerForTrack:(MIKMIDITrack *)track - (NSTimeInterval)timeInSecondsForMusicTimeStamp:(MusicTimeStamp)musicTimeStamp options:(MIKMIDISequencerTimeConversionOptions)options { + if (musicTimeStamp < 0) { return 0; } + + BOOL shouldIgnoreTempoOverride = options & MIKMIDISequencerTimeConversionOptionsIgnoreTempoOverride; BOOL ignoreLooping = options & MIKMIDISequencerTimeConversionOptionsIgnoreLooping; + + // If result is beyond the end of the loop if (!ignoreLooping && self.shouldLoop && musicTimeStamp >= self.loopEndTimeStamp) { options |= MIKMIDISequencerTimeConversionOptionsIgnoreLooping; MusicTimeStamp loopDuration = self.loopEndTimeStamp - self.loopStartTimeStamp; @@ -689,7 +694,7 @@ - (NSTimeInterval)timeInSecondsForMusicTimeStamp:(MusicTimeStamp)musicTimeStamp // Calculate initial tempo, handling case where sequence doesn't specify one. NSArray *tempoEvents = self.sequence.tempoEvents; - if (self.tempo != 0) { // Overridden tempo that should be used instead of events in the tempo track + if (self.tempo != 0 && !shouldIgnoreTempoOverride) { // Overridden tempo that should be used instead of events in the tempo track tempoEvents = @[[MIKMIDITempoEvent tempoEventWithTimeStamp:0 tempo:self.tempo]]; } else { NSUInteger tempoAtZeroIndex = [tempoEvents indexOfObjectPassingTest:^BOOL(MIKMIDITempoEvent *event, NSUInteger i, BOOL *s) { @@ -704,13 +709,13 @@ - (NSTimeInterval)timeInSecondsForMusicTimeStamp:(MusicTimeStamp)musicTimeStamp } // Get tempo events that affect the result (ie. come before musicTimeStamp) and sort them in ascending order + // Check if result would be affected by loop + BOOL timeIsInLoop = self.shouldLoop && musicTimeStamp >= self.loopEndTimeStamp && !ignoreLooping; NSIndexSet *indexesOfTempoEventsAffectingResult = [tempoEvents indexesOfObjectsPassingTest:^BOOL(MIKMIDITempoEvent *event, NSUInteger i, BOOL *s) { - if (!self.shouldLoop || musicTimeStamp < self.loopEndTimeStamp || ignoreLooping) { - return event.timeStamp <= musicTimeStamp; - } - // musicTimeStamp is within the loop region, so include all tempo events up to the end of the loop - return musicTimeStamp <= self.loopEndTimeStamp; + // if musicTimeStamp is within the loop region, include all tempo events up to the end of the loop + MusicTimeStamp limit = timeIsInLoop ? self.loopEndTimeStamp : musicTimeStamp; + return event.timeStamp <= limit; }]; NSArray *tempoEventsAffectingResult = [tempoEvents objectsAtIndexes:indexesOfTempoEventsAffectingResult]; tempoEvents = [tempoEventsAffectingResult sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"timeStamp" ascending:YES]]]; @@ -727,11 +732,74 @@ - (NSTimeInterval)timeInSecondsForMusicTimeStamp:(MusicTimeStamp)musicTimeStamp - (MusicTimeStamp)musicTimeStampForTimeInSeconds:(NSTimeInterval)timeInSeconds options:(MIKMIDISequencerTimeConversionOptions)options { - // if (self.tempo == 0 && !self.shouldLoop) { // If tempo is not overridden, and looping is not on - // // Just use MIKMIDISequence's simple implementation - // return [self.sequence musicTimeStampForTimeInSeconds:timeInSeconds]; - // } - return 0; + BOOL shouldIgnoreTempoOverride = options & MIKMIDISequencerTimeConversionOptionsIgnoreTempoOverride; + BOOL ignoreLooping = options & MIKMIDISequencerTimeConversionOptionsIgnoreLooping; + + NSTimeInterval loopEndTimeInSeconds = self.loopEndTimeStamp > 0 ? [self timeInSecondsForMusicTimeStamp:self.loopEndTimeStamp options:MIKMIDISequencerTimeConversionOptionsIgnoreLooping] : self.sequence.durationInSeconds; + // If result is beyond the end of the loop + if (!ignoreLooping && self.shouldLoop && timeInSeconds >= loopEndTimeInSeconds) { + options |= MIKMIDISequencerTimeConversionOptionsIgnoreLooping; + MusicTimeStamp loopDuration = self.loopEndTimeStamp - self.loopStartTimeStamp; + NSTimeInterval loopStartTimeInSeconds = [self timeInSecondsForMusicTimeStamp:self.loopStartTimeStamp options:options]; + NSTimeInterval loopDurationInSeconds = loopEndTimeInSeconds - loopStartTimeInSeconds; + + NSTimeInterval scratch = timeInSeconds; + scratch -= loopStartTimeInSeconds; // Subtract off time before the loop + MusicTimeStamp result = self.loopStartTimeStamp; + BOOL shouldUnroll = !(options & MIKMIDISequencerTimeConversionOptionsDontUnrollLoop); + // "Use up" the loops until we're down to a fraction of a loop + while (scratch >= loopDurationInSeconds) { + // Only add time for full loops to result if we're unrolling loops + if (shouldUnroll) { + result += loopDuration; + } + scratch -= loopDurationInSeconds; + } + // Add the remaining fraction of a loop + result += [self musicTimeStampForTimeInSeconds:(loopStartTimeInSeconds + scratch) options:options]; + result -= self.loopStartTimeStamp; + return result; + } + + // Calculate initial tempo, handling case where sequence doesn't specify one. + NSArray *tempoEvents = self.sequence.tempoEvents; + if (self.tempo != 0 && !shouldIgnoreTempoOverride) { // Overridden tempo that should be used instead of events in the tempo track + tempoEvents = @[[MIKMIDITempoEvent tempoEventWithTimeStamp:0 tempo:self.tempo]]; + } else { + NSUInteger tempoAtZeroIndex = [tempoEvents indexOfObjectPassingTest:^BOOL(MIKMIDITempoEvent *event, NSUInteger i, BOOL *s) { + return event.timeStamp == 0; + }]; + if (tempoAtZeroIndex == NSNotFound) { + NSMutableArray *scratch = [tempoEvents mutableCopy]; + MIKMIDITempoEvent *initialTempo = [MIKMIDITempoEvent tempoEventWithTimeStamp:0 tempo:kDefaultTempo]; + [scratch insertObject:initialTempo atIndex:0]; + tempoEvents = [scratch copy]; + } + } + + // Get tempo events that affect the result (ie. come before timeInSeconds) and sort them in ascending order + // Check if result would be affected by loop + BOOL timeIsInLoop = self.shouldLoop && timeInSeconds >= loopEndTimeInSeconds && !ignoreLooping; + NSIndexSet *indexesOfTempoEventsAffectingResult = + [tempoEvents indexesOfObjectsPassingTest:^BOOL(MIKMIDITempoEvent *event, NSUInteger i, BOOL *s) { + // if timeInSeconds is within the loop region, include all tempo events up to the end of the loop + NSTimeInterval limit = timeIsInLoop ? loopEndTimeInSeconds : timeInSeconds; + return [self.sequence timeInSecondsForMusicTimeStamp:event.timeStamp] <= limit; + }]; + NSArray *tempoEventsAffectingResult = [tempoEvents objectsAtIndexes:indexesOfTempoEventsAffectingResult]; + tempoEvents = [tempoEventsAffectingResult sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"timeStamp" ascending:YES]]]; + + MusicTimeStamp result = 0.0; + MIKMIDITempoEvent *lastTempoEvent = tempoEvents[0]; + for (MIKMIDITempoEvent *tempoEvent in tempoEvents) { + NSTimeInterval tempoTimeInSeconds = [self.sequence timeInSecondsForMusicTimeStamp:tempoEvent.timeStamp]; + NSTimeInterval lastTempoTimeInSeconds = [self.sequence timeInSecondsForMusicTimeStamp:lastTempoEvent.timeStamp]; + result += lastTempoEvent.bpm * (tempoTimeInSeconds - lastTempoTimeInSeconds) / 60.0; + lastTempoEvent = tempoEvent; + } + NSTimeInterval lastTempoTimeInSeconds = [self.sequence timeInSecondsForMusicTimeStamp:lastTempoEvent.timeStamp]; + result += lastTempoEvent.bpm * (timeInSeconds - lastTempoTimeInSeconds) / 60.0; + return result; } #pragma mark - Click Track From eb52ab170b676b14566185979bedd203ad175247 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Sun, 1 Sep 2019 18:12:28 -0600 Subject: [PATCH 05/53] Issue #269: Add documentation for time conversion methods. --- Source/MIKMIDISequence.h | 32 ++++++++++++++++++++++++++++++++ Source/MIKMIDISequencer.h | 25 +++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/Source/MIKMIDISequence.h b/Source/MIKMIDISequence.h index 18121684..003178e5 100644 --- a/Source/MIKMIDISequence.h +++ b/Source/MIKMIDISequence.h @@ -273,7 +273,39 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - Timing + +/** Returns the time in seconds for a given MusicTimeStamp (time in beats) in the sequence. + * + * This method converts a time in beats to the corresponding time in seconds in the sequence, taking into account the tempo of the sequence, including tempo changes. + * + * @note This methhod only considers the sequence itself. If you're playing the sequence using an MIKMIDISequencer, + * you should use the corresponding methods on MIKMIDISequencer, which take into account looping, tempo overrides, and provide options + * to control the details of the conversion algorithm. + * + * @param musicTimeStamp The time in beats you want to convert to seconds. + * + * @return A time in seconds as an NSTimeInterval. + * + * @see -musicTimeStampForTimeInSeconds: + * @see -[MIKMIDISequencer timeInSecondsForMusicTimeStamp:options:] + */ - (NSTimeInterval)timeInSecondsForMusicTimeStamp:(MusicTimeStamp)musicTimeStamp; + +/** Returns the time in beats for a given time in seconds in the sequence. +* +* This method converts a time in seconds to the corresponding time in beats in the sequence, taking into account the tempo of the sequence, including tempo changes. +* +* @note This methhod only considers the sequence itself. If you're playing the sequence using an MIKMIDISequencer, +* you should use the corresponding methods on MIKMIDISequencer, which take into account looping, tempo overrides, and provide options +* to control the details of the conversion algorithm. +* +* @param timeInSeconds The time in seconds you want to convert to a MusicTimeStamp (beats). +* +* @return A time in beats as a MusicTimeStamp. +* +* @see -timeInSecondsForMusicTimeStamp: +* @see -[MIKMIDISequencer timeInSecondsForMusicTimeStamp:options:] +*/ - (MusicTimeStamp)musicTimeStampForTimeInSeconds:(NSTimeInterval)timeInSeconds; #pragma mark - Properties diff --git a/Source/MIKMIDISequencer.h b/Source/MIKMIDISequencer.h index 09c1e6fb..0fc92660 100644 --- a/Source/MIKMIDISequencer.h +++ b/Source/MIKMIDISequencer.h @@ -276,7 +276,32 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - Time Conversion + +/** Returns the time in seconds for a given MusicTimeStamp (time in beats). + * + * This method converts a time in beats to the corresponding time in seconds on the sequencer, taking into account the tempo of the sequence, including tempo changes. + * By default, looping and an overridden tempo, if enabled, will be considered when calculating the result. This behavior can be changed by passing in the appropriate options. + * + * @param musicTimeStamp The time in beats you want to convert to seconds. + * @param options Options to control the details of the conversion algorithm. See MIKMIDISequencerTimeConversionOptions for a list of possible options. + * + * @return A time in seconds as an NSTimeInterval. + * + * @see -musicTimeStampForTimeInSeconds:options: + * @see -[MIKMIDISequence musicTimeStampForTimeInSeconds:] + */ - (NSTimeInterval)timeInSecondsForMusicTimeStamp:(MusicTimeStamp)musicTimeStamp options:(MIKMIDISequencerTimeConversionOptions)options; + +/** Returns the time in beats for a given time in seconds. + * + * @param timeInSeconds The time in seconds you want to convert to a MusicTimeStamp (beats). + * @param options Options to control the details of the conversion algorithm. See MIKMIDISequencerTimeConversionOptions for a list of possible options. + * + * @return A time in beats as a MusicTimeStamp. + * + * @see -timeInSecondsForMusicTimeStamp:options: + * @see -[MIKMIDISequence timeInSecondsForMusicTimeStamp:] + */ - (MusicTimeStamp)musicTimeStampForTimeInSeconds:(NSTimeInterval)timeInSeconds options:(MIKMIDISequencerTimeConversionOptions)options; #pragma mark - Properties From 38a65cc43b6290530422185752e3f88bcc10fbd7 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Mon, 2 Sep 2019 23:08:00 -0600 Subject: [PATCH 06/53] Fix some methods that bridge weirdly into Swift, particularly initializers where bridged methods incorrectly took an error: Void argument. Fixes issue #272. --- Source/MIKMIDIClientSourceEndpoint.h | 4 ++-- Source/MIKMIDIMapping.h | 4 ++-- Source/MIKMIDIMappingGenerator.h | 6 +++--- Source/MIKMIDISequence.h | 2 +- Source/MIKMIDISynthesizer.h | 6 +++--- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Source/MIKMIDIClientSourceEndpoint.h b/Source/MIKMIDIClientSourceEndpoint.h index 0986b0cd..3e52befc 100644 --- a/Source/MIKMIDIClientSourceEndpoint.h +++ b/Source/MIKMIDIClientSourceEndpoint.h @@ -33,7 +33,7 @@ NS_ASSUME_NONNULL_BEGIN * * @return An instance of MIKMIDIClientSourceEndpoint, or nil if an error occurs. */ -- (instancetype)initWithName:(NSString *)name error:(NSError **)error; +- (nullable instancetype)initWithName:(NSString *)name error:(NSError **)error NS_SWIFT_NAME(init(name:)); /** * Used to send MIDI messages/commands from your application to a MIDI output endpoint. @@ -70,4 +70,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIMapping.h b/Source/MIKMIDIMapping.h index 68e30bf4..646222e8 100644 --- a/Source/MIKMIDIMapping.h +++ b/Source/MIKMIDIMapping.h @@ -87,7 +87,7 @@ NS_ASSUME_NONNULL_BEGIN * * @return An initialized MIKMIDIMapping instance, or nil if an error occurred. */ -- (nullable instancetype)initWithFileAtURL:(NSURL *)url error:(NSError **)error; +- (nullable instancetype)initWithFileAtURL:(NSURL *)url error:(NSError **)error NS_SWIFT_NAME(init(fileAt:)); /** * Creates and initializes an MIKMIDIMapping object that is the same as the passed in bundled mapping @@ -263,4 +263,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIMappingGenerator.h b/Source/MIKMIDIMappingGenerator.h index 27832d22..d46854d1 100644 --- a/Source/MIKMIDIMappingGenerator.h +++ b/Source/MIKMIDIMappingGenerator.h @@ -55,7 +55,7 @@ typedef void(^MIKMIDIMappingGeneratorMappingCompletionBlock)(MIKMIDIMappingItem * * @return An initialized MIKMIDIMappingGenerator instance, or nil if an error occurred. */ -+ (instancetype)mappingGeneratorWithDevice:(MIKMIDIDevice *)device error:(NSError **)error; ++ (nullable instancetype)mappingGeneratorWithDevice:(MIKMIDIDevice *)device error:(NSError **)error; /** * Creates and initializes a mapping generator for a MIKMIDIDevice. @@ -68,7 +68,7 @@ typedef void(^MIKMIDIMappingGeneratorMappingCompletionBlock)(MIKMIDIMappingItem * * @return An initialized MIKMIDIMappingGenerator instance, or nil if an error occurred. */ -- (instancetype)initWithDevice:(MIKMIDIDevice *)device error:(NSError **)error NS_DESIGNATED_INITIALIZER; +- (nullable instancetype)initWithDevice:(MIKMIDIDevice *)device error:(NSError **)error NS_DESIGNATED_INITIALIZER; /** * Begins mapping a given MIDIResponder. This method returns immediately. @@ -232,4 +232,4 @@ shouldRemoveExistingMappingItems:(MIKSetOf(MIKMIDIMappingItem *) *)mappingItems @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDISequence.h b/Source/MIKMIDISequence.h index 2c442ff5..682a6c09 100644 --- a/Source/MIKMIDISequence.h +++ b/Source/MIKMIDISequence.h @@ -120,7 +120,7 @@ NS_ASSUME_NONNULL_BEGIN * @return If an error occurs, upon return contains an NSError object that describes the problem. If you are not interested in possible errors, * you may pass in NULL. */ -- (nullable instancetype)initWithData:(NSData *)data error:(NSError **)error; +- (nullable instancetype)initWithData:(NSData *)data error:(NSError **)error NS_SWIFT_NAME(init(data:)); /** * Initializes a new instance of MIKMIDISequence from MIDI data. diff --git a/Source/MIKMIDISynthesizer.h b/Source/MIKMIDISynthesizer.h index 332a21a6..95714b94 100644 --- a/Source/MIKMIDISynthesizer.h +++ b/Source/MIKMIDISynthesizer.h @@ -40,7 +40,7 @@ NS_ASSUME_NONNULL_BEGIN * * @return An initialized MIKMIDIEndpointSynthesizer or nil if an error occurs. */ -- (nullable instancetype)initWithError:(NSError **)error; +- (nullable instancetype)initWithError:(NSError **)error NS_SWIFT_NAME(init()); /** * Initializes an MIKMIDISynthesizer instance which uses an audio unit matching @@ -52,7 +52,7 @@ NS_ASSUME_NONNULL_BEGIN * * @return An initialized MIKMIDIEndpointSynthesizer or nil if an error occurs. */ -- (nullable instancetype)initWithAudioUnitDescription:(AudioComponentDescription)componentDescription error:(NSError **)error NS_DESIGNATED_INITIALIZER; +- (nullable instancetype)initWithAudioUnitDescription:(AudioComponentDescription)componentDescription error:(NSError **)error NS_DESIGNATED_INITIALIZER NS_SWIFT_NAME(init(audioUnitDescription:)); /** * This synthesizer's available instruments. An array of @@ -76,7 +76,7 @@ NS_ASSUME_NONNULL_BEGIN * * @see +[MIKMIDISynthesizerInstrument availableInstruments] */ -- (BOOL)selectInstrument:(MIKMIDISynthesizerInstrument *)instrument error:(NSError **)error; +- (BOOL)selectInstrument:(MIKMIDISynthesizerInstrument *)instrument error:(NSError **)error NS_SWIFT_NAME(select(instrument:)); /** * Loads the sound font (.dls or .sf2) file at fileURL. From a88004127947a687516bd52d344632efe688f1fd Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Mon, 7 Oct 2019 23:36:19 -0600 Subject: [PATCH 07/53] Add rate property to MIKMIDISequencer. Fixes #273 --- .../Resources/Base.lproj/MainWindow.xib | 43 +++++++++++++++---- Source/MIKMIDISequencer.h | 11 ++++- Source/MIKMIDISequencer.m | 16 +++++-- 3 files changed, 57 insertions(+), 13 deletions(-) diff --git a/Examples/macOS/MIDI Files Testbed/Resources/Base.lproj/MainWindow.xib b/Examples/macOS/MIDI Files Testbed/Resources/Base.lproj/MainWindow.xib index b098e60d..f49bec6b 100644 --- a/Examples/macOS/MIDI Files Testbed/Resources/Base.lproj/MainWindow.xib +++ b/Examples/macOS/MIDI Files Testbed/Resources/Base.lproj/MainWindow.xib @@ -1,8 +1,9 @@ - - + + - + + @@ -17,7 +18,7 @@ - + @@ -41,10 +42,10 @@ - + - + @@ -109,7 +110,7 @@ - + @@ -139,28 +140,52 @@ - + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/MIKMIDISequencer.h b/Source/MIKMIDISequencer.h index 790212d3..cdda4232 100644 --- a/Source/MIKMIDISequencer.h +++ b/Source/MIKMIDISequencer.h @@ -281,6 +281,15 @@ NS_ASSUME_NONNULL_BEGIN */ @property (readonly, nonatomic, getter=isRecording) BOOL recording; +/** + * @property rate + * @abstract The playback rate of the sequencer. For example, if rate is 2.0, the sequencer will play twice as fast as normal. + * Unlike the tempo property, this does not override the tempos in the sequence's tempo track. Rather, they are adjusted by multiplying by this rate. + * @discussion + * 1.0 is normal playback rate. Rate must be > 0.0. +*/ +@property (nonatomic) float rate; + /** * The tempo the sequencer should play its sequence at. When set to 0, the sequence will be played using * the tempo events from the sequence's tempo track. Default is 0. @@ -475,4 +484,4 @@ FOUNDATION_EXPORT NSString * const MIKMIDISequencerWillLoopNotification; */ FOUNDATION_EXPORT const MusicTimeStamp MIKMIDISequencerEndOfSequenceLoopEndTimeStamp; -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index b226efe2..76d19d41 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -119,6 +119,7 @@ - (instancetype)initWithSequence:(MIKMIDISequence *)sequence _processingQueueKey = &_processingQueueKey; _processingQueueContext = &_processingQueueContext; _maximumLookAheadInterval = 0.1; + _rate = 1.0; } return self; } @@ -193,6 +194,7 @@ - (void)startPlaybackAtTimeStamp:(MusicTimeStamp)timeStamp MIDITimeStamp:(MIDITi Float64 startingTempo = [self.sequence tempoAtTimeStamp:timeStamp]; if (!startingTempo) startingTempo = kDefaultTempo; + startingTempo *= self.rate; [self updateClockWithMusicTimeStamp:timeStamp tempo:startingTempo atMIDITimeStamp:midiTimeStamp]; }); @@ -314,8 +316,8 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam if (self.needsCurrentTempoUpdate) { if (!tempoEventsByTimeStamp.count) { - if (!overrideTempo) overrideTempo = [sequence tempoAtTimeStamp:fromMusicTimeStamp]; - if (!overrideTempo) overrideTempo = kDefaultTempo; + if (!overrideTempo) overrideTempo = [sequence tempoAtTimeStamp:fromMusicTimeStamp] * self.rate; + if (!overrideTempo) overrideTempo = kDefaultTempo * self.rate; MIKMIDITempoEvent *tempoEvent = [MIKMIDITempoEvent tempoEventWithTimeStamp:fromMusicTimeStamp tempo:overrideTempo]; NSNumber *timeStampKey = @(fromMusicTimeStamp); @@ -393,7 +395,7 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam if (midiTimeStamp < MIKMIDIGetCurrentTimeStamp() && midiTimeStamp > fromMIDITimeStamp) continue; // prevents events that were just recorded from being scheduled MIKMIDITempoEvent *tempoEventAtTimeStamp = tempoEventsByTimeStamp[timeStampKey]; - if (tempoEventAtTimeStamp) [self updateClockWithMusicTimeStamp:musicTimeStamp tempo:tempoEventAtTimeStamp.bpm atMIDITimeStamp:midiTimeStamp]; + if (tempoEventAtTimeStamp) [self updateClockWithMusicTimeStamp:musicTimeStamp tempo:tempoEventAtTimeStamp.bpm * self.rate atMIDITimeStamp:midiTimeStamp]; NSArray *events = allEventsByTimeStamp[timeStampKey]; for (id eventObject in events) { @@ -809,6 +811,14 @@ - (MIKMIDIMetronome *)metronome #endif } +- (void)setRate:(float)rate +{ + if (rate != _rate && rate > 0.0) { + _rate = rate; + if (self.isPlaying) self.needsCurrentTempoUpdate = YES; + } +} + - (void)setTempo:(Float64)tempo { if (tempo < 0) tempo = 0; From e9845c8ef93a7c61a336161033e6ee66f5746170 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Sun, 1 Sep 2019 23:02:12 -0600 Subject: [PATCH 08/53] Create FUNDING.yml --- .github/FUNDING.yml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..1c59cfdd --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# These are supported funding model platforms + +github: armadsen From 4d97fa84bc4b60233600ca51d0ba7f3b5ab45e48 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Mon, 2 Sep 2019 23:08:44 -0600 Subject: [PATCH 09/53] Update README.md --- README.md | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index e7fdc9b9..78b50ba8 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,9 @@ This README file is meant to give a broad overview of MIKMIDI. More complete doc MIKMIDI ------- -MIKMIDI is an easy-to-use Objective-C MIDI library created by Andrew Madsen and developed by him and Chris Flesner of [Mixed In Key](http://www.mixedinkey.com/). It's useful for programmers writing Objective-C or Swift OS X or iOS apps that use MIDI. It includes the ability to communicate with external MIDI devices, to read and write MIDI files, to record and play back MIDI, etc. MIKMIDI is used to provide MIDI functionality in the OS X versions of our DJ app, [Flow](http://flowdjsoftware.com), our flagship app [Mixed In Key](http://www.mixedinkey.com/), and our composition software, [Odesi](http://odesi.mixedinkey.com). +MIKMIDI is an easy-to-use Mac and iOS MIDI library created by Andrew Madsen and developed by him and Chris Flesner of [Mixed In Key](http://www.mixedinkey.com/). It's useful for programmers writing Objective-C or Swift macOS or iOS apps that use MIDI. It includes the ability to communicate with external MIDI devices, to read and write MIDI files, to record and play back MIDI, etc. MIKMIDI is used to provide MIDI functionality in the Mac versions of our DJ app, [Flow](http://flowdjsoftware.com), our flagship app [Mixed In Key](http://www.mixedinkey.com/), and our composition software, [Odesi](http://odesi.mixedinkey.com). -MIKMIDI can be used in projects targeting Mac OS X 10.7 and later, and iOS 6 and later. The example code in this readme is in Objective-C. However, MIKMIDI can also easily be used from Swift code. +MIKMIDI can be used in projects targeting Mac OS X 10.7 and later, and iOS 6 and later. The example code in this readme is in Swift. However, MIKMIDI can also easily be used from Objective-C code. MIKMIDI is released under an MIT license, meaning you're free to use it in both closed and open source projects. However, even in a closed source project, you must include a publicly-accessible copy of MIKMIDI's copyright notice, which you can find in the LICENSE file. @@ -16,6 +16,8 @@ How To Use MIKMIDI MIKMIDI ships with a project to build frameworks for iOS and macOS. You can also install it using CocoaPods or Carthage. See [this page](https://github.com/mixedinkey-opensource/MIKMIDI/wiki/Installing-MIKMIDI) on the MIKMIDI wiki for detailed instructions for adding MIKMIDI to your project. +*A note about Swift*: MIKMIDI is written in Objective-C, but fully supports Swift. The only caveat is that API changes that affect only Swift, but not Objective-C, such as improved nullability annotation, refined API names for Swift, etc., are *not* limited to major versions, but rather will sometimes be included in minor version releases. Bug fix/patch releases will not break Swift *or* Objective-C API. Objective-C API will be stable within a major version, e.g. 1.y.z. + MIKMIDI Overview ---------------- @@ -26,7 +28,7 @@ MIKMIDI is not limited to Objective-C interfaces for existing CoreMIDI functiona To understand MIKMIDI, it's helpful to break it down into its major subsystems: - Device support -- includes support for device discovery, connection/disconnection, and sending/receiving MIDI messages. -- Commands -- includes a number of Objective-C classes that various represent MIDI message types as received from and sent to MIDI devices and endpoints. +- Commands -- includes a number of classes that represent various MIDI message types as received from and sent to MIDI devices and endpoints. - Mapping -- support for generating, saving, loading, and using files that associate physical MIDI controls with corresponding application features. - Files -- support for reading and writing MIDI files. - Synthesis -- support for turning MIDI into audio, e.g. playback of MIDI files and incoming MIDI keyboard input. @@ -48,21 +50,18 @@ MIKMIDI's device support architecture is based on the underlying CoreMIDI archit `MIKMIDIDeviceManager` is a singleton class used for device discovery, and to send and receive MIDI messages to and from endpoints. To get a list of MIDI devices available on the system, call `-availableDevices` on the shared device manager: - NSArray *availableMIDIDevices = [[MIKMIDIDeviceManager sharedDeviceManager] availableDevices]; + let availableDevices = MIKMIDIDeviceManager.shared.availableDevices `MIKMIDIDeviceManager` also includes the ability to retrieve 'virtual' endpoints, to enable communicating with other MIDI apps, or with devices (e.g. Native Instruments controllers) which present as virtual endpoints rather than physical devices. `MIKMIDIDeviceManager`'s `availableDevices`, and `virtualSources` and `virtualDestinations` properties are Key Value Observing (KVO) compliant. This means that for example, `availableDevices` can be bound to an `NSPopupMenu` in an OS X app to provide an automatically updated list of connected MIDI devices. They can also be directly observed using key value observing to be notified when devices are connected or disconnected, etc. Additionally, `MIKMIDIDeviceManager` posts these notifications: `MIKMIDIDeviceWasAddedNotification`, `MIKMIDIDeviceWasRemovedNotification`, `MIKMIDIVirtualEndpointWasAddedNotification`, `MIKMIDIVirtualEndpointWasRemovedNotification`. -`MIKMIDIDeviceManager` is used to sign up to receive messages from MIDI endpoints as well as to send them. To receive messages from a `MIKMIDISourceEndpoint`, you must connect the endpoint and supply an event handler block to be called anytime messages are received. This is done using the `-connectInput:error:eventHandler:` method. When you no longer want to receive messages, you must call the `-disconnectInput:` method. To send MIDI messages to an `MIKMIDIDestinationEndpoint`, call `-[MIKMIDIDeviceManager sendCommands:toEndpoint:error:]` passing an `NSArray` of `MIKMIDICommand` instances. For example: - -```objective-c -NSDate *date = [NSDate date]; -MIKMIDINoteOnCommand *noteOn = [MIKMIDINoteOnCommand noteOnCommandWithNote:60 velocity:127 channel:0 timestamp:date]; -MIKMIDINoteOffCommand *noteOff = [MIKMIDINoteOffCommand noteOffCommandWithNote:60 velocity:0 channel:0 timestamp:[date dateByAddingTimeInterval:0.5]]; +`MIKMIDIDeviceManager` is used to sign up to receive messages from MIDI devices as well as to send them. To receive messages from a `MIKMIDIDevice`, you must connect the device and supply an event handler block to be called anytime messages are received. This is done using the `connect(_:, eventHandler:)` method. When you no longer want to receive messages, you must call the `disconnectConnection(forToken:)` method. To send MIDI messages to an `MIKMIDIDevice`, get the appropriate `MIKMIDIDestinationEndpoint` from the device, then call `MIKMIDIDeviceManager.send(_: [MIKMIDICommand], to:)` passing an array of `MIKMIDICommand` instances. For example: -MIKMIDIDeviceManager *dm = [MIKMIDIDeviceManager sharedDeviceManager]; -[dm sendCommands:@[noteOn, noteOff] toEndpoint:destinationEndpoint error:&error]; +```swift +let noteOn = MIKMIDINoteOnCommand(note: 60, velocity: 127, channel: 0, timestamp: Date()) +let noteOff = MIKMIDINoteOffCommand(note: 60, velocity: 127, channel: 0, timestamp: Date().advanced(by: 0.5)) +try MIKMIDIDeviceManager.shared.send([noteOn, noteOff], to: destinationEndpoint) ``` If you've used CoreMIDI before, you may be familiar with `MIDIClientRef` and `MIDIPortRef`. These are used internally by MIKMIDI, but the "public" API for MIKMIDI does not expose them -- or their Objective-C counterparts -- directly. Rather, `MIKMIDIDeviceManager` itself allows sending and receiving messages to/from `MIKMIDIEndpoint`s. @@ -70,7 +69,7 @@ If you've used CoreMIDI before, you may be familiar with `MIDIClientRef` and `MI MIDI Messages ------------- -In MIKMIDI, MIDI messages are Objective-C objects. These objects are instances of concrete subclasses of `MIKMIDICommand`. Each MIDI message type (e.g. Control Change, Note On, System Exclusive, etc.) has a corresponding class (e.g. MIKMIDIControlChangeCommand). Each command class has properties specific to that message type. By default, MIKMIDICommands are immutable. Mutable variants of each command type are also available. +In MIKMIDI, MIDI messages are objects. These objects are instances of concrete subclasses of `MIKMIDICommand`. Each MIDI message type (e.g. Control Change, Note On, System Exclusive, etc.) has a corresponding class (e.g. MIKMIDIControlChangeCommand). Each command class has properties specific to that message type. By default, MIKMIDICommands are immutable. Mutable variants of each command type are also available. MIDI Mapping ------------ @@ -97,9 +96,9 @@ MIDI Synthesis MIDI synthesis is the process by which MIDI events/messages are turned into audio that you can hear. This is accomplished using `MIKMIDISynthesizer`. Also included is a subclass of `MIKMIDISynthesizer`, `MIKMIDIEndpointSynthesizer` which can very easily be hooked up to a MIDI endpoint to synthesize incoming MIDI messages: -```objective-c -MIKMIDISourceEndpoint *endpoint = midiDevice.entities.firstObject.sources.firstObject; -MIKMIDISynthesizer *synth = [[MIKMIDIEndpointSynthesizer alloc] initWithMIDISource:endpoint]; +```swift +let endpoint = midiDevice.entities.first!.sources.first! +let synth = try MIKMIDIEndpointSynthesizer(midiSource: endpoint) ``` MIDI Sequencing @@ -107,8 +106,8 @@ MIDI Sequencing `MIKMIDISequencer` can be used to play and record to an `MIKMIDISequence`. It includes a number of high level features useful when implementing MIDI recording and playback. However, at the very simplest, MIKMIDISequencer can be used to load a MIDI file and play it like so: -```objective-c -MIKMIDISequence *sequence = [MIKMIDISequence sequenceWithFileAtURL:midiFileURL error:&error]; -MIKMIDISequencer *sequencer = [MIKMIDISequencer sequencerWithSequence:sequence]; -[sequencer startPlayback]; +```swift +let sequence = try! MIKMIDISequence(fileAt: midiFileURL) +let sequencer = MIKMIDISequencer(sequence: sequence) +sequencer.startPlayback() ``` From f18094c0225eebc242283e92d2361d44919e8019 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Mon, 7 Oct 2019 23:36:19 -0600 Subject: [PATCH 10/53] Add rate property to MIKMIDISequencer. Fixes #273 --- .../Resources/Base.lproj/MainWindow.xib | 43 +++++++++++++++---- Source/MIKMIDISequencer.h | 9 ++++ Source/MIKMIDISequencer.m | 16 +++++-- 3 files changed, 56 insertions(+), 12 deletions(-) diff --git a/Examples/macOS/MIDI Files Testbed/Resources/Base.lproj/MainWindow.xib b/Examples/macOS/MIDI Files Testbed/Resources/Base.lproj/MainWindow.xib index b098e60d..f49bec6b 100644 --- a/Examples/macOS/MIDI Files Testbed/Resources/Base.lproj/MainWindow.xib +++ b/Examples/macOS/MIDI Files Testbed/Resources/Base.lproj/MainWindow.xib @@ -1,8 +1,9 @@ - - + + - + + @@ -17,7 +18,7 @@ - + @@ -41,10 +42,10 @@ - + - + @@ -109,7 +110,7 @@ - + @@ -139,28 +140,52 @@ - + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/MIKMIDISequencer.h b/Source/MIKMIDISequencer.h index 0fc92660..3bb177db 100644 --- a/Source/MIKMIDISequencer.h +++ b/Source/MIKMIDISequencer.h @@ -330,6 +330,15 @@ NS_ASSUME_NONNULL_BEGIN */ @property (readonly, nonatomic, getter=isRecording) BOOL recording; +/** + * @property rate + * @abstract The playback rate of the sequencer. For example, if rate is 2.0, the sequencer will play twice as fast as normal. + * Unlike the tempo property, this does not override the tempos in the sequence's tempo track. Rather, they are adjusted by multiplying by this rate. + * @discussion + * 1.0 is normal playback rate. Rate must be > 0.0. +*/ +@property (nonatomic) float rate; + /** * The tempo the sequencer should play its sequence at. When set to 0, the sequence will be played using * the tempo events from the sequence's tempo track. Default is 0. diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index c422dd8f..87b3e983 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -119,6 +119,7 @@ - (instancetype)initWithSequence:(MIKMIDISequence *)sequence _processingQueueKey = &_processingQueueKey; _processingQueueContext = &_processingQueueContext; _maximumLookAheadInterval = 0.1; + _rate = 1.0; } return self; } @@ -193,6 +194,7 @@ - (void)startPlaybackAtTimeStamp:(MusicTimeStamp)timeStamp MIDITimeStamp:(MIDITi Float64 startingTempo = [self.sequence tempoAtTimeStamp:timeStamp]; if (!startingTempo) startingTempo = kDefaultTempo; + startingTempo *= self.rate; [self updateClockWithMusicTimeStamp:timeStamp tempo:startingTempo atMIDITimeStamp:midiTimeStamp]; }); @@ -314,8 +316,8 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam if (self.needsCurrentTempoUpdate) { if (!tempoEventsByTimeStamp.count) { - if (!overrideTempo) overrideTempo = [sequence tempoAtTimeStamp:fromMusicTimeStamp]; - if (!overrideTempo) overrideTempo = kDefaultTempo; + if (!overrideTempo) overrideTempo = [sequence tempoAtTimeStamp:fromMusicTimeStamp] * self.rate; + if (!overrideTempo) overrideTempo = kDefaultTempo * self.rate; MIKMIDITempoEvent *tempoEvent = [MIKMIDITempoEvent tempoEventWithTimeStamp:fromMusicTimeStamp tempo:overrideTempo]; NSNumber *timeStampKey = @(fromMusicTimeStamp); @@ -393,7 +395,7 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam if (midiTimeStamp < MIKMIDIGetCurrentTimeStamp() && midiTimeStamp > fromMIDITimeStamp) continue; // prevents events that were just recorded from being scheduled MIKMIDITempoEvent *tempoEventAtTimeStamp = tempoEventsByTimeStamp[timeStampKey]; - if (tempoEventAtTimeStamp) [self updateClockWithMusicTimeStamp:musicTimeStamp tempo:tempoEventAtTimeStamp.bpm atMIDITimeStamp:midiTimeStamp]; + if (tempoEventAtTimeStamp) [self updateClockWithMusicTimeStamp:musicTimeStamp tempo:tempoEventAtTimeStamp.bpm * self.rate atMIDITimeStamp:midiTimeStamp]; NSArray *events = allEventsByTimeStamp[timeStampKey]; for (id eventObject in events) { @@ -954,6 +956,14 @@ - (MIKMIDIMetronome *)metronome #endif } +- (void)setRate:(float)rate +{ + if (rate != _rate && rate > 0.0) { + _rate = rate; + if (self.isPlaying) self.needsCurrentTempoUpdate = YES; + } +} + - (void)setTempo:(Float64)tempo { if (tempo < 0) tempo = 0; From 7cf4cd52cd5d9935f03f3ba822f9678547381d37 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Tue, 15 Oct 2019 00:23:14 -0600 Subject: [PATCH 11/53] Issue #273: Make MIKMIDISequencer time conversion methods respect overridden rate (+ tests) --- .../MIKMIDI Tests/MIKMIDISequencerTests.m | 85 ++++++++++++++++++- Source/MIKMIDISequencer.h | 4 + Source/MIKMIDISequencer.m | 57 ++++++++++--- 3 files changed, 133 insertions(+), 13 deletions(-) diff --git a/Framework/MIKMIDI Tests/MIKMIDISequencerTests.m b/Framework/MIKMIDI Tests/MIKMIDISequencerTests.m index 4987e988..dc465a5c 100644 --- a/Framework/MIKMIDI Tests/MIKMIDISequencerTests.m +++ b/Framework/MIKMIDI Tests/MIKMIDISequencerTests.m @@ -124,6 +124,51 @@ - (void)testConversionFromMusicTimeStampToSeconds XCTAssertNotEqualWithAccuracy(withoutUnrolling, withUnrolling, 1e-6); XCTAssertNotEqualWithAccuracy(withUnrolling, ignoringLooping, 1e-6); XCTAssertEqualWithAccuracy(withoutUnrolling, expectedWithoutUnrolling, 1e-6); + + // Test with non-default rates + for (NSNumber *rateNum in @[@0.9, @1.1, @2.0]) { + float rate = rateNum.floatValue; + self.sequencer.rate = rate; + + [self.sequencer setLoopStartTimeStamp:10 endTimeStamp:15]; + + testTimeStamp = 5; // Test before loop region + expected = [s timeInSecondsForMusicTimeStamp:testTimeStamp] / rate; + actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; + XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + + testTimeStamp = 12; // Test inside loop region before first loop + expected = [s timeInSecondsForMusicTimeStamp:testTimeStamp] / rate; + actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; + XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + + testTimeStamp = 37.5; // Test inside loop region after first loop + expected = 24.284925 / rate; + actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; + XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + + [self.sequencer setLoopStartTimeStamp:200 endTimeStamp:600]; + + testTimeStamp = 160; // Test before loop region + expected = [s timeInSecondsForMusicTimeStamp:testTimeStamp] / rate; + actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; + XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + + testTimeStamp = 550; // Test inside loop region before first loop + expected = [s timeInSecondsForMusicTimeStamp:testTimeStamp] / rate; + actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; + XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + + testTimeStamp = 850; // Test inside loop region after first loop + expected = 427.46909 / rate; + actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; + XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + + testTimeStamp = 850; // Test inside loop region after first loop while ignoring rate + expected = 427.46909; + actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsIgnoreRate]; + XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + } } - (void)testConversionFromSecondsToMusicTimeStamp @@ -265,8 +310,46 @@ - (void)testTwoWayConversionBetweenMusicTimeStampAndSeconds XCTAssertEqualWithAccuracy(timeInSeconds, convertedTimeInSeconds, 2e-6); } - // Test with looping + // Test with non-default rates self.sequencer.tempo = 0; + + for (NSNumber *rateNum in @[@0.5, @0.9, @1.1, @2.0]) { + self.sequencer.rate = rateNum.floatValue; + options = MIKMIDISequencerTimeConversionOptionsNone; + for (NSNumber *number in testMusicTimeStamps) { + MusicTimeStamp timeStamp = [number doubleValue]; + NSTimeInterval timeInSeconds = [self.sequencer timeInSecondsForMusicTimeStamp:timeStamp options:options]; + MusicTimeStamp convertedTimeStamp = [self.sequencer musicTimeStampForTimeInSeconds:timeInSeconds options:options]; + NSTimeInterval convertedTimeInSeconds = [self.sequencer timeInSecondsForMusicTimeStamp:convertedTimeStamp options:options]; + XCTAssertEqualWithAccuracy(timeStamp, convertedTimeStamp, 2e-6); + XCTAssertEqualWithAccuracy(timeInSeconds, convertedTimeInSeconds, 2e-6); + } + + // Test ignoring rate + options = MIKMIDISequencerTimeConversionOptionsIgnoreRate; + for (NSNumber *number in testMusicTimeStamps) { + MusicTimeStamp timeStamp = [number doubleValue]; + NSTimeInterval timeInSeconds = [self.sequencer timeInSecondsForMusicTimeStamp:timeStamp options:options]; + MusicTimeStamp convertedTimeStamp = [self.sequencer musicTimeStampForTimeInSeconds:timeInSeconds options:options]; + NSTimeInterval convertedTimeInSeconds = [self.sequencer timeInSecondsForMusicTimeStamp:convertedTimeStamp options:options]; + XCTAssertEqualWithAccuracy(timeStamp, convertedTimeStamp, 2e-6); + XCTAssertEqualWithAccuracy(timeInSeconds, convertedTimeInSeconds, 2e-6); + } + + // Test ignoring rate one-sided + options = MIKMIDISequencerTimeConversionOptionsIgnoreRate; + for (NSNumber *number in testMusicTimeStamps) { + MusicTimeStamp timeStamp = [number doubleValue]; + NSTimeInterval timeInSeconds = [self.sequencer timeInSecondsForMusicTimeStamp:timeStamp options:options]; + MusicTimeStamp convertedTimeStamp = [self.sequencer musicTimeStampForTimeInSeconds:timeInSeconds options:MIKMIDISequencerTimeConversionOptionsNone]; + NSTimeInterval convertedTimeInSeconds = [self.sequencer timeInSecondsForMusicTimeStamp:convertedTimeStamp options:options]; + XCTAssertNotEqualWithAccuracy(timeStamp, convertedTimeStamp, 2e-6); + XCTAssertNotEqualWithAccuracy(timeInSeconds, convertedTimeInSeconds, 2e-6); + } + } + + // Test with looping + self.sequencer.rate = 1.0; self.sequencer.loop = YES; [self.sequencer setLoopStartTimeStamp:200 endTimeStamp:600]; options = MIKMIDISequencerTimeConversionOptionsNone; diff --git a/Source/MIKMIDISequencer.h b/Source/MIKMIDISequencer.h index 3bb177db..a7e3f47a 100644 --- a/Source/MIKMIDISequencer.h +++ b/Source/MIKMIDISequencer.h @@ -52,6 +52,10 @@ typedef NS_OPTIONS(NSInteger, MIKMIDISequencerTimeConversionOptions) { The same concept applies for conversion from beats to seconds.*/ MIKMIDISequencerTimeConversionOptionsDontUnrollLoop = 1 << 2, + /** + When this option is set, the sequencer's rate will be ignore, and the default rate of 1.0 will be used for time conversion calculations. + */ + MIKMIDISequencerTimeConversionOptionsIgnoreRate = 1 << 3, }; NS_ASSUME_NONNULL_BEGIN diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index 87b3e983..e957641e 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -666,6 +666,7 @@ - (NSTimeInterval)timeInSecondsForMusicTimeStamp:(MusicTimeStamp)musicTimeStamp if (musicTimeStamp < 0) { return 0; } BOOL shouldIgnoreTempoOverride = options & MIKMIDISequencerTimeConversionOptionsIgnoreTempoOverride; + BOOL shouldIgnoreRate = options & MIKMIDISequencerTimeConversionOptionsIgnoreRate; BOOL ignoreLooping = options & MIKMIDISequencerTimeConversionOptionsIgnoreLooping; // If result is beyond the end of the loop @@ -696,15 +697,26 @@ - (NSTimeInterval)timeInSecondsForMusicTimeStamp:(MusicTimeStamp)musicTimeStamp // Calculate initial tempo, handling case where sequence doesn't specify one. NSArray *tempoEvents = self.sequence.tempoEvents; + if (!shouldIgnoreRate) { + NSMutableArray *scratch = [NSMutableArray array]; + for (MIKMIDITempoEvent *event in tempoEvents) { + MIKMutableMIDITempoEvent *adjustedEvent = [event mutableCopy]; + adjustedEvent.bpm *= self.rate; + [scratch addObject:[adjustedEvent copy]]; + } + tempoEvents = [scratch copy]; + } if (self.tempo != 0 && !shouldIgnoreTempoOverride) { // Overridden tempo that should be used instead of events in the tempo track - tempoEvents = @[[MIKMIDITempoEvent tempoEventWithTimeStamp:0 tempo:self.tempo]]; + Float64 tempo = self.tempo * (shouldIgnoreRate ? 1.0 : self.rate); + tempoEvents = @[[MIKMIDITempoEvent tempoEventWithTimeStamp:0 tempo:tempo]]; } else { NSUInteger tempoAtZeroIndex = [tempoEvents indexOfObjectPassingTest:^BOOL(MIKMIDITempoEvent *event, NSUInteger i, BOOL *s) { return event.timeStamp == 0; }]; if (tempoAtZeroIndex == NSNotFound) { NSMutableArray *scratch = [tempoEvents mutableCopy]; - MIKMIDITempoEvent *initialTempo = [MIKMIDITempoEvent tempoEventWithTimeStamp:0 tempo:kDefaultTempo]; + Float64 tempo = kDefaultTempo * (shouldIgnoreRate ? 1.0 : self.rate); + MIKMIDITempoEvent *initialTempo = [MIKMIDITempoEvent tempoEventWithTimeStamp:0 tempo:tempo]; [scratch insertObject:initialTempo atIndex:0]; tempoEvents = [scratch copy]; } @@ -734,15 +746,18 @@ - (NSTimeInterval)timeInSecondsForMusicTimeStamp:(MusicTimeStamp)musicTimeStamp - (MusicTimeStamp)musicTimeStampForTimeInSeconds:(NSTimeInterval)timeInSeconds options:(MIKMIDISequencerTimeConversionOptions)options { - BOOL shouldIgnoreTempoOverride = options & MIKMIDISequencerTimeConversionOptionsIgnoreTempoOverride; - BOOL ignoreLooping = options & MIKMIDISequencerTimeConversionOptionsIgnoreLooping; + BOOL shouldIgnoreTempoOverride = (options & MIKMIDISequencerTimeConversionOptionsIgnoreTempoOverride) != 0; + BOOL shouldIgnoreRate = (options & MIKMIDISequencerTimeConversionOptionsIgnoreRate) != 0; + BOOL ignoreLooping = (options & MIKMIDISequencerTimeConversionOptionsIgnoreLooping) != 0; - NSTimeInterval loopEndTimeInSeconds = self.loopEndTimeStamp > 0 ? [self timeInSecondsForMusicTimeStamp:self.loopEndTimeStamp options:MIKMIDISequencerTimeConversionOptionsIgnoreLooping] : self.sequence.durationInSeconds; + MIKMIDISequencerTimeConversionOptions loopCalcOptions = MIKMIDISequencerTimeConversionOptionsIgnoreLooping; + if (shouldIgnoreRate) { loopCalcOptions |= MIKMIDISequencerTimeConversionOptionsIgnoreRate; } + NSTimeInterval loopEndTimeInSeconds = self.loopEndTimeStamp > 0 ? [self timeInSecondsForMusicTimeStamp:self.loopEndTimeStamp options:loopCalcOptions] : self.sequence.durationInSeconds; // If result is beyond the end of the loop if (!ignoreLooping && self.shouldLoop && timeInSeconds >= loopEndTimeInSeconds) { options |= MIKMIDISequencerTimeConversionOptionsIgnoreLooping; MusicTimeStamp loopDuration = self.loopEndTimeStamp - self.loopStartTimeStamp; - NSTimeInterval loopStartTimeInSeconds = [self timeInSecondsForMusicTimeStamp:self.loopStartTimeStamp options:options]; + NSTimeInterval loopStartTimeInSeconds = [self timeInSecondsForMusicTimeStamp:self.loopStartTimeStamp options:loopCalcOptions]; NSTimeInterval loopDurationInSeconds = loopEndTimeInSeconds - loopStartTimeInSeconds; NSTimeInterval scratch = timeInSeconds; @@ -765,20 +780,36 @@ - (MusicTimeStamp)musicTimeStampForTimeInSeconds:(NSTimeInterval)timeInSeconds o // Calculate initial tempo, handling case where sequence doesn't specify one. NSArray *tempoEvents = self.sequence.tempoEvents; + if (!shouldIgnoreRate) { + NSMutableArray *scratch = [NSMutableArray array]; + for (MIKMIDITempoEvent *event in tempoEvents) { + MIKMutableMIDITempoEvent *adjustedEvent = [event mutableCopy]; + adjustedEvent.bpm *= self.rate; + [scratch addObject:[adjustedEvent copy]]; + } + tempoEvents = [scratch copy]; + } if (self.tempo != 0 && !shouldIgnoreTempoOverride) { // Overridden tempo that should be used instead of events in the tempo track - tempoEvents = @[[MIKMIDITempoEvent tempoEventWithTimeStamp:0 tempo:self.tempo]]; + Float64 tempo = self.tempo * (shouldIgnoreRate ? 1.0 : self.rate); + tempoEvents = @[[MIKMIDITempoEvent tempoEventWithTimeStamp:0 tempo:tempo]]; } else { NSUInteger tempoAtZeroIndex = [tempoEvents indexOfObjectPassingTest:^BOOL(MIKMIDITempoEvent *event, NSUInteger i, BOOL *s) { return event.timeStamp == 0; }]; if (tempoAtZeroIndex == NSNotFound) { NSMutableArray *scratch = [tempoEvents mutableCopy]; - MIKMIDITempoEvent *initialTempo = [MIKMIDITempoEvent tempoEventWithTimeStamp:0 tempo:kDefaultTempo]; + Float64 tempo = kDefaultTempo * (shouldIgnoreRate ? 1.0 : self.rate); + MIKMIDITempoEvent *initialTempo = [MIKMIDITempoEvent tempoEventWithTimeStamp:0 tempo:tempo]; [scratch insertObject:initialTempo atIndex:0]; tempoEvents = [scratch copy]; } } + MIKMIDISequencerTimeConversionOptions ignoreOverridesIfNeeded = MIKMIDISequencerTimeConversionOptionsNone; + if (shouldIgnoreTempoOverride) { ignoreOverridesIfNeeded |= MIKMIDISequencerTimeConversionOptionsIgnoreTempoOverride; } + if (shouldIgnoreRate) { ignoreOverridesIfNeeded |= MIKMIDISequencerTimeConversionOptionsIgnoreRate; } + if (ignoreLooping) { ignoreOverridesIfNeeded |= MIKMIDISequencerTimeConversionOptionsIgnoreLooping; } + // Get tempo events that affect the result (ie. come before timeInSeconds) and sort them in ascending order // Check if result would be affected by loop BOOL timeIsInLoop = self.shouldLoop && timeInSeconds >= loopEndTimeInSeconds && !ignoreLooping; @@ -786,7 +817,9 @@ - (MusicTimeStamp)musicTimeStampForTimeInSeconds:(NSTimeInterval)timeInSeconds o [tempoEvents indexesOfObjectsPassingTest:^BOOL(MIKMIDITempoEvent *event, NSUInteger i, BOOL *s) { // if timeInSeconds is within the loop region, include all tempo events up to the end of the loop NSTimeInterval limit = timeIsInLoop ? loopEndTimeInSeconds : timeInSeconds; - return [self.sequence timeInSecondsForMusicTimeStamp:event.timeStamp] <= limit; + NSTimeInterval timeInSeconds = [self.sequence timeInSecondsForMusicTimeStamp:event.timeStamp]; + if (!shouldIgnoreRate) { timeInSeconds /= self.rate; } + return timeInSeconds <= limit; }]; NSArray *tempoEventsAffectingResult = [tempoEvents objectsAtIndexes:indexesOfTempoEventsAffectingResult]; tempoEvents = [tempoEventsAffectingResult sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"timeStamp" ascending:YES]]]; @@ -794,12 +827,12 @@ - (MusicTimeStamp)musicTimeStampForTimeInSeconds:(NSTimeInterval)timeInSeconds o MusicTimeStamp result = 0.0; MIKMIDITempoEvent *lastTempoEvent = tempoEvents[0]; for (MIKMIDITempoEvent *tempoEvent in tempoEvents) { - NSTimeInterval tempoTimeInSeconds = [self.sequence timeInSecondsForMusicTimeStamp:tempoEvent.timeStamp]; - NSTimeInterval lastTempoTimeInSeconds = [self.sequence timeInSecondsForMusicTimeStamp:lastTempoEvent.timeStamp]; + NSTimeInterval tempoTimeInSeconds = [self timeInSecondsForMusicTimeStamp:tempoEvent.timeStamp options:ignoreOverridesIfNeeded]; + NSTimeInterval lastTempoTimeInSeconds = [self timeInSecondsForMusicTimeStamp:lastTempoEvent.timeStamp options:ignoreOverridesIfNeeded]; result += lastTempoEvent.bpm * (tempoTimeInSeconds - lastTempoTimeInSeconds) / 60.0; lastTempoEvent = tempoEvent; } - NSTimeInterval lastTempoTimeInSeconds = [self.sequence timeInSecondsForMusicTimeStamp:lastTempoEvent.timeStamp]; + NSTimeInterval lastTempoTimeInSeconds = [self timeInSecondsForMusicTimeStamp:lastTempoEvent.timeStamp options:ignoreOverridesIfNeeded]; result += lastTempoEvent.bpm * (timeInSeconds - lastTempoTimeInSeconds) / 60.0; return result; } From b497f277a3fd1e8f37d1ed2b25109f587b624632 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Mon, 4 Nov 2019 22:52:09 -0700 Subject: [PATCH 12/53] Issue #275: Add performance test and baseline for -[MIKMIDISequence tempoEvents] --- .../MIKMIDI Tests/MIKMIDIEventCachingTests.m | 42 +++++++++++++++++++ Framework/MIKMIDI.xcodeproj/project.pbxproj | 5 +++ ...C98DAFC3-AC32-4BFB-848B-A1B88CB141E1.plist | 22 ++++++++++ .../Info.plist | 24 +++++++++++ .../xcshareddata/xcschemes/MIKMIDI.xcscheme | 40 +++++++++++------- 5 files changed, 118 insertions(+), 15 deletions(-) create mode 100644 Framework/MIKMIDI Tests/MIKMIDIEventCachingTests.m create mode 100644 Framework/MIKMIDI.xcodeproj/xcshareddata/xcbaselines/9D4DF1391AAB57430065F004.xcbaseline/C98DAFC3-AC32-4BFB-848B-A1B88CB141E1.plist diff --git a/Framework/MIKMIDI Tests/MIKMIDIEventCachingTests.m b/Framework/MIKMIDI Tests/MIKMIDIEventCachingTests.m new file mode 100644 index 00000000..f78a19bd --- /dev/null +++ b/Framework/MIKMIDI Tests/MIKMIDIEventCachingTests.m @@ -0,0 +1,42 @@ +// +// MIKMIDIEventCachingTests.m +// MIKMIDI Tests +// +// Created by Andrew R Madsen on 11/4/19. +// Copyright © 2019 Mixed In Key. All rights reserved. +// + +#import +#import + +@interface MIKMIDIEventCachingTests : XCTestCase + +@property (nonatomic, strong) MIKMIDISequence *sequence; + +@end + +@implementation MIKMIDIEventCachingTests + +- (void)setUp +{ + MIKMIDISequence *sequence = [MIKMIDISequence sequence]; + MIKMIDITrack *tempoTrack = sequence.tempoTrack; + for (NSInteger i=0; i<300; i++) { + Float64 tempo = arc4random_uniform(200); + MIKMIDITempoEvent *tempoEvent = [MIKMIDITempoEvent tempoEventWithTimeStamp:i tempo:tempo]; + [tempoTrack addEvent:tempoEvent]; + } + self.sequence = sequence; +} + +- (void)testPerformanceExample { + // This is an example of a performance test case. + [self measureBlock:^{ + for (NSInteger i=0; i<5000; i++) { + NSArray *tempoEvents = [self.sequence tempoEvents]; + [tempoEvents self]; + } + }]; +} + +@end diff --git a/Framework/MIKMIDI.xcodeproj/project.pbxproj b/Framework/MIKMIDI.xcodeproj/project.pbxproj index c7bd3649..d3977e7c 100644 --- a/Framework/MIKMIDI.xcodeproj/project.pbxproj +++ b/Framework/MIKMIDI.xcodeproj/project.pbxproj @@ -69,6 +69,7 @@ 9D0895F11B0D29F200A5872E /* MIKMIDIMappingItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D0895ED1B0D29F200A5872E /* MIKMIDIMappingItem.m */; }; 9D0895F31B0D2A4700A5872E /* MIKMIDIMappableResponder.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D0895F21B0D2A4700A5872E /* MIKMIDIMappableResponder.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D0895F41B0D2A4700A5872E /* MIKMIDIMappableResponder.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D0895F21B0D2A4700A5872E /* MIKMIDIMappableResponder.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9D0E6B912370B3C900AEFFE0 /* MIKMIDIEventCachingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D0E6B902370B3C900AEFFE0 /* MIKMIDIEventCachingTests.m */; }; 9D1D9C201BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D1D9C1E1BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D1D9C211BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D1D9C1E1BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D1D9C221BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D1D9C1F1BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.m */; }; @@ -387,6 +388,7 @@ 9D0895EC1B0D29F200A5872E /* MIKMIDIMappingItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMappingItem.h; sourceTree = ""; }; 9D0895ED1B0D29F200A5872E /* MIKMIDIMappingItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMappingItem.m; sourceTree = ""; }; 9D0895F21B0D2A4700A5872E /* MIKMIDIMappableResponder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMappableResponder.h; sourceTree = ""; }; + 9D0E6B902370B3C900AEFFE0 /* MIKMIDIEventCachingTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIEventCachingTests.m; sourceTree = ""; }; 9D1D9C1E1BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIPolyphonicKeyPressureCommand.h; sourceTree = ""; }; 9D1D9C1F1BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIPolyphonicKeyPressureCommand.m; sourceTree = ""; }; 9D1D9C241BF542BB001377F7 /* MIKMIDICommandTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDICommandTests.m; sourceTree = ""; }; @@ -575,6 +577,7 @@ 9DCDDB591AB3514100F8347E /* MIKMIDISequencerTests.m */, 9D2ED25E1AFBD062000325CC /* MIKMIDIResponderChainTests.m */, 9DE824A5207AD02000761A07 /* MIKMIDIChannelEventTests.m */, + 9D0E6B902370B3C900AEFFE0 /* MIKMIDIEventCachingTests.m */, 9D4DF13C1AAB57430065F004 /* Supporting Files */, 9D4DF1501AAB57CD0065F004 /* Resources */, ); @@ -1156,6 +1159,7 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, ); mainGroup = 9D74EE9B17A7129300BEE89F; @@ -1213,6 +1217,7 @@ 9D4DF14D1AAB57800065F004 /* MIKMIDISequenceTests.m in Sources */, 9D4DF1541AAB60490065F004 /* MIKMIDITrackTests.m in Sources */, 9DE824A6207AD02000761A07 /* MIKMIDIChannelEventTests.m in Sources */, + 9D0E6B912370B3C900AEFFE0 /* MIKMIDIEventCachingTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Framework/MIKMIDI.xcodeproj/xcshareddata/xcbaselines/9D4DF1391AAB57430065F004.xcbaseline/C98DAFC3-AC32-4BFB-848B-A1B88CB141E1.plist b/Framework/MIKMIDI.xcodeproj/xcshareddata/xcbaselines/9D4DF1391AAB57430065F004.xcbaseline/C98DAFC3-AC32-4BFB-848B-A1B88CB141E1.plist new file mode 100644 index 00000000..6e732abd --- /dev/null +++ b/Framework/MIKMIDI.xcodeproj/xcshareddata/xcbaselines/9D4DF1391AAB57430065F004.xcbaseline/C98DAFC3-AC32-4BFB-848B-A1B88CB141E1.plist @@ -0,0 +1,22 @@ + + + + + classNames + + MIKMIDIEventCachingTests + + testPerformanceExample + + com.apple.XCTPerformanceMetric_WallClockTime + + baselineAverage + 1.6332 + baselineIntegrationDisplayName + Nov 4, 2019 at 10:50:41 PM + + + + + + diff --git a/Framework/MIKMIDI.xcodeproj/xcshareddata/xcbaselines/9D4DF1391AAB57430065F004.xcbaseline/Info.plist b/Framework/MIKMIDI.xcodeproj/xcshareddata/xcbaselines/9D4DF1391AAB57430065F004.xcbaseline/Info.plist index f0f3f4ac..91894063 100644 --- a/Framework/MIKMIDI.xcodeproj/xcshareddata/xcbaselines/9D4DF1391AAB57430065F004.xcbaseline/Info.plist +++ b/Framework/MIKMIDI.xcodeproj/xcshareddata/xcbaselines/9D4DF1391AAB57430065F004.xcbaseline/Info.plist @@ -28,6 +28,30 @@ targetArchitecture x86_64 + C98DAFC3-AC32-4BFB-848B-A1B88CB141E1 + + localComputer + + busSpeedInMHz + 100 + cpuCount + 1 + cpuKind + 10-Core Intel Xeon W + cpuSpeedInMHz + 3000 + logicalCPUCoresPerPackage + 20 + modelCode + iMacPro1,1 + physicalCPUCoresPerPackage + 10 + platformIdentifier + com.apple.platform.macosx + + targetArchitecture + x86_64 + diff --git a/Framework/MIKMIDI.xcodeproj/xcshareddata/xcschemes/MIKMIDI.xcscheme b/Framework/MIKMIDI.xcodeproj/xcshareddata/xcschemes/MIKMIDI.xcscheme index d4987361..098a08b4 100644 --- a/Framework/MIKMIDI.xcodeproj/xcshareddata/xcschemes/MIKMIDI.xcscheme +++ b/Framework/MIKMIDI.xcodeproj/xcshareddata/xcschemes/MIKMIDI.xcscheme @@ -20,14 +20,37 @@ ReferencedContainer = "container:MIKMIDI.xcodeproj"> + + + + + shouldUseLaunchSchemeArgsEnv = "YES" + disableMainThreadChecker = "YES"> + + + + @@ -40,17 +63,6 @@ - - - - - - - - Date: Mon, 4 Nov 2019 23:04:07 -0700 Subject: [PATCH 13/53] Use event cache in implementation of -[MIKMIDITrack eventsOfClass:fromTimeStamp:toTimeStamp:] for a ~25x speedup. Fixes #275. --- Source/MIKMIDITrack.h | 4 ++-- Source/MIKMIDITrack.m | 35 +++++++++-------------------------- 2 files changed, 11 insertions(+), 28 deletions(-) diff --git a/Source/MIKMIDITrack.h b/Source/MIKMIDITrack.h index 2d59c719..fc1de1a7 100644 --- a/Source/MIKMIDITrack.h +++ b/Source/MIKMIDITrack.h @@ -80,7 +80,7 @@ NS_ASSUME_NONNULL_BEGIN * * @return An array of specified class of MIDI events. */ -- (MIKArrayOfKindOf(MIKMIDIEvent *) *)eventsOfClass:(Class)eventClass fromTimeStamp:(MusicTimeStamp)startTimeStamp toTimeStamp:(MusicTimeStamp)endTimeStamp; +- (MIKArrayOfKindOf(MIKMIDIEvent *) *)eventsOfClass:(nullable Class)eventClass fromTimeStamp:(MusicTimeStamp)startTimeStamp toTimeStamp:(MusicTimeStamp)endTimeStamp; /** * Gets all of the MIDI notes in the track starting from startTimeStamp and ending at endTimeStamp inclusively. @@ -330,4 +330,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDITrack.m b/Source/MIKMIDITrack.m index 54fbfcbb..13636a37 100644 --- a/Source/MIKMIDITrack.m +++ b/Source/MIKMIDITrack.m @@ -297,31 +297,14 @@ - (BOOL)removeMIDIEventsFromMusicTrack:(NSSet *)events error:(NSError **)error // All public event getters pass through this method - (NSArray *)eventsOfClass:(Class)eventClass fromTimeStamp:(MusicTimeStamp)startTimeStamp toTimeStamp:(MusicTimeStamp)endTimeStamp { - __block NSArray *events; - - [self dispatchSyncToSequencerProcessingQueueAsNeeded:^{ - if (!self.internalEvents.count) { events = @[]; return; } // possible WORKAROUND for Issue #100 - - MIKMIDIEventIterator *iterator = [MIKMIDIEventIterator iteratorForTrack:self]; - if (![iterator seek:startTimeStamp]) { events = @[]; return; } - - NSMutableArray *mutableEvents = [NSMutableArray array]; - - while (iterator.hasCurrentEvent) { - MIKMIDIEvent *event = iterator.currentEvent; - if (!event || event.timeStamp > endTimeStamp) break; - - if (!eventClass || [event isKindOfClass:eventClass]) { - [mutableEvents addObject:event]; - } - - [iterator moveToNextEvent]; - } - - events = mutableEvents; - }]; - - return events ?: @[]; + NSMutableArray *result = [NSMutableArray array]; + for (MIKMIDIEvent *event in self.events) { + if (event.timeStamp < startTimeStamp) { continue; } + if (event.timeStamp > endTimeStamp) { break; } + if (!eventClass || ![event isKindOfClass:eventClass]) { continue; } + [result addObject:event]; + } + return [result copy]; } - (NSArray *)eventsFromTimeStamp:(MusicTimeStamp)startTimeStamp toTimeStamp:(MusicTimeStamp)endTimeStamp @@ -344,7 +327,7 @@ - (void)reloadAllEventsFromMusicTrack MIKMIDIEvent *event = iterator.currentEvent; [allEvents addObject:event]; [iterator moveToNextEvent]; -} + } [self willChangeValueForKey:@"internalEvents"]; [self.internalEvents intersectSet:allEvents]; From e657065c16a9b31d75565b32a66ea4a98eef1f5f Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Sun, 25 Aug 2019 13:17:33 -0600 Subject: [PATCH 14/53] Issue #269: Add API to MIKMIDISequence for converting between beats and seconds, along with tests. (cherry picked from commit 6e0c342ca02df5a61a7b15e94c6b208a29a73f24) --- .../MIKMIDI Tests/MIKMIDISequenceTests.m | 45 ++++++++++++++++++ Framework/MIKMIDI Tests/tempochanges.mid | Bin 0 -> 2154 bytes Framework/MIKMIDI.xcodeproj/project.pbxproj | 5 ++ .../xcshareddata/xcschemes/MIKMIDI.xcscheme | 26 +++++----- Source/MIKMIDISequence.h | 5 ++ Source/MIKMIDISequence.m | 16 +++++++ 6 files changed, 82 insertions(+), 15 deletions(-) create mode 100644 Framework/MIKMIDI Tests/tempochanges.mid diff --git a/Framework/MIKMIDI Tests/MIKMIDISequenceTests.m b/Framework/MIKMIDI Tests/MIKMIDISequenceTests.m index 49c3fc11..4bae766d 100644 --- a/Framework/MIKMIDI Tests/MIKMIDISequenceTests.m +++ b/Framework/MIKMIDI Tests/MIKMIDISequenceTests.m @@ -139,6 +139,51 @@ - (void)testLength XCTAssertTrue([self.receivedNotificationKeyPaths containsObject:@"durationInSeconds"], @"KVO notification for durationInSeconds failed after removing longest child track."); } +- (void)testConversionFromMusicTimeStampToSeconds +{ + NSBundle *bundle = [NSBundle bundleForClass:[self class]]; + NSURL *testMIDIFileURL = [bundle URLForResource:@"tempochanges" withExtension:@"mid"]; + NSError *error = nil; + MIKMIDISequence *sequence = [MIKMIDISequence sequenceWithFileAtURL:testMIDIFileURL convertMIDIChannelsToTracks:NO error:&error]; + XCTAssertNotNil(sequence); + + XCTAssertEqual([sequence timeInSecondsForMusicTimeStamp:0], 0.0); + + MIKMIDITempoEvent *firstTempo = [sequence.tempoEvents firstObject]; + XCTAssertNotNil(firstTempo); + NSTimeInterval expectedTimeAt3 = 60 * 3.0 / firstTempo.bpm; + XCTAssertEqualWithAccuracy([sequence timeInSecondsForMusicTimeStamp:3], expectedTimeAt3, 1e-6); + + MIKMIDITempoEvent *secondTempo = sequence.tempoEvents[1]; + NSTimeInterval expectedTimeAtSecondTempo = 60 * secondTempo.timeStamp / firstTempo.bpm; + MusicTimeStamp nextCheck = secondTempo.timeStamp+1; + NSTimeInterval expectedTimeAtNextCheck = 60 * (nextCheck - secondTempo.timeStamp) / secondTempo.bpm + expectedTimeAtSecondTempo; + XCTAssertEqualWithAccuracy([sequence timeInSecondsForMusicTimeStamp:nextCheck], expectedTimeAtNextCheck, 1e-6); +} + +- (void)testConversionFromSecondsToMusicTimeStamp +{ + NSBundle *bundle = [NSBundle bundleForClass:[self class]]; + NSURL *testMIDIFileURL = [bundle URLForResource:@"tempochanges" withExtension:@"mid"]; + NSError *error = nil; + MIKMIDISequence *sequence = [MIKMIDISequence sequenceWithFileAtURL:testMIDIFileURL convertMIDIChannelsToTracks:NO error:&error]; + XCTAssertNotNil(sequence); + + XCTAssertEqual([sequence musicTimeStampForTimeInSeconds:0.0], 0); + + MIKMIDITempoEvent *firstTempo = [sequence.tempoEvents firstObject]; + XCTAssertNotNil(firstTempo); + NSTimeInterval firstCheckTime = 1.5; + MusicTimeStamp firstExpectedTimeStamp = firstTempo.bpm * firstCheckTime / 60.0; + XCTAssertEqualWithAccuracy([sequence musicTimeStampForTimeInSeconds:firstCheckTime], firstExpectedTimeStamp, 1e-6); + + MIKMIDITempoEvent *secondTempo = sequence.tempoEvents[1]; + NSTimeInterval timeAtSecondTempo = 60 * secondTempo.timeStamp / firstTempo.bpm; + NSTimeInterval secondCheckTime = 4; + MusicTimeStamp expectedTimeStampAtNextCheck = secondTempo.bpm * (secondCheckTime - timeAtSecondTempo) / 60.0 + secondTempo.timeStamp; + XCTAssertEqualWithAccuracy([sequence musicTimeStampForTimeInSeconds:secondCheckTime], expectedTimeStampAtNextCheck, 1e-6); +} + - (void)testSetTimeSignature { [self.sequence setTimeSignature:MIKMIDITimeSignatureMake(2, 4) atTimeStamp:0]; diff --git a/Framework/MIKMIDI Tests/tempochanges.mid b/Framework/MIKMIDI Tests/tempochanges.mid new file mode 100644 index 0000000000000000000000000000000000000000..398857b7c8c626c98e915c2660ca3a9146381878 GIT binary patch literal 2154 zcmai$v2GJV5Qf*g=SwI+qKJe9iYy^IP;PJc?shxuBu*?PiVPR9dkQ3=fF23E;{~XA z0VEXh0+dK}RMaRh!ZEk=kCHT@+VRZ#+nL$@$3AXfzEDc#YM}Ik56A7hS4wR*s%wXj z)iwpXxw&zErn-DksBWnT z+-$skPZVv76x5cf+A>z##9eAbInXY~Ys-9X`2cOx37l!ZU6iBA>|iu`GCMeIN0a^W zX*-*q9*v%#9PK?Ca}txZnDK(oF%vd$(BMjK#>%b|#^6e|#!1{u{M!0a>9rOAuU3k4 zJGG6>E5R$#!4e%TIaghxcO`mPqIV_VjxNC~!E@j_@Emv!JO`cw&w=N_bKtqeTV#gb zPk)ELSe?szN}s{^;Ct{r_#S)@z6alf?~!kXd@Jx8#2dr|;sNo1^8%k|g5#$_1|COn zyZlOm+v|-*rnv^%vcLL7$q&g`lozJEFnuy<=d^$2_dbV^y)T zG$Ds!jK<*Lz002jfDcqkZLQr^Gfomq|8eCSV@_c^sy3hEw8DT z*HjC+7IH1*TFAAKYa!P{uIV>uNpA|xZ-n0nzY%^T{6_eV@EhSb!pF!l7Bk0K3>`9r zjIl!=Aw%Ef5i%4`9w9^H + shouldUseLaunchSchemeArgsEnv = "YES" + enableAddressSanitizer = "YES"> + + + + @@ -40,17 +49,6 @@ - - - - - - - - Date: Sun, 25 Aug 2019 22:28:58 -0600 Subject: [PATCH 15/53] Issue #269: Add method to convert from MusicTimeStamp to seconds to MIKMIDISequencer, plus tests. (cherry picked from commit 7d746a613a2194189dff3fe52669eaa7460b8507) --- .../MIKMIDI Tests/MIKMIDISequencerTests.m | 85 ++++++++++++++++++- Source/MIKMIDISequencer.h | 7 +- Source/MIKMIDISequencer.m | 71 ++++++++++++++++ 3 files changed, 160 insertions(+), 3 deletions(-) diff --git a/Framework/MIKMIDI Tests/MIKMIDISequencerTests.m b/Framework/MIKMIDI Tests/MIKMIDISequencerTests.m index 346ed788..1ab8e82e 100644 --- a/Framework/MIKMIDI Tests/MIKMIDISequencerTests.m +++ b/Framework/MIKMIDI Tests/MIKMIDISequencerTests.m @@ -20,14 +20,95 @@ @implementation MIKMIDISequencerTests - (void)setUp { - [super setUp]; + [super setUp]; self.sequencer = [MIKMIDISequencer sequencer]; } - (void)tearDown { - [super tearDown]; + [super tearDown]; +} + +- (void)testConversionFromMusicTimeStampToSeconds +{ + NSBundle *bundle = [NSBundle bundleForClass:[self class]]; + NSURL *testMIDIFileURL = [bundle URLForResource:@"tempochanges" withExtension:@"mid"]; + NSError *error = nil; + MIKMIDISequence *s = [MIKMIDISequence sequenceWithFileAtURL:testMIDIFileURL convertMIDIChannelsToTracks:NO error:&error]; + XCTAssertNotNil(s); + self.sequencer.sequence = s; + + XCTAssertEqual([self.sequencer timeInSecondsForMusicTimeStamp:0 ignoreLooping:NO], 0.0); + + XCTAssertEqualWithAccuracy([s timeInSecondsForMusicTimeStamp:3], [self.sequencer timeInSecondsForMusicTimeStamp:3 ignoreLooping:NO], 1e-6 ); + + MIKMIDITempoEvent *secondTempo = s.tempoEvents[1]; + MusicTimeStamp nextCheck = secondTempo.timeStamp+1; + XCTAssertEqualWithAccuracy([s timeInSecondsForMusicTimeStamp:nextCheck], [self.sequencer timeInSecondsForMusicTimeStamp:nextCheck ignoreLooping:NO], 1e-6); + + MusicTimeStamp testTimeStamp = 850; + NSTimeInterval expected = [s timeInSecondsForMusicTimeStamp:testTimeStamp]; + NSTimeInterval actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp ignoreLooping:NO]; + XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + + // Test when tempo is overridden + Float64 overrideTempo = 80.0; + self.sequencer.tempo = overrideTempo; + + expected = 60 * testTimeStamp / overrideTempo; + actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp ignoreLooping:NO]; + XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + + self.sequencer.tempo = 0; // Disable tempo override + + // Test with looping + self.sequencer.loop = YES; + [self.sequencer setLoopStartTimeStamp:10 endTimeStamp:15]; + + testTimeStamp = 5; // Test before loop region, should be unaffected + expected = [s timeInSecondsForMusicTimeStamp:testTimeStamp]; + actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp ignoreLooping:NO]; + XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + + testTimeStamp = 12; // Test inside loop region before first loop, should be unaffected + expected = [s timeInSecondsForMusicTimeStamp:testTimeStamp]; + actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp ignoreLooping:NO]; + XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + + testTimeStamp = 37.5; // Test inside loop region after first loop, should be affected + expected = 24.284925; + actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp ignoreLooping:NO]; + XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + + [self.sequencer setLoopStartTimeStamp:200 endTimeStamp:600]; + + testTimeStamp = 160; // Test before loop region, should be unaffected + expected = [s timeInSecondsForMusicTimeStamp:testTimeStamp]; + actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp ignoreLooping:NO]; + XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + + testTimeStamp = 550; // Test inside loop region before first loop, should be unaffected + expected = [s timeInSecondsForMusicTimeStamp:testTimeStamp]; + actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp ignoreLooping:NO]; + XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + + testTimeStamp = 850; // Test inside loop region after first loop, should be affected + expected = 427.46909; + actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp ignoreLooping:NO]; + XCTAssertEqualWithAccuracy(expected, actual, 1e-6); +} + +- (void)testConversionFromSecondsToMusicTimeStamp +{ + NSBundle *bundle = [NSBundle bundleForClass:[self class]]; + NSURL *testMIDIFileURL = [bundle URLForResource:@"tempochanges" withExtension:@"mid"]; + NSError *error = nil; + MIKMIDISequence *s = [MIKMIDISequence sequenceWithFileAtURL:testMIDIFileURL convertMIDIChannelsToTracks:NO error:&error]; + XCTAssertNotNil(s); + self.sequencer.sequence = s; + + XCTAssertEqual([self.sequencer musicTimeStampForTimeInSeconds:0.0], 0); } - (void)testBuiltinSynthesizers diff --git a/Source/MIKMIDISequencer.h b/Source/MIKMIDISequencer.h index 790212d3..e62a64b5 100644 --- a/Source/MIKMIDISequencer.h +++ b/Source/MIKMIDISequencer.h @@ -255,6 +255,11 @@ NS_ASSUME_NONNULL_BEGIN */ - (nullable MIKMIDISynthesizer *)builtinSynthesizerForTrack:(MIKMIDITrack *)track; +#pragma mark - Time Conversion + +- (NSTimeInterval)timeInSecondsForMusicTimeStamp:(MusicTimeStamp)musicTimeStamp ignoreLooping:(BOOL)ignoreLooping; +- (MusicTimeStamp)musicTimeStampForTimeInSeconds:(NSTimeInterval)timeInSeconds; + #pragma mark - Properties /** @@ -475,4 +480,4 @@ FOUNDATION_EXPORT NSString * const MIKMIDISequencerWillLoopNotification; */ FOUNDATION_EXPORT const MusicTimeStamp MIKMIDISequencerEndOfSequenceLoopEndTimeStamp; -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index b226efe2..3c13ea40 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -657,6 +657,77 @@ - (MIKMIDISynthesizer *)builtinSynthesizerForTrack:(MIKMIDITrack *)track return [self.tracksToDefaultSynthsMap objectForKey:track]; } +#pragma mark - Time Conversion + +- (NSTimeInterval)timeInSecondsForMusicTimeStamp:(MusicTimeStamp)musicTimeStamp ignoreLooping:(BOOL)ignoreLooping +{ + if (!ignoreLooping && self.shouldLoop && musicTimeStamp >= self.loopEndTimeStamp) { + MusicTimeStamp loopDuration = self.loopEndTimeStamp - self.loopStartTimeStamp; + NSTimeInterval loopStartTimeInSeconds = [self timeInSecondsForMusicTimeStamp:self.loopStartTimeStamp ignoreLooping:YES]; + NSTimeInterval loopEndTimeInSeconds = [self timeInSecondsForMusicTimeStamp:self.loopEndTimeStamp ignoreLooping:YES]; + NSTimeInterval loopDurationInSeconds = loopEndTimeInSeconds - loopStartTimeInSeconds; + + MusicTimeStamp scratch = musicTimeStamp; + scratch -= self.loopStartTimeStamp; // Subtract off time before the loop + NSTimeInterval result = loopStartTimeInSeconds; + // "Use up" the loops until we're down to a fraction of a loop + while (scratch >= loopDuration) { + result += loopDurationInSeconds; + scratch -= loopDuration; + } + // Add the remaining fraction of a loop + result += [self timeInSecondsForMusicTimeStamp:(self.loopStartTimeStamp + scratch) ignoreLooping:YES]; + result -= loopStartTimeInSeconds; + return result; + } + + // Calculate initial tempo, handling case where sequence doesn't specify one. + NSArray *tempoEvents = self.sequence.tempoEvents; + if (self.tempo != 0) { // Overridden tempo that should be used instead of events in the tempo track + tempoEvents = @[[MIKMIDITempoEvent tempoEventWithTimeStamp:0 tempo:self.tempo]]; + } else { + NSUInteger tempoAtZeroIndex = [tempoEvents indexOfObjectPassingTest:^BOOL(MIKMIDITempoEvent *event, NSUInteger i, BOOL *s) { + return event.timeStamp == 0; + }]; + if (tempoAtZeroIndex == NSNotFound) { + NSMutableArray *scratch = [tempoEvents mutableCopy]; + MIKMIDITempoEvent *initialTempo = [MIKMIDITempoEvent tempoEventWithTimeStamp:0 tempo:kDefaultTempo]; + [scratch insertObject:initialTempo atIndex:0]; + tempoEvents = [scratch copy]; + } + } + + // Get tempo events that affect the result (ie. come before musicTimeStamp) and sort them in ascending order + NSIndexSet *indexesOfTempoEventsAffectingResult = + [tempoEvents indexesOfObjectsPassingTest:^BOOL(MIKMIDITempoEvent *event, NSUInteger i, BOOL *s) { + if (!self.shouldLoop || musicTimeStamp < self.loopEndTimeStamp || ignoreLooping) { + return event.timeStamp <= musicTimeStamp; + } + // musicTimeStamp is within the loop region, so include all tempo events up to the end of the loop + return musicTimeStamp <= self.loopEndTimeStamp; + }]; + NSArray *tempoEventsAffectingResult = [tempoEvents objectsAtIndexes:indexesOfTempoEventsAffectingResult]; + tempoEvents = [tempoEventsAffectingResult sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"timeStamp" ascending:YES]]]; + + NSTimeInterval result = 0.0; + MIKMIDITempoEvent *lastTempoEvent = tempoEvents[0]; + for (MIKMIDITempoEvent *tempoEvent in tempoEvents) { + result += 60.0 * (tempoEvent.timeStamp - lastTempoEvent.timeStamp) / lastTempoEvent.bpm; + lastTempoEvent = tempoEvent; + } + result += 60.0 * (musicTimeStamp - lastTempoEvent.timeStamp) / lastTempoEvent.bpm; + return result; +} + +- (MusicTimeStamp)musicTimeStampForTimeInSeconds:(NSTimeInterval)timeInSeconds +{ + // if (self.tempo == 0 && !self.shouldLoop) { // If tempo is not overridden, and looping is not on + // // Just use MIKMIDISequence's simple implementation + // return [self.sequence musicTimeStampForTimeInSeconds:timeInSeconds]; + // } + return 0; +} + #pragma mark - Click Track - (NSMutableArray *)clickTrackEventsFromTimeStamp:(MusicTimeStamp)fromTimeStamp toTimeStamp:(MusicTimeStamp)toTimeStamp From 16653b7fcee8d0bd02e3ba856c5758e797aec1c8 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Sun, 1 Sep 2019 16:30:29 -0600 Subject: [PATCH 16/53] Issue #269: Add options to MIKMIDISequencer time conversion methods, along with more test coverage. --- .../MIKMIDI Tests/MIKMIDISequencerTests.m | 44 ++++++++++++++----- Source/MIKMIDISequencer.h | 23 +++++++++- Source/MIKMIDISequencer.m | 18 +++++--- 3 files changed, 65 insertions(+), 20 deletions(-) diff --git a/Framework/MIKMIDI Tests/MIKMIDISequencerTests.m b/Framework/MIKMIDI Tests/MIKMIDISequencerTests.m index 1ab8e82e..b9a65933 100644 --- a/Framework/MIKMIDI Tests/MIKMIDISequencerTests.m +++ b/Framework/MIKMIDI Tests/MIKMIDISequencerTests.m @@ -39,17 +39,17 @@ - (void)testConversionFromMusicTimeStampToSeconds XCTAssertNotNil(s); self.sequencer.sequence = s; - XCTAssertEqual([self.sequencer timeInSecondsForMusicTimeStamp:0 ignoreLooping:NO], 0.0); + XCTAssertEqual([self.sequencer timeInSecondsForMusicTimeStamp:0 options:MIKMIDISequencerTimeConversionOptionsNone], 0.0); - XCTAssertEqualWithAccuracy([s timeInSecondsForMusicTimeStamp:3], [self.sequencer timeInSecondsForMusicTimeStamp:3 ignoreLooping:NO], 1e-6 ); + XCTAssertEqualWithAccuracy([s timeInSecondsForMusicTimeStamp:3], [self.sequencer timeInSecondsForMusicTimeStamp:3 options:MIKMIDISequencerTimeConversionOptionsNone], 1e-6 ); MIKMIDITempoEvent *secondTempo = s.tempoEvents[1]; MusicTimeStamp nextCheck = secondTempo.timeStamp+1; - XCTAssertEqualWithAccuracy([s timeInSecondsForMusicTimeStamp:nextCheck], [self.sequencer timeInSecondsForMusicTimeStamp:nextCheck ignoreLooping:NO], 1e-6); + XCTAssertEqualWithAccuracy([s timeInSecondsForMusicTimeStamp:nextCheck], [self.sequencer timeInSecondsForMusicTimeStamp:nextCheck options:MIKMIDISequencerTimeConversionOptionsNone], 1e-6); MusicTimeStamp testTimeStamp = 850; NSTimeInterval expected = [s timeInSecondsForMusicTimeStamp:testTimeStamp]; - NSTimeInterval actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp ignoreLooping:NO]; + NSTimeInterval actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; XCTAssertEqualWithAccuracy(expected, actual, 1e-6); // Test when tempo is overridden @@ -57,7 +57,7 @@ - (void)testConversionFromMusicTimeStampToSeconds self.sequencer.tempo = overrideTempo; expected = 60 * testTimeStamp / overrideTempo; - actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp ignoreLooping:NO]; + actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; XCTAssertEqualWithAccuracy(expected, actual, 1e-6); self.sequencer.tempo = 0; // Disable tempo override @@ -68,35 +68,55 @@ - (void)testConversionFromMusicTimeStampToSeconds testTimeStamp = 5; // Test before loop region, should be unaffected expected = [s timeInSecondsForMusicTimeStamp:testTimeStamp]; - actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp ignoreLooping:NO]; + actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; XCTAssertEqualWithAccuracy(expected, actual, 1e-6); testTimeStamp = 12; // Test inside loop region before first loop, should be unaffected expected = [s timeInSecondsForMusicTimeStamp:testTimeStamp]; - actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp ignoreLooping:NO]; + actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; XCTAssertEqualWithAccuracy(expected, actual, 1e-6); testTimeStamp = 37.5; // Test inside loop region after first loop, should be affected expected = 24.284925; - actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp ignoreLooping:NO]; + actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; XCTAssertEqualWithAccuracy(expected, actual, 1e-6); [self.sequencer setLoopStartTimeStamp:200 endTimeStamp:600]; testTimeStamp = 160; // Test before loop region, should be unaffected expected = [s timeInSecondsForMusicTimeStamp:testTimeStamp]; - actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp ignoreLooping:NO]; + actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; XCTAssertEqualWithAccuracy(expected, actual, 1e-6); testTimeStamp = 550; // Test inside loop region before first loop, should be unaffected expected = [s timeInSecondsForMusicTimeStamp:testTimeStamp]; - actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp ignoreLooping:NO]; + actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; XCTAssertEqualWithAccuracy(expected, actual, 1e-6); testTimeStamp = 850; // Test inside loop region after first loop, should be affected expected = 427.46909; - actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp ignoreLooping:NO]; + actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + + testTimeStamp = 850; // Test inside loop region after first loop, but ignoring looping should be affected + NSTimeInterval withLooping = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; + NSTimeInterval ignoringLooping = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsIgnoreLooping]; + self.sequencer.loop = NO; + NSTimeInterval withoutLooping = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; + XCTAssertNotEqualWithAccuracy(withLooping, ignoringLooping, 1e-6); + XCTAssertEqualWithAccuracy(withoutLooping, ignoringLooping, 1e-6); + + // Test loop "unrolling" + self.sequencer.loop = YES; + [self.sequencer setLoopStartTimeStamp:10 endTimeStamp:15]; + testTimeStamp = 37.5; // Test inside loop region after first loop, should be affected + NSTimeInterval withoutUnrolling = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsDontUnrollLoop]; + NSTimeInterval withUnrolling = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; + NSTimeInterval expectedWithoutUnrolling = 8.094975; + ignoringLooping = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsIgnoreLooping]; + XCTAssertNotEqualWithAccuracy(withoutUnrolling, withUnrolling, 1e-6); + XCTAssertNotEqualWithAccuracy(withUnrolling, ignoringLooping, 1e-6); + XCTAssertEqualWithAccuracy(withoutUnrolling, expectedWithoutUnrolling, 1e-6); } - (void)testConversionFromSecondsToMusicTimeStamp @@ -108,7 +128,7 @@ - (void)testConversionFromSecondsToMusicTimeStamp XCTAssertNotNil(s); self.sequencer.sequence = s; - XCTAssertEqual([self.sequencer musicTimeStampForTimeInSeconds:0.0], 0); + XCTAssertEqual([self.sequencer musicTimeStampForTimeInSeconds:0.0 options:MIKMIDISequencerTimeConversionOptionsNone], 0); } - (void)testBuiltinSynthesizers diff --git a/Source/MIKMIDISequencer.h b/Source/MIKMIDISequencer.h index e62a64b5..09c1e6fb 100644 --- a/Source/MIKMIDISequencer.h +++ b/Source/MIKMIDISequencer.h @@ -35,6 +35,25 @@ typedef NS_ENUM(NSInteger, MIKMIDISequencerClickTrackStatus) { MIKMIDISequencerClickTrackStatusAlwaysEnabled }; +typedef NS_OPTIONS(NSInteger, MIKMIDISequencerTimeConversionOptions) { + /** Use default options (consider tempo override and looping, don't unroll loops) */ + MIKMIDISequencerTimeConversionOptionsNone = 0, + /** Use the sequence's tempo events to calculate conversion, even if the sequencer has a tempo override set. The default is to use the overridden tempo for calculation if one is set.*/ + MIKMIDISequencerTimeConversionOptionsIgnoreTempoOverride = 1 << 0, + /** Calculate conversion as if looping were disabled. The default is to take into account looping if it is enabled on the sequencer.*/ + MIKMIDISequencerTimeConversionOptionsIgnoreLooping = 1 << 1, + /** When this option is set, conversion will return the time of events currently being played relative to the start of the sequence, and the result will never been greater than the end of the loop. The default, where this option is not set, is to calculate and return the absolute time since the sequence start. + + For example, consider a sequence that is 16 beats long, the tempo is a constant 75 bpm and looping is enabled for first 8 beats. The sequence will be exactly 20 seconds long, and the loop will consist of the first 10 seconds. + + If this option is *set*, and a time of 25 seconds is passed in, the result will be 4 beats, because the sequencer will be at the half way point of the loop on its third time through. If this option is *not set*, the result will be 20 beats, because 20 beats total will have elapsed since the start of the sequence. + + Setting the option allows you to determine what part of the raw sequence is currently being played, while leaving it unset allows you to determine total playback time. + + The same concept applies for conversion from beats to seconds.*/ + MIKMIDISequencerTimeConversionOptionsDontUnrollLoop = 1 << 2, +}; + NS_ASSUME_NONNULL_BEGIN /** @@ -257,8 +276,8 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - Time Conversion -- (NSTimeInterval)timeInSecondsForMusicTimeStamp:(MusicTimeStamp)musicTimeStamp ignoreLooping:(BOOL)ignoreLooping; -- (MusicTimeStamp)musicTimeStampForTimeInSeconds:(NSTimeInterval)timeInSeconds; +- (NSTimeInterval)timeInSecondsForMusicTimeStamp:(MusicTimeStamp)musicTimeStamp options:(MIKMIDISequencerTimeConversionOptions)options; +- (MusicTimeStamp)musicTimeStampForTimeInSeconds:(NSTimeInterval)timeInSeconds options:(MIKMIDISequencerTimeConversionOptions)options; #pragma mark - Properties diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index 3c13ea40..b358900b 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -659,24 +659,30 @@ - (MIKMIDISynthesizer *)builtinSynthesizerForTrack:(MIKMIDITrack *)track #pragma mark - Time Conversion -- (NSTimeInterval)timeInSecondsForMusicTimeStamp:(MusicTimeStamp)musicTimeStamp ignoreLooping:(BOOL)ignoreLooping +- (NSTimeInterval)timeInSecondsForMusicTimeStamp:(MusicTimeStamp)musicTimeStamp options:(MIKMIDISequencerTimeConversionOptions)options { + BOOL ignoreLooping = options & MIKMIDISequencerTimeConversionOptionsIgnoreLooping; if (!ignoreLooping && self.shouldLoop && musicTimeStamp >= self.loopEndTimeStamp) { + options |= MIKMIDISequencerTimeConversionOptionsIgnoreLooping; MusicTimeStamp loopDuration = self.loopEndTimeStamp - self.loopStartTimeStamp; - NSTimeInterval loopStartTimeInSeconds = [self timeInSecondsForMusicTimeStamp:self.loopStartTimeStamp ignoreLooping:YES]; - NSTimeInterval loopEndTimeInSeconds = [self timeInSecondsForMusicTimeStamp:self.loopEndTimeStamp ignoreLooping:YES]; + NSTimeInterval loopStartTimeInSeconds = [self timeInSecondsForMusicTimeStamp:self.loopStartTimeStamp options:options]; + NSTimeInterval loopEndTimeInSeconds = [self timeInSecondsForMusicTimeStamp:self.loopEndTimeStamp options:options]; NSTimeInterval loopDurationInSeconds = loopEndTimeInSeconds - loopStartTimeInSeconds; MusicTimeStamp scratch = musicTimeStamp; scratch -= self.loopStartTimeStamp; // Subtract off time before the loop NSTimeInterval result = loopStartTimeInSeconds; + BOOL shouldUnroll = !(options & MIKMIDISequencerTimeConversionOptionsDontUnrollLoop); // "Use up" the loops until we're down to a fraction of a loop while (scratch >= loopDuration) { - result += loopDurationInSeconds; + // Only add time for full loops to result if we're unrolling loops + if (shouldUnroll) { + result += loopDurationInSeconds; + } scratch -= loopDuration; } // Add the remaining fraction of a loop - result += [self timeInSecondsForMusicTimeStamp:(self.loopStartTimeStamp + scratch) ignoreLooping:YES]; + result += [self timeInSecondsForMusicTimeStamp:(self.loopStartTimeStamp + scratch) options:options]; result -= loopStartTimeInSeconds; return result; } @@ -719,7 +725,7 @@ - (NSTimeInterval)timeInSecondsForMusicTimeStamp:(MusicTimeStamp)musicTimeStamp return result; } -- (MusicTimeStamp)musicTimeStampForTimeInSeconds:(NSTimeInterval)timeInSeconds +- (MusicTimeStamp)musicTimeStampForTimeInSeconds:(NSTimeInterval)timeInSeconds options:(MIKMIDISequencerTimeConversionOptions)options { // if (self.tempo == 0 && !self.shouldLoop) { // If tempo is not overridden, and looping is not on // // Just use MIKMIDISequence's simple implementation From a932cbd094b1dd662eab6de91aab2804c1d5f7f1 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Sun, 1 Sep 2019 18:00:20 -0600 Subject: [PATCH 17/53] Issue #269: Implement -[MIKMIDISequencer timeInSecondsForMusicTimeStamp:options:] and add tests for it --- .../MIKMIDI Tests/MIKMIDISequencerTests.m | 151 +++++++++++++++++- Source/MIKMIDISequencer.m | 90 +++++++++-- 2 files changed, 229 insertions(+), 12 deletions(-) diff --git a/Framework/MIKMIDI Tests/MIKMIDISequencerTests.m b/Framework/MIKMIDI Tests/MIKMIDISequencerTests.m index b9a65933..4987e988 100644 --- a/Framework/MIKMIDI Tests/MIKMIDISequencerTests.m +++ b/Framework/MIKMIDI Tests/MIKMIDISequencerTests.m @@ -40,8 +40,10 @@ - (void)testConversionFromMusicTimeStampToSeconds self.sequencer.sequence = s; XCTAssertEqual([self.sequencer timeInSecondsForMusicTimeStamp:0 options:MIKMIDISequencerTimeConversionOptionsNone], 0.0); + // Test with negative number + XCTAssertEqual([self.sequencer timeInSecondsForMusicTimeStamp:-1 options:MIKMIDISequencerTimeConversionOptionsNone], 0.0); - XCTAssertEqualWithAccuracy([s timeInSecondsForMusicTimeStamp:3], [self.sequencer timeInSecondsForMusicTimeStamp:3 options:MIKMIDISequencerTimeConversionOptionsNone], 1e-6 ); + XCTAssertEqualWithAccuracy([s timeInSecondsForMusicTimeStamp:3], [self.sequencer timeInSecondsForMusicTimeStamp:3 options:MIKMIDISequencerTimeConversionOptionsNone], 1e-6); MIKMIDITempoEvent *secondTempo = s.tempoEvents[1]; MusicTimeStamp nextCheck = secondTempo.timeStamp+1; @@ -60,6 +62,11 @@ - (void)testConversionFromMusicTimeStampToSeconds actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + // Test while ignoring tempo override + expected = [s timeInSecondsForMusicTimeStamp:testTimeStamp]; + actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsIgnoreTempoOverride]; + XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + self.sequencer.tempo = 0; // Disable tempo override // Test with looping @@ -129,6 +136,148 @@ - (void)testConversionFromSecondsToMusicTimeStamp self.sequencer.sequence = s; XCTAssertEqual([self.sequencer musicTimeStampForTimeInSeconds:0.0 options:MIKMIDISequencerTimeConversionOptionsNone], 0); + + XCTAssertEqualWithAccuracy([s musicTimeStampForTimeInSeconds:3], [self.sequencer musicTimeStampForTimeInSeconds:3 options:MIKMIDISequencerTimeConversionOptionsNone], 1e-6); + + MIKMIDITempoEvent *secondTempo = s.tempoEvents[1]; + MusicTimeStamp nextCheckTimeStamp = secondTempo.timeStamp+1; + NSTimeInterval nextCheck = [s timeInSecondsForMusicTimeStamp:nextCheckTimeStamp]; + XCTAssertEqualWithAccuracy(nextCheckTimeStamp, [self.sequencer musicTimeStampForTimeInSeconds:nextCheck options:MIKMIDISequencerTimeConversionOptionsNone], 1e-6); + + NSTimeInterval testTimeStamp = 400; + MusicTimeStamp expected = [s musicTimeStampForTimeInSeconds:testTimeStamp]; + MusicTimeStamp actual = [self.sequencer musicTimeStampForTimeInSeconds:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; + XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + + // Test when tempo is overridden + Float64 overrideTempo = 80.0; + self.sequencer.tempo = overrideTempo; + + expected = overrideTempo * testTimeStamp / 60.0; + actual = [self.sequencer musicTimeStampForTimeInSeconds:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; + XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + + // Test while ignoring tempo override + expected = [s musicTimeStampForTimeInSeconds:testTimeStamp]; + actual = [self.sequencer musicTimeStampForTimeInSeconds:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsIgnoreTempoOverride]; + XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + + self.sequencer.tempo = 0; // Disable tempo override + + // Test with looping + self.sequencer.loop = YES; + [self.sequencer setLoopStartTimeStamp:10 endTimeStamp:15]; + + testTimeStamp = 5; // Test before loop region, should be unaffected + expected = [s musicTimeStampForTimeInSeconds:testTimeStamp]; + actual = [self.sequencer musicTimeStampForTimeInSeconds:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; + XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + + testTimeStamp = 8; // Test inside loop region before first loop, should be unaffected + expected = [s musicTimeStampForTimeInSeconds:testTimeStamp]; + actual = [self.sequencer musicTimeStampForTimeInSeconds:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; + XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + + testTimeStamp = 37.5; // Test inside loop region after first loop, should be affected + expected = 57.906294; + actual = [self.sequencer musicTimeStampForTimeInSeconds:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; + XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + + [self.sequencer setLoopStartTimeStamp:200 endTimeStamp:600]; + + testTimeStamp = 160; // Test before loop region, should be unaffected + expected = [s musicTimeStampForTimeInSeconds:testTimeStamp]; + actual = [self.sequencer musicTimeStampForTimeInSeconds:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; + XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + + testTimeStamp = 200; // Test inside loop region before first loop, should be unaffected + expected = [s musicTimeStampForTimeInSeconds:testTimeStamp]; + actual = [self.sequencer musicTimeStampForTimeInSeconds:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; + XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + + testTimeStamp = 450; // Test inside loop region after first loop, should be affected + expected = 896.188353; + actual = [self.sequencer musicTimeStampForTimeInSeconds:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; + XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + + testTimeStamp = 450; // Test inside loop region after first loop, but ignoring looping should be affected + NSTimeInterval withLooping = [self.sequencer musicTimeStampForTimeInSeconds:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; + NSTimeInterval ignoringLooping = [self.sequencer musicTimeStampForTimeInSeconds:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsIgnoreLooping]; + self.sequencer.loop = NO; + NSTimeInterval withoutLooping = [self.sequencer musicTimeStampForTimeInSeconds:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; + XCTAssertNotEqualWithAccuracy(withLooping, ignoringLooping, 1e-6); + XCTAssertEqualWithAccuracy(withoutLooping, ignoringLooping, 1e-6); + + // Test loop "unrolling" + self.sequencer.loop = YES; + [self.sequencer setLoopStartTimeStamp:10 endTimeStamp:15]; + testTimeStamp = 24.284925; // Test inside loop region after first loop, should be affected + NSTimeInterval withoutUnrolling = [self.sequencer musicTimeStampForTimeInSeconds:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsDontUnrollLoop]; + NSTimeInterval withUnrolling = [self.sequencer musicTimeStampForTimeInSeconds:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; + NSTimeInterval expectedWithoutUnrolling = 12.5; + ignoringLooping = [self.sequencer musicTimeStampForTimeInSeconds:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsIgnoreLooping]; + XCTAssertNotEqualWithAccuracy(withoutUnrolling, withUnrolling, 1e-6); + XCTAssertNotEqualWithAccuracy(withUnrolling, ignoringLooping, 1e-6); + XCTAssertEqualWithAccuracy(withoutUnrolling, expectedWithoutUnrolling, 1e-6); +} + +- (void)testTwoWayConversionBetweenMusicTimeStampAndSeconds +{ + NSBundle *bundle = [NSBundle bundleForClass:[self class]]; + NSURL *testMIDIFileURL = [bundle URLForResource:@"tempochanges" withExtension:@"mid"]; + NSError *error = nil; + MIKMIDISequence *s = [MIKMIDISequence sequenceWithFileAtURL:testMIDIFileURL convertMIDIChannelsToTracks:NO error:&error]; + XCTAssertNotNil(s); + self.sequencer.sequence = s; + + NSArray *testMusicTimeStamps = @[@3, @5, @12, @24.284925, @37.5, @57.906294, @160, @550, @850, @896.188353]; + + // Test with default conversion options + MIKMIDISequencerTimeConversionOptions options = MIKMIDISequencerTimeConversionOptionsNone; + for (NSNumber *number in testMusicTimeStamps) { + MusicTimeStamp timeStamp = [number doubleValue]; + NSTimeInterval timeInSeconds = [self.sequencer timeInSecondsForMusicTimeStamp:timeStamp options:options]; + MusicTimeStamp convertedTimeStamp = [self.sequencer musicTimeStampForTimeInSeconds:timeInSeconds options:options]; + NSTimeInterval convertedTimeInSeconds = [self.sequencer timeInSecondsForMusicTimeStamp:convertedTimeStamp options:options]; + XCTAssertEqualWithAccuracy(timeStamp, convertedTimeStamp, 2e-6); + XCTAssertEqualWithAccuracy(timeInSeconds, convertedTimeInSeconds, 2e-6); + } + + // Test with tempo override on + self.sequencer.tempo = 80.0; + for (NSNumber *number in testMusicTimeStamps) { + MusicTimeStamp timeStamp = [number doubleValue]; + NSTimeInterval timeInSeconds = [self.sequencer timeInSecondsForMusicTimeStamp:timeStamp options:options]; + MusicTimeStamp convertedTimeStamp = [self.sequencer musicTimeStampForTimeInSeconds:timeInSeconds options:options]; + NSTimeInterval convertedTimeInSeconds = [self.sequencer timeInSecondsForMusicTimeStamp:convertedTimeStamp options:options]; + XCTAssertEqualWithAccuracy(timeStamp, convertedTimeStamp, 2e-6); + XCTAssertEqualWithAccuracy(timeInSeconds, convertedTimeInSeconds, 2e-6); + } + + // Test ignoring temp override + options = MIKMIDISequencerTimeConversionOptionsIgnoreTempoOverride; + for (NSNumber *number in testMusicTimeStamps) { + MusicTimeStamp timeStamp = [number doubleValue]; + NSTimeInterval timeInSeconds = [self.sequencer timeInSecondsForMusicTimeStamp:timeStamp options:options]; + MusicTimeStamp convertedTimeStamp = [self.sequencer musicTimeStampForTimeInSeconds:timeInSeconds options:options]; + NSTimeInterval convertedTimeInSeconds = [self.sequencer timeInSecondsForMusicTimeStamp:convertedTimeStamp options:options]; + XCTAssertEqualWithAccuracy(timeStamp, convertedTimeStamp, 2e-6); + XCTAssertEqualWithAccuracy(timeInSeconds, convertedTimeInSeconds, 2e-6); + } + + // Test with looping + self.sequencer.tempo = 0; + self.sequencer.loop = YES; + [self.sequencer setLoopStartTimeStamp:200 endTimeStamp:600]; + options = MIKMIDISequencerTimeConversionOptionsNone; + for (NSNumber *number in testMusicTimeStamps) { + MusicTimeStamp timeStamp = [number doubleValue]; + NSTimeInterval timeInSeconds = [self.sequencer timeInSecondsForMusicTimeStamp:timeStamp options:options]; + MusicTimeStamp convertedTimeStamp = [self.sequencer musicTimeStampForTimeInSeconds:timeInSeconds options:options]; + NSTimeInterval convertedTimeInSeconds = [self.sequencer timeInSecondsForMusicTimeStamp:convertedTimeStamp options:options]; + XCTAssertEqualWithAccuracy(timeStamp, convertedTimeStamp, 2e-6); + XCTAssertEqualWithAccuracy(timeInSeconds, convertedTimeInSeconds, 2e-6); + } } - (void)testBuiltinSynthesizers diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index b358900b..c422dd8f 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -661,7 +661,12 @@ - (MIKMIDISynthesizer *)builtinSynthesizerForTrack:(MIKMIDITrack *)track - (NSTimeInterval)timeInSecondsForMusicTimeStamp:(MusicTimeStamp)musicTimeStamp options:(MIKMIDISequencerTimeConversionOptions)options { + if (musicTimeStamp < 0) { return 0; } + + BOOL shouldIgnoreTempoOverride = options & MIKMIDISequencerTimeConversionOptionsIgnoreTempoOverride; BOOL ignoreLooping = options & MIKMIDISequencerTimeConversionOptionsIgnoreLooping; + + // If result is beyond the end of the loop if (!ignoreLooping && self.shouldLoop && musicTimeStamp >= self.loopEndTimeStamp) { options |= MIKMIDISequencerTimeConversionOptionsIgnoreLooping; MusicTimeStamp loopDuration = self.loopEndTimeStamp - self.loopStartTimeStamp; @@ -689,7 +694,7 @@ - (NSTimeInterval)timeInSecondsForMusicTimeStamp:(MusicTimeStamp)musicTimeStamp // Calculate initial tempo, handling case where sequence doesn't specify one. NSArray *tempoEvents = self.sequence.tempoEvents; - if (self.tempo != 0) { // Overridden tempo that should be used instead of events in the tempo track + if (self.tempo != 0 && !shouldIgnoreTempoOverride) { // Overridden tempo that should be used instead of events in the tempo track tempoEvents = @[[MIKMIDITempoEvent tempoEventWithTimeStamp:0 tempo:self.tempo]]; } else { NSUInteger tempoAtZeroIndex = [tempoEvents indexOfObjectPassingTest:^BOOL(MIKMIDITempoEvent *event, NSUInteger i, BOOL *s) { @@ -704,13 +709,13 @@ - (NSTimeInterval)timeInSecondsForMusicTimeStamp:(MusicTimeStamp)musicTimeStamp } // Get tempo events that affect the result (ie. come before musicTimeStamp) and sort them in ascending order + // Check if result would be affected by loop + BOOL timeIsInLoop = self.shouldLoop && musicTimeStamp >= self.loopEndTimeStamp && !ignoreLooping; NSIndexSet *indexesOfTempoEventsAffectingResult = [tempoEvents indexesOfObjectsPassingTest:^BOOL(MIKMIDITempoEvent *event, NSUInteger i, BOOL *s) { - if (!self.shouldLoop || musicTimeStamp < self.loopEndTimeStamp || ignoreLooping) { - return event.timeStamp <= musicTimeStamp; - } - // musicTimeStamp is within the loop region, so include all tempo events up to the end of the loop - return musicTimeStamp <= self.loopEndTimeStamp; + // if musicTimeStamp is within the loop region, include all tempo events up to the end of the loop + MusicTimeStamp limit = timeIsInLoop ? self.loopEndTimeStamp : musicTimeStamp; + return event.timeStamp <= limit; }]; NSArray *tempoEventsAffectingResult = [tempoEvents objectsAtIndexes:indexesOfTempoEventsAffectingResult]; tempoEvents = [tempoEventsAffectingResult sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"timeStamp" ascending:YES]]]; @@ -727,11 +732,74 @@ - (NSTimeInterval)timeInSecondsForMusicTimeStamp:(MusicTimeStamp)musicTimeStamp - (MusicTimeStamp)musicTimeStampForTimeInSeconds:(NSTimeInterval)timeInSeconds options:(MIKMIDISequencerTimeConversionOptions)options { - // if (self.tempo == 0 && !self.shouldLoop) { // If tempo is not overridden, and looping is not on - // // Just use MIKMIDISequence's simple implementation - // return [self.sequence musicTimeStampForTimeInSeconds:timeInSeconds]; - // } - return 0; + BOOL shouldIgnoreTempoOverride = options & MIKMIDISequencerTimeConversionOptionsIgnoreTempoOverride; + BOOL ignoreLooping = options & MIKMIDISequencerTimeConversionOptionsIgnoreLooping; + + NSTimeInterval loopEndTimeInSeconds = self.loopEndTimeStamp > 0 ? [self timeInSecondsForMusicTimeStamp:self.loopEndTimeStamp options:MIKMIDISequencerTimeConversionOptionsIgnoreLooping] : self.sequence.durationInSeconds; + // If result is beyond the end of the loop + if (!ignoreLooping && self.shouldLoop && timeInSeconds >= loopEndTimeInSeconds) { + options |= MIKMIDISequencerTimeConversionOptionsIgnoreLooping; + MusicTimeStamp loopDuration = self.loopEndTimeStamp - self.loopStartTimeStamp; + NSTimeInterval loopStartTimeInSeconds = [self timeInSecondsForMusicTimeStamp:self.loopStartTimeStamp options:options]; + NSTimeInterval loopDurationInSeconds = loopEndTimeInSeconds - loopStartTimeInSeconds; + + NSTimeInterval scratch = timeInSeconds; + scratch -= loopStartTimeInSeconds; // Subtract off time before the loop + MusicTimeStamp result = self.loopStartTimeStamp; + BOOL shouldUnroll = !(options & MIKMIDISequencerTimeConversionOptionsDontUnrollLoop); + // "Use up" the loops until we're down to a fraction of a loop + while (scratch >= loopDurationInSeconds) { + // Only add time for full loops to result if we're unrolling loops + if (shouldUnroll) { + result += loopDuration; + } + scratch -= loopDurationInSeconds; + } + // Add the remaining fraction of a loop + result += [self musicTimeStampForTimeInSeconds:(loopStartTimeInSeconds + scratch) options:options]; + result -= self.loopStartTimeStamp; + return result; + } + + // Calculate initial tempo, handling case where sequence doesn't specify one. + NSArray *tempoEvents = self.sequence.tempoEvents; + if (self.tempo != 0 && !shouldIgnoreTempoOverride) { // Overridden tempo that should be used instead of events in the tempo track + tempoEvents = @[[MIKMIDITempoEvent tempoEventWithTimeStamp:0 tempo:self.tempo]]; + } else { + NSUInteger tempoAtZeroIndex = [tempoEvents indexOfObjectPassingTest:^BOOL(MIKMIDITempoEvent *event, NSUInteger i, BOOL *s) { + return event.timeStamp == 0; + }]; + if (tempoAtZeroIndex == NSNotFound) { + NSMutableArray *scratch = [tempoEvents mutableCopy]; + MIKMIDITempoEvent *initialTempo = [MIKMIDITempoEvent tempoEventWithTimeStamp:0 tempo:kDefaultTempo]; + [scratch insertObject:initialTempo atIndex:0]; + tempoEvents = [scratch copy]; + } + } + + // Get tempo events that affect the result (ie. come before timeInSeconds) and sort them in ascending order + // Check if result would be affected by loop + BOOL timeIsInLoop = self.shouldLoop && timeInSeconds >= loopEndTimeInSeconds && !ignoreLooping; + NSIndexSet *indexesOfTempoEventsAffectingResult = + [tempoEvents indexesOfObjectsPassingTest:^BOOL(MIKMIDITempoEvent *event, NSUInteger i, BOOL *s) { + // if timeInSeconds is within the loop region, include all tempo events up to the end of the loop + NSTimeInterval limit = timeIsInLoop ? loopEndTimeInSeconds : timeInSeconds; + return [self.sequence timeInSecondsForMusicTimeStamp:event.timeStamp] <= limit; + }]; + NSArray *tempoEventsAffectingResult = [tempoEvents objectsAtIndexes:indexesOfTempoEventsAffectingResult]; + tempoEvents = [tempoEventsAffectingResult sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"timeStamp" ascending:YES]]]; + + MusicTimeStamp result = 0.0; + MIKMIDITempoEvent *lastTempoEvent = tempoEvents[0]; + for (MIKMIDITempoEvent *tempoEvent in tempoEvents) { + NSTimeInterval tempoTimeInSeconds = [self.sequence timeInSecondsForMusicTimeStamp:tempoEvent.timeStamp]; + NSTimeInterval lastTempoTimeInSeconds = [self.sequence timeInSecondsForMusicTimeStamp:lastTempoEvent.timeStamp]; + result += lastTempoEvent.bpm * (tempoTimeInSeconds - lastTempoTimeInSeconds) / 60.0; + lastTempoEvent = tempoEvent; + } + NSTimeInterval lastTempoTimeInSeconds = [self.sequence timeInSecondsForMusicTimeStamp:lastTempoEvent.timeStamp]; + result += lastTempoEvent.bpm * (timeInSeconds - lastTempoTimeInSeconds) / 60.0; + return result; } #pragma mark - Click Track From db7cc281c02d9073f41f8eeb2553f426d949d31d Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Sun, 1 Sep 2019 18:12:28 -0600 Subject: [PATCH 18/53] Issue #269: Add documentation for time conversion methods. --- Source/MIKMIDISequence.h | 32 ++++++++++++++++++++++++++++++++ Source/MIKMIDISequencer.h | 25 +++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/Source/MIKMIDISequence.h b/Source/MIKMIDISequence.h index 89c5c505..218ecf37 100644 --- a/Source/MIKMIDISequence.h +++ b/Source/MIKMIDISequence.h @@ -273,7 +273,39 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - Timing + +/** Returns the time in seconds for a given MusicTimeStamp (time in beats) in the sequence. + * + * This method converts a time in beats to the corresponding time in seconds in the sequence, taking into account the tempo of the sequence, including tempo changes. + * + * @note This methhod only considers the sequence itself. If you're playing the sequence using an MIKMIDISequencer, + * you should use the corresponding methods on MIKMIDISequencer, which take into account looping, tempo overrides, and provide options + * to control the details of the conversion algorithm. + * + * @param musicTimeStamp The time in beats you want to convert to seconds. + * + * @return A time in seconds as an NSTimeInterval. + * + * @see -musicTimeStampForTimeInSeconds: + * @see -[MIKMIDISequencer timeInSecondsForMusicTimeStamp:options:] + */ - (NSTimeInterval)timeInSecondsForMusicTimeStamp:(MusicTimeStamp)musicTimeStamp; + +/** Returns the time in beats for a given time in seconds in the sequence. +* +* This method converts a time in seconds to the corresponding time in beats in the sequence, taking into account the tempo of the sequence, including tempo changes. +* +* @note This methhod only considers the sequence itself. If you're playing the sequence using an MIKMIDISequencer, +* you should use the corresponding methods on MIKMIDISequencer, which take into account looping, tempo overrides, and provide options +* to control the details of the conversion algorithm. +* +* @param timeInSeconds The time in seconds you want to convert to a MusicTimeStamp (beats). +* +* @return A time in beats as a MusicTimeStamp. +* +* @see -timeInSecondsForMusicTimeStamp: +* @see -[MIKMIDISequencer timeInSecondsForMusicTimeStamp:options:] +*/ - (MusicTimeStamp)musicTimeStampForTimeInSeconds:(NSTimeInterval)timeInSeconds; #pragma mark - Properties diff --git a/Source/MIKMIDISequencer.h b/Source/MIKMIDISequencer.h index 09c1e6fb..0fc92660 100644 --- a/Source/MIKMIDISequencer.h +++ b/Source/MIKMIDISequencer.h @@ -276,7 +276,32 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - Time Conversion + +/** Returns the time in seconds for a given MusicTimeStamp (time in beats). + * + * This method converts a time in beats to the corresponding time in seconds on the sequencer, taking into account the tempo of the sequence, including tempo changes. + * By default, looping and an overridden tempo, if enabled, will be considered when calculating the result. This behavior can be changed by passing in the appropriate options. + * + * @param musicTimeStamp The time in beats you want to convert to seconds. + * @param options Options to control the details of the conversion algorithm. See MIKMIDISequencerTimeConversionOptions for a list of possible options. + * + * @return A time in seconds as an NSTimeInterval. + * + * @see -musicTimeStampForTimeInSeconds:options: + * @see -[MIKMIDISequence musicTimeStampForTimeInSeconds:] + */ - (NSTimeInterval)timeInSecondsForMusicTimeStamp:(MusicTimeStamp)musicTimeStamp options:(MIKMIDISequencerTimeConversionOptions)options; + +/** Returns the time in beats for a given time in seconds. + * + * @param timeInSeconds The time in seconds you want to convert to a MusicTimeStamp (beats). + * @param options Options to control the details of the conversion algorithm. See MIKMIDISequencerTimeConversionOptions for a list of possible options. + * + * @return A time in beats as a MusicTimeStamp. + * + * @see -timeInSecondsForMusicTimeStamp:options: + * @see -[MIKMIDISequence timeInSecondsForMusicTimeStamp:] + */ - (MusicTimeStamp)musicTimeStampForTimeInSeconds:(NSTimeInterval)timeInSeconds options:(MIKMIDISequencerTimeConversionOptions)options; #pragma mark - Properties From bf143f13ffa65e0256202f799005d14dd493aea9 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Sat, 14 Dec 2019 23:57:19 -0700 Subject: [PATCH 19/53] Issue #282: Don't re-sort tempo events after filtering in -[MIKMIDISequencer timeInSecondsForMusicTimeStamp:options:]. --- Source/MIKMIDISequencer.m | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index e957641e..8215a2d7 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -731,8 +731,7 @@ - (NSTimeInterval)timeInSecondsForMusicTimeStamp:(MusicTimeStamp)musicTimeStamp MusicTimeStamp limit = timeIsInLoop ? self.loopEndTimeStamp : musicTimeStamp; return event.timeStamp <= limit; }]; - NSArray *tempoEventsAffectingResult = [tempoEvents objectsAtIndexes:indexesOfTempoEventsAffectingResult]; - tempoEvents = [tempoEventsAffectingResult sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"timeStamp" ascending:YES]]]; + tempoEvents = [tempoEvents objectsAtIndexes:indexesOfTempoEventsAffectingResult]; NSTimeInterval result = 0.0; MIKMIDITempoEvent *lastTempoEvent = tempoEvents[0]; From bc97bb318b8c93c1cfecd9372a6f2d3ea32313ab Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Sun, 15 Dec 2019 00:30:47 -0700 Subject: [PATCH 20/53] Issue #282: Add (private) MIKMIDITempoTrack to cache tempo events. Speeds up getting tempo events by ~99% again. --- .../MIKMIDI Tests/MIKMIDIEventCachingTests.m | 2 +- Framework/MIKMIDI.xcodeproj/project.pbxproj | 12 ++++++ Source/MIKMIDISequence.m | 5 ++- Source/MIKMIDITempoTrack.h | 23 ++++++++++ Source/MIKMIDITempoTrack.m | 42 +++++++++++++++++++ Source/MIKMIDITrack.m | 2 +- Source/MIKMIDITrack_Protected.h | 6 ++- 7 files changed, 87 insertions(+), 5 deletions(-) create mode 100644 Source/MIKMIDITempoTrack.h create mode 100644 Source/MIKMIDITempoTrack.m diff --git a/Framework/MIKMIDI Tests/MIKMIDIEventCachingTests.m b/Framework/MIKMIDI Tests/MIKMIDIEventCachingTests.m index f78a19bd..fde89336 100644 --- a/Framework/MIKMIDI Tests/MIKMIDIEventCachingTests.m +++ b/Framework/MIKMIDI Tests/MIKMIDIEventCachingTests.m @@ -29,7 +29,7 @@ - (void)setUp self.sequence = sequence; } -- (void)testPerformanceExample { +- (void)testTempoEventsPerformance { // This is an example of a performance test case. [self measureBlock:^{ for (NSInteger i=0; i<5000; i++) { diff --git a/Framework/MIKMIDI.xcodeproj/project.pbxproj b/Framework/MIKMIDI.xcodeproj/project.pbxproj index d329109e..e648cc2f 100644 --- a/Framework/MIKMIDI.xcodeproj/project.pbxproj +++ b/Framework/MIKMIDI.xcodeproj/project.pbxproj @@ -287,6 +287,10 @@ 9DBEBD6A1AAA303700E59734 /* MIKMIDIChannelPressureEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DBEBD661AAA303700E59734 /* MIKMIDIChannelPressureEvent.m */; }; 9DCDDB501AB2363C00F8347E /* Parallax-Loader.mid in Resources */ = {isa = PBXBuildFile; fileRef = 9DCDDB4F1AB2363C00F8347E /* Parallax-Loader.mid */; }; 9DCDDB5A1AB3514100F8347E /* MIKMIDISequencerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DCDDB591AB3514100F8347E /* MIKMIDISequencerTests.m */; }; + 9DD2DEBA23A6142B0023B83E /* MIKMIDITempoTrack.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DD2DEB823A6142B0023B83E /* MIKMIDITempoTrack.h */; }; + 9DD2DEBB23A6142B0023B83E /* MIKMIDITempoTrack.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DD2DEB823A6142B0023B83E /* MIKMIDITempoTrack.h */; }; + 9DD2DEBC23A6142B0023B83E /* MIKMIDITempoTrack.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DD2DEB923A6142B0023B83E /* MIKMIDITempoTrack.m */; }; + 9DD2DEBD23A6142B0023B83E /* MIKMIDITempoTrack.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DD2DEB923A6142B0023B83E /* MIKMIDITempoTrack.m */; }; 9DD48D481B4A2D4A00A50942 /* MIKMIDICommandScheduler.h in Headers */ = {isa = PBXBuildFile; fileRef = 8308F6311B46C482004307AD /* MIKMIDICommandScheduler.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9DE0FDCD23130DD1007E5939 /* tempochanges.mid in Resources */ = {isa = PBXBuildFile; fileRef = 9DE0FDCC23130DD1007E5939 /* tempochanges.mid */; }; 9DE259E519A7B4F800DA93E9 /* AudioUnit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9DE259E419A7B4F800DA93E9 /* AudioUnit.framework */; }; @@ -495,6 +499,8 @@ 9DBEBD661AAA303700E59734 /* MIKMIDIChannelPressureEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIChannelPressureEvent.m; sourceTree = ""; }; 9DCDDB4F1AB2363C00F8347E /* Parallax-Loader.mid */ = {isa = PBXFileReference; lastKnownFileType = audio.midi; path = "Parallax-Loader.mid"; sourceTree = ""; }; 9DCDDB591AB3514100F8347E /* MIKMIDISequencerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDISequencerTests.m; sourceTree = ""; }; + 9DD2DEB823A6142B0023B83E /* MIKMIDITempoTrack.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MIKMIDITempoTrack.h; sourceTree = ""; }; + 9DD2DEB923A6142B0023B83E /* MIKMIDITempoTrack.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MIKMIDITempoTrack.m; sourceTree = ""; }; 9DE0FDCC23130DD1007E5939 /* tempochanges.mid */ = {isa = PBXFileReference; lastKnownFileType = audio.midi; path = tempochanges.mid; sourceTree = ""; }; 9DE259E419A7B4F800DA93E9 /* AudioUnit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioUnit.framework; path = System/Library/Frameworks/AudioUnit.framework; sourceTree = SDKROOT; }; 9DE259E719A7B50600DA93E9 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; @@ -559,6 +565,8 @@ 839D937119C3A319007589C3 /* MIKMIDITrack.h */, 9D76DCEA1A9E52DB00A24C16 /* MIKMIDITrack_Protected.h */, 839D937219C3A319007589C3 /* MIKMIDITrack.m */, + 9DD2DEB823A6142B0023B83E /* MIKMIDITempoTrack.h */, + 9DD2DEB923A6142B0023B83E /* MIKMIDITempoTrack.m */, 9DEE37BF1A9D66C2007B7FC7 /* Events */, ); name = Files; @@ -986,6 +994,7 @@ 9D1D9C201BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.h in Headers */, 9D74EF9217A713A100BEE89F /* MIKMIDIUtilities.h in Headers */, 9D8DC3CF202BD84600DDA4A8 /* MIKMIDIControlChangeCommand+Private.h in Headers */, + 9DD2DEBA23A6142B0023B83E /* MIKMIDITempoTrack.h in Headers */, 83C3716719D607010017186B /* MIKMIDIClientDestinationEndpoint.h in Headers */, 9DAE7D8E19357AAF00B25DD7 /* MIKMIDIEndpointSynthesizer.h in Headers */, 9D74EF9417A713A100BEE89F /* NSUIApplication+MIKMIDI.h in Headers */, @@ -1004,6 +1013,7 @@ 9DAF8B661A7B008A00F46528 /* MIKMIDISystemExclusiveCommand.h in Headers */, 9D0895F41B0D2A4700A5872E /* MIKMIDIMappableResponder.h in Headers */, 9DAF8B681A7B009100F46528 /* MIKMIDIMapping.h in Headers */, + 9DD2DEBB23A6142B0023B83E /* MIKMIDITempoTrack.h in Headers */, 9DAF8B5B1A7B007300F46528 /* MIKMIDIEndpoint.h in Headers */, 9DAF8B5A1A7B007300F46528 /* MIKMIDIOutputPort.h in Headers */, 9DAF8B751A7B00A700F46528 /* MIKMIDIMetaMarkerTextEvent.h in Headers */, @@ -1295,6 +1305,7 @@ 9D74EF9317A713A100BEE89F /* MIKMIDIUtilities.m in Sources */, 9D0895F01B0D29F200A5872E /* MIKMIDIMappingItem.m in Sources */, 9D74EF9517A713A100BEE89F /* NSUIApplication+MIKMIDI.m in Sources */, + 9DD2DEBC23A6142B0023B83E /* MIKMIDITempoTrack.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1368,6 +1379,7 @@ 9DAF8B501A7AFF7500F46528 /* MIKMIDIPrivateUtilities.m in Sources */, 9D0895F11B0D29F200A5872E /* MIKMIDIMappingItem.m in Sources */, 9DEF1CB11AA6800C00E10273 /* MIKMIDIControlChangeEvent.m in Sources */, + 9DD2DEBD23A6142B0023B83E /* MIKMIDITempoTrack.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Source/MIKMIDISequence.m b/Source/MIKMIDISequence.m index 5d09c002..70cfb7a2 100644 --- a/Source/MIKMIDISequence.m +++ b/Source/MIKMIDISequence.m @@ -10,6 +10,7 @@ #import #import "MIKMIDITrack.h" #import "MIKMIDITrack_Protected.h" +#import "MIKMIDITempoTrack.h" #import "MIKMIDITempoEvent.h" #import "MIKMIDIMetaTimeSignatureEvent.h" #import "MIKMIDIDestinationEndpoint.h" @@ -141,7 +142,7 @@ - (instancetype)initWithMusicSequence:(MusicSequence)musicSequence error:(NSErro *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:err userInfo:nil]; return nil; } - self.tempoTrack = [MIKMIDITrack trackWithSequence:self musicTrack:tempoTrack]; + self.tempoTrack = [MIKMIDITempoTrack trackWithSequence:self musicTrack:tempoTrack]; UInt32 numTracks = 0; err = MusicSequenceGetTrackCount(musicSequence, &numTracks); @@ -291,7 +292,7 @@ - (MusicTimeStamp)equivalentTimeStampForLoopedTimeStamp:(MusicTimeStamp)loopedTi - (NSArray *)tempoEvents { - return [self.tempoTrack eventsOfClass:[MIKMIDITempoEvent class] fromTimeStamp:0 toTimeStamp:kMusicTimeStamp_EndOfTrack]; + return [(MIKMIDITempoTrack *)self.tempoTrack tempoEvents]; } - (BOOL)setOverallTempo:(Float64)bpm diff --git a/Source/MIKMIDITempoTrack.h b/Source/MIKMIDITempoTrack.h new file mode 100644 index 00000000..230a02c5 --- /dev/null +++ b/Source/MIKMIDITempoTrack.h @@ -0,0 +1,23 @@ +// +// MIKMIDITempoTrack.h +// MIKMIDI +// +// Created by Andrew R Madsen on 12/15/19. +// Copyright © 2019 Mixed In Key. All rights reserved. +// + +#import +#import "MIKMIDITrack.h" +#import "MIKMIDICompilerCompatibility.h" + +@class MIKMIDITempoEvent; + +NS_ASSUME_NONNULL_BEGIN + +@interface MIKMIDITempoTrack : MIKMIDITrack + +@property (nonatomic, strong, readonly) MIKArrayOf(MIKMIDITempoEvent *) *tempoEvents; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDITempoTrack.m b/Source/MIKMIDITempoTrack.m new file mode 100644 index 00000000..2381a20a --- /dev/null +++ b/Source/MIKMIDITempoTrack.m @@ -0,0 +1,42 @@ +// +// MIKMIDITempoTrack.m +// MIKMIDI +// +// Created by Andrew R Madsen on 12/15/19. +// Copyright © 2019 Mixed In Key. All rights reserved. +// + +#import "MIKMIDITempoTrack.h" +#import "MIKMIDITrack_Protected.h" +#import "MIKMIDITempoEvent.h" + +@interface MIKMIDITempoTrack () + +@property (nonatomic, copy) NSArray *tempoEventsCache; + +@end + +@implementation MIKMIDITempoTrack + +- (void)updateTempoEventsCache +{ + self.tempoEventsCache = [self eventsOfClass:[MIKMIDITempoEvent class] fromTimeStamp:0 toTimeStamp:kMusicTimeStamp_EndOfTrack]; +} + +- (NSArray *)tempoEvents +{ + [self dispatchSyncToSequencerProcessingQueueAsNeeded:^{ + if (!self.tempoEventsCache) { + [self updateTempoEventsCache]; + } + }]; + return self.tempoEventsCache; +} + +- (void)setSortedEventsCache:(NSArray *)sortedEventsCache +{ + [super setSortedEventsCache:sortedEventsCache]; + [self updateTempoEventsCache]; +} + +@end diff --git a/Source/MIKMIDITrack.m b/Source/MIKMIDITrack.m index d3454eac..d6d35f0c 100644 --- a/Source/MIKMIDITrack.m +++ b/Source/MIKMIDITrack.m @@ -8,6 +8,7 @@ #import "MIKMIDISequence.h" #import "MIKMIDITrack.h" +#import "MIKMIDITrack_Protected.h" #import "MIKMIDIEvent.h" #import "MIKMIDINoteEvent.h" #import "MIKMIDITempoEvent.h" @@ -25,7 +26,6 @@ @interface MIKMIDITrack () @property (weak, nonatomic, nullable) MIKMIDISequence *sequence; @property (nonatomic, strong) NSMutableSet *internalEvents; -@property (nonatomic, strong) NSArray *sortedEventsCache; @property (nonatomic) MusicTimeStamp restoredLength; @property (nonatomic) MusicTrackLoopInfo restoredLoopInfo; diff --git a/Source/MIKMIDITrack_Protected.h b/Source/MIKMIDITrack_Protected.h index 6bbeff4a..b01a8da6 100644 --- a/Source/MIKMIDITrack_Protected.h +++ b/Source/MIKMIDITrack_Protected.h @@ -41,6 +41,10 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)restoreLengthAndLoopInfo; +- (void)dispatchSyncToSequencerProcessingQueueAsNeeded:(void (^)(void))block; + +@property (nonatomic, strong, nullable) NSArray *sortedEventsCache; + @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END From 763ecde9c29275550f7d791c73b2138614c0ab62 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Sun, 15 Dec 2019 00:47:33 -0700 Subject: [PATCH 21/53] Issue #282: Avoid redundant calls to -timeInSecondsForMusicTimeStamp within -musicTimeStampForTimeInSeconds: --- Source/MIKMIDISequencer.m | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index 8215a2d7..0b45b1e3 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -825,13 +825,15 @@ - (MusicTimeStamp)musicTimeStampForTimeInSeconds:(NSTimeInterval)timeInSeconds o MusicTimeStamp result = 0.0; MIKMIDITempoEvent *lastTempoEvent = tempoEvents[0]; + NSTimeInterval tempoTimeInSeconds = 0; + NSTimeInterval lastTempoTimeInSeconds = [self timeInSecondsForMusicTimeStamp:lastTempoEvent.timeStamp options:ignoreOverridesIfNeeded]; for (MIKMIDITempoEvent *tempoEvent in tempoEvents) { - NSTimeInterval tempoTimeInSeconds = [self timeInSecondsForMusicTimeStamp:tempoEvent.timeStamp options:ignoreOverridesIfNeeded]; - NSTimeInterval lastTempoTimeInSeconds = [self timeInSecondsForMusicTimeStamp:lastTempoEvent.timeStamp options:ignoreOverridesIfNeeded]; + lastTempoTimeInSeconds = tempoTimeInSeconds; + tempoTimeInSeconds = [self timeInSecondsForMusicTimeStamp:tempoEvent.timeStamp options:ignoreOverridesIfNeeded]; result += lastTempoEvent.bpm * (tempoTimeInSeconds - lastTempoTimeInSeconds) / 60.0; lastTempoEvent = tempoEvent; } - NSTimeInterval lastTempoTimeInSeconds = [self timeInSecondsForMusicTimeStamp:lastTempoEvent.timeStamp options:ignoreOverridesIfNeeded]; + lastTempoTimeInSeconds = [self timeInSecondsForMusicTimeStamp:lastTempoEvent.timeStamp options:ignoreOverridesIfNeeded]; result += lastTempoEvent.bpm * (timeInSeconds - lastTempoTimeInSeconds) / 60.0; return result; } From 86f20b4301ec5c62be333018a7149dff4f750463 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Mon, 18 May 2020 22:44:57 -0600 Subject: [PATCH 22/53] Increase required accuracy of time conversion tests --- .../MIKMIDI Tests/MIKMIDISequencerTests.m | 72 +++++++++---------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/Framework/MIKMIDI Tests/MIKMIDISequencerTests.m b/Framework/MIKMIDI Tests/MIKMIDISequencerTests.m index dc465a5c..cd8721ce 100644 --- a/Framework/MIKMIDI Tests/MIKMIDISequencerTests.m +++ b/Framework/MIKMIDI Tests/MIKMIDISequencerTests.m @@ -47,12 +47,12 @@ - (void)testConversionFromMusicTimeStampToSeconds MIKMIDITempoEvent *secondTempo = s.tempoEvents[1]; MusicTimeStamp nextCheck = secondTempo.timeStamp+1; - XCTAssertEqualWithAccuracy([s timeInSecondsForMusicTimeStamp:nextCheck], [self.sequencer timeInSecondsForMusicTimeStamp:nextCheck options:MIKMIDISequencerTimeConversionOptionsNone], 1e-6); + XCTAssertEqual([s timeInSecondsForMusicTimeStamp:nextCheck], [self.sequencer timeInSecondsForMusicTimeStamp:nextCheck options:MIKMIDISequencerTimeConversionOptionsNone]); MusicTimeStamp testTimeStamp = 850; NSTimeInterval expected = [s timeInSecondsForMusicTimeStamp:testTimeStamp]; NSTimeInterval actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; - XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + XCTAssertEqualWithAccuracy(expected, actual, 1e-13); // Test when tempo is overridden Float64 overrideTempo = 80.0; @@ -60,12 +60,12 @@ - (void)testConversionFromMusicTimeStampToSeconds expected = 60 * testTimeStamp / overrideTempo; actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; - XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + XCTAssertEqualWithAccuracy(expected, actual, 1e-12); // Test while ignoring tempo override expected = [s timeInSecondsForMusicTimeStamp:testTimeStamp]; actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsIgnoreTempoOverride]; - XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + XCTAssertEqualWithAccuracy(expected, actual, 1e-12); self.sequencer.tempo = 0; // Disable tempo override @@ -76,42 +76,42 @@ - (void)testConversionFromMusicTimeStampToSeconds testTimeStamp = 5; // Test before loop region, should be unaffected expected = [s timeInSecondsForMusicTimeStamp:testTimeStamp]; actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; - XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + XCTAssertEqualWithAccuracy(expected, actual, 1e-12); testTimeStamp = 12; // Test inside loop region before first loop, should be unaffected expected = [s timeInSecondsForMusicTimeStamp:testTimeStamp]; actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; - XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + XCTAssertEqualWithAccuracy(expected, actual, 1e-12); testTimeStamp = 37.5; // Test inside loop region after first loop, should be affected expected = 24.284925; actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; - XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + XCTAssertEqualWithAccuracy(expected, actual, 1e-12); [self.sequencer setLoopStartTimeStamp:200 endTimeStamp:600]; testTimeStamp = 160; // Test before loop region, should be unaffected expected = [s timeInSecondsForMusicTimeStamp:testTimeStamp]; actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; - XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + XCTAssertEqualWithAccuracy(expected, actual, 1e-12); testTimeStamp = 550; // Test inside loop region before first loop, should be unaffected expected = [s timeInSecondsForMusicTimeStamp:testTimeStamp]; actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; - XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + XCTAssertEqualWithAccuracy(expected, actual, 1e-12); testTimeStamp = 850; // Test inside loop region after first loop, should be affected expected = 427.46909; actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; - XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + XCTAssertEqualWithAccuracy(expected, actual, 1e-12); testTimeStamp = 850; // Test inside loop region after first loop, but ignoring looping should be affected NSTimeInterval withLooping = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; NSTimeInterval ignoringLooping = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsIgnoreLooping]; self.sequencer.loop = NO; NSTimeInterval withoutLooping = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; - XCTAssertNotEqualWithAccuracy(withLooping, ignoringLooping, 1e-6); - XCTAssertEqualWithAccuracy(withoutLooping, ignoringLooping, 1e-6); + XCTAssertNotEqualWithAccuracy(withLooping, ignoringLooping, 1e-12); + XCTAssertEqualWithAccuracy(withoutLooping, ignoringLooping, 1e-12); // Test loop "unrolling" self.sequencer.loop = YES; @@ -121,9 +121,9 @@ - (void)testConversionFromMusicTimeStampToSeconds NSTimeInterval withUnrolling = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; NSTimeInterval expectedWithoutUnrolling = 8.094975; ignoringLooping = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsIgnoreLooping]; - XCTAssertNotEqualWithAccuracy(withoutUnrolling, withUnrolling, 1e-6); - XCTAssertNotEqualWithAccuracy(withUnrolling, ignoringLooping, 1e-6); - XCTAssertEqualWithAccuracy(withoutUnrolling, expectedWithoutUnrolling, 1e-6); + XCTAssertNotEqualWithAccuracy(withoutUnrolling, withUnrolling, 1e-12); + XCTAssertNotEqualWithAccuracy(withUnrolling, ignoringLooping, 1e-12); + XCTAssertEqualWithAccuracy(withoutUnrolling, expectedWithoutUnrolling, 1e-12); // Test with non-default rates for (NSNumber *rateNum in @[@0.9, @1.1, @2.0]) { @@ -135,39 +135,39 @@ - (void)testConversionFromMusicTimeStampToSeconds testTimeStamp = 5; // Test before loop region expected = [s timeInSecondsForMusicTimeStamp:testTimeStamp] / rate; actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; - XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + XCTAssertEqualWithAccuracy(expected, actual, 1e-12); testTimeStamp = 12; // Test inside loop region before first loop expected = [s timeInSecondsForMusicTimeStamp:testTimeStamp] / rate; actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; - XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + XCTAssertEqualWithAccuracy(expected, actual, 1e-12); testTimeStamp = 37.5; // Test inside loop region after first loop expected = 24.284925 / rate; actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; - XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + XCTAssertEqualWithAccuracy(expected, actual, 1e-12); [self.sequencer setLoopStartTimeStamp:200 endTimeStamp:600]; testTimeStamp = 160; // Test before loop region expected = [s timeInSecondsForMusicTimeStamp:testTimeStamp] / rate; actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; - XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + XCTAssertEqualWithAccuracy(expected, actual, 1e-12); testTimeStamp = 550; // Test inside loop region before first loop expected = [s timeInSecondsForMusicTimeStamp:testTimeStamp] / rate; actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; - XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + XCTAssertEqualWithAccuracy(expected, actual, 1e-12); testTimeStamp = 850; // Test inside loop region after first loop expected = 427.46909 / rate; actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; - XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + XCTAssertEqualWithAccuracy(expected, actual, 1e-12); testTimeStamp = 850; // Test inside loop region after first loop while ignoring rate expected = 427.46909; actual = [self.sequencer timeInSecondsForMusicTimeStamp:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsIgnoreRate]; - XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + XCTAssertEqualWithAccuracy(expected, actual, 1e-12); } } @@ -182,17 +182,17 @@ - (void)testConversionFromSecondsToMusicTimeStamp XCTAssertEqual([self.sequencer musicTimeStampForTimeInSeconds:0.0 options:MIKMIDISequencerTimeConversionOptionsNone], 0); - XCTAssertEqualWithAccuracy([s musicTimeStampForTimeInSeconds:3], [self.sequencer musicTimeStampForTimeInSeconds:3 options:MIKMIDISequencerTimeConversionOptionsNone], 1e-6); + XCTAssertEqualWithAccuracy([s musicTimeStampForTimeInSeconds:3], [self.sequencer musicTimeStampForTimeInSeconds:3 options:MIKMIDISequencerTimeConversionOptionsNone], 1e-12); MIKMIDITempoEvent *secondTempo = s.tempoEvents[1]; MusicTimeStamp nextCheckTimeStamp = secondTempo.timeStamp+1; NSTimeInterval nextCheck = [s timeInSecondsForMusicTimeStamp:nextCheckTimeStamp]; - XCTAssertEqualWithAccuracy(nextCheckTimeStamp, [self.sequencer musicTimeStampForTimeInSeconds:nextCheck options:MIKMIDISequencerTimeConversionOptionsNone], 1e-6); + XCTAssertEqualWithAccuracy(nextCheckTimeStamp, [self.sequencer musicTimeStampForTimeInSeconds:nextCheck options:MIKMIDISequencerTimeConversionOptionsNone], 1e-12); NSTimeInterval testTimeStamp = 400; MusicTimeStamp expected = [s musicTimeStampForTimeInSeconds:testTimeStamp]; MusicTimeStamp actual = [self.sequencer musicTimeStampForTimeInSeconds:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; - XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + XCTAssertEqualWithAccuracy(expected, actual, 1e-12); // Test when tempo is overridden Float64 overrideTempo = 80.0; @@ -200,12 +200,12 @@ - (void)testConversionFromSecondsToMusicTimeStamp expected = overrideTempo * testTimeStamp / 60.0; actual = [self.sequencer musicTimeStampForTimeInSeconds:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; - XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + XCTAssertEqualWithAccuracy(expected, actual, 1e-12); // Test while ignoring tempo override expected = [s musicTimeStampForTimeInSeconds:testTimeStamp]; actual = [self.sequencer musicTimeStampForTimeInSeconds:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsIgnoreTempoOverride]; - XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + XCTAssertEqualWithAccuracy(expected, actual, 1e-12); self.sequencer.tempo = 0; // Disable tempo override @@ -216,12 +216,12 @@ - (void)testConversionFromSecondsToMusicTimeStamp testTimeStamp = 5; // Test before loop region, should be unaffected expected = [s musicTimeStampForTimeInSeconds:testTimeStamp]; actual = [self.sequencer musicTimeStampForTimeInSeconds:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; - XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + XCTAssertEqualWithAccuracy(expected, actual, 1e-12); testTimeStamp = 8; // Test inside loop region before first loop, should be unaffected expected = [s musicTimeStampForTimeInSeconds:testTimeStamp]; actual = [self.sequencer musicTimeStampForTimeInSeconds:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; - XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + XCTAssertEqualWithAccuracy(expected, actual, 1e-12); testTimeStamp = 37.5; // Test inside loop region after first loop, should be affected expected = 57.906294; @@ -233,12 +233,12 @@ - (void)testConversionFromSecondsToMusicTimeStamp testTimeStamp = 160; // Test before loop region, should be unaffected expected = [s musicTimeStampForTimeInSeconds:testTimeStamp]; actual = [self.sequencer musicTimeStampForTimeInSeconds:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; - XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + XCTAssertEqualWithAccuracy(expected, actual, 1e-12); testTimeStamp = 200; // Test inside loop region before first loop, should be unaffected expected = [s musicTimeStampForTimeInSeconds:testTimeStamp]; actual = [self.sequencer musicTimeStampForTimeInSeconds:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; - XCTAssertEqualWithAccuracy(expected, actual, 1e-6); + XCTAssertEqualWithAccuracy(expected, actual, 1e-12); testTimeStamp = 450; // Test inside loop region after first loop, should be affected expected = 896.188353; @@ -250,8 +250,8 @@ - (void)testConversionFromSecondsToMusicTimeStamp NSTimeInterval ignoringLooping = [self.sequencer musicTimeStampForTimeInSeconds:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsIgnoreLooping]; self.sequencer.loop = NO; NSTimeInterval withoutLooping = [self.sequencer musicTimeStampForTimeInSeconds:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; - XCTAssertNotEqualWithAccuracy(withLooping, ignoringLooping, 1e-6); - XCTAssertEqualWithAccuracy(withoutLooping, ignoringLooping, 1e-6); + XCTAssertNotEqualWithAccuracy(withLooping, ignoringLooping, 1e-12); + XCTAssertEqualWithAccuracy(withoutLooping, ignoringLooping, 1e-12); // Test loop "unrolling" self.sequencer.loop = YES; @@ -261,9 +261,9 @@ - (void)testConversionFromSecondsToMusicTimeStamp NSTimeInterval withUnrolling = [self.sequencer musicTimeStampForTimeInSeconds:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsNone]; NSTimeInterval expectedWithoutUnrolling = 12.5; ignoringLooping = [self.sequencer musicTimeStampForTimeInSeconds:testTimeStamp options:MIKMIDISequencerTimeConversionOptionsIgnoreLooping]; - XCTAssertNotEqualWithAccuracy(withoutUnrolling, withUnrolling, 1e-6); - XCTAssertNotEqualWithAccuracy(withUnrolling, ignoringLooping, 1e-6); - XCTAssertEqualWithAccuracy(withoutUnrolling, expectedWithoutUnrolling, 1e-6); + XCTAssertNotEqualWithAccuracy(withoutUnrolling, withUnrolling, 1e-12); + XCTAssertNotEqualWithAccuracy(withUnrolling, ignoringLooping, 1e-12); + XCTAssertEqualWithAccuracy(withoutUnrolling, expectedWithoutUnrolling, 1e-12); } - (void)testTwoWayConversionBetweenMusicTimeStampAndSeconds From 223de839d27da5ad611f70cc2b135e08434472c4 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Tue, 7 Jul 2020 20:54:55 -0600 Subject: [PATCH 23/53] Issue #292: Update to latest recommended settings (Xcode 12 beta) --- .../MIDI Files Testbed.xcodeproj/project.pbxproj | 14 ++++++++++++-- Framework/MIKMIDI.xcodeproj/project.pbxproj | 12 +++++++----- .../xcshareddata/xcschemes/MIKMIDI-iOS.xcscheme | 15 +-------------- .../xcshareddata/xcschemes/MIKMIDI.xcscheme | 11 +---------- 4 files changed, 21 insertions(+), 31 deletions(-) diff --git a/Examples/macOS/MIDI Files Testbed/MIDI Files Testbed.xcodeproj/project.pbxproj b/Examples/macOS/MIDI Files Testbed/MIDI Files Testbed.xcodeproj/project.pbxproj index 4ab83e8b..fd8bcd6a 100644 --- a/Examples/macOS/MIDI Files Testbed/MIDI Files Testbed.xcodeproj/project.pbxproj +++ b/Examples/macOS/MIDI Files Testbed/MIDI Files Testbed.xcodeproj/project.pbxproj @@ -214,12 +214,12 @@ isa = PBXProject; attributes = { CLASSPREFIX = MIK; - LastUpgradeCheck = 0920; + LastUpgradeCheck = 1200; ORGANIZATIONNAME = "Mixed In Key"; }; buildConfigurationList = 9DB2A5EE192D184D0047A3EB /* Build configuration list for PBXProject "MIDI Files Testbed" */; compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, @@ -343,6 +343,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; @@ -351,14 +352,17 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -393,6 +397,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; @@ -401,14 +406,17 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -436,6 +444,7 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "-"; COMBINE_HIDPI_IMAGES = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "Source/MIDI Files Testbed-Prefix.pch"; @@ -451,6 +460,7 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "-"; COMBINE_HIDPI_IMAGES = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "Source/MIDI Files Testbed-Prefix.pch"; diff --git a/Framework/MIKMIDI.xcodeproj/project.pbxproj b/Framework/MIKMIDI.xcodeproj/project.pbxproj index d3977e7c..66906c93 100644 --- a/Framework/MIKMIDI.xcodeproj/project.pbxproj +++ b/Framework/MIKMIDI.xcodeproj/project.pbxproj @@ -1139,7 +1139,7 @@ 9D74EE9C17A7129300BEE89F /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0940; + LastUpgradeCheck = 1200; ORGANIZATIONNAME = "Mixed In Key"; TargetAttributes = { 9D4DF1391AAB57430065F004 = { @@ -1156,11 +1156,11 @@ }; buildConfigurationList = 9D74EE9F17A7129300BEE89F /* Build configuration list for PBXProject "MIKMIDI" */; compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( - English, en, + Base, ); mainGroup = 9D74EE9B17A7129300BEE89F; productRefGroup = 9D74EEA617A7129300BEE89F /* Products */; @@ -1464,6 +1464,7 @@ CLANG_WARN_OBJC_INTERFACE_IVARS = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES_AGGRESSIVE; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -1524,6 +1525,7 @@ CLANG_WARN_OBJC_INTERFACE_IVARS = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES_AGGRESSIVE; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -1601,7 +1603,7 @@ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; - CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_IDENTITY = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; @@ -1645,7 +1647,7 @@ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; - CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_IDENTITY = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; diff --git a/Framework/MIKMIDI.xcodeproj/xcshareddata/xcschemes/MIKMIDI-iOS.xcscheme b/Framework/MIKMIDI.xcodeproj/xcshareddata/xcschemes/MIKMIDI-iOS.xcscheme index 4bc0b932..3df7613d 100644 --- a/Framework/MIKMIDI.xcodeproj/xcshareddata/xcschemes/MIKMIDI-iOS.xcscheme +++ b/Framework/MIKMIDI.xcodeproj/xcshareddata/xcschemes/MIKMIDI-iOS.xcscheme @@ -1,6 +1,6 @@ - - - - - - - - - - - - From 96163956c0f631cd09b109e62b4111f4123586a0 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Tue, 7 Jul 2020 20:55:18 -0600 Subject: [PATCH 24/53] Issue #292: Switch all includes in MIKMIDI headers to use #import format. Fixes warnings --- Source/MIKMIDI.h | 128 +++++++++--------- Source/MIKMIDIChannelEvent.h | 8 +- Source/MIKMIDIChannelPressureCommand.h | 4 +- Source/MIKMIDIChannelPressureEvent.h | 6 +- Source/MIKMIDIChannelVoiceCommand.h | 6 +- ...KMIDIChannelVoiceCommand_SubclassMethods.h | 8 +- Source/MIKMIDIClientDestinationEndpoint.h | 6 +- Source/MIKMIDIClientSourceEndpoint.h | 6 +- Source/MIKMIDIClock.h | 2 +- Source/MIKMIDICommand.h | 2 +- Source/MIKMIDICommandScheduler.h | 2 +- Source/MIKMIDICommandThrottler.h | 2 +- Source/MIKMIDICommand_SubclassMethods.h | 4 +- Source/MIKMIDIConnectionManager.h | 4 +- Source/MIKMIDIControlChangeCommand.h | 4 +- Source/MIKMIDIControlChangeEvent.h | 6 +- Source/MIKMIDIDestinationEndpoint.h | 8 +- Source/MIKMIDIDevice.h | 6 +- Source/MIKMIDIDeviceManager.h | 4 +- Source/MIKMIDIEndpoint.h | 6 +- Source/MIKMIDIEndpointSynthesizer.h | 4 +- Source/MIKMIDIEntity.h | 6 +- Source/MIKMIDIErrors.h | 4 +- Source/MIKMIDIEvent.h | 6 +- Source/MIKMIDIEventIterator.h | 4 +- Source/MIKMIDIEvent_SubclassMethods.h | 6 +- Source/MIKMIDIInputPort.h | 6 +- Source/MIKMIDIMappableResponder.h | 6 +- Source/MIKMIDIMapping.h | 8 +- Source/MIKMIDIMappingGenerator.h | 6 +- Source/MIKMIDIMappingItem.h | 8 +- Source/MIKMIDIMappingManager.h | 2 +- Source/MIKMIDIMappingXMLParser.h | 4 +- Source/MIKMIDIMetaCopyrightEvent.h | 6 +- Source/MIKMIDIMetaCuePointEvent.h | 6 +- Source/MIKMIDIMetaEvent.h | 6 +- Source/MIKMIDIMetaEvent_SubclassMethods.h | 4 +- Source/MIKMIDIMetaInstrumentNameEvent.h | 6 +- Source/MIKMIDIMetaKeySignatureEvent.h | 6 +- Source/MIKMIDIMetaLyricEvent.h | 6 +- Source/MIKMIDIMetaMarkerTextEvent.h | 6 +- Source/MIKMIDIMetaSequenceEvent.h | 6 +- Source/MIKMIDIMetaTextEvent.h | 6 +- Source/MIKMIDIMetaTimeSignatureEvent.h | 6 +- Source/MIKMIDIMetaTrackSequenceNameEvent.h | 6 +- Source/MIKMIDIMetronome.h | 4 +- Source/MIKMIDINoteCommand.h | 4 +- Source/MIKMIDINoteCommand_SubclassMethods.h | 4 +- Source/MIKMIDINoteEvent.h | 10 +- Source/MIKMIDINoteOffCommand.h | 2 +- Source/MIKMIDINoteOffCommand.m | 1 + Source/MIKMIDINoteOnCommand.h | 2 +- Source/MIKMIDINoteOnCommand.m | 1 + Source/MIKMIDIObject.h | 4 +- Source/MIKMIDIObject_SubclassMethods.h | 6 +- Source/MIKMIDIOutputPort.h | 4 +- Source/MIKMIDIPitchBendChangeCommand.h | 6 +- Source/MIKMIDIPitchBendChangeEvent.h | 6 +- Source/MIKMIDIPlayer.h | 2 +- Source/MIKMIDIPolyphonicKeyPressureCommand.h | 2 +- Source/MIKMIDIPolyphonicKeyPressureEvent.h | 6 +- Source/MIKMIDIPort.h | 6 +- Source/MIKMIDIPrivateUtilities.h | 2 +- Source/MIKMIDIProgramChangeCommand.h | 6 +- Source/MIKMIDIProgramChangeEvent.h | 6 +- Source/MIKMIDIResponder.h | 4 +- Source/MIKMIDISequence+MIKMIDIPrivate.h | 4 +- Source/MIKMIDISequence.h | 4 +- Source/MIKMIDISequencer+MIKMIDIPrivate.h | 4 +- Source/MIKMIDISequencer.h | 4 +- Source/MIKMIDISourceEndpoint.h | 6 +- Source/MIKMIDISynthesizer.h | 6 +- Source/MIKMIDISynthesizerInstrument.h | 4 +- Source/MIKMIDISynthesizer_SubclassMethods.h | 4 +- Source/MIKMIDISystemExclusiveCommand.h | 4 +- Source/MIKMIDISystemKeepAliveCommand.h | 2 +- Source/MIKMIDISystemMessageCommand.h | 6 +- Source/MIKMIDITempoEvent.h | 6 +- Source/MIKMIDITrack.h | 2 +- Source/MIKMIDITrack_Protected.h | 6 +- Source/MIKMIDIUtilities.h | 6 +- Source/NSUIApplication+MIKMIDI.h | 4 +- 82 files changed, 261 insertions(+), 261 deletions(-) diff --git a/Source/MIKMIDI.h b/Source/MIKMIDI.h index 8112e221..1592fe88 100644 --- a/Source/MIKMIDI.h +++ b/Source/MIKMIDI.h @@ -9,92 +9,92 @@ /** Umbrella header for MIKMIDI public interface. */ // Core MIDI object wrapper -#import "MIKMIDIObject.h" +#import // MIDI port -#import "MIKMIDIPort.h" -#import "MIKMIDIInputPort.h" -#import "MIKMIDIOutputPort.h" +#import +#import +#import // MIDI Device support -#import "MIKMIDIDevice.h" -#import "MIKMIDIDeviceManager.h" -#import "MIKMIDIConnectionManager.h" +#import +#import +#import -#import "MIKMIDIEntity.h" +#import // Endpoints -#import "MIKMIDIEndpoint.h" -#import "MIKMIDIDestinationEndpoint.h" -#import "MIKMIDISourceEndpoint.h" -#import "MIKMIDIClientDestinationEndpoint.h" -#import "MIKMIDIClientSourceEndpoint.h" +#import +#import +#import +#import +#import // MIDI Commands/Messages -#import "MIKMIDICommand.h" -#import "MIKMIDIChannelVoiceCommand.h" -#import "MIKMIDINoteCommand.h" -#import "MIKMIDIChannelPressureCommand.h" -#import "MIKMIDIControlChangeCommand.h" -#import "MIKMIDIProgramChangeCommand.h" -#import "MIKMIDIPitchBendChangeCommand.h" -#import "MIKMIDINoteOnCommand.h" -#import "MIKMIDINoteOffCommand.h" -#import "MIKMIDIPolyphonicKeyPressureCommand.h" -#import "MIKMIDISystemExclusiveCommand.h" -#import "MIKMIDISystemMessageCommand.h" -#import "MIKMIDISystemKeepAliveCommand.h" +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import // MIDI Sequence/File support -#import "MIKMIDISequence.h" -#import "MIKMIDITrack.h" +#import +#import // MIDI Events -#import "MIKMIDIEvent.h" -#import "MIKMIDITempoEvent.h" -#import "MIKMIDINoteEvent.h" +#import +#import +#import // Channel Events -#import "MIKMIDIChannelEvent.h" -#import "MIKMIDIPolyphonicKeyPressureEvent.h" -#import "MIKMIDIControlChangeEvent.h" -#import "MIKMIDIProgramChangeEvent.h" -#import "MIKMIDIChannelPressureEvent.h" -#import "MIKMIDIPitchBendChangeEvent.h" +#import +#import +#import +#import +#import +#import // Meta Events -#import "MIKMIDIMetaEvent.h" -#import "MIKMIDIMetaCopyrightEvent.h" -#import "MIKMIDIMetaCuePointEvent.h" -#import "MIKMIDIMetaInstrumentNameEvent.h" -#import "MIKMIDIMetaKeySignatureEvent.h" -#import "MIKMIDIMetaLyricEvent.h" -#import "MIKMIDIMetaMarkerTextEvent.h" -#import "MIKMIDIMetaSequenceEvent.h" -#import "MIKMIDIMetaTextEvent.h" -#import "MIKMIDIMetaTimeSignatureEvent.h" -#import "MIKMIDIMetaTrackSequenceNameEvent.h" +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import // Sequencing and Synthesis -#import "MIKMIDISequencer.h" -#import "MIKMIDIMetronome.h" -#import "MIKMIDIClock.h" -#import "MIKMIDIPlayer.h" -#import "MIKMIDIEndpointSynthesizer.h" +#import +#import +#import +#import +#import // MIDI Mapping -#import "MIKMIDIMapping.h" -#import "MIKMIDIMappingItem.h" -#import "MIKMIDIMappableResponder.h" -#import "MIKMIDIMappingManager.h" -#import "MIKMIDIMappingGenerator.h" +#import +#import +#import +#import +#import // Intra-application MIDI command routing -#import "NSUIApplication+MIKMIDI.h" -#import "MIKMIDIResponder.h" -#import "MIKMIDICommandThrottler.h" +#import +#import +#import // Utilities -#import "MIKMIDIUtilities.h" -#import "MIKMIDIErrors.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import +#import diff --git a/Source/MIKMIDIChannelEvent.h b/Source/MIKMIDIChannelEvent.h index 1abe326e..1d92b4cf 100644 --- a/Source/MIKMIDIChannelEvent.h +++ b/Source/MIKMIDIChannelEvent.h @@ -6,8 +6,8 @@ // Copyright (c) 2015 Mixed In Key. All rights reserved. // -#import "MIKMIDIEvent.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -59,7 +59,7 @@ NS_ASSUME_NONNULL_END #pragma mark - -#import "MIKMIDICommand.h" +#import @class MIKMIDIClock; @@ -71,4 +71,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIChannelPressureCommand.h b/Source/MIKMIDIChannelPressureCommand.h index 8cdbacff..af545c18 100644 --- a/Source/MIKMIDIChannelPressureCommand.h +++ b/Source/MIKMIDIChannelPressureCommand.h @@ -6,8 +6,8 @@ // Copyright © 2015 Mixed In Key. All rights reserved. // -#import "MIKMIDIChannelVoiceCommand.h" -#import "MIKMIDIChannelPressureCommand.h" +#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/Source/MIKMIDIChannelPressureEvent.h b/Source/MIKMIDIChannelPressureEvent.h index 27ba7afd..b83142b1 100644 --- a/Source/MIKMIDIChannelPressureEvent.h +++ b/Source/MIKMIDIChannelPressureEvent.h @@ -6,8 +6,8 @@ // Copyright (c) 2015 Mixed In Key. All rights reserved. // -#import "MIKMIDIChannelEvent.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -42,4 +42,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIChannelVoiceCommand.h b/Source/MIKMIDIChannelVoiceCommand.h index 93e606d6..751e8dde 100644 --- a/Source/MIKMIDIChannelVoiceCommand.h +++ b/Source/MIKMIDIChannelVoiceCommand.h @@ -6,8 +6,8 @@ // Copyright (c) 2013 Mixed In Key. All rights reserved. // -#import "MIKMIDICommand.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -53,4 +53,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIChannelVoiceCommand_SubclassMethods.h b/Source/MIKMIDIChannelVoiceCommand_SubclassMethods.h index 7806d69f..3cc814c7 100644 --- a/Source/MIKMIDIChannelVoiceCommand_SubclassMethods.h +++ b/Source/MIKMIDIChannelVoiceCommand_SubclassMethods.h @@ -6,9 +6,9 @@ // Copyright (c) 2013 Mixed In Key. All rights reserved. // -#import "MIKMIDIChannelVoiceCommand.h" -#import "MIKMIDICommand_SubclassMethods.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -18,4 +18,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIClientDestinationEndpoint.h b/Source/MIKMIDIClientDestinationEndpoint.h index 67d61ebb..bb7afd94 100644 --- a/Source/MIKMIDIClientDestinationEndpoint.h +++ b/Source/MIKMIDIClientDestinationEndpoint.h @@ -6,8 +6,8 @@ // // -#import "MIKMIDIDestinationEndpoint.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import @class MIKMIDIClientDestinationEndpoint; @class MIKMIDICommand; @@ -50,4 +50,4 @@ typedef void(^MIKMIDIClientDestinationEndpointEventHandler)(MIKMIDIClientDestina @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIClientSourceEndpoint.h b/Source/MIKMIDIClientSourceEndpoint.h index 0986b0cd..8987307d 100644 --- a/Source/MIKMIDIClientSourceEndpoint.h +++ b/Source/MIKMIDIClientSourceEndpoint.h @@ -5,8 +5,8 @@ // Created by Dan Rosenstark on 2015-01-07 // -#import "MIKMIDISourceEndpoint.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import @class MIKMIDICommand; @@ -70,4 +70,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIClock.h b/Source/MIKMIDIClock.h index 088e65b0..c4a4b265 100644 --- a/Source/MIKMIDIClock.h +++ b/Source/MIKMIDIClock.h @@ -8,7 +8,7 @@ #import #import -#import "MIKMIDICompilerCompatibility.h" +#import /** * Returns the number of MIDITimeStamps that would occur during a specified time interval. diff --git a/Source/MIKMIDICommand.h b/Source/MIKMIDICommand.h index 4ca0b188..d5164774 100644 --- a/Source/MIKMIDICommand.h +++ b/Source/MIKMIDICommand.h @@ -8,7 +8,7 @@ #import #import -#import "MIKMIDICompilerCompatibility.h" +#import /** * Types of MIDI messages. These values correspond directly to the MIDI command type values diff --git a/Source/MIKMIDICommandScheduler.h b/Source/MIKMIDICommandScheduler.h index 3a5d423a..3f2f69bb 100644 --- a/Source/MIKMIDICommandScheduler.h +++ b/Source/MIKMIDICommandScheduler.h @@ -7,7 +7,7 @@ // #import -#import "MIKMIDICompilerCompatibility.h" +#import @class MIKMIDICommand; diff --git a/Source/MIKMIDICommandThrottler.h b/Source/MIKMIDICommandThrottler.h index bcf45673..d23c723a 100644 --- a/Source/MIKMIDICommandThrottler.h +++ b/Source/MIKMIDICommandThrottler.h @@ -7,7 +7,7 @@ // #import -#import "MIKMIDICompilerCompatibility.h" +#import @class MIKMIDIChannelVoiceCommand; diff --git a/Source/MIKMIDICommand_SubclassMethods.h b/Source/MIKMIDICommand_SubclassMethods.h index 9b33bb87..8101d175 100644 --- a/Source/MIKMIDICommand_SubclassMethods.h +++ b/Source/MIKMIDICommand_SubclassMethods.h @@ -6,8 +6,8 @@ // Copyright (c) 2013 Mixed In Key. All rights reserved. // -#import "MIKMIDICommand.h" -#import "MIKMIDITransmittable.h" +#import +#import /** * These methods can be called and/or overridden by subclasses of MIKMIDICommand, but are not diff --git a/Source/MIKMIDIConnectionManager.h b/Source/MIKMIDIConnectionManager.h index 75157b81..adb6ff4f 100644 --- a/Source/MIKMIDIConnectionManager.h +++ b/Source/MIKMIDIConnectionManager.h @@ -7,8 +7,8 @@ // #import -#import "MIKMIDICompilerCompatibility.h" -#import "MIKMIDISourceEndpoint.h" +#import +#import @class MIKMIDIDevice; @class MIKMIDINoteOnCommand; diff --git a/Source/MIKMIDIControlChangeCommand.h b/Source/MIKMIDIControlChangeCommand.h index 683288ec..9ed22e40 100644 --- a/Source/MIKMIDIControlChangeCommand.h +++ b/Source/MIKMIDIControlChangeCommand.h @@ -6,8 +6,8 @@ // Copyright (c) 2013 Mixed In Key. All rights reserved. // -#import "MIKMIDIChannelVoiceCommand.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/Source/MIKMIDIControlChangeEvent.h b/Source/MIKMIDIControlChangeEvent.h index 30bd79f1..a6d7bddf 100644 --- a/Source/MIKMIDIControlChangeEvent.h +++ b/Source/MIKMIDIControlChangeEvent.h @@ -6,8 +6,8 @@ // Copyright (c) 2015 Mixed In Key. All rights reserved. // -#import "MIKMIDIChannelEvent.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -50,4 +50,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIDestinationEndpoint.h b/Source/MIKMIDIDestinationEndpoint.h index 77993f56..9524f4b9 100644 --- a/Source/MIKMIDIDestinationEndpoint.h +++ b/Source/MIKMIDIDestinationEndpoint.h @@ -6,9 +6,9 @@ // Copyright (c) 2013 Mixed In Key. All rights reserved. // -#import "MIKMIDIEndpoint.h" -#import "MIKMIDICommandScheduler.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -40,4 +40,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIDevice.h b/Source/MIKMIDIDevice.h index 235e58b6..53d27f54 100644 --- a/Source/MIKMIDIDevice.h +++ b/Source/MIKMIDIDevice.h @@ -6,8 +6,8 @@ // Copyright (c) 2013 Mixed In Key. All rights reserved. // -#import "MIKMIDIObject.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import @class MIKMIDIEntity; @class MIKMIDIEndpoint; @@ -131,4 +131,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIDeviceManager.h b/Source/MIKMIDIDeviceManager.h index d1727e21..af5cc534 100644 --- a/Source/MIKMIDIDeviceManager.h +++ b/Source/MIKMIDIDeviceManager.h @@ -7,8 +7,8 @@ // #import -#import "MIKMIDIInputPort.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import @class MIKMIDIDevice; @class MIKMIDISourceEndpoint; diff --git a/Source/MIKMIDIEndpoint.h b/Source/MIKMIDIEndpoint.h index 4faccd49..f907f436 100644 --- a/Source/MIKMIDIEndpoint.h +++ b/Source/MIKMIDIEndpoint.h @@ -6,8 +6,8 @@ // Copyright (c) 2013 Mixed In Key. All rights reserved. // -#import "MIKMIDIObject.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import @class MIKMIDIEntity; @@ -31,4 +31,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIEndpointSynthesizer.h b/Source/MIKMIDIEndpointSynthesizer.h index fef9c20f..acfa3c5e 100644 --- a/Source/MIKMIDIEndpointSynthesizer.h +++ b/Source/MIKMIDIEndpointSynthesizer.h @@ -6,8 +6,8 @@ // Copyright (c) 2014 Mixed In Key. All rights reserved. // -#import "MIKMIDISynthesizer.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import @class MIKMIDIEndpoint; @class MIKMIDISourceEndpoint; diff --git a/Source/MIKMIDIEntity.h b/Source/MIKMIDIEntity.h index 02a99888..cd27dce0 100644 --- a/Source/MIKMIDIEntity.h +++ b/Source/MIKMIDIEntity.h @@ -6,8 +6,8 @@ // Copyright (c) 2013 Mixed In Key. All rights reserved. // -#import "MIKMIDIObject.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import @class MIKMIDIDevice; @class MIKMIDIEndpoint; @@ -71,4 +71,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIErrors.h b/Source/MIKMIDIErrors.h index a6b035d0..334ca02d 100644 --- a/Source/MIKMIDIErrors.h +++ b/Source/MIKMIDIErrors.h @@ -7,7 +7,7 @@ // #import -#import "MIKMIDICompilerCompatibility.h" +#import NS_ASSUME_NONNULL_BEGIN @@ -88,4 +88,4 @@ NSString *MIKMIDIDefaultLocalizedErrorDescriptionForErrorCode(MIKMIDIErrorCode c @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIEvent.h b/Source/MIKMIDIEvent.h index 105737d1..a3fa7aae 100644 --- a/Source/MIKMIDIEvent.h +++ b/Source/MIKMIDIEvent.h @@ -8,7 +8,7 @@ #import #import -#import "MIKMIDICompilerCompatibility.h" +#import /** * Types of MIDI events. These values are used to determine which subclass to @@ -187,7 +187,7 @@ NS_ASSUME_NONNULL_END #pragma mark - MIKMIDICommand+MIKMIDIEventToCommands -#import "MIKMIDICommand.h" +#import @class MIKMIDIClock; @@ -199,4 +199,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIEventIterator.h b/Source/MIKMIDIEventIterator.h index 21f09642..940a3d78 100644 --- a/Source/MIKMIDIEventIterator.h +++ b/Source/MIKMIDIEventIterator.h @@ -8,7 +8,7 @@ #import #import -#import "MIKMIDICompilerCompatibility.h" +#import @class MIKMIDITrack; @class MIKMIDIEvent; @@ -37,4 +37,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIEvent_SubclassMethods.h b/Source/MIKMIDIEvent_SubclassMethods.h index e7887657..01386bea 100644 --- a/Source/MIKMIDIEvent_SubclassMethods.h +++ b/Source/MIKMIDIEvent_SubclassMethods.h @@ -6,8 +6,8 @@ // Copyright (c) 2014 Mixed In Key. All rights reserved. // -#import "MIKMIDIEvent.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -112,4 +112,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIInputPort.h b/Source/MIKMIDIInputPort.h index 00329257..a8435748 100644 --- a/Source/MIKMIDIInputPort.h +++ b/Source/MIKMIDIInputPort.h @@ -6,9 +6,9 @@ // Copyright (c) 2013 Mixed In Key. All rights reserved. // -#import "MIKMIDIPort.h" -#import "MIKMIDISourceEndpoint.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import +#import @class MIKMIDIEndpoint; @class MIKMIDICommand; diff --git a/Source/MIKMIDIMappableResponder.h b/Source/MIKMIDIMappableResponder.h index c8d4d46a..75c1f52c 100644 --- a/Source/MIKMIDIMappableResponder.h +++ b/Source/MIKMIDIMappableResponder.h @@ -7,8 +7,8 @@ // #import -#import "MIKMIDIResponder.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import /** * Bit-mask constants used to specify MIDI responder types for mapping. @@ -146,4 +146,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIMapping.h b/Source/MIKMIDIMapping.h index 68e30bf4..f27892cb 100644 --- a/Source/MIKMIDIMapping.h +++ b/Source/MIKMIDIMapping.h @@ -7,10 +7,10 @@ // #import -#import "MIKMIDICompilerCompatibility.h" +#import -#import "MIKMIDICommand.h" -#import "MIKMIDIResponder.h" +#import +#import @protocol MIKMIDIMappableResponder; @@ -263,4 +263,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIMappingGenerator.h b/Source/MIKMIDIMappingGenerator.h index 27832d22..998d49a4 100644 --- a/Source/MIKMIDIMappingGenerator.h +++ b/Source/MIKMIDIMappingGenerator.h @@ -7,9 +7,9 @@ // #import -#import "MIKMIDICompilerCompatibility.h" +#import -#import "MIKMIDIMapping.h" +#import @class MIKMIDIDevice; @class MIKMIDIMapping; @@ -232,4 +232,4 @@ shouldRemoveExistingMappingItems:(MIKSetOf(MIKMIDIMappingItem *) *)mappingItems @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIMappingItem.h b/Source/MIKMIDIMappingItem.h index fcf8b21f..7c677080 100644 --- a/Source/MIKMIDIMappingItem.h +++ b/Source/MIKMIDIMappingItem.h @@ -7,9 +7,9 @@ // #import -#import "MIKMIDIMappableResponder.h" -#import "MIKMIDICommand.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import +#import @class MIKMIDIMapping; @@ -105,4 +105,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIMappingManager.h b/Source/MIKMIDIMappingManager.h index 3cb14459..935b5e21 100644 --- a/Source/MIKMIDIMappingManager.h +++ b/Source/MIKMIDIMappingManager.h @@ -7,7 +7,7 @@ // #import -#import "MIKMIDICompilerCompatibility.h" +#import #define kMIKMIDIMappingFileExtension @"midimap" diff --git a/Source/MIKMIDIMappingXMLParser.h b/Source/MIKMIDIMappingXMLParser.h index 29915dd5..1dece34b 100644 --- a/Source/MIKMIDIMappingXMLParser.h +++ b/Source/MIKMIDIMappingXMLParser.h @@ -7,7 +7,7 @@ // #import -#import "MIKMIDICompilerCompatibility.h" +#import @class MIKMIDIMapping; @@ -26,4 +26,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIMetaCopyrightEvent.h b/Source/MIKMIDIMetaCopyrightEvent.h index 66c34096..f6658294 100644 --- a/Source/MIKMIDIMetaCopyrightEvent.h +++ b/Source/MIKMIDIMetaCopyrightEvent.h @@ -6,8 +6,8 @@ // Copyright (c) 2014 Mixed In Key. All rights reserved. // -#import "MIKMIDIMetaTextEvent.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -29,4 +29,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIMetaCuePointEvent.h b/Source/MIKMIDIMetaCuePointEvent.h index a9afaecf..8fcce859 100644 --- a/Source/MIKMIDIMetaCuePointEvent.h +++ b/Source/MIKMIDIMetaCuePointEvent.h @@ -6,8 +6,8 @@ // Copyright (c) 2014 Mixed In Key. All rights reserved. // -#import "MIKMIDIMetaTextEvent.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -29,4 +29,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIMetaEvent.h b/Source/MIKMIDIMetaEvent.h index 6213e681..67001252 100644 --- a/Source/MIKMIDIMetaEvent.h +++ b/Source/MIKMIDIMetaEvent.h @@ -6,8 +6,8 @@ // Copyright (c) 2014 Mixed In Key. All rights reserved. // -#import "MIKMIDIEvent.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import static const NSUInteger MIKMIDIEventMetadataStartOffset = 8; @@ -110,4 +110,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIMetaEvent_SubclassMethods.h b/Source/MIKMIDIMetaEvent_SubclassMethods.h index cfe2dd78..ed3f1cf2 100644 --- a/Source/MIKMIDIMetaEvent_SubclassMethods.h +++ b/Source/MIKMIDIMetaEvent_SubclassMethods.h @@ -6,8 +6,8 @@ // Copyright © 2015 Mixed In Key. All rights reserved. // -#import "MIKMIDIMetaEvent.h" -#import "MIKMIDIEvent_SubclassMethods.h" +#import +#import @interface MIKMIDIMetaEvent () diff --git a/Source/MIKMIDIMetaInstrumentNameEvent.h b/Source/MIKMIDIMetaInstrumentNameEvent.h index 0d4db2ec..16313969 100644 --- a/Source/MIKMIDIMetaInstrumentNameEvent.h +++ b/Source/MIKMIDIMetaInstrumentNameEvent.h @@ -6,8 +6,8 @@ // Copyright (c) 2014 Mixed In Key. All rights reserved. // -#import "MIKMIDIMetaTextEvent.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -30,4 +30,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIMetaKeySignatureEvent.h b/Source/MIKMIDIMetaKeySignatureEvent.h index 4950c3f6..7050084f 100644 --- a/Source/MIKMIDIMetaKeySignatureEvent.h +++ b/Source/MIKMIDIMetaKeySignatureEvent.h @@ -6,8 +6,8 @@ // Copyright (c) 2014 Mixed In Key. All rights reserved. // -#import "MIKMIDIMetaEvent.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import typedef NS_ENUM(int8_t, MIKMIDIMusicalKey) { MIKMIDIMusicalKeyCFlatMajor = -7, @@ -119,4 +119,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIMetaLyricEvent.h b/Source/MIKMIDIMetaLyricEvent.h index 29f557be..da9fabcb 100644 --- a/Source/MIKMIDIMetaLyricEvent.h +++ b/Source/MIKMIDIMetaLyricEvent.h @@ -6,8 +6,8 @@ // Copyright (c) 2014 Mixed In Key. All rights reserved. // -#import "MIKMIDIMetaTextEvent.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -29,4 +29,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIMetaMarkerTextEvent.h b/Source/MIKMIDIMetaMarkerTextEvent.h index bc1e49ad..1e758494 100644 --- a/Source/MIKMIDIMetaMarkerTextEvent.h +++ b/Source/MIKMIDIMetaMarkerTextEvent.h @@ -6,8 +6,8 @@ // Copyright (c) 2014 Mixed In Key. All rights reserved. // -#import "MIKMIDIMetaTextEvent.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -29,4 +29,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIMetaSequenceEvent.h b/Source/MIKMIDIMetaSequenceEvent.h index ee446d20..542d2280 100644 --- a/Source/MIKMIDIMetaSequenceEvent.h +++ b/Source/MIKMIDIMetaSequenceEvent.h @@ -6,8 +6,8 @@ // Copyright (c) 2014 Mixed In Key. All rights reserved. // -#import "MIKMIDIMetaEvent.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -29,4 +29,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIMetaTextEvent.h b/Source/MIKMIDIMetaTextEvent.h index e603504e..60c46f81 100644 --- a/Source/MIKMIDIMetaTextEvent.h +++ b/Source/MIKMIDIMetaTextEvent.h @@ -6,8 +6,8 @@ // Copyright (c) 2014 Mixed In Key. All rights reserved. // -#import "MIKMIDIMetaEvent.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -37,4 +37,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIMetaTimeSignatureEvent.h b/Source/MIKMIDIMetaTimeSignatureEvent.h index f3f9682e..771632b9 100644 --- a/Source/MIKMIDIMetaTimeSignatureEvent.h +++ b/Source/MIKMIDIMetaTimeSignatureEvent.h @@ -6,8 +6,8 @@ // Copyright (c) 2014 Mixed In Key. All rights reserved. // -#import "MIKMIDIMetaEvent.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import /** * Represents a time signature. Note that in contrast to time signature events in raw MIDI, @@ -105,4 +105,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIMetaTrackSequenceNameEvent.h b/Source/MIKMIDIMetaTrackSequenceNameEvent.h index 5437b28d..abb89a20 100644 --- a/Source/MIKMIDIMetaTrackSequenceNameEvent.h +++ b/Source/MIKMIDIMetaTrackSequenceNameEvent.h @@ -6,8 +6,8 @@ // Copyright (c) 2014 Mixed In Key. All rights reserved. // -#import "MIKMIDIMetaTextEvent.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -36,4 +36,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIMetronome.h b/Source/MIKMIDIMetronome.h index 05117f71..247767a1 100644 --- a/Source/MIKMIDIMetronome.h +++ b/Source/MIKMIDIMetronome.h @@ -6,8 +6,8 @@ // Copyright (c) 2014 Mixed In Key. All rights reserved. // -#import "MIKMIDIEndpointSynthesizer.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/Source/MIKMIDINoteCommand.h b/Source/MIKMIDINoteCommand.h index 6ea4d2c7..1b20a775 100644 --- a/Source/MIKMIDINoteCommand.h +++ b/Source/MIKMIDINoteCommand.h @@ -6,8 +6,8 @@ // Copyright © 2017 Mixed In Key. All rights reserved. // -#import "MIKMIDIChannelVoiceCommand.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/Source/MIKMIDINoteCommand_SubclassMethods.h b/Source/MIKMIDINoteCommand_SubclassMethods.h index 78c71052..3f1d2e36 100644 --- a/Source/MIKMIDINoteCommand_SubclassMethods.h +++ b/Source/MIKMIDINoteCommand_SubclassMethods.h @@ -6,9 +6,7 @@ // Copyright (c) 2013 Mixed In Key. All rights reserved. // -#import "MIKMIDINoteCommand.h" -#import "MIKMIDIChannelVoiceCommand_SubclassMethods.h" -#import "MIKMIDICompilerCompatibility.h" +#import NS_ASSUME_NONNULL_BEGIN diff --git a/Source/MIKMIDINoteEvent.h b/Source/MIKMIDINoteEvent.h index 35531473..f77d8638 100644 --- a/Source/MIKMIDINoteEvent.h +++ b/Source/MIKMIDINoteEvent.h @@ -6,8 +6,8 @@ // Copyright (c) 2014 Mixed In Key. All rights reserved. // -#import "MIKMIDIEvent.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import @class MIKMIDIClock; @@ -115,8 +115,8 @@ NS_ASSUME_NONNULL_END #pragma mark - -#import "MIKMIDINoteOnCommand.h" -#import "MIKMIDINoteOffCommand.h" +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -134,4 +134,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDINoteOffCommand.h b/Source/MIKMIDINoteOffCommand.h index ffba1715..051c41dc 100644 --- a/Source/MIKMIDINoteOffCommand.h +++ b/Source/MIKMIDINoteOffCommand.h @@ -6,7 +6,7 @@ // Copyright (c) 2013 Mixed In Key. All rights reserved. // -#import "MIKMIDINoteCommand.h" +#import NS_ASSUME_NONNULL_BEGIN diff --git a/Source/MIKMIDINoteOffCommand.m b/Source/MIKMIDINoteOffCommand.m index ccc5b3e3..be812a58 100644 --- a/Source/MIKMIDINoteOffCommand.m +++ b/Source/MIKMIDINoteOffCommand.m @@ -9,6 +9,7 @@ #import "MIKMIDINoteOffCommand.h" #import "MIKMIDINoteCommand_SubclassMethods.h" #import "MIKMIDINoteOnCommand.h" +#import "MIKMIDICommand_SubclassMethods.h" #import "MIKMIDIUtilities.h" #if !__has_feature(objc_arc) diff --git a/Source/MIKMIDINoteOnCommand.h b/Source/MIKMIDINoteOnCommand.h index 9e3e08be..c1f0aba6 100644 --- a/Source/MIKMIDINoteOnCommand.h +++ b/Source/MIKMIDINoteOnCommand.h @@ -6,7 +6,7 @@ // Copyright (c) 2013 Mixed In Key. All rights reserved. // -#import "MIKMIDINoteCommand.h" +#import NS_ASSUME_NONNULL_BEGIN diff --git a/Source/MIKMIDINoteOnCommand.m b/Source/MIKMIDINoteOnCommand.m index 539e845c..69b4dd82 100644 --- a/Source/MIKMIDINoteOnCommand.m +++ b/Source/MIKMIDINoteOnCommand.m @@ -8,6 +8,7 @@ #import "MIKMIDINoteOnCommand.h" #import "MIKMIDINoteCommand_SubclassMethods.h" +#import "MIKMIDICommand_SubclassMethods.h" #import "MIKMIDIUtilities.h" #if !__has_feature(objc_arc) diff --git a/Source/MIKMIDIObject.h b/Source/MIKMIDIObject.h index d7026d35..ddcdf909 100644 --- a/Source/MIKMIDIObject.h +++ b/Source/MIKMIDIObject.h @@ -8,7 +8,7 @@ #import #import -#import "MIKMIDICompilerCompatibility.h" +#import NS_ASSUME_NONNULL_BEGIN @@ -101,4 +101,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIObject_SubclassMethods.h b/Source/MIKMIDIObject_SubclassMethods.h index d4b1d95a..88237ff7 100644 --- a/Source/MIKMIDIObject_SubclassMethods.h +++ b/Source/MIKMIDIObject_SubclassMethods.h @@ -6,8 +6,8 @@ // Copyright (c) 2013 Mixed In Key. All rights reserved. // -#import "MIKMIDIObject.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -59,4 +59,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIOutputPort.h b/Source/MIKMIDIOutputPort.h index 9a6e7aae..f4503b49 100644 --- a/Source/MIKMIDIOutputPort.h +++ b/Source/MIKMIDIOutputPort.h @@ -6,8 +6,8 @@ // Copyright (c) 2013 Mixed In Key. All rights reserved. // -#import "MIKMIDIPort.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import @class MIKMIDICommand; @class MIKMIDIDestinationEndpoint; diff --git a/Source/MIKMIDIPitchBendChangeCommand.h b/Source/MIKMIDIPitchBendChangeCommand.h index fbb442bf..b99dc0b6 100644 --- a/Source/MIKMIDIPitchBendChangeCommand.h +++ b/Source/MIKMIDIPitchBendChangeCommand.h @@ -6,8 +6,8 @@ // Copyright (c) 2015 Mixed In Key. All rights reserved. // -#import "MIKMIDIChannelVoiceCommand.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -44,4 +44,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIPitchBendChangeEvent.h b/Source/MIKMIDIPitchBendChangeEvent.h index 2c84e07f..01411cdf 100644 --- a/Source/MIKMIDIPitchBendChangeEvent.h +++ b/Source/MIKMIDIPitchBendChangeEvent.h @@ -6,8 +6,8 @@ // Copyright (c) 2015 Mixed In Key. All rights reserved. // -#import "MIKMIDIChannelEvent.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -43,4 +43,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIPlayer.h b/Source/MIKMIDIPlayer.h index 79b08225..2b0915c1 100644 --- a/Source/MIKMIDIPlayer.h +++ b/Source/MIKMIDIPlayer.h @@ -8,7 +8,7 @@ #import #import -#import "MIKMIDICompilerCompatibility.h" +#import @class MIKMIDISequence; @class MIKMIDIMetronome; diff --git a/Source/MIKMIDIPolyphonicKeyPressureCommand.h b/Source/MIKMIDIPolyphonicKeyPressureCommand.h index 57250a03..b5fa74df 100644 --- a/Source/MIKMIDIPolyphonicKeyPressureCommand.h +++ b/Source/MIKMIDIPolyphonicKeyPressureCommand.h @@ -6,7 +6,7 @@ // Copyright © 2015 Mixed In Key. All rights reserved. // -#import "MIKMIDIChannelVoiceCommand.h" +#import /** * A MIDI polyphonic key pressure message. This message is most often sent by pressing diff --git a/Source/MIKMIDIPolyphonicKeyPressureEvent.h b/Source/MIKMIDIPolyphonicKeyPressureEvent.h index 132317da..ecb83cd7 100644 --- a/Source/MIKMIDIPolyphonicKeyPressureEvent.h +++ b/Source/MIKMIDIPolyphonicKeyPressureEvent.h @@ -6,8 +6,8 @@ // Copyright (c) 2015 Mixed In Key. All rights reserved. // -#import "MIKMIDIChannelEvent.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -46,4 +46,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIPort.h b/Source/MIKMIDIPort.h index e1cc52bb..c44cadcc 100644 --- a/Source/MIKMIDIPort.h +++ b/Source/MIKMIDIPort.h @@ -6,9 +6,9 @@ // Copyright (c) 2013 Mixed In Key. All rights reserved. // -#import "MIKMIDIObject.h" +#import #import -#import "MIKMIDICompilerCompatibility.h" +#import @class MIKMIDIEndpoint; @@ -26,4 +26,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIPrivateUtilities.h b/Source/MIKMIDIPrivateUtilities.h index df1bf0ac..c77f8475 100644 --- a/Source/MIKMIDIPrivateUtilities.h +++ b/Source/MIKMIDIPrivateUtilities.h @@ -7,7 +7,7 @@ // #import -#import "MIKMIDICompilerCompatibility.h" +#import @class MIKMIDIChannelVoiceCommand; diff --git a/Source/MIKMIDIProgramChangeCommand.h b/Source/MIKMIDIProgramChangeCommand.h index 032422cc..3d473bd9 100644 --- a/Source/MIKMIDIProgramChangeCommand.h +++ b/Source/MIKMIDIProgramChangeCommand.h @@ -6,8 +6,8 @@ // Copyright (c) 2015 Mixed In Key. All rights reserved. // -#import "MIKMIDIChannelVoiceCommand.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -39,4 +39,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIProgramChangeEvent.h b/Source/MIKMIDIProgramChangeEvent.h index 10b5c35a..857318f7 100644 --- a/Source/MIKMIDIProgramChangeEvent.h +++ b/Source/MIKMIDIProgramChangeEvent.h @@ -6,8 +6,8 @@ // Copyright (c) 2015 Mixed In Key. All rights reserved. // -#import "MIKMIDIChannelEvent.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -50,4 +50,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIResponder.h b/Source/MIKMIDIResponder.h index 7ae52e0b..937595dc 100644 --- a/Source/MIKMIDIResponder.h +++ b/Source/MIKMIDIResponder.h @@ -7,7 +7,7 @@ // #import -#import "MIKMIDICompilerCompatibility.h" +#import @class MIKMIDICommand; @@ -75,4 +75,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDISequence+MIKMIDIPrivate.h b/Source/MIKMIDISequence+MIKMIDIPrivate.h index 290d26d7..00bec8e6 100644 --- a/Source/MIKMIDISequence+MIKMIDIPrivate.h +++ b/Source/MIKMIDISequence+MIKMIDIPrivate.h @@ -6,8 +6,8 @@ // Copyright (c) 2015 Mixed In Key. All rights reserved. // -#import "MIKMIDISequence.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import @class MIKMIDISequencer; diff --git a/Source/MIKMIDISequence.h b/Source/MIKMIDISequence.h index 2c442ff5..bb011703 100644 --- a/Source/MIKMIDISequence.h +++ b/Source/MIKMIDISequence.h @@ -8,8 +8,8 @@ #import #import -#import "MIKMIDICompilerCompatibility.h" -#import "MIKMIDIMetaTimeSignatureEvent.h" +#import +#import @class MIKMIDITrack; @class MIKMIDISequencer; diff --git a/Source/MIKMIDISequencer+MIKMIDIPrivate.h b/Source/MIKMIDISequencer+MIKMIDIPrivate.h index 9e87687c..b215fe6e 100644 --- a/Source/MIKMIDISequencer+MIKMIDIPrivate.h +++ b/Source/MIKMIDISequencer+MIKMIDIPrivate.h @@ -6,8 +6,8 @@ // Copyright (c) 2015 Mixed In Key. All rights reserved. // -#import "MIKMIDISequencer.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/Source/MIKMIDISequencer.h b/Source/MIKMIDISequencer.h index 790212d3..882177a8 100644 --- a/Source/MIKMIDISequencer.h +++ b/Source/MIKMIDISequencer.h @@ -8,7 +8,7 @@ #import #import -#import "MIKMIDICompilerCompatibility.h" +#import @class MIKMIDISequence; @class MIKMIDITrack; @@ -475,4 +475,4 @@ FOUNDATION_EXPORT NSString * const MIKMIDISequencerWillLoopNotification; */ FOUNDATION_EXPORT const MusicTimeStamp MIKMIDISequencerEndOfSequenceLoopEndTimeStamp; -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDISourceEndpoint.h b/Source/MIKMIDISourceEndpoint.h index 6a0027e5..126330ac 100644 --- a/Source/MIKMIDISourceEndpoint.h +++ b/Source/MIKMIDISourceEndpoint.h @@ -6,8 +6,8 @@ // Copyright (c) 2013 Mixed In Key. All rights reserved. // -#import "MIKMIDIEndpoint.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import @class MIKMIDISourceEndpoint; @class MIKMIDICommand; @@ -41,4 +41,4 @@ typedef void(^MIKMIDIEventHandlerBlock)(MIKMIDISourceEndpoint *source, MIKArrayO @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDISynthesizer.h b/Source/MIKMIDISynthesizer.h index 332a21a6..e96c9f50 100644 --- a/Source/MIKMIDISynthesizer.h +++ b/Source/MIKMIDISynthesizer.h @@ -8,9 +8,9 @@ #import #import -#import "MIKMIDISynthesizerInstrument.h" -#import "MIKMIDICommandScheduler.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/Source/MIKMIDISynthesizerInstrument.h b/Source/MIKMIDISynthesizerInstrument.h index 97fb1c53..70a0f9ee 100644 --- a/Source/MIKMIDISynthesizerInstrument.h +++ b/Source/MIKMIDISynthesizerInstrument.h @@ -8,7 +8,7 @@ #import #import -#import "MIKMIDICompilerCompatibility.h" +#import NS_ASSUME_NONNULL_BEGIN @@ -67,4 +67,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDISynthesizer_SubclassMethods.h b/Source/MIKMIDISynthesizer_SubclassMethods.h index 763e2dd7..11b7340b 100644 --- a/Source/MIKMIDISynthesizer_SubclassMethods.h +++ b/Source/MIKMIDISynthesizer_SubclassMethods.h @@ -6,8 +6,8 @@ // Copyright (c) 2015 Mixed In Key. All rights reserved. // -#import "MIKMIDISynthesizer.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/Source/MIKMIDISystemExclusiveCommand.h b/Source/MIKMIDISystemExclusiveCommand.h index d3cff44e..cf4ff56c 100644 --- a/Source/MIKMIDISystemExclusiveCommand.h +++ b/Source/MIKMIDISystemExclusiveCommand.h @@ -6,8 +6,8 @@ // Copyright (c) 2013 Mixed In Key. All rights reserved. // -#import "MIKMIDISystemMessageCommand.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import extern uint32_t const kMIKMIDISysexNonRealtimeManufacturerID; extern uint32_t const kMIKMIDISysexRealtimeManufacturerID; diff --git a/Source/MIKMIDISystemKeepAliveCommand.h b/Source/MIKMIDISystemKeepAliveCommand.h index f1843ef1..e15efee8 100644 --- a/Source/MIKMIDISystemKeepAliveCommand.h +++ b/Source/MIKMIDISystemKeepAliveCommand.h @@ -6,7 +6,7 @@ // Copyright © 2017 Mixed In Key. All rights reserved. // -#import "MIKMIDISystemMessageCommand.h" +#import NS_ASSUME_NONNULL_BEGIN diff --git a/Source/MIKMIDISystemMessageCommand.h b/Source/MIKMIDISystemMessageCommand.h index 7a537b84..bc7e864a 100644 --- a/Source/MIKMIDISystemMessageCommand.h +++ b/Source/MIKMIDISystemMessageCommand.h @@ -6,8 +6,8 @@ // Copyright (c) 2013 Mixed In Key. All rights reserved. // -#import "MIKMIDICommand.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -34,4 +34,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDITempoEvent.h b/Source/MIKMIDITempoEvent.h index dd8fbb31..995d6b76 100644 --- a/Source/MIKMIDITempoEvent.h +++ b/Source/MIKMIDITempoEvent.h @@ -6,8 +6,8 @@ // Copyright (c) 2014 Mixed In Key. All rights reserved. // -#import "MIKMIDIEvent.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -45,4 +45,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDITrack.h b/Source/MIKMIDITrack.h index fc1de1a7..981f9362 100644 --- a/Source/MIKMIDITrack.h +++ b/Source/MIKMIDITrack.h @@ -8,7 +8,7 @@ #import #import -#import "MIKMIDICompilerCompatibility.h" +#import @class MIKMIDISequence; @class MIKMIDIEvent; diff --git a/Source/MIKMIDITrack_Protected.h b/Source/MIKMIDITrack_Protected.h index 6bbeff4a..ba3bd3a5 100644 --- a/Source/MIKMIDITrack_Protected.h +++ b/Source/MIKMIDITrack_Protected.h @@ -6,8 +6,8 @@ // Copyright (c) 2015 Mixed In Key. All rights reserved. // -#import "MIKMIDITrack.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -43,4 +43,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIUtilities.h b/Source/MIKMIDIUtilities.h index 96256f8e..38daf62f 100644 --- a/Source/MIKMIDIUtilities.h +++ b/Source/MIKMIDIUtilities.h @@ -8,9 +8,9 @@ #import #import -#import "MIKMIDIMappableResponder.h" -#import "MIKMIDICommand.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import +#import #include NS_ASSUME_NONNULL_BEGIN diff --git a/Source/NSUIApplication+MIKMIDI.h b/Source/NSUIApplication+MIKMIDI.h index 77186491..62d94313 100644 --- a/Source/NSUIApplication+MIKMIDI.h +++ b/Source/NSUIApplication+MIKMIDI.h @@ -24,7 +24,7 @@ #endif -#import "MIKMIDICompilerCompatibility.h" +#import /** * Define MIKMIDI_SEARCH_VIEW_HIERARCHY_FOR_RESPONDERS as a non-zero value to (re)enable searching @@ -140,4 +140,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END From 48082bc1a23adcbed78c7865bae004b29def0d14 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 13 Aug 2020 22:41:42 -0600 Subject: [PATCH 25/53] Use NSEC_PER_SEC instead of hard coded nanoseconds --- Source/MIKMIDIClock.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/MIKMIDIClock.m b/Source/MIKMIDIClock.m index 1ce9ffdd..6aac3bf4 100644 --- a/Source/MIKMIDIClock.m +++ b/Source/MIKMIDIClock.m @@ -367,7 +367,7 @@ Float64 MIKMIDIClockSecondsPerMIDITimeStamp() dispatch_once(&onceToken, ^{ mach_timebase_info_data_t timeBaseInfoData; mach_timebase_info(&timeBaseInfoData); - secondsPerMIDITimeStamp = ((Float64)timeBaseInfoData.numer / (Float64)timeBaseInfoData.denom) / 1.0e9; + secondsPerMIDITimeStamp = ((Float64)timeBaseInfoData.numer / (Float64)timeBaseInfoData.denom) / NSEC_PER_SEC; }); return secondsPerMIDITimeStamp; From 9ffaef06f8fe618c58ff52561b45a2585246f54b Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Mon, 17 Aug 2020 16:38:42 -0600 Subject: [PATCH 26/53] Move to angle bracket includes in public framework headers. Fixes Issue #293. --- Source/MIKMIDI.h | 128 +++++++++--------- Source/MIKMIDIChannelEvent.h | 8 +- Source/MIKMIDIChannelPressureCommand.h | 4 +- Source/MIKMIDIChannelPressureEvent.h | 6 +- Source/MIKMIDIChannelVoiceCommand.h | 6 +- ...KMIDIChannelVoiceCommand_SubclassMethods.h | 8 +- Source/MIKMIDIClientDestinationEndpoint.h | 6 +- Source/MIKMIDIClientSourceEndpoint.h | 4 +- Source/MIKMIDIClock.h | 2 +- Source/MIKMIDICommand.h | 2 +- Source/MIKMIDICommandScheduler.h | 2 +- Source/MIKMIDICommandThrottler.h | 2 +- Source/MIKMIDICommand_SubclassMethods.h | 4 +- Source/MIKMIDIConnectionManager.h | 4 +- Source/MIKMIDIControlChangeCommand.h | 4 +- Source/MIKMIDIControlChangeEvent.h | 6 +- Source/MIKMIDIDestinationEndpoint.h | 8 +- Source/MIKMIDIDevice.h | 6 +- Source/MIKMIDIDeviceManager.h | 4 +- Source/MIKMIDIEndpoint.h | 6 +- Source/MIKMIDIEndpointSynthesizer.h | 4 +- Source/MIKMIDIEntity.h | 6 +- Source/MIKMIDIErrors.h | 4 +- Source/MIKMIDIEvent.h | 6 +- Source/MIKMIDIEventIterator.h | 4 +- Source/MIKMIDIEvent_SubclassMethods.h | 6 +- Source/MIKMIDIInputPort.h | 6 +- Source/MIKMIDIMappableResponder.h | 6 +- Source/MIKMIDIMapping.h | 6 +- Source/MIKMIDIMappingGenerator.h | 4 +- Source/MIKMIDIMappingItem.h | 8 +- Source/MIKMIDIMappingManager.h | 2 +- Source/MIKMIDIMappingXMLParser.h | 4 +- Source/MIKMIDIMetaCopyrightEvent.h | 6 +- Source/MIKMIDIMetaCuePointEvent.h | 6 +- Source/MIKMIDIMetaEvent.h | 6 +- Source/MIKMIDIMetaEvent_SubclassMethods.h | 4 +- Source/MIKMIDIMetaInstrumentNameEvent.h | 6 +- Source/MIKMIDIMetaKeySignatureEvent.h | 6 +- Source/MIKMIDIMetaLyricEvent.h | 6 +- Source/MIKMIDIMetaMarkerTextEvent.h | 6 +- Source/MIKMIDIMetaSequenceEvent.h | 6 +- Source/MIKMIDIMetaTextEvent.h | 6 +- Source/MIKMIDIMetaTimeSignatureEvent.h | 6 +- Source/MIKMIDIMetaTrackSequenceNameEvent.h | 6 +- Source/MIKMIDIMetronome.h | 4 +- Source/MIKMIDINoteCommand.h | 4 +- Source/MIKMIDINoteEvent.h | 10 +- Source/MIKMIDINoteOffCommand.h | 2 +- Source/MIKMIDINoteOnCommand.h | 2 +- Source/MIKMIDIObject.h | 4 +- Source/MIKMIDIObject_SubclassMethods.h | 6 +- Source/MIKMIDIOutputPort.h | 4 +- Source/MIKMIDIPitchBendChangeCommand.h | 6 +- Source/MIKMIDIPitchBendChangeEvent.h | 6 +- Source/MIKMIDIPlayer.h | 2 +- Source/MIKMIDIPolyphonicKeyPressureCommand.h | 2 +- Source/MIKMIDIPolyphonicKeyPressureEvent.h | 6 +- Source/MIKMIDIPort.h | 6 +- Source/MIKMIDIPrivateUtilities.h | 2 +- Source/MIKMIDIProgramChangeCommand.h | 6 +- Source/MIKMIDIProgramChangeEvent.h | 6 +- Source/MIKMIDIResponder.h | 4 +- Source/MIKMIDISequence+MIKMIDIPrivate.h | 4 +- Source/MIKMIDISequence.h | 4 +- Source/MIKMIDISequencer+MIKMIDIPrivate.h | 4 +- Source/MIKMIDISequencer.h | 2 +- Source/MIKMIDISourceEndpoint.h | 6 +- Source/MIKMIDISynthesizer.h | 6 +- Source/MIKMIDISynthesizerInstrument.h | 4 +- Source/MIKMIDISynthesizer_SubclassMethods.h | 4 +- Source/MIKMIDISystemExclusiveCommand.h | 4 +- Source/MIKMIDISystemKeepAliveCommand.h | 2 +- Source/MIKMIDISystemMessageCommand.h | 6 +- Source/MIKMIDITempoEvent.h | 6 +- Source/MIKMIDITempoTrack.h | 4 +- Source/MIKMIDITrack.h | 2 +- Source/MIKMIDITrack_Protected.h | 4 +- Source/MIKMIDIUtilities.h | 6 +- Source/NSUIApplication+MIKMIDI.h | 4 +- 80 files changed, 255 insertions(+), 255 deletions(-) diff --git a/Source/MIKMIDI.h b/Source/MIKMIDI.h index 8112e221..1592fe88 100644 --- a/Source/MIKMIDI.h +++ b/Source/MIKMIDI.h @@ -9,92 +9,92 @@ /** Umbrella header for MIKMIDI public interface. */ // Core MIDI object wrapper -#import "MIKMIDIObject.h" +#import // MIDI port -#import "MIKMIDIPort.h" -#import "MIKMIDIInputPort.h" -#import "MIKMIDIOutputPort.h" +#import +#import +#import // MIDI Device support -#import "MIKMIDIDevice.h" -#import "MIKMIDIDeviceManager.h" -#import "MIKMIDIConnectionManager.h" +#import +#import +#import -#import "MIKMIDIEntity.h" +#import // Endpoints -#import "MIKMIDIEndpoint.h" -#import "MIKMIDIDestinationEndpoint.h" -#import "MIKMIDISourceEndpoint.h" -#import "MIKMIDIClientDestinationEndpoint.h" -#import "MIKMIDIClientSourceEndpoint.h" +#import +#import +#import +#import +#import // MIDI Commands/Messages -#import "MIKMIDICommand.h" -#import "MIKMIDIChannelVoiceCommand.h" -#import "MIKMIDINoteCommand.h" -#import "MIKMIDIChannelPressureCommand.h" -#import "MIKMIDIControlChangeCommand.h" -#import "MIKMIDIProgramChangeCommand.h" -#import "MIKMIDIPitchBendChangeCommand.h" -#import "MIKMIDINoteOnCommand.h" -#import "MIKMIDINoteOffCommand.h" -#import "MIKMIDIPolyphonicKeyPressureCommand.h" -#import "MIKMIDISystemExclusiveCommand.h" -#import "MIKMIDISystemMessageCommand.h" -#import "MIKMIDISystemKeepAliveCommand.h" +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import // MIDI Sequence/File support -#import "MIKMIDISequence.h" -#import "MIKMIDITrack.h" +#import +#import // MIDI Events -#import "MIKMIDIEvent.h" -#import "MIKMIDITempoEvent.h" -#import "MIKMIDINoteEvent.h" +#import +#import +#import // Channel Events -#import "MIKMIDIChannelEvent.h" -#import "MIKMIDIPolyphonicKeyPressureEvent.h" -#import "MIKMIDIControlChangeEvent.h" -#import "MIKMIDIProgramChangeEvent.h" -#import "MIKMIDIChannelPressureEvent.h" -#import "MIKMIDIPitchBendChangeEvent.h" +#import +#import +#import +#import +#import +#import // Meta Events -#import "MIKMIDIMetaEvent.h" -#import "MIKMIDIMetaCopyrightEvent.h" -#import "MIKMIDIMetaCuePointEvent.h" -#import "MIKMIDIMetaInstrumentNameEvent.h" -#import "MIKMIDIMetaKeySignatureEvent.h" -#import "MIKMIDIMetaLyricEvent.h" -#import "MIKMIDIMetaMarkerTextEvent.h" -#import "MIKMIDIMetaSequenceEvent.h" -#import "MIKMIDIMetaTextEvent.h" -#import "MIKMIDIMetaTimeSignatureEvent.h" -#import "MIKMIDIMetaTrackSequenceNameEvent.h" +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import // Sequencing and Synthesis -#import "MIKMIDISequencer.h" -#import "MIKMIDIMetronome.h" -#import "MIKMIDIClock.h" -#import "MIKMIDIPlayer.h" -#import "MIKMIDIEndpointSynthesizer.h" +#import +#import +#import +#import +#import // MIDI Mapping -#import "MIKMIDIMapping.h" -#import "MIKMIDIMappingItem.h" -#import "MIKMIDIMappableResponder.h" -#import "MIKMIDIMappingManager.h" -#import "MIKMIDIMappingGenerator.h" +#import +#import +#import +#import +#import // Intra-application MIDI command routing -#import "NSUIApplication+MIKMIDI.h" -#import "MIKMIDIResponder.h" -#import "MIKMIDICommandThrottler.h" +#import +#import +#import // Utilities -#import "MIKMIDIUtilities.h" -#import "MIKMIDIErrors.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import +#import diff --git a/Source/MIKMIDIChannelEvent.h b/Source/MIKMIDIChannelEvent.h index 1abe326e..1d92b4cf 100644 --- a/Source/MIKMIDIChannelEvent.h +++ b/Source/MIKMIDIChannelEvent.h @@ -6,8 +6,8 @@ // Copyright (c) 2015 Mixed In Key. All rights reserved. // -#import "MIKMIDIEvent.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -59,7 +59,7 @@ NS_ASSUME_NONNULL_END #pragma mark - -#import "MIKMIDICommand.h" +#import @class MIKMIDIClock; @@ -71,4 +71,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIChannelPressureCommand.h b/Source/MIKMIDIChannelPressureCommand.h index 8cdbacff..af545c18 100644 --- a/Source/MIKMIDIChannelPressureCommand.h +++ b/Source/MIKMIDIChannelPressureCommand.h @@ -6,8 +6,8 @@ // Copyright © 2015 Mixed In Key. All rights reserved. // -#import "MIKMIDIChannelVoiceCommand.h" -#import "MIKMIDIChannelPressureCommand.h" +#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/Source/MIKMIDIChannelPressureEvent.h b/Source/MIKMIDIChannelPressureEvent.h index 27ba7afd..b83142b1 100644 --- a/Source/MIKMIDIChannelPressureEvent.h +++ b/Source/MIKMIDIChannelPressureEvent.h @@ -6,8 +6,8 @@ // Copyright (c) 2015 Mixed In Key. All rights reserved. // -#import "MIKMIDIChannelEvent.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -42,4 +42,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIChannelVoiceCommand.h b/Source/MIKMIDIChannelVoiceCommand.h index 93e606d6..751e8dde 100644 --- a/Source/MIKMIDIChannelVoiceCommand.h +++ b/Source/MIKMIDIChannelVoiceCommand.h @@ -6,8 +6,8 @@ // Copyright (c) 2013 Mixed In Key. All rights reserved. // -#import "MIKMIDICommand.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -53,4 +53,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIChannelVoiceCommand_SubclassMethods.h b/Source/MIKMIDIChannelVoiceCommand_SubclassMethods.h index 7806d69f..3cc814c7 100644 --- a/Source/MIKMIDIChannelVoiceCommand_SubclassMethods.h +++ b/Source/MIKMIDIChannelVoiceCommand_SubclassMethods.h @@ -6,9 +6,9 @@ // Copyright (c) 2013 Mixed In Key. All rights reserved. // -#import "MIKMIDIChannelVoiceCommand.h" -#import "MIKMIDICommand_SubclassMethods.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -18,4 +18,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIClientDestinationEndpoint.h b/Source/MIKMIDIClientDestinationEndpoint.h index 67d61ebb..bb7afd94 100644 --- a/Source/MIKMIDIClientDestinationEndpoint.h +++ b/Source/MIKMIDIClientDestinationEndpoint.h @@ -6,8 +6,8 @@ // // -#import "MIKMIDIDestinationEndpoint.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import @class MIKMIDIClientDestinationEndpoint; @class MIKMIDICommand; @@ -50,4 +50,4 @@ typedef void(^MIKMIDIClientDestinationEndpointEventHandler)(MIKMIDIClientDestina @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIClientSourceEndpoint.h b/Source/MIKMIDIClientSourceEndpoint.h index 3e52befc..795c0fac 100644 --- a/Source/MIKMIDIClientSourceEndpoint.h +++ b/Source/MIKMIDIClientSourceEndpoint.h @@ -5,8 +5,8 @@ // Created by Dan Rosenstark on 2015-01-07 // -#import "MIKMIDISourceEndpoint.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import @class MIKMIDICommand; diff --git a/Source/MIKMIDIClock.h b/Source/MIKMIDIClock.h index 088e65b0..c4a4b265 100644 --- a/Source/MIKMIDIClock.h +++ b/Source/MIKMIDIClock.h @@ -8,7 +8,7 @@ #import #import -#import "MIKMIDICompilerCompatibility.h" +#import /** * Returns the number of MIDITimeStamps that would occur during a specified time interval. diff --git a/Source/MIKMIDICommand.h b/Source/MIKMIDICommand.h index 4ca0b188..d5164774 100644 --- a/Source/MIKMIDICommand.h +++ b/Source/MIKMIDICommand.h @@ -8,7 +8,7 @@ #import #import -#import "MIKMIDICompilerCompatibility.h" +#import /** * Types of MIDI messages. These values correspond directly to the MIDI command type values diff --git a/Source/MIKMIDICommandScheduler.h b/Source/MIKMIDICommandScheduler.h index 3a5d423a..3f2f69bb 100644 --- a/Source/MIKMIDICommandScheduler.h +++ b/Source/MIKMIDICommandScheduler.h @@ -7,7 +7,7 @@ // #import -#import "MIKMIDICompilerCompatibility.h" +#import @class MIKMIDICommand; diff --git a/Source/MIKMIDICommandThrottler.h b/Source/MIKMIDICommandThrottler.h index bcf45673..d23c723a 100644 --- a/Source/MIKMIDICommandThrottler.h +++ b/Source/MIKMIDICommandThrottler.h @@ -7,7 +7,7 @@ // #import -#import "MIKMIDICompilerCompatibility.h" +#import @class MIKMIDIChannelVoiceCommand; diff --git a/Source/MIKMIDICommand_SubclassMethods.h b/Source/MIKMIDICommand_SubclassMethods.h index 9b33bb87..8101d175 100644 --- a/Source/MIKMIDICommand_SubclassMethods.h +++ b/Source/MIKMIDICommand_SubclassMethods.h @@ -6,8 +6,8 @@ // Copyright (c) 2013 Mixed In Key. All rights reserved. // -#import "MIKMIDICommand.h" -#import "MIKMIDITransmittable.h" +#import +#import /** * These methods can be called and/or overridden by subclasses of MIKMIDICommand, but are not diff --git a/Source/MIKMIDIConnectionManager.h b/Source/MIKMIDIConnectionManager.h index 75157b81..adb6ff4f 100644 --- a/Source/MIKMIDIConnectionManager.h +++ b/Source/MIKMIDIConnectionManager.h @@ -7,8 +7,8 @@ // #import -#import "MIKMIDICompilerCompatibility.h" -#import "MIKMIDISourceEndpoint.h" +#import +#import @class MIKMIDIDevice; @class MIKMIDINoteOnCommand; diff --git a/Source/MIKMIDIControlChangeCommand.h b/Source/MIKMIDIControlChangeCommand.h index 683288ec..9ed22e40 100644 --- a/Source/MIKMIDIControlChangeCommand.h +++ b/Source/MIKMIDIControlChangeCommand.h @@ -6,8 +6,8 @@ // Copyright (c) 2013 Mixed In Key. All rights reserved. // -#import "MIKMIDIChannelVoiceCommand.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/Source/MIKMIDIControlChangeEvent.h b/Source/MIKMIDIControlChangeEvent.h index 30bd79f1..a6d7bddf 100644 --- a/Source/MIKMIDIControlChangeEvent.h +++ b/Source/MIKMIDIControlChangeEvent.h @@ -6,8 +6,8 @@ // Copyright (c) 2015 Mixed In Key. All rights reserved. // -#import "MIKMIDIChannelEvent.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -50,4 +50,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIDestinationEndpoint.h b/Source/MIKMIDIDestinationEndpoint.h index 77993f56..9524f4b9 100644 --- a/Source/MIKMIDIDestinationEndpoint.h +++ b/Source/MIKMIDIDestinationEndpoint.h @@ -6,9 +6,9 @@ // Copyright (c) 2013 Mixed In Key. All rights reserved. // -#import "MIKMIDIEndpoint.h" -#import "MIKMIDICommandScheduler.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -40,4 +40,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIDevice.h b/Source/MIKMIDIDevice.h index 235e58b6..53d27f54 100644 --- a/Source/MIKMIDIDevice.h +++ b/Source/MIKMIDIDevice.h @@ -6,8 +6,8 @@ // Copyright (c) 2013 Mixed In Key. All rights reserved. // -#import "MIKMIDIObject.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import @class MIKMIDIEntity; @class MIKMIDIEndpoint; @@ -131,4 +131,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIDeviceManager.h b/Source/MIKMIDIDeviceManager.h index d1727e21..af5cc534 100644 --- a/Source/MIKMIDIDeviceManager.h +++ b/Source/MIKMIDIDeviceManager.h @@ -7,8 +7,8 @@ // #import -#import "MIKMIDIInputPort.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import @class MIKMIDIDevice; @class MIKMIDISourceEndpoint; diff --git a/Source/MIKMIDIEndpoint.h b/Source/MIKMIDIEndpoint.h index 4faccd49..f907f436 100644 --- a/Source/MIKMIDIEndpoint.h +++ b/Source/MIKMIDIEndpoint.h @@ -6,8 +6,8 @@ // Copyright (c) 2013 Mixed In Key. All rights reserved. // -#import "MIKMIDIObject.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import @class MIKMIDIEntity; @@ -31,4 +31,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIEndpointSynthesizer.h b/Source/MIKMIDIEndpointSynthesizer.h index fef9c20f..acfa3c5e 100644 --- a/Source/MIKMIDIEndpointSynthesizer.h +++ b/Source/MIKMIDIEndpointSynthesizer.h @@ -6,8 +6,8 @@ // Copyright (c) 2014 Mixed In Key. All rights reserved. // -#import "MIKMIDISynthesizer.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import @class MIKMIDIEndpoint; @class MIKMIDISourceEndpoint; diff --git a/Source/MIKMIDIEntity.h b/Source/MIKMIDIEntity.h index 02a99888..cd27dce0 100644 --- a/Source/MIKMIDIEntity.h +++ b/Source/MIKMIDIEntity.h @@ -6,8 +6,8 @@ // Copyright (c) 2013 Mixed In Key. All rights reserved. // -#import "MIKMIDIObject.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import @class MIKMIDIDevice; @class MIKMIDIEndpoint; @@ -71,4 +71,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIErrors.h b/Source/MIKMIDIErrors.h index a6b035d0..334ca02d 100644 --- a/Source/MIKMIDIErrors.h +++ b/Source/MIKMIDIErrors.h @@ -7,7 +7,7 @@ // #import -#import "MIKMIDICompilerCompatibility.h" +#import NS_ASSUME_NONNULL_BEGIN @@ -88,4 +88,4 @@ NSString *MIKMIDIDefaultLocalizedErrorDescriptionForErrorCode(MIKMIDIErrorCode c @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIEvent.h b/Source/MIKMIDIEvent.h index 105737d1..a3fa7aae 100644 --- a/Source/MIKMIDIEvent.h +++ b/Source/MIKMIDIEvent.h @@ -8,7 +8,7 @@ #import #import -#import "MIKMIDICompilerCompatibility.h" +#import /** * Types of MIDI events. These values are used to determine which subclass to @@ -187,7 +187,7 @@ NS_ASSUME_NONNULL_END #pragma mark - MIKMIDICommand+MIKMIDIEventToCommands -#import "MIKMIDICommand.h" +#import @class MIKMIDIClock; @@ -199,4 +199,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIEventIterator.h b/Source/MIKMIDIEventIterator.h index 21f09642..940a3d78 100644 --- a/Source/MIKMIDIEventIterator.h +++ b/Source/MIKMIDIEventIterator.h @@ -8,7 +8,7 @@ #import #import -#import "MIKMIDICompilerCompatibility.h" +#import @class MIKMIDITrack; @class MIKMIDIEvent; @@ -37,4 +37,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIEvent_SubclassMethods.h b/Source/MIKMIDIEvent_SubclassMethods.h index e7887657..01386bea 100644 --- a/Source/MIKMIDIEvent_SubclassMethods.h +++ b/Source/MIKMIDIEvent_SubclassMethods.h @@ -6,8 +6,8 @@ // Copyright (c) 2014 Mixed In Key. All rights reserved. // -#import "MIKMIDIEvent.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -112,4 +112,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIInputPort.h b/Source/MIKMIDIInputPort.h index 00329257..a8435748 100644 --- a/Source/MIKMIDIInputPort.h +++ b/Source/MIKMIDIInputPort.h @@ -6,9 +6,9 @@ // Copyright (c) 2013 Mixed In Key. All rights reserved. // -#import "MIKMIDIPort.h" -#import "MIKMIDISourceEndpoint.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import +#import @class MIKMIDIEndpoint; @class MIKMIDICommand; diff --git a/Source/MIKMIDIMappableResponder.h b/Source/MIKMIDIMappableResponder.h index c8d4d46a..75c1f52c 100644 --- a/Source/MIKMIDIMappableResponder.h +++ b/Source/MIKMIDIMappableResponder.h @@ -7,8 +7,8 @@ // #import -#import "MIKMIDIResponder.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import /** * Bit-mask constants used to specify MIDI responder types for mapping. @@ -146,4 +146,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIMapping.h b/Source/MIKMIDIMapping.h index 646222e8..430d9e4f 100644 --- a/Source/MIKMIDIMapping.h +++ b/Source/MIKMIDIMapping.h @@ -7,10 +7,10 @@ // #import -#import "MIKMIDICompilerCompatibility.h" +#import -#import "MIKMIDICommand.h" -#import "MIKMIDIResponder.h" +#import +#import @protocol MIKMIDIMappableResponder; diff --git a/Source/MIKMIDIMappingGenerator.h b/Source/MIKMIDIMappingGenerator.h index d46854d1..45516258 100644 --- a/Source/MIKMIDIMappingGenerator.h +++ b/Source/MIKMIDIMappingGenerator.h @@ -7,9 +7,9 @@ // #import -#import "MIKMIDICompilerCompatibility.h" +#import -#import "MIKMIDIMapping.h" +#import @class MIKMIDIDevice; @class MIKMIDIMapping; diff --git a/Source/MIKMIDIMappingItem.h b/Source/MIKMIDIMappingItem.h index fcf8b21f..7c677080 100644 --- a/Source/MIKMIDIMappingItem.h +++ b/Source/MIKMIDIMappingItem.h @@ -7,9 +7,9 @@ // #import -#import "MIKMIDIMappableResponder.h" -#import "MIKMIDICommand.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import +#import @class MIKMIDIMapping; @@ -105,4 +105,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIMappingManager.h b/Source/MIKMIDIMappingManager.h index 3cb14459..935b5e21 100644 --- a/Source/MIKMIDIMappingManager.h +++ b/Source/MIKMIDIMappingManager.h @@ -7,7 +7,7 @@ // #import -#import "MIKMIDICompilerCompatibility.h" +#import #define kMIKMIDIMappingFileExtension @"midimap" diff --git a/Source/MIKMIDIMappingXMLParser.h b/Source/MIKMIDIMappingXMLParser.h index 29915dd5..1dece34b 100644 --- a/Source/MIKMIDIMappingXMLParser.h +++ b/Source/MIKMIDIMappingXMLParser.h @@ -7,7 +7,7 @@ // #import -#import "MIKMIDICompilerCompatibility.h" +#import @class MIKMIDIMapping; @@ -26,4 +26,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIMetaCopyrightEvent.h b/Source/MIKMIDIMetaCopyrightEvent.h index 66c34096..f6658294 100644 --- a/Source/MIKMIDIMetaCopyrightEvent.h +++ b/Source/MIKMIDIMetaCopyrightEvent.h @@ -6,8 +6,8 @@ // Copyright (c) 2014 Mixed In Key. All rights reserved. // -#import "MIKMIDIMetaTextEvent.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -29,4 +29,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIMetaCuePointEvent.h b/Source/MIKMIDIMetaCuePointEvent.h index a9afaecf..8fcce859 100644 --- a/Source/MIKMIDIMetaCuePointEvent.h +++ b/Source/MIKMIDIMetaCuePointEvent.h @@ -6,8 +6,8 @@ // Copyright (c) 2014 Mixed In Key. All rights reserved. // -#import "MIKMIDIMetaTextEvent.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -29,4 +29,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIMetaEvent.h b/Source/MIKMIDIMetaEvent.h index 6213e681..67001252 100644 --- a/Source/MIKMIDIMetaEvent.h +++ b/Source/MIKMIDIMetaEvent.h @@ -6,8 +6,8 @@ // Copyright (c) 2014 Mixed In Key. All rights reserved. // -#import "MIKMIDIEvent.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import static const NSUInteger MIKMIDIEventMetadataStartOffset = 8; @@ -110,4 +110,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIMetaEvent_SubclassMethods.h b/Source/MIKMIDIMetaEvent_SubclassMethods.h index cfe2dd78..ed3f1cf2 100644 --- a/Source/MIKMIDIMetaEvent_SubclassMethods.h +++ b/Source/MIKMIDIMetaEvent_SubclassMethods.h @@ -6,8 +6,8 @@ // Copyright © 2015 Mixed In Key. All rights reserved. // -#import "MIKMIDIMetaEvent.h" -#import "MIKMIDIEvent_SubclassMethods.h" +#import +#import @interface MIKMIDIMetaEvent () diff --git a/Source/MIKMIDIMetaInstrumentNameEvent.h b/Source/MIKMIDIMetaInstrumentNameEvent.h index 0d4db2ec..16313969 100644 --- a/Source/MIKMIDIMetaInstrumentNameEvent.h +++ b/Source/MIKMIDIMetaInstrumentNameEvent.h @@ -6,8 +6,8 @@ // Copyright (c) 2014 Mixed In Key. All rights reserved. // -#import "MIKMIDIMetaTextEvent.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -30,4 +30,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIMetaKeySignatureEvent.h b/Source/MIKMIDIMetaKeySignatureEvent.h index 4950c3f6..7050084f 100644 --- a/Source/MIKMIDIMetaKeySignatureEvent.h +++ b/Source/MIKMIDIMetaKeySignatureEvent.h @@ -6,8 +6,8 @@ // Copyright (c) 2014 Mixed In Key. All rights reserved. // -#import "MIKMIDIMetaEvent.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import typedef NS_ENUM(int8_t, MIKMIDIMusicalKey) { MIKMIDIMusicalKeyCFlatMajor = -7, @@ -119,4 +119,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIMetaLyricEvent.h b/Source/MIKMIDIMetaLyricEvent.h index 29f557be..da9fabcb 100644 --- a/Source/MIKMIDIMetaLyricEvent.h +++ b/Source/MIKMIDIMetaLyricEvent.h @@ -6,8 +6,8 @@ // Copyright (c) 2014 Mixed In Key. All rights reserved. // -#import "MIKMIDIMetaTextEvent.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -29,4 +29,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIMetaMarkerTextEvent.h b/Source/MIKMIDIMetaMarkerTextEvent.h index bc1e49ad..1e758494 100644 --- a/Source/MIKMIDIMetaMarkerTextEvent.h +++ b/Source/MIKMIDIMetaMarkerTextEvent.h @@ -6,8 +6,8 @@ // Copyright (c) 2014 Mixed In Key. All rights reserved. // -#import "MIKMIDIMetaTextEvent.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -29,4 +29,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIMetaSequenceEvent.h b/Source/MIKMIDIMetaSequenceEvent.h index ee446d20..542d2280 100644 --- a/Source/MIKMIDIMetaSequenceEvent.h +++ b/Source/MIKMIDIMetaSequenceEvent.h @@ -6,8 +6,8 @@ // Copyright (c) 2014 Mixed In Key. All rights reserved. // -#import "MIKMIDIMetaEvent.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -29,4 +29,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIMetaTextEvent.h b/Source/MIKMIDIMetaTextEvent.h index e603504e..60c46f81 100644 --- a/Source/MIKMIDIMetaTextEvent.h +++ b/Source/MIKMIDIMetaTextEvent.h @@ -6,8 +6,8 @@ // Copyright (c) 2014 Mixed In Key. All rights reserved. // -#import "MIKMIDIMetaEvent.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -37,4 +37,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIMetaTimeSignatureEvent.h b/Source/MIKMIDIMetaTimeSignatureEvent.h index f3f9682e..771632b9 100644 --- a/Source/MIKMIDIMetaTimeSignatureEvent.h +++ b/Source/MIKMIDIMetaTimeSignatureEvent.h @@ -6,8 +6,8 @@ // Copyright (c) 2014 Mixed In Key. All rights reserved. // -#import "MIKMIDIMetaEvent.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import /** * Represents a time signature. Note that in contrast to time signature events in raw MIDI, @@ -105,4 +105,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIMetaTrackSequenceNameEvent.h b/Source/MIKMIDIMetaTrackSequenceNameEvent.h index 5437b28d..abb89a20 100644 --- a/Source/MIKMIDIMetaTrackSequenceNameEvent.h +++ b/Source/MIKMIDIMetaTrackSequenceNameEvent.h @@ -6,8 +6,8 @@ // Copyright (c) 2014 Mixed In Key. All rights reserved. // -#import "MIKMIDIMetaTextEvent.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -36,4 +36,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIMetronome.h b/Source/MIKMIDIMetronome.h index 05117f71..247767a1 100644 --- a/Source/MIKMIDIMetronome.h +++ b/Source/MIKMIDIMetronome.h @@ -6,8 +6,8 @@ // Copyright (c) 2014 Mixed In Key. All rights reserved. // -#import "MIKMIDIEndpointSynthesizer.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/Source/MIKMIDINoteCommand.h b/Source/MIKMIDINoteCommand.h index 6ea4d2c7..1b20a775 100644 --- a/Source/MIKMIDINoteCommand.h +++ b/Source/MIKMIDINoteCommand.h @@ -6,8 +6,8 @@ // Copyright © 2017 Mixed In Key. All rights reserved. // -#import "MIKMIDIChannelVoiceCommand.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/Source/MIKMIDINoteEvent.h b/Source/MIKMIDINoteEvent.h index 35531473..f77d8638 100644 --- a/Source/MIKMIDINoteEvent.h +++ b/Source/MIKMIDINoteEvent.h @@ -6,8 +6,8 @@ // Copyright (c) 2014 Mixed In Key. All rights reserved. // -#import "MIKMIDIEvent.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import @class MIKMIDIClock; @@ -115,8 +115,8 @@ NS_ASSUME_NONNULL_END #pragma mark - -#import "MIKMIDINoteOnCommand.h" -#import "MIKMIDINoteOffCommand.h" +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -134,4 +134,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDINoteOffCommand.h b/Source/MIKMIDINoteOffCommand.h index ffba1715..051c41dc 100644 --- a/Source/MIKMIDINoteOffCommand.h +++ b/Source/MIKMIDINoteOffCommand.h @@ -6,7 +6,7 @@ // Copyright (c) 2013 Mixed In Key. All rights reserved. // -#import "MIKMIDINoteCommand.h" +#import NS_ASSUME_NONNULL_BEGIN diff --git a/Source/MIKMIDINoteOnCommand.h b/Source/MIKMIDINoteOnCommand.h index 9e3e08be..c1f0aba6 100644 --- a/Source/MIKMIDINoteOnCommand.h +++ b/Source/MIKMIDINoteOnCommand.h @@ -6,7 +6,7 @@ // Copyright (c) 2013 Mixed In Key. All rights reserved. // -#import "MIKMIDINoteCommand.h" +#import NS_ASSUME_NONNULL_BEGIN diff --git a/Source/MIKMIDIObject.h b/Source/MIKMIDIObject.h index d7026d35..ddcdf909 100644 --- a/Source/MIKMIDIObject.h +++ b/Source/MIKMIDIObject.h @@ -8,7 +8,7 @@ #import #import -#import "MIKMIDICompilerCompatibility.h" +#import NS_ASSUME_NONNULL_BEGIN @@ -101,4 +101,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIObject_SubclassMethods.h b/Source/MIKMIDIObject_SubclassMethods.h index d4b1d95a..88237ff7 100644 --- a/Source/MIKMIDIObject_SubclassMethods.h +++ b/Source/MIKMIDIObject_SubclassMethods.h @@ -6,8 +6,8 @@ // Copyright (c) 2013 Mixed In Key. All rights reserved. // -#import "MIKMIDIObject.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -59,4 +59,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIOutputPort.h b/Source/MIKMIDIOutputPort.h index 9a6e7aae..f4503b49 100644 --- a/Source/MIKMIDIOutputPort.h +++ b/Source/MIKMIDIOutputPort.h @@ -6,8 +6,8 @@ // Copyright (c) 2013 Mixed In Key. All rights reserved. // -#import "MIKMIDIPort.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import @class MIKMIDICommand; @class MIKMIDIDestinationEndpoint; diff --git a/Source/MIKMIDIPitchBendChangeCommand.h b/Source/MIKMIDIPitchBendChangeCommand.h index fbb442bf..b99dc0b6 100644 --- a/Source/MIKMIDIPitchBendChangeCommand.h +++ b/Source/MIKMIDIPitchBendChangeCommand.h @@ -6,8 +6,8 @@ // Copyright (c) 2015 Mixed In Key. All rights reserved. // -#import "MIKMIDIChannelVoiceCommand.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -44,4 +44,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIPitchBendChangeEvent.h b/Source/MIKMIDIPitchBendChangeEvent.h index 2c84e07f..01411cdf 100644 --- a/Source/MIKMIDIPitchBendChangeEvent.h +++ b/Source/MIKMIDIPitchBendChangeEvent.h @@ -6,8 +6,8 @@ // Copyright (c) 2015 Mixed In Key. All rights reserved. // -#import "MIKMIDIChannelEvent.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -43,4 +43,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIPlayer.h b/Source/MIKMIDIPlayer.h index 79b08225..2b0915c1 100644 --- a/Source/MIKMIDIPlayer.h +++ b/Source/MIKMIDIPlayer.h @@ -8,7 +8,7 @@ #import #import -#import "MIKMIDICompilerCompatibility.h" +#import @class MIKMIDISequence; @class MIKMIDIMetronome; diff --git a/Source/MIKMIDIPolyphonicKeyPressureCommand.h b/Source/MIKMIDIPolyphonicKeyPressureCommand.h index 57250a03..b5fa74df 100644 --- a/Source/MIKMIDIPolyphonicKeyPressureCommand.h +++ b/Source/MIKMIDIPolyphonicKeyPressureCommand.h @@ -6,7 +6,7 @@ // Copyright © 2015 Mixed In Key. All rights reserved. // -#import "MIKMIDIChannelVoiceCommand.h" +#import /** * A MIDI polyphonic key pressure message. This message is most often sent by pressing diff --git a/Source/MIKMIDIPolyphonicKeyPressureEvent.h b/Source/MIKMIDIPolyphonicKeyPressureEvent.h index 132317da..ecb83cd7 100644 --- a/Source/MIKMIDIPolyphonicKeyPressureEvent.h +++ b/Source/MIKMIDIPolyphonicKeyPressureEvent.h @@ -6,8 +6,8 @@ // Copyright (c) 2015 Mixed In Key. All rights reserved. // -#import "MIKMIDIChannelEvent.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -46,4 +46,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIPort.h b/Source/MIKMIDIPort.h index e1cc52bb..c44cadcc 100644 --- a/Source/MIKMIDIPort.h +++ b/Source/MIKMIDIPort.h @@ -6,9 +6,9 @@ // Copyright (c) 2013 Mixed In Key. All rights reserved. // -#import "MIKMIDIObject.h" +#import #import -#import "MIKMIDICompilerCompatibility.h" +#import @class MIKMIDIEndpoint; @@ -26,4 +26,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIPrivateUtilities.h b/Source/MIKMIDIPrivateUtilities.h index df1bf0ac..c77f8475 100644 --- a/Source/MIKMIDIPrivateUtilities.h +++ b/Source/MIKMIDIPrivateUtilities.h @@ -7,7 +7,7 @@ // #import -#import "MIKMIDICompilerCompatibility.h" +#import @class MIKMIDIChannelVoiceCommand; diff --git a/Source/MIKMIDIProgramChangeCommand.h b/Source/MIKMIDIProgramChangeCommand.h index 032422cc..3d473bd9 100644 --- a/Source/MIKMIDIProgramChangeCommand.h +++ b/Source/MIKMIDIProgramChangeCommand.h @@ -6,8 +6,8 @@ // Copyright (c) 2015 Mixed In Key. All rights reserved. // -#import "MIKMIDIChannelVoiceCommand.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -39,4 +39,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIProgramChangeEvent.h b/Source/MIKMIDIProgramChangeEvent.h index 10b5c35a..857318f7 100644 --- a/Source/MIKMIDIProgramChangeEvent.h +++ b/Source/MIKMIDIProgramChangeEvent.h @@ -6,8 +6,8 @@ // Copyright (c) 2015 Mixed In Key. All rights reserved. // -#import "MIKMIDIChannelEvent.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -50,4 +50,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIResponder.h b/Source/MIKMIDIResponder.h index 7ae52e0b..937595dc 100644 --- a/Source/MIKMIDIResponder.h +++ b/Source/MIKMIDIResponder.h @@ -7,7 +7,7 @@ // #import -#import "MIKMIDICompilerCompatibility.h" +#import @class MIKMIDICommand; @@ -75,4 +75,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDISequence+MIKMIDIPrivate.h b/Source/MIKMIDISequence+MIKMIDIPrivate.h index 290d26d7..00bec8e6 100644 --- a/Source/MIKMIDISequence+MIKMIDIPrivate.h +++ b/Source/MIKMIDISequence+MIKMIDIPrivate.h @@ -6,8 +6,8 @@ // Copyright (c) 2015 Mixed In Key. All rights reserved. // -#import "MIKMIDISequence.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import @class MIKMIDISequencer; diff --git a/Source/MIKMIDISequence.h b/Source/MIKMIDISequence.h index 218ecf37..4a68d32a 100644 --- a/Source/MIKMIDISequence.h +++ b/Source/MIKMIDISequence.h @@ -8,8 +8,8 @@ #import #import -#import "MIKMIDICompilerCompatibility.h" -#import "MIKMIDIMetaTimeSignatureEvent.h" +#import +#import @class MIKMIDITrack; @class MIKMIDISequencer; diff --git a/Source/MIKMIDISequencer+MIKMIDIPrivate.h b/Source/MIKMIDISequencer+MIKMIDIPrivate.h index 9e87687c..b215fe6e 100644 --- a/Source/MIKMIDISequencer+MIKMIDIPrivate.h +++ b/Source/MIKMIDISequencer+MIKMIDIPrivate.h @@ -6,8 +6,8 @@ // Copyright (c) 2015 Mixed In Key. All rights reserved. // -#import "MIKMIDISequencer.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/Source/MIKMIDISequencer.h b/Source/MIKMIDISequencer.h index a7e3f47a..bd81a0e3 100644 --- a/Source/MIKMIDISequencer.h +++ b/Source/MIKMIDISequencer.h @@ -8,7 +8,7 @@ #import #import -#import "MIKMIDICompilerCompatibility.h" +#import @class MIKMIDISequence; @class MIKMIDITrack; diff --git a/Source/MIKMIDISourceEndpoint.h b/Source/MIKMIDISourceEndpoint.h index 6a0027e5..126330ac 100644 --- a/Source/MIKMIDISourceEndpoint.h +++ b/Source/MIKMIDISourceEndpoint.h @@ -6,8 +6,8 @@ // Copyright (c) 2013 Mixed In Key. All rights reserved. // -#import "MIKMIDIEndpoint.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import @class MIKMIDISourceEndpoint; @class MIKMIDICommand; @@ -41,4 +41,4 @@ typedef void(^MIKMIDIEventHandlerBlock)(MIKMIDISourceEndpoint *source, MIKArrayO @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDISynthesizer.h b/Source/MIKMIDISynthesizer.h index 95714b94..3e78ae3c 100644 --- a/Source/MIKMIDISynthesizer.h +++ b/Source/MIKMIDISynthesizer.h @@ -8,9 +8,9 @@ #import #import -#import "MIKMIDISynthesizerInstrument.h" -#import "MIKMIDICommandScheduler.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/Source/MIKMIDISynthesizerInstrument.h b/Source/MIKMIDISynthesizerInstrument.h index 97fb1c53..70a0f9ee 100644 --- a/Source/MIKMIDISynthesizerInstrument.h +++ b/Source/MIKMIDISynthesizerInstrument.h @@ -8,7 +8,7 @@ #import #import -#import "MIKMIDICompilerCompatibility.h" +#import NS_ASSUME_NONNULL_BEGIN @@ -67,4 +67,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDISynthesizer_SubclassMethods.h b/Source/MIKMIDISynthesizer_SubclassMethods.h index 763e2dd7..11b7340b 100644 --- a/Source/MIKMIDISynthesizer_SubclassMethods.h +++ b/Source/MIKMIDISynthesizer_SubclassMethods.h @@ -6,8 +6,8 @@ // Copyright (c) 2015 Mixed In Key. All rights reserved. // -#import "MIKMIDISynthesizer.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/Source/MIKMIDISystemExclusiveCommand.h b/Source/MIKMIDISystemExclusiveCommand.h index d3cff44e..cf4ff56c 100644 --- a/Source/MIKMIDISystemExclusiveCommand.h +++ b/Source/MIKMIDISystemExclusiveCommand.h @@ -6,8 +6,8 @@ // Copyright (c) 2013 Mixed In Key. All rights reserved. // -#import "MIKMIDISystemMessageCommand.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import extern uint32_t const kMIKMIDISysexNonRealtimeManufacturerID; extern uint32_t const kMIKMIDISysexRealtimeManufacturerID; diff --git a/Source/MIKMIDISystemKeepAliveCommand.h b/Source/MIKMIDISystemKeepAliveCommand.h index f1843ef1..e15efee8 100644 --- a/Source/MIKMIDISystemKeepAliveCommand.h +++ b/Source/MIKMIDISystemKeepAliveCommand.h @@ -6,7 +6,7 @@ // Copyright © 2017 Mixed In Key. All rights reserved. // -#import "MIKMIDISystemMessageCommand.h" +#import NS_ASSUME_NONNULL_BEGIN diff --git a/Source/MIKMIDISystemMessageCommand.h b/Source/MIKMIDISystemMessageCommand.h index 7a537b84..bc7e864a 100644 --- a/Source/MIKMIDISystemMessageCommand.h +++ b/Source/MIKMIDISystemMessageCommand.h @@ -6,8 +6,8 @@ // Copyright (c) 2013 Mixed In Key. All rights reserved. // -#import "MIKMIDICommand.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -34,4 +34,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDITempoEvent.h b/Source/MIKMIDITempoEvent.h index dd8fbb31..995d6b76 100644 --- a/Source/MIKMIDITempoEvent.h +++ b/Source/MIKMIDITempoEvent.h @@ -6,8 +6,8 @@ // Copyright (c) 2014 Mixed In Key. All rights reserved. // -#import "MIKMIDIEvent.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -45,4 +45,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDITempoTrack.h b/Source/MIKMIDITempoTrack.h index 230a02c5..8a266ff8 100644 --- a/Source/MIKMIDITempoTrack.h +++ b/Source/MIKMIDITempoTrack.h @@ -7,8 +7,8 @@ // #import -#import "MIKMIDITrack.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import @class MIKMIDITempoEvent; diff --git a/Source/MIKMIDITrack.h b/Source/MIKMIDITrack.h index fc1de1a7..981f9362 100644 --- a/Source/MIKMIDITrack.h +++ b/Source/MIKMIDITrack.h @@ -8,7 +8,7 @@ #import #import -#import "MIKMIDICompilerCompatibility.h" +#import @class MIKMIDISequence; @class MIKMIDIEvent; diff --git a/Source/MIKMIDITrack_Protected.h b/Source/MIKMIDITrack_Protected.h index b01a8da6..02ef7a1a 100644 --- a/Source/MIKMIDITrack_Protected.h +++ b/Source/MIKMIDITrack_Protected.h @@ -6,8 +6,8 @@ // Copyright (c) 2015 Mixed In Key. All rights reserved. // -#import "MIKMIDITrack.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/Source/MIKMIDIUtilities.h b/Source/MIKMIDIUtilities.h index 96256f8e..38daf62f 100644 --- a/Source/MIKMIDIUtilities.h +++ b/Source/MIKMIDIUtilities.h @@ -8,9 +8,9 @@ #import #import -#import "MIKMIDIMappableResponder.h" -#import "MIKMIDICommand.h" -#import "MIKMIDICompilerCompatibility.h" +#import +#import +#import #include NS_ASSUME_NONNULL_BEGIN diff --git a/Source/NSUIApplication+MIKMIDI.h b/Source/NSUIApplication+MIKMIDI.h index 77186491..62d94313 100644 --- a/Source/NSUIApplication+MIKMIDI.h +++ b/Source/NSUIApplication+MIKMIDI.h @@ -24,7 +24,7 @@ #endif -#import "MIKMIDICompilerCompatibility.h" +#import /** * Define MIKMIDI_SEARCH_VIEW_HIERARCHY_FOR_RESPONDERS as a non-zero value to (re)enable searching @@ -140,4 +140,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END From 1bf0095d94c8cba5dcd072fef896f7671944d203 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Sat, 19 Sep 2020 17:59:43 -0600 Subject: [PATCH 27/53] Bump deployment targest to iOS 9 and macOS 10.9 --- MIKMIDI.podspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MIKMIDI.podspec b/MIKMIDI.podspec index a56897b8..fcb413d3 100644 --- a/MIKMIDI.podspec +++ b/MIKMIDI.podspec @@ -15,8 +15,8 @@ Pod::Spec.new do |s| s.author = { 'Andrew Madsen' => 'andrew@mixedinkey.com' } s.social_media_url = 'https://twitter.com/armadsen' - s.ios.deployment_target = '8.0' - s.osx.deployment_target = '10.8' + s.ios.deployment_target = '9.0' + s.osx.deployment_target = '10.9' s.source = { :git => 'https://github.com/mixedinkey-opensource/MIKMIDI.git', :tag => s.version.to_s } s.source_files = 'Source/**/*.{h,m}' From 7e9ec9520fc040438c6c02c67b9c5dbdece6c57b Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Sat, 19 Sep 2020 18:05:06 -0600 Subject: [PATCH 28/53] Bump deployment targest to iOS 9 and macOS 10.9 in framework project (last commit was podspec) --- Framework/MIKMIDI.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Framework/MIKMIDI.xcodeproj/project.pbxproj b/Framework/MIKMIDI.xcodeproj/project.pbxproj index 5a4616eb..2aab1f00 100644 --- a/Framework/MIKMIDI.xcodeproj/project.pbxproj +++ b/Framework/MIKMIDI.xcodeproj/project.pbxproj @@ -1566,7 +1566,8 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.7; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MACOSX_DEPLOYMENT_TARGET = 10.9; MODULEMAP_FILE = ""; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -1619,7 +1620,8 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.7; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MACOSX_DEPLOYMENT_TARGET = 10.9; MODULEMAP_FILE = ""; SDKROOT = macosx; VERSIONING_SYSTEM = "apple-generic"; @@ -1702,7 +1704,6 @@ ); INFOPLIST_FILE = "MIKMIDI-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MODULEMAP_FILE = MIKMIDI.modulemap; MTL_ENABLE_DEBUG_INFO = YES; @@ -1744,7 +1745,6 @@ ); INFOPLIST_FILE = "MIKMIDI-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MODULEMAP_FILE = MIKMIDI.modulemap; MTL_ENABLE_DEBUG_INFO = NO; From 8ac9fd9c4e9aa8e90c42f9d90c69124cc53bb885 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Sat, 30 Jan 2021 20:40:12 -0700 Subject: [PATCH 29/53] Issue #304: Add includesThreeByteManufacturerID to MIKMIDISystemExclusiveCommand --- .../MIKMIDISystemExclusiveCommandTests.m | 50 +++++++++++++++++++ Source/MIKMIDISystemExclusiveCommand.h | 10 ++++ Source/MIKMIDISystemExclusiveCommand.m | 28 ++++++++--- 3 files changed, 81 insertions(+), 7 deletions(-) diff --git a/Framework/MIKMIDI Tests/MIKMIDISystemExclusiveCommandTests.m b/Framework/MIKMIDI Tests/MIKMIDISystemExclusiveCommandTests.m index 2b586637..56c7bae6 100644 --- a/Framework/MIKMIDI Tests/MIKMIDISystemExclusiveCommandTests.m +++ b/Framework/MIKMIDI Tests/MIKMIDISystemExclusiveCommandTests.m @@ -78,4 +78,54 @@ - (void)testManufacturerSpecificSystemExclusiveCommand XCTAssertEqual(command.manufacturerID, 0x002076, @"Setting a 3-byte manufacturerID on a MIKMutableMIDISystemExclusiveCommand instance failed."); } +- (void)testChangingManufacturerIDLength +{ + MIKMutableMIDISystemExclusiveCommand *command = [[MIKMutableMIDISystemExclusiveCommand alloc] init]; + command.manufacturerID = 0x41; // Roland + XCTAssertFalse(command.includesThreeByteManufacturerID); + XCTAssertEqual(command.data.length, 4); // Status, 1-byte manufacturer, (zero) channel, end delimiter byte + + command.manufacturerID = 0x414243; + XCTAssertTrue(command.includesThreeByteManufacturerID); + XCTAssertEqual(command.manufacturerID, 0x414243, @"Changing the manufacturerID length on a MIKMutableMIDISystemExclusiveCommand instance failed."); + XCTAssertEqual(command.data.length, 6); // Status, 3-byte manufacturer, (zero) channel, end delimiter byte + + command.manufacturerID = 0x41; + XCTAssertFalse(command.includesThreeByteManufacturerID); + XCTAssertEqual(command.manufacturerID, 0x41, @"Changing the manufacturerID length on a MIKMutableMIDISystemExclusiveCommand instance failed."); + XCTAssertEqual(command.data.length, 4); // Status, 1-byte manufacturer, (zero) channel, end delimiter byte +} + +- (void)testForcedThreeByteManufacturerIDLength +{ + MIKMutableMIDISystemExclusiveCommand *command = [[MIKMutableMIDISystemExclusiveCommand alloc] init]; + command.manufacturerID = 0x41; // Roland + XCTAssertFalse(command.includesThreeByteManufacturerID); + XCTAssertEqual(command.data.length, 4); // Status, 1-byte manufacturer, (zero) channel, end delimiter byte + + command.includesThreeByteManufacturerID = YES; + XCTAssertEqual(command.manufacturerID, 0x41, @"manufacturerID is wrong after includesThreeByteManufacturerID change"); + XCTAssertTrue(command.includesThreeByteManufacturerID); + XCTAssertEqual(command.data.length, 6); // Status, 3-byte manufacturer, (zero) channel, end delimiter byte + + command.includesThreeByteManufacturerID = NO; + XCTAssertEqual(command.manufacturerID, 0x41, @"manufacturerID is wrong after includesThreeByteManufacturerID change"); + XCTAssertFalse(command.includesThreeByteManufacturerID); + XCTAssertEqual(command.data.length, 4); // Status, 1-byte manufacturer, (zero) channel, end delimiter byte +} + +- (void)testSettingIncludesThreeByteManufacturerIDWithThreeByteManufacturerID +{ + MIKMutableMIDISystemExclusiveCommand *command = [[MIKMutableMIDISystemExclusiveCommand alloc] init]; + command.manufacturerID = 0x414243; + XCTAssertTrue(command.includesThreeByteManufacturerID); + XCTAssertEqual(command.manufacturerID, 0x414243, @"Changing the manufacturerID length on a MIKMutableMIDISystemExclusiveCommand instance failed."); + XCTAssertEqual(command.data.length, 6); // Status, 3-byte manufacturer, (zero) channel, end delimiter byte + + command.includesThreeByteManufacturerID = NO; + XCTAssertTrue(command.includesThreeByteManufacturerID); + XCTAssertEqual(command.manufacturerID, 0x414243, @"manufacturerID is wrong after includesThreeByteManufacturerID change"); + XCTAssertEqual(command.data.length, 6); // Status, 3-byte manufacturer, (zero) channel, end delimiter byte +} + @end diff --git a/Source/MIKMIDISystemExclusiveCommand.h b/Source/MIKMIDISystemExclusiveCommand.h index cf4ff56c..041a330f 100644 --- a/Source/MIKMIDISystemExclusiveCommand.h +++ b/Source/MIKMIDISystemExclusiveCommand.h @@ -62,6 +62,15 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, readonly) UInt32 manufacturerID; +/** + * Whether or not the command's data will include three bytes for the manufacturer ID. + * If the first three bytes of the manufacturer ID are non-zero, this *always* returns YES. + * By default, if only the last byte of the manufacturer ID is non-zero, this returns NO. + * It can be set to YES (in MIKMutableMIDISystemExclusiveCommand) to explicitly + * Include all three bytes, including the two leading zero bytes. + */ +@property (nonatomic, readonly) BOOL includesThreeByteManufacturerID; + /** * The channel of the message. Only valid for universal exclusive messages, * will always be 0 for non-universal messages. @@ -90,6 +99,7 @@ NS_ASSUME_NONNULL_BEGIN @interface MIKMutableMIDISystemExclusiveCommand : MIKMIDISystemExclusiveCommand @property (nonatomic, readwrite) UInt32 manufacturerID; +@property (nonatomic, readwrite) BOOL includesThreeByteManufacturerID; @property (nonatomic, readwrite) UInt8 sysexChannel; @property (nonatomic, strong, readwrite) NSData *sysexData; diff --git a/Source/MIKMIDISystemExclusiveCommand.m b/Source/MIKMIDISystemExclusiveCommand.m index 5767d1bf..af3bc75a 100644 --- a/Source/MIKMIDISystemExclusiveCommand.m +++ b/Source/MIKMIDISystemExclusiveCommand.m @@ -32,6 +32,8 @@ @interface MIKMIDISystemExclusiveCommand () @implementation MIKMIDISystemExclusiveCommand { BOOL _has3ByteManufacturerID; + BOOL _includesThreeByteManufacturerID; + BOOL _threeByteManufacturerIDInInternalData; } + (void)load { [super load]; [MIKMIDICommand registerSubclass:self]; } @@ -101,7 +103,7 @@ - (UInt32)manufacturerID { if ([self.internalData length] < 2) return 0; - NSUInteger manufacturerIDLength = _has3ByteManufacturerID ? 3 : 1; + NSUInteger manufacturerIDLength = _threeByteManufacturerIDInInternalData ? 3 : 1; NSData *idData = [self.internalData subdataWithRange:NSMakeRange(1, manufacturerIDLength)]; UInt8 *bytes = (UInt8 *)[idData bytes]; if (manufacturerIDLength == 1) { return bytes[0]; } @@ -112,16 +114,27 @@ - (void)setManufacturerID:(UInt32)manufacturerID { if (![[self class] isMutable]) return MIKMIDI_RAISE_MUTATION_ATTEMPT_EXCEPTION; - NSUInteger numExistingBytes = _has3ByteManufacturerID ? 3 : 1; + NSUInteger numExistingBytes = _threeByteManufacturerIDInInternalData ? 3 : 1; NSUInteger numNewBytes = (manufacturerID & 0xFFFF00) != 0 ? 3 : 1; + _has3ByteManufacturerID = (numNewBytes == 3); + if (self.includesThreeByteManufacturerID) { numNewBytes = 3; } manufacturerID = CFSwapInt32HostToBig(manufacturerID); - NSUInteger numRequiredBytes = MAX(numExistingBytes, numNewBytes) + 1; - if ([self.internalData length] < numRequiredBytes) [self.internalData increaseLengthBy:numRequiredBytes-[self.internalData length]]; - UInt8 *replacementBytes = (UInt8 *)(&manufacturerID) + 4 - numNewBytes; [self.internalData replaceBytesInRange:NSMakeRange(1, numExistingBytes) withBytes:replacementBytes length:numNewBytes]; - - _has3ByteManufacturerID = (numNewBytes == 3); + _threeByteManufacturerIDInInternalData = numNewBytes == 3; +} + +- (BOOL)includesThreeByteManufacturerID +{ + if (_has3ByteManufacturerID) { return YES; } + return _includesThreeByteManufacturerID; +} + +- (void)setIncludesThreeByteManufacturerID:(BOOL)includesThreeByteManufacturerID +{ + if (![[self class] isMutable]) return MIKMIDI_RAISE_MUTATION_ATTEMPT_EXCEPTION; + _includesThreeByteManufacturerID = includesThreeByteManufacturerID; + self.manufacturerID = self.manufacturerID; } - (BOOL)isUniversal @@ -226,6 +239,7 @@ + (BOOL)isMutable { return YES; } // One of the super classes already implements a getter *and* setter for these. @dynamic keeps the compiler happy. @dynamic manufacturerID; +@dynamic includesThreeByteManufacturerID; @dynamic sysexChannel; @dynamic sysexData; @dynamic timestamp; From f4e1d29f7b9108085c9e15e5a651743c6cc44d2b Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Sat, 30 Jan 2021 21:52:57 -0700 Subject: [PATCH 30/53] Fix setSysexData: clobbering manufacturer ID if includesThreeByteManufacturerID is YES (bug in implementation of Issue #304) --- .../MIKMIDISystemExclusiveCommandTests.m | 14 ++++++++++++++ Source/MIKMIDISystemExclusiveCommand.m | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/Framework/MIKMIDI Tests/MIKMIDISystemExclusiveCommandTests.m b/Framework/MIKMIDI Tests/MIKMIDISystemExclusiveCommandTests.m index 56c7bae6..75391f0e 100644 --- a/Framework/MIKMIDI Tests/MIKMIDISystemExclusiveCommandTests.m +++ b/Framework/MIKMIDI Tests/MIKMIDISystemExclusiveCommandTests.m @@ -114,6 +114,20 @@ - (void)testForcedThreeByteManufacturerIDLength XCTAssertEqual(command.data.length, 4); // Status, 1-byte manufacturer, (zero) channel, end delimiter byte } +- (void)testForcedThreeByteManufacturerIDLengthWithSysexData +{ + MIKMutableMIDISystemExclusiveCommand *command = [[MIKMutableMIDISystemExclusiveCommand alloc] init]; + command.includesThreeByteManufacturerID = YES; + command.manufacturerID = 0x41; // Roland + XCTAssertEqual(command.manufacturerID, 0x41, @"manufacturerID is wrong after includesThreeByteManufacturerID change"); + XCTAssertEqual(command.data.length, 6); // Status, 3-byte manufacturer, (zero) channel, end delimiter byte + XCTAssertTrue(command.includesThreeByteManufacturerID); + + command.sysexData = [NSData dataWithBytes:(UInt8[]){0xde, 0xad, 0xbe, 0xef} length:4]; + XCTAssertEqual(command.manufacturerID, 0x41, @"manufacturerID is wrong after setting sysexData change"); + XCTAssertEqual(command.data.length, 9); // Status, 3-byte manufacturer, 4-byte sysex data, end delimiter byte +} + - (void)testSettingIncludesThreeByteManufacturerIDWithThreeByteManufacturerID { MIKMutableMIDISystemExclusiveCommand *command = [[MIKMutableMIDISystemExclusiveCommand alloc] init]; diff --git a/Source/MIKMIDISystemExclusiveCommand.m b/Source/MIKMIDISystemExclusiveCommand.m index af3bc75a..82fc5c09 100644 --- a/Source/MIKMIDISystemExclusiveCommand.m +++ b/Source/MIKMIDISystemExclusiveCommand.m @@ -172,7 +172,7 @@ - (void)setSysexChannel:(UInt8)sysexChannel - (NSUInteger)sysexDataStartLocation { - NSUInteger sysexStartLocation = _has3ByteManufacturerID ? 4 : 2; + NSUInteger sysexStartLocation = _threeByteManufacturerIDInInternalData ? 4 : 2; if (self.isUniversal) { sysexStartLocation++; } From b5b8cd57d8eb584ecf02763dc55b3d330a8e0e07 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Sat, 30 Jan 2021 22:38:12 -0700 Subject: [PATCH 31/53] Issue #306: Add new convenience method for MIKMIDISystemExclusiveCommand and associated tests --- .../MIKMIDISystemExclusiveCommandTests.m | 40 ++++++++++++++++++- Source/MIKMIDISystemExclusiveCommand.h | 19 ++++++++- Source/MIKMIDISystemExclusiveCommand.m | 13 ++++++ 3 files changed, 69 insertions(+), 3 deletions(-) diff --git a/Framework/MIKMIDI Tests/MIKMIDISystemExclusiveCommandTests.m b/Framework/MIKMIDI Tests/MIKMIDISystemExclusiveCommandTests.m index 75391f0e..27620703 100644 --- a/Framework/MIKMIDI Tests/MIKMIDISystemExclusiveCommandTests.m +++ b/Framework/MIKMIDI Tests/MIKMIDISystemExclusiveCommandTests.m @@ -15,7 +15,7 @@ @interface MIKMIDISystemExclusiveCommandTests : XCTestCase @implementation MIKMIDISystemExclusiveCommandTests -- (void)testSystemExclusiveCommand +- (void)testInitializingSystemExclusiveCommand { Class immutableClass = [MIKMIDISystemExclusiveCommand class]; Class mutableClass = [MIKMutableMIDISystemExclusiveCommand class]; @@ -41,6 +41,44 @@ - (void)testSystemExclusiveCommand XCTAssertEqual(mutableCommand.sysexChannel, 27, @"Setting the sysexChannel on a MIKMutableMIDISystemExclusiveCommand instance failed."); } +- (void)testSysexCommandConvenienceMethod +{ + Class immutableClass = [MIKMIDISystemExclusiveCommand class]; + Class mutableClass = [MIKMutableMIDISystemExclusiveCommand class]; + + NSDate *timestamp = [NSDate date]; + NSData *sysexData = [NSData dataWithBytes:(UInt8[]){0xde, 0xad, 0xbe, 0xef} length:4]; + MIKMIDISystemExclusiveCommand *command = + [MIKMIDISystemExclusiveCommand systemExclusiveCommandWithManufacturerID:0x41 + sysexChannel:1 + sysexData:sysexData + timestamp:timestamp]; + XCTAssert([command isMemberOfClass:[immutableClass class]], @"[MIKMIDISystemExclusiveCommand systemExclusiveCommandWithManufacturerID:...] did not return an MIKMIDISystemExclusiveCommand instance."); + XCTAssertEqual(command.commandType, MIKMIDICommandTypeSystemExclusive, @"[MIKMIDISystemExclusiveCommand systemExclusiveCommandWithManufacturerID] produced a command instance with the wrong command type."); + XCTAssertEqual(command.data.length, 7, "MIKMIDISystemExclusiveCommand had an incorrect data length %@ (should be 4)", @(command.data.length)); + XCTAssertEqual(command.manufacturerID, 0x41, @"The manufacturerID on a MIKMIDISystemExclusiveCommand instance was incorrect."); + XCTAssertEqual(command.sysexChannel, 0, @"The sysexChannel on a MIKMIDISystemExclusiveCommand instance was incorrect."); + XCTAssertEqualObjects(command.sysexData, sysexData, @"The sysexData on a MIKMIDISystemExclusiveCommand instance was incorrect."); + + MIKMutableMIDISystemExclusiveCommand *mutableCommand = + [MIKMutableMIDISystemExclusiveCommand systemExclusiveCommandWithManufacturerID:0x41 + sysexChannel:1 + sysexData:sysexData + timestamp:timestamp]; + XCTAssert([mutableCommand isMemberOfClass:[mutableClass class]], @"[MIKMutableMIDISystemExclusiveCommand systemExclusiveCommandWithManufacturerID:...] did not return an MIKMIDISystemExclusiveCommand instance."); + XCTAssertEqual(mutableCommand.commandType, MIKMIDICommandTypeSystemExclusive, @"[MIKMutableMIDISystemExclusiveCommand systemExclusiveCommandWithManufacturerID] produced a command instance with the wrong command type."); + XCTAssertEqual(mutableCommand.data.length, 7, "MIKMutableMIDISystemExclusiveCommand had an incorrect data length %@ (should be 4)", @(command.data.length)); + XCTAssertEqual(mutableCommand.manufacturerID, 0x41, @"The manufacturerID on a MIKMutableMIDISystemExclusiveCommand instance was incorrect."); + XCTAssertEqual(mutableCommand.sysexChannel, 0, @"The sysexChannel on a MIKMutableMIDISystemExclusiveCommand instance was incorrect."); + XCTAssertEqualObjects(mutableCommand.sysexData, sysexData, @"The sysexData on a MIKMutableMIDISystemExclusiveCommand instance was incorrect."); + + XCTAssertNoThrow([mutableCommand setSysexData:[NSData data]], @"-[MIKMIDISystemExclusiveCommand setSysexData:] was not allowed on mutable instance."); + XCTAssertNoThrow([mutableCommand setSysexChannel:10], @"-[MIKMIDISystemExclusiveCommand setSysexChannel:] was not allowed on mutable instance."); + + mutableCommand.manufacturerID = 0x42; + XCTAssertEqual(mutableCommand.manufacturerID, 0x42, @"Setting the manufacturerID on a MIKMutableMIDISystemExclusiveCommand instance failed."); +} + - (void)testSettingSysexData { MIKMutableMIDISystemExclusiveCommand *command = [[MIKMutableMIDISystemExclusiveCommand alloc] init]; diff --git a/Source/MIKMIDISystemExclusiveCommand.h b/Source/MIKMIDISystemExclusiveCommand.h index 041a330f..17aea2b7 100644 --- a/Source/MIKMIDISystemExclusiveCommand.h +++ b/Source/MIKMIDISystemExclusiveCommand.h @@ -38,13 +38,28 @@ NS_ASSUME_NONNULL_BEGIN */ + (instancetype)identityRequestCommand; +/** + * Creates a SysEx command. + * + * @param manufacturerID The manufacturer ID for the command, + * @param sysexChannel The channel of the message. Only valid for universal exclusive messages, + * will always be ignored for non-universal messages. + * @param sysexData The system exclusive data for the message. Should not include status byte, manufacturer ID, channel. + * End delimiter (0x7F) is optional and will be added if not present. + * @param timestamp The timestamp for the command. Pass nil to use the current date/time. + */ ++ (instancetype)systemExclusiveCommandWithManufacturerID:(UInt32)manufacturerID + sysexChannel:(UInt8)sysexChannel + sysexData:(NSData *)sysexData + timestamp:(nullable NSDate *)timestamp; + /** * Initializes the command with raw sysex data and timestamp. * * @param data Assumed to be valid with begin+end delimiters. * @param timeStamp Time at which the first sysex byte was received. */ -- (id)initWithRawData:(NSData *)data timeStamp:(MIDITimeStamp)timeStamp; +- (instancetype)initWithRawData:(NSData *)data timeStamp:(MIDITimeStamp)timeStamp; /** * The manufacturer ID for the command. This is used by devices to determine @@ -82,7 +97,7 @@ NS_ASSUME_NONNULL_BEGIN * * For universal messages subID's are included in sysexData, for non-universal * messages, any device specific information (such as modelID, versionID or - * whatever manufactures decide to include) will be included in sysexData. + * whatever manufacturers decide to include) will be included in sysexData. */ @property (nonatomic, strong, readonly) NSData *sysexData; diff --git a/Source/MIKMIDISystemExclusiveCommand.m b/Source/MIKMIDISystemExclusiveCommand.m index 82fc5c09..9606888a 100644 --- a/Source/MIKMIDISystemExclusiveCommand.m +++ b/Source/MIKMIDISystemExclusiveCommand.m @@ -69,6 +69,19 @@ + (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key return result; } ++ (instancetype)systemExclusiveCommandWithManufacturerID:(UInt32)manufacturerID + sysexChannel:(UInt8)sysexChannel + sysexData:(NSData *)sysexData + timestamp:(nullable NSDate *)timestamp +{ + MIKMutableMIDISystemExclusiveCommand *result = [[MIKMutableMIDISystemExclusiveCommand alloc] init]; + result.manufacturerID = manufacturerID; + result.sysexChannel = sysexChannel; + result.sysexData = sysexData; + result.timestamp = timestamp ?: [NSDate date]; + return [self isMutable] ? result : [result copy]; +} + - (id)initWithMIDIPacket:(MIDIPacket *)packet { self = [super initWithMIDIPacket:packet]; From 446f5674a20f5ba466529ba0f6c76c8195dad4ae Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Sun, 31 Jan 2021 13:00:24 -0700 Subject: [PATCH 32/53] Issue #308: Fix parsing 3 byte manufacturer IDs in MIKMIDISystemExclusiveCommand --- .../MIKMIDISystemExclusiveCommandTests.m | 27 +++++++++++++++++++ Source/MIKMIDISystemExclusiveCommand.m | 3 ++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/Framework/MIKMIDI Tests/MIKMIDISystemExclusiveCommandTests.m b/Framework/MIKMIDI Tests/MIKMIDISystemExclusiveCommandTests.m index 27620703..b94a1bd4 100644 --- a/Framework/MIKMIDI Tests/MIKMIDISystemExclusiveCommandTests.m +++ b/Framework/MIKMIDI Tests/MIKMIDISystemExclusiveCommandTests.m @@ -180,4 +180,31 @@ - (void)testSettingIncludesThreeByteManufacturerIDWithThreeByteManufacturerID XCTAssertEqual(command.data.length, 6); // Status, 3-byte manufacturer, (zero) channel, end delimiter byte } +- (void)testParsingManufacturerID +{ + // 0xf0 00 00 41 00 f7 + MIDIPacket packet = MIKMIDIPacketCreate(0, 6, @[@0xf0, @0, @0, @0x41, @0, @0xf7]); + MIKMIDISystemExclusiveCommand *command = (MIKMIDISystemExclusiveCommand *)[MIKMIDICommand commandWithMIDIPacket:&packet]; + XCTAssertNotNil(command); + XCTAssertTrue([command isKindOfClass:[MIKMIDISystemExclusiveCommand class]]); + XCTAssertEqual(command.manufacturerID, 0x41); + XCTAssertTrue(command.includesThreeByteManufacturerID); + + // 0xf0 41 00 f7 + packet = MIKMIDIPacketCreate(0, 4, @[@0xf0, @0x41, @0, @0xf7]); + command = (MIKMIDISystemExclusiveCommand *)[MIKMIDICommand commandWithMIDIPacket:&packet]; + XCTAssertNotNil(command); + XCTAssertTrue([command isKindOfClass:[MIKMIDISystemExclusiveCommand class]]); + XCTAssertEqual(command.manufacturerID, 0x41); + XCTAssertFalse(command.includesThreeByteManufacturerID); + + // 0xf0 00 42 43 00 f7 + packet = MIKMIDIPacketCreate(0, 6, @[@0xf0, @0, @0x42, @0x43, @0, @0xf7]); + command = (MIKMIDISystemExclusiveCommand *)[MIKMIDICommand commandWithMIDIPacket:&packet]; + XCTAssertNotNil(command); + XCTAssertTrue([command isKindOfClass:[MIKMIDISystemExclusiveCommand class]]); + XCTAssertEqual(command.manufacturerID, 0x4243); + XCTAssertTrue(command.includesThreeByteManufacturerID); +} + @end diff --git a/Source/MIKMIDISystemExclusiveCommand.m b/Source/MIKMIDISystemExclusiveCommand.m index 9606888a..220fa7e4 100644 --- a/Source/MIKMIDISystemExclusiveCommand.m +++ b/Source/MIKMIDISystemExclusiveCommand.m @@ -74,7 +74,7 @@ + (instancetype)systemExclusiveCommandWithManufacturerID:(UInt32)manufacturerID sysexData:(NSData *)sysexData timestamp:(nullable NSDate *)timestamp { - MIKMutableMIDISystemExclusiveCommand *result = [[MIKMutableMIDISystemExclusiveCommand alloc] init]; + MIKMutableMIDISystemExclusiveCommand *result = [[MIKMutableMIDISystemExclusiveCommand alloc] initWithMIDIPacket:NULL]; result.manufacturerID = manufacturerID; result.sysexChannel = sysexChannel; result.sysexData = sysexData; @@ -91,6 +91,7 @@ - (id)initWithMIDIPacket:(MIDIPacket *)packet UInt8 firstByte = self.dataByte1; if (firstByte == 0) { _has3ByteManufacturerID = YES; + _threeByteManufacturerIDInInternalData = YES; if ([self.internalData length] < 4) [self.internalData increaseLengthBy:4-[self.internalData length]]; } } From 06679c79dd0070a47e23d7f763daf7f69fdc9320 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Tue, 9 Feb 2021 23:19:42 -0700 Subject: [PATCH 33/53] Issue #310: Add new memberwise convenience method to MIKMIDIPitchBendChangeCommand --- Framework/MIKMIDI Tests/MIKMIDICommandTests.m | 33 +++++++++++++++++++ Source/MIKMIDIPitchBendChangeCommand.h | 10 ++++++ Source/MIKMIDIPitchBendChangeCommand.m | 13 +++++++- 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/Framework/MIKMIDI Tests/MIKMIDICommandTests.m b/Framework/MIKMIDI Tests/MIKMIDICommandTests.m index 5772bbd1..4cca33a1 100644 --- a/Framework/MIKMIDI Tests/MIKMIDICommandTests.m +++ b/Framework/MIKMIDI Tests/MIKMIDICommandTests.m @@ -66,6 +66,39 @@ - (void)testChannelPressureCommand XCTAssertEqual(mutableCommand.pressure, 27, @"Setting the pressure on a MIKMutableMIDIChannelPressureCommand instance failed."); } +- (void)testPitchBendChangeCommand +{ + MIDIPacket packet = MIKMIDIPacketCreate(0, 1, @[@0xef]); + XCTAssertTrue([[MIKMIDICommand commandWithMIDIPacket:&packet] isKindOfClass:[MIKMIDIPitchBendChangeCommand class]]); + + Class immutableClass = [MIKMIDIPitchBendChangeCommand class]; + Class mutableClass = [MIKMutableMIDIPitchBendChangeCommand class]; + + MIKMIDIPitchBendChangeCommand *command = [[immutableClass alloc] init]; + XCTAssert([command isMemberOfClass:[immutableClass class]], @"[[MIKMIDIPitchBendChangeCommand alloc] init] did not return an MIKMIDIPitchBendChangeCommand instance."); + XCTAssert([[MIKMIDICommand commandForCommandType:MIKMIDICommandTypePitchWheelChange] isMemberOfClass:[immutableClass class]], @"[MIKMIDICommand commandForCommandType:MIKMIDICommandTypeSystemExclusive] did not return an MIKMIDIPitchBendChangeCommand instance."); + XCTAssert([[command copy] isMemberOfClass:[immutableClass class]], @"[MIKMIDIPitchBendChangeCommand copy] did not return an MIKMIDIPitchBendChangeCommand instance."); + XCTAssertEqual(command.commandType, MIKMIDICommandTypePitchWheelChange, @"[[MIKMIDIPitchBendChangeCommand alloc] init] produced a command instance with the wrong command type."); + XCTAssertEqual(command.data.length, 3, "MIKMIDIPitchBendChangeCommand had an incorrect data length %@ (should be 3)", @(command.data.length)); + + MIKMutableMIDIPitchBendChangeCommand *mutableCommand = [command mutableCopy]; + XCTAssert([mutableCommand isMemberOfClass:[mutableClass class]], @"-[MIKMIDIPitchBendChangeCommand mutableCopy] did not return an mutableClass instance."); + XCTAssert([[mutableCommand copy] isMemberOfClass:[immutableClass class]], @"-[mutableClass mutableCopy] did not return an MIKMIDIPitchBendChangeCommand instance."); + + NSDate *date = [NSDate date]; + MIKMIDIPitchBendChangeCommand *convenienceTest = [MIKMIDIPitchBendChangeCommand pitchBendChangeCommandWithPitchChange:42 channel:2 timestamp:date]; + XCTAssertNotNil(convenienceTest); + XCTAssert([convenienceTest isMemberOfClass:[immutableClass class]], @"[MIKMIDIPitchBendChangeCommand pitchBendChangeCommandWithPitchChange:...] did not return an MIKMIDIPitchBendChangeCommand instance."); + XCTAssertEqual(convenienceTest.pitchChange, 42); + XCTAssertEqual(convenienceTest.channel, 2); + + MIKMutableMIDIPitchBendChangeCommand *mutableConvenienceTest = [MIKMutableMIDIPitchBendChangeCommand pitchBendChangeCommandWithPitchChange:42 channel:2 timestamp:date]; + XCTAssertNotNil(mutableConvenienceTest); + XCTAssert([mutableConvenienceTest isMemberOfClass:[mutableClass class]], @"[MIKMutableMIDIPitchBendChangeCommand pitchBendChangeCommandWithPitchChange:...] did not return an MIKMutableMIDIPitchBendChangeCommand instance."); + XCTAssertEqual(mutableConvenienceTest.pitchChange, 42); + XCTAssertEqual(mutableConvenienceTest.channel, 2); +} + - (void)testKeepAliveCommand { MIDIPacket packet = MIKMIDIPacketCreate(0, 1, @[@0xfe]); diff --git a/Source/MIKMIDIPitchBendChangeCommand.h b/Source/MIKMIDIPitchBendChangeCommand.h index b99dc0b6..9b3d81f8 100644 --- a/Source/MIKMIDIPitchBendChangeCommand.h +++ b/Source/MIKMIDIPitchBendChangeCommand.h @@ -18,6 +18,16 @@ NS_ASSUME_NONNULL_BEGIN */ @interface MIKMIDIPitchBendChangeCommand : MIKMIDIChannelVoiceCommand +/** + * Convenience method for creating a pitch bend change command. + * + * @param pitchChange The pitch change for the command. Valid range: 0-16383, center (no pitch change) at 8192. + * @param channel The channel for the command. Must be between 0 and 15. + * @param timestamp The timestamp for the command. Pass nil to use the current date/time. + * @return An initialized MIKMIDIChannelPressureCommand instance. + */ ++ (instancetype)pitchBendChangeCommandWithPitchChange:(UInt16)pitchChange channel:(UInt8)channel timestamp:(nullable NSDate *)timestamp; + /** * A 14-bit value indicating the pitch bend. * Center is 0x2000 (8192). diff --git a/Source/MIKMIDIPitchBendChangeCommand.m b/Source/MIKMIDIPitchBendChangeCommand.m index 00763ccc..04cb911c 100644 --- a/Source/MIKMIDIPitchBendChangeCommand.m +++ b/Source/MIKMIDIPitchBendChangeCommand.m @@ -31,6 +31,17 @@ - (NSString *)additionalCommandDescription return [NSString stringWithFormat:@"pitch change: %u", (unsigned)self.pitchChange]; } ++ (instancetype)pitchBendChangeCommandWithPitchChange:(UInt16)pitchChange channel:(UInt8)channel timestamp:(nullable NSDate *)timestamp +{ + MIKMutableMIDIPitchBendChangeCommand *result = [[MIKMutableMIDIPitchBendChangeCommand alloc] init]; + result.pitchChange = pitchChange; + result.channel = channel; + result.timestamp = timestamp ?: [NSDate date]; + + return [self isMutable] ? result : [result copy]; +} + + #pragma mark - Properties - (UInt16)pitchChange @@ -84,4 +95,4 @@ - (void)setCommandType:(MIKMIDICommandType)commandType data[0] &= 0x0F | (commandType & 0xF0); // Need to avoid changing channel } -@end \ No newline at end of file +@end From f5f218dd3edaf5d605eb00114242646e2d6134c4 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 15 Dec 2021 21:45:41 -0700 Subject: [PATCH 34/53] Issue #324: Add specialized -isEqual: method to MIKMIDIEvent --- Source/MIKMIDIEvent.h | 8 ++++++++ Source/MIKMIDIEvent.m | 12 ++++++++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/Source/MIKMIDIEvent.h b/Source/MIKMIDIEvent.h index a3fa7aae..b404bfe6 100644 --- a/Source/MIKMIDIEvent.h +++ b/Source/MIKMIDIEvent.h @@ -153,6 +153,14 @@ NS_ASSUME_NONNULL_BEGIN */ - (nullable instancetype)initWithTimeStamp:(MusicTimeStamp)timeStamp midiEventType:(MIKMIDIEventType)eventType data:(nullable NSData *)data NS_DESIGNATED_INITIALIZER; +/** + * Compares two events for equality. + * @param otherEvent The event with which to compare the receiver. + * + * @return YES if the events have the same type, timeStamp, and data, NO otherwise. + */ +- (BOOL)isEqualToEvent:(MIKMIDIEvent *)otherEvent; + /** * The MIDI event type. */ diff --git a/Source/MIKMIDIEvent.m b/Source/MIKMIDIEvent.m index 95eefbb9..77e1bba0 100644 --- a/Source/MIKMIDIEvent.m +++ b/Source/MIKMIDIEvent.m @@ -85,14 +85,18 @@ - (NSString *)description #pragma mark - Equality +- (BOOL)isEqualToEvent:(MIKMIDIEvent *)otherEvent +{ + if (otherEvent.eventType != self.eventType) return NO; + return (self.timeStamp == otherEvent.timeStamp && [self.internalData isEqualToData:otherEvent.internalData]); +} + - (BOOL)isEqual:(id)object { if (object == self) return YES; if (![object isKindOfClass:[MIKMIDIEvent class]]) return NO; - - MIKMIDIEvent *otherEvent = (MIKMIDIEvent *)object; - if (otherEvent.eventType != self.eventType) return NO; - return (self.timeStamp == otherEvent.timeStamp && [self.internalData isEqualToData:otherEvent.internalData]); + + return [self isEqualToEvent:(MIKMIDIEvent *)object]; } - (NSUInteger)hash From 122c8ae5392a0e988ad04b5eed28c0b4c1806c84 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 15 Dec 2021 21:46:25 -0700 Subject: [PATCH 35/53] Issue #324: Implement NSCopying in MIKMIDISequence and add associated test --- .../MIKMIDI Tests/MIKMIDISequenceTests.m | 25 ++++++++++++++ Source/MIKMIDISequence.h | 2 +- Source/MIKMIDISequence.m | 33 +++++++++++++++++++ 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/Framework/MIKMIDI Tests/MIKMIDISequenceTests.m b/Framework/MIKMIDI Tests/MIKMIDISequenceTests.m index 4bae766d..1a5d5299 100644 --- a/Framework/MIKMIDI Tests/MIKMIDISequenceTests.m +++ b/Framework/MIKMIDI Tests/MIKMIDISequenceTests.m @@ -189,6 +189,31 @@ - (void)testSetTimeSignature [self.sequence setTimeSignature:MIKMIDITimeSignatureMake(2, 4) atTimeStamp:0]; } +- (void)testCopyingSequence +{ + NSBundle *bundle = [NSBundle bundleForClass:[self class]]; + NSURL *testMIDIFileURL = [bundle URLForResource:@"Parallax-Loader" withExtension:@"mid"]; + NSError *error = nil; + MIKMIDISequence *sequence = [MIKMIDISequence sequenceWithFileAtURL:testMIDIFileURL convertMIDIChannelsToTracks:NO error:&error]; + XCTAssertNotNil(sequence); + + MIKMIDISequence *copiedSequence = [sequence copy]; + XCTAssertNotIdentical(sequence, copiedSequence, @"Copied sequence was same instance as original"); + XCTAssertEqual(sequence.tracks.count, copiedSequence.tracks.count); + XCTAssertEqual(sequence.length, copiedSequence.length); + XCTAssertEqual(sequence.durationInSeconds, copiedSequence.durationInSeconds); + + NSMutableArray *allTracks = [NSMutableArray arrayWithObject:sequence.tempoTrack]; + [allTracks addObjectsFromArray:sequence.tracks]; + NSMutableArray *allCopiedTracks = [NSMutableArray arrayWithObject:copiedSequence.tempoTrack]; + [allCopiedTracks addObjectsFromArray:copiedSequence.tracks]; + for (NSUInteger i=0; i /** * Creates and initializes a new instance of MIKMIDISequence. diff --git a/Source/MIKMIDISequence.m b/Source/MIKMIDISequence.m index 70cfb7a2..dded4fb1 100644 --- a/Source/MIKMIDISequence.m +++ b/Source/MIKMIDISequence.m @@ -33,6 +33,8 @@ @interface MIKMIDISequence () @property (nonatomic, strong) NSMutableArray *internalTracks; @property (nonatomic) MusicTimeStamp lengthDefinedByTracks; +@property (nonatomic, getter=isLengthUpdatingDisabled) BOOL lengthUpdatingDisabled; + @end @@ -185,6 +187,27 @@ - (void)dealloc if (err) NSLog(@"DisposeMusicSequence() failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__); } +#pragma mark - NSCopying + +- (id)copyWithZone:(NSZone *)zone +{ + NSError *error = nil; + MIKMIDISequence *newSequence = [MIKMIDISequence sequence]; + newSequence.lengthUpdatingDisabled = YES; + [newSequence.tempoTrack copyEventsFromMIDITrack:self.tempoTrack fromTimeStamp:0 toTimeStamp:self.tempoTrack.length andInsertAtTimeStamp:0]; + for (MIKMIDITrack *track in self.tracks) { + MIKMIDITrack *newTrack = [newSequence addTrackWithError:&error]; + if (!newTrack) { + NSLog(@"Error creating new track during copy of %@: %@", self, error); + return nil; + } + [newTrack copyEventsFromMIDITrack:track fromTimeStamp:0 toTimeStamp:track.length andInsertAtTimeStamp:0]; + } + + newSequence.lengthUpdatingDisabled = NO; + return newSequence; +} + #pragma mark - Sequencer Synchronization - (void)dispatchSyncToSequencerProcessingQueueAsNeeded:(void (^)(void))block @@ -515,6 +538,16 @@ - (NSData *)dataValue return (__bridge_transfer NSData *)data; } +- (void)setLengthUpdatingDisabled:(BOOL)lengthUpdatingDisabled +{ + if (lengthUpdatingDisabled != _lengthUpdatingDisabled) { + _lengthUpdatingDisabled = lengthUpdatingDisabled; + if (!_lengthUpdatingDisabled) { + [self updateLengthDefinedByTracks]; + } + } +} + #pragma mark - Deprecated + (instancetype)sequenceWithData:(NSData *)data From b49eb8c5b7716312ae3fb1e320a5733102c58e8a Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 15 Dec 2021 21:47:16 -0700 Subject: [PATCH 36/53] Issue #324: Improve performance of copying events into a track, along with performance test for sequence copying and updated baselines --- .../MIKMIDI Tests/MIKMIDISequenceTests.m | 13 ++++++++ ...3C4B92F8-480E-48B6-B83C-917B91EB0717.plist | 32 +++++++++++++++++++ ...C1C9286E-763A-4210-B3E7-DF2205A6AA20.plist | 10 +++--- ...C98DAFC3-AC32-4BFB-848B-A1B88CB141E1.plist | 2 +- .../Info.plist | 24 ++++++++++++++ Source/MIKMIDITrack.m | 5 ++- 6 files changed, 77 insertions(+), 9 deletions(-) create mode 100644 Framework/MIKMIDI.xcodeproj/xcshareddata/xcbaselines/9D4DF1391AAB57430065F004.xcbaseline/3C4B92F8-480E-48B6-B83C-917B91EB0717.plist diff --git a/Framework/MIKMIDI Tests/MIKMIDISequenceTests.m b/Framework/MIKMIDI Tests/MIKMIDISequenceTests.m index 1a5d5299..e5e14f9c 100644 --- a/Framework/MIKMIDI Tests/MIKMIDISequenceTests.m +++ b/Framework/MIKMIDI Tests/MIKMIDISequenceTests.m @@ -214,6 +214,19 @@ - (void)testCopyingSequence XCTAssertEqualObjects(track.events, copiedTrack.events); } } + +- (void)testCopyingSequencePerformance +{ + NSBundle *bundle = [NSBundle bundleForClass:[self class]]; + NSURL *testMIDIFileURL = [bundle URLForResource:@"Parallax-Loader" withExtension:@"mid"]; + NSError *error = nil; + MIKMIDISequence *sequence = [MIKMIDISequence sequenceWithFileAtURL:testMIDIFileURL convertMIDIChannelsToTracks:NO error:&error]; + XCTAssertNotNil(sequence); + [self measureBlock:^{ + [sequence copy]; + }]; +} + #pragma mark - KVO - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context diff --git a/Framework/MIKMIDI.xcodeproj/xcshareddata/xcbaselines/9D4DF1391AAB57430065F004.xcbaseline/3C4B92F8-480E-48B6-B83C-917B91EB0717.plist b/Framework/MIKMIDI.xcodeproj/xcshareddata/xcbaselines/9D4DF1391AAB57430065F004.xcbaseline/3C4B92F8-480E-48B6-B83C-917B91EB0717.plist new file mode 100644 index 00000000..9ed3b56a --- /dev/null +++ b/Framework/MIKMIDI.xcodeproj/xcshareddata/xcbaselines/9D4DF1391AAB57430065F004.xcbaseline/3C4B92F8-480E-48B6-B83C-917B91EB0717.plist @@ -0,0 +1,32 @@ + + + + + classNames + + MIKMIDISequenceTests + + testCopyingSequencePerformance + + com.apple.XCTPerformanceMetric_WallClockTime + + baselineAverage + 0.066800 + baselineIntegrationDisplayName + Local Baseline + + + testMIDIFileReadPerformance + + com.apple.XCTPerformanceMetric_WallClockTime + + baselineAverage + 0.056909 + baselineIntegrationDisplayName + Local Baseline + + + + + + diff --git a/Framework/MIKMIDI.xcodeproj/xcshareddata/xcbaselines/9D4DF1391AAB57430065F004.xcbaseline/C1C9286E-763A-4210-B3E7-DF2205A6AA20.plist b/Framework/MIKMIDI.xcodeproj/xcshareddata/xcbaselines/9D4DF1391AAB57430065F004.xcbaseline/C1C9286E-763A-4210-B3E7-DF2205A6AA20.plist index 66738b5a..46b9304e 100644 --- a/Framework/MIKMIDI.xcodeproj/xcshareddata/xcbaselines/9D4DF1391AAB57430065F004.xcbaseline/C1C9286E-763A-4210-B3E7-DF2205A6AA20.plist +++ b/Framework/MIKMIDI.xcodeproj/xcshareddata/xcbaselines/9D4DF1391AAB57430065F004.xcbaseline/C1C9286E-763A-4210-B3E7-DF2205A6AA20.plist @@ -11,7 +11,7 @@ com.apple.XCTPerformanceMetric_WallClockTime baselineAverage - 0.05 + 0.050000 baselineIntegrationDisplayName Local Baseline @@ -21,11 +21,11 @@ com.apple.XCTPerformanceMetric_WallClockTime baselineAverage - 1.09 + 1.090000 baselineIntegrationDisplayName Nov 9, 2017, 3:28:48 PM maxPercentRelativeStandardDeviation - 25 + 25.000000 @@ -36,11 +36,11 @@ com.apple.XCTPerformanceMetric_WallClockTime baselineAverage - 0.253 + 0.253000 baselineIntegrationDisplayName Nov 9, 2017, 3:28:56 PM maxPercentRelativeStandardDeviation - 10 + 10.000000 diff --git a/Framework/MIKMIDI.xcodeproj/xcshareddata/xcbaselines/9D4DF1391AAB57430065F004.xcbaseline/C98DAFC3-AC32-4BFB-848B-A1B88CB141E1.plist b/Framework/MIKMIDI.xcodeproj/xcshareddata/xcbaselines/9D4DF1391AAB57430065F004.xcbaseline/C98DAFC3-AC32-4BFB-848B-A1B88CB141E1.plist index 6e732abd..09693cf5 100644 --- a/Framework/MIKMIDI.xcodeproj/xcshareddata/xcbaselines/9D4DF1391AAB57430065F004.xcbaseline/C98DAFC3-AC32-4BFB-848B-A1B88CB141E1.plist +++ b/Framework/MIKMIDI.xcodeproj/xcshareddata/xcbaselines/9D4DF1391AAB57430065F004.xcbaseline/C98DAFC3-AC32-4BFB-848B-A1B88CB141E1.plist @@ -11,7 +11,7 @@ com.apple.XCTPerformanceMetric_WallClockTime baselineAverage - 1.6332 + 1.633200 baselineIntegrationDisplayName Nov 4, 2019 at 10:50:41 PM diff --git a/Framework/MIKMIDI.xcodeproj/xcshareddata/xcbaselines/9D4DF1391AAB57430065F004.xcbaseline/Info.plist b/Framework/MIKMIDI.xcodeproj/xcshareddata/xcbaselines/9D4DF1391AAB57430065F004.xcbaseline/Info.plist index 91894063..efab976f 100644 --- a/Framework/MIKMIDI.xcodeproj/xcshareddata/xcbaselines/9D4DF1391AAB57430065F004.xcbaseline/Info.plist +++ b/Framework/MIKMIDI.xcodeproj/xcshareddata/xcbaselines/9D4DF1391AAB57430065F004.xcbaseline/Info.plist @@ -4,6 +4,30 @@ runDestinationsByUUID + 3C4B92F8-480E-48B6-B83C-917B91EB0717 + + localComputer + + busSpeedInMHz + 0 + cpuCount + 1 + cpuKind + Apple M1 Max + cpuSpeedInMHz + 0 + logicalCPUCoresPerPackage + 10 + modelCode + MacBookPro18,2 + physicalCPUCoresPerPackage + 10 + platformIdentifier + com.apple.platform.macosx + + targetArchitecture + arm64 + C1C9286E-763A-4210-B3E7-DF2205A6AA20 localComputer diff --git a/Source/MIKMIDITrack.m b/Source/MIKMIDITrack.m index d6d35f0c..da32c028 100644 --- a/Source/MIKMIDITrack.m +++ b/Source/MIKMIDITrack.m @@ -557,9 +557,8 @@ - (void)addInternalEventsObject:(MIKMIDIEvent *)event - (void)addInternalEvents:(NSSet *)events { - for (MIKMIDIEvent *event in events) { - [self addInternalEventsObject:[event copy]]; - } + [self.internalEvents addObjectsFromArray:[events allObjects]]; + self.sortedEventsCache = nil; } - (void)removeInternalEventsObject:(MIKMIDIEvent *)event From 702d48525704e97956c6b14bbe852bdf0f52cb82 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Sun, 13 Feb 2022 17:09:39 -0700 Subject: [PATCH 37/53] Issue #327: Add new capability to MIKMIDICommand creation machinery to allow specializing command subclass initialization based on MIDI packet contents, not just command type --- Framework/MIKMIDI Tests/MIKMIDICommandTests.m | 14 +++ Framework/MIKMIDI.xcodeproj/project.pbxproj | 20 +++++ Source/MIKMIDICommand.m | 89 ++++++++++++++----- Source/MIKMIDICommand_SubclassMethods.h | 29 ++++++ 4 files changed, 130 insertions(+), 22 deletions(-) diff --git a/Framework/MIKMIDI Tests/MIKMIDICommandTests.m b/Framework/MIKMIDI Tests/MIKMIDICommandTests.m index 4cca33a1..b251fea4 100644 --- a/Framework/MIKMIDI Tests/MIKMIDICommandTests.m +++ b/Framework/MIKMIDI Tests/MIKMIDICommandTests.m @@ -119,6 +119,20 @@ - (void)testKeepAliveCommand XCTAssert([[mutableCommand copy] isMemberOfClass:[immutableClass class]], @"-[mutableClass mutableCopy] did not return an MIKMIDISystemKeepAliveCommand instance."); } +- (void)testMMCLocateCommand +{ + NSArray *bytes = @[@(0xf0), @(0x7f), @(0x7f), @(0x06), @(0x44), @(0x06), @(0x01), @(0x21), @(0x00), @(0x00), @(0x00), @(0x00), @(0xf7)]; + MIDIPacket packet = MIKMIDIPacketCreate(0, bytes.count, bytes); + + MIKMIDICommand *command = [MIKMIDICommand commandWithMIDIPacket:&packet]; + XCTAssertTrue([command isMemberOfClass:[MIKMIDIMachineControlLocateCommand class]]); + + bytes = @[@(0xf0), @(0x7f), @(0x7f), @(0x06), @(0x45), @(0x06), @(0x01), @(0x21), @(0x00), @(0x00), @(0x00), @(0x00), @(0xf7)]; + packet = MIKMIDIPacketCreate(0, bytes.count, bytes); + command = [MIKMIDICommand commandWithMIDIPacket:&packet]; // Should not be a locate command because message type byte is 0x45, not 0x44 + XCTAssertFalse([command isMemberOfClass:[MIKMIDIMachineControlLocateCommand class]]); +} + - (void)testMultipleCommandTypesInOnePacket { MIKMIDINoteOnCommand *noteOn = [MIKMIDINoteOnCommand noteOnCommandWithNote:60 velocity:64 channel:0 timestamp:nil]; diff --git a/Framework/MIKMIDI.xcodeproj/project.pbxproj b/Framework/MIKMIDI.xcodeproj/project.pbxproj index 2524b965..01bbb013 100644 --- a/Framework/MIKMIDI.xcodeproj/project.pbxproj +++ b/Framework/MIKMIDI.xcodeproj/project.pbxproj @@ -413,6 +413,9 @@ 9D1D9C241BF542BB001377F7 /* MIKMIDICommandTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDICommandTests.m; sourceTree = ""; }; 9D1D9C261BF54D38001377F7 /* MIKMIDIChannelPressureCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIChannelPressureCommand.h; sourceTree = ""; }; 9D1D9C271BF54D38001377F7 /* MIKMIDIChannelPressureCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIChannelPressureCommand.m; sourceTree = ""; }; + 9D1FE4F327B07B00005317B2 /* MIKMIDIMachineControlLocateCommand.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMachineControlLocateCommand.h; sourceTree = ""; }; + 9D1FE4F427B07B00005317B2 /* MIKMIDIMachineControlLocateCommand.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMachineControlLocateCommand.m; sourceTree = ""; }; + 9D1FE4F827B07B34005317B2 /* MIKMIDIMachineControl.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMachineControl.h; sourceTree = ""; }; 9D2ED25E1AFBD062000325CC /* MIKMIDIResponderChainTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIResponderChainTests.m; sourceTree = ""; }; 9D4DF13A1AAB57430065F004 /* MIKMIDI Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "MIKMIDI Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 9D4DF13D1AAB57430065F004 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -586,6 +589,16 @@ name = Files; sourceTree = ""; }; + 9D1FE4F227B07AEC005317B2 /* MIDI Machine Control (MMC) */ = { + isa = PBXGroup; + children = ( + 9D1FE4F827B07B34005317B2 /* MIKMIDIMachineControl.h */, + 9D1FE4F327B07B00005317B2 /* MIKMIDIMachineControlLocateCommand.h */, + 9D1FE4F427B07B00005317B2 /* MIKMIDIMachineControlLocateCommand.m */, + ); + name = "MIDI Machine Control (MMC)"; + sourceTree = ""; + }; 9D4DF13B1AAB57430065F004 /* MIKMIDI Tests */ = { isa = PBXGroup; children = ( @@ -903,6 +916,7 @@ 9D74EF5C17A713A100BEE89F /* MIKMIDISystemExclusiveCommand.m */, 9D9F02A51FB5101500FE340E /* MIKMIDISystemKeepAliveCommand.h */, 9D9F02A61FB5101500FE340E /* MIKMIDISystemKeepAliveCommand.m */, + 9D1FE4F227B07AEC005317B2 /* MIDI Machine Control (MMC) */, ); name = Commands; sourceTree = ""; @@ -958,6 +972,7 @@ 9DEF1CA81AA6769E00E10273 /* MIKMIDIChannelEvent.h in Headers */, 9D8495221AA7773500C52475 /* MIKMIDIPolyphonicKeyPressureEvent.h in Headers */, 839D933219C3A2C9007589C3 /* MIKMIDIEvent_SubclassMethods.h in Headers */, + 9D1FE4FA27B07B34005317B2 /* MIKMIDIMachineControl.h in Headers */, 9D5946CE1A9FA7C800B5ACFB /* MIKMIDISynthesizer_SubclassMethods.h in Headers */, 839D935519C3A2F5007589C3 /* MIKMIDIMetaInstrumentNameEvent.h in Headers */, 9D9F029E1FB50B1900FE340E /* MIKMIDI-Prefix.pch in Headers */, @@ -1002,6 +1017,7 @@ 833B73DE1A26346F00E0CC9F /* MIKMIDIClock.h in Headers */, 9D76DCEC1A9E52DB00A24C16 /* MIKMIDITrack_Protected.h in Headers */, 9D74EF8E17A713A100BEE89F /* MIKMIDISystemExclusiveCommand.h in Headers */, + 9D1FE4F527B07B00005317B2 /* MIKMIDIMachineControlLocateCommand.h in Headers */, 9DF99E791831841A004EE5F4 /* MIKMIDICommandThrottler.h in Headers */, 9D74EF9017A713A100BEE89F /* MIKMIDISystemMessageCommand.h in Headers */, 83BC19BB1A23CD0D004F384F /* MIKMIDIMetronome.h in Headers */, @@ -1023,6 +1039,7 @@ 9DAF8B5D1A7B007300F46528 /* MIKMIDIClientSourceEndpoint.h in Headers */, 9DAF8B7A1A7B00A700F46528 /* MIKMIDINoteEvent.h in Headers */, 9DAF8B6C1A7B00A700F46528 /* MIKMIDITrack.h in Headers */, + 9D1FE4FF27B07BA6005317B2 /* MIKMIDIMachineControlLocateCommand.h in Headers */, 9DEBD03C1F7085DB00676C42 /* MIKMIDINoteCommand.h in Headers */, 9DAF8B661A7B008A00F46528 /* MIKMIDISystemExclusiveCommand.h in Headers */, 9D0895F41B0D2A4700A5872E /* MIKMIDIMappableResponder.h in Headers */, @@ -1077,6 +1094,7 @@ 9D9FBCCC1B4A29A5009A7936 /* MIKMIDIPort_SubclassMethods.h in Headers */, 9DBEBD5A1AAA21C400E59734 /* MIKMIDIEvent_SubclassMethods.h in Headers */, 9DAF8B561A7B007300F46528 /* MIKMIDIDevice.h in Headers */, + 9D1FE4FE27B07B9D005317B2 /* MIKMIDIMachineControl.h in Headers */, 9DAF8B761A7B00A700F46528 /* MIKMIDIMetaSequenceEvent.h in Headers */, 9DAF8B791A7B00A700F46528 /* MIKMIDIMetaTrackSequenceNameEvent.h in Headers */, 9DAF8B6F1A7B00A700F46528 /* MIKMIDIMetaCopyrightEvent.h in Headers */, @@ -1286,6 +1304,7 @@ 839D936419C3A2F5007589C3 /* MIKMIDIMetaTrackSequenceNameEvent.m in Sources */, 9D74EF6717A713A100BEE89F /* MIKMIDICommand.m in Sources */, 9D74EF6A17A713A100BEE89F /* MIKMIDIControlChangeCommand.m in Sources */, + 9D1FE4F627B07B00005317B2 /* MIKMIDIMachineControlLocateCommand.m in Sources */, 839D935819C3A2F5007589C3 /* MIKMIDIMetaKeySignatureEvent.m in Sources */, 839D937419C3A319007589C3 /* MIKMIDITempoEvent.m in Sources */, 9D74EF6C17A713A100BEE89F /* MIKMIDIDestinationEndpoint.m in Sources */, @@ -1360,6 +1379,7 @@ 9DAF8B211A7AFF5900F46528 /* MIKMIDIDevice.m in Sources */, 9DAF8B221A7AFF5900F46528 /* MIKMIDIEntity.m in Sources */, 9DAF8B231A7AFF5900F46528 /* MIKMIDIPort.m in Sources */, + 9D1FE4F727B07B04005317B2 /* MIKMIDIMachineControlLocateCommand.m in Sources */, 9DAF8B241A7AFF5900F46528 /* MIKMIDIInputPort.m in Sources */, 9DED4E391AA90CA700DA8356 /* MIKMIDIPitchBendChangeCommand.m in Sources */, 9DAF8B251A7AFF5900F46528 /* MIKMIDIOutputPort.m in Sources */, diff --git a/Source/MIKMIDICommand.m b/Source/MIKMIDICommand.m index d7078f51..42114a7b 100644 --- a/Source/MIKMIDICommand.m +++ b/Source/MIKMIDICommand.m @@ -35,6 +35,7 @@ + (void)registerSubclass:(Class)subclass; + (BOOL)isMutable { return NO; } + (BOOL)supportsMIDICommandType:(MIKMIDICommandType)type { return [[self supportedMIDICommandTypes] containsObject:@(type)]; } ++ (MIKMIDICommandPacketHandlingIntent)handlingIntentForMIDIPacket:(MIDIPacket *)packet { return MIKMIDICommandPacketHandlingIntentAccept; } + (NSArray *)supportedMIDICommandTypes { return @[]; } + (Class)immutableCounterpartClass; { return [MIKMIDICommand class]; } + (Class)mutableCounterpartClass; { return [MIKMutableMIDICommand class]; } @@ -43,8 +44,7 @@ + (instancetype)commandWithMIDIPacket:(MIDIPacket *)packet; { Class subclass = Nil; if (packet) { - MIKMIDICommandType commandType = packet->data[0]; - subclass = [[self class] subclassForCommandType:commandType]; + subclass = [[self class] subclassForMIDIPacket:packet]; } if (!subclass) { subclass = self; } @@ -87,7 +87,7 @@ + (NSArray *)commandsWithMIDIPacket:(MIDIPacket *)inputPacket + (instancetype)commandForCommandType:(MIKMIDICommandType)commandType; // Most useful for mutable commands { - Class subclass = [[self class] subclassForCommandType:commandType]; + Class subclass = [[[self class] allSubclassesForCommandType:commandType] firstObject]; if (!subclass) subclass = self; if ([self isMutable]) subclass = [subclass mutableCounterpartClass]; return [[subclass alloc] init]; @@ -150,26 +150,71 @@ - (BOOL)isEqualToCommand:(MIKMIDICommand *)command #pragma mark - Private -+ (Class)subclassForCommandType:(MIKMIDICommandType)commandType ++ (NSArray *)allSubclassesForCommandType:(MIKMIDICommandType)commandType { - Class result = nil; - for (Class subclass in registeredMIKMIDICommandSubclasses) { - if ([[subclass supportedMIDICommandTypes] containsObject:@(commandType)]) { - result = subclass; - break; - } - } - if (!result) { - // Try again ignoring lower 4 bits - commandType |= 0x0f; - for (Class subclass in registeredMIKMIDICommandSubclasses) { - if ([[subclass supportedMIDICommandTypes] containsObject:@(commandType)]) { - result = subclass; - break; - } - } - } - return result; + NSMutableArray *result = [NSMutableArray array]; + for (Class subclass in registeredMIKMIDICommandSubclasses) { + if ([[subclass supportedMIDICommandTypes] containsObject:@(commandType)]) { + [result addObject:subclass]; + } + } + if (!result.count) { + // Try again ignoring lower 4 bits + commandType |= 0x0f; + for (Class subclass in registeredMIKMIDICommandSubclasses) { + if ([[subclass supportedMIDICommandTypes] containsObject:@(commandType)]) { + [result addObject:subclass]; + } + } + } + return result; +} + ++ (Class)subclassForMIDIPacket:(MIDIPacket *)packet +{ + MIKMIDICommandType commandType = packet->data[0]; + + NSArray *allSubclasses = [self allSubclassesForCommandType:commandType]; + NSMutableArray *subclasses = [NSMutableArray array]; + NSMutableArray *specificHandlingSubclasses = [NSMutableArray array]; + + for (Class subclass in allSubclasses) { + MIKMIDICommandPacketHandlingIntent intent = [subclass handlingIntentForMIDIPacket:packet]; + if (intent == MIKMIDICommandPacketHandlingIntentReject) { + continue; + } + [subclasses addObject:subclass]; + if (intent == MIKMIDICommandPacketHandlingIntentAcceptWithHigherPrecedence) { + [specificHandlingSubclasses addObject:subclass]; + } + } + + if (specificHandlingSubclasses.count > 1) { + NSData *packetData = [NSData dataWithBytes:packet->data length:packet->length]; + NSLog(@"[MIKMIDI] Warning: More than one subclass of MIKMIDICommand was found to handle MIDI message data (%@). Candidates are: %@. Which one is used is random/undefined. This is likely a bug, and should be reported to the maintainers of MIKMIDI.", packetData, specificHandlingSubclasses); + } + + if (specificHandlingSubclasses.count) { + subclasses = specificHandlingSubclasses; + } + + // Sort so that deepest subclass hierarchy children come first + NSArray *sortedSubclasses = [subclasses sortedArrayWithOptions:0 usingComparator:^NSComparisonResult(Class class1, Class class2) { + if ([class1 isEqualTo:class2]) { return NSOrderedSame; } + if ([class1 isSubclassOfClass:class2]) { return NSOrderedDescending; } + if ([class2 isSubclassOfClass:class1]) { return NSOrderedAscending; } + return NSOrderedAscending; + }]; + + // Return the first subclass that doesn't reject this MIDI packet + for (Class subclass in sortedSubclasses) { + if ([subclass handlingIntentForMIDIPacket:packet] == MIKMIDICommandPacketHandlingIntentReject) { + continue; + } + return subclass; + } + + return nil; } #pragma mark - NSCopying diff --git a/Source/MIKMIDICommand_SubclassMethods.h b/Source/MIKMIDICommand_SubclassMethods.h index 8101d175..aa1444b8 100644 --- a/Source/MIKMIDICommand_SubclassMethods.h +++ b/Source/MIKMIDICommand_SubclassMethods.h @@ -9,6 +9,21 @@ #import #import +/** + Used by MIKMIDICommand subclasses to communicate their desire to handle a specific + MIDIPacket. + + @see +[MIKMIDICommand handlingIntentForMIDIPacket:] + */ +typedef NS_ENUM(NSInteger, MIKMIDICommandPacketHandlingIntent) { + /** The receiver never wants to be initialized with the given MIDIPacket */ + MIKMIDICommandPacketHandlingIntentReject, + /** The receiver can be initialized with the given MIDIPacket but doesn't need precedence over other supporters of the same command type. The default. */ + MIKMIDICommandPacketHandlingIntentAccept, + /** The receiver implements special support for the passed-in MIDI packet and should be used to handle it instead of other supports (e.g. the superclass) */ + MIKMIDICommandPacketHandlingIntentAcceptWithHigherPrecedence, +}; + /** * These methods can be called and/or overridden by subclasses of MIKMIDICommand, but are not * otherwise part of the public interface to MIKMIDICommand. They should not be called directly @@ -41,6 +56,20 @@ */ + (MIKArrayOf(NSNumber *) *)supportedMIDICommandTypes; +/** + * Subclasses of MIKMIDICommand can implement this to indicate that they want to handle + * a specific MIDI packet, even if other commands (e.g. superclass(es)) can also handle the + * more general MIDI message type. For example, this is used for the MIDI Machine Control-spefic + * subclasses of MIKMIDISystemExclusiveCommand to indicate that they should take precedence + * over MIKMIDISystemExclusiveCommand itself for their specific MMC sysex messages. + * + * Note that this method will only be called if the receiver is registered as a subclass with MIKMIDICommand + * and has already returned the MIDI command type represented by packet from its +supportedMIDICommandTypes method. + * + * @return One of the values in MIKMIDICommandPacketHandlingIntent + */ ++ (MIKMIDICommandPacketHandlingIntent)handlingIntentForMIDIPacket:(MIDIPacket *)packet; + /** * The immutable counterpart class of the receiver. * From 1d74f601586d0f27a1c9275ff3fdd69ba86f7e51 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Sun, 13 Feb 2022 17:10:05 -0700 Subject: [PATCH 38/53] Issue #327: Add basic MIKMIDIMachineControlLocateCommand subclass. WIP --- Framework/MIKMIDI Tests/MIKMIDICommandTests.m | 14 ------- .../MIKMIDIMachineControlTests.m | 41 +++++++++++++++++++ Framework/MIKMIDI.xcodeproj/project.pbxproj | 38 +++++++++++++---- Source/MIKMIDI.h | 1 + Source/MIKMIDICommand.m | 21 +++++----- Source/MIKMIDIMachineControl.h | 12 ++++++ Source/MIKMIDIMachineControlCommand.h | 21 ++++++++++ Source/MIKMIDIMachineControlCommand.m | 38 +++++++++++++++++ ...MIKMIDIMachineControlLocateTargetCommand.h | 17 ++++++++ ...MIKMIDIMachineControlLocateTargetCommand.m | 37 +++++++++++++++++ 10 files changed, 207 insertions(+), 33 deletions(-) create mode 100644 Framework/MIKMIDI Tests/MIKMIDIMachineControlTests.m create mode 100644 Source/MIKMIDIMachineControl.h create mode 100644 Source/MIKMIDIMachineControlCommand.h create mode 100644 Source/MIKMIDIMachineControlCommand.m create mode 100644 Source/MIKMIDIMachineControlLocateTargetCommand.h create mode 100644 Source/MIKMIDIMachineControlLocateTargetCommand.m diff --git a/Framework/MIKMIDI Tests/MIKMIDICommandTests.m b/Framework/MIKMIDI Tests/MIKMIDICommandTests.m index b251fea4..4cca33a1 100644 --- a/Framework/MIKMIDI Tests/MIKMIDICommandTests.m +++ b/Framework/MIKMIDI Tests/MIKMIDICommandTests.m @@ -119,20 +119,6 @@ - (void)testKeepAliveCommand XCTAssert([[mutableCommand copy] isMemberOfClass:[immutableClass class]], @"-[mutableClass mutableCopy] did not return an MIKMIDISystemKeepAliveCommand instance."); } -- (void)testMMCLocateCommand -{ - NSArray *bytes = @[@(0xf0), @(0x7f), @(0x7f), @(0x06), @(0x44), @(0x06), @(0x01), @(0x21), @(0x00), @(0x00), @(0x00), @(0x00), @(0xf7)]; - MIDIPacket packet = MIKMIDIPacketCreate(0, bytes.count, bytes); - - MIKMIDICommand *command = [MIKMIDICommand commandWithMIDIPacket:&packet]; - XCTAssertTrue([command isMemberOfClass:[MIKMIDIMachineControlLocateCommand class]]); - - bytes = @[@(0xf0), @(0x7f), @(0x7f), @(0x06), @(0x45), @(0x06), @(0x01), @(0x21), @(0x00), @(0x00), @(0x00), @(0x00), @(0xf7)]; - packet = MIKMIDIPacketCreate(0, bytes.count, bytes); - command = [MIKMIDICommand commandWithMIDIPacket:&packet]; // Should not be a locate command because message type byte is 0x45, not 0x44 - XCTAssertFalse([command isMemberOfClass:[MIKMIDIMachineControlLocateCommand class]]); -} - - (void)testMultipleCommandTypesInOnePacket { MIKMIDINoteOnCommand *noteOn = [MIKMIDINoteOnCommand noteOnCommandWithNote:60 velocity:64 channel:0 timestamp:nil]; diff --git a/Framework/MIKMIDI Tests/MIKMIDIMachineControlTests.m b/Framework/MIKMIDI Tests/MIKMIDIMachineControlTests.m new file mode 100644 index 00000000..b4c709b1 --- /dev/null +++ b/Framework/MIKMIDI Tests/MIKMIDIMachineControlTests.m @@ -0,0 +1,41 @@ +// +// MIKMIDIMachineControlTests.m +// MIKMIDI Tests +// +// Created by Andrew R Madsen on 2/13/22. +// Copyright © 2022 Mixed In Key. All rights reserved. +// + +#import +#import + +@interface MIKMIDIMachineControlTests : XCTestCase + +@end + +@implementation MIKMIDIMachineControlTests + +- (void)testGenericMIDIMachineControlCommand +{ + NSArray *bytes = @[@(0xf0), @(0x7f), @(0x7f), @(0x06), @(0x01)]; + MIDIPacket packet = MIKMIDIPacketCreate(0, bytes.count, bytes); + + MIKMIDICommand *command = [MIKMIDICommand commandWithMIDIPacket:&packet]; + XCTAssertTrue([command isMemberOfClass:[MIKMIDIMachineControlCommand class]]); +} + +- (void)testMMCLocateTargetCommand +{ + NSArray *bytes = @[@(0xf0), @(0x7f), @(0x7f), @(0x06), @(0x44), @(0x06), @(0x01), @(0x21), @(0x00), @(0x00), @(0x00), @(0x00), @(0xf7)]; + MIDIPacket packet = MIKMIDIPacketCreate(0, bytes.count, bytes); + + MIKMIDICommand *command = [MIKMIDICommand commandWithMIDIPacket:&packet]; + XCTAssertTrue([command isMemberOfClass:[MIKMIDIMachineControlLocateTargetCommand class]]); + + bytes = @[@(0xf0), @(0x7f), @(0x7f), @(0x06), @(0x45), @(0x06), @(0x01), @(0x21), @(0x00), @(0x00), @(0x00), @(0x00), @(0xf7)]; + packet = MIKMIDIPacketCreate(0, bytes.count, bytes); + command = [MIKMIDICommand commandWithMIDIPacket:&packet]; // Should not be a locate command because message type byte is 0x45, not 0x44 + XCTAssertFalse([command isMemberOfClass:[MIKMIDIMachineControlLocateTargetCommand class]]); + XCTAssertTrue([command isMemberOfClass:[MIKMIDIMachineControlCommand class]]); +} +@end diff --git a/Framework/MIKMIDI.xcodeproj/project.pbxproj b/Framework/MIKMIDI.xcodeproj/project.pbxproj index 01bbb013..c1817a90 100644 --- a/Framework/MIKMIDI.xcodeproj/project.pbxproj +++ b/Framework/MIKMIDI.xcodeproj/project.pbxproj @@ -84,6 +84,11 @@ 9D0895F31B0D2A4700A5872E /* MIKMIDIMappableResponder.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D0895F21B0D2A4700A5872E /* MIKMIDIMappableResponder.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D0895F41B0D2A4700A5872E /* MIKMIDIMappableResponder.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D0895F21B0D2A4700A5872E /* MIKMIDIMappableResponder.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D0E6B912370B3C900AEFFE0 /* MIKMIDIEventCachingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D0E6B902370B3C900AEFFE0 /* MIKMIDIEventCachingTests.m */; }; + 9D1C0D4127B9D71A00094AED /* MIKMIDIMachineControlCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D1C0D3F27B9D71A00094AED /* MIKMIDIMachineControlCommand.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9D1C0D4227B9D71A00094AED /* MIKMIDIMachineControlCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D1C0D3F27B9D71A00094AED /* MIKMIDIMachineControlCommand.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9D1C0D4327B9D71A00094AED /* MIKMIDIMachineControlCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D1C0D4027B9D71A00094AED /* MIKMIDIMachineControlCommand.m */; }; + 9D1C0D4427B9D71A00094AED /* MIKMIDIMachineControlCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D1C0D4027B9D71A00094AED /* MIKMIDIMachineControlCommand.m */; }; + 9D1C0D4627B9D76D00094AED /* MIKMIDIMachineControlTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D1C0D4527B9D76D00094AED /* MIKMIDIMachineControlTests.m */; }; 9D1D9C201BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D1D9C1E1BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D1D9C211BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D1D9C1E1BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D1D9C221BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D1D9C1F1BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.m */; }; @@ -93,6 +98,12 @@ 9D1D9C291BF54D38001377F7 /* MIKMIDIChannelPressureCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D1D9C261BF54D38001377F7 /* MIKMIDIChannelPressureCommand.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D1D9C2A1BF54D38001377F7 /* MIKMIDIChannelPressureCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D1D9C271BF54D38001377F7 /* MIKMIDIChannelPressureCommand.m */; }; 9D1D9C2B1BF54D38001377F7 /* MIKMIDIChannelPressureCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D1D9C271BF54D38001377F7 /* MIKMIDIChannelPressureCommand.m */; }; + 9D1FE4F527B07B00005317B2 /* MIKMIDIMachineControlLocateTargetCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D1FE4F327B07B00005317B2 /* MIKMIDIMachineControlLocateTargetCommand.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9D1FE4F627B07B00005317B2 /* MIKMIDIMachineControlLocateTargetCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D1FE4F427B07B00005317B2 /* MIKMIDIMachineControlLocateTargetCommand.m */; }; + 9D1FE4F727B07B04005317B2 /* MIKMIDIMachineControlLocateTargetCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D1FE4F427B07B00005317B2 /* MIKMIDIMachineControlLocateTargetCommand.m */; }; + 9D1FE4FA27B07B34005317B2 /* MIKMIDIMachineControl.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D1FE4F827B07B34005317B2 /* MIKMIDIMachineControl.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9D1FE4FE27B07B9D005317B2 /* MIKMIDIMachineControl.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D1FE4F827B07B34005317B2 /* MIKMIDIMachineControl.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9D1FE4FF27B07BA6005317B2 /* MIKMIDIMachineControlLocateTargetCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D1FE4F327B07B00005317B2 /* MIKMIDIMachineControlLocateTargetCommand.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D2ED25F1AFBD062000325CC /* MIKMIDIResponderChainTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D2ED25E1AFBD062000325CC /* MIKMIDIResponderChainTests.m */; }; 9D3781561AA407A7007A61BE /* MIKMIDIResponder.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D74EF5817A713A100BEE89F /* MIKMIDIResponder.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D4DF1401AAB57430065F004 /* MIKMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9D74EEA517A7129300BEE89F /* MIKMIDI.framework */; }; @@ -408,13 +419,16 @@ 9D0895ED1B0D29F200A5872E /* MIKMIDIMappingItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMappingItem.m; sourceTree = ""; }; 9D0895F21B0D2A4700A5872E /* MIKMIDIMappableResponder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMappableResponder.h; sourceTree = ""; }; 9D0E6B902370B3C900AEFFE0 /* MIKMIDIEventCachingTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIEventCachingTests.m; sourceTree = ""; }; + 9D1C0D3F27B9D71A00094AED /* MIKMIDIMachineControlCommand.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMachineControlCommand.h; sourceTree = ""; }; + 9D1C0D4027B9D71A00094AED /* MIKMIDIMachineControlCommand.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMachineControlCommand.m; sourceTree = ""; }; + 9D1C0D4527B9D76D00094AED /* MIKMIDIMachineControlTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMachineControlTests.m; sourceTree = ""; }; 9D1D9C1E1BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIPolyphonicKeyPressureCommand.h; sourceTree = ""; }; 9D1D9C1F1BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIPolyphonicKeyPressureCommand.m; sourceTree = ""; }; 9D1D9C241BF542BB001377F7 /* MIKMIDICommandTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDICommandTests.m; sourceTree = ""; }; 9D1D9C261BF54D38001377F7 /* MIKMIDIChannelPressureCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIChannelPressureCommand.h; sourceTree = ""; }; 9D1D9C271BF54D38001377F7 /* MIKMIDIChannelPressureCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIChannelPressureCommand.m; sourceTree = ""; }; - 9D1FE4F327B07B00005317B2 /* MIKMIDIMachineControlLocateCommand.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMachineControlLocateCommand.h; sourceTree = ""; }; - 9D1FE4F427B07B00005317B2 /* MIKMIDIMachineControlLocateCommand.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMachineControlLocateCommand.m; sourceTree = ""; }; + 9D1FE4F327B07B00005317B2 /* MIKMIDIMachineControlLocateTargetCommand.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMachineControlLocateTargetCommand.h; sourceTree = ""; }; + 9D1FE4F427B07B00005317B2 /* MIKMIDIMachineControlLocateTargetCommand.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMachineControlLocateTargetCommand.m; sourceTree = ""; }; 9D1FE4F827B07B34005317B2 /* MIKMIDIMachineControl.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMachineControl.h; sourceTree = ""; }; 9D2ED25E1AFBD062000325CC /* MIKMIDIResponderChainTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIResponderChainTests.m; sourceTree = ""; }; 9D4DF13A1AAB57430065F004 /* MIKMIDI Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "MIKMIDI Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -593,8 +607,10 @@ isa = PBXGroup; children = ( 9D1FE4F827B07B34005317B2 /* MIKMIDIMachineControl.h */, - 9D1FE4F327B07B00005317B2 /* MIKMIDIMachineControlLocateCommand.h */, - 9D1FE4F427B07B00005317B2 /* MIKMIDIMachineControlLocateCommand.m */, + 9D1C0D3F27B9D71A00094AED /* MIKMIDIMachineControlCommand.h */, + 9D1C0D4027B9D71A00094AED /* MIKMIDIMachineControlCommand.m */, + 9D1FE4F327B07B00005317B2 /* MIKMIDIMachineControlLocateTargetCommand.h */, + 9D1FE4F427B07B00005317B2 /* MIKMIDIMachineControlLocateTargetCommand.m */, ); name = "MIDI Machine Control (MMC)"; sourceTree = ""; @@ -605,6 +621,7 @@ 9DFF406D202E45A000562EC9 /* MIKMIDIInputPortTests.m */, 6609EF0B1EF300C400B4DAE5 /* MIKMIDISysexCoalescingTests.m */, 9D1D9C241BF542BB001377F7 /* MIKMIDICommandTests.m */, + 9D1C0D4527B9D76D00094AED /* MIKMIDIMachineControlTests.m */, 9DEBD0431F708C2200676C42 /* MIKMIDINoteCommandTests.m */, 9D8DC3CC202BBBFB00DDA4A8 /* MIKMIDIFourteenBitCCCommandTests.m */, 9DECB3C02035EE4100B8C7A8 /* MIKMIDISystemExclusiveCommandTests.m */, @@ -949,6 +966,7 @@ 9D74EF6417A713A100BEE89F /* MIKMIDIChannelVoiceCommand.h in Headers */, 9D74EF6617A713A100BEE89F /* MIKMIDICommand.h in Headers */, 9D9F02A71FB5101500FE340E /* MIKMIDISystemKeepAliveCommand.h in Headers */, + 9D1C0D4127B9D71A00094AED /* MIKMIDIMachineControlCommand.h in Headers */, 9D0895F31B0D2A4700A5872E /* MIKMIDIMappableResponder.h in Headers */, 9D74EF6917A713A100BEE89F /* MIKMIDIControlChangeCommand.h in Headers */, 9D8DC3D2202BD95000DDA4A8 /* MIKMIDITransmittable.h in Headers */, @@ -1017,7 +1035,7 @@ 833B73DE1A26346F00E0CC9F /* MIKMIDIClock.h in Headers */, 9D76DCEC1A9E52DB00A24C16 /* MIKMIDITrack_Protected.h in Headers */, 9D74EF8E17A713A100BEE89F /* MIKMIDISystemExclusiveCommand.h in Headers */, - 9D1FE4F527B07B00005317B2 /* MIKMIDIMachineControlLocateCommand.h in Headers */, + 9D1FE4F527B07B00005317B2 /* MIKMIDIMachineControlLocateTargetCommand.h in Headers */, 9DF99E791831841A004EE5F4 /* MIKMIDICommandThrottler.h in Headers */, 9D74EF9017A713A100BEE89F /* MIKMIDISystemMessageCommand.h in Headers */, 83BC19BB1A23CD0D004F384F /* MIKMIDIMetronome.h in Headers */, @@ -1039,7 +1057,7 @@ 9DAF8B5D1A7B007300F46528 /* MIKMIDIClientSourceEndpoint.h in Headers */, 9DAF8B7A1A7B00A700F46528 /* MIKMIDINoteEvent.h in Headers */, 9DAF8B6C1A7B00A700F46528 /* MIKMIDITrack.h in Headers */, - 9D1FE4FF27B07BA6005317B2 /* MIKMIDIMachineControlLocateCommand.h in Headers */, + 9D1FE4FF27B07BA6005317B2 /* MIKMIDIMachineControlLocateTargetCommand.h in Headers */, 9DEBD03C1F7085DB00676C42 /* MIKMIDINoteCommand.h in Headers */, 9DAF8B661A7B008A00F46528 /* MIKMIDISystemExclusiveCommand.h in Headers */, 9D0895F41B0D2A4700A5872E /* MIKMIDIMappableResponder.h in Headers */, @@ -1107,6 +1125,7 @@ 9DAF8B6E1A7B00A700F46528 /* MIKMIDIEventIterator.h in Headers */, 9DB366F11A964C55001D1CF3 /* MIKMIDISynthesizer.h in Headers */, 9DAF8B571A7B007300F46528 /* MIKMIDIEntity.h in Headers */, + 9D1C0D4227B9D71A00094AED /* MIKMIDIMachineControlCommand.h in Headers */, 9DAF8B621A7B008A00F46528 /* MIKMIDIControlChangeCommand.h in Headers */, 9D07CAC81BEA70E200C4ABB0 /* MIKMIDICompilerCompatibility.h in Headers */, 9DAF8B821A7B00BB00F46528 /* MIKMIDIClock.h in Headers */, @@ -1284,6 +1303,7 @@ 9DCDDB5A1AB3514100F8347E /* MIKMIDISequencerTests.m in Sources */, 9D8DC3CD202BBBFB00DDA4A8 /* MIKMIDIFourteenBitCCCommandTests.m in Sources */, 9D0225311CC92ECF0090EAB4 /* MIKMIDIMetaEventTests.m in Sources */, + 9D1C0D4627B9D76D00094AED /* MIKMIDIMachineControlTests.m in Sources */, 6609EF0C1EF300C400B4DAE5 /* MIKMIDISysexCoalescingTests.m in Sources */, 9DECB3C12035EE4100B8C7A8 /* MIKMIDISystemExclusiveCommandTests.m in Sources */, 9D2ED25F1AFBD062000325CC /* MIKMIDIResponderChainTests.m in Sources */, @@ -1304,7 +1324,7 @@ 839D936419C3A2F5007589C3 /* MIKMIDIMetaTrackSequenceNameEvent.m in Sources */, 9D74EF6717A713A100BEE89F /* MIKMIDICommand.m in Sources */, 9D74EF6A17A713A100BEE89F /* MIKMIDIControlChangeCommand.m in Sources */, - 9D1FE4F627B07B00005317B2 /* MIKMIDIMachineControlLocateCommand.m in Sources */, + 9D1FE4F627B07B00005317B2 /* MIKMIDIMachineControlLocateTargetCommand.m in Sources */, 839D935819C3A2F5007589C3 /* MIKMIDIMetaKeySignatureEvent.m in Sources */, 839D937419C3A319007589C3 /* MIKMIDITempoEvent.m in Sources */, 9D74EF6C17A713A100BEE89F /* MIKMIDIDestinationEndpoint.m in Sources */, @@ -1358,6 +1378,7 @@ 9DED4E381AA90CA700DA8356 /* MIKMIDIPitchBendChangeCommand.m in Sources */, 9D74EF8F17A713A100BEE89F /* MIKMIDISystemExclusiveCommand.m in Sources */, 9D1D9C2A1BF54D38001377F7 /* MIKMIDIChannelPressureCommand.m in Sources */, + 9D1C0D4327B9D71A00094AED /* MIKMIDIMachineControlCommand.m in Sources */, 9D74EF9117A713A100BEE89F /* MIKMIDISystemMessageCommand.m in Sources */, 9D07CB241BEC13E400C4ABB0 /* MIKMIDIConnectionManager.m in Sources */, 839D935419C3A2F5007589C3 /* MIKMIDIMetaEvent.m in Sources */, @@ -1379,7 +1400,7 @@ 9DAF8B211A7AFF5900F46528 /* MIKMIDIDevice.m in Sources */, 9DAF8B221A7AFF5900F46528 /* MIKMIDIEntity.m in Sources */, 9DAF8B231A7AFF5900F46528 /* MIKMIDIPort.m in Sources */, - 9D1FE4F727B07B04005317B2 /* MIKMIDIMachineControlLocateCommand.m in Sources */, + 9D1FE4F727B07B04005317B2 /* MIKMIDIMachineControlLocateTargetCommand.m in Sources */, 9DAF8B241A7AFF5900F46528 /* MIKMIDIInputPort.m in Sources */, 9DED4E391AA90CA700DA8356 /* MIKMIDIPitchBendChangeCommand.m in Sources */, 9DAF8B251A7AFF5900F46528 /* MIKMIDIOutputPort.m in Sources */, @@ -1433,6 +1454,7 @@ 9DAF8B4C1A7AFF7500F46528 /* MIKMIDISequencer.m in Sources */, 9DB366F31A964C55001D1CF3 /* MIKMIDISynthesizer.m in Sources */, 9D1D9C2B1BF54D38001377F7 /* MIKMIDIChannelPressureCommand.m in Sources */, + 9D1C0D4427B9D71A00094AED /* MIKMIDIMachineControlCommand.m in Sources */, 9DAF8B4D1A7AFF7500F46528 /* MIKMIDIUtilities.m in Sources */, 9D07CB251BEC13E400C4ABB0 /* MIKMIDIConnectionManager.m in Sources */, 9DAF8B4E1A7AFF7500F46528 /* MIKMIDICommandThrottler.m in Sources */, diff --git a/Source/MIKMIDI.h b/Source/MIKMIDI.h index 1592fe88..b8f72bf8 100644 --- a/Source/MIKMIDI.h +++ b/Source/MIKMIDI.h @@ -44,6 +44,7 @@ #import #import #import +#import // Includes many individual MMC command types // MIDI Sequence/File support #import diff --git a/Source/MIKMIDICommand.m b/Source/MIKMIDICommand.m index 42114a7b..58150ddb 100644 --- a/Source/MIKMIDICommand.m +++ b/Source/MIKMIDICommand.m @@ -167,7 +167,14 @@ - (BOOL)isEqualToCommand:(MIKMIDICommand *)command } } } - return result; + + // Sort so that deepest subclass hierarchy children come last + return [result sortedArrayWithOptions:0 usingComparator:^NSComparisonResult(Class class1, Class class2) { + if ([class1 isEqualTo:class2]) { return NSOrderedSame; } + if ([class1 isSubclassOfClass:class2]) { return NSOrderedDescending; } + if ([class2 isSubclassOfClass:class1]) { return NSOrderedAscending; } + return NSOrderedAscending; + }]; } + (Class)subclassForMIDIPacket:(MIDIPacket *)packet @@ -198,16 +205,8 @@ + (Class)subclassForMIDIPacket:(MIDIPacket *)packet subclasses = specificHandlingSubclasses; } - // Sort so that deepest subclass hierarchy children come first - NSArray *sortedSubclasses = [subclasses sortedArrayWithOptions:0 usingComparator:^NSComparisonResult(Class class1, Class class2) { - if ([class1 isEqualTo:class2]) { return NSOrderedSame; } - if ([class1 isSubclassOfClass:class2]) { return NSOrderedDescending; } - if ([class2 isSubclassOfClass:class1]) { return NSOrderedAscending; } - return NSOrderedAscending; - }]; - - // Return the first subclass that doesn't reject this MIDI packet - for (Class subclass in sortedSubclasses) { + // Return the deepest child subclass that doesn't reject this MIDI packet + for (Class subclass in subclasses.reverseObjectEnumerator) { if ([subclass handlingIntentForMIDIPacket:packet] == MIKMIDICommandPacketHandlingIntentReject) { continue; } diff --git a/Source/MIKMIDIMachineControl.h b/Source/MIKMIDIMachineControl.h new file mode 100644 index 00000000..4b5a27f1 --- /dev/null +++ b/Source/MIKMIDIMachineControl.h @@ -0,0 +1,12 @@ +// +// MIKMIDIMachineControl.h +// MIKMIDI +// +// Created by Andrew R Madsen on 2/6/22. +// Copyright © 2022 Mixed In Key. All rights reserved. +// + +#import + +#import +#import diff --git a/Source/MIKMIDIMachineControlCommand.h b/Source/MIKMIDIMachineControlCommand.h new file mode 100644 index 00000000..b346a8df --- /dev/null +++ b/Source/MIKMIDIMachineControlCommand.h @@ -0,0 +1,21 @@ +// +// MIKMIDIMachineControlCommand.h +// MIKMIDI +// +// Created by Andrew R Madsen on 2/13/22. +// Copyright © 2022 Mixed In Key. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface MIKMIDIMachineControlCommand : MIKMIDISystemExclusiveCommand + +@end + +@interface MIKMutableMIDIMachineControlCommand : MIKMIDIMachineControlCommand + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIMachineControlCommand.m b/Source/MIKMIDIMachineControlCommand.m new file mode 100644 index 00000000..3771fc9b --- /dev/null +++ b/Source/MIKMIDIMachineControlCommand.m @@ -0,0 +1,38 @@ +// +// MIKMIDIMachineControlCommand.m +// MIKMIDI +// +// Created by Andrew R Madsen on 2/13/22. +// Copyright © 2022 Mixed In Key. All rights reserved. +// + +#import "MIKMIDIMachineControlCommand.h" +#import "MIKMIDICommand_SubclassMethods.h" + +@implementation MIKMIDIMachineControlCommand + ++ (void)load { [super load]; [MIKMIDICommand registerSubclass:self]; } ++ (NSArray *)supportedMIDICommandTypes { return @[@(MIKMIDICommandTypeSystemExclusive)]; } + ++ (MIKMIDICommandPacketHandlingIntent)handlingIntentForMIDIPacket:(MIDIPacket *)packet +{ + if (packet->length < 5) { return MIKMIDICommandPacketHandlingIntentReject; } + uint8_t directionByteIndex = 3; + uint8_t firstByte = packet->data[1]; + if (firstByte == 0) { // Three byte manufacturer ID + directionByteIndex += 2; + } + uint8_t directionByte = packet->data[directionByteIndex]; + if (directionByte != 0x06 && directionByte != 0x07) { return MIKMIDICommandPacketHandlingIntentReject; } + + return MIKMIDICommandPacketHandlingIntentAccept; +} + ++ (Class)immutableCounterpartClass; { return [MIKMIDIMachineControlCommand class]; } ++ (Class)mutableCounterpartClass; { return [MIKMutableMIDISystemExclusiveCommand class]; } + +@end + +@implementation MIKMutableMIDIMachineControlCommand + +@end diff --git a/Source/MIKMIDIMachineControlLocateTargetCommand.h b/Source/MIKMIDIMachineControlLocateTargetCommand.h new file mode 100644 index 00000000..ebd039e6 --- /dev/null +++ b/Source/MIKMIDIMachineControlLocateTargetCommand.h @@ -0,0 +1,17 @@ +// +// MIKMIDIMachineControlLocateTargetCommand.h +// MIKMIDI +// +// Created by Andrew R Madsen on 2/6/22. +// Copyright © 2022 Mixed In Key. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface MIKMIDIMachineControlLocateTargetCommand : MIKMIDISystemExclusiveCommand + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIMachineControlLocateTargetCommand.m b/Source/MIKMIDIMachineControlLocateTargetCommand.m new file mode 100644 index 00000000..20e22f3d --- /dev/null +++ b/Source/MIKMIDIMachineControlLocateTargetCommand.m @@ -0,0 +1,37 @@ +// +// MIKMIDIMachineControlLocateTargetCommand.m +// MIKMIDI +// +// Created by Andrew R Madsen on 2/6/22. +// Copyright © 2022 Mixed In Key. All rights reserved. +// + +#import "MIKMIDIMachineControlLocateTargetCommand.h" +#import "MIKMIDICommand_SubclassMethods.h" + +@implementation MIKMIDIMachineControlLocateTargetCommand + ++ (void)load { [super load]; [MIKMIDICommand registerSubclass:self]; } ++ (NSArray *)supportedMIDICommandTypes { return @[@(MIKMIDICommandTypeSystemExclusive)]; } + ++ (MIKMIDICommandPacketHandlingIntent)handlingIntentForMIDIPacket:(MIDIPacket *)packet +{ + if (packet->length < 5) { return MIKMIDICommandPacketHandlingIntentReject; } + uint8_t directionByteIndex = 3; + uint8_t firstByte = packet->data[1]; + if (firstByte == 0) { // Three byte manufacturer ID + directionByteIndex += 2; + } + uint8_t messageTypeIndex = directionByteIndex + 1; + if (packet->length <= messageTypeIndex) { return MIKMIDICommandPacketHandlingIntentReject; } + + uint8_t messageType = packet->data[messageTypeIndex]; + + if (messageType == 0x44) { return MIKMIDICommandPacketHandlingIntentAcceptWithHigherPrecedence; } + return MIKMIDICommandPacketHandlingIntentReject; +} + ++ (Class)immutableCounterpartClass; { return [MIKMIDIMachineControlLocateTargetCommand class]; } ++ (Class)mutableCounterpartClass; { return [MIKMutableMIDISystemExclusiveCommand class]; } + +@end From 54c5185ea5369b2f9c8f0791657100796763de8d Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Sun, 13 Feb 2022 18:18:58 -0700 Subject: [PATCH 39/53] Issue #327: Flesh out implementation of MIKMIDIMachineControlCommand including tests --- .../MIKMIDIMachineControlTests.m | 44 ++++++- Source/MIKMIDIMachineControlCommand.h | 108 +++++++++++++++++ Source/MIKMIDIMachineControlCommand.m | 114 +++++++++++++++++- 3 files changed, 263 insertions(+), 3 deletions(-) diff --git a/Framework/MIKMIDI Tests/MIKMIDIMachineControlTests.m b/Framework/MIKMIDI Tests/MIKMIDIMachineControlTests.m index b4c709b1..8b1f5f16 100644 --- a/Framework/MIKMIDI Tests/MIKMIDIMachineControlTests.m +++ b/Framework/MIKMIDI Tests/MIKMIDIMachineControlTests.m @@ -17,11 +17,51 @@ @implementation MIKMIDIMachineControlTests - (void)testGenericMIDIMachineControlCommand { - NSArray *bytes = @[@(0xf0), @(0x7f), @(0x7f), @(0x06), @(0x01)]; + Class immutableClass = [MIKMIDIMachineControlCommand class]; + Class mutableClass = [MIKMutableMIDIMachineControlCommand class]; + + NSArray *bytes = @[@(0xf0), @(0x7f), @(0xab), @(0x07), @(0x01)]; MIDIPacket packet = MIKMIDIPacketCreate(0, bytes.count, bytes); - MIKMIDICommand *command = [MIKMIDICommand commandWithMIDIPacket:&packet]; + MIKMIDIMachineControlCommand *command = [MIKMIDIMachineControlCommand commandWithMIDIPacket:&packet]; XCTAssertTrue([command isMemberOfClass:[MIKMIDIMachineControlCommand class]]); + XCTAssertEqual(command.deviceAddress, 0xab); + XCTAssertEqual(command.direction, MIKMIDIMachineControlDirectionResponse); + XCTAssertEqual(command.MMCCommandType, MIKMIDIMachineControlCommandTypeStop); + XCTAssertEqual(command.commandType, MIKMIDICommandTypeSystemExclusive, @"[[MIKMIDIMachineControlCommand alloc] init] produced a command instance with the wrong command type."); + XCTAssert([[command copy] isMemberOfClass:immutableClass], @"[MIKMIDIMachineControlCommand copy] did not return an MIKMIDIMachineControlCommand instance."); + XCTAssert([[command mutableCopy] isMemberOfClass:mutableClass], @"-[MIKMIDIMachineControlCommand mutableCopy] did not return a mutableClass instance."); + + + MIKMutableMIDIMachineControlCommand *mutableCommand = [[MIKMutableMIDIMachineControlCommand alloc] init]; + XCTAssert([mutableCommand isMemberOfClass:mutableClass], @"-[MIKMIDIMachineControlCommand mutableCopy] did not return a mutableClass instance."); + XCTAssert([[mutableCommand copy] isMemberOfClass:immutableClass], @"[MIKMutableMIDIMachineControlCommand copy] did not return an MIKMIDIMachineControlCommand instance."); + XCTAssertEqual(mutableCommand.commandType, MIKMIDICommandTypeSystemExclusive, @"[[MIKMIDIMachineControlCommand alloc] init] produced a command instance with the wrong command type."); + + XCTAssertEqual(mutableCommand.deviceAddress, 0x7f); + XCTAssertEqual(mutableCommand.direction, MIKMIDIMachineControlDirectionCommand); + XCTAssertEqual(mutableCommand.MMCCommandType, MIKMIDIMachineControlCommandTypeUnknown); + + XCTAssertThrows([(MIKMutableMIDIMachineControlCommand *)command setDirection:MIKMIDIMachineControlDirectionResponse]); + XCTAssertThrows([(MIKMutableMIDIMachineControlCommand *)command setMMCCommandType:MIKMIDIMachineControlCommandTypePlay]); + + XCTAssertNoThrow([mutableCommand setDirection:MIKMIDIMachineControlDirectionResponse]); + XCTAssertNoThrow([mutableCommand setMMCCommandType:MIKMIDIMachineControlCommandTypePlay]); + + mutableCommand.deviceAddress = 0x9f; + mutableCommand.direction = MIKMIDIMachineControlDirectionResponse; + mutableCommand.MMCCommandType = MIKMIDIMachineControlCommandTypeRecordExit; + XCTAssertEqual(mutableCommand.deviceAddress, 0x9f); + XCTAssertEqual(mutableCommand.direction, MIKMIDIMachineControlDirectionResponse); + XCTAssertEqual(mutableCommand.MMCCommandType, MIKMIDIMachineControlCommandTypeRecordExit); + + MIKMIDIMachineControlCommand *createdCommand = + [MIKMIDIMachineControlCommand machineControlCommandWithDeviceAddress:0xcd + direction:MIKMIDIMachineControlDirectionResponse + MMCCommandType:MIKMIDIMachineControlCommandTypePause]; + XCTAssertEqual(createdCommand.deviceAddress, 0xcd); + XCTAssertEqual(createdCommand.direction, MIKMIDIMachineControlDirectionResponse); + XCTAssertEqual(createdCommand.MMCCommandType, MIKMIDIMachineControlCommandTypePause); } - (void)testMMCLocateTargetCommand diff --git a/Source/MIKMIDIMachineControlCommand.h b/Source/MIKMIDIMachineControlCommand.h index b346a8df..bd541b6d 100644 --- a/Source/MIKMIDIMachineControlCommand.h +++ b/Source/MIKMIDIMachineControlCommand.h @@ -8,14 +8,122 @@ #import +/** + * The MMC sub-ID. + * As defined by the MMC spec, a message can be either a command or a response. + */ +typedef NS_ENUM(UInt8, MIKMIDIMachineControlDirection) { + MIKMIDIMachineControlDirectionCommand = 0x06, // aka. 'mcc' + MIKMIDIMachineControlDirectionResponse = 0x07, // aka. 'mcr' +}; + +/** + * The possible command types represented by an MMC message. These are set forth and described in the + * MMC part of the MIDI spec. + */ +typedef NS_ENUM(UInt8, MIKMIDIMachineControlCommandType) { + MIKMIDIMachineControlCommandTypeUnknown = 0x00, + + MIKMIDIMachineControlCommandTypeStop = 0x01, + MIKMIDIMachineControlCommandTypePlay = 0x02, + MIKMIDIMachineControlCommandTypeDeferredPlay = 0x03, + MIKMIDIMachineControlCommandTypeFastForward = 0x04, + MIKMIDIMachineControlCommandTypeRewind = 0x05, + MIKMIDIMachineControlCommandTypeRecordStrobe = 0x06, + MIKMIDIMachineControlCommandTypeRecordExit = 0x07, + MIKMIDIMachineControlCommandTypeRecordPause = 0x08, + MIKMIDIMachineControlCommandTypePause = 0x09, + MIKMIDIMachineControlCommandTypeEject = 0x0a, + MIKMIDIMachineControlCommandTypeChase = 0x0b, + MIKMIDIMachineControlCommandTypeCommandErrorReset = 0xc, + MIKMIDIMachineControlCommandTypeMMCRest = 0xd, + MIKMIDIMachineControlCommandTypeWrite = 0x40, + MIKMIDIMachineControlCommandTypeMaskedWrite = 0x41, + MIKMIDIMachineControlCommandTypeRead = 0x42, + MIKMIDIMachineControlCommandTypeUpdate = 0x43, + MIKMIDIMachineControlCommandTypeLocate = 0x44, + MIKMIDIMachineControlCommandTypeVariablePlay = 0x45, + MIKMIDIMachineControlCommandTypeSearch = 0x46, + MIKMIDIMachineControlCommandTypeShuttle = 0x47, + MIKMIDIMachineControlCommandTypeStep = 0x48, + MIKMIDIMachineControlCommandTypeAssignSystemMaster = 0x49, + MIKMIDIMachineControlCommandTypeGeneratorCommand = 0x4a, + MIKMIDIMachineControlCommandTypeMIDITimeCodeCommand = 0x4b, + MIKMIDIMachineControlCommandTypeMove = 0x4c, + MIKMIDIMachineControlCommandTypeAdd = 0x4d, + MIKMIDIMachineControlCommandTypeSubtract = 0x4e, + MIKMIDIMachineControlCommandTypeDropFrameAdjust = 0x4f, + MIKMIDIMachineControlCommandTypeProcedure = 0x50, + MIKMIDIMachineControlCommandTypeEvent = 0x51, + MIKMIDIMachineControlCommandTypeGroup = 0x52, + MIKMIDIMachineControlCommandTypeCommandSegment = 0x53, + MIKMIDIMachineControlCommandTypeDeferredVariablePlay = 0x54, + MIKMIDIMachineControlCommandTypeRecordStrobeVariable = 0x55, + MIKMIDIMachineControlCommandTypeWait = 0x7C, + MIKMIDIMachineControlCommandTypeResume = 0x7F, +}; + NS_ASSUME_NONNULL_BEGIN +/** + * MIKMIDIMachineControlCommand is used to represent MIDI messages that are used for + * MIDI Machine Control (MMC), per the MIDI spec. Specific support for MMC command + * subtypes is provided by subclasses of MIKMIDIMachineControlCommand (e.g. + * MIKMIDIMachineControlLocatedTargetCommand, etc.) + */ @interface MIKMIDIMachineControlCommand : MIKMIDISystemExclusiveCommand +/** + * Convenience method for creating a machine control command. + * + * @param deviceAddress The device address for the command. Destination address for commands, source address for responses + * @param direction The direction the command is going, either a command or a response + * @param mmcCommandType The sub-type for the command. See MIKMIDIMachineControlCommandType for values + * + * @return An initialized MIKMIDIMachineControlCommand (or subclass) instance + */ ++ (instancetype)machineControlCommandWithDeviceAddress:(UInt8)deviceAddress + direction:(MIKMIDIMachineControlDirection)direction + MMCCommandType:(MIKMIDIMachineControlCommandType)mmcCommandType; + + +/** + * The device address in the message represented by the receiver. Per the MMC spec, this is the destination + * device for commands, and the source device address for responses. + */ +@property (nonatomic, readonly) UInt8 deviceAddress; + +/** + * The direction this message is going. As defined by the MMC spec, a message can be either a command + * or a response. See MIKMIDIMachineControlDirection for possible values. + */ +@property (nonatomic, readonly) MIKMIDIMachineControlDirection direction; + +/** + * The MMC command type represented by the receiver. For a complete list of possible values + * see MIKMIDIMachineControlCommandType. + */ +@property (nonatomic, readonly) MIKMIDIMachineControlCommandType MMCCommandType; + @end +/** + * The mutable counterpart of MIKMIDIMachineControlCommand. + */ @interface MIKMutableMIDIMachineControlCommand : MIKMIDIMachineControlCommand +@property (nonatomic, readwrite) UInt8 deviceAddress; +@property (nonatomic, readwrite) MIKMIDIMachineControlDirection direction; +@property (nonatomic, readwrite) MIKMIDIMachineControlCommandType MMCCommandType; + +@property (nonatomic, strong, readwrite) NSDate *timestamp; +@property (nonatomic, readwrite) MIKMIDICommandType commandType; +@property (nonatomic, readwrite) UInt8 dataByte1; +@property (nonatomic, readwrite) UInt8 dataByte2; + +@property (nonatomic, readwrite) MIDITimeStamp midiTimestamp; +@property (nonatomic, copy, readwrite, null_resettable) NSData *data; + @end NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIMachineControlCommand.m b/Source/MIKMIDIMachineControlCommand.m index 3771fc9b..ea527400 100644 --- a/Source/MIKMIDIMachineControlCommand.m +++ b/Source/MIKMIDIMachineControlCommand.m @@ -29,10 +29,122 @@ + (MIKMIDICommandPacketHandlingIntent)handlingIntentForMIDIPacket:(MIDIPacket *) } + (Class)immutableCounterpartClass; { return [MIKMIDIMachineControlCommand class]; } -+ (Class)mutableCounterpartClass; { return [MIKMutableMIDISystemExclusiveCommand class]; } ++ (Class)mutableCounterpartClass; { return [MIKMutableMIDIMachineControlCommand class]; } + +- (id)initWithMIDIPacket:(MIDIPacket *)packet +{ + self = [super initWithMIDIPacket:packet]; + if (self) { + if (!packet) { + if ([self.internalData length] < 5) { + [self.internalData increaseLengthBy:5-[self.internalData length]]; + } + UInt8 *data = (UInt8 *)[self.internalData mutableBytes]; + data[0] = 0xf0; + data[1] = 0x7f; // Sysex start + data[2] = 0x7f; // generic device address + data[3] = MIKMIDIMachineControlDirectionCommand; + data[4] = MIKMIDIMachineControlCommandTypeUnknown; + } + } + return self; +} + ++ (instancetype)machineControlCommandWithDeviceAddress:(UInt8)deviceAddress + direction:(MIKMIDIMachineControlDirection)direction + MMCCommandType:(MIKMIDIMachineControlCommandType)mmcCommandType +{ + Class resultClass = [MIKMIDIMachineControlCommand class]; + if (mmcCommandType == MIKMIDIMachineControlCommandTypeLocate) { resultClass = [MIKMIDIMachineControlLocateTargetCommand class]; } + + MIKMutableMIDIMachineControlCommand *result = [[[resultClass mutableCounterpartClass] alloc] init]; + result.deviceAddress = deviceAddress; + result.direction = direction; + result.MMCCommandType = mmcCommandType; + + return [self isMutable] ? result : [result copy]; +} + +#pragma mark - Properties + +- (UInt8)deviceAddress +{ + UInt8 deviceIDByteIndex = self.includesThreeByteManufacturerID ? 4 : 2; + UInt8 deviceID = ((UInt8 *)self.data.bytes)[deviceIDByteIndex]; + return deviceID; +} + +- (void)setDeviceAddress:(UInt8)deviceAddress +{ + if (![[self class] isMutable]) return MIKMIDI_RAISE_MUTATION_ATTEMPT_EXCEPTION; + + UInt8 deviceIDByteIndex = self.includesThreeByteManufacturerID ? 4 : 2; + if ([self.internalData length] <= deviceIDByteIndex) { + [self.internalData increaseLengthBy:deviceIDByteIndex + 1 - [self.internalData length]]; + } + + UInt8 *data = (UInt8 *)[self.internalData mutableBytes]; + data[deviceIDByteIndex] = deviceAddress; +} + +- (MIKMIDIMachineControlDirection)direction +{ + UInt8 directionByteIndex = self.includesThreeByteManufacturerID ? 5 : 3; + MIKMIDIMachineControlDirection direction = ((UInt8 *)self.data.bytes)[directionByteIndex]; + return direction; +} + +- (void)setDirection:(MIKMIDIMachineControlDirection)direction +{ + if (![[self class] isMutable]) return MIKMIDI_RAISE_MUTATION_ATTEMPT_EXCEPTION; + + UInt8 directionByteIndex = self.includesThreeByteManufacturerID ? 5 : 3; + if ([self.internalData length] <= directionByteIndex) { + [self.internalData increaseLengthBy:directionByteIndex + 1 - [self.internalData length]]; + } + + UInt8 *data = (UInt8 *)[self.internalData mutableBytes]; + data[directionByteIndex] = direction; +} + +- (MIKMIDIMachineControlCommandType)MMCCommandType +{ + UInt8 commandTypeByteIndex = self.includesThreeByteManufacturerID ? 6 : 4; + MIKMIDIMachineControlCommandType commandType = ((UInt8 *)self.data.bytes)[commandTypeByteIndex]; + return commandType; +} + +- (void)setMMCCommandType:(MIKMIDIMachineControlCommandType)MMCCommandType +{ + if (![[self class] isMutable]) return MIKMIDI_RAISE_MUTATION_ATTEMPT_EXCEPTION; + + UInt8 commandTypeByteIndex = self.includesThreeByteManufacturerID ? 6 : 4; + if ([self.internalData length] <= commandTypeByteIndex) { + [self.internalData increaseLengthBy:commandTypeByteIndex + 1 - [self.internalData length]]; + } + + UInt8 *data = (UInt8 *)[self.internalData mutableBytes]; + data[commandTypeByteIndex] = MMCCommandType; +} @end @implementation MIKMutableMIDIMachineControlCommand ++ (BOOL)isMutable { return YES; } + +#pragma mark - Properties + +@dynamic deviceAddress; +@dynamic direction; +@dynamic MMCCommandType; + +// MIKMIDICommand already implements these. This keeps the compiler happy. +@dynamic timestamp; +@dynamic dataByte1; +@dynamic dataByte2; +@dynamic midiTimestamp; +@dynamic data; +@dynamic commandType; + @end From bf04a2a2f508afddc86814bbdc998ea01c8f2806 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Sun, 13 Feb 2022 18:32:15 -0700 Subject: [PATCH 40/53] Issue #327: Make MIKMIDIMachineControlLocateTargetCommand a subclass of MIKMIDIMachineControlCommand --- Framework/MIKMIDI.xcodeproj/project.pbxproj | 12 ++++++------ Source/MIKMIDIMachineControlCommand.h | 2 +- Source/MIKMIDIMachineControlCommand.m | 2 ++ Source/MIKMIDIMachineControlLocateTargetCommand.h | 4 ++-- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/Framework/MIKMIDI.xcodeproj/project.pbxproj b/Framework/MIKMIDI.xcodeproj/project.pbxproj index c1817a90..3e5458f7 100644 --- a/Framework/MIKMIDI.xcodeproj/project.pbxproj +++ b/Framework/MIKMIDI.xcodeproj/project.pbxproj @@ -89,6 +89,8 @@ 9D1C0D4327B9D71A00094AED /* MIKMIDIMachineControlCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D1C0D4027B9D71A00094AED /* MIKMIDIMachineControlCommand.m */; }; 9D1C0D4427B9D71A00094AED /* MIKMIDIMachineControlCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D1C0D4027B9D71A00094AED /* MIKMIDIMachineControlCommand.m */; }; 9D1C0D4627B9D76D00094AED /* MIKMIDIMachineControlTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D1C0D4527B9D76D00094AED /* MIKMIDIMachineControlTests.m */; }; + 9D1C0D4E27B9E88500094AED /* MIKMIDIMachineControl.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D1C0D4D27B9E88500094AED /* MIKMIDIMachineControl.h */; }; + 9D1C0D4F27B9E88500094AED /* MIKMIDIMachineControl.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D1C0D4D27B9E88500094AED /* MIKMIDIMachineControl.h */; }; 9D1D9C201BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D1D9C1E1BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D1D9C211BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D1D9C1E1BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D1D9C221BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D1D9C1F1BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.m */; }; @@ -101,8 +103,6 @@ 9D1FE4F527B07B00005317B2 /* MIKMIDIMachineControlLocateTargetCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D1FE4F327B07B00005317B2 /* MIKMIDIMachineControlLocateTargetCommand.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D1FE4F627B07B00005317B2 /* MIKMIDIMachineControlLocateTargetCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D1FE4F427B07B00005317B2 /* MIKMIDIMachineControlLocateTargetCommand.m */; }; 9D1FE4F727B07B04005317B2 /* MIKMIDIMachineControlLocateTargetCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D1FE4F427B07B00005317B2 /* MIKMIDIMachineControlLocateTargetCommand.m */; }; - 9D1FE4FA27B07B34005317B2 /* MIKMIDIMachineControl.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D1FE4F827B07B34005317B2 /* MIKMIDIMachineControl.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 9D1FE4FE27B07B9D005317B2 /* MIKMIDIMachineControl.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D1FE4F827B07B34005317B2 /* MIKMIDIMachineControl.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D1FE4FF27B07BA6005317B2 /* MIKMIDIMachineControlLocateTargetCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D1FE4F327B07B00005317B2 /* MIKMIDIMachineControlLocateTargetCommand.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D2ED25F1AFBD062000325CC /* MIKMIDIResponderChainTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D2ED25E1AFBD062000325CC /* MIKMIDIResponderChainTests.m */; }; 9D3781561AA407A7007A61BE /* MIKMIDIResponder.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D74EF5817A713A100BEE89F /* MIKMIDIResponder.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -422,6 +422,7 @@ 9D1C0D3F27B9D71A00094AED /* MIKMIDIMachineControlCommand.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMachineControlCommand.h; sourceTree = ""; }; 9D1C0D4027B9D71A00094AED /* MIKMIDIMachineControlCommand.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMachineControlCommand.m; sourceTree = ""; }; 9D1C0D4527B9D76D00094AED /* MIKMIDIMachineControlTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMachineControlTests.m; sourceTree = ""; }; + 9D1C0D4D27B9E88500094AED /* MIKMIDIMachineControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMachineControl.h; sourceTree = ""; }; 9D1D9C1E1BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIPolyphonicKeyPressureCommand.h; sourceTree = ""; }; 9D1D9C1F1BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIPolyphonicKeyPressureCommand.m; sourceTree = ""; }; 9D1D9C241BF542BB001377F7 /* MIKMIDICommandTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDICommandTests.m; sourceTree = ""; }; @@ -429,7 +430,6 @@ 9D1D9C271BF54D38001377F7 /* MIKMIDIChannelPressureCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIChannelPressureCommand.m; sourceTree = ""; }; 9D1FE4F327B07B00005317B2 /* MIKMIDIMachineControlLocateTargetCommand.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMachineControlLocateTargetCommand.h; sourceTree = ""; }; 9D1FE4F427B07B00005317B2 /* MIKMIDIMachineControlLocateTargetCommand.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMachineControlLocateTargetCommand.m; sourceTree = ""; }; - 9D1FE4F827B07B34005317B2 /* MIKMIDIMachineControl.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMachineControl.h; sourceTree = ""; }; 9D2ED25E1AFBD062000325CC /* MIKMIDIResponderChainTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIResponderChainTests.m; sourceTree = ""; }; 9D4DF13A1AAB57430065F004 /* MIKMIDI Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "MIKMIDI Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 9D4DF13D1AAB57430065F004 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -606,7 +606,7 @@ 9D1FE4F227B07AEC005317B2 /* MIDI Machine Control (MMC) */ = { isa = PBXGroup; children = ( - 9D1FE4F827B07B34005317B2 /* MIKMIDIMachineControl.h */, + 9D1C0D4D27B9E88500094AED /* MIKMIDIMachineControl.h */, 9D1C0D3F27B9D71A00094AED /* MIKMIDIMachineControlCommand.h */, 9D1C0D4027B9D71A00094AED /* MIKMIDIMachineControlCommand.m */, 9D1FE4F327B07B00005317B2 /* MIKMIDIMachineControlLocateTargetCommand.h */, @@ -990,7 +990,6 @@ 9DEF1CA81AA6769E00E10273 /* MIKMIDIChannelEvent.h in Headers */, 9D8495221AA7773500C52475 /* MIKMIDIPolyphonicKeyPressureEvent.h in Headers */, 839D933219C3A2C9007589C3 /* MIKMIDIEvent_SubclassMethods.h in Headers */, - 9D1FE4FA27B07B34005317B2 /* MIKMIDIMachineControl.h in Headers */, 9D5946CE1A9FA7C800B5ACFB /* MIKMIDISynthesizer_SubclassMethods.h in Headers */, 839D935519C3A2F5007589C3 /* MIKMIDIMetaInstrumentNameEvent.h in Headers */, 9D9F029E1FB50B1900FE340E /* MIKMIDI-Prefix.pch in Headers */, @@ -1029,6 +1028,7 @@ 9D74EF8C17A713A100BEE89F /* MIKMIDISourceEndpoint.h in Headers */, 9DBEBD671AAA303700E59734 /* MIKMIDIChannelPressureEvent.h in Headers */, 833B73DA1A262FE100E0CC9F /* MIKMIDISequencer.h in Headers */, + 9D1C0D4E27B9E88500094AED /* MIKMIDIMachineControl.h in Headers */, 8308F6321B46C482004307AD /* MIKMIDICommandScheduler.h in Headers */, 9D1D9C281BF54D38001377F7 /* MIKMIDIChannelPressureCommand.h in Headers */, 9DB366F01A964C55001D1CF3 /* MIKMIDISynthesizer.h in Headers */, @@ -1112,9 +1112,9 @@ 9D9FBCCC1B4A29A5009A7936 /* MIKMIDIPort_SubclassMethods.h in Headers */, 9DBEBD5A1AAA21C400E59734 /* MIKMIDIEvent_SubclassMethods.h in Headers */, 9DAF8B561A7B007300F46528 /* MIKMIDIDevice.h in Headers */, - 9D1FE4FE27B07B9D005317B2 /* MIKMIDIMachineControl.h in Headers */, 9DAF8B761A7B00A700F46528 /* MIKMIDIMetaSequenceEvent.h in Headers */, 9DAF8B791A7B00A700F46528 /* MIKMIDIMetaTrackSequenceNameEvent.h in Headers */, + 9D1C0D4F27B9E88500094AED /* MIKMIDIMachineControl.h in Headers */, 9DAF8B6F1A7B00A700F46528 /* MIKMIDIMetaCopyrightEvent.h in Headers */, 9DAF8B531A7B005C00F46528 /* MIKMIDIErrors.h in Headers */, 9D1D9C211BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.h in Headers */, diff --git a/Source/MIKMIDIMachineControlCommand.h b/Source/MIKMIDIMachineControlCommand.h index bd541b6d..c2246bcb 100644 --- a/Source/MIKMIDIMachineControlCommand.h +++ b/Source/MIKMIDIMachineControlCommand.h @@ -6,7 +6,7 @@ // Copyright © 2022 Mixed In Key. All rights reserved. // -#import +#import /** * The MMC sub-ID. diff --git a/Source/MIKMIDIMachineControlCommand.m b/Source/MIKMIDIMachineControlCommand.m index ea527400..bda9334f 100644 --- a/Source/MIKMIDIMachineControlCommand.m +++ b/Source/MIKMIDIMachineControlCommand.m @@ -8,6 +8,8 @@ #import "MIKMIDIMachineControlCommand.h" #import "MIKMIDICommand_SubclassMethods.h" +#import "MIKMIDIMachineControlLocateTargetCommand.h" +#import "MIKMIDIUtilities.h" @implementation MIKMIDIMachineControlCommand diff --git a/Source/MIKMIDIMachineControlLocateTargetCommand.h b/Source/MIKMIDIMachineControlLocateTargetCommand.h index ebd039e6..21f315fc 100644 --- a/Source/MIKMIDIMachineControlLocateTargetCommand.h +++ b/Source/MIKMIDIMachineControlLocateTargetCommand.h @@ -6,11 +6,11 @@ // Copyright © 2022 Mixed In Key. All rights reserved. // -#import +#import NS_ASSUME_NONNULL_BEGIN -@interface MIKMIDIMachineControlLocateTargetCommand : MIKMIDISystemExclusiveCommand +@interface MIKMIDIMachineControlLocateTargetCommand : MIKMIDIMachineControlCommand @end From e00aa461713b9ac4790691ccb95f1edb16cc26eb Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Sun, 13 Feb 2022 18:40:08 -0700 Subject: [PATCH 41/53] Issue #327: Fix building tests target --- Framework/MIKMIDI.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Framework/MIKMIDI.xcodeproj/project.pbxproj b/Framework/MIKMIDI.xcodeproj/project.pbxproj index 3e5458f7..a2e1c5fb 100644 --- a/Framework/MIKMIDI.xcodeproj/project.pbxproj +++ b/Framework/MIKMIDI.xcodeproj/project.pbxproj @@ -89,8 +89,8 @@ 9D1C0D4327B9D71A00094AED /* MIKMIDIMachineControlCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D1C0D4027B9D71A00094AED /* MIKMIDIMachineControlCommand.m */; }; 9D1C0D4427B9D71A00094AED /* MIKMIDIMachineControlCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D1C0D4027B9D71A00094AED /* MIKMIDIMachineControlCommand.m */; }; 9D1C0D4627B9D76D00094AED /* MIKMIDIMachineControlTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D1C0D4527B9D76D00094AED /* MIKMIDIMachineControlTests.m */; }; - 9D1C0D4E27B9E88500094AED /* MIKMIDIMachineControl.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D1C0D4D27B9E88500094AED /* MIKMIDIMachineControl.h */; }; - 9D1C0D4F27B9E88500094AED /* MIKMIDIMachineControl.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D1C0D4D27B9E88500094AED /* MIKMIDIMachineControl.h */; }; + 9D1C0D4E27B9E88500094AED /* MIKMIDIMachineControl.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D1C0D4D27B9E88500094AED /* MIKMIDIMachineControl.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9D1C0D4F27B9E88500094AED /* MIKMIDIMachineControl.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D1C0D4D27B9E88500094AED /* MIKMIDIMachineControl.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D1D9C201BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D1D9C1E1BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D1D9C211BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D1D9C1E1BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D1D9C221BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D1D9C1F1BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.m */; }; From 7fd4e26b7ba146b8468d0550ddecbf22a4e85185 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Sun, 13 Feb 2022 21:26:53 -0700 Subject: [PATCH 42/53] Issue #327: Implement specialized functionality of MIKMMCLocateTargetCommand (fka. MIKMIDIMachineControlLocateTargetCommand) --- .../MIKMIDIMachineControlTests.m | 30 ++- Framework/MIKMIDI.xcodeproj/project.pbxproj | 24 +- Source/MIKMIDIMachineControl.h | 2 +- Source/MIKMIDIMachineControlCommand.m | 4 +- ...MIKMIDIMachineControlLocateTargetCommand.h | 17 -- ...MIKMIDIMachineControlLocateTargetCommand.m | 37 --- Source/MIKMMCLocateTargetCommand.h | 45 ++++ Source/MIKMMCLocateTargetCommand.m | 241 ++++++++++++++++++ 8 files changed, 326 insertions(+), 74 deletions(-) delete mode 100644 Source/MIKMIDIMachineControlLocateTargetCommand.h delete mode 100644 Source/MIKMIDIMachineControlLocateTargetCommand.m create mode 100644 Source/MIKMMCLocateTargetCommand.h create mode 100644 Source/MIKMMCLocateTargetCommand.m diff --git a/Framework/MIKMIDI Tests/MIKMIDIMachineControlTests.m b/Framework/MIKMIDI Tests/MIKMIDIMachineControlTests.m index 8b1f5f16..76080d62 100644 --- a/Framework/MIKMIDI Tests/MIKMIDIMachineControlTests.m +++ b/Framework/MIKMIDI Tests/MIKMIDIMachineControlTests.m @@ -66,16 +66,36 @@ - (void)testGenericMIDIMachineControlCommand - (void)testMMCLocateTargetCommand { - NSArray *bytes = @[@(0xf0), @(0x7f), @(0x7f), @(0x06), @(0x44), @(0x06), @(0x01), @(0x21), @(0x00), @(0x00), @(0x00), @(0x00), @(0xf7)]; + Class immutableClass = [MIKMMCLocateTargetCommand class]; + Class mutableClass = [MIKMutableMMCLocateTargetCommand class]; + + NSArray *bytes = @[@(0xf0), @(0x7f), @(0x7f), @(0x06), @(0x44), @(0x06), @(0x01), @(0x21), @(0x07), @(0x13), @(0x15), @(0x00), @(0xf7)]; MIDIPacket packet = MIKMIDIPacketCreate(0, bytes.count, bytes); - MIKMIDICommand *command = [MIKMIDICommand commandWithMIDIPacket:&packet]; - XCTAssertTrue([command isMemberOfClass:[MIKMIDIMachineControlLocateTargetCommand class]]); + MIKMMCLocateTargetCommand *command = (MIKMMCLocateTargetCommand *)[MIKMIDICommand commandWithMIDIPacket:&packet]; + XCTAssertTrue([command isMemberOfClass:[MIKMMCLocateTargetCommand class]]); + XCTAssertEqual(command.timeCodeInSeconds, 4039.84); + XCTAssertThrows([(MIKMutableMMCLocateTargetCommand *)command setTimeCodeInSeconds:27.0]); bytes = @[@(0xf0), @(0x7f), @(0x7f), @(0x06), @(0x45), @(0x06), @(0x01), @(0x21), @(0x00), @(0x00), @(0x00), @(0x00), @(0xf7)]; packet = MIKMIDIPacketCreate(0, bytes.count, bytes); - command = [MIKMIDICommand commandWithMIDIPacket:&packet]; // Should not be a locate command because message type byte is 0x45, not 0x44 - XCTAssertFalse([command isMemberOfClass:[MIKMIDIMachineControlLocateTargetCommand class]]); + command = (MIKMMCLocateTargetCommand *)[MIKMIDICommand commandWithMIDIPacket:&packet]; // Should not be a locate command because message type byte is 0x45, not 0x44 + XCTAssertFalse([command isMemberOfClass:[MIKMMCLocateTargetCommand class]]); XCTAssertTrue([command isMemberOfClass:[MIKMIDIMachineControlCommand class]]); + + MIKMutableMMCLocateTargetCommand *mutableCommand = [[MIKMutableMMCLocateTargetCommand alloc] init]; + XCTAssert([mutableCommand isMemberOfClass:mutableClass], @"-[MIKMMCLocateTargetCommand mutableCopy] did not return a mutableClass instance."); + XCTAssert([[mutableCommand copy] isMemberOfClass:immutableClass], @"[MIKMutableMMCLocateTargetCommand copy] did not return an MIKMMCLocateTargetCommand instance."); + XCTAssertEqual(mutableCommand.commandType, MIKMIDICommandTypeSystemExclusive, @"[[MIKMMCLocateTargetCommand alloc] init] produced a command instance with the wrong command type."); + + mutableCommand.timeType = MIKMMCLocateTargetCommandTimeType25FPS; + mutableCommand.timeCodeInSeconds = 4039.84; + XCTAssertEqual(mutableCommand.timeType, MIKMMCLocateTargetCommandTimeType25FPS); + XCTAssertEqual(mutableCommand.timeCodeInSeconds, 4039.84); + + XCTAssertNoThrow([mutableCommand setTimeCodeInSeconds:27.0]); + + MIKMMCLocateTargetCommand *createdCommand = [[MIKMMCLocateTargetCommand alloc] init]; + XCTAssertEqual(createdCommand.MMCCommandType, MIKMIDIMachineControlCommandTypeLocate); } @end diff --git a/Framework/MIKMIDI.xcodeproj/project.pbxproj b/Framework/MIKMIDI.xcodeproj/project.pbxproj index a2e1c5fb..4837e05e 100644 --- a/Framework/MIKMIDI.xcodeproj/project.pbxproj +++ b/Framework/MIKMIDI.xcodeproj/project.pbxproj @@ -100,10 +100,10 @@ 9D1D9C291BF54D38001377F7 /* MIKMIDIChannelPressureCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D1D9C261BF54D38001377F7 /* MIKMIDIChannelPressureCommand.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D1D9C2A1BF54D38001377F7 /* MIKMIDIChannelPressureCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D1D9C271BF54D38001377F7 /* MIKMIDIChannelPressureCommand.m */; }; 9D1D9C2B1BF54D38001377F7 /* MIKMIDIChannelPressureCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D1D9C271BF54D38001377F7 /* MIKMIDIChannelPressureCommand.m */; }; - 9D1FE4F527B07B00005317B2 /* MIKMIDIMachineControlLocateTargetCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D1FE4F327B07B00005317B2 /* MIKMIDIMachineControlLocateTargetCommand.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 9D1FE4F627B07B00005317B2 /* MIKMIDIMachineControlLocateTargetCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D1FE4F427B07B00005317B2 /* MIKMIDIMachineControlLocateTargetCommand.m */; }; - 9D1FE4F727B07B04005317B2 /* MIKMIDIMachineControlLocateTargetCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D1FE4F427B07B00005317B2 /* MIKMIDIMachineControlLocateTargetCommand.m */; }; - 9D1FE4FF27B07BA6005317B2 /* MIKMIDIMachineControlLocateTargetCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D1FE4F327B07B00005317B2 /* MIKMIDIMachineControlLocateTargetCommand.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9D1FE4F527B07B00005317B2 /* MIKMMCLocateTargetCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D1FE4F327B07B00005317B2 /* MIKMMCLocateTargetCommand.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9D1FE4F627B07B00005317B2 /* MIKMMCLocateTargetCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D1FE4F427B07B00005317B2 /* MIKMMCLocateTargetCommand.m */; }; + 9D1FE4F727B07B04005317B2 /* MIKMMCLocateTargetCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D1FE4F427B07B00005317B2 /* MIKMMCLocateTargetCommand.m */; }; + 9D1FE4FF27B07BA6005317B2 /* MIKMMCLocateTargetCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D1FE4F327B07B00005317B2 /* MIKMMCLocateTargetCommand.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D2ED25F1AFBD062000325CC /* MIKMIDIResponderChainTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D2ED25E1AFBD062000325CC /* MIKMIDIResponderChainTests.m */; }; 9D3781561AA407A7007A61BE /* MIKMIDIResponder.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D74EF5817A713A100BEE89F /* MIKMIDIResponder.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D4DF1401AAB57430065F004 /* MIKMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9D74EEA517A7129300BEE89F /* MIKMIDI.framework */; }; @@ -428,8 +428,8 @@ 9D1D9C241BF542BB001377F7 /* MIKMIDICommandTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDICommandTests.m; sourceTree = ""; }; 9D1D9C261BF54D38001377F7 /* MIKMIDIChannelPressureCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIChannelPressureCommand.h; sourceTree = ""; }; 9D1D9C271BF54D38001377F7 /* MIKMIDIChannelPressureCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIChannelPressureCommand.m; sourceTree = ""; }; - 9D1FE4F327B07B00005317B2 /* MIKMIDIMachineControlLocateTargetCommand.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMachineControlLocateTargetCommand.h; sourceTree = ""; }; - 9D1FE4F427B07B00005317B2 /* MIKMIDIMachineControlLocateTargetCommand.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMachineControlLocateTargetCommand.m; sourceTree = ""; }; + 9D1FE4F327B07B00005317B2 /* MIKMMCLocateTargetCommand.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MIKMMCLocateTargetCommand.h; sourceTree = ""; }; + 9D1FE4F427B07B00005317B2 /* MIKMMCLocateTargetCommand.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MIKMMCLocateTargetCommand.m; sourceTree = ""; }; 9D2ED25E1AFBD062000325CC /* MIKMIDIResponderChainTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIResponderChainTests.m; sourceTree = ""; }; 9D4DF13A1AAB57430065F004 /* MIKMIDI Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "MIKMIDI Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 9D4DF13D1AAB57430065F004 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -609,8 +609,8 @@ 9D1C0D4D27B9E88500094AED /* MIKMIDIMachineControl.h */, 9D1C0D3F27B9D71A00094AED /* MIKMIDIMachineControlCommand.h */, 9D1C0D4027B9D71A00094AED /* MIKMIDIMachineControlCommand.m */, - 9D1FE4F327B07B00005317B2 /* MIKMIDIMachineControlLocateTargetCommand.h */, - 9D1FE4F427B07B00005317B2 /* MIKMIDIMachineControlLocateTargetCommand.m */, + 9D1FE4F327B07B00005317B2 /* MIKMMCLocateTargetCommand.h */, + 9D1FE4F427B07B00005317B2 /* MIKMMCLocateTargetCommand.m */, ); name = "MIDI Machine Control (MMC)"; sourceTree = ""; @@ -1035,7 +1035,7 @@ 833B73DE1A26346F00E0CC9F /* MIKMIDIClock.h in Headers */, 9D76DCEC1A9E52DB00A24C16 /* MIKMIDITrack_Protected.h in Headers */, 9D74EF8E17A713A100BEE89F /* MIKMIDISystemExclusiveCommand.h in Headers */, - 9D1FE4F527B07B00005317B2 /* MIKMIDIMachineControlLocateTargetCommand.h in Headers */, + 9D1FE4F527B07B00005317B2 /* MIKMMCLocateTargetCommand.h in Headers */, 9DF99E791831841A004EE5F4 /* MIKMIDICommandThrottler.h in Headers */, 9D74EF9017A713A100BEE89F /* MIKMIDISystemMessageCommand.h in Headers */, 83BC19BB1A23CD0D004F384F /* MIKMIDIMetronome.h in Headers */, @@ -1057,7 +1057,7 @@ 9DAF8B5D1A7B007300F46528 /* MIKMIDIClientSourceEndpoint.h in Headers */, 9DAF8B7A1A7B00A700F46528 /* MIKMIDINoteEvent.h in Headers */, 9DAF8B6C1A7B00A700F46528 /* MIKMIDITrack.h in Headers */, - 9D1FE4FF27B07BA6005317B2 /* MIKMIDIMachineControlLocateTargetCommand.h in Headers */, + 9D1FE4FF27B07BA6005317B2 /* MIKMMCLocateTargetCommand.h in Headers */, 9DEBD03C1F7085DB00676C42 /* MIKMIDINoteCommand.h in Headers */, 9DAF8B661A7B008A00F46528 /* MIKMIDISystemExclusiveCommand.h in Headers */, 9D0895F41B0D2A4700A5872E /* MIKMIDIMappableResponder.h in Headers */, @@ -1324,7 +1324,7 @@ 839D936419C3A2F5007589C3 /* MIKMIDIMetaTrackSequenceNameEvent.m in Sources */, 9D74EF6717A713A100BEE89F /* MIKMIDICommand.m in Sources */, 9D74EF6A17A713A100BEE89F /* MIKMIDIControlChangeCommand.m in Sources */, - 9D1FE4F627B07B00005317B2 /* MIKMIDIMachineControlLocateTargetCommand.m in Sources */, + 9D1FE4F627B07B00005317B2 /* MIKMMCLocateTargetCommand.m in Sources */, 839D935819C3A2F5007589C3 /* MIKMIDIMetaKeySignatureEvent.m in Sources */, 839D937419C3A319007589C3 /* MIKMIDITempoEvent.m in Sources */, 9D74EF6C17A713A100BEE89F /* MIKMIDIDestinationEndpoint.m in Sources */, @@ -1400,7 +1400,7 @@ 9DAF8B211A7AFF5900F46528 /* MIKMIDIDevice.m in Sources */, 9DAF8B221A7AFF5900F46528 /* MIKMIDIEntity.m in Sources */, 9DAF8B231A7AFF5900F46528 /* MIKMIDIPort.m in Sources */, - 9D1FE4F727B07B04005317B2 /* MIKMIDIMachineControlLocateTargetCommand.m in Sources */, + 9D1FE4F727B07B04005317B2 /* MIKMMCLocateTargetCommand.m in Sources */, 9DAF8B241A7AFF5900F46528 /* MIKMIDIInputPort.m in Sources */, 9DED4E391AA90CA700DA8356 /* MIKMIDIPitchBendChangeCommand.m in Sources */, 9DAF8B251A7AFF5900F46528 /* MIKMIDIOutputPort.m in Sources */, diff --git a/Source/MIKMIDIMachineControl.h b/Source/MIKMIDIMachineControl.h index 4b5a27f1..541b12be 100644 --- a/Source/MIKMIDIMachineControl.h +++ b/Source/MIKMIDIMachineControl.h @@ -9,4 +9,4 @@ #import #import -#import +#import diff --git a/Source/MIKMIDIMachineControlCommand.m b/Source/MIKMIDIMachineControlCommand.m index bda9334f..dc805888 100644 --- a/Source/MIKMIDIMachineControlCommand.m +++ b/Source/MIKMIDIMachineControlCommand.m @@ -8,7 +8,7 @@ #import "MIKMIDIMachineControlCommand.h" #import "MIKMIDICommand_SubclassMethods.h" -#import "MIKMIDIMachineControlLocateTargetCommand.h" +#import "MIKMMCLocateTargetCommand.h" #import "MIKMIDIUtilities.h" @implementation MIKMIDIMachineControlCommand @@ -57,7 +57,7 @@ + (instancetype)machineControlCommandWithDeviceAddress:(UInt8)deviceAddress MMCCommandType:(MIKMIDIMachineControlCommandType)mmcCommandType { Class resultClass = [MIKMIDIMachineControlCommand class]; - if (mmcCommandType == MIKMIDIMachineControlCommandTypeLocate) { resultClass = [MIKMIDIMachineControlLocateTargetCommand class]; } + if (mmcCommandType == MIKMIDIMachineControlCommandTypeLocate) { resultClass = [MIKMMCLocateTargetCommand class]; } MIKMutableMIDIMachineControlCommand *result = [[[resultClass mutableCounterpartClass] alloc] init]; result.deviceAddress = deviceAddress; diff --git a/Source/MIKMIDIMachineControlLocateTargetCommand.h b/Source/MIKMIDIMachineControlLocateTargetCommand.h deleted file mode 100644 index 21f315fc..00000000 --- a/Source/MIKMIDIMachineControlLocateTargetCommand.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// MIKMIDIMachineControlLocateTargetCommand.h -// MIKMIDI -// -// Created by Andrew R Madsen on 2/6/22. -// Copyright © 2022 Mixed In Key. All rights reserved. -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface MIKMIDIMachineControlLocateTargetCommand : MIKMIDIMachineControlCommand - -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIMachineControlLocateTargetCommand.m b/Source/MIKMIDIMachineControlLocateTargetCommand.m deleted file mode 100644 index 20e22f3d..00000000 --- a/Source/MIKMIDIMachineControlLocateTargetCommand.m +++ /dev/null @@ -1,37 +0,0 @@ -// -// MIKMIDIMachineControlLocateTargetCommand.m -// MIKMIDI -// -// Created by Andrew R Madsen on 2/6/22. -// Copyright © 2022 Mixed In Key. All rights reserved. -// - -#import "MIKMIDIMachineControlLocateTargetCommand.h" -#import "MIKMIDICommand_SubclassMethods.h" - -@implementation MIKMIDIMachineControlLocateTargetCommand - -+ (void)load { [super load]; [MIKMIDICommand registerSubclass:self]; } -+ (NSArray *)supportedMIDICommandTypes { return @[@(MIKMIDICommandTypeSystemExclusive)]; } - -+ (MIKMIDICommandPacketHandlingIntent)handlingIntentForMIDIPacket:(MIDIPacket *)packet -{ - if (packet->length < 5) { return MIKMIDICommandPacketHandlingIntentReject; } - uint8_t directionByteIndex = 3; - uint8_t firstByte = packet->data[1]; - if (firstByte == 0) { // Three byte manufacturer ID - directionByteIndex += 2; - } - uint8_t messageTypeIndex = directionByteIndex + 1; - if (packet->length <= messageTypeIndex) { return MIKMIDICommandPacketHandlingIntentReject; } - - uint8_t messageType = packet->data[messageTypeIndex]; - - if (messageType == 0x44) { return MIKMIDICommandPacketHandlingIntentAcceptWithHigherPrecedence; } - return MIKMIDICommandPacketHandlingIntentReject; -} - -+ (Class)immutableCounterpartClass; { return [MIKMIDIMachineControlLocateTargetCommand class]; } -+ (Class)mutableCounterpartClass; { return [MIKMutableMIDISystemExclusiveCommand class]; } - -@end diff --git a/Source/MIKMMCLocateTargetCommand.h b/Source/MIKMMCLocateTargetCommand.h new file mode 100644 index 00000000..296d7246 --- /dev/null +++ b/Source/MIKMMCLocateTargetCommand.h @@ -0,0 +1,45 @@ +// +// MIKMMCLocateTargetCommand.h +// MIKMIDI +// +// Created by Andrew R Madsen on 2/6/22. +// Copyright © 2022 Mixed In Key. All rights reserved. +// + +#import + +typedef NS_ENUM(UInt8, MIKMMCLocateTargetCommandTimeType) { + MIKMMCLocateTargetCommandTimeType24FPS = 0x00, + MIKMMCLocateTargetCommandTimeType25FPS = 0x01, + MIKMMCLocateTargetCommandTimeType30FPSDropFrame = 0x02, + MIKMMCLocateTargetCommandTimeType30FPS = 0x03, +}; + +NS_ASSUME_NONNULL_BEGIN + +@interface MIKMMCLocateTargetCommand : MIKMIDIMachineControlCommand + +@property (nonatomic, readonly) NSTimeInterval timeCodeInSeconds; +@property (nonatomic, readonly) MIKMMCLocateTargetCommandTimeType timeType; + +@end + +@interface MIKMutableMMCLocateTargetCommand : MIKMMCLocateTargetCommand + +@property (nonatomic, readwrite) NSTimeInterval timeCodeInSeconds; +@property (nonatomic, readwrite) MIKMMCLocateTargetCommandTimeType timeType; + +@property (nonatomic, readwrite) UInt8 deviceAddress; +@property (nonatomic, readwrite) MIKMIDIMachineControlDirection direction; + +@property (nonatomic, strong, readwrite) NSDate *timestamp; +@property (nonatomic, readwrite) MIKMIDICommandType commandType; +@property (nonatomic, readwrite) UInt8 dataByte1; +@property (nonatomic, readwrite) UInt8 dataByte2; + +@property (nonatomic, readwrite) MIDITimeStamp midiTimestamp; +@property (nonatomic, copy, readwrite, null_resettable) NSData *data; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMMCLocateTargetCommand.m b/Source/MIKMMCLocateTargetCommand.m new file mode 100644 index 00000000..42c54d12 --- /dev/null +++ b/Source/MIKMMCLocateTargetCommand.m @@ -0,0 +1,241 @@ +// +// MIKMMCLocateTargetCommand.m +// MIKMIDI +// +// Created by Andrew R Madsen on 2/6/22. +// Copyright © 2022 Mixed In Key. All rights reserved. +// + +#import "MIKMMCLocateTargetCommand.h" +#import "MIKMIDICommand_SubclassMethods.h" +#import "MIKMIDIUtilities.h" + +@implementation MIKMMCLocateTargetCommand + ++ (void)load { [super load]; [MIKMIDICommand registerSubclass:self]; } ++ (NSArray *)supportedMIDICommandTypes { return @[@(MIKMIDICommandTypeSystemExclusive)]; } + ++ (MIKMIDICommandPacketHandlingIntent)handlingIntentForMIDIPacket:(MIDIPacket *)packet +{ + if (packet->length < 5) { return MIKMIDICommandPacketHandlingIntentReject; } + UInt8 directionByteIndex = 3; + UInt8 firstByte = packet->data[1]; + if (firstByte == 0) { // Three byte manufacturer ID + directionByteIndex += 2; + } + UInt8 messageTypeIndex = directionByteIndex + 1; + if (packet->length <= messageTypeIndex) { return MIKMIDICommandPacketHandlingIntentReject; } + + UInt8 messageType = packet->data[messageTypeIndex]; + + if (messageType == 0x44) { return MIKMIDICommandPacketHandlingIntentAcceptWithHigherPrecedence; } + + UInt8 subtypeIndex = messageTypeIndex + 2; + if (packet->length <= subtypeIndex) { return MIKMIDICommandPacketHandlingIntentReject; } + + UInt8 subtype = packet->data[subtypeIndex]; + if (subtype != 0x01) { return MIKMIDICommandPacketHandlingIntentReject; } // Otherwise, it's not a target command + + return MIKMIDICommandPacketHandlingIntentReject; +} + ++ (Class)immutableCounterpartClass; { return [MIKMMCLocateTargetCommand class]; } ++ (Class)mutableCounterpartClass; { return [MIKMutableMMCLocateTargetCommand class]; } + +- (id)initWithMIDIPacket:(MIDIPacket *)packet +{ + self = [super initWithMIDIPacket:packet]; + if (self) { + if (!packet) { + if ([self.internalData length] < 5) { + [self.internalData increaseLengthBy:5-[self.internalData length]]; + } + UInt8 *data = (UInt8 *)[self.internalData mutableBytes]; + data[4] = MIKMIDIMachineControlCommandTypeLocate; + } + } + return self; +} + +#pragma mark - Properties + +#pragma mark Public + +- (NSTimeInterval)timeCodeInSeconds +{ + NSData *timecodeData = [self timecodeData]; + if (!timecodeData) { return -1; } + + UInt8 *timecodeBytes = (UInt8 *)timecodeData.bytes; + UInt8 hoursAndTypeByte = timecodeBytes[0]; + UInt8 minutesByte = timecodeBytes[1]; + UInt8 secondsByte = timecodeBytes[2]; + UInt8 framesByte = timecodeBytes[3]; + UInt8 finalByte = timecodeBytes[4]; + + UInt8 hours = (hoursAndTypeByte & 0x1F); + NSTimeInterval frameRate = [self frameRate]; + +// UInt8 colorFrameFlag = (minutesByte & 0x40) >> 6; + UInt8 minutes = (minutesByte & 0x3F); + +// UInt8 blankBit = (secondsByte & 0x40) >> 6; + UInt8 seconds = (secondsByte & 0x3F); + + UInt8 sign = (framesByte & 0x40) >> 6; + UInt8 finalByteID = (framesByte & 0x20) >> 5; + UInt8 frames = (framesByte & 0x1F); + + NSTimeInterval result = 0.0; + + result += hours * 3600.0; + result += minutes * 60.0; + result += seconds; + result += (NSTimeInterval)frames / frameRate; + + if (finalByteID == 0) { + UInt8 subframes = finalByte; + // Final byte is subframes + result += ((NSTimeInterval)subframes/100.0) / frameRate; + } + + if (sign) { + result = -result; + } + + return result; +} + +- (void)setTimeCodeInSeconds:(NSTimeInterval)timeCodeInSeconds +{ + if (![[self class] isMutable]) { return MIKMIDI_RAISE_MUTATION_ATTEMPT_EXCEPTION; } + + NSTimeInterval frameRate = [self frameRate]; + + UInt8 hours = (UInt8)(timeCodeInSeconds / 3600.0) & 0x1F; // Hours + UInt8 minutes = (UInt8)((timeCodeInSeconds - hours * 3600) / 60); + UInt8 seconds = (UInt8)(timeCodeInSeconds - hours * 3600 - minutes * 60); + UInt8 frames = (UInt8)((timeCodeInSeconds - hours * 3600 - minutes * 60 - seconds) * frameRate); + UInt8 subframes = (UInt8)((timeCodeInSeconds - hours * 3600 - minutes * 60 - seconds - (NSTimeInterval)frames/frameRate) * 100.0); + + NSMutableData *newTimecodeData = [NSMutableData dataWithLength:5]; + UInt8 *timecodeBytes = (UInt8 *)newTimecodeData.mutableBytes; + timecodeBytes[0] = ((self.timeType & 0x03) << 5) + hours; + timecodeBytes[1] = (timecodeBytes[1] & 0xC0) | (minutes & 0x3F); + timecodeBytes[2] = (timecodeBytes[2] & 0xC0) | (seconds & 0x3F); + timecodeBytes[3] = (timecodeBytes[2] & 0xE0) | (frames & 0x1F); + timecodeBytes[3] &= 0xDF; // set final byte ID to 0 for subframes + timecodeBytes[4] = subframes; + [self setTimecodeData:newTimecodeData]; +} + +- (MIKMMCLocateTargetCommandTimeType)timeType +{ + NSData *timecodeData = [self timecodeData]; + if (timecodeData.length < 1) { return MIKMMCLocateTargetCommandTimeType30FPS; } + + UInt8 *timecodeBytes = (UInt8 *)timecodeData.bytes; + UInt8 hoursAndTypeByte = timecodeBytes[0]; + + MIKMMCLocateTargetCommandTimeType type = (hoursAndTypeByte & 0x60) >> 5; + return type; +} + +- (void)setTimeType:(MIKMMCLocateTargetCommandTimeType)timeType +{ + if (![[self class] isMutable]) { return MIKMIDI_RAISE_MUTATION_ATTEMPT_EXCEPTION; } + + NSMutableData *timecodeData = [[self timecodeData] mutableCopy]; + if (!timecodeData) { + timecodeData = [NSMutableData dataWithLength:5]; + } + UInt8 *timecodeBytes = (UInt8 *)timecodeData.mutableBytes; + timecodeBytes[0] = (timecodeBytes[0] & 0x9F) + ((timeType & 0x03) << 5); + [self setTimecodeData:timecodeData]; +} + +#pragma mark Private + +- (NSTimeInterval)frameRate +{ + switch ([self timeType]) { + case MIKMMCLocateTargetCommandTimeType24FPS: + return 24.0; + break; + case MIKMMCLocateTargetCommandTimeType25FPS: + return 25.0; + case MIKMMCLocateTargetCommandTimeType30FPSDropFrame: // Not currently handling drop frame rates exactly correctly + return 29.97; + default: + case MIKMMCLocateTargetCommandTimeType30FPS: + return 30.0; + } +} + +- (NSData *)timecodeData +{ + NSUInteger byteCountIndex = 5; + if (self.includesThreeByteManufacturerID) { + byteCountIndex += 2; + } + if (self.data.length <= byteCountIndex) { return nil; } + + UInt8 byteCount = ((UInt8 *)self.data.bytes)[byteCountIndex]; + if (self.data.length < (byteCountIndex + byteCount)) { return nil; } + + UInt8 subcommandIndex = byteCountIndex += 1; + UInt8 subcommand = ((UInt8 *)self.data.bytes)[subcommandIndex]; + if (subcommand != 0x01) { return nil; } // Not a locate target command + + NSUInteger timecodeDataLocation = subcommandIndex+1; + NSData *timecodeData = [self.data subdataWithRange:NSMakeRange(timecodeDataLocation, byteCount-1)]; + return timecodeData; +} + +- (void)setTimecodeData:(NSData *)data +{ + NSUInteger requiredLength = 13; + if (self.includesThreeByteManufacturerID) { requiredLength += 2; } + if ([self.internalData length] < requiredLength) { + [self.internalData increaseLengthBy:requiredLength - [self.internalData length]]; + } + + NSUInteger byteCountIndex = 5; + if (self.includesThreeByteManufacturerID) { + byteCountIndex += 2; + } + ((UInt8 *)self.internalData.mutableBytes)[byteCountIndex] = 6; + + UInt8 subcommandIndex = byteCountIndex += 1; + ((UInt8 *)self.internalData.mutableBytes)[subcommandIndex] = 0x01; + + NSUInteger timecodeDataLocation = subcommandIndex+1; + [self.internalData replaceBytesInRange:NSMakeRange(timecodeDataLocation, 5) + withBytes:data.bytes length:data.length]; +} + +@end + +@implementation MIKMutableMMCLocateTargetCommand + ++ (BOOL)isMutable { return YES; } + +#pragma mark - Properties + +@dynamic timeCodeInSeconds; +@dynamic timeType; + +// MIKMIDICommand or MIKMIDIMachineControlCommand already implements these. This keeps the compiler happy. + +@dynamic deviceAddress; +@dynamic direction; +@dynamic MMCCommandType; + +@dynamic timestamp; +@dynamic dataByte1; +@dynamic dataByte2; +@dynamic midiTimestamp; +@dynamic data; +@dynamic commandType; + +@end From 5bbdf7c30e739ab3b09466465cc91fba6cc34bda Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Sun, 13 Feb 2022 21:42:42 -0700 Subject: [PATCH 43/53] Issue #327: Implement convenience method for creating MIKMMCLocateTargetCommand --- Source/MIKMMCLocateTargetCommand.h | 3 +++ Source/MIKMMCLocateTargetCommand.m | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/Source/MIKMMCLocateTargetCommand.h b/Source/MIKMMCLocateTargetCommand.h index 296d7246..c6921c2b 100644 --- a/Source/MIKMMCLocateTargetCommand.h +++ b/Source/MIKMMCLocateTargetCommand.h @@ -19,6 +19,9 @@ NS_ASSUME_NONNULL_BEGIN @interface MIKMMCLocateTargetCommand : MIKMIDIMachineControlCommand ++ (instancetype)locateTargetCommandWithTimeCodeInSeconds:(NSTimeInterval)timecode + timeType:(MIKMMCLocateTargetCommandTimeType)timeType; + @property (nonatomic, readonly) NSTimeInterval timeCodeInSeconds; @property (nonatomic, readonly) MIKMMCLocateTargetCommandTimeType timeType; diff --git a/Source/MIKMMCLocateTargetCommand.m b/Source/MIKMMCLocateTargetCommand.m index 42c54d12..023dd5f7 100644 --- a/Source/MIKMMCLocateTargetCommand.m +++ b/Source/MIKMMCLocateTargetCommand.m @@ -57,6 +57,15 @@ - (id)initWithMIDIPacket:(MIDIPacket *)packet return self; } ++ (instancetype)locateTargetCommandWithTimeCodeInSeconds:(NSTimeInterval)timecode + timeType:(MIKMMCLocateTargetCommandTimeType)timeType +{ + MIKMutableMMCLocateTargetCommand *result = [[MIKMutableMMCLocateTargetCommand alloc] init]; + result.timeType = timeType; + result.timeCodeInSeconds = timecode; + return [self isMutable] ? result : [result copy]; +} + #pragma mark - Properties #pragma mark Public From 5b60952a8f3a0c52ae51b8692cacee86bfaaa059 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Sun, 13 Feb 2022 21:43:20 -0700 Subject: [PATCH 44/53] Issue #327: Add -additionalCommandDescription implementation to MIKMMCLocateTargetCommand --- Source/MIKMMCLocateTargetCommand.m | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Source/MIKMMCLocateTargetCommand.m b/Source/MIKMMCLocateTargetCommand.m index 023dd5f7..8b0885eb 100644 --- a/Source/MIKMMCLocateTargetCommand.m +++ b/Source/MIKMMCLocateTargetCommand.m @@ -66,6 +66,11 @@ + (instancetype)locateTargetCommandWithTimeCodeInSeconds:(NSTimeInterval)timecod return [self isMutable] ? result : [result copy]; } +- (NSString *)additionalCommandDescription +{ + return [NSString stringWithFormat:@"timecode: %@", @(self.timeCodeInSeconds)]; +} + #pragma mark - Properties #pragma mark Public From 8214bd1d15040916eb371459288a2029079dc261 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Fri, 17 Jun 2022 10:56:36 -0600 Subject: [PATCH 45/53] Add tempo retrieval test --- Framework/MIKMIDI Tests/MIKMIDISequenceTests.m | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Framework/MIKMIDI Tests/MIKMIDISequenceTests.m b/Framework/MIKMIDI Tests/MIKMIDISequenceTests.m index e5e14f9c..2fba4e5e 100644 --- a/Framework/MIKMIDI Tests/MIKMIDISequenceTests.m +++ b/Framework/MIKMIDI Tests/MIKMIDISequenceTests.m @@ -227,6 +227,13 @@ - (void)testCopyingSequencePerformance }]; } +- (void)testTempoRetrieval +{ + MIKMIDISequence *sequence = [MIKMIDISequence sequence]; + [sequence setOverallTempo:75]; + XCTAssertEqual([sequence tempoAtTimeStamp:0], 75); +} + #pragma mark - KVO - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context From a77f943565e6608a79ff46b72237a135aab7baf8 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Sun, 19 Jun 2022 15:39:11 -0600 Subject: [PATCH 46/53] Issue #297: Fix weird bridging of -[MIKMIDIClientSourceEndpoint initWithName:error:] into Swift --- Source/MIKMIDIClientSourceEndpoint.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Source/MIKMIDIClientSourceEndpoint.h b/Source/MIKMIDIClientSourceEndpoint.h index 0986b0cd..dd00a4bb 100644 --- a/Source/MIKMIDIClientSourceEndpoint.h +++ b/Source/MIKMIDIClientSourceEndpoint.h @@ -33,7 +33,7 @@ NS_ASSUME_NONNULL_BEGIN * * @return An instance of MIKMIDIClientSourceEndpoint, or nil if an error occurs. */ -- (instancetype)initWithName:(NSString *)name error:(NSError **)error; +- (nullable instancetype)initWithName:(NSString *)name error:(NSError **)error; /** * Used to send MIDI messages/commands from your application to a MIDI output endpoint. @@ -66,8 +66,10 @@ NS_ASSUME_NONNULL_BEGIN * * @return An instance of MIKMIDIClientSourceEndpoint, or nil if an error occurs. */ -- (nullable instancetype)initWithName:(NSString *)name DEPRECATED_ATTRIBUTE; +- (nullable instancetype)initWithName:(NSString *)name +DEPRECATED_ATTRIBUTE +NS_SWIFT_UNAVAILABLE("Use the error throwing variant instead."); @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END From ac265274fedbefd69b47a249b6fee9dddbf4753a Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Sun, 19 Jun 2022 16:33:01 -0600 Subject: [PATCH 47/53] Update to latest recommended settings (Xcode 14.0 beta1) --- Framework/MIKMIDI.xcodeproj/project.pbxproj | 14 +++++++++++--- .../xcshareddata/xcschemes/MIKMIDI-iOS.xcscheme | 2 +- .../xcshareddata/xcschemes/MIKMIDI.xcscheme | 2 +- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/Framework/MIKMIDI.xcodeproj/project.pbxproj b/Framework/MIKMIDI.xcodeproj/project.pbxproj index bd36b2a2..1a9d27c9 100644 --- a/Framework/MIKMIDI.xcodeproj/project.pbxproj +++ b/Framework/MIKMIDI.xcodeproj/project.pbxproj @@ -1203,7 +1203,7 @@ 9D74EE9C17A7129300BEE89F /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1230; + LastUpgradeCheck = 1400; ORGANIZATIONNAME = "Mixed In Key"; TargetAttributes = { 9D2C28EA24E64F1B00DF62CD = { @@ -1481,6 +1481,7 @@ isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 2CX7W99FJ9; PRODUCT_NAME = "$(TARGET_NAME)"; }; @@ -1490,6 +1491,7 @@ isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 2CX7W99FJ9; PRODUCT_NAME = "$(TARGET_NAME)"; }; @@ -1503,6 +1505,7 @@ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; COMBINE_HIDPI_IMAGES = YES; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; FRAMEWORK_SEARCH_PATHS = ( @@ -1536,6 +1539,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; FRAMEWORK_SEARCH_PATHS = ( @@ -1589,6 +1593,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 877; + DEAD_CODE_STRIPPING = YES; DYLIB_CURRENT_VERSION = "${CURRENT_PROJECT_VERSION}"; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -1609,7 +1614,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MACOSX_DEPLOYMENT_TARGET = 10.9; MODULEMAP_FILE = ""; ONLY_ACTIVE_ARCH = YES; @@ -1651,6 +1656,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = YES; CURRENT_PROJECT_VERSION = 877; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DYLIB_CURRENT_VERSION = "${CURRENT_PROJECT_VERSION}"; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -1664,7 +1670,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MACOSX_DEPLOYMENT_TARGET = 10.9; MODULEMAP_FILE = ""; SDKROOT = macosx; @@ -1679,6 +1685,7 @@ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CODE_SIGN_IDENTITY = ""; COMBINE_HIDPI_IMAGES = YES; + DEAD_CODE_STRIPPING = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; FRAMEWORK_VERSION = A; @@ -1701,6 +1708,7 @@ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CODE_SIGN_IDENTITY = ""; COMBINE_HIDPI_IMAGES = YES; + DEAD_CODE_STRIPPING = YES; DEFINES_MODULE = YES; DEPLOYMENT_POSTPROCESSING = YES; DYLIB_COMPATIBILITY_VERSION = 1; diff --git a/Framework/MIKMIDI.xcodeproj/xcshareddata/xcschemes/MIKMIDI-iOS.xcscheme b/Framework/MIKMIDI.xcodeproj/xcshareddata/xcschemes/MIKMIDI-iOS.xcscheme index 42ea00f2..a33a0e8f 100644 --- a/Framework/MIKMIDI.xcodeproj/xcshareddata/xcschemes/MIKMIDI-iOS.xcscheme +++ b/Framework/MIKMIDI.xcodeproj/xcshareddata/xcschemes/MIKMIDI-iOS.xcscheme @@ -1,6 +1,6 @@ Date: Thu, 23 Jun 2022 11:35:40 -0600 Subject: [PATCH 48/53] Fix use of Mac-only -isEqualTo: causing build failure for iOS --- Source/MIKMIDICommand.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/MIKMIDICommand.m b/Source/MIKMIDICommand.m index 58150ddb..36ee1fd6 100644 --- a/Source/MIKMIDICommand.m +++ b/Source/MIKMIDICommand.m @@ -170,7 +170,7 @@ - (BOOL)isEqualToCommand:(MIKMIDICommand *)command // Sort so that deepest subclass hierarchy children come last return [result sortedArrayWithOptions:0 usingComparator:^NSComparisonResult(Class class1, Class class2) { - if ([class1 isEqualTo:class2]) { return NSOrderedSame; } + if ([class1 isEqual:class2]) { return NSOrderedSame; } if ([class1 isSubclassOfClass:class2]) { return NSOrderedDescending; } if ([class2 isSubclassOfClass:class1]) { return NSOrderedAscending; } return NSOrderedAscending; From c4011df03d61ec917029f4b22bbf9d41b3f99206 Mon Sep 17 00:00:00 2001 From: Patrick Machielse Date: Thu, 1 Feb 2024 16:00:12 +0100 Subject: [PATCH 49/53] Updated deployment target to macOS 10.11 to fix linking on Xcode 15.x + macOS 14 SDK. --- Framework/MIKMIDI.xcodeproj/project.pbxproj | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Framework/MIKMIDI.xcodeproj/project.pbxproj b/Framework/MIKMIDI.xcodeproj/project.pbxproj index 1a9d27c9..ab0a3740 100644 --- a/Framework/MIKMIDI.xcodeproj/project.pbxproj +++ b/Framework/MIKMIDI.xcodeproj/project.pbxproj @@ -1523,7 +1523,6 @@ GCC_WARN_UNUSED_FUNCTION = YES; INFOPLIST_FILE = "MIKMIDI Tests/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.10; MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.mixedinkey.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1553,7 +1552,6 @@ GCC_WARN_UNUSED_FUNCTION = YES; INFOPLIST_FILE = "MIKMIDI Tests/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.10; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = "com.mixedinkey.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1615,7 +1613,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 12.0; - MACOSX_DEPLOYMENT_TARGET = 10.9; + MACOSX_DEPLOYMENT_TARGET = 10.11; MODULEMAP_FILE = ""; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -1671,7 +1669,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 12.0; - MACOSX_DEPLOYMENT_TARGET = 10.9; + MACOSX_DEPLOYMENT_TARGET = 10.11; MODULEMAP_FILE = ""; SDKROOT = macosx; VERSIONING_SYSTEM = "apple-generic"; From 1447c9e6acfdc58472dba738e919f88de6beb93f Mon Sep 17 00:00:00 2001 From: Patrick Machielse Date: Wed, 9 Oct 2024 19:28:09 +0200 Subject: [PATCH 50/53] MIKMIDIPacketListSizeForCommands() now accounts for the fact that on ARM the MIDIPackets in a MIDIPacketList are 4-byte aligned, and not packed as they are on X86/PowerPC. --- Source/MIKMIDICommand.m | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Source/MIKMIDICommand.m b/Source/MIKMIDICommand.m index 36ee1fd6..ff02a5c9 100644 --- a/Source/MIKMIDICommand.m +++ b/Source/MIKMIDICommand.m @@ -384,6 +384,18 @@ ByteCount MIKMIDIPacketListSizeForCommands(NSArray *commands) return 0; } +#if defined(__arm__) || defined(__aarch64__) + // [4-byte aligned] + // Compute the size of static members of MIDIPacketList + ByteCount packetListSize = offsetof(MIDIPacketList, packet); + + for (MIKMIDICommand *command in commands) { + // Compute the size of MIDIPacket + ByteCount packetSize = offsetof(MIDIPacket, data) + command.data.length; + packetListSize += 4 * ((packetSize + 3) / 4); + } +#else + // [packed] // Compute the size of static members of MIDIPacketList and (MIDIPacket * [commands count]) ByteCount packetListSize = offsetof(MIDIPacketList, packet) + offsetof(MIDIPacket, data) * [commands count]; @@ -391,7 +403,7 @@ ByteCount MIKMIDIPacketListSizeForCommands(NSArray *commands) for (MIKMIDICommand *command in commands) { packetListSize += [[command data] length]; } - +#endif return packetListSize; } From ca6cbe4e3f4e23cd3a455a5b06602330209c5cdc Mon Sep 17 00:00:00 2001 From: Patrick Machielse Date: Thu, 17 Oct 2024 23:14:55 +0200 Subject: [PATCH 51/53] Better types and stricter value checking when looping over MIDIPacket data in [MIKMIDICommand commandsWithMIDIPacket:]. --- Source/MIKMIDICommand.m | 39 ++++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/Source/MIKMIDICommand.m b/Source/MIKMIDICommand.m index ff02a5c9..d83ffa47 100644 --- a/Source/MIKMIDICommand.m +++ b/Source/MIKMIDICommand.m @@ -55,16 +55,33 @@ + (instancetype)commandWithMIDIPacket:(MIDIPacket *)packet; + (NSArray *)commandsWithMIDIPacket:(MIDIPacket *)inputPacket { NSMutableArray *result = [NSMutableArray array]; - NSInteger dataOffset = 0; + ByteCount dataOffset = 0; while (dataOffset < inputPacket->length) { - const Byte *packetData = inputPacket->data + dataOffset; - MIKMIDICommandType commandType = (MIKMIDICommandType)packetData[0]; - NSInteger standardLength = MIKMIDIStandardLengthOfMessageForCommandType(commandType); - if (commandType == MIKMIDICommandTypeSystemExclusive) { - // For sysex, the packet can only contain a single MIDI message (as per documentation for MIDIPacket) - standardLength = inputPacket->length; + ByteCount eventDataLength = 0; + const Byte *eventData = inputPacket->data + dataOffset; + MIKMIDICommandType commandType = (MIKMIDICommandType)eventData[0]; + switch (commandType) { + // For sysex, the packet can only contain a single MIDI message (as per documentation for MIDIPacket) + case MIKMIDICommandTypeSystemExclusive: + eventDataLength = inputPacket->length; + break; + + // Is MIKMIDIStandardLengthOfMessageForCommandType() correct returning -1? + // This seems to be the realtime 'System Reset' message coming from a device + // (but could be a meta packet when coming from a file?) + case MIKMIDICommandTypeSystemMessage: + eventDataLength = 1; + break; + + default: { + __auto_type standardLength = MIKMIDIStandardLengthOfMessageForCommandType(commandType); + NSAssert(standardLength > 0, @"message length failed for command type %lu", commandType); + eventDataLength = (ByteCount)standardLength; + break; + } } - if (dataOffset > (inputPacket->length - standardLength)) break; + + if (dataOffset > (inputPacket->length - eventDataLength)) break; // This is gross, but it's the only way I can find to reliably create a // single-message MIDIPacket. @@ -74,12 +91,12 @@ + (NSArray *)commandsWithMIDIPacket:(MIDIPacket *)inputPacket sizeof(MIDIPacketList), midiPacket, inputPacket->timeStamp, - standardLength, - packetData); + eventDataLength, + eventData); MIKMIDICommand *command = [MIKMIDICommand commandWithMIDIPacket:midiPacket]; if (command) [result addObject:command]; - dataOffset += standardLength; + dataOffset += eventDataLength; } return result; From 302e7f802b5fee298917ceefee1fdf48d0724a6b Mon Sep 17 00:00:00 2001 From: Patrick Machielse Date: Wed, 6 Nov 2024 17:07:36 +0100 Subject: [PATCH 52/53] Fixed nullability issues. --- Source/MIKMIDIMapping.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/MIKMIDIMapping.m b/Source/MIKMIDIMapping.m index a42e10a8..45d70e4d 100644 --- a/Source/MIKMIDIMapping.m +++ b/Source/MIKMIDIMapping.m @@ -378,11 +378,11 @@ - (BOOL)loadPropertiesFromXMLDocument:(NSXMLDocument *)xmlDocument NSArray *nameAttributes = [mapping nodesForXPath:@"./@MappingName" error:&error]; if (!nameAttributes) NSLog(@"Unable to get name attributes from MIDI Mapping XML: %@", error); - self.name = [[nameAttributes lastObject] stringValue]; + self.name = [[nameAttributes lastObject] stringValue] ? : @""; NSArray *controllerNameAttributes = [mapping nodesForXPath:@"./@ControllerName" error:&error]; if (!controllerNameAttributes) NSLog(@"Unable to get controller name attributes from MIDI Mapping XML: %@", error); - self.controllerName = [[controllerNameAttributes lastObject] stringValue]; + self.controllerName = [[controllerNameAttributes lastObject] stringValue] ? : @""; NSArray *mappingItemElements = [mapping nodesForXPath:@"./MappingItems/MappingItem" error:&error]; if (!mappingItemElements) { @@ -471,4 +471,4 @@ - (instancetype)initWithFileAtURL:(NSURL *)url return [self initWithFileAtURL:url error:NULL]; } -@end \ No newline at end of file +@end From dbfab2906e92bf31d3414bdb421a2dc480ccba76 Mon Sep 17 00:00:00 2001 From: Patrick Machielse Date: Fri, 8 Nov 2024 15:52:48 +0100 Subject: [PATCH 53/53] Now defaulting to a command lenght of 1 if we couldn't figure it out. --- Source/MIKMIDICommand.m | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Source/MIKMIDICommand.m b/Source/MIKMIDICommand.m index d83ffa47..4c638935 100644 --- a/Source/MIKMIDICommand.m +++ b/Source/MIKMIDICommand.m @@ -75,8 +75,11 @@ + (NSArray *)commandsWithMIDIPacket:(MIDIPacket *)inputPacket default: { __auto_type standardLength = MIKMIDIStandardLengthOfMessageForCommandType(commandType); - NSAssert(standardLength > 0, @"message length failed for command type %lu", commandType); - eventDataLength = (ByteCount)standardLength; + if ( standardLength > 0 ) { + eventDataLength = (ByteCount)standardLength; + } else { /* -1 or NSIntegerMin */ + eventDataLength = 1; /* assume 1 and hope for the best */ + } break; } }