Question

I'm writing a synthesizer in C# using NAudio. I'm trying to make it slide smoothly between frequencies. But I have a feeling I'm not understanding something about the math involved. It slides wildly at a high pitch before switching to the correct next pitch.

What's the mathematically correct way to slide from one pitch to another?

Here's the code:

public override int Read(float[] buffer, int offset, int sampleCount) { int sampleRate = WaveFormat.SampleRate;

        for (int n = 0; n < sampleCount; n++)
        {
            if (nextFrequencyQueue.Count > 0)
            {                    
                nextFrequency = nextFrequencyQueue.Dequeue();
            }

            if (nextFrequency > 0 && Frequency != nextFrequency)
            {
                if (Frequency == 0) //special case for first note
                {
                    Frequency = nextFrequency;
                }
                else //slide up or down to next frequency
                {
                    if (Frequency < nextFrequency)
                    {
                        Frequency = Clamp(Frequency + frequencyStep, nextFrequency, Frequency);
                    }
                    if (Frequency > nextFrequency)
                    {
                        Frequency = Clamp(Frequency - frequencyStep, Frequency, nextFrequency);
                    }
                }
            }

            buffer[n + offset] = (float)(Amplitude * Math.Sin(2 * Math.PI * time * Frequency));
            try
            {
                time += (double)1 / (double)sampleRate;
            }
            catch
            {
                time = 0;
            }
        }
        return sampleCount;
    }
Was it helpful?

Solution

You are using absolute time to determine the wave function, so when you change the frequency very slightly, the next sample is what it would have been had you started the run at that new frequency.

I don't know the established best approach, but a simple approach that's probably good enough is to compute the phase (φ = t mod 1/fold) and adjust t to preserve the phase under the new frequency (t = φ/fnew).

A smoother approach would be to preserve the first derivative. This is more difficult because, unlike for the wave itself, the amplitude of the first derivative varies with frequency, which means that preserving the phase isn't sufficient. In any event, this added complexity is almost certainly overkill, given that you are varying the frequency smoothly.

OTHER TIPS

One approach is to use wavetables. You construct a full cycle of a sine wave in an array, then in your Read function you can simply lookup into it. Each sample you read, you advance by an amount calculated from the desired output frequency. Then when you want to glide to a new frequency, you calculate the new delta for lookups into the table, and then instead of going straight there you adjust the delta incrementally to move to the new value over a set period of time (the 'glide' or portamento time).

  Frequency = Clamp(Frequency + frequencyStep, nextFrequency, Frequency);

The human ear doesn't work like that, it is highly non-linear. Nature is logarithmic. The frequency of middle C is 261.626 Hz. The next note, C#, is related to the previous one by a factor of Math.Pow(2, 1/12.0) or about 1.0594631. So C# is 277.183 Hz, an increment of 15.557 Hz.

The next C up the scale has double the frequency, 523.252 Hz. And C# after that is 554.366 Hz, an increment of 31.084 Hz. Note how the increment doubled. So the frequencyStep in your code snippet should not be an addition, it should be a multiplication.

  buffer[n + offset] = (float)(Amplitude * Math.Sin(2 * Math.PI * time * Frequency));

That's a problem as well. Your calculated samples do not smoothly transition from one frequency to the next. There's a step when "Frequency" changes. You have to apply an offset to "time" so it produces the exact same sample value at sample time "time - 1", the same value you previously calculated with the previous value of Frequency. These steps produce high frequency artifacts with many harmonics that are gratingly obvious to the human ear.

Background info is available in this Wikipedia article. It will help to visualize the wave form you generate, you would have easily diagnosed the step problem. I'll copy the Wiki image:

enter image description here

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top