Export buffer to WAV in C++

3.6k views Asked by At

I have a simple program that creates a single cycle sine wave and puts the float numbers to a buffer. Then this is exported to a text file. But I want to be able to export it to a WAV file (24 bit). Is there a simple way of doing it like on the text file?

Here is the code I have so far:

#include <iostream>
#include <fstream>
#include <cmath>
using namespace std;

int main ()
{
    long double pi = 3.14159265359; // Declaration of PI

    ofstream textfile; // Text object
    textfile.open("sine.txt"); // Creating the txt

    double samplerate = 44100.00; // Sample rate

    double frequency = 200.00; // Frequency

    int bufferSize = (1/frequency)*samplerate; // Buffer size

    double buffer[bufferSize]; // Buffer

    for (int i = 0; i <= (1/frequency)*samplerate; ++i) // Single cycle
    {
        buffer[i] = sin(frequency * (2 * pi) * i / samplerate); // Putting into buffer the float values
        textfile << buffer[i] << endl; // Exporting to txt
    }

    textfile.close(); // Closing the txt
    return 0; // Success
}
1

There are 1 answers

1
jaket On

First you need to open the stream for binary.

ofstream stream;
stream.open("sine.wav", ios::out | ios::binary);

Next you'll need to write out a wave header. You can search to find the details of the wave file format. The important bits are the sample rate, bit depth, and length of the data.

int bufferSize = (1/frequency)*samplerate; 
stream.write("RIFF", 4);                    // RIFF chunk
write<int>(stream, 36 + bufferSize*sizeof(int)); // RIFF chunk size in bytes
stream.write("WAVE", 4);                    // WAVE chunk
stream.write("fmt ", 4);                    // fmt chunk
write32(stream, 16);                     // size of fmt chunk
write16(stream, 1);                       // Format = PCM
write16(stream, 1);                       // # of Channels
write32(stream, samplerate);                // Sample Rate
write32(stream, samplerate*sizeof(int));    // Byte rate
write16(stream, sizeof(int));             // Frame size
write16(stream, 24);                      // Bits per sample
stream.write("data", 4);                   // data chunk
write32(stream, bufferSize*sizeof(int));   // data chunk size in bytes

Now that the header is out of the way, you'll just need to modify your loop to first convert the double (-1.0,1.0) samples into 32-bit signed int. Truncate the bottom 8-bits since you only want 24-bit and then write out the data. Just so you know, it is common practice to store 24-bit samples inside of a 32-bit word because it is much easier to stride through using native types.

for (int i = 0; i < bufferSize; ++i) // Single cycle
{
    double tmp = sin(frequency * (2 * pi) * i / samplerate); 
    int intVal = (int)(tmp * 2147483647.0) & 0xffffff00;
    stream << intVal;
}

A couple other things:

1) I don't know how you weren't overflowing buffer by using the <= in your loop. I changed it to a <.

2) Again regarding the buffer size. I'm not sure if you are aware but you can't have a repeated waveform represented by a single cycle for all frequencies. What I mean is that for most frequencies if you use this code and expect to play the waveform repeated, you're going to hear a glitch on every cycle. It'll work for nice synchronous frequencies like 1kHz because there will be exactly 48 samples per cycle and it will come around to exactly the same phase. 999.9 Hz will be a different story though.