( /* We follow the description from https://en.wikipedia.org/wiki/Neo-Riemannian_theory 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) */ 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! }; 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") } }; 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") } }; var riemann = (); // Dictionary -> // 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) 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) } // Neither major nor minor? Debug! { true }{ triad.debug("[P] unknown modus") } }; // 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) } // Neither major nor minor? Debug! { 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) } // Neither major nor minor? Debug! { true }{triad.debug("[L] 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]; // neoOrbit 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; }; }; }; ) ( Pbind( \scale, Scale.chromatic, \degree, ~neoOrbit.value(chord: [0, 4, 7]+2, stream: Pseq([\R, \L], inf).asStream), \dur, 0.5, ).play; )