I recently faced an interesting problem when trying to combine 3 simple sinewaves for a creating a simple A major chord. I wanted to combine the tree frequencies to play at the same time and produced this a simple major chord, or any

Making a simple sine

When working with raw audio data, in this example we use .wav, we want to sample on a discrete domain, therefore time, in .wav we use 44100 sample per seconds. I won’t go into all the setup to output those data into a file, as it has been a lot covered online. So let’s define a simple sinewave class, this way we can start messing around with signals.

class SineOscillator
{
float frequency, amplitude, angle = 0.0f, offset = 0.0f;
public:
SineOscillator(float freq, float amp) : frequency(freq), amplitude(amp)
{
offset = 2 * PI * frequency / sampleRate;
};
float process()
{
auto sample = amplitude * std::sin(angle);
angle += offset;
return sample;
}
};

In the “audio-loop” we can call a simple process to render a simple 440hz(A4), this is how we do it.

        SineOscillator sineOscillator2(440, .5);

float duration = 2.0f;
int samplerate = 44100;
// Audiorender Loop
for (int i = 0; i < sampleRate * duration; i++)
{

float sample = sineOscillator1.process();
int intSample = static_cast<int>((sample) * maxAmplitude);
writeToFile(audioFile, intSample, 2);

}

We generate with this loop a 2 seconds output with a simple tone of 440 hertz. If you are interested in reading more on processing tone to musical <—> note, I wrote an article on that too. But in short, every note on your piano has a root frequencies associated to it. In the case of a A on the fourth octave, it is 440 hz.

If you are curious, here are the formulas to map around those fuckers. Let’s note that we use the 440 standard tuning.

Or in more simpler format:

A is 440, let’s render the data and listen that(very ugly) sound.

Let’s analyse the data

On a audio spectrum analyzer

We clearly see that the frequency is 440hz. I used Ableton Spectrum, for this snapshot.

Let’s take a the file sampling itself.

As mentionned, for 1 second of audio, we generate 44100 samples in code, one sinewave is very boring but let’s take a look at it. For a more decent understanding of the sinewave, let’s use a visual approach of the function sin(x)

sin(0) = 0 and sin(2pi) = 0, wich defines the core idea of sinewave meaning sine function is periodic. Complete cycle is 2pi. We do not use x in time but in concrete domain of time with 44100 sampling.

In our code, we defined a sinewave being sampled 44100 time per seconds, wich leads to this amount of sampling data.

So if we scale by two the sine in code we will have a twice more.

With out two oscillator in code:

	SineOscillator topImage(880, .5);
SineOscillator bottomImage(440, .5);

We see clearly that idea of a 440 * 2, or playing the upper note on your piano. Somehow like hitting those two note at the same time in blue it’s 440, and in red it 440*2.

How to combine those two.

We are a bit closer to a chord, but we are not there yet. Let’s add 2 sinewave for now. We already have two sinewaves, one playing a 440hz and another one playing a 880hz, but how can we combine them?

	// Define oscillator shit
SineOscillator sinewave1(880, .5);
SineOscillator sinewave2(440, .5);
// Audiorender Loop
for (int i = 0; i < sampleRate * duration; i++)
{

float sample = sinewave1.process();
int intSample = static_cast<int>((sample) * maxAmplitude);
writeToFile(audioFile, intSample, 2);

}

One naive way to do it would be to check for sample every two iteration, this way we could play them side by side with only one sampling different every (1/44100) seconds.

By doing that :

	// Audiorender Loop
for (int i = 0; i < sampleRate * duration; i++)
{
if (i % 2 == 0)
{
float sample = sinewave1.process();
int intSample = static_cast<int>((sample)*maxAmplitude);
writeToFile(audioFile, intSample, 2);
}
else
{
float sample = sinewave2.process();
int intSample = static_cast<int>((sample)*maxAmplitude);
writeToFile(audioFile, intSample, 2);

}
}

We clearly have a problem… The two tones get process and add to the audioloop, but we do not clearly have the expected result.