Re: very simple phase-locked loop pitch detection in C

Dave Long <[email protected]>
Newsgroups gmane.culture.people.kragen.discuss
Message-ID <[email protected]>
>     /* A PLL in one line of C. arecord | ./tinypll | aplay */
>     main(a,b){for(;;)putchar(b+=16+(a+=(b&256?1:-1)*getchar()-a/ 
> 512)/1024);}
>
> However, it took me most of two days to write that one line of  
> text, and I
> still don't fully understand it.


after writing some python (vide infra), here's my interpretation of  
that line:
	b is the local oscillator, which produces a triangle wave ramping  
between (0-255)
	a is the feedback control
	the +(b&256?1:-1) term chops the input[0,1] from getchar(), but at  
only half of b's frequency (hence the doubled output)
	the -a/512 term produces a "leaky integrator" to low-pass the feedback

-Dave

[0] this is a Horowitz & Hill type I (dot product) phase comparison;  
the python below uses an edge-based lead-lag[2] (type II) phase  
comparison.  H&H think that the problem most people have with PLL's  
is attempting to "cut & try" as we have both done, instead of  
calculating the feedback loop properly; they point out that this is  
somewhat trickier than usual because one measures phase but controls  
frequency, hence the feedback loop is already lagging by at least a  
quarter-wave.
[1] cf lock-in amplification, a related hack.  Could dreaming be an  
epiphenomenon of an evolved "lock-in amplification" for learning (in  
which the brain's inputs are chopped between "reality" while awake  
and "dreams" while asleep) in order to cope with the fact that the  
SNR of reality often leaves much to be desired?
[2] presumably one could make simple adjustments to "zeroph" to deal  
with noisier (read "real") input.  cf o-scope trigger parameters

:: :: ::

import math

# wav: produce an "analog" sampled waveform at a given "frequency"
# zeroph: "digitization": true at positive zero-crossings (flag cycle  
starts)
cycle   = lambda a,b: math.sin(a*math.pi/64*b)
wav     = lambda f:   [cycle(f,x) for x in range(1024)]
zeroph  = lambda w:   [(i<0 and j>0) for i,j in zip(w[:-1],w[1:])]

# cdelta: -ve in lead (0-1), +ve in lag (1-0), 0 when matched (0-0)  
or (1-1)
# cprime: accumulate signal for each sample that lag or lead is present
# compare: Horowitz & Hill type II phase detector (sum all lags and/ 
or leads)
cdelta  = lambda (i,j):   j-i
cprime  = lambda ds,ll:   ds+[ds[-1] + cdelta(ll)]
compare = lambda ref,sig: sum(reduce(cprime,zip(zeroph(ref),zeroph 
(sig)),[0]))

# odelta: calculate the adjustment by comparing lead/lag of cycle starts
# oprime: scan-update the local oscillator value
odelta  = lambda osc, sig: compare(wav(osc),wav(sig))/4096.0
oprime  = lambda os, sig:  os+[os[-1] + odelta(os[-1],sig)]
# sig: the signal to track, varying in "frequency" between 2 and 22
# osc: the local oscillator, initial "frequency" is 100
sig     = [12 - 10*cycle(4,r) for r in range(40)]
osc     = reduce(oprime, sig, [100])[1:]

table = lambda ls: "\n".join(ls)

print table(["%s\t%s\t%s" % ("signal freq","osc freq","ratio")] +
             ["%f\t%f\t%f" % (s,o,o/s) for o,s in zip(osc,sig)])

-- 
To unsubscribe: http://lists.canonical.org/mailman/listinfo/kragen-discuss
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.