Building a Music Theory API with Types

Note Type

type NoteName
= C
| D
| E
| F
| G
| A
| B

Render Notes

renderNotes : List NoteName -> Html msg
renderNotes notes =
svg [...] [...]

Note Flashcards

The Immortal Game
Blank Chess Board
Chess Figure 1

Frey, P.W. & Adesman, P. Memory & Cognition (1976) 4: 541. https://doi.org/10.3758/BF03213216

Intervals

noteNameToInt : NoteName -> Int
noteNameToInt note =
case note of
C -> 0
D -> 1
E -> 2
F -> 3
G -> 4
A -> 5
B -> 6

Compute Distance

computeDistance : NoteName -> NoteName -> Int
computeDistance note1 note2 =
let
n1 = noteNameToInt note1
n2 = noteNameToInt note2
in
abs <| n1 - n2

What about the octave?

Note Record

type alias Note =
{ name : NoteName
, octave : Int
}

New Distance Function

computeDistance : NoteName -> NoteName -> Int
computeDistance : Note -> Note -> Int
computeDistance note1 note2 =
let
n1 = noteNameToInt note1
n2 = noteNameToInt note2
n1 = noteNameToInt note1.name + (note1.octave * 7)
n2 = noteNameToInt note2.name + (note2.octave * 7)
in
abs <| n1 - n2

Generate an interval

getBasicInterval : Note -> Int -> Note
getBasicInterval note interval =
...
middleC =
{ name : C
, octave : 4
}
interval = 3
getBasicInterval middleC interval == Note E 4

Interval Flashcards

Black keys?

Accidental

type Accidental
= Sharp
| None
| Flat
...

New Note Record

type alias Note =
{ name : NoteName
, accidental : Accidental
, octave : Int
}

Interval Quality

Major and minor

type Quality
= Minor
| Major
...
noteNameToHalfStep : NoteName -> Int
noteNameToHalfStep note =
case note of
C -> 0
D -> 2
E -> 4
F -> 5
G -> 7
A -> 9
B -> 11
accidentalToHalfStep : Accidental -> Int
accidentalToHalfStep accidental =
case accidental of
Sharp -> 1

None -> 0

Flat -> -1

...
noteToHalfStep : Note -> Int
noteToHalfStep note =
noteNameToHalfStep note.name +
accidentalToHalfStep note.accidental +
note.octave * 12
computeInterval : Note -> Note -> (Quality, Int)
computeInterval note1 note2 =
let
halfSteps =
abs <| noteToHalfStep note1 - noteToHalfStep note2

letterDistance =
computeDistance note1 note2
in
    case letterDistance + 1 of
3 ->
case halfSteps of
3 -> (Minor, letterDistance + 1)
4 -> (Major, letterDistance + 1)
...

What about a 10th?

Mod Arithmetic

    case letterDistance + 1 of
case (modBy 7 letterDistance) + 1 of
3 ->
case halfSteps of
case modBy 12 halfSteps of
3 -> (Minor, letterDistance + 1)
4 -> (Major, letterDistance + 1)

Generate Full Interval

getInterval : Note -> (Quality, Int) -> Note
getInterval note (quality, interval) =
...
middleC =
{ name : C
, accidental : None
, octave : 4
}
interval = (Minor, 3)
getInterval middleC interval == Note E Flat 4

Interval Flashcards

Triads

getTriad : Note -> Quality -> List Note
getTriad root quality =
case quality of
Major ->
[ root
, getInterval root Major 3
, getInterval root Perfect 5
]
...

Triad Flashcards

What’s next?

  • Harmonic Analysis (music)
  • Music Generation
  • Typed Theory Backend (Rust or Haskell)

app.knowyourtheory.com

@pianomanfrazier