-
Notifications
You must be signed in to change notification settings - Fork 45
Controlling the Sequencer
As soon as the AudioEngine::start()-method has been invoked, the engine is ready and continuously looking to render audio output. However, in most applications you will not need to extend the AudioEngine nor Sequencer to get your musical mayhem going (as en-queing and de-queing of AudioEvents is delegated to their respective Instruments, which have their own chapter inside the Wiki).
You will however need to exert control on what the Sequencer should be doing, which can be done using the SequencerController.
sequencercontroller.h
When using the engine solely in C++, you can construct a new instance of SequencerController directly :
SequencerController::SequencerController()
When using the engine through JNI in Java, you cannot construct a SequencerController directly as it is part of the initialization of the nl.igorski.mwengine.MWEngine-class (see below).
After construction you can configure the engine :
void prepare( int aBufferSize, int aSampleRate, float aQueuedTempo,
int aTimeSigBeatAmount, int aTimeSigBeatUnit )
Where aBufferSize is the desired buffer size (will overwrite the AudioEngineProps defined in global.h) and aSampleRate is the desired sample rate (will overwrite the AudioEngineProps defined in global.h). As optimal buffer sizes and sample rates differ per device, it is best to use this dynamic approach when initializing the engine (only invoke the start()-thread of the AudioEngine after this preparation routine).
aQueuedTempo describes the tempo (in BPM) at which the sequence runs (for instance 120.0). aTimeSigBeatAmount and aTimeSigBeatUnit describe the time signature at which the sequence runs (beat amount describes the upper numeral in a time signature, for instance the "3" in 3/4, where as beat unit describes the lower numeral, i.e.: the "4" in 3/4).
void setTempo( float aTempo, int aTimeSigBeatAmount, int aTimeSigBeatUnit )
Updates the tempo to given aTempo (in BPM) and the time signature to given aTimeSigBeatAmount / aTimeSigBeatUnit. Note this isn't executed immediately but at the end of the current render cycle of the AudioEngine (and as such is theoretically delayed by the maximum of a a full buffer size). When adjusting the tempo in the Sequencer, the existing playback range values of all instantiated AudioEvents are updated by the same ratio.
setTempoNow( float aTempo, int aTimeSigBeatAmount, int aTimeSigBeatUnit )
The same as setTempo() only applies the tempo update immediately. This should only be used when the engine isn't running or the Sequencer is paused to overcome reading from buffers that should be mutated to match the new sequence properties.
float getTempo()
Returns the current tempo value of the engine.
void setVolume( float aVolume )
Sets the master output volume to given aVolume which should be a value between 0.0 to 1.0.
void setPlaying( bool value )
Starts (value true) or pauses (value false) the Sequencer. When started, the queued AudioEvent s will be audible. when paused, the engine is still running (which has the benefit that Processors can still operate, for instance: to keep repeating an echo buffer).
void setLoopRange( int aStartPosition, int aEndPosition, int aStepsPerBar )
Defines the range of the sequence to play back. The sequencer will play everything between given aStartPosition and given aEndPosition and loop again from aStartPosition indefinitely. aStartPosition and aEndPosition are specified as buffer samples and aStepsPerBar defines the subdivision of each measure (if this value differs from the current steps per bar, updateStepsPerBar() is invoked). This method basically instructs the Sequencer which subset of the total "song" to play. The benefit of using buffer samples instead of measure numbers is that this enables you to loop between very specific subsets of a full measure (e.g. to loop between individual 16th notes, etc.).
Let's assume we are working at 120 BPM in 4/4 using a sample rate of 44.1 kHz, a single measure lasts 88200 samples (consult this page for the math). For this example we are working with 8 measures. The entire sequence then works in a range of aStartPosition 0 (the start) up to aEndPosition 705559 ((88200 * 8) - 1 ). If we wish to loop the second measure the range would be aStartPosition 88200 (start of the second measure) up to aEndPosition 176399 (start of the second measure + a full measure length - 1). Note the subtractions of 1 as the sample indices start at 0.
void updateStepsPerBar( int aStepsPerBar )
aStepsPerBar describes the amount of subdivisions used for each individual measure. The steps are matched against the current time signature as this is used for the periodic notification of the engines position (see Observer). aStepsPerBar can be any positive integer value, though it is suggested to keep this musical (e.g. "16" to work in a 16-step sequencer context, or "64" to support 64th notes, etc.).
void updateMeasures( int aValue, int aStepsPerBar )
Sets the amount of measures the current sequence should hold. aValue is an integer value describing the amount of measures, while aStepsPerBar describes the amount of subdivisions used for each individual measure, if this value differs from the current steps per bar, updateStepsPerBar() is invoked.
Note this basically sets the total duration of the sequence, but leaves the currently looping range unchanged. In other words : if the Sequencer was looping a single measure, and updateMeasures() has been invoked to reserve a total "song length" of four measures, the Sequencer will continue to loop the first measure.
int getStepPosition()
Retrieves the current step position (smallest subdivision within a measure) of the Sequencer. If updateStepsPerBar() has been invoked with a value of 16, this can be any value in the range of 0 to 15.
int getBufferPosition()
Retrieves the current position in samples of the engine. If the engine works at 120 BPM in 4/4 at a sample rate of 44.1 kHz, a single measure lasts 88200 samples. As such, the buffer position can be any value in the range of 0 to 88199.
void setBufferPosition( int aValue )
Sets the current position in samples of the engine to given integer aValue. This method will sanitize given value to remain within the minimum and maximum buffer position defined by the loop range.
int getSamplesPerBeat()
Returns an integer value describing the amount of samples needed for a single beat of the current sequence. If the engine works at 120 BPM in 4/4 at a sample rate of 44.1 kHz, there are 88200 samples in a single measure, and a single beat will hold ( 88200 / 4 beats ) == 22050 samples.
int getSamplesPerStep()
Returns an integer value describing the amount of samples needed for a single step of the current sequence. If the engine works at 120 BPM in 4/4 at a sample rate of 44.1 kHz, there are 88200 samples in a single measure. If the amount of steps per bar has been configured to hold 16 steps, a single beat will hold ( 88200 / 16 steps ) == 5512 samples.
int getSamplesPerBar()
Returns an integer value describing the amount of samples needed for a single bar/measure of the current sequence. If the engine works at 120 BPM in 4/4 at a sample rate of 44.1 kHz, there are 88200 samples within a single measure.
int getTimeSigBeatAmount()
Returns the amount of beats of the current time signature. This is the upper numeral of the time signaturs (e.g. the "3" in 3/4)
int getTimeSigBeatUnit()
Returns the amount of units of the current time signature. This is the lower numeral of the time signature (e.g. the "4" in 3/4)
void rewind()
Sets the sequencers position to the start of the current range (as defined in setLoopRange()).
void setNotificationMarker( int aPosition )
When the Sequencer 's "playback head" reaches given aPosition a notification is broadcast over the Observers. You can use this method to receive a callback whenever the Sequencer has reached a "point of interest". You can set aPosition to -1 to omit broadcasting notifications regarding marker positions.
BulkCacher* getBulkCacher()
Retrieves the BulkCacher instance which can be used in conjunction with cacheAudioEventsForMeasure() to pre-cache the BaseCacheableAudioEvents that should be audible in a next measure.
void cacheAudioEventsForMeasure( int aMeasure )
Can be used to precache all instances of BaseCacheableAudioEvent that are present in given aMeasure (number of the measure). This can be used to pre-render AudioEvents that have an expensive render-routine, ensuring that their contents are ready for playback when the Sequencers playback position enters the given measure. Most applications don't require this, especially when working solely with SampleEvents or BaseSynthEvents that use a WaveTable.
This is exactly the same as in the steps mentioned above, apart from the little fact mentioned in the configuration of the engine-page, that the nl.igorski.mwengine.MWEngine-class acts as a wrapper for the AudioEngine and creates its own instance of SequencerController.
void createOutput( int sampleRate, int bufferSize, int outputChannels, Drivers.types audioDriver );
Should be invoked prior to calling start() (which starts the render thread to output audio). Given sampleRate is the desired sample rate, where bufferSize is the desired output buffer size. This method will create a new instance of SequencerController, making the engine ready for use
SequencerController getSequencerController()
Returns the SequencerController-instance wrapped inside the MWEngine.