The goal of this package is to provide a Julian interface for representing the objects and structures in ("Western") music theory (based on semitones).
- Pitches with scientific notation, e.g. C4 for middle C
- Intervals
- Scales
- Notes and rests with durations
- Chords
- Triads
Pitch names are exported in the MusicTheory.PitchNames
submodule.
Specifying just the name of a pitch gives a PitchClass
, representing all notes of that
pitch, e.g.
julia> C♯
C♯
julia> typeof(C♯)
PitchClass
Indexing gives a pitch with a specific octave, e.g.
julia> C♯[4]
C♯₄
The Interval
type computes the interval between two pitches:
julia> Interval(C[4], E[4])
Major 3rd
julia> C[4] + Interval(3, Major)
E₄
General scales are supported; they are specified as a sequence of intervals dividing up an octave, e.g.
julia> show(major_scale)
Interval[Major 2nd, Major 2nd, Minor 2nd, Major 2nd, Major 2nd, Major 2nd, Minor 2nd]
The Scale
type is a standard Julia iterator over the scale:
julia> scale = Scale(C[4], major_scale)
Scale{Pitch}(C₄, Dict{PitchClass, Interval}(C => Major 2nd, E => Minor 2nd, B => Minor 2nd, F => Major 2nd, D => Major 2nd, G => Major 2nd, A => Major 2nd))
julia> scale_tones = Base.Iterators.take(scale, 8) |> collect;
julia> show(scale_tones)
Pitch[C₄, D₄, E₄, F₄, G₄, A₄, B₄, C₅]
Notes have a pitch and a duration, which is a rational number, e.g. 1 // 4
for a
quarter note (crotchet). Rests are specified using rest
, e.g.
julia> notes = [C[5] / 4, rest / 8, D[5] / 8]
3-element Vector{Note}:
Note(C₅, 1//4)
Note(MusicTheory.Rest(), 1//8)
Note(D₅, 1//8)
A motivating example for writing this package was to be able to do the following types of computations.
When playing the violin, it's common to have a scale in thirds: take a scale and for each scale tone, play the note two steps above it in the scale at the same time.
Question: Which combinations of half/whole steps and pairs of major/minor thirds are possible?
Answer:
julia> scale = Scale(C[4], major_scale)
julia> notes = Base.Iterators.take(scale, 10) |> collect
julia> thirds = zip(notes, notes[3:end]) |> collect
julia> thirds_intervals = Interval.(thirds)
julia> note_intervals = Interval.(zip(notes, notes[2:end]))
julia> combinations =
[ (note_intervals[i], thirds_intervals[i], thirds_intervals[i+1])
for i in 1:(length(thirds)-1)
]
julia> result = unique(combinations)
4-element Vector{Tuple{Interval, Interval, Interval}}:
(Major 2nd, Major 3rd, Minor 3rd)
(Major 2nd, Minor 3rd, Minor 3rd)
(Minor 2nd, Minor 3rd, Major 3rd)
(Major 2nd, Major 3rd, Major 3rd)
- Play the notes
- Print the notes in standard notation, e.g. via an interface to Lilypond
- Interfaces with other packages
- music21 (Python)
- abjad (Python)
- Haskore (Haskell)
- Euterpea (Haskell)
- Supercollider
- List of music packages on GitHub: https://github.com/topics/music-theory
I tried not to look at other packages, either in Julia or in other languages, while writing this. Apologies for any duplication.
Copyright David P. Sanders, 2024