This post is going to be a pretty short one, so here it is 😛
The Beating Effect
The beating effect occurs when you play two similar frequencies of sound at the same time.
Because playing two sounds at once means adding them together, and due to the fact that sound waves are made up of positive and negative values (aka positive and negative pressures), the sounds playing at different frequencies will sometimes add peaks and troughs together to get louder, and other times the peak of one wave will add to the valley of another wave and the result will be quieter. This is know as constructive and destructive interference respectively.
The end result is that the sound will have a pulsing quality to it, like a tremolo effect. If one sound is played at frequency F1 and the other sound is played at frequency F2, the pulsing sound will occur at 2*(F2-F1) times a second.
Here’s a demo of this in action where the first sound is 200hz, the second sound is 205hz, and the result of them played together has a 10hz tremolo effect!
Binaural Beat
The beating effect happens when sound waves physically mix together.
Believe it or not though, there is a part of your brain where it mixes (adds) the sounds from each ear together as well.
That means that if you play similar frequencies to different ears, they will mix INSIDE YOUR BRAIN, and you will perceive a different kind of beating effect.
Below is a demo of that. You might need a pair of headphones to get the full effect.
If you think this is pretty neat, you might want to google “psycho-acoustics” for more stuff like this (:
Source Code
Here’s the C++ code that generated these sound files.
#include <stdio.h>
#include <memory.h>
#include <inttypes.h>
#include <vector>
// constants
const float c_pi = 3.14159265359f;
const float c_twoPi = 2.0f * c_pi;
// typedefs
typedef uint16_t uint16;
typedef uint32_t uint32;
typedef int16_t int16;
typedef int32_t int32;
//this struct is the minimal required header data for a wav file
struct SMinimalWaveFileHeader
{
//the main chunk
unsigned char m_chunkID[4];
uint32 m_chunkSize;
unsigned char m_format[4];
//sub chunk 1 "fmt "
unsigned char m_subChunk1ID[4];
uint32 m_subChunk1Size;
uint16 m_audioFormat;
uint16 m_numChannels;
uint32 m_sampleRate;
uint32 m_byteRate;
uint16 m_blockAlign;
uint16 m_bitsPerSample;
//sub chunk 2 "data"
unsigned char m_subChunk2ID[4];
uint32 m_subChunk2Size;
//then comes the data!
};
//this writes
template <typename T>
bool WriteWaveFile(const char *fileName, std::vector<T> data, int16 numChannels, int32 sampleRate)
{
int32 dataSize = data.size() * sizeof(T);
int32 bitsPerSample = sizeof(T) * 8;
//open the file if we can
FILE *File = nullptr;
fopen_s(&File, fileName, "w+b");
if (!File)
return false;
SMinimalWaveFileHeader waveHeader;
//fill out the main chunk
memcpy(waveHeader.m_chunkID, "RIFF", 4);
waveHeader.m_chunkSize = dataSize + 36;
memcpy(waveHeader.m_format, "WAVE", 4);
//fill out sub chunk 1 "fmt "
memcpy(waveHeader.m_subChunk1ID, "fmt ", 4);
waveHeader.m_subChunk1Size = 16;
waveHeader.m_audioFormat = 1;
waveHeader.m_numChannels = numChannels;
waveHeader.m_sampleRate = sampleRate;
waveHeader.m_byteRate = sampleRate * numChannels * bitsPerSample / 8;
waveHeader.m_blockAlign = numChannels * bitsPerSample / 8;
waveHeader.m_bitsPerSample = bitsPerSample;
//fill out sub chunk 2 "data"
memcpy(waveHeader.m_subChunk2ID, "data", 4);
waveHeader.m_subChunk2Size = dataSize;
//write the header
fwrite(&waveHeader, sizeof(SMinimalWaveFileHeader), 1, File);
//write the wave data itself
fwrite(&data[0], dataSize, 1, File);
//close the file and return success
fclose(File);
return true;
}
template <typename T>
void ConvertFloatSamples (const std::vector<float>& in, std::vector<T>& out)
{
// make our out samples the right size
out.resize(in.size());
// convert in format to out format !
for (size_t i = 0, c = in.size(); i < c; ++i)
{
float v = in[i];
if (v < 0.0f)
v *= -float(std::numeric_limits<T>::lowest());
else
v *= float(std::numeric_limits<T>::max());
out[i] = T(v);
}
}
void GenerateMonoBeatingSamples (std::vector<float>& samples, int sampleRate)
{
int sectionLength = samples.size() / 3;
int envelopeLen = sampleRate / 20;
for (int index = 0, numSamples = samples.size(); index < numSamples; ++index)
{
samples[index] = 0.0f;
int section = index / sectionLength;
int sectionOffset = index % sectionLength;
// apply an envelope at front and back of each section keep it from popping between sounds
float envelope = 1.0f;
if (sectionOffset < envelopeLen)
envelope = float(sectionOffset) / float(envelopeLen);
else if (sectionOffset > sectionLength - envelopeLen)
envelope = (float(sectionLength) - float(sectionOffset)) / float(envelopeLen);
// first sound
if (section == 0 || section == 2)
samples[index] += sin(float(index) * c_twoPi * 200.0f / float(sampleRate)) * envelope;
// second sound
if (section == 1 || section == 2)
samples[index] += sin(float(index) * c_twoPi * 205.0f / float(sampleRate)) * envelope;
// scale it to prevent clipping
if (section == 2)
samples[index] *= 0.5f;
samples[index] *= 0.95f;
}
}
void GenerateStereoBeatingSamples (std::vector<float>& samples, int sampleRate)
{
int sectionLength = (samples.size() / 2) / 3;
int envelopeLen = sampleRate / 20;
for (int index = 0, numSamples = samples.size() / 2; index < numSamples; ++index)
{
samples[index * 2] = 0.0f;
samples[index * 2 + 1] = 0.0f;
int section = index / sectionLength;
int sectionOffset = index % sectionLength;
// apply an envelope at front and back of each section keep it from popping between sounds
float envelope = 1.0f;
if (sectionOffset < envelopeLen)
envelope = float(sectionOffset) / float(envelopeLen);
else if (sectionOffset > sectionLength - envelopeLen)
envelope = (float(sectionLength) - float(sectionOffset)) / float(envelopeLen);
envelope *= 0.95f;
// first sound
if (section == 0 || section == 2)
samples[index * 2] += sin(float(index) * c_twoPi * 200.0f / float(sampleRate)) * envelope;
// second sound
if (section == 1 || section == 2)
samples[index * 2 + 1] += sin(float(index) * c_twoPi * 205.0f / float(sampleRate)) * envelope;
}
}
//the entry point of our application
int main(int argc, char **argv)
{
// generate the mono beating effect
{
// sound format parameters
const int c_sampleRate = 44100;
const int c_numSeconds = 4;
const int c_numChannels = 1;
const int c_numSamples = c_sampleRate * c_numChannels * c_numSeconds;
// make space for our samples
std::vector<float> samples;
samples.resize(c_numSamples);
// generate samples
GenerateMonoBeatingSamples(samples, c_sampleRate);
// convert from float to the final format
std::vector<int32> samplesInt;
ConvertFloatSamples(samples, samplesInt);
// write our samples to a wave file
WriteWaveFile("monobeat.wav", samplesInt, c_numChannels, c_sampleRate);
}
// generate the stereo beating effect (binaural beat)
{
// sound format parameters
const int c_sampleRate = 44100;
const int c_numSeconds = 4;
const int c_numChannels = 2;
const int c_numSamples = c_sampleRate * c_numChannels * c_numSeconds;
// make space for our samples
std::vector<float> samples;
samples.resize(c_numSamples);
// generate samples
GenerateStereoBeatingSamples(samples, c_sampleRate);
// convert from float to the final format
std::vector<int32> samplesInt;
ConvertFloatSamples(samples, samplesInt);
// write our samples to a wave file
WriteWaveFile("stereobeat.wav", samplesInt, c_numChannels, c_sampleRate);
}
}
Links
For a more mathematical explanation of these, check out wikipedia (:
Wikipedia: Beat (acoustics)