1. Quick Setup
This quick setup, as seen in the short video above, will take you through creating a new step sequencer and introduce you to sequencing audio using the component’s inspector.
1. Create a new Game Object and add the Step Sequencer component.
2. Assign the “Synth” sound bank included with Stepp to the sequencer’s Sound Bank property.
3. Select “Play On Awake” in the sequencer’s Playback section and run the scene to begin playback.
4. Select “Show” in the sequencer’s Sequence section to reveal the sequence grid. As per a standard step sequencer, steps progress from left to right, with pitch represented on the vertical axis from bottom to top. For example, the image below shows an ascending C Minor scale over eight beats.
In addition to the sequence grid, two key properties available in the component inspector are the Beats Per Minute and the Number Of Steps.
Beats Per Minute - Adjust this value to change the number of beats per minute. You may alter this value live as the sequencer is playing and Stepp will adjust on the fly.
Number Of Steps - This determines the length of the sequence. Changes will be reflected in the sequence grid.
That concludes the quick setup. The next section will cover creating sound banks - the audio clips that the step sequencer will use - so you can begin sequencing your own sounds.
2. Sound Banks
A sound bank in Stepp maps audio clips to note identifiers. When you tell the step sequencer to play note “C3”, it will query the current sound bank for the audio clip associated with note “C3”. This frees you to use any audio clips that you wish with Stepp.
To create a sound bank, right-click in the project window or select “Assets” from the menu bar and choose “Create-->Stepp-->Sound Bank”.
This will create an empty sound bank in your project, which can then be configured by adding audio clips to it. This can be done in two ways - manually and automatically.
Manual Configuration
To add audio clips manually, use the “+” button in the sound bank’s inspector. You may assign any audio clip from your project and use the dropdown to select which note identifier this audio clip should be assigned to. The “X” button will remove an entry.
Automatic Configuration
Stepp can assign your audio clips for you if you adhere to a naming convention. This is particularly useful when using large numbers of audio files that would take a while to manually add one at a time. The naming convention is as follows:
- Name your sound bank whatever you wish.
- Name each audio clip the same name as your sound bank appended with “@{note-id}”, replacing “{note-id}” with the note identifier the audio clip represents.
So, for example, if you call your sound bank “Grand Piano”, then your C3 audio sample will be called “Grand Piano@C3”. You can see the sound bank included with the Stepp demos using this naming convention below. (It helps to export the audio files from your DAW using the naming convention so you don’t have to rename them all.)
Once you have your audio files following the naming convention outlined above, simply press the “Auto-Assign Audio Clips” button and Stepp will populate the sound bank for you.
The sound bank is now ready to use, so you can assign it to your step sequencer’s Sound Bank property and try it out. Note that the Sequence shown in the step sequencer's inspector will automatically show only the notes that exist in the current sound bank.
Audio Clip Settings
The settings of your audio clips are mostly left to you to determine as they will vary depending on your project. However, here are a few recommendations on audio clips being used with Stepp:
Preload Audio Data
Recommended setting: Off
If you leave this selected, Unity will preload your audio files when the scene loads. This is fine for a small sound bank but if you have large numbers of sound banks, you don’t necessarily want them all in memory at once. Stepp can load your sound bank into memory as required at run time, so you are free to deselect this option and let Stepp load the audio data in as required.
Load Type
Required Setting: Decompress On Load
Load In Background
Recommended setting: On
If you enable “Load in Background”, when audio clips are loaded it will be done asynchronously on a background thread, allowing your scene to remain responsive.
NOTE IDENTIFIERS
Note identifiers are simply Stepp’s way of representing a note in a sequence. It follows the traditional naming convention in digital music of using the note followed by the octave, e.g. C3, Eb4, etc. The available notes range from C0 up to G10, which gives 128 possible notes, and flats are always used instead of sharps, i.e. Eb3 not D#3.
It is worth highlighting that note identifiers impose no restrictions upon what audio clips you can use. For example, an audio clip assigned to C3 has no requirement whatsoever to represent a pitch of C in the third octave. If you want to use a percussive sound, or a spoken word, or a different pitch completely, you are free to do so. How you configure your sound banks is up to you.
Playback Speed
As of version 2.1, Stepp allows you to specify a playback speed for your SoundBank’s entries. This allows you to use the same audio clip across multiple notes by playing back the audio clip at different pitches (playback speeds). Stepp detects when the same audio clip is being used multiple times and will only store the audio data for the clip in memory once, greatly optimizing memory footprint.
For example, the musical app Lily uses this feature, requiring only 12 audio samples to cover a 37 note range. Shown below is the configuration of a SoundBank from the Lily app. Notice how the same AudioClip is used several times across multiple notes with a different playback speed.
As you can see in the example above, each audio clip is being played back at speeds of 0.94054, 1, and 1.05946. Where do these numbers come from? Well, they represent semitone shifts. That is, playing an audio sample back at a speed of 0.94054 achieves a decrease in pitch of a semitone. Likewise, playing back at 1.05946 speed achieves a pitch increase of a semitone. Stepp provides a useful calculator utility to aid with working out what playback speed is required for a desired pitch shift. On any SoundBank, simply scroll to the bottom to find the Playback Speed Calculator, enter the desired number of semitones you wish to shift, and it will calculate the required playback speed.
It is perhaps worth mentioning that, naturally, the more you pitch shift via playback speed, the more noticeable it becomes. One or two semitones is standard practice in many audio samplers, but as you begin to go further, you may find you get better results by using a new audio clip, as the example above does.
That covers creating your own sound banks in Stepp. The next section will cover scripting with Stepp so you can control step sequencers from your own scripts and start building custom musical sequencing interfaces.
3. Scripting
This section covers the majority of the scripting interface to Stepp. A few of the more specialist features will be covered in the Advanced section.
The demo scenes will provide you with working examples of scripting with Stepp. Inside the folder "Stepp-->Demos-->Scripts" you’ll find all the code needed to implement the demo scenes, which uses the APIs outlined below. Additionally, all source code is included with Stepp so you’re free to inspect how a feature is implemented or modify the code, as you wish.
To begin using Stepp from a script, add the following line to the top of the script:
using P7.Stepp;
We will need a reference to the step sequencer we want to control. One way to do this is to simply add a public StepSequencer property on our script and assign it in the inspector.
public StepSequencer m_StepSequencer;
Basic Settings
We can adjust the basic settings of our step sequencer with the following properties:
public int BeatsPerMinute; public int NumberOfSteps;
For example, we could configure our sequencer on Start like so:
private void Start() { m_StepSequencer.BeatsPerMinute = 120; m_StepSequencer.NumberOfSteps = 16; }
Playback
Playback can be controlled via the following methods:
public void Play(); public void Stop();
So after adjusting our sequencer’s settings, we could then play it:
private void Start() { m_StepSequencer.BeatsPerMinute = 120; m_StepSequencer.NumberOfSteps = 16; m_StepSequencer.Play(); }
We can retrieve the current playback position using the following property:
public float PlaybackPosition01;
As defined in the property’s name, the value returned is a normalized value between 0 and 1. The Stepp demos use this property to move the playback head across the grid like so:
private void Update() { Vector2 playheadPosition = Vector2.zero; playheadPosition.x = m_StepSequencer.PlaybackPosition01 * m_SequencingGrid.m_RectTransform.rect.width; m_Playhead.anchoredPosition = playheadPosition; }
Sequencing
We can modify the sequence using the following methods:
public void AddNoteAtStep(NoteId noteId, int step); public void RemoveNoteAtStep(NoteId noteId, int step);
So, we could program an ascending C minor pentatonic scale in our Start method as below:
private void Start() { m_StepSequencer.BeatsPerMinute = 120; m_StepSequencer.NumberOfSteps = 16; m_StepSequencer.Play(); m_StepSequencer.AddNoteAtStep(NoteId.C3, 0); m_StepSequencer.AddNoteAtStep(NoteId.Eb3, 1); m_StepSequencer.AddNoteAtStep(NoteId.F3, 2); m_StepSequencer.AddNoteAtStep(NoteId.G3, 3); m_StepSequencer.AddNoteAtStep(NoteId.Bb3, 4); m_StepSequencer.AddNoteAtStep(NoteId.C4, 5); }
We could wrap this up in a loop:
private void Start() { m_StepSequencer.BeatsPerMinute = 120; m_StepSequencer.NumberOfSteps = 16; m_StepSequencer.Play(); NoteId[] cMinorPentatonic = { NoteId.C3, NoteId.Eb3, NoteId.F3, NoteId.G3, NoteId.Bb3, NoteId.C4, }; for (int step = 0; step < cMinorPentatonic.Length; step++) { NoteId note = cMinorPentatonic[step]; m_StepSequencer.AddNoteAtStep(note, step); } }
(Note that your current sound bank will need to have entries for any notes that you add to hear anything.)
The current sequence is represented by the step sequencer’s Sequence property.
public Sequence Sequence;
You can inspect the sequence using the following methods on the Sequence object:
public NoteId[] NotesForStep(int step); public List<NoteId> NotesForStepNonAlloc(int step)
This is useful when you need to query the sequencer for information about the current sequence. For example, the Stepp demos use this method to create the pulse animation on only the active triggers as they play, like so:
private void OnStepSequencerAdvancedStep(StepSequencer stepSequencer, int currentStep, int? previousStep) { NoteId[] notesAtCurrentStep = stepSequencer.Sequence.NotesForStep(currentStep); if (notesAtCurrentStep.Length > 0) { foreach (SequencingToggle toggle in m_SequencingToggles[currentStep]) { if (notesAtCurrentStep.Contains(toggle.m_NoteId)) { toggle.AnimateTriggered(); } } } }
Events
As hinted at in the previous code snippet, we can be notified when the step sequencer advances a step by subscribing to the OnAdvancedStep event, as defined:
public delegate void AdvancedStepAction(StepSequencer stepSequencer, int currentStep, int? previousStep); public event AdvancedStepAction OnAdvancedStep;
So we could register to receive these callbacks in our Start method, but don’t forget to deregister when you no longer need to receive callbacks.
private void Start() { m_StepSequencer.OnAdvancedStep += OnStepSequencerAdvancedStep; } private void OnDestroy() { m_StepSequencer.OnAdvancedStep -= OnStepSequencerAdvancedStep; } private void OnStepSequencerAdvancedStep(StepSequencer stepSequencer, int currentStep, int? previousStep) { Debug.LogFormat("Step sequencer just advanced to step {0}!", currentStep); }
Sound Banks
The current sound bank can be get and set via the step sequencer’s SoundBank property.
public SoundBank SoundBank;
With a reference to a sound bank of our choice, we could change the step sequencer’s sound bank on Start:
public SoundBank m_PianoSoundBank; private void Start() { m_StepSequencer.SoundBank = m_PianoSoundBank; m_StepSequencer.Play(); }
In the above example, Stepp will take care of loading the sound bank into memory for you when you tell the sequencer to play. Additionally, if your sequencer is already playing when you set a new sound bank, Stepp will load the sound bank into memory first and then switch to the new sound bank to prevent a period of silence whilst the sound bank is being loaded.
Depending on many factors, including the size of your sound bank and your Audio Clip Settings, loading sound banks can take a few seconds to load a large number of audio files. You may therefore wish to take control of loading and unloading your sound banks depending upon your project’s requirements. This can be done using the following methods available on the SoundBank object.
public void Load(MonoBehaviour loader, System.Action completion = null) public void Unload() public static void LoadSoundBanks(MonoBehaviour loader, System.Action completion = null, params SoundBank[] soundbanks)
For example, the Stepp demo scenes load their sound bank asynchronously and only display their play button after their sound bank has loaded, like so:
private void Awake() { m_PlayButton.gameObject.SetActive(false); m_StepSequencer.SoundBank.Load(this, () => { m_PlayButton.gameObject.SetActive(true); }); }
You could also load a collection of sound banks:
public SoundBank m_PianoSoundBank; public SoundBank m_PercussionSoundBank; private void Start() { SoundBank[] soundbanksToLoad = { m_PianoSoundBank, m_PercussionSoundBank }; SoundBank.LoadSoundBanks(this, () => { Debug.Log("All sound banks have been loaded!"); }, soundbanksToLoad); }
That covers the majority of scripting with Stepp. The next section will cover some more advanced features of Stepp.
4. Advanced
This section covers more advanced features of Stepp. The majority of these features arose from real project needs when developing my game, Lily, which Stepp was developed for.
Multiple Sequencers
Stepp supports using multiple sequencers together by allowing you to share a metronome between step sequencers. This ensures that sequencers remain beat synchronised, even as you change the settings, such as the BeatsPerMinute or the NumberOfSteps.The Multi-Sequencer demo shows a working example of how to achieve this.
To share a metronome between step sequencers, add a Metronome property to your script and assign it to your step sequencer using the ShareMetronome method, as shown below:
public Metronome m_SharedMetronome; public StepSequencer[] m_StepSequencers; private void Start() { foreach (StepSequencer stepSequencer in m_StepSequencers) { stepSequencer.ShareMetronome(m_SharedMetronome); } }
Now when you change your shared metronome’s BeatsPerMinute, you’ll hear both step sequencers follow.
That covers how to have multiple sequencers synchronised with one another. Additionally, when using multiple sequencers in this way we can alter each sequencer's relative tempo using the step sequencer's NumberOfBeatsPerStep property.
public int NumberOfBeatsPerStep;
This will alter how many beats must occur for each step sequencer to ‘step’. Therefore, we can control the tempo of each step sequencer relative to one another, whilst maintaining beat synchronisation. For example, a sequencer with two beats per step would step at half the beats per minute that a sequencer with one beat per step would. This is analogous to altering a musical time signature’s denominator.
This is how the Multi-Sequencer demo is able to create four synchronised step sequencers that are stepping at different tempo denominations - eighth-note, quarter-note, half-note, and whole-note (quaver, crotchet, minim, and semibreve 🇬🇧 ).
For best results, when using a shared metronome you should reset it each time ALL associated step sequencers are stopped. This will ensure that when you subsequently play a sequencer it will step immediately and not wait for up to one beat before the first step. The Multi-Sequencer demo does this when the stop button is pressed.
if (play == false) { m_SharedMetronome.Reset(); }
Audio Routing & Mixing
Unity introduced their audio mixing functionality in Unity 5, which is a really powerful way to mix and route audio in Unity, much as you would in a music DAW like Logic. Stepp allows you to work with all this functionality by exposing the step sequencer’s AudioSource component through the AudioSource property:
public AudioSource AudioSource;
This allows you to assign an output mixer to your step sequencer, so you’re free to use all of Unity’s powerful mixing functionality, including snapshots and audio effects.
public AudioMixerGroup m_SequencersOutputMixer; private void Start() { m_StepSequencer.AudioSource.outputAudioMixerGroup = m_SequencersOutputMixer; }
For example, Lily routes all sequencers to one mixer and all sound effects to another, and uses audio snapshots to alter the mix depending upon what the player is doing.
Usage statistics & Performance
The number of notes currently in progress is the biggest factor in the performance of Stepp. The more notes in progress, the more audio clips that must be read from in each audio render cycle, and thus the more work that must be done. The Usage Statistics section in the component inspector gives some visibility of these numbers and how much work is currently being done.
Voices In Use
This shows you the number of notes that are currently being played by the system. Each step sequencer is allocated a maximum of 64 ‘voices’ during initialization to avoid memory allocations during playback. Each time a note is played, it is assigned a ‘voice’ until that audio clip has fully rendered, at which point the voice is released back to the system. This graphic will give you a clear indication of how many voices you are using.
Signal Processing Time
This will show you the number of milliseconds that the audio render cycle is currently taking to execute. Used in conjunction with the Voices In Use graphic, you’ll be able to get a good idea of how much audio processing you're doing with Stepp. Note that the bar graphic for this property is projected upon an estimated maximum time, which may vary depending upon the platform.
All values used by the inspector can be obtained via script using the following properties:
public int NumberOfNotesInProgress; public int NumberOfAvailableVoices; public double CurrentSignalProcessingTime;
Considerations
As outlined above, the number of notes in progress is the biggest factor in performance with Stepp. The lower you can keep this number, the more performant you’ll be. Therefore:
Consider the length of your sound bank’s audio clips. A four second clip is going to require playback for twice as long as a two second clip. So a sound bank with exclusively four second clips will be twice as demanding as a sound bank with two second clips, because each note (voice) must play for twice as long.
In conjunction with the previous point, consider your beats per minute. The quicker your sequencer is running, the more audio clips you’re playing at once. Therefore, using very long clips and a fast BPM is going to be more demanding on the system than using short clips and a slow BPM.
And of course, test on your target platform. Naturally, a mobile device is going to be capable of sequencing less than a PC. The Usage Statistics are there to help give visibility of how much work is being done and to aid in getting the most from Stepp.
5. Version History
2.0
August 19th 2016
- Initial version 2 release
2.0.1
October 14th 2016
- Added a CurrentBeat property to the StepSequencer to allow external users of the class to set & retrieve the current beat.
- Account for an issue in Unity whereby the same DSP time is reported for the first two frames of OnAudioFilterRead. This could have caused step sequencers to double step on their first step in the case that they are started immediately on awake and their sound bank is already loaded and they aren't using a shared metronome. (Unity Issue Tracker)
2.0.2
May 7th 2017
- Fixed an issue whereby sound banks would not persist their changes when manually adding audio files.
2.1
November 10th 2017
- Dynamic playback speed - You can now specify a playback speed for your audio samples. This allows you to reuse the same sample across multiple notes and play it back at different pitches - a handy feature for optimizing your game’s memory footprint.
- SoundBanks (collections of audio files) have been optimized and now both load much faster as well as require less memory at runtime.
- Added a Pause() method to the StepSequencer to support pausing and resuming playback.
- Fixed an issue whereby if no audio system sample rate was set in Unity, audio samples could be played back at slightly different pitches when deployed across different platforms.
- Support for Unity 2017.
Thanks to everyone who has purchased Stepp and for all the kind messages and helpful feedback. If you have any questions, suggestions, ideas, feedback, or just want to say hi:
Andy ✌️