( ~classicOrbit = { arg center = 0, // The tonal center in semitones from c position = 0, // The current position in the scale modulationRate = 0.0, // Rate of modulation transpose = 0; // Transpose all chords by this var semitone = center + Scale.major.degrees[position]; // The current root in semitones from c /* Allowed transitions in a major scale: From | To -----+----------- I | anywhere II | V, VII°⁶ III | VI, IV IV | II, V, VII°⁶, I V | I, VII°⁶, VI VI | II, IV VII°⁶| I */ var majorTrans = [ [2, 3, 4, 5, 6, 7]-1, // any [5, 7]-1, // V, vii° [4, 6]-1, // IV, vi [1, 2, 5, 7]-1, // I, ii, V, vii° [1, 6, 7]-1, // I, vi, vii° [2, 4]-1, // ii, IV [1]-1, // I ]; var major3 = { arg root = 0; root + [0, 4, 7] }; var minor3 = { arg root = 0; root + [0, 3, 7] }; var dimis3 = { arg root = 0; root + [0, 3, 6] }; // triads is an array containing functions, namely the corresponding var triads = [major3, minor3, minor3, major3, major3, minor3, dimis3]; // dist and findBestInversion are helper functions to find the // inversions with the best voice leading. See example "Voice Leading". var dist = {arg tr1, tr2; (tr2-tr1).sum({|i| i**2}).sqrt(); }; var findBestInversion = { arg tr1, tr2; var inversions = [ tr2, (tr2 + [0 , -12, -12]).sort, (tr2 + [0 , 0, -12]).sort, tr2 - 12, ]; var distances = inversions.collect({|chord| dist.(tr1, chord)}); var minIdx = distances.minIndex; inversions[minIdx]; }; postln("starting classicOrbit with tonal center "++ center ++ " at position "++ (position +1) ++ ", transposed by "++ transpose ++ " and modulation rate "++ modulationRate); Routine { // For the given tonal center and position, yield the // corresponding triad, in degrees of semitones. var chord = ((triads[position].value(semitone) + transpose)%12).sort; var prev = chord; // Remember _this_ chord for later chord.debug("start"); chord.yield; // For the current position, choose from the array of allowed // progressions. while {position = majorTrans[position].choose; position != nil} { // Calculate actual degree in semitones for the given // tonal center and current position. semitone = center + Scale.major.degrees[position]; chord = (triads[position].value(semitone) + transpose); // For better voice leading, find the best inversion // for the chord, based on the previous chord in the progression. chord = findBestInversion.value(prev, chord); // Now, yield the correspoding triad chord.debug("chord at next position "++(position +1)); chord.yield; prev = chord; // Remember this chord for the next round. // Spicing up the progression with sporadic modulation if(// the predicate: ((position == 3) || (position == 4)) && (1.0.rand < modulationRate), // holds, do: { (position+1).debug("modulate to"); center = (semitone % 12).debug("new center"); position = 0; // We are now back to root position }, // else do nothing: {} ); } } } ) ( x = Pbind( \scale, Scale.chromatic, \degree, ~classicOrbit.value(center: -6, modulationRate: 0.5), \dur, 1/2, ).play; )