How to compensate for onepole~ delay?
Last week was my first exposure to Pure Data. Yesterday I downloaded Max. I'm a C++ programmer by profession (computer games) with a strong interest in experimental sound synthesis.
For my fist experiment, I'm tinkering with the Karplus-Strong algorithm. I'm sure most of you are familiar with it. It's a tapin~/tapout~ delay with a onepole~ in the feedback loop. You have seen it a thousand times.
I noticed that when I reduce the cutoff frequency of the onepole~, the pitch goes down, and my notes sound flat. This is especially noticeable in the higher registers.
I speculate that the onepole~ introduces an additional delay, that gets longer as the cut off frequency gets lower.
Am I right? If that is the case, I think I could keep the pitch stable by reducing the length of the tapin~/tapout~ to compensate for the delay that is introduced by the onepole~.
But that would require me to compute the delay that is introduced by the onepole~ filter, as a function of the cut off frequency. How do I do that?
-Ron.
the onepole delay would not change-- by definition and design it never changes-- but there would be artifacts to do with tapin/out sub-sample interpoloation algorithm (or lack thereof) and other artifacts to do with the feedback delay limited by the signal vector size (which you can change). if you're using max 6 then using gen to get around this particular problem. A compensatory delay is not going to solve it.
Maybe post an example patch to illustrate the cutoff freq of the onepole affecting pitch-- I suspect it's more like a timbral change affecting the perception of pitch change
T
Ok, I'll try to post an example. (Never done this before... hope this works)
+1 for perception theory
Definitely not perception. I added a 440 Hz reference tone to make sure I'm not imagining it. Check out my demo patch. Reference tone is on the right slider, string sound on the left. When the cut off is at 10 KHz, there is barely any beating. As you move the cut off to 3 kHz, there is a distinctly audible beating between the two frequencies, as the string resonator goes flat.
-Ron.
I can't hear any beating in your example (admittedly listening through laptop speakers) , but have a look at this adaptation of your patch: is there any beating here? Make sure your vector size is 64 or less (for 44100hz sample rate) to get the 440 hz fundamental.
I'd say the drop in pitch you hear is very much perceptual, but there might be something else going on to cause the beating...
Thanks Terry.
Yes, in your version, if (for example) you set the dials at 8k and 4k respectively, I can clearly hear that the two strings are out of tune with each other.
I tried vector size 1, and even sample rate 96k, with the same result.
I think I understand what is going on. Sorry my math skillz are poor, I don't speak the Greek letter lingo, but I can explain in programming terms.
Consider the original KS algorithm. Remember they did not use a filter, but rather they averaged the samples at [length] and [length+1]. A poor man's low pass filter. But averaging two adjacent samples is what I would do to linearly interpolate delay at non-integer positions. In other words, KS were reading the interpolated sample at [length+0.5]. That's half a sample longer than the intended string length. Hardly noticeable when your string is long, but the shorter it gets, the more significant the error becomes.
And I'm sure that onepole~ does something essentially similar. I bet onepole~ has a one or two sample delay inside of it, and combines the input with the delayed samples according to some set of coefficients. That's how a digital filter works, right? Those extra one or two samples of delay in the filter make the string just a smidgen longer.
-Ron.
Yup. Check out the helpfile for onepole~ and biquad~ together. (onepole~ is really just a degenerate biquad~) Onepole~ is recursive with a one-sample delay in the feedback. Onepole~ has two coefficients a0 (undelayed) and b1 (recursive).
From looking at the help file, I noticed a shift in the magnitude between the a0 and b1 coefficients around 3-4k; at that point the absolute magnitude of a0 starts to exceed that of b1. Because the feedback is more pronounced than the input before this point, the delay line is functionally longer (as in it takes several iterations for the input to affect the output substantially), and this probably accounts for what you are experiencing. (in essence, it's not only the length of the delay (1 sample), but also the weighting)
Also, something that could (and I'm not sure that it is the culprit here, but it could...) also affect this is that digital filters introduce phase shifts. (see the onepole~ help patch) In a feedback situation this becomes more pronounced because you have a series of phase shifts from the repetitions.
KS is nice for its simplicity, but definitely has its limitations. It seems like their lowpass scheme might be a simple way of reducing the weighting issues.
I altered your patch again to have both output signals going into the spectroscope, and you can see it's not beating due to frequency difference (where all the harmonics would beat at the same rate), but most likely to do with phase response differences, as the second partial is the one that varies in amplitude mostly.
Thanks Peter. I bet that if I could compute the phase shift at the fundamental frequency (the one I'm trying to play) then I could shorten the delay line by just the right amount so it ends up resonating at the correct frequency.
But that involves math that is above my head. And it's not tremendously important. I'm just playing around.
I managed to make a few nice additions to the KS algorithm. I can now morph from a string-like resonance to a pipe-like resonance.
But I think the more important part of what makes the KS algorithm into a playable instrument, is what you do with the excitation. Picking, plucking, bowing, blowing, bouncing... And how this is hooked up to velocity and midi controllers. That will be my project for next weekend.
Thanks everyone.
-Ron.
For stuff like karplus-strong, you should probably be looking at gen~. In fact, look at /Max6/examples/gen/gen~.karplus-strong.maxpat .
Eventually the vector size of any delay object is going to be more of a pain in the neck than you want to deal with.
mz
Also, be sure to mess around with the impulse that you send in. It can affect the sound drastically. If you "lowpass" the impulse itself...
I'd second what mzed said re: gen~. Also, if you're checking out physical modelling, be sure to look at the old percolate library; it's open-source and has plenty of fun things to mess with.
Strangely, that gen~.karplus-strong.maxpat sample appears to have the same CPU load as my own version: 14% (on an iMac with a 3.4 GHz Intel Core i7, Max sample rate 96k and vector size 1). And my patch does more than the gen~ sample (the one that I posted was stripped way down)
-Ron.
to measure the cpu usage of a gen~ object you need to give it the '@dumpoutlet 1' argument, turn on 'cpumeasure 1' and then poll it with the 'getcpu' message. explained in the helpfile. the cpu usage in the minimixer or the audio status is of all of max. it would be great if the minimixer only computed local cpu usage, indeed. however, if you are seeing a huge cpu use like that for the gen~ example KS patch something is wrong. my cpu use for that patch is very very low.
thanks for a quality thread rpieket.
CPU usage of gen~ == percerption theory #2
@Ron make sure you set the signal vector size back to a reasonable value (64 or more). Gen~ can do feedback loop of just one sample delay inside so you don't have to lower the signal vector size of the entire patcher do obtain high frequencies with the karplus strong.