Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds support to MIKMIDISequencer for muted, solo, and offset properties of MIKMIDITrack. #138

Merged
merged 7 commits into from
Mar 8, 2016
2 changes: 1 addition & 1 deletion Source/MIKMIDINoteEvent.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, readonly) Float32 duration;

/**
* The time stamp at the end of the notes duration.
* The time stamp at the end of the notes duration. This is simply the event's timeStamp + duration.
*/
@property (nonatomic, readonly) MusicTimeStamp endTimeStamp;

Expand Down
28 changes: 27 additions & 1 deletion Source/MIKMIDISequencer.m
Original file line number Diff line number Diff line change
Expand Up @@ -326,8 +326,34 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam
}

// Get other events

NSMutableArray *nonMutedTracks = [[NSMutableArray alloc] init];
NSMutableArray *soloTracks = [[NSMutableArray alloc] init];
for (MIKMIDITrack *track in sequence.tracks) {
NSArray *events = [track eventsFromTimeStamp:MAX(fromMusicTimeStamp - playbackOffset, 0) toTimeStamp:toMusicTimeStamp - playbackOffset];
if (track.isMuted) continue;

[nonMutedTracks addObject:track];
if (track.solo) { [soloTracks addObject:track]; }
}

// Never play muted tracks. If any non-muted tracks are soloed, only play those. Matches MusicPlayer behavior
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;
NSArray *events = [track eventsFromTimeStamp:startTimeStamp toTimeStamp:endTimeStamp];
if (track.offset != 0) {
// Shift events by offset
NSMutableArray *shiftedEvents = [NSMutableArray array];
for (MIKMIDIEvent *event in events) {
MIKMutableMIDIEvent *shiftedEvent = [event mutableCopy];
shiftedEvent.timeStamp += track.offset;
[shiftedEvents addObject:shiftedEvent];
}
events = shiftedEvents;
}

id<MIKMIDICommandScheduler> 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);
Expand Down
18 changes: 8 additions & 10 deletions Source/MIKMIDITrack.h
Original file line number Diff line number Diff line change
Expand Up @@ -185,30 +185,28 @@ NS_ASSUME_NONNULL_BEGIN
/**
* A MIDI track’s start time in terms of beat number. By default this value is 0.
*
* This property can be observed using Key Value Observing.
* This can be used to offset playback by MIKMIDISequencer of individual tracks in a sequence.
*
* @note This property is not yet used by MIKMIDISequencer (Issue #99). If this property
* is required for your playback situation, you should stick with MIKMIDIPlayer in the meantime.
* This property can be observed using Key Value Observing.
*/
@property (nonatomic) MusicTimeStamp offset;

/**
* Whether or not the MIDI track is muted.
* Whether or not the MIDI track is muted. Muted tracks are not played by MIKMIDISequencer.
*
* This property can be observed using Key Value Observing.
*
* @note This property is not yet used by MIKMIDISequencer (Issue #99). If this property
* is required for your playback situation, you should stick with MIKMIDIPlayer in the meantime.
*/
@property (nonatomic, getter = isMuted) BOOL muted;

/**
* Whether or not the MIDI track is soloed.
*
* This property can be observed using Key Value Observing.
* If a track is muted, it is not played by MIKMIDISequencer, and its solo status is ignored.
* If any non-muted tracks in a sequence have this property set to YES, MIKMIDISequencer will only play those,
* and will not play the non-solo tracks. If this is set to NO for all non-muted tracks, then
* all non-muted tracks will be played.
*
* @note This property is not yet used by MIKMIDISequencer (Issue #99). If this property
* is required for your playback situation, you should stick with MIKMIDIPlayer in the meantime.
* This property can be observed using Key Value Observing.
*/
@property (nonatomic, getter = isSolo) BOOL solo;

Expand Down
82 changes: 59 additions & 23 deletions Source/MIKMIDITrack.m
Original file line number Diff line number Diff line change
Expand Up @@ -616,51 +616,87 @@ - (NSInteger)trackNumber
return (NSInteger)trackNumber;
}

@synthesize offset = _offset;

- (MusicTimeStamp)offset
{
MusicTimeStamp offset = 0;
UInt32 offsetLength = sizeof(offset);
OSStatus err = MusicTrackGetProperty(self.musicTrack, kSequenceTrackProperty_OffsetTime, &offset, &offsetLength);
if (err) NSLog(@"MusicTrackGetProperty() failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__);
return offset;
if (_offset != 0) return _offset;

if (self.musicTrack) {
MusicTimeStamp offset = 0;
UInt32 offsetLength = sizeof(offset);
OSStatus err = MusicTrackGetProperty(self.musicTrack, kSequenceTrackProperty_OffsetTime, &offset, &offsetLength);
if (err) NSLog(@"MusicTrackGetProperty() failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__);
return offset;
} else {
return 0;
}
}

- (void)setOffset:(MusicTimeStamp)offset
{
OSStatus err = MusicTrackSetProperty(self.musicTrack, kSequenceTrackProperty_OffsetTime, &offset, sizeof(offset));
if (err) NSLog(@"MusicTrackSetProperty() failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__);
_offset = offset;

if (self.musicTrack) {
OSStatus err = MusicTrackSetProperty(self.musicTrack, kSequenceTrackProperty_OffsetTime, &offset, sizeof(offset));
if (err) NSLog(@"MusicTrackSetProperty() failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__);
}
}

@synthesize muted = _muted;

- (BOOL)isMuted
{
Boolean isMuted = FALSE;
UInt32 isMutedLength = sizeof(isMuted);
OSStatus err = MusicTrackGetProperty(self.musicTrack, kSequenceTrackProperty_MuteStatus, &isMuted, &isMutedLength);
if (err) NSLog(@"MusicTrackGetProperty() failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__);
return isMuted ? YES : NO;
if (_muted) return YES;

if (self.musicTrack) {
Boolean isMuted = FALSE;
UInt32 isMutedLength = sizeof(isMuted);
OSStatus err = MusicTrackGetProperty(self.musicTrack, kSequenceTrackProperty_MuteStatus, &isMuted, &isMutedLength);
if (err) NSLog(@"MusicTrackGetProperty() failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__);
return isMuted ? YES : NO;
} else {
return NO;
}
}

- (void)setMuted:(BOOL)muted
{
Boolean mutedBoolean = muted ? TRUE : FALSE;
OSStatus err = MusicTrackSetProperty(self.musicTrack, kSequenceTrackProperty_MuteStatus, &mutedBoolean, sizeof(mutedBoolean));
if (err) NSLog(@"MusicTrackSetProperty() failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__);
_muted = muted;

if (self.musicTrack) {
Boolean mutedBoolean = muted ? TRUE : FALSE;
OSStatus err = MusicTrackSetProperty(self.musicTrack, kSequenceTrackProperty_MuteStatus, &mutedBoolean, sizeof(mutedBoolean));
if (err) NSLog(@"MusicTrackSetProperty() failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__);
}
}

@synthesize solo = _solo;

- (BOOL)isSolo
{
Boolean isSolo = FALSE;
UInt32 isSoloLength = sizeof(isSolo);
OSStatus err = MusicTrackGetProperty(self.musicTrack, kSequenceTrackProperty_SoloStatus, &isSolo, &isSoloLength);
if (err) NSLog(@"MusicTrackGetProperty() failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__);
return isSolo ? YES : NO;
if (_solo) return YES;

if (self.musicTrack) {
Boolean isSolo = FALSE;
UInt32 isSoloLength = sizeof(isSolo);
OSStatus err = MusicTrackGetProperty(self.musicTrack, kSequenceTrackProperty_SoloStatus, &isSolo, &isSoloLength);
if (err) NSLog(@"MusicTrackGetProperty() failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__);
return isSolo ? YES : NO;
} else {
return NO;
}
}

- (void)setSolo:(BOOL)solo
{
Boolean soloBoolean = solo ? TRUE : FALSE;
OSStatus err = MusicTrackSetProperty(self.musicTrack, kSequenceTrackProperty_SoloStatus, &soloBoolean, sizeof(soloBoolean));
if (err) NSLog(@"MusicTrackSetProperty() failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__);
_solo = solo;

if (self.musicTrack) {
Boolean soloBoolean = solo ? TRUE : FALSE;
OSStatus err = MusicTrackSetProperty(self.musicTrack, kSequenceTrackProperty_SoloStatus, &soloBoolean, sizeof(soloBoolean));
if (err) NSLog(@"MusicTrackSetProperty() failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__);
}
}

+ (NSSet *)keyPathsForValuesAffectingLength
Expand Down