Generating Square Waves with NAudio

Feb 8, 2011 at 12:26 PM

Hello Mark,

First off, thank you very much for the time and effort you have put in to developing the NAudio Library.  I

I looked at your example code for generating a sine wave.  I compiling it and looked at the waveform

output on a spectrum anlayzer and oscilloscope.  Pretty stable!  The next trick was to write an algorithm

for generation of a square wave and this didn't work out so well.   It seems as if the buffering is not

stable enough so there is a jump (cycling) in amplitude and frequency [very obvious when viewed on

the scope and spectrum analyzer.  Here's the modification to your sine wave example:

 

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using NAudio.Wave;
using NAudio.CoreAudioApi;

namespace SquareWaveGenerator2
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private WaveOut waveOut;

        public void StartStopSquareWave()
        {
            if (waveOut == null)
            {
                SquareWaveProvider32 squareWaveProvider = new SquareWaveProvider32();
                squareWaveProvider.SetWaveFormat(16000, 1); // 16kHz mono
                squareWaveProvider.Frequency = 125;
                squareWaveProvider.Amplitude = 0.25f;
                waveOut = new WaveOut();
                waveOut.Init(squareWaveProvider);
                waveOut.DesiredLatency = 10;
                waveOut.Play();
            }
            else
            {
                waveOut.Stop();
                waveOut.Dispose();
                waveOut = null;
            }
        }

        private void btnExit_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        private void btnRunStop_Click(object sender, EventArgs e)
        {
            StartStopSquareWave();
        }
    }
    public abstract class WaveProvider32 : IWaveProvider
    {
        private WaveFormat waveFormat;

        public WaveProvider32()
            : this(44100, 1)
        {
        }

        public WaveProvider32(int sampleRate, int channels)
        {
            SetWaveFormat(sampleRate, channels);
        }

        public void SetWaveFormat(int sampleRate, int channels)
        {
            this.waveFormat = WaveFormat.CreateIeeeFloatWaveFormat(sampleRate, channels);
        }

        public int Read(byte[] buffer, int offset, int count)
        {
            WaveBuffer waveBuffer = new WaveBuffer(buffer);
            int samplesRequired = count / 4;
            int samplesRead = Read(waveBuffer.FloatBuffer, offset / 4, samplesRequired);
            return samplesRead * 4;
        }

        public abstract int Read(float[] buffer, int offset, int sampleCount);

        public WaveFormat WaveFormat
        {
            get { return waveFormat; }
        }
    }
    public class SquareWaveProvider32 : WaveProvider32
    {
        int sample;

        public SquareWaveProvider32()
        {
            Frequency = 440;
            Amplitude = 0.25f; // let's not hurt our ears
        }

        public float Frequency { get; set; }
        public float Amplitude { get; set; }

        public override int Read(float[] buffer, int offset, int sampleCount)
        {
            int sampleRate = WaveFormat.SampleRate;
            int Channels = WaveFormat.Channels;
            double phaseAngle;
            phaseAngle = ((Math.PI * 2 * Frequency) / (sampleRate * Channels));


            for (int n = 0; n < sampleCount - 1; n++)
            {
                if (Math.Sign(Math.Sin(phaseAngle * n)) > 0)
                    buffer[n + offset] = Amplitude;
                else
                    buffer[n + offset] = -Amplitude;

                sample++;
                if (sample >= sampleRate) sample = 0;
            }
            return sampleCount;
        }
    }
}

 

I've used 125 Hz on purpose.  If you compile and run this you will hear the instability.  This works fine

for multiples of 10 i.e., 40, 50, 60, ... 220, 220 and so on but anything other than this setting for

Frequency generates instability in the resultant audio output.

I suspect there might be some function or class I could add to process pre-buffering that might

clean this up a bit?  I'm pretty sure (being a mathematician) that the algorithm for square wave

generation is correct.  What would you suggest?

 

Thanks in advance,

BearSharp

Coordinator
Feb 8, 2011 at 1:12 PM

well one obvious flaw is that the waveform generation is reset on every call to Read and the amount read each time is hardly guaranteed to be an exact cycle of the waveform, so you need to remember where you are up to from outside. Also, it should be n<sampleCount not sampleCount-1

Mark

Coordinator
Feb 8, 2011 at 1:14 PM

just noticed you do have the sample variable available to use. So make use of it like this:

Math.Sign(Math.Sin(phaseAngle * sample)

Feb 8, 2011 at 10:39 PM

Thank you very much Mark.  Changing the algorithm to Math.Sign(Math.Sin(phaseAngle * sample) and n < sampleCount did the trick. 

There is hardly any skip (anomaly) on the oscilloscope or spectrum analyzer now when I set to frequencies like 255 or 394 and the like.

 

Again, thanks for your effort and skill in designing and deployment of NAudio.

 

yours,

BearSharp