4
votes

I am currently writing a procedural music engine with openFrameworks in C++. I can generate a song and play it back from a normalised (between -1 and 1) buffer of floats perfectly fine, however I am having a couple of issues when I try to write the same buffer of floats to a 32-bit .WAV file.

  1. When I play back the file in Finder's preview (I'm on OSX 10.9.2) the playback for the entire song duration is extremely clipped and distorted. It seems to be able to read the format fine though as it displays the correct file duration, bit-rate and sample-rate http://i.stack.imgur.com/fz2w8.png. Strangely, when I drag this same file into Logic Pro X, it is read fine, converted successfully and plays back without distortion. It also generates a waveform display where I can see the waveform for the two channels (the file is stereo) perfectly normalised (for the first half at least... see next issue).

  2. Although Logic Pro X is able to read the file far more successfully than Finder's preview, there is a substantial amplitude jump exactly halfway through the song and the waveform starts clipping (though nowhere near as much as in the Finder playback). This happens to every generated song (they're structurally, rhythmically and instrumentally different every-time) that I've tried to write to .WAV. You can see an example here http://i.stack.imgur.com/59y5w.jpg.

The following is the code that I am using to write to the .WAV file:

template <typename T>
void write(std::ofstream& stream, const T& t) {
    stream.write((const char*)&t, sizeof(T));
}

template <typename SampleType>
void writeWAVData(const char* outFile, SampleType* buf, size_t bufSize, int sampleRate, short channels)
{
    std::ofstream stream(outFile, std::ios::binary);                // Open file stream at "outFile" location

    /* Header */
    stream.write("RIFF", 4);                                        // sGroupID (RIFF = Resource Interchange File Format)
    write<int>(stream, 36 + bufSize);                               // dwFileLength
    stream.write("WAVE", 4);                                        // sRiffType

    /* Format Chunk */
    stream.write("fmt ", 4);                                        // sGroupID (fmt = format)
    write<int>(stream, 16);                                         // Chunk size (of Format Chunk)
    write<short>(stream, 1);                                        // Format (1 = PCM)
    write<short>(stream, channels);                                 // Channels
    write<int>(stream, sampleRate);                                 // Sample Rate
    write<int>(stream, sampleRate * channels * sizeof(SampleType)); // Byterate
    write<short>(stream, channels * sizeof(SampleType));            // Frame size aka Block align
    write<short>(stream, 8 * sizeof(SampleType));                   // Bits per sample

    /* Data Chunk */
    stream.write("data", 4);                                        // sGroupID (data)
    stream.write((const char*)&bufSize, 4);                         // Chunk size (of Data, and thus of bufferSize)
    stream.write((const char*)buf, bufSize);                        // The samples DATA!!!
}

I call the "writeWAVData" function with the following line:

writeWAVData(path.c_str(), &buffer[0], sampleDuration * NUM_OF_CHANNELS * sizeof(buffer[0]), sampleRate, NUM_OF_CHANNELS);

Where:

  • path is a string with the file path.
  • buffer is a dynamically allocated array of floats that is my buffer of samples (I am normalising the samples just before this step and when I cout them I can see that they are perfectly between -1.0f and 1.0f without any clipping).
  • sampleRate is the sample rate as an int.
  • sampleDuration is the duration in sample as an int.
  • NUM_OF_CHANNELS is a header defined int (2 in this case).

Any suggestions, slaps on the wrist, perfect solutions or heavy criticism greatly appreciated!

SOLUTION: The problem was that I was setting the format tag in the "Format Chunk" to 1 representing PCM. After changing it to 3 (for the FLOAT format), the .wav file plays back perfectly. Original answer here https://stackoverflow.com/a/22227440/1711917.

1

1 Answers

2
votes

I didn't check exactly tour header, it looks fine, but it would be a good idea to view your file in an hexadecimal editor and compare with a reference. I think your problem lies here:

buffer is a dynamically allocated array of floats that is my buffer of samples (I am normalising the samples just before this step and when I cout them I can see that they are perfectly between -1.0f and 1.0f without any clipping).

You directly dump your float array, but WAV/PCM works with integer samples, typically 8 or 16 bits. Here you specify 32 bits samples and encode them as floats instead of ints.

You have 3 options:

  • work with char[] or short[] data
  • generate float[] but convert to char[] or short[] before writing to file
  • change your format to support floating point data: format-tag 3/FLOAT instead of 1/PCM, see here.