[chuck-users] UGen based Pitch tracking demo

Kassen signal.automatique at gmail.com
Tue Dec 30 17:43:41 EST 2008

Fellow ChucKists,

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).

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

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.



//zero-crossing based pitch tracking by Kas.
//permission granted to copy, redistribute, extend and/or remix.
//no warranties, no refunds.
//please mind your speakers and neighbours.

PitchTrack p;

adc => p.in;
p.out => SawOsc s => dac;

//make the SawOsc track the PitchTrack's output.
0 => s.freq;
3 => s.sync;

.10::second => p.avgTime;
hour=> now;

class PitchTrack
   //primitive pitch tracker based on zero crossings.
   //counts the zerocrossings over a certain duration (default is one
   //suitable for demonstrating the power of UGens
   //suitable for whistling saw-waves
   //not suitable for complex input signals.
   //however, this will continuously and smoothly (attempt to) track a
signal, unlike fft

   Gain in => ZeroX x => FullRect f => OnePole avg => Gain out;
   f => Delay d => avg;

   //make the averaging filter simply stack up all input values
   //here it is counting how many zero crossings it's seen
   -1 => avg.a1;
   1 => avg.b0;

   //remove incoming values after a second
   //Thanks to Frostburn for this trick
   5::second => d.max;
   second => d.delay;
   -1 => d.gain;

   //compensate for tracking two crossings per cycle
   .5 => avg.gain;

   //========member functions=====================

   //sets time to average the input freq over.
   //reaction speed v.s. accuracy
   fun dur avgTime( dur length)
      if (length < samp)
         <<<"warning, duration too short, correcting">>>;
         ms => d.delay;
      if (length > d.max())
         <<<"warning, duration too long, correcting">>>;
         d.max() => d.delay;
      else length => d.delay;

      .5 * (second / d.delay() ) => avg.gain;

      return d.delay();
   //returns this time
   fun dur avgTime( dur length)
      return d.delay();

   //pretend we are a UGen
   //note this value will start at 0 and we still need a blackhole to
   fun float last()
      return avg.last();
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.cs.princeton.edu/pipermail/chuck-users/attachments/20081230/d3f871cf/attachment.html>

More information about the chuck-users mailing list