11
Sep
Ah Um: mingus 0.3
Version 0.3 of mingus, the music theory package for Python I started working on a while back has been released. This new release contains a couple of new features since the last time I posted something:

  • The structures for notes (bars, tracks, compositions, etc.) are ready and allow you to express a truck load of musical concepts from within Python.
  • A lot of time went into building and refining chord, scale, progression and interval recognition (more about this later), but it all works now.
  • A LilyPond generator to create sheet music (as suggested by rahul) has been added to the extra sub-package.
  • Up to date, complete documentation and tutorials in the wiki. (Good documentation is crucial for packages like this, so don't hesitate to be a pedant in the wiki comment section.)

A complete, up to date feature list and download can be found at mingus' google code page.

Since this library is mostly aimed at developers, I will now take some time to discuss some of the things I ran into while working on this release, that you may find interesting.

Searching Problems

Interval Recognition

This new release contains a couple of functions that let you analyze groups of notes. This begun relatively simple, with a function that could recognize intervals between notes. For instance, intervals.determine('E', 'F') would return 'minor second', and intervals.determine('E', 'Ebb') would return 'diminished unison'.

I started out naively; treating this as a counting problem. To determine the interval between E and F I simply counted the steps between them (1) and used that number as an index in a simple ordered list to get the proper naming. This works for the E and F example, but fails horribly in the E-Ebb case (which would probably return a minor seventh, instead of a diminished unison). Music notation and theory is pretty elegant in itself, but it doesn't really play well with the list processing stuff I usually pull to get stuff done. So I added some unit tests, knocked myself over the head a couple of times and eventually whipped out the cycle of fifths again to fix that function.

The trick is to count the steps of fifths in the note names and compare this to the measured half note steps. For example: the steps of fifths in the interval F-C# is 1 (F-C), however, there are 8 half note steps in the interval F-C#. 8 is bigger then 7 (which is the expected number of half note steps in a fifth), so the interval must be augmented and the function can happily return an augmented fifth. This method still chokes on unisons though (eg. E-Ebb: 0 fifth steps, 10 half note steps...augmented unison?? No.), so I added them as a little side case. The actual function is a little bit more complicated, but this is the gist of it.

Once the interval recognition was working as it should, I could build another underestimated part of the library on top of it:

The Chord Recognizer

The chord recognizer would have to be able to recognize all kinds of triads (chords of three notes), sevenths (a triad + a seventh), extended chords (a seventh chord + a ninth, eleventh or thirteenth), altered chords (every chord mentioned before, but in a slightly changed version (flat five, flat ninth, etc)), and the very lovable polychords (chords with other chords above them; eg. Gm|C is a C major triad with a G minor triad above it). Already this has become a huge task. The task becomes even bigger when you also have to recognize every inverted chord (eg. 'C', 'E', 'A' is still an A minor triad) and when you realize that a chord made of notes 'A', 'C' and 'G' is also a valid seventh chord, even when the fifth ('E') is missing.

So yeah; the chord module turned out to be a lot of work and lines of code, but in the current release it does all the things mentioned above and can recognize almost every chord in the universe (missing chords should immediately be reported). It can probably do with a bit of refactoring and memoization (planned for the next release), but it's pretty fast already - all unit tests run within a second on my machine.

The way it works is a bit hard to explain, but I'll give it a generalized shot. The chords.determine function first measures the length of the note list and delegates the request to the right function for the job. Eg. a call to chords.determine with a list of three notes will be redirected to chords.determine_triad. Then the function looks at the intervals and figures out if it's a chord; every hit will be added to a list, other things will be ignored. This will find a lot of chords already, but can't really find inversions by itself (you would have to manually enter every possible inversion; an error prone process). That's why the function gets recursively called again, inverting the list of notes each time until it's at its last inversion. The function keeps track of the number of inversions performed so far and adds the results accordingly.

Finding polychords is separated from this process and the function responsible for this will just be called before returning the results, because once you can determine chords of size 3-7, it turns out you can easily find chords made of a combination of them. The actual determine_polychord function is only about 15 lines of actual code and a nice example of the aforementioned list processing stuff.

Scales and Progressions

mingus is also equiped with a scale and progression recognizer, which are a lot cleaner and easier to grasp from reading the source code. The scale.determine function basically starts out with all the good answers in a list and then removes (sieves out) all the wrong ones. I thought it was an interesting approach to pattern matching and it works much faster than 'brute forcing' it with endless `if` statements.

The scales.determine function will be altered however, because it's quite useless for automatic recognition in ie. a single bar, since it needs all the notes in the scale to find the scale. The plan is to alter the behaviour so that it will return all the scales that you could possibly play within, using the notes passed as an argument. ie. ['A', 'C', 'E'] could be played in one of the minor scales, or in a plain diatonic C, etc. The more notes are given, the more precise you can determine the scale (your ear does the same thing, so I like this).

Contributing to mingus

There are a couple of things that would be very nice to have: alsa, csound and midi support (in extra) and more analytic tools for containers are just a few of them. I can imagine people having their own ideas of cool extras or other things that would be nice to have and that's why I'm trying to open up the development process.

I have transferred all my stuff from my own repository into google's and also added my bugzilla entries into their issue tracker, so the development can take place over there. Building and distributing a new version and its reference documentation is 98% automated as well, so almost everything is in place.

The only thing that's missing now is a proper way to communicate about the project using a mailing list, google group or forum, but that will be coming soon (I'm open for suggestions). I will also add some new pages to the wiki aimed at people who may want to contribute to mingus.

mingus' downloads, docs and tutorials are here



No Comments

Be the first to leave a comment for this article ;)


Leave a comment
Name*
E-mail
Website
Title*
Comment*
Notify me when somebody else comments on this article