Genome Music Sequencer Update — The Data Model

A quick look at how the Genome maps concepts from genetics to music composition and sound design

Jeremy Brown
4 min readJan 12, 2023
Genome Music Sequencer wireframe

In this article I’ll share how the data model for the Genome Music Sequencer is structured. I’m thinking that this will be a relatively quick explanation that will set the foundation for a deeper dive into the strategy for mutating between two sequences (in a future article).

Before we get into it please note: This project is only “inspired” by genetics and evolution. It’s not intended to be biologically accurate necessarily. For more information on the original concept for the instrument see my intro article here.

Genome Music Sequencer wireframe

The Model

Naturally, the Genome data model is a hierarchical one. A top down view of it looks something like this:

                          Genome
||
|---- ----|
GeneSequence GeneSequence
| |
|----|----| |----|----|
Gene Gene Gene Gene Gene Gene
| | | | | |
|----|----| ... ... ... ... |----|----|
Nuc ... Nuc Nuc ... Nuc

Nucleotides
We’ll start by looking at the most atomic structures in the data model, the Nucleotides, each of which represent a single mutate-able sonic parameter. Pitch, amplitude, waveform, attack and release are all examples of such parameters. The total number of them has yet to be determined.

One important aspect about each of the parameters is that they are internally represented as discrete values. While an amplitude in a normal program might be thought of as a continuous value from zero to one, in this project, such ranges are actually discretized.

Using math.js library for discretizing values

In a typical audio program an amplitude value might be a real value on the interval [0, 1]. In a Nucleotide, the interval is transformed into a specific quantity of discrete values. If that quantity were five, then the possible values would be [0, 0.25, 0.5, 0.75, 1.0]. Thus, it’s an integer index pointing at the value that can be mutated. This makes the mutation process much simpler, because rather than randomly mutating to any value between zero and one, we only need to mutate by whole amounts.

Genes
Each Gene represents a single musical note, whose sonic parameters are defined by its Nucleotides. In the sketch below, there are ten Genes. The second one is having its pitch Nucleotide edited.

Each of the squares on the bottom row represents a Gene

Genes are fairly simple objects, whose “state” can be mutated to one of four possibilities: Off, On, Null & Offset. I’ll provide more context for these states in a later article detailing the mutation strategy.

Gene Sequences
A series of Genes creates, surprisingly, a GeneSequence and represents a several musical notes that are played one after another. The only mutation that is possible in a Gene Sequence is the length of the sequence. This is possible when two Genomes are of different size.

Genome
The Genome is a collection of Gene Sequences and can be thought of as a complete musical “composition”. It’s easy to imagine the entire genome as a grid of squares, where each row is a sequence of notes. When a Genome is “played” each column of notes plays on each beat or tick. This grid view is typical in most music making software like Ableton Live or Logic.

Wireframe of a complete Genome

A somewhat unique feature of a Genome is that blank spaces can be inserted before sequences begin. I added this feature as a way to introduce more complexity into the sequences. Combined with a “contract & expand” feature, where the total number of steps can be increased or decreased, causing sequences to loop or truncate where needed, should add a lot of expressiveness to the instrument. It will make creating polyrhythmic sequences and atypical time signatures possible. It does increase the code complexity a bit, but in my opinion it will be worth it.

The Common Thread: mutate(omega):delta

Unit testing the Genome Data Model

All of the models discussed here share one thing in common, a function called mutate. This function accepts a single argument omega, a target object of the same type, and returns a numeric value delta, the distance from itself to the target. When this function is called on a Genome, it then calls the same function on each of its GeneSequences, which call it on all of their Genes and the Genes call it on each of their Nucleotides. At each level a delta value is calculated and returned. Eventually, after many generations, sometimes up to a million depending on various parameters, the delta is 0 and the Genome has fully evolved!

There is a lot more to discuss with respect to how mutations occur. As previously mentioned, we’ll take a deep dive into that topic in an up-coming article!

--

--

Jeremy Brown

Software Engineer, originally from the U.S. and now living and working in Vienna, Austria. I write about Programming, Music, Machine Learning & NLP