Skip to content

Commit

Permalink
MIKMIDISequencer now sends out a notification prior to looping playba…
Browse files Browse the repository at this point in the history
…ck. Also added syncedClock properties to MIKMIDIClock and MIKMIDISequencer.
  • Loading branch information
kris2point0 committed May 20, 2015
1 parent 42b6de0 commit 413876b
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 11 deletions.
17 changes: 17 additions & 0 deletions Source/MIKMIDIClock.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

53 changes: 52 additions & 1 deletion Source/MIKMIDIClock.m
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -37,14 +48,16 @@ - (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;
}

- (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
Expand Down Expand Up @@ -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
9 changes: 9 additions & 0 deletions Source/MIKMIDISequencer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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;
31 changes: 21 additions & 10 deletions Source/MIKMIDISequencer.m
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@

#define MIKMIDISequencerDefaultTempo 120

NSString * const MIKMIDISequencerWillLoopNotification = @"MIKMIDISequencerWillLoopNotification";


#pragma mark -

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;

Expand Down

0 comments on commit 413876b

Please sign in to comment.