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.
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).
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 offloat
s that is my buffer of samples (I am normalising the samples just before this step and when Icout
them I can see that they are perfectly between-1.0f
and1.0f
without any clipping).sampleRate
is the sample rate as anint
.sampleDuration
is the duration in sample as anint
.NUM_OF_CHANNELS
is a header definedint
(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.