| Author |
Message |
andrewjmonks

Joined: Dec 30, 2008 Posts: 8 Location: Concord, MA
|
Posted: Tue Dec 30, 2008 1:17 am Post subject:
finding the frequency of audio input |
 |
|
I'm trying to use my theremin as an interface for ChucK. To do this, I'd need to be able to find the frequency of the adc's input. Because it generates a simple sine wave, it should be easy(er) to accurately find the pitch of whatever I'm playing.
However, one area of ChucK that I really haven't explored is manipulating audio input. I feel like there should be a way to do this with UAnae, but I've been digging through the UAna reference, but I haven't had any luck.
Where should I look now? |
|
|
Back to top
|
|
 |
Inventor
Stream Operator

Joined: Oct 13, 2007 Posts: 6221 Location: near Austin, Tx, USA
Audio files: 270
|
Posted: Tue Dec 30, 2008 2:35 am Post subject:
|
 |
|
Hi and welcome to the forum. You can do this with an FFT or (preferably) with a ZeroX.
For the FFT, you figure out what the frequency of each bin is, find the largest frequency with a for loop, and multiply it's index by the bin frequency. The problem with the FFT approach is frequency accuracy as you can probably only do FFTs of 1024 or 2048 bins before your computer gets too slow to track in real time. So that's all the frequency resolution you get.
For the ZeroX you set up a while (true) loop spinning on samp => now and count the sample steps between positive ZeroX pulses using the .last() member function. Then you calculate the frequency with sample accuracy. It's also cpu intensive to spin on samp => now, but you're not doing much in the loop so it's OK.
Hope that helps. _________________ "Let's make noise for peace." - Kijjaz |
|
|
Back to top
|
|
 |
andrewjmonks

Joined: Dec 30, 2008 Posts: 8 Location: Concord, MA
|
Posted: Tue Dec 30, 2008 3:19 am Post subject:
|
 |
|
| Quote: | | For the ZeroX you set up a while (true) loop spinning on samp => now and count the sample steps between positive ZeroX pulses using the .last() member function. Then you calculate the frequency with sample accuracy. It's also cpu intensive to spin on samp => now, but you're not doing much in the loop so it's OK. |
I can't quite follow all of what you said, but I got this far:
| Code: | SinOsc s => ZeroX z => blackhole;
SinOsc t => dac;
440 => s.freq;
int count;
while(true){
if(/*ZeroX is happening now*/){
count => t.freq;
0 => count;
}
1::samp => now;
count++;
} |
I think that if I could figure out how to tell when ZeroX was pulsing, this would generate a sine wave at 440 hertz, find the frequency of that sine wave (440 hertz, of course) and play a new sine wave at that frequency.
I'm not following your example exactly because I don't understand what last() does, or how I could use it. I think I'm very close either way, though.
Sorry if this is a simple question, I'm new at this. |
|
|
Back to top
|
|
 |
Inventor
Stream Operator

Joined: Oct 13, 2007 Posts: 6221 Location: near Austin, Tx, USA
Audio files: 270
|
Posted: Tue Dec 30, 2008 3:33 am Post subject:
|
 |
|
Here, I made your code work:
| Code: | SinOsc s => ZeroX z => blackhole;
SinOsc t => dac;
440 => s.freq;
int count;
while(true){
if(z.last() > 0.5){
second / (count * samp) => t.freq;
0 => count;
}
1::samp => now;
count++;
} |
z.last() returns the last sample value of z, and you can see how the frequency calculation works, I hope. You did great for a new ChucKist, only needed a little extra information. Commendable! _________________ "Let's make noise for peace." - Kijjaz |
|
|
Back to top
|
|
 |
andrewjmonks

Joined: Dec 30, 2008 Posts: 8 Location: Concord, MA
|
Posted: Tue Dec 30, 2008 3:58 am Post subject:
|
 |
|
Beautiful, thank you!
One more question, though.
It works great as long as it's getting a good signal, but when it isn't, it just gives out garbled nonsense. I think if I added a simple gate so that it only tried to analyze signal that was above some level it would be perfect.
I think I would be able to code it myself if I knew of a function to find out how loud something is. What's a good way to do that? |
|
|
Back to top
|
|
 |
andrewjmonks

Joined: Dec 30, 2008 Posts: 8 Location: Concord, MA
|
Posted: Tue Dec 30, 2008 4:26 am Post subject:
|
 |
|
| Never mind, I guess that's just more last(). |
|
|
Back to top
|
|
 |
Inventor
Stream Operator

Joined: Oct 13, 2007 Posts: 6221 Location: near Austin, Tx, USA
Audio files: 270
|
Posted: Tue Dec 30, 2008 4:56 am Post subject:
|
 |
|
| andrewjmonks wrote: | Beautiful, thank you!
One more question, though.
It works great as long as it's getting a good signal, but when it isn't, it just gives out garbled nonsense. I think if I added a simple gate so that it only tried to analyze signal that was above some level it would be perfect.
I think I would be able to code it myself if I knew of a function to find out how loud something is. What's a good way to do that? |
Send the signal into a FullRect, then an LPF and set the frequency of the LPF to the lowest frequency of interest divided by four. Then use the .last() on the LPF. _________________ "Let's make noise for peace." - Kijjaz |
|
|
Back to top
|
|
 |
Kassen
Janitor


Joined: Jul 06, 2004 Posts: 7678 Location: The Hague, NL
G2 patch files: 3
|
Posted: Tue Dec 30, 2008 7:16 am Post subject:
|
 |
|
If you just want a noise gate Dyno is likely the most simple option; it comes with a noise gate preset. There is a example for a amplitude tracker in /examples/deep that may help as well, it works slightly different from Inventor's.
Another way to prevent garbage from FFT-based pitch tracking might be to simply ignore the signal if the highest value in your FFT array is below a certain value? That would be quite simple indeed with very little computational cost. _________________ Kassen |
|
|
Back to top
|
|
 |
andrewjmonks

Joined: Dec 30, 2008 Posts: 8 Location: Concord, MA
|
Posted: Tue Dec 30, 2008 12:47 pm Post subject:
|
 |
|
Thanks for all the help.
I think I'll use something like /examples/deep/follower.ck |
|
|
Back to top
|
|
 |
Kassen
Janitor


Joined: Jul 06, 2004 Posts: 7678 Location: The Hague, NL
G2 patch files: 3
|
Posted: Tue Dec 30, 2008 2:06 pm Post subject:
|
 |
|
I think, BTW, that we could do a zero-crossing-detection-based pitch follower in a more easy way as well. I'll report back with my findings. _________________ Kassen |
|
|
Back to top
|
|
 |
Kassen
Janitor


Joined: Jul 06, 2004 Posts: 7678 Location: The Hague, NL
G2 patch files: 3
|
Posted: Tue Dec 30, 2008 3:25 pm Post subject:
|
 |
|
Here we go. Only using UGens to track pitches of simple signals. Not at all suitable for complex signals but it'll track whistling. Will likely be suitable for a Theremin's simple signal but clearly you'd need to add volume tracking as well or there will be garbage (due to adc noise) when the Theremin is silent.
| Code: |
//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 second)
//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( )
{
return d.delay();
}
//pretend we are a UGen
//note this value will start at 0 and we still need a blackhole to operate
fun float last()
{
return avg.last();
}
}
|
edit; I just noticed a typo in the overloading of the .avgTime() function and corrected it. The "get" version of course shouldn't take a argument. If you copied this trick to your "bag of tricks" you may want to use this updated version. _________________ Kassen Last edited by Kassen on Thu Jan 01, 2009 7:33 am; edited 1 time in total |
|
|
Back to top
|
|
 |
Inventor
Stream Operator

Joined: Oct 13, 2007 Posts: 6221 Location: near Austin, Tx, USA
Audio files: 270
|
Posted: Tue Dec 30, 2008 4:04 pm Post subject:
|
 |
|
Spiffy trick, Kassen, nicely done! _________________ "Let's make noise for peace." - Kijjaz |
|
|
Back to top
|
|
 |
Kassen
Janitor


Joined: Jul 06, 2004 Posts: 7678 Location: The Hague, NL
G2 patch files: 3
|
Posted: Tue Dec 30, 2008 4:39 pm Post subject:
|
 |
|
Thanks! I like how many of the UGens are very basic but you can join them up to do quite advanced things.
Here I added comments and functions to make it as accessible as I could but when it comes down to it this is very little code for some quite advanced functionality. Basically 4 UGens and a few settings will do pitch tracking; that's quite nice.
I wonder if it's good enough for Andrew's problem. the Theremin should have a very simple signal but I'm not 100% sure exactly *how* simple it is. It does lag a bit behind the input but it's fundamentally impossible to track pitch without lagging at least a bit and at least this is smooth. _________________ Kassen |
|
|
Back to top
|
|
 |
andrewjmonks

Joined: Dec 30, 2008 Posts: 8 Location: Concord, MA
|
Posted: Tue Dec 30, 2008 11:06 pm Post subject:
|
 |
|
Wow, Kassen. That works really well.
I never would have thought to find the average that way. I'll definitely play with that some more.
I ended up using ftom() for a bit of pitch correction. I found that it made it sound much less noisy. Also, it sounds cool:
| Code: |
//set up the pitch tracker
adc => ZeroX z => blackhole;
SinOsc t => JCRev reverb => dac;
.1 => reverb.mix;
//and the gate
adc => Gain g => OnePole p => blackhole;
adc => g;
3 => g.op;
0.99 => p.pole;
0 => float frequency;
int count;
while(true){
//you might have to change this threshhold
if(p.last() > 0.00001){
if(z.last() > 0.5){
(Std.ftom(second / (count * samp)) - 12) => frequency;
Std.mtof((frequency - (frequency + .5) % 3/* change this number to change the pitch correction */)) => t.freq;
0 => count;
}
}
else{
0 => t.freq;
}
1::samp => now;
count++;
} |
Thanks everybody for your help. |
|
|
Back to top
|
|
 |
Kassen
Janitor


Joined: Jul 06, 2004 Posts: 7678 Location: The Hague, NL
G2 patch files: 3
|
Posted: Wed Dec 31, 2008 6:58 am Post subject:
|
 |
|
Great!
I tend to try to use UGens instead of loops running at a 1::samp rate. In most cases that leads to CPU savings but of course you can't always do that. For example; you are using a modulo here and I don't know how to do that in UGens. I wonder how far simple additions (using a "Step") and multiplications (using a "Gain" set to multiply) would get you here but it's not completely clear to me what sort of correction you are after here.
There's yet another way to do pitch tracking, BTW, that is called a "phase locked loop". That might also be suitable but I will have to read up on it again and see whether that can be done efficiently. If not it might be worth considering trying to add that method as a UGen to our "blob" project for home-made UGens. _________________ Kassen |
|
|
Back to top
|
|
 |
DrJustice

Joined: Sep 13, 2004 Posts: 2112 Location: Morokulien
Audio files: 4
|
Posted: Wed Dec 31, 2008 7:35 am Post subject:
|
 |
|
I'm not a ChucK user, just a lurker, so this may be irrelevant, but:
If Chuck (sorry can't be bothered to type that last haxxor'ed K, even though this paranthesis was a lot more typing ) has a complex FFT, then consecutive FFTs can yield exact partial frequencies, i.e. between the bin frequencies, by unwrapping the phases.
DJ
-- |
|
|
Back to top
|
|
 |
Kassen
Janitor


Joined: Jul 06, 2004 Posts: 7678 Location: The Hague, NL
G2 patch files: 3
|
Posted: Wed Dec 31, 2008 8:29 am Post subject:
|
 |
|
Yes, we do. In fact, chuck (see; it's no big deal ;¬) ) applies it's "strong timing" to the FFT control, meaning you can pull a frame at any moment you want, this need not be a steady rate either, in fact the rate can depend on the results of previous analysis outcomes without a lot of work. Conceivably you could take one frame per sample though obviously that won't run in realtime any time soon. I seem to remember that with some undocumented trickery you can do custom window shapes as well.
So yes; we can, but I'm not quite sure what you mean by "unwrapping the phases". We do get the phase; we get a amplitude and phase as a series of complex numbers in a array.
I think that means we are good to go and I don't think there is anything like that in the examples. If you can explain the technique to me I'll try porting it and we'll see if we can get it in the official examples as that sounds quite useful.
In general I think it's good to build up a bit of a library of methods of pitch detection/tracking as most forms of pitch detection tend to suck in general. There is no guarantee at all that any easily detected frequency in a signal will also be the perceived "pitch" according to the human ear. I feel the "trick" is to line up a suitable method with the type of material you wish to treat so a larger toolbox will definitely help a lot there. _________________ Kassen |
|
|
Back to top
|
|
 |
Kassen
Janitor


Joined: Jul 06, 2004 Posts: 7678 Location: The Hague, NL
G2 patch files: 3
|
Posted: Thu Jan 01, 2009 7:34 am Post subject:
|
 |
|
Please note that I made a small edit to my example above. I hope this didn't confuse or bother anyone. _________________ Kassen |
|
|
Back to top
|
|
 |
DrJustice

Joined: Sep 13, 2004 Posts: 2112 Location: Morokulien
Audio files: 4
|
Posted: Thu Jan 01, 2009 9:06 am Post subject:
|
 |
|
Kassen, the phase unwrapping is what a phase vocoder performs to track an instantaneous frequency per bin.
The principle is simple: you look at the same bin in two consecutive FFTs, and if there is a phase difference you know that the frequency did not fit precisely in the bin. If the phase has advanced the frequency is higher than the bin frequency and vice versa.
In practise you convert the complex spectrum (or just the one bin we are looking at) to a phase and magnitude spectrum. Then to find the phase difference, you just subtract the phase from the previous FFT frame (store the phase each time around so you can pick up the previous one). Then you unwrap the phase difference so that it is in the range -PI to PI. The unwrapping part here is just subtracting or adding PI/2 from/to the phase difference until you are within -PI to PI. Now you can get the frequency with something like:
freq = phasediff * factor + bin_no * fundamental
where:
factor = sample_rate / (2 * PI)
fundamental = the lowest bin frequency
This is the simplest method, there are more esoteric methods out there too. If you want I can dig up and mail you some phase vocoder C sources, or you can search the net for "FFT phase unwrapping" and "phase vocoder". Also have a look at this for a few more possibilities (including the above method).
DJ
-- |
|
|
Back to top
|
|
 |
dewdrop_world

Joined: Aug 28, 2006 Posts: 858 Location: Guangzhou, China
Audio files: 4
|
Posted: Thu Jan 01, 2009 9:10 am Post subject:
|
 |
|
In supercollider-land, I've gotten the best pitch tracking results from Nick Collins' Tartini ugen, part of the sc3-plugins sourceforge project.
| from helpfile wrote: | In technical terms, this UGen calculates a modified autocorrelation function following the method used in the Tartini open source (GNU GPL) pitch following software (http://miracle.otago.ac.nz/postgrads/tartini/)
The algorithm is based on the following paper:
Philip McLeod and Geoff Wyvill (2005) "A Smarter Way to Find Pitch." ICMC Proceedings; 138-141. |
On the website they even announced the SC port of the algorithm, so there's reason to believe they'd be happy with a chuck port also.
James _________________ ddw online: http://www.dewdrop-world.net
sc3 online: http://supercollider.sourceforge.net |
|
|
Back to top
|
|
 |
Kassen
Janitor


Joined: Jul 06, 2004 Posts: 7678 Location: The Hague, NL
G2 patch files: 3
|
Posted: Thu Jan 01, 2009 11:40 am Post subject:
|
 |
|
DrJ; I think I get it. Do I understand correctly that this is assuming that the pitch we are trying to track will never be a "misfit" for the bin by more then a cycle? I'm not sure I understand why that is a safe assumption (but then there is a lot of stuff in the details of the FFT that are arcane to me :¬) ).
Also; in a practical scenario we'll probably have overlapping frames, say with 50% overlap, in that case we can simply double the phase difference, then perform the same math, right?
James, thanks, that looks very interesting. A quick peek at the source told me that porting that is completely beyond me though. I'll start with something a little easier as a first go at new UGens but it *does* sound like a very nice thing to have. Maybe the current interest in MIR will make Ge or Rebecca have a go at it, based on Nick's work (looking at that source I was feeling it would be really nice if somebody else would've already pulled all the actual analysis bits out of there....).
I have to pick my battles and right now I'm a little more interested in more advanced analysis of controller signals then of audio signals. _________________ Kassen |
|
|
Back to top
|
|
 |
DrJustice

Joined: Sep 13, 2004 Posts: 2112 Location: Morokulien
Audio files: 4
|
Posted: Thu Jan 01, 2009 2:28 pm Post subject:
|
 |
|
| Kassen wrote: | DrJ; I think I get it. Do I understand correctly that this is assuming that the pitch we are trying to track will never be a "misfit" for the bin by more then a cycle?
|
Almost; actually we will never cross over into or past the neighbouring bins since the FFT result is restricted to that to begin with. I.e. if the frequency was that much different from it's bin, it will have landed in another bin as a function of the FFT (so that never happens then).
| Quote: | Also; in a practical scenario we'll probably have overlapping frames, say with 50% overlap, in that case we can simply double the phase difference, then perform the same math, right?
|
Right.
Being a lousy teacher, a bit worn from new years eve and rusty on the details of the subject, I'll just conveniently quote from section 14 in that previous linked to document which factors in the overlap. The maths is arranged a bit differently, but it's the same thing going on. The overlap, or hop size, is represented by 'offset' below.
| Code: | 2 FFT's + Phase Vocoder analysis for sequential or overlapping frames.
This method uses phase information between two FFT's with different
offset (disjoint or overlapping) apertures into the data, and
assuming that the same peak bin appears in both FFT frames.
i = nearest FFT bin# to peak, (initial f estimate = (i * sr / n))
ph0 = atan2( imag(i), real(i) )
ph1 = phase in second FFT frame or aperture
p = ( ph0 + i * 2 * pi * offset / n ) - ph1
Unwrap the phase offset p to between -pi and pi
The phase vocoder corrected frequency estimate is then:
f = (i * sr / n) - (p * sr / (2 * pi * offset))
Try a von Hann or Blackman-Nuttall window for better accuracy.
(equation corrected 2008Jun19 by rhn)
Note that a phase vocoder can be considered a fancy method of
measuring zero crossings, but using the phase of the correlation
against a sinusoid to get a better statistical estimate of where
multiple zero crossings of the frequency should have been located.
Note that you can interpolate phases between FFT bins by using
something like FFTShift to (re)reference the phases (real = even,
imag = odd) to the center of the FFT aperture.
(Note #2: Phase Vocoder accuracy is highest if the frame offset
is close to a multiple of a full period of the frequency being
estimated. This is because the shape of the "spectral leakage"
is slightly different for odd/even sinusoids, especially for
relatively low frequency sinusoids. 2008-Feb-18 )
|
DJ
-- |
|
|
Back to top
|
|
 |
DrJustice

Joined: Sep 13, 2004 Posts: 2112 Location: Morokulien
Audio files: 4
|
Posted: Fri Jan 02, 2009 11:11 am Post subject:
|
 |
|
Kassen, in the first example I think you can do :
factor = sample_rate / ((hop_size / n) * 2 * PI)
to factor in the overlap. Not tested though - I may be wrong!
DJ
-- |
|
|
Back to top
|
|
 |
Kassen
Janitor


Joined: Jul 06, 2004 Posts: 7678 Location: The Hague, NL
G2 patch files: 3
|
Posted: Fri Jan 02, 2009 12:09 pm Post subject:
|
 |
|
Check.
I don't think it's that hard. I mean; if the phase differs by X over half a frame it should differ 2·X over a whole frame.
That's not the side of math that I'm scared of; with a system like this we can easily generate tuning signals at a known frequency to verify our attempt and re-trying means re-compiling which is a hot-key and will be done in the blink of a eye. It's when the Greek symbols start flying that I hide below the bed.
One of my friends is a professional mathematician and he'd likely frown at that sort of thing but then I can just do this in secret with the curtains closed and the door locked :¬).
Thanks, I'll give this a go over the weekend. _________________ Kassen |
|
|
Back to top
|
|
 |
radarsat1

Joined: Mar 14, 2008 Posts: 85 Location: Montreal
|
Posted: Fri Jan 02, 2009 12:43 pm Post subject:
|
 |
|
| someone needs to port Puckette's fiddle~ to ChucK. ;-) |
|
|
Back to top
|
|
 |
|