1
votes

I am using naudio with SineWaveProvider32 code directly from http://mark-dot-net.blogspot.com/2009/10/playback-of-sine-wave-in-naudio.html to generate sine wave tones. The relevant code in the SineWaveProvider32 class:

public override int Read(float[] buffer, int offset, int sampleCount)
        {
            int sampleRate = WaveFormat.SampleRate;
            for (int n = 0; n < sampleCount; n++)
            {
                buffer[n + offset] =
                    (float)(Amplitude * Math.Sin((2 * Math.PI * sample * Frequency) / sampleRate));
                sample++;
                if (sample >= sampleRate) sample = 0;
            }
            return sampleCount;
        }

I was getting clicks/beats every second, so I changed

if (sample >= sampleRate) sample = 0;

to

if (sample >= (int)(sampleRate / Frequency)) sample = 0;

This fixed the clicks every second (so that "sample" was always relative to a zero-crossing, not the sample rate).

However, whenever I set the Amplitude variable, I get a click. I tried setting it only when the buffer[] was at a zero-crossing, thinking that a sudden jump in amplitude might be causing the problem. That did not solve the problem. I am setting the Amplitude to values between 0.25 and 0.0

I tried adusting the latency and number of buffers as suggested in NAudio change volume in runtime but that had no effect either.

My code that changes the Amplitude:

public async void play(int durationMS, float amplitude = .25f)
        {
        PitchPlayer pPlayer = new PitchPlayer(this.frequency, amplitude);
            pPlayer.play();
            await Task.Delay(durationMS/2);
            pPlayer.provider.Amplitude = .15f;
            await Task.Delay(durationMS /2);
            pPlayer.stop();
    }
2

2 Answers

0
votes

the clicks are caused by a discontinuity in the waveform. This is hard to fix in a class like this because ideally you would slowly ramp the volume from one value to the other. This can be done by modifying the code to have a target amplitude, and then if the current amplitude is not equal to the target amplitude then you move towards it by a small delta amount calculated each time through the loop. So over a period of say 10ms, you move from the old to new amplitude. But you'd need to write this yourself unfortunately.

For a similar concept where the frequency is being changed gradually rather than the amplitude, take a look at my blog post on portamento in NAudio.

0
votes

Angular speed

Instead of frequency it is easier to think in terms of angular speed. How much to increase the angular argument of a sin() function for each sample.

When using radians for angle, one periode completing a full circle is 2*pi so the angular velocity of one Hz is (2*pi)/T = (2*pi)/1/f = f*2*pi = 1*2*pi [rad/s]

The sample rate is in [samples per second] and the angular velocity is in [radians per second] so to get the [angle per sample] you simply divide angular velocity by sample rate to get [radians/second]/[samples/second] = [radians/sample].

That is the number to continuously increase the angle of the sin() function for each sample - no multiplication is needed.

To sweep from one frequency to another you simply move from one angular increment to another in small steps over a number of samples.

By sweeping between frequencies there will be a continuous chain of adjacent samples and transient spread out smoothly over time.

Moving from one amplitude to another could also be spread out over multiple samples to avoid sharp transients.

Fade in and fade out incrementally adjusting the amplitude at the start and end of a sound is more graceful than stepping the output from one level to another in one sample.

Sharp steps produce rings on the water that propagate out in the world.

About sin() calculations

For speedy calculations it may be better to rotate a vector of the length of the amplitude and calculate sn=sin(delta), cs=cos(delta) only when angular speed changes:

Wikipedia Link to theory

where amplitude^2 = x^2 + y^2, each new sample can be calculated as:

px = x * cs - y * sn; 
py = x * sn + y * cs;

To increase the amplitude you simply multiply px and py by a factor say 1.01. To make the next sample you set x=px, y=py and run the px, py calculation again with cs and sn the same all the time.

py or px can be used as the signal output and will be 90 deg out of phase.

On the first sample you can set x=amplitude and y=0.