[ANN] Announcing MusicTheory.jl

MusicTheory.jl

I’m happy to announce the new package MusicTheory.jl.
The goal of the package is to provide a foundation for representing musical objects in a Julian way.

The main challenge has been to find the right abstraction for each type of object.
[I deliberately have not looked in detail at other packages.]
Currently the package is based on the structures of “Western” music, and in particular on semitones (rather than smaller subdivisions).

Contributions in the form of Pull Requests are very welcome!

Outline

The package is based around Pitches, for example C♯[4], and Intervals;
Everything else is constructed from these.

E.g. a scale is a way of dividing up an octave into a sequence of intervals; this sequence then repeats each octave. A natural way to represent this in Julia is thus a vector of Intervals, representing the scale steps, wrapped into an infinite iterator. [The naturalness of this representation took a while to dawn!]

Features

  • Pitches using scientific notation, e.g. C4 for middle C
  • Intervals
  • Arbitrary scales (modes)
  • Notes and rests with durations
  • Motifs using scale tones, such as arpeggios

Partially implemented

  • Chords
  • Triads

Desired features (not yet implemented)

  • Iterate scales in a descending direction
  • Ability to play notes
  • Export to Lilypond, Midi, MusicXML
  • Interaction with related Julia packages from the JuliaMusic org

Basic usage (see also the README)

julia> using MusicTheory, MusicTheory.PitchNames

julia> C♯
C♯

julia> typeof(C♯)
PitchClass

julia> C♯[4]
C♯₄

julia> typeof(C♯[4])
Pitch

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

Example of using scales

julia> show(major_scale) 
Interval[Major 2nd, Major 2nd, Minor 2nd, Major 2nd, Major 2nd, Major 2nd, Minor 2nd]

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₅]
55 Likes

Awesome! Do you have an idea in mind how how people might utilize the package?

1 Like

Thanks – no, not really! There’s an example in the README of how I have used it to do a “computation” to do with a scale in thirds.

You can also just use it as a way to input a piece of music – although currently there’s no way to output it… :wink:

2 Likes

One thing I had in mind was for e.g. ear training and generating random intervals to recognise / practise. @Datseris has done similar things in packages in the JuliaMusic org.

E.g. it would be relatively simple to write a function random_interval that generates a random interval and displays it, e.g. using Makie.jl, and asks you what type of interval it is. (I have some draft code for using Makie.jl that is not in the repo. Maybe I should put it there.)

1 Like

Radical. With some music theory knowledge and a bit more work on the package, one could create study flashcards for tools like Anki to drill you on practicing novel practice pieces. Like: Play this small variation at X bpm in X/X time signature.

Fun stuff @dpsanders! Love seeing stuff like this emerge!

4 Likes

Are microtonal intervals or non-Western scales planned?

4 Likes

Is there a comparable package in another language that you drew inspiration from? Have you looked at e.g. Haskore or some of the many github music-theory packages?

In a vacuum, it’s not obvious to me what the essential features of a music-theory package should be, and what data structures are best. e.g. why is a scale an iterator and not an indexable object? Should there be a transpose function? Chords? Time signatures?

Is the intended use case electronic composition? Analyzing music? Basic pedagogy?

2 Likes

Do you In the background simply use the frequency of each note to make the computations ? E.g. you may identify a minor third by the fact that the frequency ratio is 2^{3/12}

Interesting …! I have always thought building out a library of data structures to represent the basic building blocks of tonal theory would be a cool way to exercise a programming language’s type system, with chords and harmonic progression as a stretch goal.

Sort of like the famous Cards.jl, this project shows the expressiveness of the type system even if it doesn’t have an immediate use case in mind.

Looks like you are encoding each scale as dictionary mapping each PitchClass in the scale to the Interval that points to the PitchClass that follows it ascending order in the scale. May I ask about the motivation for this? Why not a vector (or even SVector{PitchClass, 7}) just giving the pitches in ascending order?

1 Like

Fantastic, thanks for sharing! Would be nice if we can get conversions between a MIDI.Note from MIDI.jl to a MusicTheory.Pitch and vice versa. Would allow even more advanced music data analysis in terms of music theory this way!

3 Likes

I can talk for MusicManipulations.jl which has transposition and some other basic “music” operations: its usage is all three of these aspects: I’ve used it to generate music, to generate educational material for learning music (e.g., generate practice drills) and I’ve used it extensively to analyze real music data recorded in the form of MIDI.jl. As far as I can tell, MusicTheory.jl can couple to all of these aspects and allow generating music drills and also analyzing real music in terms of music theory aspects and compositional aspects!

3 Likes

I deliberately chose not to look at other packages, to avoid being influenced by their choices. Now would be a good time to do so, though. I’ll add a list of relevant packages to the README. Thanks for the references; I didn’t know about these.
There’s abjad and music21 in Python that I am aware of.

Unfortunately my personal knowledge about both of these is ≤ 0, so I won’t be the one to implement them. But these would be great to have if we can work out the correct data structures, yes. PRs are welcome!

Agreed, the data structures are non-obvious.
It’s a great point that scales should be indexable; I’ve added an issue for that.

For transpose, one thing I forgot to mention is that the package is built around adding intervals, e.g.

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

so that makes transpose very easy to implement :slight_smile:

Chords are already “implemented”, and I’ve made a start on triads. Please see the (minimal) tests and the code; help is always welcome with the docs, if anybody is good at this, of course! I haven’t been following developments regarding Documenter.jl or whatever the current facilities are so help with that would be great.

It’s intended to be a common language for all things music in Julia. Maybe some of it should be split out into MusicBase.jl or similar.

1 Like

No, actually there’s no mention of frequencies in the package (yet).

Fortunately or unfortunately, music notation is not a direct mapping from maths. In particular, you cannot specify pitches uniquely using a single number like frequency, since e.g. D♭ and C♯ return to the “same note” – at least in equal temperament.
So each pitch needs at least two independent labels. In the package I have chosen a semitone label (numbered up from C0) and a tone label (not a particularly good name), which is the “number of pitch classes from C0”. E.g. the tone for C♭ , C♮ and C♯ are the same number.

Currently both the semitone and tone labels are just Ints, but they should probably be dedicated Semitone and PitchClassNumber (or something) structs.

This is actually one of the trickiest points with representing music, as far as I can see!

This has its pros and cons, but it loses the second-mover advantage of being able to distill the best features of other people’s work and avoid repeating mistakes…

3 Likes

I’m using a dictionary so that it’s easier to look up “which note in the scale is the next one after E”. And I’m using intervals to allow iterating to the next note by adding the corresponding interval, so that I can keep going arbitrarily far in the same direction, e.g.

julia> notes = Base.Iterators.take(scale, 15) |> collect; show(notes)
Pitch[C₄, D₄, E₄, F₄, G₄, A₄, B₄, C₅, D₅, E₅, F₅, G₅, A₅, B₅, C₆]

But @stevengj’s suggestion to implement indexing might be a more natural solution for this, in which case we would probably want a scale_tones field in the Scale struct that contains the pitches in the scale as you suggest!

1 Like

Haskell School of Music is a textbook about Euterpea and working with music in Haskell. It was originally started by Paul Hudak and was later finished by Donya Quick.

https://www.euterpea.com/haskell-school-of-music/

2 Likes

Thanks George! Yes we definitely need some good inter-operability with your great packages :slight_smile:

1 Like

You never stop impressing me.

3 Likes

Two options: either i have to disagree with you either i am not undertanding what is a pitch. For me, a pitch is a frequency : the equal temperament western music uses nowadays is just a “set of common frequencies” that we happen to use to make music and harmonies, but there are a lot of places in the world where other standards are used. On the other hand, intervals are ratio of pitches.

you cannot specify pitches uniquely using a single number like frequency, since e.g. D♭ and C♯ return to the “same note”

for me same note = same pitch = same frequency.

So each pitch needs at least two independent labels

Or an infinite number of them if you start considering C♯♯ as different from D♮.

music notation is not a direct mapping from maths

I guess the mapping from music notations to math is not a bijection, but it is still surjective. You can then exploit this property to encode stuff as math: computing an interval between two notes is a simple division of frequencies. For example, would you return a different interval from A to D♭ and from A to C♯ ?

2 Likes

In your vocabulary do you have any term for the type of objects where D♭ !== C♯ in which they are not the same?

1 Like