mirror of
https://github.com/RPCS3/soundtouch.git
synced 2024-11-10 04:42:50 +01:00
985 lines
24 KiB
C++
985 lines
24 KiB
C++
////////////////////////////////////////////////////////////////////////////////
|
|
///
|
|
/// Classes for easy reading & writing of WAV sound files.
|
|
///
|
|
/// For big-endian CPU, define _BIG_ENDIAN_ during compile-time to correctly
|
|
/// parse the WAV files with such processors.
|
|
///
|
|
/// Admittingly, more complete WAV reader routines may exist in public domain,
|
|
/// but the reason for 'yet another' one is that those generic WAV reader
|
|
/// libraries are exhaustingly large and cumbersome! Wanted to have something
|
|
/// simpler here, i.e. something that's not already larger than rest of the
|
|
/// SoundTouch/SoundStretch program...
|
|
///
|
|
/// Author : Copyright (c) Olli Parviainen
|
|
/// Author e-mail : oparviai 'at' iki.fi
|
|
/// SoundTouch WWW: http://www.surina.net/soundtouch
|
|
///
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// License :
|
|
//
|
|
// SoundTouch audio processing library
|
|
// Copyright (c) Olli Parviainen
|
|
//
|
|
// This library is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU Lesser General Public
|
|
// License as published by the Free Software Foundation; either
|
|
// version 2.1 of the License, or (at your option) any later version.
|
|
//
|
|
// This library is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
// Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public
|
|
// License along with this library; if not, write to the Free Software
|
|
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include <stdio.h>
|
|
#include <string>
|
|
#include <sstream>
|
|
#include <cstring>
|
|
#include <cassert>
|
|
#include <climits>
|
|
|
|
#include "WavFile.h"
|
|
#include "STTypes.h"
|
|
|
|
using namespace std;
|
|
|
|
namespace soundstretch
|
|
{
|
|
|
|
#if _WIN32
|
|
#define FOPEN(name, mode) _wfopen(name, STRING_CONST(mode))
|
|
#else
|
|
#define FOPEN(name, mode) fopen(name, mode)
|
|
#endif
|
|
|
|
static const char riffStr[] = "RIFF";
|
|
static const char waveStr[] = "WAVE";
|
|
static const char fmtStr[] = "fmt ";
|
|
static const char factStr[] = "fact";
|
|
static const char dataStr[] = "data";
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Helper functions for swapping byte order to correctly read/write WAV files
|
|
// with big-endian CPU's: Define compile-time definition _BIG_ENDIAN_ to
|
|
// turn-on the conversion if it appears necessary.
|
|
//
|
|
// For example, Intel x86 is little-endian and doesn't require conversion,
|
|
// while PowerPC of Mac's and many other RISC cpu's are big-endian.
|
|
|
|
#ifdef BYTE_ORDER
|
|
// In gcc compiler detect the byte order automatically
|
|
#if BYTE_ORDER == BIG_ENDIAN
|
|
// big-endian platform.
|
|
#define _BIG_ENDIAN_
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef _BIG_ENDIAN_
|
|
// big-endian CPU, swap bytes in 16 & 32 bit words
|
|
|
|
// helper-function to swap byte-order of 32bit integer
|
|
static inline int _swap32(int& dwData)
|
|
{
|
|
dwData = ((dwData >> 24) & 0x000000FF) |
|
|
((dwData >> 8) & 0x0000FF00) |
|
|
((dwData << 8) & 0x00FF0000) |
|
|
((dwData << 24) & 0xFF000000);
|
|
return dwData;
|
|
}
|
|
|
|
// helper-function to swap byte-order of 16bit integer
|
|
static inline short _swap16(short& wData)
|
|
{
|
|
wData = ((wData >> 8) & 0x00FF) |
|
|
((wData << 8) & 0xFF00);
|
|
return wData;
|
|
}
|
|
|
|
// helper-function to swap byte-order of buffer of 16bit integers
|
|
static inline void _swap16Buffer(short* pData, int numWords)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < numWords; i++)
|
|
{
|
|
pData[i] = _swap16(pData[i]);
|
|
}
|
|
}
|
|
|
|
#else // BIG_ENDIAN
|
|
// little-endian CPU, WAV file is ok as such
|
|
|
|
// dummy helper-function
|
|
static inline int _swap32(int& dwData)
|
|
{
|
|
// do nothing
|
|
return dwData;
|
|
}
|
|
|
|
// dummy helper-function
|
|
static inline short _swap16(short& wData)
|
|
{
|
|
// do nothing
|
|
return wData;
|
|
}
|
|
|
|
// dummy helper-function
|
|
static inline void _swap16Buffer(short*, int)
|
|
{
|
|
// do nothing
|
|
}
|
|
|
|
#endif // BIG_ENDIAN
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Class WavFileBase
|
|
//
|
|
|
|
WavFileBase::WavFileBase()
|
|
{
|
|
convBuff = nullptr;
|
|
convBuffSize = 0;
|
|
}
|
|
|
|
|
|
WavFileBase::~WavFileBase()
|
|
{
|
|
delete[] convBuff;
|
|
convBuffSize = 0;
|
|
}
|
|
|
|
|
|
/// Get pointer to conversion buffer of at min. given size
|
|
void* WavFileBase::getConvBuffer(int sizeBytes)
|
|
{
|
|
if (convBuffSize < sizeBytes)
|
|
{
|
|
delete[] convBuff;
|
|
|
|
convBuffSize = (sizeBytes + 15) & -8; // round up to following 8-byte bounday
|
|
convBuff = new char[convBuffSize];
|
|
}
|
|
return convBuff;
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Class WavInFile
|
|
//
|
|
|
|
WavInFile::WavInFile(const STRING& fileName)
|
|
{
|
|
// Try to open the file for reading
|
|
fptr = FOPEN(fileName.c_str(), "rb");
|
|
if (fptr == nullptr)
|
|
{
|
|
ST_THROW_RT_ERROR("Error : Unable to open file for reading.");
|
|
}
|
|
|
|
init();
|
|
}
|
|
|
|
|
|
WavInFile::WavInFile(FILE* file)
|
|
{
|
|
// Try to open the file for reading
|
|
fptr = file;
|
|
if (!file)
|
|
{
|
|
ST_THROW_RT_ERROR("Error : Unable to access input stream for reading");
|
|
}
|
|
|
|
init();
|
|
}
|
|
|
|
|
|
/// Init the WAV file stream
|
|
void WavInFile::init()
|
|
{
|
|
int hdrsOk;
|
|
|
|
// assume file stream is already open
|
|
assert(fptr);
|
|
|
|
// Read the file headers
|
|
hdrsOk = readWavHeaders();
|
|
if (hdrsOk != 0)
|
|
{
|
|
ST_THROW_RT_ERROR("Input file is corrupt or not a WAV file");
|
|
}
|
|
|
|
// sanity check for format parameters
|
|
if ((header.format.channel_number < 1) || (header.format.channel_number > 9) ||
|
|
(header.format.sample_rate < 4000) || (header.format.sample_rate > 192000) ||
|
|
(header.format.byte_per_sample < 1) || (header.format.byte_per_sample > 320) ||
|
|
(header.format.bits_per_sample < 8) || (header.format.bits_per_sample > 32))
|
|
{
|
|
ST_THROW_RT_ERROR("Error: Illegal wav file header format parameters.");
|
|
}
|
|
|
|
dataRead = 0;
|
|
}
|
|
|
|
|
|
WavInFile::~WavInFile()
|
|
{
|
|
if (fptr) fclose(fptr);
|
|
fptr = nullptr;
|
|
}
|
|
|
|
|
|
void WavInFile::rewind()
|
|
{
|
|
int hdrsOk;
|
|
|
|
fseek(fptr, 0, SEEK_SET);
|
|
hdrsOk = readWavHeaders();
|
|
assert(hdrsOk == 0);
|
|
dataRead = 0;
|
|
}
|
|
|
|
|
|
int WavInFile::checkCharTags() const
|
|
{
|
|
// header.format.fmt should equal to 'fmt '
|
|
if (memcmp(fmtStr, header.format.fmt, 4) != 0) return -1;
|
|
// header.data.data_field should equal to 'data'
|
|
if (memcmp(dataStr, header.data.data_field, 4) != 0) return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int WavInFile::read(unsigned char* buffer, int maxElems)
|
|
{
|
|
int numBytes;
|
|
uint afterDataRead;
|
|
|
|
// ensure it's 8 bit format
|
|
if (header.format.bits_per_sample != 8)
|
|
{
|
|
ST_THROW_RT_ERROR("Error: WavInFile::read(char*, int) works only with 8bit samples.");
|
|
}
|
|
assert(sizeof(char) == 1);
|
|
|
|
numBytes = maxElems;
|
|
afterDataRead = dataRead + numBytes;
|
|
if (afterDataRead > header.data.data_len)
|
|
{
|
|
// Don't read more samples than are marked available in header
|
|
numBytes = (int)header.data.data_len - (int)dataRead;
|
|
assert(numBytes >= 0);
|
|
}
|
|
|
|
assert(buffer);
|
|
numBytes = (int)fread(buffer, 1, numBytes, fptr);
|
|
dataRead += numBytes;
|
|
|
|
return numBytes;
|
|
}
|
|
|
|
|
|
int WavInFile::read(short* buffer, int maxElems)
|
|
{
|
|
unsigned int afterDataRead;
|
|
int numBytes;
|
|
int numElems;
|
|
|
|
assert(buffer);
|
|
switch (header.format.bits_per_sample)
|
|
{
|
|
case 8:
|
|
{
|
|
// 8 bit format
|
|
unsigned char* temp = (unsigned char*)getConvBuffer(maxElems);
|
|
int i;
|
|
|
|
numElems = read(temp, maxElems);
|
|
// convert from 8 to 16 bit
|
|
for (i = 0; i < numElems; i++)
|
|
{
|
|
buffer[i] = (short)(((short)temp[i] - 128) * 256);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 16:
|
|
{
|
|
// 16 bit format
|
|
|
|
assert(sizeof(short) == 2);
|
|
|
|
numBytes = maxElems * 2;
|
|
afterDataRead = dataRead + numBytes;
|
|
if (afterDataRead > header.data.data_len)
|
|
{
|
|
// Don't read more samples than are marked available in header
|
|
numBytes = (int)header.data.data_len - (int)dataRead;
|
|
assert(numBytes >= 0);
|
|
}
|
|
|
|
numBytes = (int)fread(buffer, 1, numBytes, fptr);
|
|
dataRead += numBytes;
|
|
numElems = numBytes / 2;
|
|
|
|
// 16bit samples, swap byte order if necessary
|
|
_swap16Buffer((short*)buffer, numElems);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
stringstream ss;
|
|
ss << "\nOnly 8/16 bit sample WAV files supported in integer compilation. Can't open WAV file with ";
|
|
ss << (int)header.format.bits_per_sample;
|
|
ss << " bit sample format. ";
|
|
ST_THROW_RT_ERROR(ss.str().c_str());
|
|
}
|
|
};
|
|
|
|
return numElems;
|
|
}
|
|
|
|
|
|
/// Read data in float format. Notice that when reading in float format
|
|
/// 8/16/24/32 bit sample formats are supported
|
|
int WavInFile::read(float* buffer, int maxElems)
|
|
{
|
|
unsigned int afterDataRead;
|
|
int numBytes;
|
|
int numElems;
|
|
int bytesPerSample;
|
|
|
|
assert(buffer);
|
|
|
|
bytesPerSample = header.format.bits_per_sample / 8;
|
|
if ((bytesPerSample < 1) || (bytesPerSample > 4))
|
|
{
|
|
stringstream ss;
|
|
ss << "\nOnly 8/16/24/32 bit sample WAV files supported. Can't open WAV file with ";
|
|
ss << (int)header.format.bits_per_sample;
|
|
ss << " bit sample format. ";
|
|
ST_THROW_RT_ERROR(ss.str().c_str());
|
|
}
|
|
|
|
numBytes = maxElems * bytesPerSample;
|
|
afterDataRead = dataRead + numBytes;
|
|
if (afterDataRead > header.data.data_len)
|
|
{
|
|
// Don't read more samples than are marked available in header
|
|
numBytes = (int)header.data.data_len - (int)dataRead;
|
|
assert(numBytes >= 0);
|
|
}
|
|
|
|
// read raw data into temporary buffer
|
|
char* temp = (char*)getConvBuffer(numBytes);
|
|
numBytes = (int)fread(temp, 1, numBytes, fptr);
|
|
dataRead += numBytes;
|
|
|
|
numElems = numBytes / bytesPerSample;
|
|
|
|
// swap byte ordert & convert to float, depending on sample format
|
|
switch (bytesPerSample)
|
|
{
|
|
case 1:
|
|
{
|
|
unsigned char* temp2 = (unsigned char*)temp;
|
|
double conv = 1.0 / 128.0;
|
|
for (int i = 0; i < numElems; i++)
|
|
{
|
|
buffer[i] = (float)(temp2[i] * conv - 1.0);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 2:
|
|
{
|
|
short* temp2 = (short*)temp;
|
|
double conv = 1.0 / 32768.0;
|
|
for (int i = 0; i < numElems; i++)
|
|
{
|
|
short value = temp2[i];
|
|
buffer[i] = (float)(_swap16(value) * conv);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 3:
|
|
{
|
|
char* temp2 = (char*)temp;
|
|
double conv = 1.0 / 8388608.0;
|
|
for (int i = 0; i < numElems; i++)
|
|
{
|
|
int value = *((int*)temp2);
|
|
value = _swap32(value) & 0x00ffffff; // take 24 bits
|
|
value |= (value & 0x00800000) ? 0xff000000 : 0; // extend minus sign bits
|
|
buffer[i] = (float)(value * conv);
|
|
temp2 += 3;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 4:
|
|
{
|
|
int* temp2 = (int*)temp;
|
|
double conv = 1.0 / 2147483648.0;
|
|
assert(sizeof(int) == 4);
|
|
for (int i = 0; i < numElems; i++)
|
|
{
|
|
int value = temp2[i];
|
|
buffer[i] = (float)(_swap32(value) * conv);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return numElems;
|
|
}
|
|
|
|
|
|
int WavInFile::eof() const
|
|
{
|
|
// return true if all data has been read or file eof has reached
|
|
return ((uint)dataRead == header.data.data_len || feof(fptr));
|
|
}
|
|
|
|
|
|
// test if character code is between a white space ' ' and little 'z'
|
|
static int isAlpha(char c)
|
|
{
|
|
return (c >= ' ' && c <= 'z') ? 1 : 0;
|
|
}
|
|
|
|
|
|
// test if all characters are between a white space ' ' and little 'z'
|
|
static int isAlphaStr(const char* str)
|
|
{
|
|
char c;
|
|
|
|
c = str[0];
|
|
while (c)
|
|
{
|
|
if (isAlpha(c) == 0) return 0;
|
|
str++;
|
|
c = str[0];
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
int WavInFile::readRIFFBlock()
|
|
{
|
|
if (fread(&(header.riff), sizeof(WavRiff), 1, fptr) != 1) return -1;
|
|
|
|
// swap 32bit data byte order if necessary
|
|
_swap32((int&)header.riff.package_len);
|
|
|
|
// header.riff.riff_char should equal to 'RIFF');
|
|
if (memcmp(riffStr, header.riff.riff_char, 4) != 0) return -1;
|
|
// header.riff.wave should equal to 'WAVE'
|
|
if (memcmp(waveStr, header.riff.wave, 4) != 0) return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int WavInFile::readHeaderBlock()
|
|
{
|
|
char label[5];
|
|
string sLabel;
|
|
|
|
// lead label string
|
|
if (fread(label, 1, 4, fptr) != 4) return -1;
|
|
label[4] = 0;
|
|
|
|
if (isAlphaStr(label) == 0) return -1; // not a valid label
|
|
|
|
// Decode blocks according to their label
|
|
if (strcmp(label, fmtStr) == 0)
|
|
{
|
|
int nLen, nDump;
|
|
|
|
// 'fmt ' block
|
|
memcpy(header.format.fmt, fmtStr, 4);
|
|
|
|
// read length of the format field
|
|
if (fread(&nLen, sizeof(int), 1, fptr) != 1) return -1;
|
|
// swap byte order if necessary
|
|
_swap32(nLen);
|
|
|
|
// calculate how much length differs from expected
|
|
nDump = nLen - ((int)sizeof(header.format) - 8);
|
|
|
|
// verify that header length isn't smaller than expected structure
|
|
if ((nLen < 0) || (nDump < 0)) return -1;
|
|
|
|
header.format.format_len = nLen;
|
|
|
|
// if format_len is larger than expected, read only as much data as we've space for
|
|
if (nDump > 0)
|
|
{
|
|
nLen = sizeof(header.format) - 8;
|
|
}
|
|
|
|
// read data
|
|
if (fread(&(header.format.fixed), nLen, 1, fptr) != 1) return -1;
|
|
|
|
// swap byte order if necessary
|
|
_swap16((short&)header.format.fixed); // short int fixed;
|
|
_swap16((short&)header.format.channel_number); // short int channel_number;
|
|
_swap32((int&)header.format.sample_rate); // int sample_rate;
|
|
_swap32((int&)header.format.byte_rate); // int byte_rate;
|
|
_swap16((short&)header.format.byte_per_sample); // short int byte_per_sample;
|
|
_swap16((short&)header.format.bits_per_sample); // short int bits_per_sample;
|
|
|
|
// if format_len is larger than expected, skip the extra data
|
|
if (nDump > 0)
|
|
{
|
|
fseek(fptr, nDump, SEEK_CUR);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
else if (strcmp(label, factStr) == 0)
|
|
{
|
|
int nLen, nDump;
|
|
|
|
// 'fact' block
|
|
memcpy(header.fact.fact_field, factStr, 4);
|
|
|
|
// read length of the fact field
|
|
if (fread(&nLen, sizeof(int), 1, fptr) != 1) return -1;
|
|
// swap byte order if necessary
|
|
_swap32(nLen);
|
|
|
|
// calculate how much length differs from expected
|
|
nDump = nLen - ((int)sizeof(header.fact) - 8);
|
|
|
|
// verify that fact length isn't smaller than expected structure
|
|
if ((nLen < 0) || (nDump < 0)) return -1;
|
|
|
|
header.fact.fact_len = nLen;
|
|
|
|
// if format_len is larger than expected, read only as much data as we've space for
|
|
if (nDump > 0)
|
|
{
|
|
nLen = sizeof(header.fact) - 8;
|
|
}
|
|
|
|
// read data
|
|
if (fread(&(header.fact.fact_sample_len), nLen, 1, fptr) != 1) return -1;
|
|
|
|
// swap byte order if necessary
|
|
_swap32((int&)header.fact.fact_sample_len); // int sample_length;
|
|
|
|
// if fact_len is larger than expected, skip the extra data
|
|
if (nDump > 0)
|
|
{
|
|
fseek(fptr, nDump, SEEK_CUR);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
else if (strcmp(label, dataStr) == 0)
|
|
{
|
|
// 'data' block
|
|
memcpy(header.data.data_field, dataStr, 4);
|
|
if (fread(&(header.data.data_len), sizeof(uint), 1, fptr) != 1) return -1;
|
|
|
|
// swap byte order if necessary
|
|
_swap32((int&)header.data.data_len);
|
|
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
uint len, i;
|
|
uint temp;
|
|
// unknown block
|
|
|
|
// read length
|
|
if (fread(&len, sizeof(len), 1, fptr) != 1) return -1;
|
|
// scan through the block
|
|
for (i = 0; i < len; i++)
|
|
{
|
|
if (fread(&temp, 1, 1, fptr) != 1) return -1;
|
|
if (feof(fptr)) return -1; // unexpected eof
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
int WavInFile::readWavHeaders()
|
|
{
|
|
int res;
|
|
|
|
memset(&header, 0, sizeof(header));
|
|
|
|
res = readRIFFBlock();
|
|
if (res) return 1;
|
|
// read header blocks until data block is found
|
|
do
|
|
{
|
|
// read header blocks
|
|
res = readHeaderBlock();
|
|
if (res < 0) return 1; // error in file structure
|
|
} while (res == 0);
|
|
// check that all required tags are legal
|
|
return checkCharTags();
|
|
}
|
|
|
|
|
|
uint WavInFile::getNumChannels() const
|
|
{
|
|
return header.format.channel_number;
|
|
}
|
|
|
|
|
|
uint WavInFile::getNumBits() const
|
|
{
|
|
return header.format.bits_per_sample;
|
|
}
|
|
|
|
|
|
uint WavInFile::getBytesPerSample() const
|
|
{
|
|
return getNumChannels() * getNumBits() / 8;
|
|
}
|
|
|
|
|
|
uint WavInFile::getSampleRate() const
|
|
{
|
|
return header.format.sample_rate;
|
|
}
|
|
|
|
|
|
uint WavInFile::getDataSizeInBytes() const
|
|
{
|
|
return header.data.data_len;
|
|
}
|
|
|
|
|
|
uint WavInFile::getNumSamples() const
|
|
{
|
|
if (header.format.byte_per_sample == 0) return 0;
|
|
if (header.format.fixed > 1) return header.fact.fact_sample_len;
|
|
return header.data.data_len / (unsigned short)header.format.byte_per_sample;
|
|
}
|
|
|
|
|
|
uint WavInFile::getLengthMS() const
|
|
{
|
|
double numSamples;
|
|
double sampleRate;
|
|
|
|
numSamples = (double)getNumSamples();
|
|
sampleRate = (double)getSampleRate();
|
|
|
|
return (uint)(1000.0 * numSamples / sampleRate + 0.5);
|
|
}
|
|
|
|
|
|
/// Returns how many milliseconds of audio have so far been read from the file
|
|
uint WavInFile::getElapsedMS() const
|
|
{
|
|
return (uint)(1000.0 * (double)dataRead / (double)header.format.byte_rate);
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Class WavOutFile
|
|
//
|
|
|
|
WavOutFile::WavOutFile(const STRING& fileName, int sampleRate, int bits, int channels)
|
|
{
|
|
bytesWritten = 0;
|
|
fptr = FOPEN(fileName.c_str(), "wb");
|
|
if (fptr == nullptr)
|
|
{
|
|
ST_THROW_RT_ERROR("Error : Unable to open file for writing.");
|
|
}
|
|
|
|
fillInHeader(sampleRate, bits, channels);
|
|
writeHeader();
|
|
}
|
|
|
|
|
|
WavOutFile::WavOutFile(FILE* file, int sampleRate, int bits, int channels)
|
|
{
|
|
bytesWritten = 0;
|
|
fptr = file;
|
|
if (fptr == nullptr)
|
|
{
|
|
ST_THROW_RT_ERROR("Error : Unable to access output file stream.");
|
|
}
|
|
|
|
fillInHeader(sampleRate, bits, channels);
|
|
writeHeader();
|
|
}
|
|
|
|
|
|
WavOutFile::~WavOutFile()
|
|
{
|
|
finishHeader();
|
|
if (fptr) fclose(fptr);
|
|
fptr = nullptr;
|
|
}
|
|
|
|
|
|
void WavOutFile::fillInHeader(uint sampleRate, uint bits, uint channels)
|
|
{
|
|
// fill in the 'riff' part..
|
|
|
|
// copy string 'RIFF' to riff_char
|
|
memcpy(&(header.riff.riff_char), riffStr, 4);
|
|
// package_len unknown so far
|
|
header.riff.package_len = 0;
|
|
// copy string 'WAVE' to wave
|
|
memcpy(&(header.riff.wave), waveStr, 4);
|
|
|
|
// fill in the 'format' part..
|
|
|
|
// copy string 'fmt ' to fmt
|
|
memcpy(&(header.format.fmt), fmtStr, 4);
|
|
|
|
header.format.format_len = 0x10;
|
|
header.format.fixed = 1;
|
|
header.format.channel_number = (short)channels;
|
|
header.format.sample_rate = (int)sampleRate;
|
|
header.format.bits_per_sample = (short)bits;
|
|
header.format.byte_per_sample = (short)(bits * channels / 8);
|
|
header.format.byte_rate = header.format.byte_per_sample * (int)sampleRate;
|
|
header.format.sample_rate = (int)sampleRate;
|
|
|
|
// fill in the 'fact' part...
|
|
memcpy(&(header.fact.fact_field), factStr, 4);
|
|
header.fact.fact_len = 4;
|
|
header.fact.fact_sample_len = 0;
|
|
|
|
// fill in the 'data' part..
|
|
|
|
// copy string 'data' to data_field
|
|
memcpy(&(header.data.data_field), dataStr, 4);
|
|
// data_len unknown so far
|
|
header.data.data_len = 0;
|
|
}
|
|
|
|
|
|
void WavOutFile::finishHeader()
|
|
{
|
|
// supplement the file length into the header structure
|
|
header.riff.package_len = bytesWritten + sizeof(WavHeader) - sizeof(WavRiff) + 4;
|
|
header.data.data_len = bytesWritten;
|
|
header.fact.fact_sample_len = bytesWritten / header.format.byte_per_sample;
|
|
|
|
writeHeader();
|
|
}
|
|
|
|
|
|
void WavOutFile::writeHeader()
|
|
{
|
|
WavHeader hdrTemp;
|
|
int res;
|
|
|
|
// swap byte order if necessary
|
|
hdrTemp = header;
|
|
_swap32((int&)hdrTemp.riff.package_len);
|
|
_swap32((int&)hdrTemp.format.format_len);
|
|
_swap16((short&)hdrTemp.format.fixed);
|
|
_swap16((short&)hdrTemp.format.channel_number);
|
|
_swap32((int&)hdrTemp.format.sample_rate);
|
|
_swap32((int&)hdrTemp.format.byte_rate);
|
|
_swap16((short&)hdrTemp.format.byte_per_sample);
|
|
_swap16((short&)hdrTemp.format.bits_per_sample);
|
|
_swap32((int&)hdrTemp.data.data_len);
|
|
_swap32((int&)hdrTemp.fact.fact_len);
|
|
_swap32((int&)hdrTemp.fact.fact_sample_len);
|
|
|
|
// write the supplemented header in the beginning of the file
|
|
fseek(fptr, 0, SEEK_SET);
|
|
res = (int)fwrite(&hdrTemp, sizeof(hdrTemp), 1, fptr);
|
|
if (res != 1)
|
|
{
|
|
ST_THROW_RT_ERROR("Error while writing to a wav file.");
|
|
}
|
|
|
|
// jump back to the end of the file
|
|
fseek(fptr, 0, SEEK_END);
|
|
}
|
|
|
|
|
|
void WavOutFile::write(const unsigned char* buffer, int numElems)
|
|
{
|
|
int res;
|
|
|
|
if (header.format.bits_per_sample != 8)
|
|
{
|
|
ST_THROW_RT_ERROR("Error: WavOutFile::write(const char*, int) accepts only 8bit samples.");
|
|
}
|
|
assert(sizeof(char) == 1);
|
|
|
|
res = (int)fwrite(buffer, 1, numElems, fptr);
|
|
if (res != numElems)
|
|
{
|
|
ST_THROW_RT_ERROR("Error while writing to a wav file.");
|
|
}
|
|
|
|
bytesWritten += numElems;
|
|
}
|
|
|
|
|
|
void WavOutFile::write(const short* buffer, int numElems)
|
|
{
|
|
int res;
|
|
|
|
// 16 bit samples
|
|
if (numElems < 1) return; // nothing to do
|
|
|
|
switch (header.format.bits_per_sample)
|
|
{
|
|
case 8:
|
|
{
|
|
int i;
|
|
unsigned char* temp = (unsigned char*)getConvBuffer(numElems);
|
|
// convert from 16bit format to 8bit format
|
|
for (i = 0; i < numElems; i++)
|
|
{
|
|
temp[i] = (unsigned char)(buffer[i] / 256 + 128);
|
|
}
|
|
// write in 8bit format
|
|
write(temp, numElems);
|
|
break;
|
|
}
|
|
|
|
case 16:
|
|
{
|
|
// 16bit format
|
|
|
|
// use temp buffer to swap byte order if necessary
|
|
short* pTemp = (short*)getConvBuffer(numElems * sizeof(short));
|
|
memcpy(pTemp, buffer, (size_t)numElems * 2L);
|
|
_swap16Buffer(pTemp, numElems);
|
|
|
|
res = (int)fwrite(pTemp, 2, numElems, fptr);
|
|
|
|
if (res != numElems)
|
|
{
|
|
ST_THROW_RT_ERROR("Error while writing to a wav file.");
|
|
}
|
|
bytesWritten += 2 * numElems;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
stringstream ss;
|
|
ss << "\nOnly 8/16 bit sample WAV files supported in integer compilation. Can't open WAV file with ";
|
|
ss << (int)header.format.bits_per_sample;
|
|
ss << " bit sample format. ";
|
|
ST_THROW_RT_ERROR(ss.str().c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/// Convert from float to integer and saturate
|
|
inline int saturate(float fvalue, float minval, float maxval)
|
|
{
|
|
if (fvalue > maxval)
|
|
{
|
|
fvalue = maxval;
|
|
}
|
|
else if (fvalue < minval)
|
|
{
|
|
fvalue = minval;
|
|
}
|
|
return (int)fvalue;
|
|
}
|
|
|
|
|
|
void WavOutFile::write(const float* buffer, int numElems)
|
|
{
|
|
int numBytes;
|
|
int bytesPerSample;
|
|
|
|
if (numElems == 0) return;
|
|
|
|
bytesPerSample = header.format.bits_per_sample / 8;
|
|
numBytes = numElems * bytesPerSample;
|
|
void* temp = getConvBuffer(numBytes + 7); // round bit up to avoid buffer overrun with 24bit-value assignment
|
|
|
|
switch (bytesPerSample)
|
|
{
|
|
case 1:
|
|
{
|
|
unsigned char* temp2 = (unsigned char*)temp;
|
|
for (int i = 0; i < numElems; i++)
|
|
{
|
|
temp2[i] = (unsigned char)saturate(buffer[i] * 128.0f + 128.0f, 0.0f, 255.0f);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 2:
|
|
{
|
|
short* temp2 = (short*)temp;
|
|
for (int i = 0; i < numElems; i++)
|
|
{
|
|
short value = (short)saturate(buffer[i] * 32768.0f, -32768.0f, 32767.0f);
|
|
temp2[i] = _swap16(value);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 3:
|
|
{
|
|
char* temp2 = (char*)temp;
|
|
for (int i = 0; i < numElems; i++)
|
|
{
|
|
int value = saturate(buffer[i] * 8388608.0f, -8388608.0f, 8388607.0f);
|
|
*((int*)temp2) = _swap32(value);
|
|
temp2 += 3;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 4:
|
|
{
|
|
int* temp2 = (int*)temp;
|
|
for (int i = 0; i < numElems; i++)
|
|
{
|
|
int value = saturate(buffer[i] * 2147483648.0f, -2147483648.0f, 2147483647.0f);
|
|
temp2[i] = _swap32(value);
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
assert(false);
|
|
}
|
|
|
|
int res = (int)fwrite(temp, 1, numBytes, fptr);
|
|
|
|
if (res != numBytes)
|
|
{
|
|
ST_THROW_RT_ERROR("Error while writing to a wav file.");
|
|
}
|
|
bytesWritten += numBytes;
|
|
}
|
|
|
|
}
|