Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Music primitives

notebook/music_prims.livemd

Music primitives

Definitions

Scales

This is a demonstration of the functions from MusicPrims. First thing we’ll do is import it.

import MusicPrims

When we compute a major scale we get a list of notes. Each note is a tuple of {key, octave}. Since we didn’t specify an octave, we get octave 0 which is a pretty low C.

major_scale(:C)

Let’s do the same thing with a different octave.

major_scale(:C, 4)

Middle C on a piano is {:C, 4}. This is denoted as midi note 60.

{:C, 4} |> to_midi

The key of F major has one flat.

major_scale(:F)

The key of G majore has one sharp

major_scale(:G)

We can determine which key we’re playing from the number of sharps or flats, depending on the scale type.

key(:major, 0, :sharps)
key(:minor, 0, :flats)

For any note, we might want to know the fifth above.

next_fifth({:C, 1})
next_fifth({:G, 1})

We also might want to know the next fourth, which for G is C

next_fourth({:G, 0})

Thus, the next fourth of the next fifth brings us back to the starting point one octave higher.

next_fifth({:C, 1}) |> next_fourth

To compute the circle of fifths, we iterate on the next_fifth:

Stream.iterate({:C, 1}, &next_fifth(&1)) |> Enum.take(12)
chromatic_scale({:C, 0}) |> Enum.take(4)

Chords

Chord follow a similar computational pattern as scales

major_chord(:F)

The same chord with the first inversion.

major_chord(:F) |> first_inversion
major_chord(:F) |> second_inversion
major_seventh_chord(:F)

Midi

We’ve already seen to_midi for a single note. It’s also defined for note sequences like chords and scales.

major_seventh_chord(:F) |> to_midi

And for chord sequences

Chord patterns

import ChordPrims
chord_syms_to_chords([:I, :IV, :vi, :V], {{:G, 0}, :major})
Enum.map(chord_syms_to_chords([:I, :IV, :vi, :V], {{:G, 0}, :major}), &chord_to_notes(&1))
chord_syms_to_midi([:I, :IV, :vi, :V], {{:G, 0}, :major})

You never give me your money

yngmym = [:i7, :iv7, :VII7, :III, :VImaj7, :ii7, :V7, :i]
chord_syms_to_chords(yngmym, {{:A, 2}, :minor})
chord_syms_to_midi(yngmym, {{:A, 0}, :minor})

Now for something kind of fun. We define the major diatonic progression as:

major_diatonic_progression()

and the minor diatonic progression as:

minor_diatonic_progression()

The diatonic progressions are basically chord scales.

Using the table of usual progressions which we’ve gotten from Walter Piston’s book Harmony (https://www.amazon.com/Harmony-Walter-Piston/dp/B0041OKJDO)

table_of_usual_progressions()

where the keys are the current chord and the values are a list of chords that flow from that chord in normal order. We’ve defined the likelyhood of hitting one or the order with the following list of odds:

usual_odds()

Why have we chosen to represent the usual progressions as numbers instead of symbols? Because it works for any progression of chords scales of which we’ve so far defined two: major diatonic and minor diatonic.

The sum total of the odds is:

usual_odds() |> Enum.sum()

Thus, if your root chord is 1, the odds of the next chord being 4 are 10/26 where the odds of it being 3 are just 1/26.

Using these progressions and odds we can define a random, but well formed, chord pattern generator:

# jere we are going to get a 4 chord progression starting at root chord 1
random_progression(4, 1)

Taking that a step further, let’s define a progression of random length that ends when it comes back to the root which we will convert to an actual set of chord symbols based on a given chord scale. This will give different results each time it is evaluated.

chord_progression = random_progression_to_root(1, major_diatonic_progression())

Let’s reify that to an actual chord progression:

chord_progression |> chord_syms_to_chords({{:G, 0}, :major})

It’s an automatic song writer - well, chords at least, you’ll still have to write the lyrics :)

I’ve played a bunch of these patterns on my guitar and they generally sound pretty good. Play around with strum and durations. Truthfully, several of them reminded me of Radiohead songs - I’d heard that Jonny Greenwood was into programming so maybe this is how they do it :)

Looking at these progressions makes me wonder about the distribution of the length. In other words, each progression is built by walking a directed, but random walk to the root chord. How many chords do we get?

f = &random_progression_to_root/0
freqs = Stream.repeatedly(f) |> Enum.take(100_000) |> Enum.map(&length(&1)) |> Enum.frequencies()
freqs |> Map.keys() |> Enum.sort() |> Enum.map(&{&1, freqs[&1] / 1000})

So, 26 percent of the progressions have two chords, 17 percent have three and so on. This strikes me as a reasonable estimate of the songs in popular music. at least in the Anglo-Saxon world, if not beyond that.