Exercise: Stockhausen Studie II Non-Octave Scale in SuperCollider

Posted on Posted in Tutorials

After my recent post about using custom just intonation scales with Patterns in SuperCollider, I was asked how one could use a scale similar to the one used by Karlheinz Stockhausen in his work Studie II, citing the description here:

For the pitches, Stockhausen built a scale in which the interval between successive steps consists of the frequency proportion of the twenty fifth root of 5 —in other words, the interval of 5:1 (two octaves plus a just major third) is divided into 25 equal parts. This differs from the traditional tempered tuning system, in which an octave consists of twelve segments, the interval between two adjacent steps being therefore defined by the ratio twelfth root of 2. The intervallic unit is a “large semitone”, about 10% larger than the semitone of the equal-tempered twelve-tone system. Beginning at 100 Hz, this scale reaches to ca. 17,200 Hz, with a total of 81 equally spaced pitches. Because of the chosen basic interval, no octave duplications can occur (Stockhausen 1964, 37). The highest pitch, 17,200 Hz, is near the upper limit of human hearing, and occurs only in a single tone mixture, as the uppermost of its five pitches (Toop 2005, 6).

The Simple Solution

This does not quite lend itself to the same Scale approach as last time since, as the description clearly states, there are no octave repetitions. In this case it makes the most sense to just create the scale as an array or frequencies. Here’s some example code where we randomly walk around the Studie II scale:


(
Pbind(
\freq, Pwalk( // random walk around our scale
// make our non-octave scale
Array.geom( // since we are dealing with frequency, we use geom rather than series
81, // 81 steps
100, // starting from 100Hz
5.pow(1/25) // the growth rate (step size) is the 25th root of 5
),
Pwrand([-2, -1, 0, 1, 2], [0.05, 0.1, 0.15, 1, 0.1].normalizeSum, inf), // random stepping
Pseq([1, -1], inf), // reverse direction at boundaries
40 // start in the middle of the scale
),
\dur, 0.125 // each note is a 1/16th
).play
)

Most of the Pattern code here is straight from the Pwalk help file so I won’t talk about that too much. The main thing here is how we make the scale. In reading the description, we can see that Stockhausen started at 100Hz, and each of the 81 steps is higher by the 25th root of 5. SuperCollider’s Array.geom method is perfect for quickly giving us this:

Array.geom(81,100,5.pow(1/25))

That should be pretty straightforward. Here we say that we want 81 steps and that we will start from 100 (Hz). This much is exactly like Array.series if you are familiar with that. The only difference is that series adds the growth amount each step, whereas geom multiplies the last step by the growth amount to get the next step. Since we are working with frequency and not midi notes, we must multiply. 5.pow(1/25) is how we calculate the 25th root of 5.

A Slightly Better Solution

Now, this approach is fine and all but suffers from the same limitation I talked about in the last post: namely that you can’t use it like a true scale and pass in degrees. As I said before, it doesn’t totally make sense to think about this as a traditional scale since it has no octave equivalent notes, but it would be rather nice to be able to pass in indexes to it. This would allow us to get the “tone mixtures” that Stockhausen uses by “degree” rather than frequency. Here’s an example of just walking up the scale:


(
Pbind(
\root, 7 + (100.cpsmidi - 100.cpsmidi.round), // adjust the root to be 100Hz (g + 35 cents)
\octave, 3, // set the octave to the correct g (3)
\scale, (Array.geom(81,100,5.pow(1/25)).cpsmidi - 100.cpsmidi), // adjusted scale in midi numbers
\degree, Pseq((0..80),inf), // walk up the scale
\dur, 0.125 // each note is a 1/16th
).play
)

There are a few new things going on here due to how Patterns handle scale degrees. \root specifies what pitch class (midi number between 0 and 11) will be used for \degree 0. Our scale starts from 100Hz with is midi number 43.349957715001 or G3 + 35 cents. Therefore we set the root to 7 (G) and add the cents to it with (100.cpsmidi - 100.cpsmidi.round) just to be as exact as possible. Next we set \octave to 3 to make the root G3 (5 is the default). At this point, using a degree of 0 will give us 100Hz.

How we use this with \scale is slightly different as well. Rather than using a combination of Scale and Tuning like last time, we will just give the pattern our array that we talked about earlier. However, the array from before was in Hz and scales need to be in relative midi numbers. This is easily handled with (Array.geom(81,100,5.pow(1/25)).cpsmidi - 100.cpsmidi). We simple convert our array to midi and subtract our root note from it. IMPORTANT: this must be done with both the array and root in midi scale, not frequency.

Final Thoughts

I don’t do much with with non-octave or equal division of the anything scales myself so this may not be the cleanest approach. I really just wanted to try my hand at it. I know that Tuning has a way of handling “stretched” tunings like this somehow but I haven’t quite figured it out at the moment. With this approach you should be able to pretty easily drop in your own scale array and get crackin’ pretty quickly, keeping in mind that it’s your responsibility to know if and when your scale recycles. Hope this helps in some way.