Example: Voice Leading
The complete example can be downloaded here: voice-leading.scd.
Suppose we have two chords as part of a progression, given as triads in root position, and we want to choose an appropriate inversion of the second chord for good voice leading. For example, given the triads C-major and e-minor, we want to choose instead of or .
How can we programmatically differentiate these cases? One way would be to define and calculate a distance between two triads and compare the distances in all cases. If we consider a triad as a 3-dimensional vector with integer entries, we can simply use the euclidean distance. For two vectors \((a, b, c)\) and \((x, y, z)\) their euclidean distance is defined as \(\sqrt{(a-x)^2 + (b-y)^2 + (c-z)^2}\). Let's write this as a function in sclang:
// ~distance is a function that expects two triads as arguments and calculates
// the euclidian distance between them. In other words, it considers both
// triads as points in a three-dimensional space.
~distance = {
arg tr1, tr2;
// Given two triads tr1=[a, b, c] and tr2=[x, y, z], the euclidian distance is
// defined as
// dist(tr1, tr2) = sqrt((a-x)² + (b-y)² + (c-z)²)
// In sclang, we can write this from the inside out:
(tr2-tr1).sum({|i| i**2}).sqrt();
};
We can now write the function that, given two triads, will return the "best" inversion for the second triad in the sense that it chooses an inversion of the second triad that has the minimal "distance" to the first:
// ~findBestInversion is a function that expects two triads as arguments and
// returns an inversion of the second triad that has the minimal distance to
// the first in terms of steps in the voices.
~findBestInversion = {
arg tr1, tr2; // triads given.
// Let's generate an array of all inversions of the second triad. We
// will later find and return the one with the minimal distance.
var inversions = [
tr2, // root position
(tr2 + [0 , -12, -12]).sort, // first inversion, down an octave
(tr2 + [0 , 0, -12]).sort, // second inversion, down an octave
tr2 - 12, // root position, down an octave
];
// For each chord in the array `inversions` we calculate its ~distance
// to the first triad and collect the results into an array.
var distances = inversions.collect({|chord| ~distance.value(tr1, chord)});
// Find the index that contains the minimal distance.
var minIdx = distances.minIndex;
// Return the inversion at that index as our choice for the progression.
inversions[minIdx];
};
After putting all the code above within the same scope (…)
, let's try the
function ~fundBestInversion
on
C-major ([0, 4, 7]
) and e-minor ([4, 7, 11]
):
~findBestInversion.value([0, 4, 7], [4, 7, 11]);
In the Post window we see the result [ -1, 4, 7]
which corresponds to the
first inversion, as expected.
We will soon use the function ~findBestInversion
within the other examples of
chord progressions.