🎼Music, Haskell... and Westeros

Posted August 2017

Music

Music is a recurring theme on this blog. Quod Libet takes up a fair bit of spare time, but the more abstract intersection of music and programming is fun, and it turns out functional programming has a lot to offer in this space. I assume some basic knowledge of Haskell / Elm (or ML-like languages perhaps), but a minimal amount of music knowledge.

Enter Euterpea

Euterpea1 is a great music DSL for Haskell, with a long lineage based at Yale and around the late Paul Hudak’s Haskell School Of Music. I won’t try to explain its wide remit and concepts, but definitely worth reading some introductions. We’ll be focusing on the composition (music) rather than synthesis (audio) side.

There are a few other projects around (even in Haskell alone). One that looks particularly interesting, for more electronic / sample / dance-based work is Tidal Cycles, but we’re going to concentrate on Euterpea here.

Where now?

Rather than algorithmic generation of music, I became interested in how far this library could be used to create or recreate, say, Western popular music (for a start) by hand, but in a concise and readable way.

The ability to use General MIDI seemed of limited value, but having set up Timidity it became clear bog-standard software synths have come a long way since, err, the late 90s, and this is a quick, free solution for creating passable music without specialist hardware or software. Interesting…

The history of trackers

Talking of the late 90s, some readers will be too young to remember Trackers on the Amiga / ST / DOS from then, so a quick recap! tracker

These were nearly all:

  • non-notated (i.e. no traditional music score)
  • sample-based (you could provide your own lo-fi instrument sounds)
  • tabular based on discrete time-steps, like a spreadsheet of sorts with channels as columns and time steps (1/8th of a note) as … Playback would going down the screen
  • declarative (not recorded, or err, generative maybe). Entries (notes) looked like C 4 A0, or similar, meaning C in the 4th octave with a volume of 0xA0 or 160/256.
  • embedded metadata (such as BPM, vibrato, dynamics) into individual notes

Some of this experience is simply outdated by newer GUIs and advanced MIDI Sequencers, though for dance music / drum machines, similar interface persist.

…and some drawbacks

The ability to replicate entire sections in trackers was usually available in some way, but without any variation. What was completely lacking was the ability to re-use individual channels or phrases, or apply transformations (transpose, make louder or quieter, change instrument) to pre-existing parts.

In fact, this problem is sounding a lot like bad codebases, especially ones that don’t employ DRY. As developers, we’ll usually try to refactor: extracting common code to methods, building abstractions (even just functions that operate on data) and using generic types to write algorithms that can be independent of the data structure.

In functional programming these are even more important, and generic datatypes, type classes, higher-order functions and lazy evaluation allow us to do take these principles further. Could these somehow be applied to music?

Euterpea basics

The Euterpea quick reference guide is a very useful one-pager, and the SimpleMusic examples was helpful to get started. But here’s a very quick summary from the high level:

Core datatypes

data Music a  =
       Prim (Primitive a)               --  primitive value
    |  Music a :+: Music a              --  sequential composition
    |  Music a :=: Music a              --  parallel composition
    |  Modify Control (Music a)         --  modifier
  deriving (Show, Eq, Ord)

And the Primitive datatype is defined:

data Primitive a  =  Note Dur a
                  |  Rest Dur
     deriving (Show, Eq, Ord)
  • Dur, the duration for notes / rests is a Rational alias, so you’re not restricted to any particular quantisation of notes. Yes, this means it’s easy to do all sorts of polyrhythms, but more on that later (or not – we’re aiming for simple timing here).
  • Control is to apply transformations (dynamics, tempo adjustments) to other music sections, a powerful concept modelled very simply.
  • Both Music and Primitive are Functors, though this isn’t important right now.

Getting started

Well, it’s no fun just talking about this. How about we make some noise?

Setting up the project

Most of the snippets below should be run interactively in GHCi (using stack repl), but it’s best to have a Stack project set up already, so do that now (or use my euterpea-sandbox one). Here’s the interesting bits from my stack.yaml to get you started:

extra-deps:
- Euterpea-2.0.2
- PortMidi-0.1.5.2
- Stream-0.4.7.2
- arrows-0.4.4.1
- heap-0.6.0
- lazysmallcheck-0.6
- stm-2.4.2
resolver: lts-9.0

The Cabal file just needs Euterpea > 2.0.0 && < 2.1.0. I’ve put the full source on Github if you’re feeling lazy.

Set up MIDI

The Euterpea guide to MIDI output is as good as any.

If you’re on Linux I strongly recommend Timidity unless you have hardware synths of course. The FreePats samples are a good start (but don’t cover all GM instruments). For detailed setup, the Arch Linux Timidity page is good too, especially if you want to set Timitidy to run by default.

For OS X users, SimpleSynth is recommended, but I haven’t tried it myself. Windows users shouldn’t have too many problems with the default setup I believe.

Run your MIDI synth

Make sure your synth (see above) is running. FYI, I use timidity -iA -Os -f -A 210 --verbose=2 in a separate shell. Note what channel your MIDI synth now running on (mine is usually 4).

Make some noise!

If we load GHCi (with stack repl), we can play around live:

λ> import Euterpea
λ> devices

Input devices:
  InputDeviceID 1       Midi Through Port-0
  InputDeviceID 3       Scarlett 2i4 USB MIDI 1

Output devices:
  OutputDeviceID 0      Midi Through Port-0
  OutputDeviceID 2      Scarlett 2i4 USB MIDI 1
  OutputDeviceID 4      TiMidity port 0
  OutputDeviceID 5      TiMidity port 1
  OutputDeviceID 6      TiMidity port 2
  OutputDeviceID 7      TiMidity port 3

λ> channel = 4      -- Or whatever works for you
λ> playDev channel $ c 4 wn

Woah – some sound! More precisely, an acoustic grand piano playing middle C for a whole note (wn)… in 4/4 at 120bpm, assuming the usual defaults. Remember to choose the right output channel for your setup for that playDev.

Writing music

Composing parts

The two fundamental operations for composing (in the FP sense) sounds are

  • :=:, which plays them in parallel, i.e. together.
  • :+:, which plays them in sequence, i.e. one thing after another.

Wait… :=: means lots of notes played together? Even the non-musicians will recognise this one – that’s a chord. And yes, there’s a list helper function for that: chord, as lists are generally easier to type or manipulate. Try this in your REPL:

λ> cMinor = chord [c 3 qn, ef 3 qn, g 3 qn]
λ> cMinor' = c 3 qn :=: ef 3 qn :=: g 3 qn
λ> print cMinor
Prim (Note (1 % 4) (C,3)) :=: (Prim (Note (1 % 4) (Ef,3)) :=: (Prim (Note (1 % 4) (G,3)) :=: Prim (Rest (0 % 1))))
λ> playDev channel cMinor

Cool! So we can see the internal representation of this, noting the zero-length rest at the end, and how it mirrors list construction where x : [] == [x]. This is why cMinor ≠ cMinor' even though it probably should2.

Let’s try some more. The line function is the equivalent helper for :+: – it takes a list and composes the elements sequentially, like moving right in piece of notated music (or down in a tracker).

λ> playDev channel $ line [c 3 qn, e 3 qn, g 3 qn, bf 3 qn]

A nice C7 arpeggio – note that we’re using qn for quarter notes (aka a crotchet in music theory).

Infinite music

Just to spice things up a bit, what if we make… an infinite piece of music? I mean, Haskell is a non-strict (≅“lazy”) language, right? So we can use the line operator on infinite lists as well, and we’ll have some infinite Music a. Let’s use the standard cycle list function to repeat our arpeggio forever and see what we get.

For display we can avoid printing the infinite list by using Euterpea’s cut function, which limits a piece of Music to the specified number of beats.

λ> arpeggio = line $ cycle [c 3 qn, e 3 qn, g 3 qn, bf 3 qn]
λ> print (cut 2 arpeggio)
Prim (Note (1 % 4) (C,3)) :+: (Prim (Note (1 % 4) (E,3)) :+: (Prim (Note (1 % 4)
(G,3)) :+: (Prim (Note (1 % 4) (Bf,3)) :+: (Prim (Note (1 % 4) (C,3)) :+: (Prim (
Note (1 % 4) (E,3)) :+: (Prim (Note (1 % 4) (G,3)) :+: (Prim (Note (1 % 4) (Bf,3)
) :+: Prim (Rest (0 % 1)))))))))

If you squint a bit (or are used to LISPs…) you can see this is a series of Note Primitives, all of a 1/4 time, composed together with the :+: operator. Again, the empty element3 is visible here.

Westeros

Taking these basic operators, we can now easily create pieces of music. Now here’s one I made earlier (you’ll need to put this in your .hs source file, getting too big for a REPL session)

-- It's in 3-time (3/4 or 6/8)
-- so each bar lasts one dotted half note (@dhn@)
melody = line
         [ g  3 dhn,
           c  3  hn, rest  qn,
           ef 3  en, f  3  en, g  3  hn,
           c  3  hn, ef 3  en, f  3  en,

           d  3 (dwn + dhn), rest dhn,

           f  3 dhn,
           bf 2  hn, rest  qn,
           d  3  en, ef 3  en, f  3  hn,
           bf 2 dhn,

           ef 3  en, d  3  en, c  3  dwn,
           rest dhn
          ]

-- Convenient wrapper
inst :: InstrumentName -> Volume -> Music Pitch -> Music (Pitch, Volume)
inst i v = addVolume v . instrument i

-- Repeat each element four times
quadruplicate :: [a] -> [a]
quadruplicate = concatMap (replicate 4)

-- I always find this useful
bpm = tempo . (/ 120.0)
dragon

The layout for melody is slightly non-conventional, but I find spacing it like this, whilst not linear in time (like trackers), allows us to compare notes and phrases easier. The line spacing separates 4 bar sections (if you can read music, see this simplified score which helped with the above).

Try reloading your REPL and doing playDev channel $ bpm 170 . inst Cello 70 $ melody!

Note also because Dur is just a Rational (i.e. a Num), we can use normal arithmetic on note durations! So yes, (dwn + dhn) is a thing, and it’s, erm, 9 quarter notes (or 3 bars here) I reckon. This allows for some nice maths / shorthand in your music (and in fact pitches have similar opportunity).

Add some harmony

Remember, harmony is just two or more different notes playing at once, so we already have the tools to do that, Euterpea’s :=: operator. So we’ll just add a very simple sequence of bass notes changing every four bars as per the melody.

λ> chordSeq = [c 1, g 1, bf 1, f 1]         -- Just the notes
λ> contraBass = line $ map ($ 3) chordSeq   -- 3 is 4 whole bars in 3/4
λ> song = inst Cello 70 melody :=: inst TremoloStrings 70 contraBass
λ> playDev channel $ bpm 170 song

…and some rhythm

That bass is pretty boring though. Really, we want some percussion, but for a quick fix, let’s try adding a little rhythm to that bass. For each bar we can repeat the one note but in a particular rhythm.

λ> rhythmFor note = [note dqn, rest en, note en, note en]
λ> bass2 = line $ concatMap rhythmFor $ quadruplicate chordSeq
λ> song = inst Cello 60 melody :=: inst AcouticBass 80 bass2
λ> playDev channel $ bpm 170 song

Note we use concatMap to flatten, as each note in the chord sequence is mapped to a list of Music Pitch by our rhythmFor function.

Summing up

Hopefully lots to think about and play with there for newcomers. You should be able to see the quick turnaround that using a REPL for (simple) music can bring, just as programmers often enjoy for code, as well as the potential advantages for quickly building music pieces from smaller parts… just like with functional programming itself. I’ve definitely become very taken by the library even at this basic level.

Next up

Next post we’ll see investigate creating drum patterns, pushing the use of MIDI further and allowing DRY to help us create more realistic sounds from even a basic synth setup.

More Reading

Euterpea has also been used for music students (without necessarily any programming experience) as presented in this paper.


Footnotes

1 The name is from the Greek muse / goddess of music, apparently.
2 Hmm, bug report… or feature?
3 All sounds a bit monoidal to me, but I’m sure someone better at Haskell can tell me otherwise.