Skip to content

Meet Cascade: A Piece of Music Software I Built with Max and Rapid Prototyping

A Step-By-Step Guide to Building this Strange Littler Sequencer

This post is about how I built Cascade, a sequencer that holds every combination of eighth notes possible in a 4/4 bar. I cover rapid prototyping, a little bit of music theory, and Max programming techniques. The Max patch, M4L Device, and Ableton Live Demo are available here. I also made a short demo video showing life before and after Cascade.

What I Almost Built Instead

Shortly after finishing my cellular automata project I started thinking about how I might apply what I’d learned to music. I sketched out this interface and quickly ran away in horror:

The idea was you could type in a string — “cat” in this prototype — and a generation of cells would be seeded into something that resembled a step sequencer. Then you could play the chords while stepping through each iteration of life.

An artist’s most valuable resource is their energy. Learning which ideas to pursue and which to abandon has been a life long journey for me. I am happy to report I recognized this thing was dead on arrival.

Learning which ideas to pursue and which to abandon has been a life long journey for me.

Here’s how I knew to stop and build something else instead:

  • Wrong Tech: I find working with music in web browsers to be incredibly frustrating and fraught with compromises.

  • Too Optimistic: I sheepishly sketched in “Cmaj7” in this wireframe. Sure, some Cmaj7 chords might be generated from 8 possible notes at some point in time, but would they make any type of aesthetic sense?

  • Not Interoperable: Let’s say I did ignore the technology issue and convinced myself it would be useful. What would the ceiling of this thing be? Without having an interface to connect with a DAW or hardware there’s no way to actually use this to produce music besides resampling the output.

  • Doesn’t Solve Anyone’s Problem: Have you ever wanted to type “cat” and get a deterministic series of cellular automata chord permutations that can only be played in a browser? Neither have I.

This is why rapid prototyping is great. By minimally investing in a quick drawing, I learned it was an awful idea and saved myself hours of heartache. As a developer, this is an especially useful skill as I’ve often plunged straight into the code and then quickly fallen victim to the sunk cost fallacy.

170 = 10101011

Ruminating on the “Optimistic Cmaj7” problem above lead me to the thought, “Well, just how many combinations can there actually be?”

Turns out there are only 256:

“00000000" is intentionally absent from this list.

Interesting. A data set of manageable size that relates directly to a decision set all electronic musicians face! Maybe there was more to this?

This is a simple bar of music with a few eighth notes and rests. A lot of music is in 4/4 and a lot of music software defaults to this signature and these 8th note divisions.

What if you could very quickly cycle through all 256 permutations of eighth notes? How much manual sequencing would that reduce? How could a creative tool like that be abused to unlock new musical ideas? I saw this piece of the UI in my mind’s eye:

128 + 128 = 256… unless you are zero-indexed.

The “cat” idea is back! But reincarnated as a number!

The standard range on MIDI dials is 0–127. Since they start counting at zero instead of one, they actually have 128 different values. 128 + 128 = 256. Each of the 256 numbers could be associated with a unique pattern. In computer science terms this is known as a “key-value pair.” Just remember the number and you can access your favorite pattern in seconds!

At this point, I knew this was going to be a Max patch and maybe a Max for Live device, solving the previous concept’s “Wrong Tech” and “Not Interoperable” deficiencies. I wrote a little throwaway “spike” in Max to prove my binary lookup table and dials worked. They did.

Time to develop?

Nope.

Step back.

Time to prototype again.

Rapid Prototyping “Cascade”

I knew the UI would feature two dials, the sum of the dials, a visual representation of the pattern, and some array of MIDI routing controls:

Iteration 1

The MIDI Thru would either allow or block previous signals in the chain. The “Note Send” dropdown would open up a list of MIDI out channels. Adding this reminded me how frustrating that activity can be. There’s too much data and it is so tedious to pick from a list. If only there was a way to quickly cycle through large tables. Oh, we just built something like that, didn’t we?

Iteration 2

Here the “Note Send” became another dial just like the other two controls. At this point I an “ah ha” moment and was very excited to see this thing through to completion.

Iteration 3

Here I was working through display paradigms and hierarchy.

Iteration 4

This iteration was a bit of a breakthrough. While working on the spike, I found myself scrolling through the zeros and ones, becoming almost hypnotized at the patterns. This gave me an intuitive understanding of what would come next and which direction I needed to go to find a certain type of pattern.

I decided to expose this rich “cascade” of data to the user. And there’s our name!

Iteration 5

Here I boxed off the MIDI routing into its own component to show it was clearly concerned with something other than the cascading numbers.

Iteration 6

How might we easily see which pattern is selected?

Iteration 7

Another breakthrough. Reading zeros and ones activates the linguistic parts of our brains, which triggers all sorts of other junk that isn’t helpful for making music. I replaced the text with a simple matrix of on/off cells.

Iteration 8

Adding the number back in was important. It accidentally fell off between iterations 4 and 5 when I switched to the number cascade.

Mute was important too. Our set of patterns was not going to include the non-pattern of “00000000” because that would just be silence. Performing mute buttons always allows for interesting music possibilities, enabling you to cut out particular notes.

The Random button was pure feature creep. There was some empty space so I filled it with the first thing that came to mind.

Iteration 9

My favorite phase of design: “tightening up”. This is when you have a pretty high degree of certainty that all elements are present.

Iteration 10

The MIDI routing is the least important part of this device. It also controls the output, so it should be at the end.

Iteration 11

Nice. I can tell this is a great level of fidelity to build from because there is both precision and a still room for the imagination to work. And here’s a fun animation of the evolution:

Time to Develop

Here’s a screenshot of the complete patch in Max. I think it is helpful to see the whole thing before diving into the details.

Building the UI

I like to build UI loosely coupled with initializing [receive] and [send] objects. This is good practice in all software development because you can freely move them around without causing structural damage to your code.

I’m also a fan of limited color palettes and black. How unique!

From left to right we have:

  • A and B dial controls: These are the primary controls of the device.

  • A value, B value, and sum: While developing I found it useful to see a read out of each dial as I turned it. This “developer feature” ended up turning into a “end user” feature.

  • The binary representation of the pattern: This is another developer feature, but one that didn’t make it into the final.

  • Muted / Unmuted button: I like my text on toggles to change depending on the state. Just inverting the color of the text and background is difficult for me to grok.

  • MIDI Out and Note: You’ll notice I dropped the “Thru” feature.

Note Out and Initialization

This section initializes all our UI values to 0. Looking at it now I realize the two [int 0] objects could be refactored to be only one. Oh well. Perfect is the enemy of shipped software. I will say I had originally thought I would have an [int] for each of the [send] objects.

Perfect is the enemy of shipped software.

[r uiDial3] receives data from this the MIDI Out dial and just cycles through a big list of human-readable MIDI notes (from C-2 up to C8).

Dial Sum

This little section adds the values of the dials together. Since the [+] object only fires on the left inlet so I added a trigger with [t b i]. Now whenever integers stream in from [r uiDial2], it will cause a bang to be sent to the left inlet of [+], adding our values together.

Dial Controller

The dial sum is then sent here. I’m calling this a “controller” because it kinda resembles the Controller part of MVC framework: it is controlling and directing action. First it is sent to an encapsulated subpatcher before some output goes to [s s4] and [s uiPatternValue]. It is also sent to [s uiPatternKey], which is just the readout we saw in the UI.

Key to Sequence Converter

This is the contents of [p keyToSequence]. The first two [if] statements are pieces of defensive coding to make sure no values outside of our bounds ever make it in. Next up is a [umenu] containing our list of all the binary permutations. This is sent out to the second outlet and to an [unpack] object. After the list is unpacked, each number gets prepended with a different column and row number so we can populate our cascade matrix.

The Cascade Matrix

This subpatch is then reused multiple times for the cascade matrix. I opted to use seven 1x8 [matrixctrl] objects instead of a single 7x8 object because this made more sense to me.

Whatever number is selected with A and B dials will set the middle row. Then that number is incremented and decremented to show the following and preceding rows.

It looks really cool when it’s running:

Transport

Finally, we have some simple transport controls. The start button in the UI triggers a [metro] which then sends a getcolumn message to the [matrixctrl]. The lower right fork gives us a visual indicator to see the current step on yet another [matrixctrl] object set with lower opacity.

The “API”

This is it! The fruits of our labor. [noteOut] displays the MIDI note that was selected with the MIDI dial. [noteToggle] toggles on and off based on our pattern and transport position. With these two values, future developers will be able to expand this however they want!

Conclusion

I quickly packaged this up in a M4L device and to my surprise, it worked on the first try! 

You might also like