diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index 42fa1c73..3c9867e8 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -82,7 +82,6 @@ @interface MIKMIDISequencer () @property (nonatomic, strong) NSMutableDictionary *pendingRecordedNoteEvents; -@property (nonatomic) MusicTimeStamp playbackOffset; @property (nonatomic) MusicTimeStamp startingTimeStamp; @property (nonatomic, strong) NSMapTable *tracksToDestinationsMap; @@ -149,14 +148,25 @@ - (void)startPlayback } - (void)startPlaybackAtTimeStamp:(MusicTimeStamp)timeStamp +{ + [self startPlaybackAtTimeStamp:timeStamp adjustForPreRollWhenRecording:YES]; +} + +- (void)startPlaybackAtTimeStamp:(MusicTimeStamp)timeStamp adjustForPreRollWhenRecording:(BOOL)adjustForPreRoll { MIDITimeStamp midiTimeStamp = MIKMIDIGetCurrentTimeStamp() + MIKMIDIClockMIDITimeStampsPerTimeInterval(0.001); [self startPlaybackAtTimeStamp:timeStamp MIDITimeStamp:midiTimeStamp]; } - (void)startPlaybackAtTimeStamp:(MusicTimeStamp)timeStamp MIDITimeStamp:(MIDITimeStamp)midiTimeStamp +{ + [self startPlaybackAtTimeStamp:timeStamp MIDITimeStamp:midiTimeStamp adjustForPreRollWhenRecording:YES]; +} + +- (void)startPlaybackAtTimeStamp:(MusicTimeStamp)timeStamp MIDITimeStamp:(MIDITimeStamp)midiTimeStamp adjustForPreRollWhenRecording:(BOOL)adjustForPreRoll { if (self.isPlaying) [self stop]; + if (adjustForPreRoll && self.isRecording) timeStamp -= self.preRoll; NSString *queueLabel = [[[NSBundle mainBundle] bundleIdentifier] stringByAppendingFormat:@".%@.%p", [self class], self]; dispatch_queue_attr_t attr = DISPATCH_QUEUE_SERIAL; @@ -172,10 +182,9 @@ - (void)startPlaybackAtTimeStamp:(MusicTimeStamp)timeStamp MIDITimeStamp:(MIDITi self.processingQueue = queue; dispatch_sync(queue, ^{ - MusicTimeStamp startingTimeStamp = timeStamp + self.playbackOffset; - self.startingTimeStamp = startingTimeStamp; + self.startingTimeStamp = timeStamp; - Float64 startingTempo = [self.sequence tempoAtTimeStamp:startingTimeStamp]; + Float64 startingTempo = [self.sequence tempoAtTimeStamp:timeStamp]; if (!startingTempo) startingTempo = kDefaultTempo; [self updateClockWithMusicTimeStamp:timeStamp tempo:startingTempo atMIDITimeStamp:midiTimeStamp]; }); @@ -252,7 +261,7 @@ - (void)stopWithDispatchToProcessingQueue:(BOOL)dispatchToProcessingQueue self.looping = NO; MusicTimeStamp stopMusicTimeStamp = [clock musicTimeStampForMIDITimeStamp:stopTimeStamp]; - _currentTimeStamp = (stopMusicTimeStamp <= self.sequenceLength + self.playbackOffset) ? stopMusicTimeStamp - self.playbackOffset : self.sequenceLength; + _currentTimeStamp = (stopMusicTimeStamp <= self.sequenceLength) ? stopMusicTimeStamp : self.sequenceLength; [clock unsyncMusicTimeStampsAndTemposFromMIDITimeStamps]; }; @@ -260,7 +269,6 @@ - (void)stopWithDispatchToProcessingQueue:(BOOL)dispatchToProcessingQueue dispatchToProcessingQueue ? dispatch_sync(self.processingQueue, stopPlayback) : stopPlayback(); self.processingQueue = NULL; - self.playbackOffset = 0; self.playing = NO; self.recording = NO; } @@ -272,9 +280,8 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam MIKMIDIClock *clock = self.clock; MIKMIDISequence *sequence = self.sequence; - MusicTimeStamp playbackOffset = self.playbackOffset; - MusicTimeStamp loopStartTimeStamp = self.loopStartTimeStamp + playbackOffset; - MusicTimeStamp loopEndTimeStamp = self.effectiveLoopEndTimeStamp + playbackOffset; + MusicTimeStamp loopStartTimeStamp = self.loopStartTimeStamp; + MusicTimeStamp loopEndTimeStamp = self.effectiveLoopEndTimeStamp; MusicTimeStamp fromMusicTimeStamp = [clock musicTimeStampForMIDITimeStamp:fromMIDITimeStamp]; MusicTimeStamp calculatedToMusicTimeStamp = [clock musicTimeStampForMIDITimeStamp:toMIDITimeStamp]; BOOL isLooping = (self.shouldLoop && calculatedToMusicTimeStamp > loopStartTimeStamp && loopEndTimeStamp > loopStartTimeStamp); @@ -290,9 +297,9 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam Float64 overrideTempo = self.tempo; if (!overrideTempo) { - NSArray *sequenceTempoEvents = [sequence.tempoTrack eventsOfClass:[MIKMIDITempoEvent class] fromTimeStamp:MAX(fromMusicTimeStamp - playbackOffset, 0) toTimeStamp:toMusicTimeStamp - playbackOffset]; + NSArray *sequenceTempoEvents = [sequence.tempoTrack eventsOfClass:[MIKMIDITempoEvent class] fromTimeStamp:MAX(fromMusicTimeStamp, 0) toTimeStamp:toMusicTimeStamp]; for (MIKMIDITempoEvent *tempoEvent in sequenceTempoEvents) { - NSNumber *timeStampKey = @(tempoEvent.timeStamp + playbackOffset); + NSNumber *timeStampKey = @(tempoEvent.timeStamp); allEventsByTimeStamp[timeStampKey] = [NSMutableArray arrayWithObject:tempoEvent]; tempoEventsByTimeStamp[timeStampKey] = tempoEvent; } @@ -326,7 +333,6 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam } // Get other events - NSMutableArray *nonMutedTracks = [[NSMutableArray alloc] init]; NSMutableArray *soloTracks = [[NSMutableArray alloc] init]; for (MIKMIDITrack *track in sequence.tracks) { @@ -340,8 +346,8 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam NSArray *tracksToPlay = soloTracks.count != 0 ? soloTracks : nonMutedTracks; for (MIKMIDITrack *track in tracksToPlay) { - MusicTimeStamp startTimeStamp = MAX(fromMusicTimeStamp - playbackOffset - track.offset, 0); - MusicTimeStamp endTimeStamp = toMusicTimeStamp - playbackOffset - track.offset; + MusicTimeStamp startTimeStamp = MAX(fromMusicTimeStamp - track.offset, 0); + MusicTimeStamp endTimeStamp = toMusicTimeStamp - track.offset; NSArray *events = [track eventsFromTimeStamp:startTimeStamp toTimeStamp:endTimeStamp]; if (track.offset != 0) { // Shift events by offset @@ -356,7 +362,7 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam id destination = events.count ? [self commandSchedulerForTrack:track] : nil; // only get the destination if there's events so we don't create a destination endpoint if not needed for (MIKMIDIEvent *event in events) { - NSNumber *timeStampKey = @(event.timeStamp + playbackOffset); + NSNumber *timeStampKey = @(event.timeStamp); NSMutableArray *eventsAtTimeStamp = allEventsByTimeStamp[timeStampKey] ? allEventsByTimeStamp[timeStampKey] : [NSMutableArray array]; [eventsAtTimeStamp addObject:[MIKMIDIEventWithDestination eventWithDestination:destination event:event]]; allEventsByTimeStamp[timeStampKey] = eventsAtTimeStamp; @@ -365,7 +371,7 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam // Get click track events for (MIKMIDIEventWithDestination *destinationEvent in [self clickTrackEventsFromTimeStamp:fromMusicTimeStamp toTimeStamp:toMusicTimeStamp]) { - NSNumber *timeStampKey = @(destinationEvent.event.timeStamp + playbackOffset); + NSNumber *timeStampKey = @(destinationEvent.event.timeStamp); NSMutableArray *eventsAtTimesStamp = allEventsByTimeStamp[timeStampKey] ? allEventsByTimeStamp[timeStampKey] : [NSMutableArray array]; [eventsAtTimesStamp addObject:destinationEvent]; allEventsByTimeStamp[timeStampKey] = eventsAtTimesStamp; @@ -413,7 +419,7 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam } } else if (!self.isRecording) { // Don't stop automatically during recording MIDITimeStamp systemTimeStamp = MIKMIDIGetCurrentTimeStamp(); - if ((systemTimeStamp > actualToMIDITimeStamp) && ([clock musicTimeStampForMIDITimeStamp:systemTimeStamp] >= self.sequenceLength + playbackOffset)) { + if ((systemTimeStamp > actualToMIDITimeStamp) && ([clock musicTimeStampForMIDITimeStamp:systemTimeStamp] >= self.sequenceLength)) { [self stopWithDispatchToProcessingQueue:NO]; } } @@ -447,12 +453,7 @@ - (void)scheduleEventWithDestination:(MIKMIDIEventWithDestination *)destinationE command = [MIKMIDICommand commandFromChannelEvent:(MIKMIDIChannelEvent *)event clock:clock]; } - // Adjust command time stamp to account for our playback offset. - if (command) { - MIKMutableMIDICommand *adjustedCommand = [command mutableCopy]; - adjustedCommand.midiTimestamp += [clock midiTimeStampsPerMusicTimeStamp:self.playbackOffset]; - [self scheduleCommands:@[adjustedCommand] withCommandScheduler:destination]; - } + if (command) [self scheduleCommands:@[command] withCommandScheduler:destination]; } - (void)sendAllPendingNoteOffsWithMIDITimeStamp:(MIDITimeStamp)offTimeStamp @@ -528,7 +529,6 @@ - (void)resumeRecording - (void)prepareForRecordingWithPreRoll:(BOOL)includePreRoll { self.pendingRecordedNoteEvents = [NSMutableDictionary dictionary]; - if (includePreRoll) self.playbackOffset = self.preRoll; self.recording = YES; } @@ -537,9 +537,7 @@ - (void)recordMIDICommand:(MIKMIDICommand *)command if (!self.isRecording) return; MIDITimeStamp midiTimeStamp = command.midiTimestamp; - - MusicTimeStamp playbackOffset = self.playbackOffset; - MusicTimeStamp musicTimeStamp = [self.clock musicTimeStampForMIDITimeStamp:midiTimeStamp] - playbackOffset; + MusicTimeStamp musicTimeStamp = [self.clock musicTimeStampForMIDITimeStamp:midiTimeStamp]; MIKMIDIEvent *event; if ([command isKindOfClass:[MIKMIDINoteOnCommand class]]) { // note On @@ -648,18 +646,17 @@ - (NSMutableArray *)clickTrackEventsFromTimeStamp:(MusicTimeStamp)fromTimeStamp MIDINoteMessage tockMessage = metronome.tockMessage; MIKMIDISequence *sequence = self.sequence; - MusicTimeStamp playbackOffset = self.playbackOffset; - MIKMIDITimeSignature timeSignature = [sequence timeSignatureAtTimeStamp:fromTimeStamp - playbackOffset]; + MIKMIDITimeSignature timeSignature = [sequence timeSignatureAtTimeStamp:MAX(fromTimeStamp, 0)]; NSMutableArray *timeSignatureEvents = [[sequence.tempoTrack eventsOfClass:[MIKMIDIMetaTimeSignatureEvent class] - fromTimeStamp:MAX(fromTimeStamp - playbackOffset, 0) - toTimeStamp:toTimeStamp] mutableCopy]; + fromTimeStamp:MAX(fromTimeStamp, 0) + toTimeStamp:MAX(toTimeStamp, 0)] mutableCopy]; MusicTimeStamp clickTimeStamp = floor(fromTimeStamp); while (clickTimeStamp <= toTimeStamp) { - if (clickTrackStatus == MIKMIDISequencerClickTrackStatusEnabledOnlyInPreRoll && clickTimeStamp >= self.startingTimeStamp) break; + if (clickTrackStatus == MIKMIDISequencerClickTrackStatusEnabledOnlyInPreRoll && clickTimeStamp >= self.startingTimeStamp + self.preRoll) break; MIKMIDIMetaTimeSignatureEvent *event = [timeSignatureEvents firstObject]; - if (event && event.timeStamp - playbackOffset <= clickTimeStamp) { + if (event && event.timeStamp <= clickTimeStamp) { timeSignature = (MIKMIDITimeSignature) { .numerator = event.numerator, .denominator = event.denominator }; [timeSignatureEvents removeObjectAtIndex:0]; } @@ -668,7 +665,7 @@ - (NSMutableArray *)clickTrackEventsFromTimeStamp:(MusicTimeStamp)fromTimeStamp NSInteger adjustedTimeStamp = clickTimeStamp * timeSignature.denominator / 4.0; BOOL isTick = !((adjustedTimeStamp + timeSignature.numerator) % (timeSignature.numerator)); MIDINoteMessage clickMessage = isTick ? tickMessage : tockMessage; - MIKMIDINoteEvent *noteEvent = [MIKMIDINoteEvent noteEventWithTimeStamp:clickTimeStamp - playbackOffset message:clickMessage]; + MIKMIDINoteEvent *noteEvent = [MIKMIDINoteEvent noteEventWithTimeStamp:clickTimeStamp message:clickMessage]; [clickEvents addObject:[MIKMIDIEventWithDestination eventWithDestination:metronome event:noteEvent]]; } @@ -737,8 +734,7 @@ - (MusicTimeStamp)currentTimeStamp MIKMIDIClock *clock = self.clock; if (clock.isReady) { MusicTimeStamp timeStamp = [clock musicTimeStampForMIDITimeStamp:MIKMIDIGetCurrentTimeStamp()]; - MusicTimeStamp playbackOffset = self.playbackOffset; - _currentTimeStamp = MAX(((timeStamp <= self.sequenceLength + playbackOffset) ? timeStamp - playbackOffset : self.sequenceLength), self.startingTimeStamp); + _currentTimeStamp = MAX(((timeStamp <= self.sequenceLength) ? timeStamp : self.sequenceLength), self.startingTimeStamp); } return _currentTimeStamp; } @@ -749,7 +745,7 @@ - (void)setCurrentTimeStamp:(MusicTimeStamp)currentTimeStamp BOOL isRecording = self.isRecording; [self stop]; if (isRecording) [self prepareForRecordingWithPreRoll:NO]; - [self startPlaybackAtTimeStamp:currentTimeStamp]; + [self startPlaybackAtTimeStamp:currentTimeStamp adjustForPreRollWhenRecording:NO]; } else { _currentTimeStamp = currentTimeStamp; }