electro-music.org   Dedicated to experimental electro-acoustic
and electronic music
 
 Front Page  |  Radio  |  Forum  |  Media  |  Playlists  |  Wiki
Forum with support of Syndicator RSS
 FAQFAQ   CalendarCalendar   SearchSearch   MemberlistMemberlist   UsergroupsUsergroups   LinksLinks
 RegisterRegister   ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in  Chat RoomChat Room 
 Forum index » DIY Hardware and Software » ChucK programming language
finding the frequency of audio input
Post new topic   Reply to topic Moderators: Kassen
Page 1 of 2 [36 Posts]
View unread posts
View new posts in the last week
Mark the topic unread :: View previous topic :: View next topic
Goto page: 1, 2 Next
Author Message
andrewjmonks



Joined: Dec 30, 2008
Posts: 8
Location: Concord, MA

PostPosted: Tue Dec 30, 2008 1:17 am    Post subject: finding the frequency of audio input Reply with quote  Mark this post and the followings unread

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
View user's profile Send private message
Inventor
Stream Operator


Joined: Oct 13, 2007
Posts: 6221
Location: near Austin, Tx, USA
Audio files: 270

PostPosted: Tue Dec 30, 2008 2:35 am    Post subject: Reply with quote  Mark this post and the followings unread

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
View user's profile Send private message Send e-mail
andrewjmonks



Joined: Dec 30, 2008
Posts: 8
Location: Concord, MA

PostPosted: Tue Dec 30, 2008 3:19 am    Post subject: Reply with quote  Mark this post and the followings unread

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
View user's profile Send private message
Inventor
Stream Operator


Joined: Oct 13, 2007
Posts: 6221
Location: near Austin, Tx, USA
Audio files: 270

PostPosted: Tue Dec 30, 2008 3:33 am    Post subject: Reply with quote  Mark this post and the followings unread

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
View user's profile Send private message Send e-mail
andrewjmonks



Joined: Dec 30, 2008
Posts: 8
Location: Concord, MA

PostPosted: Tue Dec 30, 2008 3:58 am    Post subject: Reply with quote  Mark this post and the followings unread

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
View user's profile Send private message
andrewjmonks



Joined: Dec 30, 2008
Posts: 8
Location: Concord, MA

PostPosted: Tue Dec 30, 2008 4:26 am    Post subject: Reply with quote  Mark this post and the followings unread

Never mind, I guess that's just more last().
Back to top
View user's profile Send private message
Inventor
Stream Operator


Joined: Oct 13, 2007
Posts: 6221
Location: near Austin, Tx, USA
Audio files: 270

PostPosted: Tue Dec 30, 2008 4:56 am    Post subject: Reply with quote  Mark this post and the followings unread

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
View user's profile Send private message Send e-mail
Kassen
Janitor
Janitor


Joined: Jul 06, 2004
Posts: 7678
Location: The Hague, NL
G2 patch files: 3

PostPosted: Tue Dec 30, 2008 7:16 am    Post subject: Reply with quote  Mark this post and the followings unread

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
View user's profile Send private message Send e-mail Visit poster's website
andrewjmonks



Joined: Dec 30, 2008
Posts: 8
Location: Concord, MA

PostPosted: Tue Dec 30, 2008 12:47 pm    Post subject: Reply with quote  Mark this post and the followings unread

Thanks for all the help.
I think I'll use something like /examples/deep/follower.ck
Back to top
View user's profile Send private message
Kassen
Janitor
Janitor


Joined: Jul 06, 2004
Posts: 7678
Location: The Hague, NL
G2 patch files: 3

PostPosted: Tue Dec 30, 2008 2:06 pm    Post subject: Reply with quote  Mark this post and the followings unread

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
View user's profile Send private message Send e-mail Visit poster's website
Kassen
Janitor
Janitor


Joined: Jul 06, 2004
Posts: 7678
Location: The Hague, NL
G2 patch files: 3

PostPosted: Tue Dec 30, 2008 3:25 pm    Post subject: Reply with quote  Mark this post and the followings unread

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
View user's profile Send private message Send e-mail Visit poster's website
Inventor
Stream Operator


Joined: Oct 13, 2007
Posts: 6221
Location: near Austin, Tx, USA
Audio files: 270

PostPosted: Tue Dec 30, 2008 4:04 pm    Post subject: Reply with quote  Mark this post and the followings unread

Spiffy trick, Kassen, nicely done!
_________________
"Let's make noise for peace." - Kijjaz
Back to top
View user's profile Send private message Send e-mail
Kassen
Janitor
Janitor


Joined: Jul 06, 2004
Posts: 7678
Location: The Hague, NL
G2 patch files: 3

PostPosted: Tue Dec 30, 2008 4:39 pm    Post subject: Reply with quote  Mark this post and the followings unread

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
View user's profile Send private message Send e-mail Visit poster's website
andrewjmonks



Joined: Dec 30, 2008
Posts: 8
Location: Concord, MA

PostPosted: Tue Dec 30, 2008 11:06 pm    Post subject: Reply with quote  Mark this post and the followings unread

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
View user's profile Send private message
Kassen
Janitor
Janitor


Joined: Jul 06, 2004
Posts: 7678
Location: The Hague, NL
G2 patch files: 3

PostPosted: Wed Dec 31, 2008 6:58 am    Post subject: Reply with quote  Mark this post and the followings unread

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
View user's profile Send private message Send e-mail Visit poster's website
DrJustice



Joined: Sep 13, 2004
Posts: 2112
Location: Morokulien
Audio files: 4

PostPosted: Wed Dec 31, 2008 7:35 am    Post subject: Reply with quote  Mark this post and the followings unread

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 Laughing) 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
View user's profile Send private message Visit poster's website
Kassen
Janitor
Janitor


Joined: Jul 06, 2004
Posts: 7678
Location: The Hague, NL
G2 patch files: 3

PostPosted: Wed Dec 31, 2008 8:29 am    Post subject: Reply with quote  Mark this post and the followings unread

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
View user's profile Send private message Send e-mail Visit poster's website
Kassen
Janitor
Janitor


Joined: Jul 06, 2004
Posts: 7678
Location: The Hague, NL
G2 patch files: 3

PostPosted: Thu Jan 01, 2009 7:34 am    Post subject: Reply with quote  Mark this post and the followings unread

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
View user's profile Send private message Send e-mail Visit poster's website
DrJustice



Joined: Sep 13, 2004
Posts: 2112
Location: Morokulien
Audio files: 4

PostPosted: Thu Jan 01, 2009 9:06 am    Post subject: Reply with quote  Mark this post and the followings unread

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
View user's profile Send private message Visit poster's website
dewdrop_world



Joined: Aug 28, 2006
Posts: 858
Location: Guangzhou, China
Audio files: 4

PostPosted: Thu Jan 01, 2009 9:10 am    Post subject: Reply with quote  Mark this post and the followings unread

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
View user's profile Send private message Visit poster's website AIM Address
Kassen
Janitor
Janitor


Joined: Jul 06, 2004
Posts: 7678
Location: The Hague, NL
G2 patch files: 3

PostPosted: Thu Jan 01, 2009 11:40 am    Post subject: Reply with quote  Mark this post and the followings unread

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
View user's profile Send private message Send e-mail Visit poster's website
DrJustice



Joined: Sep 13, 2004
Posts: 2112
Location: Morokulien
Audio files: 4

PostPosted: Thu Jan 01, 2009 2:28 pm    Post subject: Reply with quote  Mark this post and the followings unread

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
View user's profile Send private message Visit poster's website
DrJustice



Joined: Sep 13, 2004
Posts: 2112
Location: Morokulien
Audio files: 4

PostPosted: Fri Jan 02, 2009 11:11 am    Post subject: Reply with quote  Mark this post and the followings unread

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
View user's profile Send private message Visit poster's website
Kassen
Janitor
Janitor


Joined: Jul 06, 2004
Posts: 7678
Location: The Hague, NL
G2 patch files: 3

PostPosted: Fri Jan 02, 2009 12:09 pm    Post subject: Reply with quote  Mark this post and the followings unread

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
View user's profile Send private message Send e-mail Visit poster's website
radarsat1



Joined: Mar 14, 2008
Posts: 85
Location: Montreal

PostPosted: Fri Jan 02, 2009 12:43 pm    Post subject: Reply with quote  Mark this post and the followings unread

someone needs to port Puckette's fiddle~ to ChucK. ;-)
Back to top
View user's profile Send private message
Display posts from previous:   
Post new topic   Reply to topic Moderators: Kassen
Page 1 of 2 [36 Posts]
View unread posts
View new posts in the last week
Goto page: 1, 2 Next
Mark the topic unread :: View previous topic :: View next topic
 Forum index » DIY Hardware and Software » ChucK programming language
Jump to:  

You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum
You cannot attach files in this forum
You can download files in this forum
Log in
Username:     Password:      Log me on automatically each visit    


Forum with support of Syndicator RSS
Powered by phpBB © 2001, 2005 phpBB Group
Copyright © 2003 through 2026 by electro-music.org