Implementing a Pitch Shifter in SuperCollider

Due to some recent circumstances involving Bit Graves acquiring a norns, I found myself in need of a pitch shifting unit generator for SuperCollider.



A pitch shifter takes an input signal, like the sound of a voice or a synthesizer, and (as you might suspect) shifts the pitch up or down by some given ratio. For example, a ratio of 2 would make the output signal sound an octave higher, and a ratio of 0.5 would produce an output an octave lower.



SuperCollider's PitchShift

Looking around, I noticed that SuperCollider ships with a PitchShift UGen. I gave it a try, only to discover that for some implementation reason, the ratio of this generator is limited to a maximum value of 4, representing two octaves. Unfortunately this is a dealbreaker for Bit Graves. We frequently use pitch shifters to layer harmonics over a base sound, and those harmonics often need to be more than two octaves above the source signal. If you try to put some value higher than 4 into PitchShift, it clamps the value to 4. So this option was a no-go.



STK PitShift

After a short time playing with SuperCollider's PitchShift, I realized that what I really wanted was ChucK's pitch shifter. Prior to using a norns, we ran our patches on a plain old laptop, and we wrote our patches in ChucK. ChucK imports STK PitShift.



Unlike SuperCollider's built in PitchShift, which is granular, PitShift uses two interpolating delay lines whose lengths cycle through the last ~0.1 second of the input signal in order to get a doppler-like shift effect. This UGen sounds pretty weird under certain conditions. Some input signals produce detuned and phased outputs. We love how it sounds.



Crucially, there is no upper bound on the shift ratio in STK PitShift. (Well, that's an exaggeration, but the limit is much higher than 4.)



Aside: A nice person has done the work to import some of STK to SuperCollider. For whatever reason, they only pulled in a subset of STK that doesn't include PitShift.



Implementing STK PitShift in SuperCollider

I ultimately ended up just reading the STK PitShift source code and implementing the same algorithm as a pseudo-UGen in SuperCollider. Here's my delay-based PitShift:



PitShift {
	classvar delayLengthSamp = 5024;
	*ar { arg in, shift;
		var delayLength = delayLengthSamp / Server.default.sampleRate;
		var halfLength = delayLength / 2;
		var sweep = Sweep.ar(
			Impulse.kr(0),
			1.0 - shift
		);
		var env = EnvGen.ar(
			Env.triangle(delayLength),
			Impulse.ar(delayLength.reciprocal)
		);
		var lines = DelayL.ar(
			in,
			maxdelaytime: delayLength,
			delaytime: Wrap.ar(
				[sweep, sweep + halfLength],
				0, delayLength
			),
			mul: [env, 1.0 - env]
		);
                ^lines.sum;
        }
}



The cycling delay length is implemented as a Sweep inside a Wrap. The shift parameter controls the sweep rate. The two delays are a half-window out of phase from one another, and mixed together with a triangular envelope.



Here's how to use it:



{
    PitShift.ar(
        in: SinOsc.ar(),
        shift: XLine.ar(start: 1, end: 8, dur: 10)
    )
}.play;



Lastly, here's an old recording of a track that makes extensive use of STK PitShift. You can hear the input signal (a KORG MS-20) in the left channel, and the processed signal in the right channel.



Thanks for reading! Let me know if you have any suggestions, improvements, or feedback. I just started learning SuperCollider a couple weeks ago, and it seems fairly possible that I'm doing something stupid. Also please let me know if somebody else in the SuperCollider community has already found a better solution for this. I couldn't find anything.

Published by Ben Roth (ben) 5 years ago on Thursday the 30th of May 2019.

To reply you need to sign in.