From 413876ba2b3cb0e3d270d2da9bf6d4f3bba5fe8d Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Wed, 20 May 2015 17:07:15 -0500 Subject: [PATCH] MIKMIDISequencer now sends out a notification prior to looping playback. Also added syncedClock properties to MIKMIDIClock and MIKMIDISequencer. --- Source/MIKMIDIClock.h | 17 +++++++++++++ Source/MIKMIDIClock.m | 53 ++++++++++++++++++++++++++++++++++++++- Source/MIKMIDISequencer.h | 9 +++++++ Source/MIKMIDISequencer.m | 31 +++++++++++++++-------- 4 files changed, 99 insertions(+), 11 deletions(-) diff --git a/Source/MIKMIDIClock.h b/Source/MIKMIDIClock.h index 001ef363..a5b7f374 100644 --- a/Source/MIKMIDIClock.h +++ b/Source/MIKMIDIClock.h @@ -99,5 +99,22 @@ */ - (MIDITimeStamp)midiTimeStampsPerMusicTimeStamp:(MusicTimeStamp)musicTimeStamp; + +/** + * A readonly copy of the clock that remains synced with this instance. + * + * This clock can be queried and will always return the same timing information + * as the clock instance that dispensed the synced clock. + * + * Attempting to call -setMusicTimeStamp:withTempo:atMusicTimeStamp on the synced + * has no effect. + */ +- (MIKMIDIClock *)syncedClock; + +/** + * The tempo that was set in the last call to -setMusicTimeStamp:withTempo:atMIDITimeStamp: + */ +@property (readonly, nonatomic) Float64 tempo; + @end diff --git a/Source/MIKMIDIClock.m b/Source/MIKMIDIClock.m index a76cb111..40460030 100644 --- a/Source/MIKMIDIClock.m +++ b/Source/MIKMIDIClock.m @@ -13,13 +13,24 @@ #error MIKMIDIClock.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIMappingManager.m in the Build Phases for this target #endif + +#pragma mark - +@interface MIKMIDISyncedClockProxy : NSProxy ++ (instancetype)syncedClockWithClock:(MIKMIDIClock *)masterClock; +@property (readonly, nonatomic) MIKMIDIClock *masterClock; +@end + + +#pragma mark - @interface MIKMIDIClock () +@property (nonatomic) Float64 tempo; @property (nonatomic) MIDITimeStamp timeStampZero; @property (nonatomic) Float64 musicTimeStampsPerMIDITimeStamp; @property (nonatomic) Float64 midiTimeStampsPerMusicTimeStamp; @end +#pragma mark - @implementation MIKMIDIClock #pragma mark - Lifecycle @@ -37,6 +48,7 @@ - (void)setMusicTimeStamp:(MusicTimeStamp)musicTimeStamp withTempo:(Float64)temp Float64 secondsPerMusicTimeStamp = 1.0 / (tempo / 60.0); Float64 midiTimeStampsPerMusicTimeStamp = secondsPerMusicTimeStamp / secondsPerMIDITimeStamp; + self.tempo = tempo; self.timeStampZero = midiTimeStamp - (musicTimeStamp * midiTimeStampsPerMusicTimeStamp); self.midiTimeStampsPerMusicTimeStamp = midiTimeStampsPerMusicTimeStamp; self.musicTimeStampsPerMIDITimeStamp = secondsPerMIDITimeStamp / secondsPerMusicTimeStamp; @@ -44,7 +56,8 @@ - (void)setMusicTimeStamp:(MusicTimeStamp)musicTimeStamp withTempo:(Float64)temp - (MusicTimeStamp)musicTimeStampForMIDITimeStamp:(MIDITimeStamp)midiTimeStamp { - return (midiTimeStamp - self.timeStampZero) * self.musicTimeStampsPerMIDITimeStamp; + MIDITimeStamp timeStampZero = self.timeStampZero; + return (midiTimeStamp >= timeStampZero) ? ((midiTimeStamp - timeStampZero) * self.musicTimeStampsPerMIDITimeStamp) : -((midiTimeStamp - timeStampZero) * self.musicTimeStampsPerMIDITimeStamp); } - (MIDITimeStamp)midiTimeStampForMusicTimeStamp:(MusicTimeStamp)musicTimeStamp @@ -87,4 +100,42 @@ - (id)copyWithZone:(NSZone *)zone return clock; } +#pragma mark - Synced Clock + +- (MIKMIDIClock *)syncedClock +{ + return (MIKMIDIClock *)[MIKMIDISyncedClockProxy syncedClockWithClock:self]; +} + @end + + +#pragma mark - +@implementation MIKMIDISyncedClockProxy + ++ (instancetype)syncedClockWithClock:(MIKMIDIClock *)masterClock +{ + MIKMIDISyncedClockProxy *proxy = [self alloc]; + proxy->_masterClock = masterClock; + return proxy; +} + +- (void)forwardInvocation:(NSInvocation *)invocation +{ + SEL selector = invocation.selector; + if (selector == @selector(setMusicTimeStamp:withTempo:atMIDITimeStamp:)) return; + + if (selector == @selector(syncedClock)) { + MIKMIDISyncedClockProxy *syncedClock = self; + return [invocation setReturnValue:&syncedClock]; + } + + [invocation invokeWithTarget:self.masterClock]; +} + +- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel +{ + return [self.masterClock methodSignatureForSelector:sel]; +} + +@end \ No newline at end of file diff --git a/Source/MIKMIDISequencer.h b/Source/MIKMIDISequencer.h index e475f86a..b8c88786 100644 --- a/Source/MIKMIDISequencer.h +++ b/Source/MIKMIDISequencer.h @@ -15,6 +15,7 @@ @class MIKMIDICommand; @class MIKMIDIDestinationEndpoint; @class MIKMIDISynthesizer; +@class MIKMIDIClock; /** * Types of click track statuses, that determine when the click track will be audible. @@ -311,4 +312,12 @@ typedef NS_ENUM(NSInteger, MIKMIDISequencerClickTrackStatus) { */ @property (copy, nonatomic) NSSet *recordEnabledTracks; +/** + * An MIKMIDIClock that is synced with the sequencer's internal clock. + */ +@property (readonly, nonatomic) MIKMIDIClock *syncedClock; + @end + + +FOUNDATION_EXPORT NSString * const MIKMIDISequencerWillLoopNotification; diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index 8e368313..6e005821 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -29,6 +29,8 @@ #define MIKMIDISequencerDefaultTempo 120 +NSString * const MIKMIDISequencerWillLoopNotification = @"MIKMIDISequencerWillLoopNotification"; + #pragma mark - @@ -91,6 +93,7 @@ - (instancetype)initWithSequence:(MIKMIDISequence *)sequence if (self = [super init]) { _sequence = sequence; _clock = [MIKMIDIClock clock]; + _syncedClock = [_clock syncedClock]; _loopEndTimeStamp = -1; _preRoll = 4; _clickTrackStatus = MIKMIDISequencerClickTrackStatusEnabledInRecord; @@ -275,6 +278,8 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam MIDITimeStamp loopStartMIDITimeStamp = [clock midiTimeStampForMusicTimeStamp:loopStartTimeStamp + loopLength]; [self updateClockWithMusicTimeStamp:loopStartTimeStamp tempo:tempo atMIDITimeStamp:loopStartMIDITimeStamp]; + + [[NSNotificationCenter defaultCenter] postNotificationName:MIKMIDISequencerWillLoopNotification object:self userInfo:nil]; [self processSequenceStartingFromMIDITimeStamp:loopStartMIDITimeStamp]; } } else if (!self.isRecording) { // Don't stop automatically during recording @@ -360,6 +365,20 @@ - (void)sendPendingNoteOffCommandsUpToMIDITimeStamp:(MIDITimeStamp)toTimeStamp } } +- (MIKMIDIClock *)clockForMIDITimeStamp:(MIDITimeStamp)midiTimeStamp +{ + MIKMIDIClock *clock; + for (NSNumber *historicalClockTimeStamp in [[self.historicalClockMIDITimeStamps reverseObjectEnumerator] allObjects]) { + if ([historicalClockTimeStamp unsignedLongLongValue] > midiTimeStamp) { + clock = self.historicalClocks[historicalClockTimeStamp]; + } else { + break; + } + } + if (!clock) clock = self.clock; + return clock; +} + - (void)updateClockWithMusicTimeStamp:(MusicTimeStamp)musicTimeStamp tempo:(Float64)tempo atMIDITimeStamp:(MIDITimeStamp)midiTimeStamp { // Override tempo if neccessary @@ -450,16 +469,8 @@ - (void)recordMIDICommand:(MIKMIDICommand *)command if (!self.isRecording) return; MIDITimeStamp midiTimeStamp = command.midiTimestamp; - MIKMIDIClock *clockAtTimeStamp; - for (NSNumber *historicalClockTimeStamp in [[self.historicalClockMIDITimeStamps reverseObjectEnumerator] allObjects]) { - if ([historicalClockTimeStamp unsignedLongLongValue] > midiTimeStamp) { - clockAtTimeStamp = self.historicalClocks[historicalClockTimeStamp]; - } else { - break; - } - } - if (!clockAtTimeStamp) clockAtTimeStamp = self.clock; - + MIKMIDIClock *clockAtTimeStamp = [self clockForMIDITimeStamp:midiTimeStamp]; + MusicTimeStamp playbackOffset = self.playbackOffset; MusicTimeStamp musicTimeStamp = [clockAtTimeStamp musicTimeStampForMIDITimeStamp:midiTimeStamp] - playbackOffset;