1
$\begingroup$

I'm building my own MIDI synth.

I have to create a decent sounding tone for every note on the piano keyboard (A-1 through C7, or MIDI 21 through 21+88=109)

I've been using a basic ring resonator, where I create a ring of the right size (for example, sampling at 44.1 kHz the ring for A440 a.k.a. concert pitch would be 44100/440 i.e. 100.25 ~ 100 samples), fill it with static (values between -1 and +1) and then walk through it, averaging consecutive values.

The problem is that A-1 rumbles for 30 seconds, whereas C7 dies almost instantly.

I never noticed this problem before as I was working with a much smaller range; < two octaves.

Attempting to balance out the decays by multiplying by a suitable decay constant, say X[n] = .995*(X[n-1] + X[n-2])/2, where I choose the constant carefully for each note, is also failing.

Problem here is that even with decay=1.0, a high note still disappears almost instantly. And it makes sense thinking about it; if the ring is only 10 samples, even in 1/100sec we have covered 440 samples i.e. 44 revolutions, which will have smeared everything pretty close to 0.

One method I tried is feeding energy into the ring; creating a buffer of static enveloped by an exponential decay, and feeding this buffer into the ring. It isn't working very well.

What I found works better is linear interpolation, so instead of doing ( X[n-1] + X[n-2] ) / 2, I do maybe:

0.98 * X[n-1]  +  0.02 * X[n-2] for a high note,  and 
0.6 * X[n-1]  +  0.4 * X[n-2]  for a note lower down

etc.

But now the question is: how to choose the interpolation factor depending on the note?

I need a much better model to work with.

I think I also need to implement fractional delay;

Samp[ N ] += k *  Samp[ N - L ], 

and if L = 10.2 say, I would have to do:

Samp[ N ] += k * ( 0.8 * Samp[10]  +  0.2 * Samp[ 11 ] )

so that is a completely different linear interpolation.

So my main question is: How to mathematically determine the necessary interpolation factor for a given note, so that all notes have an equal decay?

Supplementary questions are:
- do I need to use a decay constant in addition to adjusting the interpolation? - is my understanding of fractional delay sufficient to get a working result?

If anyone knows of any resource for simulating a harmonic tone that is reasonably accessible, please do put it in as an answer. I've looked through JOS's resources, but he writes at a very advanced level, and I have difficulty understanding the material.

π

PS I can't find an appropriate tag; pitch, note, gen*, tone, synth*, none of these are available.

$\endgroup$
3

3 Answers 3

2
$\begingroup$

First, note that the behaviour in which high-pitched notes disappear faster than low-pitched notes is found in many musical instruments. You might better not totally compensate for that.

One way I got the behaviour I wanted out of Karplus-Strong was the following:

  • I add an additional exponential decay for the lowest notes - a "loss" factor so that x[n] = loss * (x[n] + x[n - 1]) / 2
  • For the highest notes, I randomly decide whether I will do a buffer update or not ; with an increasingly high probability for the "no update" event as pitch increases.

To avoid dealing with fractional delays - and to get any kind of pitch modulation I want (but also to simplify the code), I use a ring buffer of the nearest power of 2 (for example, 128 samples for A4) and then do a linearly interpolated read from it. One can see this as a wavetable oscillator whose lookup table is dynamically modified/refreshed as the phase increment counter moves through it.

$\endgroup$
2
  • $\begingroup$ Thank you very much for this answer. I like the idea of having a fractional pointer move through a PoT buffer and linearly interpolate between X[floor(ptr)] and X[ceil(ptr)]. These random buffer updates -- I guess it could be as simple as picking a random element X[k] of the buffer and replacing it with .25 * X[k-1] + .5 * X[k] + .25 * X[k+1]. Or { .1 .8 .1 } etc. Is there any logic to choosing Probability( doBufferUpdateThisSample ), or choosing loss for a given note, or do you just play with various transfer functions? I found that just experimenting makes my tones bulge in the middle... $\endgroup$
    – P i
    Commented Nov 11, 2013 at 23:59
  • $\begingroup$ Sorry to resurrect this old thread, but @pichenettes, your idea of using a ring buffer as a wavetable sounds very interesting...I've been trying to deal with the pitch issues of KS and this simplifies it quickly. One question: How do you deal with filtering? I take it that you have to keep filtering the buffer and replacing it as you go, but how do you keep in sync with the read pointer? The buffer needs to get filtered one sample at a time, while the read pointer is not really moving through one sample at a time. Thanks! $\endgroup$
    – pizzafilms
    Commented Mar 15, 2019 at 0:50
1
$\begingroup$

https://ccrma.stanford.edu/realsimple/faust_strings/faust_strings.pdf

This looks like a very good resource; I'm guessing it will take me a couple of weeks to work through...

$\endgroup$
1
$\begingroup$

Using a loss filter, G(w), each harmonic (really, the overtones will be nearly harmonic) will decay by G(w) over each pass. So, when you use a lowpass filter (e.g., .995*(X[n-1] + X[n-2])/2), higher harmonics are attenuated more than lower ones. Also, since higher notes use shorter delay lines (or rings), these losses will occur more often per time unit. Hence, the higher notes decay especially fast. So, if you want to stick with the moving average loss filter, you need to design that loss filter's magnitude response toward getting the desired decay time for each harmonic, given the delay (ring) length.

So, if you have some harmonic at frequency, w1, and you want it to decay with envelope A1(t) = exp(-alpha*t) where t = 0,1,2... , you need to solve for G(w1) where exp(-alpha*t) = [G(w1)]^(t/L) where L is the delay length for the note. Please note that -1 < G(w1) < 1 for stability.

For your purposes, I'd suggest setting the fundamental to some desired decay, and tweaking from there. Using a two-tap filter doesn't give you a lot of control.

For what you're looking to do, I'd recommend you start with the Extended KS paper: http://www.music.mcgill.ca/~gary/courses/papers/Jaffe-Extensions-CMJ-1983.pdf If you're just trying to do some simple KS stuff, it has everything you need.

$\endgroup$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.