#include "tmachine.h"
#include "tsio_wav.h"
#include "tsystem.h"
#include "tfilepath_io.h"
using namespace std;
#if !defined(TNZ_LITTLE_ENDIAN)
TNZ_LITTLE_ENDIAN undefined !!
#endif
//------------------------------------------------------------------------------
void
swapAndCopySamples(short *srcBuffer, short *dstBuffer, TINT32 sampleCount);
//==============================================================================
// TWAVChunk: classe base per i vari chunk WAV
class TWAVChunk
{
public:
static TINT32 HDR_LENGTH;
string m_name;
TINT32 m_length; // lunghezza del chunk in byte
TWAVChunk(string name, TINT32 length)
: m_name(name), m_length(length) {}
virtual ~TWAVChunk() {}
virtual bool read(Tifstream &is)
{
skip(is);
return true;
}
void skip(Tifstream &is)
{
is.seekg(m_length, ios::cur);
}
static bool readHeader(Tifstream &is, string &name, TINT32 &length)
{
char cName[5];
TINT32 len = 0;
memset(cName, 0, sizeof(cName));
is.read(cName, 4);
if (is.fail())
return false;
cName[4] = '\0';
is.read((char *)&len, sizeof(len));
if (is.fail())
return false;
// il formato WAV memorizza i dati come little-endian
// se la piattaforma non e' little-endian bisogna scambiare i byte
if (!TNZ_LITTLE_ENDIAN)
len = swapTINT32(len);
name = string(cName);
length = len;
return true;
}
};
TINT32 TWAVChunk::HDR_LENGTH = 8;
//====================================================================
// FMT Chunk: Chunk contenente le informazioni sulla traccia
class TFMTChunk : public TWAVChunk
{
public:
static TINT32 LENGTH;
USHORT m_encodingType; // PCM, ...
USHORT m_chans; // numero di canali
TUINT32 m_sampleRate;
TUINT32 m_avgBytesPerSecond;
USHORT m_bytesPerSample;
USHORT m_bitPerSample;
TFMTChunk(TINT32 length)
: TWAVChunk("fmt ", length) {}
virtual bool read(Tifstream &is)
{
is.read((char *)&m_encodingType, sizeof(m_encodingType));
is.read((char *)&m_chans, sizeof(m_chans));
is.read((char *)&m_sampleRate, sizeof(m_sampleRate));
is.read((char *)&m_avgBytesPerSecond, sizeof(m_avgBytesPerSecond));
is.read((char *)&m_bytesPerSample, sizeof(m_bytesPerSample));
is.read((char *)&m_bitPerSample, sizeof(m_bitPerSample));
if (!TNZ_LITTLE_ENDIAN) {
m_encodingType = swapUshort(m_encodingType);
m_chans = swapUshort(m_chans);
m_sampleRate = swapTINT32(m_sampleRate);
m_avgBytesPerSecond = swapTINT32(m_avgBytesPerSecond);
m_bytesPerSample = swapUshort(m_bytesPerSample);
m_bitPerSample = swapUshort(m_bitPerSample);
}
assert(m_length >= 16);
if (m_length > 16)
is.seekg((long)is.tellg() + m_length - 16);
return true;
}
bool write(ofstream &os)
{
TUINT32 length = m_length;
USHORT type = m_encodingType;
USHORT chans = m_chans;
TUINT32 sampleRate = m_sampleRate;
TUINT32 bytesPerSecond = m_avgBytesPerSecond;
USHORT bytesPerSample = m_bytesPerSample;
USHORT bitPerSample = m_bitPerSample;
if (!TNZ_LITTLE_ENDIAN) {
length = swapTINT32(length);
type = swapUshort(type);
chans = swapUshort(chans);
sampleRate = swapTINT32(sampleRate);
bytesPerSecond = swapTINT32(bytesPerSecond);
bytesPerSample = swapUshort(bytesPerSample);
bitPerSample = swapUshort(bitPerSample);
}
os.write((char *)"fmt ", 4);
os.write((char *)&length, sizeof(length));
os.write((char *)&type, sizeof(type));
os.write((char *)&chans, sizeof(chans));
os.write((char *)&sampleRate, sizeof(sampleRate));
os.write((char *)&bytesPerSecond, sizeof(bytesPerSecond));
os.write((char *)&bytesPerSample, sizeof(bytesPerSample));
os.write((char *)&bitPerSample, sizeof(bitPerSample));
return true;
}
};
TINT32 TFMTChunk::LENGTH = TWAVChunk::HDR_LENGTH + 16;
//====================================================================
// DATA Chunk: Chunk contenente i campioni
class TDATAChunk : public TWAVChunk
{
public:
UCHAR *m_samples;
TDATAChunk(TINT32 length)
: TWAVChunk("data", length) {}
~TDATAChunk()
{
if (m_samples)
delete[] m_samples;
}
bool read(Tifstream &is)
{
// alloca il buffer dei campioni
m_samples = new UCHAR[m_length];
if (!m_samples)
return false;
is.read((char *)m_samples, m_length);
return true;
}
bool write(ofstream &os)
{
TINT32 length = m_length;
if (!TNZ_LITTLE_ENDIAN) {
length = swapTINT32(length);
}
os.write((char *)"data", 4);
os.write((char *)&length, sizeof(length));
os.write((char *)m_samples, m_length);
return true;
}
};
//==============================================================================
TSoundTrackReaderWav::TSoundTrackReaderWav(const TFilePath &fp)
: TSoundTrackReader(fp)
{
}
//------------------------------------------------------------------------------
TSoundTrackP TSoundTrackReaderWav::load()
{
char chunkName[5];
char RIFFType[5];
TINT32 chunkLength;
Tifstream is(m_path);
if (!is)
throw TException(m_path.getWideString() + L" : File doesn't exist");
// legge il nome del chunk
is.read((char *)&chunkName, sizeof(chunkName) - 1);
chunkName[4] = '\0';
// legge la lunghezza del chunk
is.read((char *)&chunkLength, sizeof(chunkLength));
if (!TNZ_LITTLE_ENDIAN)
chunkLength = swapTINT32(chunkLength);
// legge il RIFFType
is.read((char *)&RIFFType, sizeof(RIFFType) - 1);
RIFFType[4] = '\0';
// per i .wav il RIFFType DEVE essere uguale a "WAVE"
if ((string(RIFFType, 4) != "WAVE"))
throw TException("The WAV file doesn't contain the WAVE form");
TFMTChunk *fmtChunk = 0;
TDATAChunk *dataChunk = 0;
while (!is.eof()) {
string name = "";
TINT32 length = 0;
bool ret = TWAVChunk::readHeader(is, name, length);
if (!ret)
break;
// legge solo i chunk che ci interessano, ossia FMT e DATA
if (name == "fmt ") {
// legge i dati del chunk FMT
fmtChunk = new TFMTChunk(length);
fmtChunk->read(is);
// considera il byte di pad alla fine del chunk nel caso
// in cui la lunghezza di questi e' dispari
if (length & 1) {
is.seekg((long)is.tellg() + 1);
}
} else if (name == "data") {
// legge i dati del chunk DATA
dataChunk = new TDATAChunk(length);
dataChunk->read(is);
// considera il byte di pad alla fine del chunk nel caso
// in cui la lunghezza di questi e' dispari
if (length & 1) {
is.seekg((long)is.tellg() + 1);
}
} else {
// spostati nello stream di un numero di byte pari a length
if (length & 1)
is.seekg((long)is.tellg() + (long)length + 1);
else
is.seekg((long)is.tellg() + (long)length);
}
}
TSoundTrackP track = 0;
if (fmtChunk && dataChunk) {
TINT32 sampleCount = dataChunk->m_length / fmtChunk->m_bytesPerSample;
bool signedSample = (fmtChunk->m_bitPerSample != 8);
track = TSoundTrack::create(
(int)fmtChunk->m_sampleRate,
fmtChunk->m_bitPerSample,
fmtChunk->m_chans,
sampleCount, signedSample);
if (track) {
switch (fmtChunk->m_bitPerSample) {
case 8:
memcpy(
(void *)track->getRawData(),
(void *)(dataChunk->m_samples),
sampleCount * fmtChunk->m_bytesPerSample);
break;
case 16:
if (!TNZ_LITTLE_ENDIAN)
swapAndCopySamples(
(short *)dataChunk->m_samples,
(short *)track->getRawData(),
sampleCount * fmtChunk->m_chans);
else
memcpy(
(void *)track->getRawData(),
(void *)(dataChunk->m_samples),
sampleCount * fmtChunk->m_bytesPerSample);
//#endif
break;
case 24:
if (!TNZ_LITTLE_ENDIAN) {
UCHAR *begin = (UCHAR *)track->getRawData();
for (int i = 0; i < (int)(sampleCount * fmtChunk->m_chans); ++i) {
*(begin + 4 * i) = 0;
*(begin + 4 * i + 1) =
*(dataChunk->m_samples + 3 * i + 2);
*(begin + 4 * i + 2) =
*(dataChunk->m_samples + 3 * i + 1);
*(begin + 4 * i + 3) =
*(dataChunk->m_samples + 3 * i);
}
} else {
UCHAR *begin = (UCHAR *)track->getRawData();
for (int i = 0; i < (int)(sampleCount * fmtChunk->m_chans); ++i) {
memcpy(
(void *)(begin + 4 * i),
(void *)(dataChunk->m_samples + 3 * i), 3);
*(begin + 4 * i + 3) = 0;
}
}
//#endif
break;
}
}
/*if (!TNZ_LITTLE_ENDIAN)
{
if (fmtChunk->m_bitPerSample > 8)
{
assert(fmtChunk->m_bitPerSample <= 16);
swapAndCopySamples(
(short*)dataChunk->m_samples,
(short*)track->getRawData(),
sampleCount*fmtChunk->m_chans);
}
else
memcpy(
(void*)track->getRawData(),
(void*)(dataChunk->m_samples),
sampleCount*fmtChunk->m_bytesPerSample);
}
else
memcpy(
(void*)track->getRawData(),
(void*)(dataChunk->m_samples),
sampleCount*fmtChunk->m_bytesPerSample);*/
}
if (fmtChunk)
delete fmtChunk;
if (dataChunk)
delete dataChunk;
return track;
}
//==============================================================================
TSoundTrackWriterWav::TSoundTrackWriterWav(const TFilePath &fp)
: TSoundTrackWriter(fp)
{
}
//------------------------------------------------------------------------------
bool TSoundTrackWriterWav::save(const TSoundTrackP &sndtrack)
{
if (!sndtrack)
throw TException(L"Unable to save the soundtrack: " + m_path.getWideString());
if (sndtrack->getBitPerSample() == 8 && sndtrack->isSampleSigned())
throw TException("The format (8 bit signed) is incompatible with WAV file");
TINT32 soundDataLenght = (TINT32)(sndtrack->getSampleCount() * (sndtrack->getBitPerSample() / 8) * sndtrack->getChannelCount() /*sndtrack->getSampleSize()*/);
TINT32 RIFFChunkLength = TFMTChunk::LENGTH + TWAVChunk::HDR_LENGTH + soundDataLenght;
TFileStatus fs(m_path);
if (fs.doesExist() && !fs.isWritable())
throw TException(L"Unable to save the soundtrack: " +
m_path.getWideString() + L" is read-only");
Tofstream os(m_path);
TFMTChunk fmtChunk(16);
fmtChunk.m_encodingType = 1; // PCM
fmtChunk.m_chans = sndtrack->getChannelCount();
fmtChunk.m_sampleRate = sndtrack->getSampleRate();
fmtChunk.m_avgBytesPerSecond = (sndtrack->getBitPerSample() / 8) * fmtChunk.m_chans * sndtrack->getSampleRate();
//sndtrack->getSampleSize()*sndtrack->getSampleRate();
fmtChunk.m_bytesPerSample = (sndtrack->getBitPerSample() / 8) * fmtChunk.m_chans; //sndtrack->getSampleSize();
fmtChunk.m_bitPerSample = sndtrack->getBitPerSample();
TDATAChunk dataChunk(soundDataLenght);
UCHAR *waveData = new UCHAR[soundDataLenght];
if (!TNZ_LITTLE_ENDIAN)
RIFFChunkLength = swapTINT32(RIFFChunkLength);
// era if defined(MACOSX)
#if (!TNZ_LITTLE_ENDIAN)
{
if (fmtChunk.m_bitPerSample == 8)
memcpy(
(void *)waveData,
(void *)sndtrack->getRawData(),
soundDataLenght);
else if (fmtChunk.m_bitPerSample == 16) {
swapAndCopySamples(
(short *)sndtrack->getRawData(),
(short *)waveData,
sndtrack->getSampleCount() * fmtChunk.m_chans);
} else if (fmtChunk.m_bitPerSample == 24) { //swap e togliere quarto byte
UCHAR *begin = (UCHAR *)sndtrack->getRawData();
for (int i = 0; i < (int)sndtrack->getSampleCount() * fmtChunk.m_chans; ++i) {
*(waveData + 3 * i) = *(begin + 4 * i + 3);
*(waveData + 3 * i + 1) = *(begin + 4 * i + 2);
*(waveData + 3 * i + 2) = *(begin + 4 * i + 1);
}
}
}
#else
{
if (fmtChunk.m_bitPerSample != 24)
memcpy(
(void *)waveData,
(void *)sndtrack->getRawData(),
soundDataLenght);
else { //togliere quarto byte
UCHAR *begin = (UCHAR *)sndtrack->getRawData();
for (int i = 0; i < (int)sndtrack->getSampleCount() * fmtChunk.m_chans; ++i) {
*(waveData + 3 * i) = *(begin + 4 * i);
*(waveData + 3 * i + 1) = *(begin + 4 * i + 1);
*(waveData + 3 * i + 2) = *(begin + 4 * i + 2);
}
}
}
#endif
dataChunk.m_samples = waveData;
os.write("RIFF", 4);
os.write((char *)&RIFFChunkLength, sizeof(TINT32));
os.write("WAVE", 4);
fmtChunk.write(os);
dataChunk.write(os);
return true;
}