python example 1
Chords generated using the Python script (without width-limiting compensation to the PR) showing highly parsimonious changes.

So, the other night I was trying to describe some of my recent experiments with algorithmic music composition to a friend over a beer, and I was doing a really poor job describing all of this, and then it got late, and I had to run to catch my bus… I started writing it all down as an email, but then I though I’d better just dump it all here in case anyone out there is interested. My guess is that there are a few other composers who might be interested in this, particularly in my use of the Python programing language to generate musically significant results. I’d really appreciate technical suggestions as well.

In case you’re wondering where my blog went these last few months, yeah, I was in Iceland a while back, and I have yet to post anything about that, not to mention that I still have to post a summary of all the field recording I did last year! I’m really behind, but whatever. Sorry, I’d rather be composing! It will all be posted here eventually though.

python example 1
The same chords generated using the Python script in XY-grid representation. (Graphic generated with ACToolbox.)

A little background information: I’ve been working a lot with string instrument harmonics lately, specifically very large masses of very sustained string harmonics. I’m interested in the intrinsically untempered tuning that results from this. Since the harmonic palette is limited by the harmonics that are actually playable on string instruments, I see algorithmic composition as an appropriate approach to generating desirable chord sequences within these constraints.

I’m currently working with the set of all harmonics up to the 9th partial in the string section of the European orchestra. Represented with MIDI note numbers (a standard system of representing pitches as numbers), this set would be:

[28, 33, 36, 38, 40, 43, 45, 47, 48, 50, 52, 55, 55.86, 57, 59, 60, 60.86, 61.69, 62, 63.86, 64, 65.86, 66, 66.69, 67, 69, 69.69, 70.86, 71, 71.69, 72, 74, 75.86, 76, 76.69, 77.86, 79, 81, 81.69, 82.86, 83.69, 84, 84.86, 86, 88, 88.69, 89.86, 90.69, 91, 93, 95, 95.69, 96.86, 98, 100]

(This set excludes certain very high partials (on the violin e-string) that are practically impossible to actually produce. Decimals represent the partials that naturally differ in tuning from the tempered scale, and pitches that differ by 2 cents or less are counted as the same pitch.)

I’m interested in algorithmically producing chains of subsets of the parent set (chords) that are linked through highly parsimonious changes. (In music theory “parsimony” refers to chords whose members move very little from one chord to the next.) Here’s my solution if you’re interested in downloading it. Please forgive my amateurish Python code (or better yet send me your suggestions please!):

Download Python file (Dropbox link)

The Python script takes an initial subset as an input and compares it to all equal-length combinations of the members of the parent set. The absolute-value difference between the given set and the equivalent member of the compared set is found, and these differences are summed to create what I’m calling the “parsimony ranking” (PR) of that chord movement. (The PR is a completely arbitrary number that serves to rank all possible combinations of the parent set according to their distance from the starting set.)

For example if the initial set is [a, b, c, d] and the combination set is [e, f, g, h], the PR would be found via

[( | a -/+ e | ) + ( | b -/+ f | ) + ( | c –/+ g | ) + ( | d -/+ h | )].

If the initial set is [50, 55, 60, 64] and the combination set is [50, 52, 60, 65.86], the parsimony ranking (at least in this initial statement of the problem) would be 4.86 (since ( | 55 – 52 | ) + ( | 64 – 65.86 | ) = 4.86 and the two values are shared by the sets.

A problem occurs when the starting set and the derived set share several members (or are otherwise highly parsimonious) but have a large distance between one or a few members. For instance the PR of [50, 55, 60, 64] and [50, 55, 60, 69] is only 5, but the chords are actually not very musically parsimonious since there is a single very large leap between 64 and 69 that draws the listener’s attention. I’ve solved this problem by raising the difference between the elements of the two sets to a power. This greatly increases the PR in the case of large distances and thereby “punishes” chord movement containing large leaps (large distances between corresponding elements of the sets).

For instance finding the PR of [50, 55, 60, 64] and [50, 55, 60, 69] would involve an additional step of raising 5 (the difference between the corresponding elements of the sets) to the power of 1.2, for instance, yielding a PR of 7 (rounded) and favoring other, more parsimonious sets.

This approach yields good results, but there is one other problem: The subsets have the tendency to gradually spread out towards the high and low extremes of the parent set creating very “wide” musical chords, not necessarily the most attractive musical results! I’m working on a further modification to the PR that will favor sets with a narrow distance between high and low elements in comparison to the width of the starting set.

The python program works by simply generating a list of sets whose PR is below a specified threshold. One set is then selected from this list using a probability distribution that favors positive numbers close to zero. (An exponential distribution might work well, although the example code uses a triangular distribution with a low mode value.) Once a set is selected from the available options, this set becomes the starting set and the process for deriving the next chord will start all over again. The process repeats until a specified number of chords are generated.

I’m very pleased with the musicality of these results already, but I face several key challenges: Computing power is a limitation on the number of combinations that can easily be searched. I’m already pushing that limit with six-note chords. I also struggle with a very basic computer science and mathematics skill set. Until I gain more experience in these fields, I will have to continue to rely on the generous advice and support of my more numerate friends. Please help improve my code and or concept. Send me your thoughts. Thanks!

Leave a comment