Algorithmic music: grammars

Evoking Elliott Carter

Here's a piece built from a very simple recursive grammar which sound remarkably like Elliott Carter's music---in this case, the piece is for piano and harpsichord, meaning it resembles Carter's Double Concerto for Piano and Harpsichord. Here's the piece.

Another attempt at computer-assisted composition

MP3 This is a combination of algorithm and human decision.

Networks of linked pitch class sets (algorithmic music)

In my first experiment in linked pitch class sets, I was unhappy with the lack of a sense of shape or direction in the composition. So I tried another experiment. I take a pitch class sets such as [0135]. Then I generate all forms of it available through transposition and inversion, 24 of them. I consider two forms to be "linked" if they share are least two pitch classes. I wrote a program to arrange these 24 forms as 24 nodes of a network, in which a two nodes are connected if they are linked. I pick one node as the start, then find the minimum path distance between that node and every other node in the network. E.g., some nodes have distance one, meaning they are one link away from the start node. Some nodes have distance three, meaning that at least three links need to be traversed to reach the start node.

Then I generate a chord sequence by traversing the network, giving the sequence an overall shape by staying near nodes with the minimum distance at the beginning, moving gradually to nodes with the greatest distance at the middle, then moving gradually back to nodes with the minimum distance at the end.

Here's a piece made this way, out of [01358].

Here's a piece made this way, out of [01578].

Simple melodic line

Here's a short composition built from applying a recursive grammar. I love how simple rules can make something that sounds almost like human expression.. it has phrases, themes, a sense of flow.

Here's part of the code that makes this composition. It declares rules that indicate how to expand symbols. An "A" expands to "ABA". A "B" expands to "BAB". Also, each symbol carries attributes: duration, pitch, and amplitude. The formulas indicate how to compute the attributes for new symbols.

recurgram.declare( "AB", "pitch", "dur", "amp" )

Rule( "A", "ABA",
      "A1.dur = A1.dur",
      "A1.pitch = A1.pitch - 1",
      "A1.amp = A1.amp ",

      "B1.dur = A1.dur * 2.0",
      "B1.pitch = A1.pitch + 1",
      "B1.amp = A1.amp -6",

      "A2.dur = A1.dur",
      "A2.pitch = A1.pitch + 5",
      "A2.amp = A1.amp ",
      )

Rule( "B", "BAB",
      "B1.dur = B1.dur",
      "B1.pitch = B1.pitch + 3",
      "B1.amp = B1.amp",

      "A1.dur = B1.dur * 0.9",
      "A1.pitch = B1.pitch -2",
      "A1.amp = B1.amp",

      "B2.dur = B1.dur",
      "B2.pitch = B1.pitch - 5",
      "B2.amp = B1.amp + 3",
      )

An example of how the composition is initialized:

  n1 = acomp.Node( symbol="A",
                    pitch = 60,
                    dur = 1.0,
                    amp = 65,
                    )

  initial = [ n1 ]

Here's the piece.

Here's some other information about how the symbols are converted to notes. In this case, after 3 to 5 applications of the grammar, you end up with a string of anywhere from 27 to several hundred symbols. Each symbol becomes a note of the specified pitch, duration, and amplitude. I regard these notes as a single melodic line of non-overlapping notes. Thus the duration determines the rhythmic patterns.

First attempt at counterpoint

Then what interested me was creating a sense of overlapping, simultaneous or near-simultaneous events. How do you take a grammar which is inherently sequential and create from it counterpoint?

My first attempt to address this problem was to assign extra attributes, representing channel (notes are divided into right and left channels to give the sense of two instruments), and two extra attributes to affect rhythm. First I conceptually separated the rhythm from the duration in this way: Notes still have duration, but what determines when the next note arrives is something called the "span". If the spans are short, for example, the rhythm will be fast. But at the same time the durations could be long, leading to overlapping notes. There is one other attribute to control rhythm, which is "time offset." To determine the actual start time of a note, first all the notes are taken as though a single melody with the 'span' attribute indicating how much time passes between attacks, and then each note is offset in time according to the 'time offset' attribute.

Thus some notes move close together in time and feel more like a single event, a clump separated from other events in the composition.

An example rule set is here. This indicates that every occurance of 'A' in the string of symbols turns into 'ABA', and every occurance of B turns into 'BAB'.

def ruleSet2() :
   r1 = Rule( "A", "ABA",
              "A1.dur = A1.dur * 1.333",
              "A1.pitch = A1.pitch - 1",
              "A1.amp = A1.amp -4 ",
              "A1.span = A1.span * 0.75",
              "A1.offset = - 1.333 * A1.offset ",
              "A1.channel = (A1.channel + 1) % 2",
           
              "B1.dur = A1.dur * 0.75",
              "B1.pitch = A1.pitch",
              "B1.amp = A1.amp+1",
              "B1.span = A1.span * 1.1",
              "B1.offset = 0.8 * A1.offset",
              "B1.channel = A1.channel",
           
              "A2.dur = A1.dur",
              "A2.pitch = A1.pitch + 7",
              "A2.amp = A1.amp-2 ",
              "A2.span = A1.span * 0.8",
              "A2.offset = 0.5 * A1.offset",
              "A2.channel = A1.channel",
           )

   r2 = Rule( "B", "BAB",
              "B1.dur = B1.dur * 1.333",
              "B1.pitch = B1.pitch - 1",
              "B1.amp = B1.amp",
              "B1.span = B1.span * 0.666",
              "B1.offset = B1.offset",
              "B1.channel = (B1.channel + 1) % 2",
           
              "A1.dur = B1.dur",
              "A1.pitch = B1.pitch +1",
              "A1.amp = B1.amp  + 3",
              "A1.span = B1.span * 1.2",
              "A1.offset = B1.offset - 0.1666 * B1.span",
              "A1.channel = B1.channel",
           
              "B2.dur = B1.dur * .5",
              "B2.pitch = B1.pitch + 6",
              "B2.amp = B1.amp-1",
              "B2.span = B1.span * 0.5",
              "B2.offset = B1.offset + 0.3333*B1.span",
              "B2.channel = (B1.channel + 1) % 2",
           )

   ruleList = [ r1, r2 ]
   return ruleList

One possible way to initialize the composition is here:

  n1 = acomp.Node( symbol="A",
                    pitch = 60,
                    dur = 1.0,
                    amp = 65,
                    span = 0.6,
                    offset = 0.5,
                    channel = 0,
                    )

  n2 = acomp.Node( symbol="B",
                   pitch = 65,
                   dur = 1,
                   amp = 72,
                   span = 0.3,
                   offset = 0.5,
                   channel = 1
                   )

  initial = [ n1 ]

Here's an MP3.

Second attempt at counterpoint.

Here's a second attempt to create multiple instruments and overlapping events. In this case, each symbol of the grammar carries attributes of two notes: two pitches, two durations, two offsets, and so on. (Note, however, there is only one span.) Then upon converting the symbol list into a composition, each symbol becomes two notes, one for each instrument.

An example grammar is here. (Note: apparently this is an earlier version that had a span2 attribute, but currently that is ignored.)

def ruleSet3() :
   """ First attempt to base offset on ratio to span only"""
   r1 = Rule( "A", "ABA",
"A1.offset1 = A1.offset1 + 1.0 * A1.span2",
"A1.offset2 = A1.offset2 + 1.333 * A1.span1",
"A1.dur1 = 2.0 * A1.span2",
"A1.dur2 = 1.25 * A1.span2",
"A1.span1 = 0.666 * A1.span2",
"A1.span2 = 1.0 * A1.dur1",
"A1.pitch1 = -5 + A1.pitch1",
"A1.pitch2 = 5 + A1.pitch1",
"A1.amp1 = -4 + A1.amp1",
"A1.amp2 = -4 + A1.amp1",
"B1.offset1 = A1.offset1 + 1.25 * A1.span2",
"B1.offset2 = A1.offset2 + 0.8333 * A1.span2",
"B1.dur1 = 0.8333 * A1.dur1",
"B1.dur2 = 0.8333 * A1.span2",
"B1.span1 = 1.5 * A1.span1",
"B1.span2 = 1.0 * A1.dur1",
"B1.pitch1 = -1 + A1.pitch2",
"B1.pitch2 = 1 + A1.pitch2",
"B1.amp1 = 3 + A1.amp1",
"B1.amp2 = 0 + A1.amp1",
"A2.offset1 = A1.offset1 + 1.25 * A1.span2",
"A2.offset2 = A1.offset2 + 0.5 * A1.span2",
"A2.dur1 = 1.0 * A1.span2",
"A2.dur2 = 1.0 * A1.span1",
"A2.span1 = 0.75 * A1.dur2",
"A2.span2 = 1.25 * A1.dur1",
"A2.pitch1 = -4 + A1.pitch2",
"A2.pitch2 = 3 + A1.pitch1",
"A2.amp1 = 4 + A1.amp1",
"A2.amp2 = -4 + A1.amp2",


              )
   r2 = Rule( "B", "BAB",
"B1.offset1 = B1.offset1 + 1.5 * B1.span2",
"B1.offset2 = B1.offset2 + 1.25 * B1.span2",
"B1.dur1 = 2.0 * B1.span2",
"B1.dur2 = 0.666 * B1.dur1",
"B1.span1 = 0.75 * B1.span2",
"B1.span2 = 0.8333 * B1.dur1",
"B1.pitch1 = 4 + B1.pitch2",
"B1.pitch2 = -7 + B1.pitch2",
"B1.amp1 = 0 + B1.amp1",
"B1.amp2 = 4 + B1.amp2",
"A1.offset1 = B1.offset1 + 0.666 * B1.span1",
"A1.offset2 = B1.offset2 + 0.5 * B1.span2",
"A1.dur1 = 1.333 * B1.dur2",
"A1.dur2 = 0.666 * B1.dur1",
"A1.span1 = 1.0 * B1.span2",
"A1.span2 = 0.8333 * B1.span2",
"A1.pitch1 = -7 + B1.pitch1",
"A1.pitch2 = 2 + B1.pitch1",
"A1.amp1 = -1 + B1.amp1",
"A1.amp2 = 4 + B1.amp1",
"B2.offset1 = B1.offset1 + 1.25 * B1.span2",
"B2.offset2 = B1.offset2 + 2.0 * B1.span1",
"B2.dur1 = 0.8333 * B1.span1",
"B2.dur2 = 1.0 * B1.span1",
"B2.span1 = 1.333 * B1.dur1",
"B2.span2 = 1.333 * B1.dur1",
"B2.pitch1 = -2 + B1.pitch2",
"B2.pitch2 = -7 + B1.pitch2",
"B2.amp1 = -2 + B1.amp2",
"B2.amp2 = -3 + B1.amp1",
              )
   return [r1, r2]
              
 

And an example of a way to initial the composition is here:

  n1 = acomp.Node( symbol="B",
                   pitch1 = 65,
                   pitch2 = 67,
                   dur1 = 1.0,
                   dur2 = 1.0,
                   amp1 = 70,
                   amp2 = 70,
                   span1 = 0.6,
                   span2 = 0.6,
                   offset1 = 0.0,
                   offset2 = 0.0
                   )

An example of this, using a piano as one instrument, and harpsichord-like synthesis for the other, is here:

MP3 soundfile.

Linked pitch-class sets