From the original SynthMaker forum (2004–2014), with its members’ names and dates.
oddson ·
I've been trying to understand the 'mechanics' of the Karplus Strong algo using Jorge's integer and Sambean's fractional delay versions.
I've put them together in a single OSM with an output graph and have discovered some mysterious behaviour.
Would anyone care to comment on the following:
1) Holding down any note with the feedback value less than 1 and waiting for the output to settle will show discontinuities in the output signal.
2) With the fractional delay and feedback to unity, all the notes except G (the one you get the the letter 'B' on the PC keyboard) settle towards less complex forms. (at 44,100 at least)
a) why doesn't G?
b) why does this differ from the integer so dramatically?
3) With low feedback levels the signal dies to flatline, but not at zero but either + or - 1.
Also, is the smooth function the same (apart from the fraction) as the standard KS as illustrated:
http://ccrma.stanford.edu/~jos/SimpleStrings/Karplus_Strong_Algorithm.html
Seems to me it's not.
Any help in understanding this would be most appreciated. 🙂
stefan ·
1) This is just a redraw error, it's not what actually happens to the sound as you can see if you record it in an audio editor or by just listening to it. It settles as a sine.
2,2a) All notes settle as sine for me, they just take different time in doing so
2b) due to the huge interpolation noise that occurs in the delay line
3) A highpass filter in the feedback path is probably a good idea since a section of a noise signal most probably won't have an average of zero
Usually you don't want to amplify by 1/2, but with a tweakable setting. That's what that simple lowpass filter code does for you, and is often called "dampening" when talking about karplus-strong.
Your component smoother seems a bit strange though, if you want it to work like in the illustration you should probably just use one one sample delay, not two. Again, this will just be a simple, but static, lowpass filter. Using an ordinary lowpass filter with adjustable settings can probably yield more interesting results.
stefan ·
Here's a _very_ basic highpass filter code snippet which you can try putting in to your feedback path:
polyin in;
polyout out;
polyin cutoff;
float lp;
lp=lp*(1.0-cutoff)+in*cutoff;
out = in - lp;
sambean ·
For the fractional delay in the Karplus I used linear interpolation, which means if you want a delay of 100.75 samples, you take a bit of a 100 sample delay and add it to a bit of a 101 sample delay. This is equivalent to simple lowpass filter (look at the filter used in the diagram you provided, this is equivalent to a fractional delay of 0.5 samples..)
The problem with using linear interpolation is that different fractional parts of the delay filter the sound differently. For example a delay of 100 samples exactly is not filtered at all, whereas a 100.5 sample delay is equivalent to a 100 sample delay with a lowpass filter added.. This is why you are getting different timbres with different notes..
A different form of fractional delay using an allpass filter will reduce this problem (at the expense of more cpu). Change the code in the fractional karplus with the following:
stefan wrote: ... Your component smoother seems a bit strange though, if you want it to work like in the illustration you should probably just use one one sample delay, not two. ...
Oops - that's what I had to start with, the second delay was just me playing arround (since deleted in updated file). That part of the schematic is only there because I was making sure I understood the code version.
sambean wrote: ...The problem with using linear interpolation is that different fractional parts of the delay filter the sound differently. For example a delay of 100 samples exactly is not filtered at all, whereas a 100.5 sample delay is equivalent to a 100 sample delay with a lowpass filter added.. This is why you are getting different timbres with different notes..
I figured there would be a difference between even integer sample lengths and interpolated ones. The delay value on G below middle C is very, very close to a whole number and so the timbre more or less matches the integer value. But for some reason last night I didn't bother to have the machine check my math and I had the wrong value for the delay calculated for that note. So it didn't seem to be the explanation for the behaviour -- which it clearly is. (Updated OSM in orginal post now has a Midi-mono inside the delay length module to show the calculated delay values.)
stefan wrote: ...This is just a redraw error...
Isn't that a bug then? It's not a redraw problem coming from the ticker refresh. It's there in the graph results. (Turn the refresh 'down' to 1 per second and you'll see the discontinuity is still present in most of the draws.)
Thanks to you both... I'll be chewing through the rest of your responses for a while. 🙂
oddson ·
oddson wrote: ...With low feedback levels the signal dies to flatline, but not at zero but either + or - 1.
More stupidity here. I had a normalized graph. It's flatlining very near zero.
Still playing. New version has Sam's allpass fractional and some extra features.
Also added integer rounding for the integer version. It wasn't getting the closest integer delay -- just dropping the fraction.
[edit - wrong. I thought rndint() rounds down. According to thisis does not.]
Changed the 'feedback' to damping and reversed the knob action so more damping = faster
OSM
oddson ·
stefan wrote: Here's a _very_ basic highpass filter code snippet which you can try putting in to your feedback path:...
I've tried inserting the state variable filter to see what would happen (see this OSM previously posted in a bug report). The highpass doesn't work too well. (My guess is you're damping out the fundamental too quickly. You can make some wicked feedback noises with this version, especially after replacing the fractional with the allpass.)
Tried yours, damps instantly unless the cutoff is really low.
stefan ·
Cutoff SHOULD be really low. It's just meant to make sure there's no DC offset in the signal, the actual audio should be unaltered 🙂
PS. That allpass version works really great, nice sound out of it! DS.
oddson ·
stefan wrote: Cutoff SHOULD be really low. It's just meant to make sure there's no DC offset in the signal, the actual audio should be unaltered 🙂
Right, what would you say is a good cutoff value. I'm thinking it could be hard coded instead of fed as a stream value. I'm using 0.00025 for now based on what I can see with an FFT display. (BTW - is there a problem with the FFT logged? The output doesn't look right to me.)
stefan wrote: PS. That allpass version works really great, nice sound out of it! DS.
Very true. Sam is a DSP guru! 🙂
[p.s. What's DS?]
oddson ·
Here's a wacky idea.
Now that I've got a high-pass in there to take out the DC element, I thought, hmm, what would happen if I plucked that string really hard in one direction.
So I seeded stage 0 with
float mem[10000]=rand(-2,0);
..and it really does sound like a hard pluck (with a rather harsh click at t=0 which I'd assume I could minimize).
Can this harm anything? (Speakers etc.?)
sambean ·
Thanks Oddson.. I've still got a long way to go yet..
Not sure about putting it all up to 2, whether it'll be bad or not..
A couple of interesting things to do though:
Don't generate the noise in the code, but use an oscillator to generate it and use an AD envelope to shape it (Making sure the total AD time = the length of the delay) with this you can emphasise different harmonics- like plucking the string at different points..
Feed continuous noise into the feedback
Invert the feedback for woodwind type noises
All good fun..
oddson ·
sambean wrote: Not sure about putting it all up to 2, whether it'll be bad or not..
I'm not worried about the magnitude. The poly-to-mono scales everything back before it hits the analogue world. I don't know about feeding DC to a speaker though.
sambean wrote: A couple of interesting things to do though:
Don't generate the noise in the code, but use an oscillator to generate it and use an AD envelope to shape it (Making sure the total AD time = the length of the delay) with this you can emphasise different harmonics- like plucking the string at different points..
Feed continuous noise into the feedback
Invert the feedback for woodwind type noises...
I've been trying to get a grip on these kinds of issues.
The length of the delay is realy short... (100 samples for A=440 - well 441 in the integer version). How much shaping can you do in a few dozen samples?
As for continuous noise, it tends to overpower the fundamental. But adding resonance from filters in the feedback path gives interesting effects.
I'm going to keep plugging away. Thanks for your input, as always. 🙂
sambean ·
Can't advise about speakers, but I don't think you'd get much difference in the sound to merit trying..
The length of the delay is realy short... (100 samples for A=440 - well 441 in the integer version). How much shaping can you do in a few dozen samples?
Well you could definately add emphasis to some low partials, for example 25 samples attack and 75 decay should add to the second partial..
oddson ·
I'll give it a go with fixed values for A=220hs (using 50 and 150) in the integer delay and then generallizing if the results warrant.
oddson ·
WOW! 😮
Doing as you suggested made some interesting results. But more than that it opened up a whole new way of looking a hitting the string and some of the sounds are really incredible.
😀
EDIT - turns out the input signal's duration can be considerable more than one wave of the fundamental and still sound natural (even with random noise). I don't think I'll bother adapting the length of input based on the note value.
stefan ·
Try this:
Non-noise input. Like for instance just an envelopes amplitude, a short envelope, I tried with a highpassed DC, which will basicly end up as a decay slope.
Use negative feedback.
Fat square bass sound 🙂
oddson ·
Actually, that's part of what my WOW was about. I used the multi-ocs module to add the noise and then tried it with other sources. I've got the Attack/Delay as a ratio on a split knob and another to select the length of the input signal (right now to 1000 samples). Having much fun!
I've got a two OCS version (with detune) going now but it's starting to sap my CPU.
In essence you can put any Synth/sound source in front of the Karplus with a short burst and it will handle the sustain and decay with the waveform damping towards sine. Boosting and cutting various frequencies between the delay and the feedback gives you even more flexibility.
I've got a messy synth now and will post it once I clean in up a bit.
Shifrin ·
Not sure what CPU you're using but I wouldn't have thought 2 Oscs of the type described (Karplus-Strong), would sap CPU unless you were getting denormal problems - they're fairly light on processor usage.
Also, you could try adding a DC offset in the signal path to creat an asymmetric waveform - or inserting a waveshaper of some kind - lots of fun 🙂 [edit]You'd probably want a high-pass filter in there too to cancel the DC offset![/edit]
Shif.
oddson ·
There is some kind of problem.
BTW - it's two regular OSCs prior to the KS. Which I also can't see being an issue.
There's filters all over the place too. Code here and there. It's really a mess but a great deal of fun when it works.
I've got a denormal 'filter' in the feedback chain of the KS so I don't know where the problem is coming from.
If I can't make it work I'll post the messy version for help.
Shifrin wrote: Also, you could try adding a DC offset in the signal path to creat an asymmetric waveform - or inserting a waveshaper of some kind - lots of fun Smile [edit]You'd probably want a high-pass filter in there too to cancel the DC offset![/edit]
That the 'pluck' thing. I haven't tried this specific suggestion yet but the coded noise version was quite 'striking'. I can't see why it matters (and this seems in line with Sam's opinion) but it does. Sounds like when you slap a bass string or pull a string way up between two fingers. And you definitely need a high-pass to dampen the DC out.
Does anyone have any comment on using 0.00025 for a cutoff value in Stefan's filter. It seems to work without any audible impact. Can/should I go even lower?
oddson ·
Here's what I've been working on. It's still plenty messy but I can't figure where I'm getting denormals [if that's what's causing CPU problems].
This version doesn't have the DC 'shock' treatment as I can't make it work outside of the rand() function in the code.
Anyone know where the problem might be on either front?
OSM
sambean ·
Hey oddson, looks like you may be getting denormals in the feedback loop..
Easy way, with low cpu, to solve is to use a timer25 module to control a counter that flicks between 1 and -1, then multiply this number by '1e-11' in a float data and plug the output into the feedback path..
The amplitute is way to quiet to hear but large enough to get rid of denormals, you may need some going into the reverb as well (anything that feeds back- filters etc)
What I have started doing in my own projects is having a denormal module high up in the scematic, generating the number and sending it out with a wireless connector, then every time you need a denormal you can just use a wireless connector..
Another suggestion; in the Karplus module you are still using an old version of the fractional delay that was quite buggy and is causing crackles, I suggest switching it to the code I posted earlier..
Sam
oddson ·
sambean wrote: Hey oddson, looks like you may be getting denormals in the feedback loop..
Why? Isn't the denormal module getting rid of them by quantification?
sambean wrote: Another suggestion; in the Karplus module you are still using an old version of the fractional delay that was quite buggy and is causing crackles, I suggest switching it to the code I posted earlier..
Did I miss something. I only remember one post with the allpass version.
sambean ·
The link to your OSM is to an older version methinks?...
oddson ·
D'oh...
Too right.
sambean ·
That's more like it..
🙂
I get a 'run time' error when I fisrt press a key.. nothing crashes but maybe should be filed as bug?
Sounds good, until the denorms kick in.. I'm not sure if you're denormal coding is working, you're adding a number, then subtracting it in the same cycle, effectively doing nothing to the signal, I'm not sure if that get rid of denorms but try the following instead:
streamin in;
streamout out;
float denorm=1e-11;
out = in + denorm;
denorm = denorm * -1;
Actually don't think it's that.. It's not denormals It sound's like it's when there are too many voices, somehow they're not getting cancelled perhaps.. I'm not sure what to do about this, I'll have a closer look though..
Sam
sambean ·
Hey oddson,
Try unplugging the filter ADSR in the feedback, It seems to work now..
This must be a bug, for some reason the voices are not switching off, and hence it slows down as you press more notes..
Synth is upcoming Superwave Synth and off course the Karplus Synth... Version 1 it appears O_o
oddson ·
skOre wrote: Love the Banjo Like Preset very _very_ much
Banjo was in the early version. It doesn't really sound like a banjo at the settings I left it at. If you turn the cutoff and rez down a bit it gets closer.
I haven't tried to make any preset in version 2 becuase of the problems.
edit - if anyone is interested in the early version it's HERE
skOre ·
Yeah, thats exactly what I did, plus some more tweakage. Love it!
oddson ·
Hey skOre, great song!
I can hear the tweaking in the karplus with the band reject filter on. It's getting some of the sounds I've found as well - dulcimer, koto, banjo and morphing between them. I love it too!
Some of your percussion sounds are a little cheezy though. Clash with the very natural sounds of the karplus IMHO. What are you using for those?
The pads in the background are great. Overall really really GOOD.
skOre ·
Yeah, I use some sampled casio, LinnDrum and Roland 909 on this. I like how the bad electronic sounds clash with the good electronic sounds 🙂
and thanks for the kind words 🙂
[edit]My 808th post 🙂[/edit]
oddson ·
sambean wrote: ...I'm not sure if your denormal coding is working, you're adding a number, then subtracting it in the same cycle, effectively doing nothing to the signal, I'm not sure if that get rid of denorms...
That's not my code sam - I think it's stefan's as I got it from the reverb.
The technique is called 'ellimination by quantification' and it works by losing the denormals by forcing them to round to zero when adding a value of a larger magnitude (and then removing it).
oddson ·
I need coding help. My latest adventure in Karplus-land has me trying to accomplish what I said I wasn't going to bother with -- note specific envelopes.
The ADSR is no use here. So I figured, since it doesn't envolve any signal processing, I could try coding it.
Well, maybe not.
Here's what I tried. Can anyone spot what's wrong?
// Variable AD env for Karplus
polyin delay; // delay in samples for each note
monoin L; // length multiplier
monoin AD; // A/D ratio
polyout env;
float A,D,t; // length in samples of A and D, and index
L = delay * L; // input length * multiplier (adjusted env length)
A = (AD - 1)*L; // length of attack in samples
D = AD*L; // length of decay in samples
// three linear sections to slope A, D and t>L = 0
env = t < A & 1/A*t ;
env = t >= A & 1 - (1/D*(t-A)) ;
env = t > L & 0.0 ;
t = t + 1 ; // increment index
The idea is to allow the the decay to remain open when a burst of the delay length fires -- and to make this adjustable. Hence the L multiplier, which then becomes the length when multiplied to the Karplus delay length calculated elsewhere.
The attack slope should be 1/A
The decay slope should be -1/D
I'm thinking it's either a bonehead mistake in the setup or I don't get how to do the mask part.
stefan ·
The main issue is your assignments to the "env" variable. For instance, the last one:
env = t > L & 0.0 ;
What this one does is the following
Basicly, the above expression remains the same if I add zero to it:
env = 0.0 + t > L & 0.0;
Which means that we set env to 0.0, and if t is above L, we also add 0.0 to env. I guess that what you really want to do is _keep_ the value of env until t > L. You can do that by flipping the comparison around:
env = t <= L & env;
Should work. When t gets larger than L, the expression evaluates to zero.
You need to look at the decay one as well, it should probably end up something like this (if keeping your calculations):
env = ( t < A & env ) + ( t >= A & 1 - (1/D*(t-A)) );
this way, you keep the current env value if t < A, and sets it to another value if t >= A.
I hope I didn't mess up 🙂
oddson ·
This syntax seems seriously messed up to me.
Originally I thought it required addition/subtraction based on the example. But then I thought you put me straight HERE.
Now I don't know what's what. 😕
I don't think the line you've given will work. Won't it leave the attack slope calculating when the decay is in effect?
Here's a schematic if someone wants to try this for me.
OSM
oddson ·
I finally realized I can use the graph to see what going wrong. I'll figure this out eventually.
stefan ·
oddson wrote: This syntax seems seriously messed up to me.
Originally I thought it required addition/subtraction based on the example. But then I thought you put me straight HERE.
Now I don't know what's what. 😕
I don't think the line you've given will work. Won't it leave the attack slope calculating when the decay is in effect?
Here's a schematic if someone wants to try this for me.
OSM
Yes, the attack slope will be calculated eventhough we are decaying. But we won't use the _result_ of the attack slope calculations, since we mask iit out with a boolean that is false (t < A is false).
That's the whole deal with parallelized calculations, you can't do different calculations for different in data (like with our use of SSE).
oddson ·
Actually it was so riddled with mistakes it might be a good thing I hit a snag.
But here's the snag.
L = delay * L;
this line won't work! Once I declare the multiplier a separate variable I start getting somewhere.
stefan ·
oddson wrote: Actually it was so riddled with mistakes it might be a good thing I hit a snag.
But here's the snag.
L = delay * L;
this line won't work! Once I declare the multiplier a separate variable I start getting somewhere.
Yeah, such a line would probably cause a conflict. When you do something like this:
monoin in;
in = in * 2;
You actually try to alter the actual input signal, not just internally in the code component, but the actual value that exists outside of the code component. It's probably best to treat input signals as "read only".
oddson ·
Thanks Stefan...
I got this working shortly after my last post but the adaptor for my cable-modem blew and couldn't get back to say I'd figured it out.
// Variable AD env for Karplus
polyin delay; // delay in samples for each note
monoin LM; // length multiplier
monoin AD; // A/D ratio
polyout env;
float A,D,L,t; // length in samples of A and D, L=A+D and index
L = delay * LM; // input length * multiplier (adjusted env length)
A = (1 -AD)*L; // length of attack in samples
D = AD*L; // length of decay in samples
// three linear sections to slope A, D and t>L = 0
env = 1; // trying to cover t=A
env = (t < A & (1/A)*t) + (t > A & (1 - (1/D)*(t-A))) ; //A and D portions
env = t <= L & env + t > L & 0.0 ; // for flatline after A+D
t = t + 1 ; // increment index
I'm not sure if env is defined at t = A but it looks (on the graph output) and sounds like it's working.
edit - wrong. should be
// three linear sections to slope A, D and t>L = 0
env = (t < A & (1/A)*t) + (t >= A & (1 - (1/D)*(t-A))) ;
env = t <= L & env + t > L & 0.0 ;
t = t + 1 ; // increment index
arke ·
stefan wrote: Here's a _very_ basic highpass filter code snippet which you can try putting in to your feedback path:
polyin in;
polyout out;
polyin cutoff;
float lp;
lp=lp*(1.0-cutoff)+in*cutoff;
out = in - lp;
Is this a one-pole filter?
sambean ·
Hi arke, welcome to the forums.. 🙂
Yup, thats a one pole highpass, for a lowpass change the last line to 'out = lp'..
Thing is though is it's not tuned, as in the cutoff doesn't directly relate to frequency, to tune it you need a conversion before the cutoff, which I can't remember..
Also, here's a useful document with loads of different filter design equations- 1st order one-pole and 2nd order two-pole
Cheers
Sam
arke ·
sambean wrote: Hi arke, welcome to the forums.. 🙂
Thanks 🙂
sambean wrote: Yup, thats a one pole highpass, for a lowpass change the last line to 'out = lp'..
Thing is though is it's not tuned, as in the cutoff doesn't directly relate to frequency, to tune it you need a conversion before the cutoff, which I can't remember..
Well, usually on a VST plugin you use your ears and not some number 😉