Serial technique is highly algorithmic way of generating musical ideas, and lends itself well to programming. In this post I’m going to sketch out a few bits of JavaScript I’ve been using to explore some of the possibilities of this technique.
(For those unfamiliar with serialism, and the 12-tone technique in particular, it is a method for producing musical — typically pitch — material by taking a list of values as a starting point. Various transformations can then be applied to generate new sequences. The multiplicity of different sequences provides variation, whilst their relationships back to the original series can give a sense of repetition and unity in the resulting music.)
Let’s consider 12-tone serialism.
A prime row can be considered as a mapping function P from a position to a pitch.
If we have a row [0, 3, 5, 8, 11, 1, 6, 10, 4, 9, 2, 7], then we can define P as follows:
P(0) => 0
P(1) => 3
P(2) => 5
P(3) => 8
P(4) => 11
P(5) => 1
P(6) => 6
P(7) => 10
P(8) => 4
P(9) => 9
P(10) => 2
P(11) => 7
On this basis, we can write a JavaScript function prime
which will take an argument n and return the appropriate pitch class name. (You can play along by pasting the code samples into the JS console in your browser).
var prime = (function () {
var p = [0, 3, 5, 8, 11, 1, 6, 10, 4, 9, 2, 7];
return function(n) {
return p[n % 12]; // if a value of 12 or above is given, start from 0 again
};
}());
prime(1); // => 3
prime(4); // => 11
(I’ve used a module pattern here to instantiate the variable p
just once, and keep it hidden from the external scope.)
I can also write another function getPitches
which will take a mapping function and apply it to an array [0..11]:
var getPitches = (function () {
var d = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
return function (callback) {
return d.map(function(n) {
return callback(n) % 12; // if the result is 12 or above, transpose down an octave
});
};
}());
getPitches(prime);
// => [0, 3, 5, 8, 11, 1, 6, 10, 4, 9, 2, 7]
// or
getPitches(function(n) { return prime(n); });
// => [0, 3, 5, 8, 11, 1, 6, 10, 4, 9, 2, 7]
We can now retrieve transformations of the row.
To get transpositions, re feed in prime(n) + x
:
getPitches(function(n) { return prime(n) + 1; });
// => [1, 4, 6, 9, 0, 2, 7, 11, 5, 10, 3, 8]
getPitches(function(n) { return prime(n) + 4; });
// => [4, 7, 9, 0, 3, 5, 10, 2, 8, 1, 6, 11]
To get the Inversion row, we use 12 - prime(n)
:
getPitches(function(n) { return 12 - prime(n); });
// => [0, 9, 7, 4, 1, 11, 6, 2, 8, 3, 10, 5]
getPitches(function(n) { return 12 - prime(n) + 3; });
// => [3, 0, 10, 7, 4, 2, 9, 5, 11, 6, 1, 8]
To form the Retrograde we use prime(11 - n)
:
getPitches(function(n) { return prime(11 - n); });
// => [7, 2, 9, 4, 10, 6, 1, 11, 8, 5, 3, 0]
getPitches(function(n) { return prime(11 - n) + 3; });
// => [10, 5, 0, 7, 1, 9, 4, 2, 11, 8, 6, 3]
(The discrepancy between subtracting from 12 for the Inversion and 11 for the Retrograde is because we expect the Inversion to start on the same pitch, while the Retrograde moves the starting pitch to the end.)
And of course we can get the Retrograde Inversion with 12 - prime(11 - n)
:
getPitches(function(n) { return 12 - prime(11 - n); });
// => [5, 10, 3, 8, 2, 6, 11, 1, 4, 7, 9, 0]
getPitches(function(n) { return 12 - prime(11 - n) + 3; });
// => [8, 1, 6, 11, 5, 9, 2, 4, 7, 10, 0, 3]
In addition to these transformations, we can rotate the row (so, for instance, we start with the third value in the series):
getPitches(function(n) { return prime(n + 2); });
// => [5, 8, 11, 1, 6, 10, 4, 9, 2, 7, 0, 3]
And we can also experiment with multiplication:
Multiplying the argument passed into prime
viz prime(n * x
will jumpt between elements in the original series. If the multiplier is a factor of 12, then we will get a result that repeats a subsequence of the original; if it’s not a factor, then we will get a reordering:
getPitches(function(n) { return prime(n * 2); });
// => [0, 5, 11, 6, 4, 2, 0, 5, 11, 6, 4, 2]
getPitches(function(n) { return prime(n * 3); });
// => [0, 8, 6, 9, 0, 8, 6, 9, 0, 8, 6, 9]
getPitches(function(n) { return prime(n * 4); });
// => [0, 11, 4, 0, 11, 4, 0, 11, 4, 0, 11, 4]
getPitches(function(n) { return prime(n * 5); });
// => [0, 1, 2, 8, 4, 3, 6, 7, 11, 9, 5, 10]
getPitches(function(n) { return prime(n * 6); });
// => [0, 6, 0, 6, 0, 6, 0, 6, 0, 6, 0, 6]
getPitches(function(n) { return prime(n * 7); });
// => [0, 10, 5, 9, 11, 7, 6, 3, 4, 8, 2, 1]
getPitches(function(n) { return prime(n * 8); });
// => [0, 4, 11, 0, 4, 11, 0, 4, 11, 0, 4, 11]
getPitches(function(n) { return prime(n * 9); });
// => [0, 9, 6, 8, 0, 9, 6, 8, 0, 9, 6, 8]
getPitches(function(n) { return prime(n * 10); });
// => [0, 2, 4, 6, 11, 5, 0, 2, 4, 6, 11, 5]
getPitches(function(n) { return prime(n * 11); });
// => [0, 7, 2, 9, 4, 10, 6, 1, 11, 8, 5, 3]
getPitches(function(n) { return prime(n * 12); });
// => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Note the following properties of these series:
- Maps to 6 pitches, repeated twice
- Maps to 4 pitches, repeated thrice
- Maps to 3 pitches, repeated four times
- Remaps all pitches
- Maps to 2 pitches (the fact that they are 0 and 6 is a propert of the Prime row, rather than this ordering)
- Remaps all pitches, 0-based Retrograde of 5
- Maps to 3 pitches, 0-based Retrograde of 4
- Maps to 4 pitches, 0-based Retrograde of 3
- Maps to 6 pitches, 0-based Retrograde of 2
- 0-based Retrograde of Prime
- Degenerate case: maps to just one pitch
Feeding in prime(n) * x
remaps the pitches, rather than their ordering:
getPitches(function(n) { return prime(n) * 2; });
// => [0, 6, 10, 4, 10, 2, 0, 8, 8, 6, 4, 2]
getPitches(function(n) { return prime(n) * 3; });
// => [0, 9, 3, 0, 9, 3, 6, 6, 0, 3, 6, 9]
getPitches(function(n) { return prime(n) * 4; });
// => [0, 0, 8, 8, 8, 4, 0, 4, 4, 0, 8, 4]
getPitches(function(n) { return prime(n) * 5; });
// => [0, 3, 1, 4, 7, 5, 6, 2, 8, 9, 10, 11]
getPitches(function(n) { return prime(n) * 6; });
// => [0, 6, 6, 0, 6, 6, 0, 0, 0, 6, 0, 6]
getPitches(function(n) { return prime(n) * 7; });
// => [0, 9, 11, 8, 5, 7, 6, 10, 4, 3, 2, 1]
getPitches(function(n) { return prime(n) * 8; });
// => [0, 0, 4, 4, 4, 8, 0, 8, 8, 0, 4, 8]
getPitches(function(n) { return prime(n) * 9; });
// => [0, 3, 9, 0, 3, 9, 6, 6, 0, 9, 6, 3]
getPitches(function(n) { return prime(n) * 10; });
// => [0, 6, 2, 8, 2, 10, 0, 4, 4, 6, 8, 10]
getPitches(function(n) { return prime(n) * 11; });
// => [0, 9, 7, 4, 1, 11, 6, 2, 8, 3, 10, 5]
getPitches(function(n) { return prime(n) * 12; });
// => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Note the pitch-sets that these values of x map to:
- Whole-tone scale
- Diminished 7th
- Augmented triad
- Full chromatic remapping
- Tritone
- Full chromatic remapping, Inversion of 5
- Augmented triad, Inversion of 4
- Diminished 7th, Inversion of 3
- Whole-tone scale, Inversion of 2
- Chromatic scale, Inversion of Prime
- Degenerate case: unison
From these two multiplicative transformations, we have new ways to define Inversion and Retrogression, and also a realm of other possible transformations. We can write something like this:
getPitches(function(n) { return prime(n * 5 + 8) * 7 + 4; });
// => [8, 1, 10, 5, 9, 7, 3, 2, 4, 11, 6, 0]
and know that the material is still explicitly derived from the original series.
These examples are just a few first examples of the generative power of a few simple lines of JavaScript. Next time I want to investigate what happens when you reflect the position/pitch axis, and introduce a way to deal with negative numbers, but this is enough for one evening!