Generative Art and Music

2020-11-19codefpmusicartrc

RC Friday Show

November 19, 2020

The three pieces below, Duet, Nervous Chase, and Space Invaders, were written using Paul Hudak’s Haskell library, Euterpea. I’ve learned everything that went into these compositions from The Haskell School of Music, by Paul Hudak and Donya Quick, and also from Donya Quick’s website and other resources she has published on the web.

Duet

^^ High quality version (18mb .wav file versus 1.3mb .mp3; you hear the difference!)

Duet is constructed with traditional compositional principles, i.e., is not algorithmic.

Nervous Chase

Nervous Chase is based on the notion of L-systems, a kind of formal grammar that Aristid Lindenmayer used to describe the growth of plants.

Space Invasion

Space Invaders, is based on random sequences of integers, some produced by a uniform probability distributions, others produced by random walk (Brownian motion). These sequences can be used to make visual art as well as music. We illustrate this with points and remarks and images at the end of this post.

As a side note, both kinds of sequences can be used to produce noise: white noise and brown noise, respectively. I find the first annoying (it is like static). The latter is relaxing — more like the sound of the sea at dawn.

The image below depicts the three lines of MIDI music in Space Invaders. Can you guess which type of random sequence is used for each line of music?

Comment. Musical composition is, among other things, the art of balancing novelty, repetition, and structure. Pure randomness is fleetingly entertaining but not musically satisfying. If it is used, it must be managed so as to yield a composition with some structure. Space Invasion achieves this to an extent by the use of three lines, each played on different instruments, with a variety of entrance and exit points.

Program Notes

Here is the repo for the code. It is badly in need of some cleanup and editing. Soon!

Below is a short snippet of Euterpea code, the “theme” from which Duet is composed. It reads as follows: Make a line of music beginning with a whole note D in octave 1, playing first a D minor triad in whole notes. Follow this by a 5-note passage in quarter notes and end with half-note whole-note resolution.

bass1 :: Music Pitch
bass1 = line $ [ d 1 wn, f 1 wn, a 1 wn,  g 1 1, f 1 qn, e 1 qn, d 1 qn
               , c 1 qn, e 1 hn, d 1 wn]

Duet

^^ High quality version (18mb .wav file versus 1.3mb .mp3; you hear the difference!

Code

This piece is a duet between two bassoon-tuba combos, with a later entrance by a trombone ostinato. It is composed using quasi-traditional rather than algorithmic methods, building up the piece by successive application of operators like :+: and :=: — the first joins pieces of music in series while the second stacks them in parallel. Here is a fragment derived from bass1:

bass2 d = instrument Bassoon $ bass1 
       :=: (instrument Tuba $ transpose 19 $ offset d $ retro $ bass1)

It reads as follows: play bass1 on bassoon and stack it against a line derived from it played on Tuba. To derive that line, offset bass1 by d whole notes and then transpose it up 19 semitones, that is, an octave and a perfect fifth. While the delayed entrance can be any rational multiple of a whole note, we use small integer multiples here.

Note that bass2 is a function of type Rational -> Music Pitch. We use it to derive a set of four lines which we subsequently join end-to-end:

b1 = bass2 0 :+: rest wn
b2 = transpose 5 $ bass2 2 :+: rest wn
b3 = retro $ transpose 7 $ bass2 4 :+: rest wn
b4 = retro $ transpose 5 $ bass2 2 :+: rest wn

Each phrase ends with a whole-note rest so that when the pieces are joined end-to-end, there is a bit of intervening silence. The function retro :: Music Pitch -> Music Pitch re-arranges the given notes in reverse order. This is a classic compositional technique):

Retrograde was not mentioned in theoretical treatises prior to 1500.[1] Nicola Vicentino (1555) discussed the difficulty in finding canonic imitation: “At times, the fugue or canon cannot be discovered through the systems mentioned above, either because of the impediment of rests, or because one part is going up while another is going down, or because one part starts at the beginning and the other at the end. In such cases a student can begin at the end and work back to the beginning in order to find where and in which voice he should begin the canons.”[2] Vicentino derided those who achieved purely intellectual pleasure from retrograde (and similar permutations).” — Wikipedia

A long line is constructed using these derived parts:

bass3 = b1 :+: b2 :+: b3 :+: b4 :+: dim 0.6 b1

The operator dim 0.6 applies a diminuendo to its argument, the line b1. To achieve a somewhat richer texture, a second part is composed as follows.

ostinato1 = instrument Trombone $ sta 0.9 $  
   line [a 3 hn, d 3 hn, c 3 hn,  g 2 hn, e 3 hn, d 3 wn, rest wn]

ostinato2 = mSequence [0, 2, 7, 5, 0] ostinato1 
   :+: (instrument Trombone $ dim 0.5 $ transpose (-5) 
       $ line [a 3 hn, d 3 hn, c 3 hn,  g 2 hn, e 3 wn, d 3 2])

The function sta 0.9 renders its argument with staccato articulation. The function mSequence makes a sequence out of the given phrase using the supplied list of intervals to transpose the phrase:

mSequence :: [Int] -> Music a -> Music a
mSequence [] m = rest 0
mSequence (i:is) m = (transpose i m) :+: (mSeq is m)

The last step is to stack the parts on top of one another:

duet = bass3 :=: (offset 6 $ transpose 12 $ bass3) :=: (offset 12 $ ostinato2)

One plays the piece like this:

playDev 6 duet

Here the number 6 is the device ID for the synthesizer I happen to be using (fluidsynth). For MIDI output, I used

writeMidi "duet.MID" duet

I imported the MIDI file into Logic Pro for processing: balancing the parts, adding more reverb, etc. The edited MIDI file was exported to an audio file in AIFF format and then converted to MP3:

ffmpeg -i duet.aiff duet.mp3

Nervous Chase

Code

Nervous Chase is an exercise in the use of L-systems in musical composition, per Haskell School of Music, chapter 13. L-systems were developed in 1968 by the Dutch-Hungarian theoretical biologist Aristid Lindenmayer as a mathematical model to describe the growth and development of organisms such as algae and trees. Here is a simple example. The L-system has two symbols, A and B, where A is the axiom. It also has two production rules, A -> AB and B -> A. Starting with the axiom, one uses the rules to generate ever more complex sequences of symbols:

A -> AB -> ABA -> ABAAB -> ABAABABA -> ...

One can use a system like this to generate a long string of symbols that can then be transcribed to music. Here is the RedAlgae formal grammar of The Haskell School of Music:

redAlgae = DetGrammar 'a'
  [('a', "b|c"), ('b', "b"), ('c', "b|d"),
   ('d', "e\\d"), ('e', "f"), ('f', "g"),
   ('g', "h(a)"), ('h', "h"), ('|', "|"),
   ('(', "("), (')', ")"), ('/', "\\"),
   ('\\', "/")]

The twelve symbols “a” through “h” represent the twelve notes of a major scale with given starting pitch, “/“ maps to a rest, “\” maps to rest four times as long. The other symbols map to operators on Music Pitch values, with “(“ mapping to the operator “transpose up 9 semitones” (a major sixth), and “)” mappping to “transpose down a major sixth.” These are slight variations on the text of Hudak, pp. 186-7.

The function below generates music from the redAlgae grammar given a length parameter n, an absolute pitch ap, and a duration value for notes, dur:

lsMusic :: Int -> AbsPitch -> Dur -> Music Pitch
lsMusic n ap dur = 
    strToMusic ap dur $ mconcat $ take n $ detGenerate redAlgae

The final piece is constructed from lsMusic using the operators :+: and :=: as well as functions to modify the performance, e.g., cre for crescendo:

nervousChase :: Int -> AbsPitch -> Dur -> Music Pitch
nervousChase n ap dur = 
      cre 0.4 $ (instrument Xylophone $ phrase [Dyn (Loudness 70)] $ rest 2 :+: lsMusic n (ap + 7) (dur))
      :=: (cre 0.4 $ (dim 0.2 $ instrument Bassoon $ phrase [Dyn (Loudness 70), Art (Staccato 0.7)] $ lsMusic n ap (dur)))

It is important in a piece like this to have some kind of sectional structure. Quite simple here, but it makes a difference. The piece is played like this:

playDev 6 $ nervousChase 15 40 sn

Here sn stands for “sixteenth note,” i.e., the rational number 1/16.

Space Invasion

Code

Space Invasion is based on the code and ideas in some notes of Donya Quick. The main new element here is the use of sequences of integers generated by bounded random walk as well as uniformly distributed sequences of integers. Here is the bounded random walk generator:

boundedRandomWalk :: (Int, Int) -> Int -> Int -> Int -> [Int]
boundedRandomWalk (lowerBound, upperBound) start step seed = 
    recInts (seed + magicNumber) (mkStdGen seed) where
        recInts current g = 
            let 
                (i,g') = next g 
                delta = (2 * (mod i 2) - 1) * step
                current' = bounce (lowerBound, upperBound) 
                           start step $ current + delta
            in current' : recInts current' g'

The function of bounce is to “bounce” integers into the specified range if they stray from it. Music is generated from a random walk by the function below:

melGen2 :: (Int, Int) -> Int -> Int -> Int -> Music (Pitch, Volume)
melGen2 (lowerBound, upperBound) start step seed = 
    let 
        pitches = map pitch $ boundedRandomWalk2 
                 (lowerBound, upperBound) start step seed
        vols = randIntsRange (40,100) (seed + 1)
    in  line $ map (note sn) $ zip pitches vols

There is a similar function melGen1 for generating music from the uniform distribution. With this code in hand, one can construct the final piece:

spaceInvasion s = chord [xylo s, bells s, marimba 12 s]

xylophone :: Dur -> Int -> Int -> Dur -> Dur -> Int -> Music (Pitch, Volume)
xylophone  n l h d r s = 
  instrument Xylophone $ dim d $ rit r $ cut n $ melGen1 (l, h) (345 + s)

marimba :: Dur -> Int -> Music (Pitch, Volume)
marimba n s = 
   instrument Marimba $ cre 0.5 $ acc 0.4  
      $ cut n $ melGen2 (30, 80) 30 2 (234 + s)

tubularBells n l h s = 
   instrument TubularBells $ cut n $ melGen2 (l, h) 45 4 (789 + s)

xylo s =  xylophone 5 10 110 0 0 s :+: rest 2 
          :+: xylophone (2 + hn) 10 110 0.5 0.5 (2 * s)

bells s = rest 1 :+: tubularBells 2 30 60 s :+: rest 1 
          :+: tubularBells 2 20 100 (2 * s)

Using Random Walk to Generate Images

All the images below were generated by random walk using some Haskell code.

This post explains the basics of the code used.

Random Walk

Generate successive points in the plane using random walk. Then connect the dots.

Cloud Shapes

Draw a small colored square centered at each of the points generated by the random walk.

Abstract Art

The same as above, but with larger squares with semi-transparent color (water color wash). Need to experiment more with transparency.