Pseudo-classical chord progression
The complete example can be downloaded here: pseudo-classical-progression.scd.
Consider the following graph:
Each arrow represents an "allowed" progression from one functional harmony to another in a western major scale. So I, ii, iii, IV, V, vi and vii° represent the triad or chord over the corresponding note in the scale (with lower-case roman numerals for minor chords and a dimished chord for the seventh). From I we are allowed to progress to any other chord, represented by "*".
Let's write a random chord progression in sclang that follows the arrows in this graph!
To approach this task, we can represent the graph in form of a table:
From | To |
---|---|
I | any |
ii | V, vii° |
iii | vi, IV |
IV | I, ii, V, vii° |
V | I, vi, vii° |
vi | ii, iv |
vii° | I |
And in sclang we can represent this table as an array of arrays:
var majorTrans = [
[1, 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
];
Each position in the array majorTrans
represents the corresponding From
entry in the table above, except that the first position I is represented as
index 0 in the table. The corresponding entry in the array is an array
itself, representing the list of allowed positions to progress to. These are
as in the graph/table (starting with 1 for the first position), so we have to
substract 1 from each element to get valid indices into our own table (again,
because the first index being 0).
If we continue to think abstractly only in "positions", we will need functions to build major, minor and dimished chords based on the position in the scale:
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] };
var triads = [major3, minor3, minor3, major3, major3, minor3, dimis3];
Note that triads
is an array of functions: At each position of a major
scale we store the corresponding function to build the corresponding triad.
Now we have everything to generate a routine that randomly walks through the graph:
~classicOrbit = {
arg center = 0, // The tonal center in semitones from c
position = 0; // The current position in the scale
var semitone = center + Scale.major.degrees[position]; // The current root in semitones from c
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] };
var triads = [major3, minor3, minor3, major3, major3, minor3, dimis3];
var majorTrans = [
[1, 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
];
Routine {
// For the given tonal center and position, yield the
// corresponding triad, in degrees of semitones.
triads[position].value(semitone).debug("start").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];
// Yield the correspoding triad
triads[position].value(semitone).debug("next "++(position +1)).yield;
}
}
}
Let's play the progression:
(
Pbind(
\scale, Scale.chromatic,
\degree, ~classicOrbit.value(center: -12),
\dur, 0.5,
).play;
)
Adding modulation
We can spice it up by adding modulation by, say, 75% of the times whenever we reach the IV or V position, which then become the new tonal center and progression continues:
if(
// the predicate:
((position == 3) || (position == 4)) && (1.0.rand < 0.75),
// holds, do:
{
(position+1).debug("modulate to");
center = (semitone % 12).debug("new center");
position = 0;
},
// else, do nothing:
{}
);
Incorporating this into ~classicOrbit
and making the rate of modulation an
argument modulationRate
, as well as adding an argument transpose
for
transposing all chords we end up with:
~classicOrbit = {
arg center = 0, // The tonal center in semitones from c
position = 0, // The current position in the scale
modulationRate = 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
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] };
var triads = [major3, minor3, minor3, major3, major3, minor3, dimis3];
var majorTrans = [
[1, 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
];
postln("starting classicOrbit with tonal center "++ center ++
" at position "++ (position +1) ++
", transposed by "++ transpose ++
" and modulation rate "++ rate);
Routine {
// For the given tonal center and position, yield the
// corresponding triad, in degrees of semitones.
(triads[position].value(semitone) + transpose).debug("start").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];
// Yield the correspoding triad
(triads[position].value(semitone) + transpose).
debug("next "++(position +1)).
yield;
// 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;
},
// else, do nothing:
{}
);
}
}
}
Let's start the orbit with a rate of modulation of 0.75 and transposing all chords by -12 semitones:
(
Pbind(
\scale, Scale.chromatic,
\degree, ~classicOrbit.value(transpose: -12, modulationRate: 0.75),
\dur, 0.5,
).play;
)
In the post-window we see an output like this:
starting classicOrbit with tonal center 0 at position 1, transposed by -12 and modulation rate 0.75
start: [ -12, -8, -5 ]
next 6: [ -3, 0, 4 ]
next 2: [ -10, -7, -3 ]
next 5: [ -5, -1, 2 ]
modulate to: 5
new center: 7
next 1: [ -5, -1, 2 ]
next 5: [ 2, 6, 9 ]
next 6: [ 4, 7, 11 ]
next 2: [ -3, 0, 4 ]
next 5: [ 2, 6, 9 ]
modulate to: 5
new center: 2
next 6: [ -1, 2, 6 ]
next 4: [ -5, -1, 2 ]
modulate to: 4
new center: 7
next 7: [ 6, 9, 12 ]
next 1: [ -5, -1, 2 ]
next 5: [ 2, 6, 9 ]
modulate to: 5
new center: 2
next 1: [ -10, -6, -3 ]
next 4: [ -5, -1, 2 ]
next 2: [ -8, -5, -1 ]
next 5: [ -3, 1, 4 ]
next 1: [ -10, -6, -3 ]
next 5: [ -3, 1, 4 ]
...
This is how it sounds:
Better voice leading
All the chords in the progression so far are in first position. For better
voice leading, we can incorporate our functions dist
and findBestInversion
from the Voice Leading example. The final version of
~classicOrbit
than looks like this:
(
~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:
{}
);
}
}
}
)
Let's put the orbit into a Pbind:
(
x = Pbind(
\scale, Scale.chromatic,
\degree, ~classicOrbit.value(center: -6, modulationRate: 0.75),
\dur, 0.5,
).play;
)
This time, it sounds like this: