It documents the process of me smashing my head against the keyboard to build a game called LETSGO
Itās gotten long enough to break into several sections:
So there is a couple things I seek to accomplish now that the CreateMotif function is created.
Much of those things are clearly evident in the latest demo:
More audio chaos than music.
The reason is pretty simple- thereās only 2 musical strategies available, so it keeps creating motifs over and over again.
What I need is more musical strategies, strategies that consume and manipulate a created motif.
Technically I built the ability to do this in the data structure of ComposerData in Generative Music Without LLMs, but it would be a looping thing that kind of smells.
And writing the CreateMotif
function was fairly difficult due to fitting all the different data structures together.
So Iām thinking there is the opportunity to add a layer of abstraction to the MusicComposer.
A NaĆÆve Approach to Music Composition
Currently, the Composer runs a complicated tick function to trigger whether or not it generates music, writing musical bars and determining which musical strategy is most appropriate.
It does not have much nuance. Itās only considering the next two bars of music, filling the next two bars with whatever strategy seems most appropriate.
What I need is something that sits on top of this, something that manages a long view of the musical composition.
- It understands the structure of music: intro, bridge, chorus, verse, etc.
- It understands that the appropriateness of musical strategies is different in each section.
- It should make managing musical strategies easier
- Right now the game crashes due to a weird bug related to how strategies are being managed.
- It should be unconcerned with the actual notes and data structures the rest of the game requires to make the actual sounds.
I will need to design an object that satisfies the above requirements, build that object, refactor the MusicComposer to use it, then start implementing musical strategies using the new system.
Easy peasy, right?
Requirement Refinement
It should be unconcerned with the actual notes and data structures the rest of the game requires to make actual sound.
Lets dive into this.
In the game, the player can set tonic key, major/minor third, etc.
Which means off the rip the composer does not have knowledge of what keys it can use.
Thankfully, in music theory donāt need specific keys in order to generate musical structures.
Consider the common jazz progression: ii V I (minor second chord, major fifth chord, tonic chord)
This works whether the I chord is in the key of Db or F# or any other of the 12 notes dividing culturally western music.
So, I will need an object that works with music in the same way, defining the musical structures without specific keys.
In a similar vein, one of the insights I already built into the Composer is that it only uses the chromatic scale (a scale containing all 12 notes).
The chromatic scale doesnāt sound all that musical (or, at least, is very easy to make sound unmusical), but all other scales are a subset of the chromatic- all scales are the chromatic scale, just with missing notes.
What I wanted with the CreateMotif
function was to generate a musical idea. That idea got ground down into the requirements of FInstrumentSchedule
and FComposerData
.
I want something lighter. I want something like a seed idea [ii V I]
, a simple structure that cascades and evolves throughout the musical composition.
Something even simpler than that - [2nd 5th 1st]
, or simply [2, 5, 1]
as a dead simple array of notes. A seed idea.
That simple idea is then given an order of operations.
āEstablish bass pedal point on the 1stā ā āBass plays the 2-5-1 progression on the first note of each barā
- For this, the bass writes out music like
[1, 1, 1, 1], [2], [5], [1]
- The MusicConductor consumes this and converts it to the data used by the rest of the game:
[A, A, A, A], [B, -, -, -], [E, -, -, -], [A, -, -, -]
So, we need an object that Generates Seed Idea
, whose output is something like [2, 5, 1]
We need an object that, for each instrument we manage, generates an order of operations.
- For the above example, that was only a single instrument. How does bass interact with alto, tenor, soprano?
We need an object that