Fellow ChucKists,<br><br>There was a question on the forum about tracking the pitch of a Theremin. FFT-based techniques may be the most obvious choice for attempting to track a pitch in general but I felt that such a simple signal could be tracked by some modest UGens. Below you'll find a class that counts zero crossings over a given duration, tracking them in real time and outputing a value corresponding to the pitch (in Hz) of the input signal. I did in "mock UGen" form because of the tracking ability of the plain oscillators (see usage example below).<br>
<br>Might be useful in some cases or good for amusement value. Thanks to Frostburn for teaching me the OnPole+Delay trick this is based on; turns out it's good for more then just calculating the average over the last n samples.<br>
<br>Please note; when you see people with a flute next to a modular synth; there is a good reason why they are using a flute and not a more complex signal; this is a very primitive method.<br><br>Yours,<br>Kas.<br><br>//----------------8<-------------------------------------------------<br>
<br><br>//zero-crossing based pitch tracking by Kas.
<br>
//permission granted to copy, redistribute, extend and/or remix.
<br>
//no warranties, no refunds.
<br>
//please mind your speakers and neighbours.
<br>
<br>
PitchTrack p;
<br>
<br>
adc => <a href="http://p.in">p.in</a>;
<br>
p.out => SawOsc s => dac;
<br>
<br>
//make the SawOsc track the PitchTrack's output.
<br>
0 => s.freq;
<br>
3 => s.sync;
<br>
<br>
.10::second => p.avgTime;
<br>
hour=> now;
<br>
<br>
class PitchTrack
<br>
{
<br>
//primitive pitch tracker based on zero crossings.<br>
//counts the zerocrossings over a certain duration (default is one second)
<br>
//suitable for demonstrating the power of UGens
<br>
//suitable for whistling saw-waves
<br>
//not suitable for complex input signals.
<br>
//however, this will continuously and smoothly (attempt to) track a signal, unlike fft
<br>
<br>
Gain in => ZeroX x => FullRect f => OnePole avg => Gain out;
<br>
f => Delay d => avg;
<br>
<br>
//make the averaging filter simply stack up all input values
<br>
//here it is counting how many zero crossings it's seen
<br>
-1 => avg.a1;
<br>
1 => avg.b0;
<br>
<br>
//remove incoming values after a second
<br>
//Thanks to Frostburn for this trick
<br>
5::second => d.max;
<br>
second => d.delay;
<br>
-1 => d.gain;
<br>
<br>
//compensate for tracking two crossings per cycle
<br>
.5 => avg.gain;
<br>
<br>
//========member functions=====================
<br>
<br>
//sets time to average the input freq over.
<br>
//reaction speed v.s. accuracy
<br>
fun dur avgTime( dur length)
<br>
{
<br>
if (length < samp)
<br>
{
<br>
<<<"warning, duration too short, correcting">>>;
<br>
ms => d.delay;
<br>
}
<br>
if (length > d.max())
<br>
{
<br>
<<<"warning, duration too long, correcting">>>;
<br>
d.max() => d.delay;
<br>
}
<br>
else length => d.delay;
<br>
<br>
.5 * (second / d.delay() ) => avg.gain;
<br>
<br>
return d.delay();
<br>
}
<br>
//returns this time
<br>
fun dur avgTime( dur length)
<br>
{
<br>
return d.delay();
<br>
}
<br>
<br>
//pretend we are a UGen
<br>
//note this value will start at 0 and we still need a blackhole to operate
<br>
fun float last()
<br>
{
<br>
return avg.last();
<br>
}
<br>
}