Example: Neo-Riemannian Orbit
The complete example can be downloaded here: neo-riemannian.scd.
Suppose we want to generate chord progressions based on Neo-Riemannian Theory, which gives a theoretical framework to navigate through all possible major- and minor triads by applying certain transformations. To quote from Wikipedia about Neo-Riemannian Theory:
The principal transformations of neo-Riemannian triadic theory connect triads of different species (major and minor), and are their own inverses (a second application undoes the first). These transformations are purely harmonic, and do not need any particular voice leading between chords: all instances of motion from a C major to a C minor triad represent the same neo-Riemannian transformation, no matter how the voices are distributed in register.
The three transformations move one of the three notes of the triad to produce a different triad:
- The P transformation exchanges a triad for its Parallel. In a Major Triad move the third down a semitone (C major to C minor), in a Minor Triad move the third up a semitone (C minor to C major)
- The R transformation exchanges a triad for its Relative. In a Major Triad move the fifth up a tone (C major to A minor), in a Minor Triad move the root down a tone (A minor to C major)
- The L transformation exchanges a triad for its Leading-Tone Exchange. In a Major Triad the root moves down by a semitone (C major to E minor), in a Minor Triad the fifth moves up by a semitone (E minor to C major)
The transformations P, R and L
We will have to implement the transformations P, R and L. Let's keep them in a
dictionary called riemann
, where we put each transformation associated with
it's symbol, like \L
.
var riemann = (); // Dictionary <symbol> -> <neo-riemannian-transformation>
The P transformation exchanges a triad for its Parallel. In a Major Triad move the third down a semitone (C major to C minor), in a Minor Triad move the third up a semitone (C minor to C major).
Even if we don't have yet the means to decide programmatically if a given triad
is a major or minor one, or how to raise or lower particular notes in it, we
can start expressing our intend right away by writing code that calls
not-yet-available functions, but with names that we like to use. For example,
the description for the transformation \P
above could be expressed like this:
riemann[\P] = { |triad| // [a, b, c]
case
// In a Major Triad move the third down a semitone (C major to C minor),
{ isMajor.(triad) } { moveThird.(triad, -1); }
// in a Minor Triad move the third up a semitone (C minor to C major)
{ isMinor.(triad) } { moveThird.(triad, 1); }
{true}{triad.debug("[P] unknown modus");};
};
We use the case {}{}…{}{}
construct to differentiate between different cases
(major, minor and catch-all case (for detecting errors)) and call functions
isMajor
, isMinor
and moveThird
, which we will need to implement
seperately. Nevertheless, our intend has been clearly stated and readers of our
code will understand.
Before implementing the missing functions, let's write the other two transformations in a similar manner.
The R transformation exchanges a triad for its Relative. In a Major Triad move the fifth up a tone (C major to A minor), in a Minor Triad move the root down a tone (A minor to C major)
riemann[\R] = { |triad| // [a, b, c]
case
// In a Major Triad move the fifth up a tone (C major to A minor),
{ isMajor.(triad) } { moveFifth.(triad, 2) }
// in a Minor Triad move the root down a tone (A minor to C major)
{ isMinor.(triad) } { moveRoot.(triad, -2) }
{true}{triad.debug("[R] unknown modus");};
};
The L transformation exchanges a triad for its Leading-Tone Exchange. In a Major Triad the root moves down by a semitone (C major to E minor), in a Minor Triad the fifth moves up by a semitone (E minor to C major)
riemann[\L] = { |triad| // [a, b, c]
case
// In a Major Triad the root moves down by a semitone (C major to E minor),
{ isMajor.(triad) } { moveRoot.(triad, -1) }
// in a Minor Triad the fifth moves up by a semitone (E minor to C major)
{ isMinor.(triad) } { moveFifth.(triad, 1) }
{true}{triad.debug("[L] unknown modus")};
};
Helper functions
Finally, we will now implement the missing functions and put them before what we have written so far. First, we need function to distinguish Major and Minor chords:
var isMajor = { |triad|
var f = triad[1]-triad[0]; // first interval
var s = triad[2]-triad[1]; // second interval
// Ex.: triad == [0, 4, 7] (C Major)
// ⇒ triad[0] == 0, triad[1] == 4, triad[2] == 7
// ⇒ f == 4 and s == 3
// Check all possible inversions of a major chord by looking at the
// intervals:
case
{(f==4 && s==3) || (f==3 && s==5) || (f==5 && s==4)} { true } // major!
{(f==3 && s==4) || (f==5 && s==3) || (f==4 && s==5)} { false } // minor!
// Other interval combinations mean invalid triad, help debugging it:
{ true }{ triad.debug("[isMajor] unknown modus"); false } // error!
};
var isMinor = { |triad|
var f = triad[1]-triad[0]; // first interval
var s = triad[2]-triad[1]; // second interval
case
{(f==3 && s==4) || (f==5 && s==3) || (f==4 && s==5)} { true } // minor!
{(f==4 && s==3) || (f==3 && s==5) || (f==5 && s==4)} { false } // major!
{ true }{ triad.debug("[isMinor] unknown modus"); false } // error!
};
... and functions manipulating chords in specific ways:
var moveRoot = { |triad, d|
// triad = [a, b, c]
var a = triad[0];
var b = triad[1];
var c = triad[2];
case
// root position: fifth between a and c, root == a
{ c-a == 7 }{ triad + [ d, 0, 0 ] }
// first inversion: forth between b and c, root == c
{ c-b == 5 }{ triad + [ 0, 0, d ] }
// second inversion: forth between a and b, root == b
{ b-a == 5 }{ triad + [ 0, d, 0 ] }
// Couldn't identify the root! Help debugging it.
{ true }{ triad.debug("unknown root") }
};
var moveThird = { |triad, d|
var a = triad[0];
var b = triad[1];
var c = triad[2];
case
// root position: fifth between a and c, third == b
{ c-a == 7 }{ triad + [ 0, d, 0] }
// first inversion: forth between b and c, third == a
{ c-b == 5 }{ triad + [ d, 0, 0] }
// second inversion: forth between a and b, third == c
{ b-a == 5 }{ triad + [ 0, 0, d] }
{ true }{ triad.debug("unknown third") }
};
var moveFifth = { |triad, d|
var a = triad[0];
var b = triad[1];
var c = triad[2];
case
// root position: fifth between a and c, fifth == c
{ c-a == 7 }{ triad + [ 0, 0, d] }
// first inversion: forth between b and c, fifth == b
{ c-b == 5 }{ triad + [ 0, d, 0] }
// second inversion: forth between a and b, fifth == a
{ b-a == 5 }{ triad + [ d, 0, 0] }
{ true }{ triad.debug("unknown fifth") }
};
For printing chord names:
var noteNames = ["C", "C♯", "D", "D♯", "E", "F", "F♯", "G", "G♯", "A", "A♯", "B"];
var getRoot = { |triad|
var a = triad[0];
var b = triad[1];
var c = triad[2];
case
{ c-a == 7 }{ a }
{ b-a == 5 }{ b }
{ c-b == 5 }{ c }
{ true }{ triad.debug("[getRoot] unknown root") }
};
var chordName = { |triad|
var root = getRoot.(triad);
var name = noteNames[root];
case
{ isMajor.(triad) } { name ++ " Major" }
{ isMinor.(triad) } { name.toLower ++ " Minor" }
{ true }{ triad.debug("[chordName] unknown modus") }
};
Secondary transformations
The N (or Nebenverwandt) relation exchanges a major triad for its minor subdominant, and a minor triad for its major dominant (C major and F minor). The "N" transformation can be obtained by applying R, L, and P successively.
riemann[\N] = riemann[\R]<>riemann[\L]<>riemann[\P];
S (or Slide) relation exchanges two triads that share a third (C major and C♯ minor); it can be obtained by applying L, P, and R successively in that order:
riemann[\S] = riemann[\L]<>riemann[\P]<>riemann[\R];
The H relation (LPL) exchanges a triad for its hexatonic pole (C major and A♭ minor):
riemann[\H] = riemann[\L]<>riemann[\P]<>riemann[\L];
The combination PRL has no name on wikipedia, transforming C major to D major. Let's call it U.
riemann[\U] = riemann[\P]<>riemann[\R]<>riemann[\L];
An orbit routine
neoOrbit
is a function that expects a chord and a stream of neo-riemannian
transformations (as symbols) and returns a routine that yields the progression
of chords by applying the transformations consecutively.
~neoOrbit = {
arg chord = [0, 4, 7], stream = Pseq([\P, \L, \R], inf).asStream;
Routine {
var tr;
loop{
chordName.(chord).debug("chord");
// Eject the current chord to the consumer of this
// routine, when called .next() on us
chord.yield;
// Get the next transformation (symbol) from the stream
tr = stream.next().debug("trans");
// Apply the transformation
chord = riemann[tr].(chord);
// Stay within the 0-11 range
chord = chord%12;
// After applying `%12` the array might be out of order
chord.sort;
};
};
};
Following the orbit
Let's put everything together and generate a chord progression, starting at D-Major and alternating between the transformations R and L:
(
Pbind(
\scale, Scale.chromatic,
\degree, ~neoOrbit.value(chord: [0, 4, 7]+2, stream: Pseq([\R, \L], inf).asStream),
\dur, 0.5,
).play;
)
If everything worked well, you should hear a chord progression, starting at D-Major and moving through the whole Tonnetz, i.e. orbiting through all 24 possible chords. The output in the Post window should look like this:
chord: D Major
trans: R
chord: b minor
trans: L
chord: G Major
trans: R
chord: e minor
trans: L
chord: C Major
trans: R
chord: a minor
trans: L
chord: F Major
trans: R
chord: d minor
trans: L
chord: A♯ Major
trans: R
chord: g minor
trans: L
chord: D♯ Major
⋮
Have a listen to the first few seconds of the progression: