1

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
}
Jesus Ginard
  • 41
  • 2
  • 5
  • Side note: `int bufferSize = (1/frequency)*samplerate; double buffer[bufferSize]; // Buffer` This is not valid ANSI C++, since arrays must be declared with a constant expression as the number of entries. The other issue with this line is that floating point calculations are not exact. The `bufferSize` could be different depending on compiler, compiler optimizations, etc. – PaulMcKenzie Jun 24 '15 at 10:32
  • You either look for documentation or standard of the wav format and program according to that or use a library, see also http://stackoverflow.com/questions/16075233/reading-and-processing-wav-file-data-in-c-c – stefaanv Jun 24 '15 at 10:32
  • So is it better to define a const number like bufferSize[1000] ? – Jesus Ginard Jun 24 '15 at 10:33
  • @JesusGinard Maybe it would be better to use a fixed size. Also, this `for (int i = 0; i <= (1/frequency)*samplerate; ++i)` is faulty for the same reasons in my earlier comment. See this: https://isocpp.org/wiki/faq/newbie#floating-point-arith – PaulMcKenzie Jun 24 '15 at 10:36
  • @PaulMcKenzie but what if I only want to export a single cycle? If I put a fixed value on the for I'll have more than one cycle. – Jesus Ginard Jun 24 '15 at 10:38
  • Why not allocate the amount of memory you need dynamically? `auto buffer = std::unique_ptr(new double[bufferSize]);` – Michael Jun 24 '15 at 10:41
  • @JesusGinard First, use `std::vector` or as the previous comment suggested, a `unique_ptr` array. That will make the code ANSI C++ compatible. Second, how you come up with a fixed number for `bufferSize` should be part of your design. I can't tell you how to do this -- you have to figure out a way, by hook or by crook, to ensure that the `bufferSize` will be the same value, regardless of the floating point being used to calculate it. It may require doing everything in integer, and inside the loop perform the FP by dividing by some factor, etc. – PaulMcKenzie Jun 24 '15 at 10:45

1 Answers1

2

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.

jaket
  • 9,140
  • 2
  • 25
  • 44
  • What is write16, write32? What header should be included? Is it really that too much to `include` to your answer? no pun intended – IOviSpot Apr 12 '22 at 16:24