l-sequencer: for branching Lindenmayer System strings

l-sequencer

A Max-based sequencer for branching Lindenmayer System strings.

This project is a Max Package that provides three Max abstractions for working with branching Lindenmayer Systems (L-systems). The core abstraction, the [lsq.sequencer] object, is a high-level, symbolic sequencer. You can define L-system production rules and process lists of symbols through the rules in the standard manner of L-system string rewriting.

It’s all about the branching…

However, the primary use case for this sequencer is not its simple string rewriting as this has been done before and in much more detailed and better implementations. …what this book presupposes, is maybe he didn’t… uhhh, I mean, the focus of the package is on interpreting L-system strings with branching symbols so that a sequence can branch into simultaneous events in a manner similar to a polyphonic sequencer. Consider the following L-system string of characters:

+ + F Y X [ F + Y ] - F [ X ] [ - Y + F ]

The symbol pairs + / - and [ / ] have special meaning within the l-sequencer package. The first set are “metadata” symbols that can be paired with sequencer events (see the Max documentation for more details). The second set are the branching symbols. Branches cause new sub-sequences of events to start occurring simultaneous to the events from which they are offshoots. As noted above, the simplest way to think about this would be polyphony: if the symbols represent notes, when a branch happens, simultaneous notes will play as a chord.

The 1-dimensional string above (that is, a simple array-like sequence of characters) would be represented in a 2-dimensional matrix in the sequencer as:

1      2      3      4      5

++F    Y      X      -F
              F      +Y
                      X
                     -Y     +F

The numbers are not part of the representation, they are just step indices in the sequencer and are added to aid in explanation. Because the first branching character “[” happens after the first “X” event character, the next event character in the string, an “F”, happens at the same time as its preceding event. Therefore both the “X” and “F” events occur on step 3 in the sequencer. The symbols started in a branch will stay in the same matrix row until the branch terminates with the “]” character.

Symbolism, or, what’s with all the F’s X’s and Y’s?

I refer to this as a symbolic sequencer because it deals in symbols only. The meaning and interpretation of any symbols is left entirely to you, the composer. The symbols could represent low-level data points like MIDI note numbers, but they could also be used as events that fire off a subsequent complex process.

Caveats and notes

Internally, the sequencer uses a Jitter matrix for data storage and access. It is not an efficient use of the matrix and a lot of cells in the matrix will go unused (because managing branches is hard, yo). If you advance a given L-system past a certain point, it will very likely begin to produce strings that exceed the number of events the matrix might store.

The package contains no externals and is built entirely from core Max objects, so hopefully there are no problems with installation. In this way, I hope that it can also serve as a useful reference implementation for others to tear apart and improve for their own needs. But doing this entirely in Max objects is also, shall we say, Super Finicky Event Circuitry™. I would not be surprised if there are bugs and if some of the bugs be dragons.

This sequencer is functional for me, but I have not pushed it particularly hard in terms of seeing where it hits any limits. You can configure the [lsq.sequencer] @steps count and the @maxbranches (matrix row count) attributes, but at this point I don’t have any idea if or when it would fall over under the strain of trying to process too many simultaneous events. Feel free to report back here if you notice this happening, a la, share the knowledge!

Requirements

Max 8

Documentation

Aside from the README, documentation is done entirely within Max itself. The three objects in the package [lsq.sequencer], [lsq.intepreter] and [lsq.translator] have help files and reference pages so you should be able to see within Max how they work and the kinds of messages they accept. The screenshot above is one such help patcher.

I have also created a Max “vignette” that should show up if you use the Max search feature and search for “l-sequencer”.

Finally, in the package’s extras directory/folder you will find a simple Max patcher that illustrates the sequencer’s use. It uses the default rules that the system loads by default and plays 3 notes that are transformed by walking through the circle of 5ths depending on the metadata symbols associated with a given step. It is a very rudimentary version of one of the branching L-system musical applications in R. Luke DuBois’ dissertation.

I am my own primary reader of the docs (“Your worst collaborator is you from six months ago and that person doesn’t respond to email…”), so please let me know if something is unclear.

Download

v0.1.0 - Download or git clone the repository into your Max Packages folder. For me, on a Mac, this means I have the files saved in the following location:

~/Documents/Max 8/Packages/l-sequencer

Note that if you download the zip it may append the version or branch name, so you may want to make sure it is not named “l-sequencer-main” or something similar.

Inspiration and thank you lines…

This package is inspired by R. Luke DuBois’ dissertation, which I learned about through the generous knowledge sharing that happens here (see the Generative systems thread). Please kick the tires on this weird little bundle of patchers. I hope it might inspire someone. And of course, let me know if anything is unclear.

13 Likes

Thank you for sharing this – great idea, literally generating many other ideas…

Thanks for the feedback! I’d love to hear your ideas. I started writing some possible use cases I thought of, but my initial post had already gotten super long.

Here is one that may pique some interest a la the recent discussions about Fell-ian Pattern Synthesis. Because multiple “metadata” symbols can be associated with an event, I have done some experiments where I count the number of “+” metadata symbols prior to an event and feed it into a tmh.rapid_trigger by @sonoptik

Generally, the L-system concept is proving very interesting as a component within a bigger ecosystem.

1 Like

nice @stephenmeyer - I’m going to check this out :slight_smile:

Bump to v0.1.1 for a bug fix.

I found a (rather embarrassing) bug that has now been fixed. Simple L-System production rules where one character is replaced by only one other like:

F => X

were not working (apparently I tend to live exclusively in a land of needless complexity, but that is another issue altogether). I discovered it by trying use this package to replicate some simple “finite automaton” examples in Tom Johnson’s Self-Similar Melodies.

The specific issue is that there are some [coll] objects used for intermediary storage in the patch. I was not aware that when a single symbol is output from a [coll] that it would prefix the value with ‘symbol’.

It was a relatively easy fix, but given how the event programming in the relevant Max abstraction is complex, I decided it was time to add some unit and/or functional-style tests. So the package now includes a spec directory where a test suite puts the Max abstraction patchers through their paces. The test suite is written in Ruby using RSpec so I could have all the usual programming conventions for tests (reasonable way to think about mocks, assertions, post-test cleanup, etc). Ruby communicates with Max via OSC (a brief write-up is in the README’s Testing section).

1 Like