Building a Harmonizer for SuperCollider: Chopin's Prelude in E minor experiment

Hello everyone. I’m in the process of creating a harmonizer extension based of MusicEngine, and I’d like to share a simple experiment I made to see how MEChord handles very long progressions when set into a loop.

The MEChord class is one of the tools I’m working on now. It creates all valid chord realizations given a symbol and a set or rules. Additionally the class may also take, as argument, a previous chord, a rule profile and number of voices:

MEChord.new(symbol, prevChord, ruleProf, voiceNum)

To test a very long progression I used an analysis I made of Chopin’s Etude Op.10, Nº1, that had all the chord symbols written down. A total of 92 chords across the entire etude:

// Chords
~p = [
	"C", "F", "F#-7o5", "D7", "G", "D7", "Dm3P4d5m7", "G7", "GM3A5m7",               // S1
	"C", "F", "F#-7o5", "Gs4", "G", "Cs2", "C",                                      // S2
	"F^7", "Bo", "B-7o5", "E7", "A-", "A-7", "F^7", "BFr", "Es4", "E",               // S3
	"A7", "DM2P4P5m7", "D7", "GP4P5m7", "G7", "C7", "C-7o5", "F7", "Ab-",            // S4
	"Bb7", "EFr", "A", "A", "D7", "GP4P5m7", "G7", "C^7", "FA4P5M7", "F^7",          // S5
	"B-7o5", "E-7", "A-7", "D-7", "G7", "C^7", "F^7", "B-7o5", "B7", "E", "E", "G7", // S6
	"C", "F", "F#-7o5", "D7", "G", "D7", "D-7o5", "G7", "GM3A5m7",                   // S7
	"C", "F", "F#-7o5", "F#o7", "Gs4", "G", "D7", "F7", "BFr",                       // S8
	"E", "D-7", "G7", "C", "C7", "F#o7", "Bo7", "C", "F#o7", "Bo7",                  // S9
	"Eo7", "F#m3d6d7", "F#o7", "D-7o5", "G7", "C"                                    // S10
];

// Durations
~d = [
	4, 2, 1.5, 0.5, 2, 2, 2, 1.5, 0.5,      // S1
	4, 2, 2, 2, 2, 2, 2,                    // S2
	2, 1, 1, 2, 1, 1, 2, 2, 2, 2,           // S3
	2, 1.5, 0.5, 2, 2, 2, 2, 2, 2,          // S4
	2, 2, 2, 2, 2, 1.5, 0.5, 2, 1.5, 0.5,   // S5
	2, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1.5, 0.5, // S6
	4, 2, 1.5, 0.5, 2, 2, 2, 1.5, 0.5,      // S7
	4, 2, 1, 1, 2, 2, 2, 1, 1,              // S8
	4, 2, 2, 1, 1, 1, 1, 2, 1, 1,           // S9
	2, 1.5, 0.5, 2, 2, 4                    // S10
];

And, much to my surprise, I was able to do it by calling MEChord inside a simple loop:

~r = Array.new(~p.size);
~v = 5;

~p.do { |c, i|
	var temp;

	temp = if ( i == 0 ) {
		MEChord(c, prevChord: nil, voiceNum: ~v).chords.choose;
	} {
		MEChord(
			c,
			prevChord: ~r[i - 1],
			ruleProf: [enforceUnisonProhibition: false, enforceChordPosition: true],
			voiceNum: ~v
		).chord;
	};
	~r.add(temp);
};

And this is what it sounds like:

To play the progression I created a simple FM synth with an organ sound, inspired by one of Eli Fieldsteel’s tutorials on FM. My main purpose was to be able to hear the chord changes clearly as a test.

The code is available at sc_musicengine_harmonizer/experiments/experiment02.scd:

(The repo is a bit messy still, and the README is outdated, sorry. Will get to that soon)

If you find these type of projects interesting I’d love to know your thoughts…
All the best!

5 Likes

Alôl alô, hope everyone is doing well!

I want to share my latest experiment. Based on the previous one, I decided to go for a full Chopin interpretation and I chose the Prelude in E minor - It’s small, has sophisticated harmonies and the chords progress by movement of parts, preserving common tones, which is ideal for experimenting with sustained common tones.

This time I broke the progression into two sections, plus the final cadence.

~p1 = [
	"Em", "C^7", "BP4P5m7", "B7", "D7", "BFr", "Bm3d5m7", "G#d4d5d7", "E7", "E-7", "C#o7",  // S1
	"A-7", "F#-7o5", "F#P4d5m7", "F#-7o5", "D#o7", "BM3P5m9", "D7", "D-7", "B-7o5", "G#o7", // S2
	"CM3A5M7", "A-", "BP4P5m7", "B7", "F#-7o5", "A-", "B7", "F#-7o5", "A-", "BM3m7",        // S3
];

~p2 = [
	"E-", "C^7", "BP4P5m7", "BFr", "D#Gr", "G#m3P5d7", "G#o7", "E7", "CM3A5M9", "E-7", "A#o7", "A#o7", "F#-7o5", // S4
	"Bs4", "D#o7", "GM3A5M7", "E-", "A-", "F#-7o5", "Bs4", "Am", "Bs4", "Am", "Bs4", "B", "B7",                  // S5
	"C", "CM3m7", "F#-7o5", "Es4", "E","E-", "Em3P5M9", "A#Gr"                                                   // S6
];

~p3 = ["Bs4", "B", "E-"];

As before, I started by generating a chord progression. From there I extracted the voices and sustained common tones by removing adjacent duplicate notes and adding their duration. Such that, given a voice with notes:

[a, a, a, b, b, c]

with duration:

[1, 1, 1, 1, 1, 1]

we get:

[a, b, c]
[3, 2, 1]

This method creates an interesting counterpoint-like effect:

Finally I added the melody, a bass line and percussion. Overall I like the result and find it a bit better than previous musical experiments, though my sound design and mixing skills are still lagging a bit behind.

Anyway, here’s the final result:

code at:

As always, I’m open to constructive criticism.
All the best!

1 Like

Thank you for sharing this. I haven’t looked closely at your code yet, but at first glance it seems that you organise the symbols based on absolute pitches. How would users transpose the material once they have finished their work? If the symbol system were one of the following, transposition would be considerably simpler:

  • Roman numeral analysis
  • Functional analysis
  • Figured bass numbers with the bass pitch

If you have already addressed transposition in your implementation, I apologise for offering my thoughts without examining your post in detail.

1 Like

No worries, offer away :slight_smile:

I’ve addressed it to a certain extend in MusicEngine. I’ve overloaded the shift operators to do transposition, upwards >> and downwards <<.

~r = MENoteRange("F^7") >< [4, 5]; // get octaves 4 to 5

~r.postln;
// MENoteRange[C4:P5, E4:M7, F4:P1, A4:M3, C5:P5, E5:M7, F5:P1, A5:M3]

~t1 = ~r >> \d3; // Transpose up by specifying interval
~t2 = ~r >> 2;   // Transpose up by default value (defaults to M2)

~t1.postln;
// MENoteRange[Ebb4:P5, Gb4:M7, Abb4:P1, Cb5:M3, Ebb5:P5, Gb5:M7, Abb5:P1, Cb6:M3]

~t2.postln;
// MENoteRange[D4:P5, F#4:M7, G4:P1, B4:M3, D5:P5, F#5:M7, G5:P1, B5:M3]

The two transposed ranges are enharmonic. They both sound the same but the spelling of the notes is different.

Since chords and voices, in this project, are MENoteRange objects, created using the alternative constructor MENoteRange.with(symbol ... args), the same syntax can be applied:

~c = MEChord("F^7"); // get all four part realizations of F^7

~c.chords[0].postln;          // MENoteRange[E2:M7, C3:P5, A3:M3, F4:P1]
(~c.chords[0] >> \M2).postln; // MENoteRange[F#2:M7, D3:P5, B3:M3, G4:P1]

This current implementation doesn’t transpose a complete progression directly, you have to iterate over each chord/voice and transpose one at a time. I’m hopping to extend that functionality to a future MEProgression class.

Alternatively, if you simply want to transpose midi or frequency values, for example, you can do:

~c = MEChord("F^7");

~c.chords[0].midi.postln;       // [40, 48, 57, 65]
(~c.chords[0].midi + 2).postln; // [42, 50, 59, 67]

// or:
~c.chords[0].freq.postln;       // [82.406889228217, 130.8127826503, 220.0, 349.228231433]

// up an octave:
(~c.chords[0].freq * 2).postln; // [164.81377845643, 261.6255653006, 440.0, 698.45646286601]

The .midi and .freq methods return the range as an array of midi or frequency values.

I like your suggestions and they touch into an area I find really interesting and would like to explore in the the future: Key detection and ways to implement a MEKey class. That would allow for harmony generation simply by specifying a key and not a complete chord progression, for example.

1 Like

Thank you very much for your kind and detailed explanation.
Great solution!
I’ll take a closer look at your code when I have time.

1 Like

I restrained myself from commenting earlier, but now I am trying to do taxes so the procrastination pull is just too strong …

anyway, you might be interested in looking at some ~genuine bona fide~ music theory as you design your automatic key detection algorithm (especially if you’re looking at reactionary crap hallowed and much studied repertoire pieces such as this). I’d be very surprised if voice leading didn’t play a role there. Why I’m saying this: Your labels (I wouldn’t call that an “analysis” tbh) do not take chord inversion into account and consequently there’s very little chance of their reflecting any sort of voice-leading pattern; there’s also a distinction missing occasionally between harmonic and non-harmonic tones, which is a very important thing to understand in most tonal music.

Also, the first etude is probably a better suited piece to do this with (compare the czerny reduction, visualized here by an analysis prof at the paris conservatory) but the e minor prelude might be trickier (to “analyse” or analyse).

I feel like I should probably recommend some books so this doesn’t come off too negatively. There are lots of garbage textbooks out there and I have more warnings than recommendations, and I don’t know your background (e.g. do you play piano, or any other instruments, …); a good one for classical music is Laitz, “the complete musician”. (But don’t buy it, steal it somewhere; don’t give the textbook racket any of your money!)
The issue with any textbook though is that this relies a lot on practice, so one needs to do the exercises rather than just read through it in order to gain fluency…

1 Like

Hey there, thanks for your comments and observations.

I did use an analysis of the first etude, but what I’ve shown above is not the analysis. That’s a chord progression I extracted and adapted from the analysis I made. It does not include chord positions because at this stage of development assigning chord positions to chords is not practical. To do so I have to create a dictionary for each symbol specifying chord position, and pass it as argument:

// Something like

~rules = Dictionary[
    \enforceChordPosition -> true,
    \enforceRootPosition -> true,
   \enforceFirstInversion -> false,
   ...
];

~c = MEChord("F^7", ruleProf: ~rules);

Hopefully this process will be simplified as development goes on.

There are in fact non-harmonic tones in some of the chord symbols, normally from retardation, anticipations, and other means. For the purpose of testing, it means more to me that I’m able to express different chord configurations, like FA4P5M7, than the notion that the apparent augmented fourth emerges as a retardation that will resolve to a major third once we reach F^7, therefore the real chord should be F^7.

Key detection seems like an interesting problem, and challenge, to program, but that’s not what I’m doing right now, nor will I be working on that anytime soon. I’m building a harmonizer, as a tool, to make working with harmonies in SuperCollider simpler. That’s it. That’s what these two experiment are all about. The project is based on music theory, to some extent, but it’s not meant to be a tool for musical analysis, or to be able to recreate specific styles from the past with perfect accuracy. In that sense, I chose Chopin simply because I had a long progression at hand, ready to go. It was simply convenient.

FA4P5M7 , the notion that the apparent augmented fourth emerges as a retardation that will resolve to a major third once we reach F^7 , therefore the real chord should be F^7 .

That one is basically ok, but I was talking about things like:

Dm3P4d5m7

Where you just got the wrong fundamental bass (the correct one is the G, printed in the score).
This is simply a G7 with a 9b/4 to 8/3 suspension (i.e., a version of the “cadential 6/4”). But if the fundamental bass is wrong, there’s no way you’ll analyze (or label) this correctly.
Two bars following each other, with the first G7/9/4 and the second its resolution G7/8/3 do not contain a change of harmony; it’s just the appoggiatura (cadential 4) and its resolution (3).
Whereas if you write it as one bar with a fundamental bass D, another with a fundamental bass G, it seems like there’s a change of harmony.

Re key detection, I simply misread your previous post, I thought that’s what you wanted to do next when you were simply reacting to someone else’s post.

I was looking at my analysis and I do interpret the chord as a D-7o5 over a G in the bass, with the G being an anticipation to the G7 chord cumming next. this makes for a common subdominant - dominant - tonic move towards C major. A D-7o5 makes for an altered chord over the ii degree, in the context of C major, but it’s not like that’s unheard of around that time. I get your explanation and interpretation. But I also don’t think mine is that unreasonable. Besides, both interpretation account for the same exact notes. I could have written GP4P5m7m9, instead of Dm3P4d5m7 and it would have sounded exactly the same.

But still, it’s perfectly possible I made a mistake, it happens.

It’s not a “mistake” but rather an analytically unhelpful label, I would say… again, I wrongly assumed you wanted to do key detection, in which case it helps to be a bit more familiar with the style, on which there is tons of literature. Otoh if it’s just about making music and, as you say, you’re just using that progression to try out the harmonizer, everything goes really…