JuliaMusic / MusicTheory.jl

Music theory in Julia
MIT License
46 stars 1 forks source link
music-theory-julia

MusicTheory.jl

Build Status

The goal of this package is to provide a Julian interface for representing the objects and structures in ("Western") music theory (based on semitones).

Contents of the package

Pitches

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♯₄

Intervals

The Interval type computes the interval between two pitches:

julia> Interval(C[4], E[4])
Major 3rd

julia> C[4] + Interval(3, Major)
E₄

Scales

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

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)

Example

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)

TODO

Related packages

Author

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