Simple sequencer

Hi all

So I’m on a quest to make a music editor of sorts, with a sequencer built in. I have a basic synthesizer working and now I’m trying to figure out the best way to get basic sequencing working.

First I tested the simple and naive way. I made a function called ‘play’ that plays every note that should be played at that instant, and at the end of this function there’s an invoke to itself after a specific amount of time. It worked better than I expected, to be honest. But, sure enough, it breaks down if you increase speed too much. It lags.

Anyway, now I’m studying that script on the AudioSettings.dspTime docs page (link) and that seems like the way to go to make a more precise sequencer. But this is a lot harder too. I’m currently stuck at how to proceed. I was trying to keep the data reading as a on-demand thing, for example, ‘next tick these are the notes that should be played’, then the next time iteration it would look up which notes come next etc.

However, working inside that OnAudioFilterRead function requires subtlety. I can’t call external functions from it (or maybe I can but since it’s called so many times it’s not advised) and updating things in Update might happen slightly before or after that information is needed inside onaudiofilterread. It seems like communication between stuff happening inside and outside this function is a little limited when you want to keep things precise, thus the solution seems to be to store all the tracks to be played, in their right order, before execution. That way you can have a loop inside this function that can just increment time and look what to do next all by itself.

Since doing that will require some nasty restructuring of my code I decided to check with the community for some advice, tips, tricks, pitfalls etc :slight_smile:

Go with the OnAudioFilterRead, but structure your code so you don’t need to call anything from inside that method. The only thing that method should do is play sounds. But it can play them when and how you want. Of the top of my head, I’d probably create a 2D array that holds note values. Each column might represent a quarter note. Each row, a note number. Users could populate this array with notes through the GUI. I’d then iterate through this array in my OnAudioFilterRead() method and output the notes. Users can change the outputted notes by modifying the GUI. I’m no guru, but my gut instinct tells me if you find yourself needing to call external methods within OnAudioFilterRead() you may need to redesign things a little.

Thanks, Rory. Deep down I was hoping for someone to come and say “nooo there’s a much simpler way!” lol.

Turns out though it’s not that big of a restructuring after all. I’ll keep both methods of play. A precise method and a dynamic method, for when you want to drag the mouse back and forth across a particular section of the track or something like that.

I am working on something similar and when searching I stumbled upon some audio demos provided by Unity:

https://bitbucket.org/Unity-Technologies/audiodemos

I can’t remember where the link to this was, but it appears to be recently updated. The Techno demo seems like the preferred method to set up a sequencer and rather than use OnAudioFilterRead it uses PlayScheduled to precisely play sounds. It is also interesting to see that the tempo is based on the Master audio Pitch (speed). And, the script moves a Pitch effect to compensate so the sound is faster, but the pitch of the notes remains the same.

My approach was completely different, where I created a metronome and tied my samples to that beat. But, as you said it is tricky using OnAudioFilterRead to set up other effects based on timing. I think the PlayScheduled approach may be better…

Thanks, Voronoi.

I have a system in place using OnAudioFilterRead. Part of the reason I chose this is because it also uses a custom synthesizer. You could still make your own wave pattern and create a clip though.

Anyway, it’s working. It pops and crackles occasionally. The whole thing looks very fragile but it works lol.