478 lines
16 KiB
Objective-C
478 lines
16 KiB
Objective-C
//
|
|
// MIKMIDISequencer.h
|
|
// MIKMIDI
|
|
//
|
|
// Created by Chris Flesner on 11/26/14.
|
|
// Copyright (c) 2014 Mixed In Key. All rights reserved.
|
|
//
|
|
|
|
#import <Foundation/Foundation.h>
|
|
#import <AudioToolbox/AudioToolbox.h>
|
|
#import "MIKMIDICompilerCompatibility.h"
|
|
|
|
@class MIKMIDISequence;
|
|
@class MIKMIDITrack;
|
|
@class MIKMIDIMetronome;
|
|
@class MIKMIDICommand;
|
|
@class MIKMIDIDestinationEndpoint;
|
|
@class MIKMIDISynthesizer;
|
|
@class MIKMIDIClock;
|
|
@protocol MIKMIDICommandScheduler;
|
|
|
|
/**
|
|
* Types of click track statuses, that determine when the click track will be audible.
|
|
*
|
|
* @see clickTrackStatus
|
|
*/
|
|
typedef NS_ENUM(NSInteger, MIKMIDISequencerClickTrackStatus) {
|
|
/** The click track will not be heard during playback or recording. */
|
|
MIKMIDISequencerClickTrackStatusDisabled,
|
|
/** The click track will only be heard while recording. */
|
|
MIKMIDISequencerClickTrackStatusEnabledInRecord,
|
|
/** The click track will only be heard while recording and while the playback position is still in the pre-roll. */
|
|
MIKMIDISequencerClickTrackStatusEnabledOnlyInPreRoll,
|
|
/** The click track will always be heard during playback and recording. */
|
|
MIKMIDISequencerClickTrackStatusAlwaysEnabled
|
|
};
|
|
|
|
NS_ASSUME_NONNULL_BEGIN
|
|
|
|
/**
|
|
* MIKMIDISequencer can be used to play and record to an MIKMIDISequence.
|
|
*
|
|
* @note Recording and using the click track may not yet be fully functional, and should
|
|
* be considered experimental in the meantime. Please submit issues and/or pull requests
|
|
* when you find areas that don't work as expected.
|
|
*/
|
|
@interface MIKMIDISequencer : NSObject
|
|
|
|
#pragma mark - Creation
|
|
|
|
/**
|
|
* Convenience method for creating a new MIKMIDISequencer instance with an empty sequence.
|
|
*
|
|
* @return An initialized MIKMIDISequencer.
|
|
*/
|
|
+ (instancetype)sequencer;
|
|
|
|
/**
|
|
* Initializes and returns a new MIKMIDISequencer ready to playback and record to the
|
|
* specified sequence.
|
|
*
|
|
* @param sequence The sequence to playback and record to.
|
|
*
|
|
* @return An initialized MIKMIDISequencer.
|
|
*/
|
|
- (instancetype)initWithSequence:(MIKMIDISequence *)sequence;
|
|
|
|
/**
|
|
* Convenience method for creating a new MIKMIDISequencer ready to playback and
|
|
* record to the specified sequence.
|
|
*
|
|
* @param sequence The sequence to playback and record to.
|
|
*
|
|
* @return An initialized MIKMIDISequencer.
|
|
*/
|
|
+ (instancetype)sequencerWithSequence:(MIKMIDISequence *)sequence;
|
|
|
|
#pragma mark - Playback
|
|
|
|
/**
|
|
* Starts playback from the beginning of the sequence.
|
|
*/
|
|
- (void)startPlayback;
|
|
|
|
/**
|
|
* Starts playback from the specified time stamp.
|
|
*
|
|
* @param timeStamp The position in the sequence to begin playback from.
|
|
*/
|
|
- (void)startPlaybackAtTimeStamp:(MusicTimeStamp)timeStamp;
|
|
|
|
/**
|
|
* Starts playback from the specified MusicTimeStamp at the specified MIDITimeStamp.
|
|
* This could be useful if you need to synchronize the playback with another source
|
|
* such as an audio track, or another MIKMIDISequencer instance.
|
|
*
|
|
* @param timeStamp The position in the sequence to begin playback from.
|
|
* @param midiTimeStamp The MIDITimeStamp to begin playback at.
|
|
*/
|
|
- (void)startPlaybackAtTimeStamp:(MusicTimeStamp)timeStamp MIDITimeStamp:(MIDITimeStamp)midiTimeStamp;
|
|
|
|
/**
|
|
* Starts playback from the position returned by -currentTimeStamp.
|
|
*
|
|
* @see currentTimeStamp
|
|
*/
|
|
- (void)resumePlayback;
|
|
|
|
/**
|
|
* Stops all playback and recording.
|
|
*/
|
|
- (void)stop;
|
|
|
|
/**
|
|
* Sends any pending note offs for the command scheduler immeidately.
|
|
* This can be useful if you are changing the notes in the MIDI track and
|
|
* you want the old notes to immediately stop rather than play until their
|
|
* original end time stamp.
|
|
*/
|
|
- (void)stopAllPlayingNotesForCommandScheduler:(id<MIKMIDICommandScheduler>)scheduler;
|
|
|
|
/**
|
|
* Allows subclasses to modify the MIDI commands that are about to be
|
|
* scheduled with a command scheduler.
|
|
*
|
|
* @param commandsToBeScheduled An array of MIKMIDICommands that are about
|
|
* to be scheduled.
|
|
*
|
|
* @param scheduler The command scheduler the commands will be scheduled with
|
|
* after they are modified.
|
|
*
|
|
* @note You should not call this method directly. It is made public solely to
|
|
* give subclasses a chance to alter or override any MIDI commands parsed from the
|
|
* MIDI sequence before they get sent to their destination endpoint.
|
|
*
|
|
*/
|
|
- (MIKArrayOf(MIKMIDICommand *) *)modifiedMIDICommandsFromCommandsToBeScheduled:(MIKArrayOf(MIKMIDICommand *) *)commandsToBeScheduled forCommandScheduler:(id<MIKMIDICommandScheduler>)scheduler;
|
|
|
|
/**
|
|
* Sets the loopStartTimeStamp and loopEndTimeStamp properties.
|
|
*
|
|
* @param loopStartTimeStamp The MusicTimeStamp to begin looping at.
|
|
*
|
|
* @param loopEndTimeStamp The MusicTimeStamp to end looping at. To have
|
|
* the loop end at the end of the sequence, regardless of sequence length,
|
|
* pass in MIKMIDISequencerEndOfSequenceLoopEndTimeStamp.
|
|
*
|
|
* @see loopStartTimeStamp
|
|
* @see loopEndTimeStamp
|
|
* @see loop
|
|
* @see looping
|
|
*/
|
|
- (void)setLoopStartTimeStamp:(MusicTimeStamp)loopStartTimeStamp endTimeStamp:(MusicTimeStamp)loopEndTimeStamp;
|
|
|
|
|
|
#pragma mark - Recording
|
|
|
|
/**
|
|
* Starts playback from the beginning of the sequence minus the value returned
|
|
* by -preRoll, and enables recording of incoming events to the record enabled tracks.
|
|
*
|
|
* @see preRoll
|
|
* @see recordEnabledTracks
|
|
*/
|
|
- (void)startRecording;
|
|
|
|
/**
|
|
* Starts playback from the specified time stamp minus the value returned by
|
|
* -preRoll, and enables recording of incoming events to the record enabled tracks.
|
|
*
|
|
* @see preRoll
|
|
* @see recordEnabledTracks
|
|
*/
|
|
- (void)startRecordingAtTimeStamp:(MusicTimeStamp)timeStamp;
|
|
|
|
/**
|
|
* Starts playback from the specified MusicTimeStamp minus the value returned by -preRoll
|
|
* at the specified MIDITimeStamp, and enables recording of incoming events to the
|
|
* record enabled tracks.
|
|
*
|
|
* @see preRoll
|
|
* @see recordEnabledTracks
|
|
*/
|
|
- (void)startRecordingAtTimeStamp:(MusicTimeStamp)timeStamp MIDITimeStamp:(MIDITimeStamp)midiTimeStamp;
|
|
|
|
/**
|
|
* Starts playback from the position returned by -currentTimeStamp minus the
|
|
* value returned by -preRoll, and enables recording of incoming events to the
|
|
* record enabled tracks.
|
|
*
|
|
* @see preRoll
|
|
* @see recordEnabledTracks
|
|
*/
|
|
- (void)resumeRecording;
|
|
|
|
/**
|
|
* Records a MIDI command to the record enabled tracks.
|
|
*
|
|
* @param command The MIDI command to record to the record enabled tracks.
|
|
*
|
|
* @note When recording is NO, calls to this method will do nothing.
|
|
*
|
|
* @see recording
|
|
* @see recordEnabledTracks
|
|
*/
|
|
- (void)recordMIDICommand:(MIKMIDICommand *)command;
|
|
|
|
#pragma mark - Configuration
|
|
|
|
/**
|
|
* Sets the command scheduler for a track in the sequencer's sequence.
|
|
* Calling this method is optional. By default, the sequencer will setup internal synthesizers
|
|
* so that playback "just works".
|
|
*
|
|
* @note If track is not contained by the receiver's sequence, this method does nothing.
|
|
*
|
|
* @param commandScheduler An object that conforms to MIKMIDICommandScheduler with which events
|
|
* in track should be scheduled during playback. MIKMIDIDestinationEndpoint and MIKMIDISynthesizer both conform to MIKMIDICommandScheduler, so they can be used here. Pass nil to remove an existing command scheduler.
|
|
* @param track An MIKMIDITrack instance.
|
|
*/
|
|
- (void)setCommandScheduler:(nullable id<MIKMIDICommandScheduler>)commandScheduler forTrack:(MIKMIDITrack *)track;
|
|
|
|
/**
|
|
* Returns the command scheduler for a track in the sequencer's sequence.
|
|
*
|
|
* MIKMIDISequencer will automatically create its own default synthesizers connected
|
|
* for any tracks not configured manually. This means that even if you haven't called
|
|
* -setCommandScheduler:forTrack:, you can use this method to retrieve
|
|
* the default command scheduler for a given track.
|
|
*
|
|
* @note If track is not contained by the receiver's sequence, this method returns nil.
|
|
*
|
|
* @param track An MIKMIDITrack instance.
|
|
*
|
|
* @return The command scheduler associated with track, or nil if one can't be found.
|
|
*
|
|
* @see -setCommandScheduler:forTrack:
|
|
* @see -builtinSynthesizerForTrack:
|
|
* @see createSynthsIfNeeded
|
|
*/
|
|
- (nullable id<MIKMIDICommandScheduler>)commandSchedulerForTrack:(MIKMIDITrack *)track;
|
|
|
|
/**
|
|
* Returns synthesizer the receiver will use to synthesize MIDI during playback
|
|
* for any tracks whose MIDI has not been routed to a custom scheduler using
|
|
* -setCommandScheduler:forTrack:. For tracks where a custom scheduler has
|
|
* been set, this method returns nil.
|
|
*
|
|
* The caller is free to reconfigure the synthesizer(s) returned by this method,
|
|
* e.g. to load a custom soundfont file or select a different instrument.
|
|
*
|
|
* @param track The track for which the builtin synthesizer is desired.
|
|
*
|
|
* @return An MIKMIDISynthesizer instance, or nil if a builtin synthesizer for track doesn't exist.
|
|
*/
|
|
- (nullable MIKMIDISynthesizer *)builtinSynthesizerForTrack:(MIKMIDITrack *)track;
|
|
|
|
#pragma mark - Properties
|
|
|
|
/**
|
|
* The sequence to playback and record to.
|
|
*/
|
|
@property (nonatomic, strong) MIKMIDISequence *sequence;
|
|
|
|
/**
|
|
* Whether or not the sequencer is currently playing. This can be observed with KVO.
|
|
*
|
|
* @see recording
|
|
*/
|
|
@property (readonly, nonatomic, getter=isPlaying) BOOL playing;
|
|
|
|
/**
|
|
* Whether or not the sequence is currently playing and is record enabled.
|
|
* This can be observed with KVO.
|
|
*
|
|
* @note When recording is YES, events will only be recorded to the tracks
|
|
* specified by -recordEnabledTracks.
|
|
*
|
|
* @see playing
|
|
* @see recordEnabledTracks
|
|
*/
|
|
@property (readonly, nonatomic, getter=isRecording) BOOL recording;
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
@property (nonatomic) Float64 tempo;
|
|
|
|
/**
|
|
* The length the that the sequencer should consider its sequence to be. When set to 0, the sequencer
|
|
* will use sequence.length instead.
|
|
*
|
|
* This can be handy if you want to alter the duration of playback to be shorter or longer
|
|
* than the sequence's length without affecting the sequence itself.
|
|
*/
|
|
@property (nonatomic) MusicTimeStamp overriddenSequenceLength;
|
|
|
|
/**
|
|
* The current playback position in the sequence.
|
|
*
|
|
* @note This property is *not* observable using Key Value Observing.
|
|
*/
|
|
@property (nonatomic) MusicTimeStamp currentTimeStamp;
|
|
|
|
|
|
/**
|
|
* The amount of time (in beats) to pre-roll the sequence before recording.
|
|
* For example, if preRoll is set to 4 and you begin recording, the sequence
|
|
* will start 4 beats ahead of the specified recording position.
|
|
*
|
|
* The default is 4.
|
|
*/
|
|
@property (nonatomic) MusicTimeStamp preRoll;
|
|
|
|
/**
|
|
* Whether or not playback should loop when between loopStartTimeStamp and loopEndTimeStamp.
|
|
*
|
|
* @see loopStartTimeStamp
|
|
* @see loopEndTimeStamp
|
|
* @see looping
|
|
*/
|
|
@property (nonatomic, getter=shouldLoop) BOOL loop;
|
|
|
|
/**
|
|
* Whether or not playback is currently looping between loopStartTimeStamp and loopEndTimeStamp.
|
|
*
|
|
* @note If loop is YES, and playback starts before loopStartTimeStamp, looping will be NO until
|
|
* currentTimeStamp reaches loopStartTimeStamp. At that point, looped playback will begin and
|
|
* the looping property will become YES. Conversely, if playback starts after loopEndTimeStamp,
|
|
* then the looped area of playback will never be reached and looping will remain NO.
|
|
*
|
|
* @see loop
|
|
* @see loopStartTimeStamp
|
|
* @see loopEndTimeStamp
|
|
* @see -setLoopStartTimeStamp:loopEndTimeStamp:
|
|
* @see currentTimeStamp
|
|
*/
|
|
@property (readonly, nonatomic, getter=isLooping) BOOL looping;
|
|
|
|
/**
|
|
* The loop's beginning time stamp during looped playback.
|
|
*
|
|
* @see -setLoopStartTimeStamp:loopEndTimeStamp:
|
|
*/
|
|
@property (readonly, nonatomic) MusicTimeStamp loopStartTimeStamp;
|
|
|
|
/**
|
|
* The loop's ending time stamp during looped playback, or MIKMIDISequencerEndOfSequenceLoopEndTimeStamp.
|
|
*
|
|
* @note When this is set to MIKMIDISequencerEndOfSequenceLoopEndTimeStamp
|
|
* the loopEndTimeStamp will be treated as if it is set to the length of the
|
|
* sequence. The default is MIKMIDISequencerEndOfSequenceLoopEndTimeStamp.
|
|
*
|
|
* @see effectiveLoopEndTimeStamp
|
|
* @see -setLoopStartTimeStamp:loopEndTimeStamp:
|
|
*/
|
|
@property (readonly, nonatomic) MusicTimeStamp loopEndTimeStamp;
|
|
|
|
/**
|
|
* The loop's ending time stamp during looped playback.
|
|
*
|
|
* @note When loopEndTimeStamp is set to MIKMIDISequencerEndOfSequenceLoopEndTimeStamp,
|
|
* this will return the same length as the sequence.length. Otherwise loopEndTimeStamp
|
|
* will be returned.
|
|
*/
|
|
@property (readonly, nonatomic) MusicTimeStamp effectiveLoopEndTimeStamp;
|
|
|
|
|
|
/**
|
|
* Whether or not the sequencer should create synthesizers and endpoints
|
|
* for MIDI tracks that are not assigned an endpoint.
|
|
*
|
|
* When this property is YES, -commandSchedulerForTrack: will create a
|
|
* synthesizer for any track that has MIDI commands scheduled for it
|
|
* and doesn't already have an assigned scheduler. The default for this property
|
|
* is YES.
|
|
*
|
|
* @see -commandSchedulerForTrack:
|
|
*/
|
|
@property (nonatomic, getter=shouldCreateSynthsIfNeeded) BOOL createSynthsIfNeeded;
|
|
|
|
/**
|
|
* The metronome to send click track events to.
|
|
*/
|
|
@property (nonatomic, strong, nullable) MIKMIDIMetronome *metronome;
|
|
|
|
/**
|
|
* When the click track should be heard.
|
|
* The default is MIKMIDISequencerClickTrackStatusEnabledInRecord.
|
|
*/
|
|
@property (nonatomic) MIKMIDISequencerClickTrackStatus clickTrackStatus;
|
|
|
|
/**
|
|
* The tracks to record incoming MIDI events to while recording is enabled.
|
|
*
|
|
* Each incoming event is added to every track in this set.
|
|
*
|
|
* @see recording
|
|
*
|
|
*/
|
|
@property (nonatomic, copy, nullable) MIKSetOf(MIKMIDITrack *) *recordEnabledTracks;
|
|
|
|
/**
|
|
* An MIKMIDIClock that is synced with the sequencer's internal clock.
|
|
*
|
|
* @ @see -[MIKMIDIClock syncedClock]
|
|
*/
|
|
@property (nonatomic, readonly) MIKMIDIClock *syncedClock;
|
|
|
|
|
|
/**
|
|
* The latest MIDITimeStamp the sequencer has looked ahead to to schedule MIDI events.
|
|
*/
|
|
@property (nonatomic, readonly) MIDITimeStamp latestScheduledMIDITimeStamp;
|
|
|
|
|
|
/**
|
|
* The maximum amount the sequencer will look ahead to schedule MIDI events. (0.05 to 1s).
|
|
*
|
|
* The default of 0.1s should suffice for most uses. You may however, need a longer time
|
|
* if your sequencer needs to playback on iOS while the device is locked.
|
|
*/
|
|
@property (nonatomic) NSTimeInterval maximumLookAheadInterval;
|
|
|
|
#pragma mark - Deprecated
|
|
|
|
/**
|
|
* @deprecated Use -setCommandScheduler:forTrack: instead.
|
|
*
|
|
* Sets the destination endpoint for a track in the sequencer's sequence.
|
|
* Calling this method is optional. By default, the sequencer will setup internal default endpoints
|
|
* connected to synthesizers so that playback "just works".
|
|
*
|
|
* @note If track is not contained by the receiver's sequence, this method does nothing.
|
|
*
|
|
* @param endpoint The MIKMIDIDestinationEndpoint instance to which events in track should be sent during playback.
|
|
* @param track An MIKMIDITrack instance.
|
|
*/
|
|
- (void)setDestinationEndpoint:(MIKMIDIDestinationEndpoint *)endpoint forTrack:(MIKMIDITrack *)track __attribute((deprecated("use -setCommandScheduler:forTrack: instead")));
|
|
|
|
/**
|
|
* @deprecated Use -commandSchedulerForTrack: instead.
|
|
*
|
|
* Returns the destination endpoint for a track in the sequencer's sequence.
|
|
*
|
|
* MIKMIDISequencer will automatically create its own default endpoints connected to
|
|
* MIKMIDISynthesizers for any tracks not configured manually. This means that even if you
|
|
* haven't called -setDestinationEndpoint:forTrack:, you can use this method to retrieve
|
|
* the default endpoint for a given track.
|
|
*
|
|
* @note If track is not contained by the receiver's sequence, this method returns nil.
|
|
*
|
|
* @param track An MIKMIDITrack instance.
|
|
*
|
|
* @return The destination endpoint associated with track, or nil if one can't be found.
|
|
*
|
|
* @see -setDestinationEndpoint:forTrack:
|
|
* @see -builtinSynthesizerForTrack:
|
|
* @see createSynthsAndEndpointsIfNeeded
|
|
*/
|
|
- (nullable MIKMIDIDestinationEndpoint *)destinationEndpointForTrack:(MIKMIDITrack *)track __attribute((deprecated("use -setCommandScheduler:forTrack: instead")));
|
|
|
|
@end
|
|
|
|
|
|
/**
|
|
* Sent out shortly before playback loops.
|
|
*/
|
|
FOUNDATION_EXPORT NSString * const MIKMIDISequencerWillLoopNotification;
|
|
|
|
/**
|
|
* Set loopEndTimeStamp to this to have the loop end at the end of the
|
|
* sequence regardless of sequence length.
|
|
*/
|
|
FOUNDATION_EXPORT const MusicTimeStamp MIKMIDISequencerEndOfSequenceLoopEndTimeStamp;
|
|
|
|
NS_ASSUME_NONNULL_END |