Keenan Tims
15fd253130
* Fixed many sample generation bugs * IMD tests now generating as expected * WAV writing fundamentals in place
137 lines
4.2 KiB
C++
137 lines
4.2 KiB
C++
#pragma once
|
|
|
|
#include <cstdint>
|
|
|
|
#include <array>
|
|
#include <fstream>
|
|
#include <ostream>
|
|
#include <string>
|
|
|
|
using std::array;
|
|
using std::ofstream;
|
|
using std::ostream;
|
|
using std::string;
|
|
|
|
class WavWriter {
|
|
public:
|
|
enum FormatCode : uint16_t {
|
|
WAVE_FORMAT_PCM = 0x01,
|
|
WAVE_FORMAT_IEEE_FLOAT = 0x03,
|
|
WAVE_FORMAT_ALAW = 0x06,
|
|
WAVE_FORMAT_MULAW = 0x07,
|
|
WAVE_FORMAT_EXTENSIBLE = 0xfffe,
|
|
};
|
|
|
|
WavWriter(const string& filename, WavWriter::FormatCode format,
|
|
uint16_t nChannels, uint16_t nBitsPerSample, uint32_t nSamplesPerSec)
|
|
: f(filename, std::ofstream::binary)
|
|
, format(format)
|
|
, nChannels(nChannels)
|
|
, nBitsPerSample(nBitsPerSample)
|
|
, nSamplesPerSec(nSamplesPerSec)
|
|
{
|
|
f.write("RIFF\0\0\0\0WAVE", 12);
|
|
updateHeader();
|
|
f.seekp(0, std::ios_base::end);
|
|
};
|
|
~WavWriter() { f.close(); }
|
|
|
|
template <typename T> void writeSamples(const T data[], size_t n)
|
|
{
|
|
f.write(reinterpret_cast<const char*>(data), n);
|
|
updateHeader();
|
|
};
|
|
|
|
void close();
|
|
|
|
private:
|
|
uint32_t dataSize;
|
|
FormatCode format;
|
|
uint16_t nChannels;
|
|
uint32_t nSamplesPerSec;
|
|
uint16_t nBitsPerSample;
|
|
ofstream f;
|
|
|
|
void updateHeader()
|
|
{
|
|
auto curPos = f.tellp();
|
|
dataSize = curPos - std::streamoff(8);
|
|
f.seekp(0);
|
|
// Update main header total size
|
|
f.write("RIFF", 4);
|
|
f.write(reinterpret_cast<const char*>(&dataSize), sizeof(dataSize));
|
|
f.write("WAVE", 4);
|
|
// Write new format and data size
|
|
WavWriter::fmtChunk fmt(this);
|
|
fmt.writeTo(f);
|
|
f.write("data", 4);
|
|
uint32_t ckSize = curPos - f.tellp();
|
|
f.write(reinterpret_cast<const char*>(&ckSize), sizeof(ckSize));
|
|
|
|
f.seekp(curPos);
|
|
};
|
|
|
|
struct Chunk {
|
|
public:
|
|
char ckID[4];
|
|
|
|
protected:
|
|
Chunk(WavWriter* parent, const char* ckID)
|
|
: parent(parent)
|
|
, ckID{ ckID[0], ckID[1], ckID[2], ckID[3] } {};
|
|
virtual void writeTo(ostream& of) = 0;
|
|
|
|
WavWriter* parent;
|
|
};
|
|
|
|
struct fmtChunk : public Chunk {
|
|
fmtChunk(WavWriter* parent)
|
|
: Chunk(parent, "fmt ")
|
|
{};
|
|
|
|
uint32_t cksize;
|
|
uint16_t cbSize = 0;
|
|
uint32_t dwChannelMask;
|
|
char SubFormat[16];
|
|
|
|
void writeTo(ostream& of)
|
|
{
|
|
of.write(this->ckID, sizeof(this->ckID));
|
|
cksize = parent->format == WAVE_FORMAT_PCM ? 16 : 18;
|
|
of.write(reinterpret_cast<const char*>(&cksize), sizeof(cksize));
|
|
of.write(reinterpret_cast<const char*>(&parent->format),
|
|
sizeof(parent->format));
|
|
of.write(reinterpret_cast<const char*>(&parent->nChannels),
|
|
sizeof(parent->nChannels));
|
|
of.write(reinterpret_cast<const char*>(&parent->nSamplesPerSec),
|
|
sizeof(parent->nSamplesPerSec));
|
|
uint32_t byteRate = parent->nSamplesPerSec * parent->nChannels
|
|
* parent->nBitsPerSample / 8;
|
|
of.write(
|
|
reinterpret_cast<const char*>(&byteRate), sizeof(byteRate));
|
|
uint16_t blockAlign
|
|
= parent->nChannels * parent->nBitsPerSample / 8;
|
|
of.write(
|
|
reinterpret_cast<const char*>(&blockAlign), sizeof(blockAlign));
|
|
of.write(reinterpret_cast<const char*>(&parent->nBitsPerSample),
|
|
sizeof(parent->nBitsPerSample));
|
|
if (parent->format != WAVE_FORMAT_PCM)
|
|
of.write(
|
|
reinterpret_cast<const char*>(&cbSize), sizeof(cbSize));
|
|
if (parent->format == WAVE_FORMAT_EXTENSIBLE) {
|
|
of.write(reinterpret_cast<const char*>(&parent->nBitsPerSample),
|
|
sizeof(parent->nBitsPerSample));
|
|
of.write(reinterpret_cast<const char*>(&dwChannelMask),
|
|
sizeof(dwChannelMask));
|
|
of.write(reinterpret_cast<const char*>(SubFormat),
|
|
sizeof(SubFormat));
|
|
}
|
|
}
|
|
};
|
|
struct factChunk : public Chunk {
|
|
factChunk(WavWriter* parent)
|
|
: Chunk(parent, "fact"){};
|
|
uint32_t cksize = 4;
|
|
uint32_t dwSampleLength();
|
|
};
|
|
}; |