Making a simple audio synthesizer in C#

I have been into producing dance music for a while now and have always wondered how difficult it would be to make my own simple synthesizer. Turns out it not that difficult at all. I have created a very simple synthesizer in just one afternoon.

How does a software synth work?

Very similar to a hardware synth, it works by creating and mixing waveforms before effecting them to get a desired sound.

How do I create a waveform?

Well you need to write some code that will produce a wave. The most common wave to produce is a sine wave. This sounds like a constant tone with no harmonics.

This is what a sine wave looks like

Sine wave

Ok, so how do I make a sine wave in C#?

Its actually pretty easy to make a sine wave in C#. It a matter of using the mathematical function Math.Sin() then sending in the angle (in radians). Here is an example of my Sine occilator in my program:

public class SineOccilator : SignWaveTest.IOccilator {

	private double _radiansPerCircle = Math.PI * 2;
	private double _currentFrequency = 2000;
	private double _sampleRate = 44100;

	public SineOccilator(double sampleRate) {
		_sampleRate = sampleRate;
	}

	public void SetFrequency(double value){
		_currentFrequency = value;
	}

	public double GetNext(int sampleNumberInSecond) {
		double samplesPerOccilation = (_sampleRate / _currentFrequency);
		double depthIntoOccilations = (sampleNumberInSecond % samplesPerOccilation) / samplesPerOccilation;
		return Math.Sin( depthIntoOccilations * _radiansPerCircle);
	}
}

All of the hard work is done in the GetNext method. The calling code tells the GetNext what sample (of the current second) that it wants and GetNext will return a number between 1 and -1.

This works by working out the ‘samplesPerOccilation’ value, which is the basically “how many samples does one oscillation take at the defined frequency”

After working that out we can work out how far we would be through an oscillation by taking the remainder of the sampleNumberInSecond / samplesPerOccilation. Then making that a number between 0 – 1 by dividing that answer by samplesPerOccilation.

Now we just need to work out that the numeric value for the waveform at that point, this is as simple as using calling Math.Sin and passing in the (depthIntoOccilations * _radiansPerCircle).

So here is how you would call it:

List<double> data = new List<double>();

SquareOccilator o = new SquareOccilator(sampleRate);
SineOccilator j = new SineOccilator(sampleRate);
SawToothOccilator s = new SawToothOccilator(sampleRate);
RoyalSawToothOccilator rs = new RoyalSawToothOccilator(sampleRate);
SawToothOccilatorSteadyDetunable detunableOccilator = new SawToothOccilatorSteadyDetunable(sampleRate);
detunableOccilator.SetDetune(0.05);

s.SetFrequency(GetNoteFrequnecy.C);
for (int i = 0; i < sampleRate * 2; i++) {
	data.Add(s.GetNext(i));
}
rs.SetFrequency(GetNoteFrequnecy.C);
for (int i = 0; i < sampleRate * 2; i++) {
	data.Add(rs.GetNext(i));
}

Create a list of doubles to store the resulting wave sample values, Set the frequency to a “C” then add 2 Seconds worth ( 2 X 441000 samples) to the list.

How can I actually hear what it sounds like?

The easiest way to hear it is to export the wave you have produced to a .wav file. It is reasonbly simple to create a .wav file just follow the specification here.

I found an implementation in the Sixport Synth project that I changed slightly for my needs. It simply writes a “test.wav” file to the bin directory of the application.

public static void SaveIntoStream(double[] sampleData, long sampleCount, int samplesPerSecond) {
	// Export
	FileStream stream = File.Create("test.wav");
	System.IO.BinaryWriter writer = new System.IO.BinaryWriter(stream);
	int RIFF = 0x46464952;
	int WAVE = 0x45564157;
	int formatChunkSize = 16;
	int headerSize = 8;
	int format = 0x20746D66;
	short formatType = 1;
	short tracks = 2;
	short bitsPerSample = 16;
	short frameSize = (short)(tracks * ((bitsPerSample + 7) / 8));
	int bytesPerSecond = samplesPerSecond * frameSize;
	int waveSize = 4;
	int data = 0x61746164;
	int samples = (int)sampleCount;
	int dataChunkSize = samples * frameSize;
	int fileSize = waveSize + headerSize + formatChunkSize + headerSize + dataChunkSize;
	writer.Write(RIFF);
	writer.Write(fileSize);
	writer.Write(WAVE);
	writer.Write(format);
	writer.Write(formatChunkSize);
	writer.Write(formatType);
	writer.Write(tracks);
	writer.Write(samplesPerSecond);
	writer.Write(bytesPerSecond);
	writer.Write(frameSize);
	writer.Write(bitsPerSample);
	writer.Write(data);
	writer.Write(dataChunkSize);

	double sample_l;
	short sl;
	for (int i = 0; i < sampleCount; i++) {
		sample_l = sampleData[i] * 30000.0;
		if (sample_l < -32767.0f) { sample_l = -32767.0f; }
		if (sample_l > 32767.0f) { sample_l = 32767.0f; }
		sl = (short)sample_l;
		stream.WriteByte((byte)(sl & 0xff));
		stream.WriteByte((byte)(sl >> 8));
		stream.WriteByte((byte)(sl & 0xff));
		stream.WriteByte((byte)(sl >> 8));
	}
	stream.Close();
}

That’s pretty cool but I don’t think its going to win any awards

Well, yeah it is a really very simple synth but using that principle and mixing it with other waveforms it is possible to make some awesome sounds.

The source can be downloaded here