That code is NOT EVEN CLOSE to be the correct way to read a WAV file. A WAV file has structure to it, but the code is completely ignoring that structure, making inaccurate assumptions without validating them:
- it assumes the first subchunk inside the
WAVE
chunk is fmt\0
- not always true!
- it assumes the size of the
fmt\0
chunk's data is exactly 16 bytes - not always true!
- it assumes that no other chunks exist between the
fmt\0
and data
chunks - not always true!
You really should use a pre-existing library to read audio files, such as libav, but if you are going to do it manually, at least pay attention to what you are reading. Every chunk has a header that indicates the chunk type and data size. You MUST take those into account correctly. Read the chunks in a loop, reading each header, data payload, and optional padding as needed, checking for specific chunks that you are interested in, and ignoring other chunks that you are not interested in.
Try something more like this:
#ifndef FFT_FFT_H
#define FFT_FFT_H
#include <fstream>
#include <string>
class FFT {
public:
FFT(const std::string& filename);
private:
std::ifstream file;
};
#endif //FFT_FFT_H
#include "fft.h"
#include <cstdint>
#include <cstring>
#include <iostream>
#include <iomanip>
#include <vector>
#include <stdexcept>
#pragma pack(push, 1)
struct chunkHdr
{
char chunkID[4];
uint32_t dataSize;
};
struct waveFmt
{
uint16_t wFormatTag;
uint16_t nChannels;
uint32_t nSamplesPerSec;
uint32_t nAvgBytesPerSec;
uint16_t nBlockAlign;
};
struct waveFmtEx
{
waveFmt wf;
uint16_t wBitsPerSample;
uint16_t cbSize;
// cbSize number of bytes follow this struct...
};
struct pcmWaveFmt
{
waveFmt wf;
uint16_t wBitsPerSample;
};
union uWaveFmt
{
waveFmt wf;
waveFmtEx wfx;
pcmWaveFmt pcm;
};
#endif
void readBytes(std::ifstream &f, void *buf, std::streamsize bufsize)
{
if (!f.read(reinterpret_cast<char*>(buf), bufsize))
throw std::runtime_error("not enough bytes in file");
}
void skipBytes(std::ifstream &f, std::streamsize bufsize)
{
if (!f.seekg(bufsize, std::ios_base::cur))
throw std::runtime_error("not enough bytes in file");
}
void readChunkHeader(std::ifstream &f, chunkHdr &hdr)
{
readBytes(f, &hdr, sizeof(hdr));
// if you are on a big-endian system, you need to swap the bytes of hdr.dataSize here...
}
FFT::FFT(const std::string& filename)
{
file.open(filename.c_str(), std::ifstream::binary);
if (!file.is_open())
throw std::runtime_error("cannot open the file");
chunkHdr hdr;
char riffType[4];
std::vector<uint8_t> waveFmtBuffer;
uWaveFmt *fmt = NULL;
readChunkHeader(file, hdr); // should be RIFF
if( (hdr.chunkID[0] != 'R') ||
(hdr.chunkID[1] != 'I') ||
(hdr.chunkID[2] != 'F') ||
(hdr.chunkID[3] != 'F') )
throw std::runtime_error("Expected chunk 'RIFF' not detected");
readBytes(file, riffType, 4); // should be WAVE
if( (riffType[0] != 'W') ||
(riffType[1] != 'A') ||
(riffType[2] != 'V') ||
(riffType[3] != 'E') )
throw std::runtime_error("Expected type 'WAVE' not detected");
while (!file.eof())
{
readChunkHeader(file, hdr);
if(
(hdr.chunkID[0] == 'f') &&
(hdr.chunkID[1] == 'm') &&
(hdr.chunkID[2] == 't') &&
(hdr.chunkID[3] == '\0') )
{
if (fmt)
throw std::runtime_error("Only one 'fmt' chunk is allowed");
if (hdr.dataSize == 0)
throw std::runtime_error("Invalid 'fmt' data size detected");
waveFmtBuffer.resize(hdr.dataSize);
readBytes(file, &data[0], hdr.dataSize);
fmt = reinterpret_cast<uWaveFmt*>(&waveFmtBuffer[0]);
if (hdr.dataSize >= sizeof(waveFmtEx))
{
// if you are on a big-endian system, you need to swap the bytes of the uWaveFmt->wfx fields first...
if (fmt->wfx.wFormatTag == 1) // PCM
fmt->wfx.cbSize = 0; // not used in PCM
else if (hdr.dataSize < (sizeof(waveFmtEx) + fmt->cbSize))
throw std::runtime_error("Invalid 'fmt' data size detected");
}
else if (hdr.dataSize == sizeof(waveFmt))
{
// if you are on a big-endian system, you need to swap the bytes of the fmt->wf fields first...
if (fmt->wf.wFormatTag == 1) // PCM
throw std::runtime_error("Invalid 'fmt' data size detected"); // needed at least sizeof(pcmWaveFmt) bytes
}
else
throw std::runtime_error("Invalid 'fmt' data size detected");
// use fmt as needed...
}
else if(
(hdr.chunkID[0] == 'd') &&
(hdr.chunkID[1] == 'a') &&
(hdr.chunkID[2] == 't') &&
(hdr.chunkID[3] == 'a') )
{
if (!fmt)
throw std::runtime_error("'fmt' chunk not detected before 'data' chunk");
// read exactly hdr.dataSize bytes, processing audio samples
// as needed based on the format defined in the 'fmt' chunk...
skipBytes(file, hdr.dataSize);
}
// any other chunks you are interested in...
else
{
// skip everything else...
skipBytes(file, hdr.dataSize);
}
// skip trailing pad byte if the data size is not even...
if ((hdr.dataSize % 2) != 0)
skipBytes(file, 1);
}
}