Phase locked loops

This is a reply to Daniel’s question about “phase locked loops” but somehow I cannot find the original thread.

Hi Daniel,
Very interesting/intriguing question, I started to play with some ideas and I end up with this.

Basically a PLL like the CD4046B has three building blocks:
a phase comparator (the chip above has two different types), a low pass filter and a VCO. Since the LPF and VCO are obvious in sc the trick is in the phase comparator which “compares the phase and frequency of the signal input with the VCO frequency and generates an error voltage proportional to the phase and frequency difference of the input signal and the VCO. The error voltage, Ve(t), is filtered and applied to the control input of the VCO. Vd(t) varies in a direction that reduces the frequency difference between the VCO and signal-input frequency”

The phase comparator type I is described as a “exclusive-OR network that operates analogously to an overdriven balanced mixer”

Since we don’t have such a thing as “BinaryOpUGen(‘XOR’, a, b);” in the scsynth I figured it out that the XOR logic is actually a sum with module 2 like this:

XOR logic is

true xor: true;//false
false xor: false;//false
true xor: false;//true
false xor: true;//true

0==0 xor: (0==0);//false
1==0 xor: (0==1);//false
0==1 xor: (1==1);//true
0==0 xor: (0==1);//true

Equivalent to:


So I made this sketch, you have to oscillators, one of it is getting the frequency through the filtered phase comparator output, the other is the external one.

For the filter, I tried OnePole but of course at high values is slower and we end up basically with ascending glissandi so lag feels more convenient for this application. Playing with the lag time reveals the errors (bubbling) of the phase comparator typical of the PLL.

The mapping of the frequency should have different settings, for example in the a-196 PLL from Doepfer is a switch of three ranges. That could be refined in the code.

The external oscillator could be replaced by an

How does it sound?


Alejandro Olarte





var oscRef, oscExt, output, pc, freqfbk=10;

freqfbk =,0);

oscRef =, 0, 0.5);

oscExt =,1000), 0, 0.5);

pc = (oscRef+oscExt)%2;

pc = pc.floor.lag(\;

//pc =,0.99999);

pc = pc.linexp(0,1,1,10000);








Ndef(\pll).set(\lag, 1);

Has anyone experiences with implementing such in SC ?


Hello Alejandro,

that’s a very inspiring patch, thank you so much for sharing !
For slow frequency changes the adaption works very well, sometimes latching into partials, as can be expected from theory.


Ndef(\pll, {
	var oscRef, oscExt, output, pc, freqsrc, freqfbk=10, freqQ;

	freqfbk =,0);
	freqsrc =, 200);
	freqQ = freqfbk / freqsrc;
	freqQ.poll(label: \freqQ);
	oscRef =, 0, 0.5);
	oscExt =, 0, 0.5);

	pc = (oscRef+oscExt)%2;
	pc = pc.floor.lag(\;

	// pc =,0.99999);

	pc = pc.linexp(0,1,1,10000);;
	oscRef * 0.1

Certainly a lot of things we can play with this model !




i came across this album on bandcamp and got inspired:

are there any further developments/ideas for this Ndef?

Why do you have to change the (kr) blocksize here?

XOR-based PLLs are generally not stable. (For some types of sound production that might be a feature rather than a problem though.)

Well you don’t have to. As the poll shows frequency detection is better with lower blockSize, where minimum feedback delay (LocalIn/LocalOut) is closer to sample duration. You might also decide that bad adaption is nicer and use a higher blockSize. IMO non-stability is the core of such an approach.

1 Like