A computer programmer’s approach to learning music

Learning an instrument is hard. For example, to master the flute (which I’ve been learning for a couple of years and am way off mastering) you need to perfect: melody, rhythm, tone, dynamics (quiet and loud), articulation (slurred, staccato), not to mention scales, arpegios, …

Personally, I find one of the hardest parts is understanding the rhythms from sheet music notation. Especially how to create the right feel to a piece. As a computer programmer I’m wondering whether I can explore some of these challeges through a music programming language.

There are already some great music programming languages out there including Sonic Pi and Overtone however I’m wondering whether I can create a more direct syntax for expressing musical ideas…

What about creating a Domain Specific Language?

So here’s some questions we are going to explore:

  • how a programming language can be used to describe rhythms
  • whether a Domain Specific Language can express music concisely
  • whether I can build some useful tools to explore the feel of music.

What is a DSL?

Before we get to some rhythm programming, let’s define what we mean by a DSL.

A Domain Specific Language (DSL) is a programming language made to fit a problem domain so that you, the programmer, can express your ideas clearly without the usual cruft associated with regular languages.

Here’s an example from Racket Stamps a language for generating art:

(define-shape scene
  (circle [hue 120]
          [sat 1]
             [brightness 1])
     (triangle [hue 200]
               [sat 1]
               [brightness 0.8])
     (scene [scale 0.8]
            [rotate 15]))

Even though you probably don’t know this language you can see that we are defining a shape, which contains a cicle and a triangle. The clever bit is that this repeats recursively with successive circles and triangles scaled by 80% and rotated by 15 degrees.

A language for music programming

Let’s start by looking at what’s possible with Sonic Pi. Here’s how we could program a melody and rhythm:

use_synth :piano
use_bpm 120

4.times do
  play :c5
  sleep 0.5
  play :e5
  sleep 0.5
  play :g5
  sleep 1
  play :g5
  sleep 1
  play :g5
  sleep 1
end

This can be simplified using play_pattern_timed to:

4.times do
  play_pattern_timed [:c5, :e5, :g5, :g5, :g5 ], [0.5, 0.5, 1, 1, 1]
end

If we wanted to add a baseline we can use two live_loops:

use_synth :piano
use_bpm 120

live_loop :melody do
  4.times do
    play_pattern_timed [:c5, :e5, :g5, :g5, :g5 ], [0.5, 0.5, 1, 1, 1]
  end
end

live_loop :bass do
  4.times do
    play_pattern_timed [:c2, :g2 ], [2]
  end
end

That’s quite a bit of typing for a simple musical idea.

How about with drum patterns? As far I’m aware we have to use the long form as there is no equivalent to play_pattern_timed for samples:

use_bpm 120

4.times do
  sample :drum_bass_soft
  sleep 0.5
  sample :drum_cymbal_soft
  sleep 0.5

  sample :drum_bass_soft
  sleep 1
  sample :drum_bass_soft
  sleep 1
  sample :drum_bass_soft
  sleep 1
end

As with the melody and bass line this is a lot of typing for a simple musical idea. Let’s try and do better by creating a DSL for music programming.

Some ideas for our DSL

Let’s organise our rhythms into beats and bars, e.g. we might have 4 beats in a bar and a kick on the odd beats and a snare on the evens like this:

>       Beat: 1    2     3    4
> Instrument: kick snare kick snare

Or we might have an alternating kick and high hat:

>       Beat: 1    &   2    &   3    &   4    &
>       Hats:      hat      hat      hat      hat
>      Kicks: kick     kick     kick     kick

This seems a layout worth exploring, so how do we convert it into a programming language? And which one?

Some languages are more suited to creating DSLs than others and Lisp and Scheme dialects have a long history in this field because of their highly regular syntax and macro capabilities. Macros enable us to extend the language, redefining or adding our own programming structures.

So which Lisp? Well Overtone is writtin in Clojure, so let’s try using this as a starting point and see how it works out.

Also, crucially we want to use the music generation capabilities of something like Overtone so that we can focus on the DSL and not on the mechanics of playng samples.

Introducing Overtone

Overtone describes itself as “an open source audio environment designed to explore new musical ideas from synthesis and sampling to instrument building, live-coding and collaborative jamming.”

You can find out more and install it yourself by visiting the Overtone site.

Here’s the standard way to program a rhythm in Overtone:

(ns overtone-loops.examples.old-style-loop
"Using regular Overtone `at` notation to make loops"
(:use [overtone.live]))

;; Define some samples from Freesound.org
(def kick (freesound 250547))
(def snare (freesound 270156))
(def hat (freesound 96140))

;;
(def metro (metronome 128))

;;
(defn player [beat]
  (at (metro beat) (kick))
  (at (metro (+ 0.5 beat)) (hat))

  (at (metro (+ 1 beat)) (kick))
  (at (metro (+ 1.5 beat)) (hat))

  (at (metro (+ 2 beat)) (snare))
  (at (metro (+ 2.5 beat)) (hat))

  (at (metro (+ 3.5 beat)) (hat))

  (apply-by (metro (+ 4 beat)) #'player (+ 4 beat) []))

(metro-bpm metro 120)
(player (metro))

That’s even more typing than Sonic Pi, let’s see if we can do better than that…

A better loop syntax

Let’s work out a better version of the player function first. Using our earlier ideas about bars and beats we could aim for something like this…

;;                           1 & 2 & 3 & 4 &
(defloop kicks  1/2 kick    [8 _ 7 _ _ _ _ _])
(defloop snares 1/2 snare   [_ _ _ _ 8 _ _ _])
(defloop hats   1/2 hat     [_ 5 _ 5 _ 5 _ 5])

Here we’re making 3 loops, each in half beats, with a specified instrument, then we supply the amplitude (volume) of each half beat.

Now all we need to do is schedule them, for now we can stick with the usual Overtone way of doing it and pass each loop a metro counter:

(kicks (metro))
(snares (metro))
(hats (metro))

This is a much conciser syntax, we can clearly see what’s playing, when it’s playing, and how the instruments fit together to make the rhythm.

So how do we code the DSL?

In basic terms we need to get from (defloop ... to something like the earlier player function using the at function to schedule each beat.

We’ll explore this in the next blog post… (coming soon).

You can see the complete working program here on GitHub.

Further reading

https://wiki.c2.com/?StumblingBlocksForDomainSpecificLanguages


Questions, comments, feedback? Email me: eric@bn7.net