|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#ifndef _AS_AudioFile_h |
|
|
#define _AS_AudioFile_h |
|
|
|
|
|
#include <iostream> |
|
|
#include <vector> |
|
|
#include <cassert> |
|
|
#include <string> |
|
|
#include <cstring> |
|
|
#include <fstream> |
|
|
#include <unordered_map> |
|
|
#include <iterator> |
|
|
#include <algorithm> |
|
|
|
|
|
|
|
|
#if defined (_MSC_VER) |
|
|
__pragma(warning (push)) |
|
|
__pragma(warning (disable : 4244)) |
|
|
__pragma(warning (disable : 4457)) |
|
|
__pragma(warning (disable : 4458)) |
|
|
__pragma(warning (disable : 4389)) |
|
|
__pragma(warning (disable : 4996)) |
|
|
#elif defined (__GNUC__) |
|
|
_Pragma("GCC diagnostic push") |
|
|
_Pragma("GCC diagnostic ignored \"-Wconversion\"") |
|
|
_Pragma("GCC diagnostic ignored \"-Wsign-compare\"") |
|
|
_Pragma("GCC diagnostic ignored \"-Wshadow\"") |
|
|
#endif |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
enum class AudioFileFormat |
|
|
{ |
|
|
Error, |
|
|
NotLoaded, |
|
|
Wave, |
|
|
Aiff |
|
|
}; |
|
|
|
|
|
|
|
|
template <class T> |
|
|
class AudioFile |
|
|
{ |
|
|
public: |
|
|
|
|
|
|
|
|
typedef std::vector<std::vector<T> > AudioBuffer; |
|
|
|
|
|
|
|
|
|
|
|
AudioFile(); |
|
|
|
|
|
|
|
|
AudioFile (std::string filePath); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool load (std::string filePath); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool save (std::string filePath, AudioFileFormat format = AudioFileFormat::Wave); |
|
|
|
|
|
|
|
|
|
|
|
uint32_t getSampleRate() const; |
|
|
|
|
|
|
|
|
int getNumChannels() const; |
|
|
|
|
|
|
|
|
bool isMono() const; |
|
|
|
|
|
|
|
|
bool isStereo() const; |
|
|
|
|
|
|
|
|
int getBitDepth() const; |
|
|
|
|
|
|
|
|
int getNumSamplesPerChannel() const; |
|
|
|
|
|
|
|
|
double getLengthInSeconds() const; |
|
|
|
|
|
|
|
|
void printSummary() const; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool setAudioBuffer (AudioBuffer& newBuffer); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void setAudioBufferSize (int numChannels, int numSamples); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void setNumSamplesPerChannel (int numSamples); |
|
|
|
|
|
|
|
|
void setNumChannels (int numChannels); |
|
|
|
|
|
|
|
|
void setBitDepth (int numBitsPerSample); |
|
|
|
|
|
|
|
|
void setSampleRate (uint32_t newSampleRate); |
|
|
|
|
|
|
|
|
|
|
|
void shouldLogErrorsToConsole (bool logErrors); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
AudioBuffer samples; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
std::string iXMLChunk; |
|
|
|
|
|
private: |
|
|
|
|
|
|
|
|
enum class Endianness |
|
|
{ |
|
|
LittleEndian, |
|
|
BigEndian |
|
|
}; |
|
|
|
|
|
|
|
|
AudioFileFormat determineAudioFileFormat (std::vector<uint8_t>& fileData); |
|
|
bool decodeWaveFile (std::vector<uint8_t>& fileData); |
|
|
bool decodeAiffFile (std::vector<uint8_t>& fileData); |
|
|
|
|
|
|
|
|
bool saveToWaveFile (std::string filePath); |
|
|
bool saveToAiffFile (std::string filePath); |
|
|
|
|
|
|
|
|
void clearAudioBuffer(); |
|
|
|
|
|
|
|
|
int32_t fourBytesToInt (std::vector<uint8_t>& source, int startIndex, Endianness endianness = Endianness::LittleEndian); |
|
|
int16_t twoBytesToInt (std::vector<uint8_t>& source, int startIndex, Endianness endianness = Endianness::LittleEndian); |
|
|
int getIndexOfString (std::vector<uint8_t>& source, std::string s); |
|
|
int getIndexOfChunk (std::vector<uint8_t>& source, const std::string& chunkHeaderID, int startIndex, Endianness endianness = Endianness::LittleEndian); |
|
|
|
|
|
|
|
|
T sixteenBitIntToSample (int16_t sample); |
|
|
int16_t sampleToSixteenBitInt (T sample); |
|
|
|
|
|
|
|
|
uint8_t sampleToSingleByte (T sample); |
|
|
T singleByteToSample (uint8_t sample); |
|
|
|
|
|
uint32_t getAiffSampleRate (std::vector<uint8_t>& fileData, int sampleRateStartIndex); |
|
|
bool tenByteMatch (std::vector<uint8_t>& v1, int startIndex1, std::vector<uint8_t>& v2, int startIndex2); |
|
|
void addSampleRateToAiffData (std::vector<uint8_t>& fileData, uint32_t sampleRate); |
|
|
T clamp (T v1, T minValue, T maxValue); |
|
|
|
|
|
|
|
|
void addStringToFileData (std::vector<uint8_t>& fileData, std::string s); |
|
|
void addInt32ToFileData (std::vector<uint8_t>& fileData, int32_t i, Endianness endianness = Endianness::LittleEndian); |
|
|
void addInt16ToFileData (std::vector<uint8_t>& fileData, int16_t i, Endianness endianness = Endianness::LittleEndian); |
|
|
|
|
|
|
|
|
bool writeDataToFile (std::vector<uint8_t>& fileData, std::string filePath); |
|
|
|
|
|
|
|
|
void reportError (std::string errorMessage); |
|
|
|
|
|
|
|
|
AudioFileFormat audioFileFormat; |
|
|
uint32_t sampleRate; |
|
|
int bitDepth; |
|
|
bool logErrorsToConsole {true}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static std::unordered_map <uint32_t, std::vector<uint8_t>> aiffSampleRateTable = { |
|
|
{8000, {64, 11, 250, 0, 0, 0, 0, 0, 0, 0}}, |
|
|
{11025, {64, 12, 172, 68, 0, 0, 0, 0, 0, 0}}, |
|
|
{16000, {64, 12, 250, 0, 0, 0, 0, 0, 0, 0}}, |
|
|
{22050, {64, 13, 172, 68, 0, 0, 0, 0, 0, 0}}, |
|
|
{32000, {64, 13, 250, 0, 0, 0, 0, 0, 0, 0}}, |
|
|
{37800, {64, 14, 147, 168, 0, 0, 0, 0, 0, 0}}, |
|
|
{44056, {64, 14, 172, 24, 0, 0, 0, 0, 0, 0}}, |
|
|
{44100, {64, 14, 172, 68, 0, 0, 0, 0, 0, 0}}, |
|
|
{47250, {64, 14, 184, 146, 0, 0, 0, 0, 0, 0}}, |
|
|
{48000, {64, 14, 187, 128, 0, 0, 0, 0, 0, 0}}, |
|
|
{50000, {64, 14, 195, 80, 0, 0, 0, 0, 0, 0}}, |
|
|
{50400, {64, 14, 196, 224, 0, 0, 0, 0, 0, 0}}, |
|
|
{88200, {64, 15, 172, 68, 0, 0, 0, 0, 0, 0}}, |
|
|
{96000, {64, 15, 187, 128, 0, 0, 0, 0, 0, 0}}, |
|
|
{176400, {64, 16, 172, 68, 0, 0, 0, 0, 0, 0}}, |
|
|
{192000, {64, 16, 187, 128, 0, 0, 0, 0, 0, 0}}, |
|
|
{352800, {64, 17, 172, 68, 0, 0, 0, 0, 0, 0}}, |
|
|
{2822400, {64, 20, 172, 68, 0, 0, 0, 0, 0, 0}}, |
|
|
{5644800, {64, 21, 172, 68, 0, 0, 0, 0, 0, 0}} |
|
|
}; |
|
|
|
|
|
|
|
|
enum WavAudioFormat |
|
|
{ |
|
|
PCM = 0x0001, |
|
|
IEEEFloat = 0x0003, |
|
|
ALaw = 0x0006, |
|
|
MULaw = 0x0007, |
|
|
Extensible = 0xFFFE |
|
|
}; |
|
|
|
|
|
|
|
|
enum AIFFAudioFormat |
|
|
{ |
|
|
Uncompressed, |
|
|
Compressed, |
|
|
Error |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
template <class T> |
|
|
AudioFile<T>::AudioFile() |
|
|
{ |
|
|
static_assert(std::is_floating_point<T>::value, "ERROR: This version of AudioFile only supports floating point sample formats"); |
|
|
|
|
|
bitDepth = 16; |
|
|
sampleRate = 44100; |
|
|
samples.resize (1); |
|
|
samples[0].resize (0); |
|
|
audioFileFormat = AudioFileFormat::NotLoaded; |
|
|
} |
|
|
|
|
|
|
|
|
template <class T> |
|
|
AudioFile<T>::AudioFile (std::string filePath) |
|
|
: AudioFile<T>() |
|
|
{ |
|
|
load (filePath); |
|
|
} |
|
|
|
|
|
|
|
|
template <class T> |
|
|
uint32_t AudioFile<T>::getSampleRate() const |
|
|
{ |
|
|
return sampleRate; |
|
|
} |
|
|
|
|
|
|
|
|
template <class T> |
|
|
int AudioFile<T>::getNumChannels() const |
|
|
{ |
|
|
return (int)samples.size(); |
|
|
} |
|
|
|
|
|
|
|
|
template <class T> |
|
|
bool AudioFile<T>::isMono() const |
|
|
{ |
|
|
return getNumChannels() == 1; |
|
|
} |
|
|
|
|
|
|
|
|
template <class T> |
|
|
bool AudioFile<T>::isStereo() const |
|
|
{ |
|
|
return getNumChannels() == 2; |
|
|
} |
|
|
|
|
|
|
|
|
template <class T> |
|
|
int AudioFile<T>::getBitDepth() const |
|
|
{ |
|
|
return bitDepth; |
|
|
} |
|
|
|
|
|
|
|
|
template <class T> |
|
|
int AudioFile<T>::getNumSamplesPerChannel() const |
|
|
{ |
|
|
if (samples.size() > 0) |
|
|
return (int) samples[0].size(); |
|
|
else |
|
|
return 0; |
|
|
} |
|
|
|
|
|
|
|
|
template <class T> |
|
|
double AudioFile<T>::getLengthInSeconds() const |
|
|
{ |
|
|
return (double)getNumSamplesPerChannel() / (double)sampleRate; |
|
|
} |
|
|
|
|
|
|
|
|
template <class T> |
|
|
void AudioFile<T>::printSummary() const |
|
|
{ |
|
|
std::cout << "|======================================|" << std::endl; |
|
|
std::cout << "Num Channels: " << getNumChannels() << std::endl; |
|
|
std::cout << "Num Samples Per Channel: " << getNumSamplesPerChannel() << std::endl; |
|
|
std::cout << "Sample Rate: " << sampleRate << std::endl; |
|
|
std::cout << "Bit Depth: " << bitDepth << std::endl; |
|
|
std::cout << "Length in Seconds: " << getLengthInSeconds() << std::endl; |
|
|
std::cout << "|======================================|" << std::endl; |
|
|
} |
|
|
|
|
|
|
|
|
template <class T> |
|
|
bool AudioFile<T>::setAudioBuffer (AudioBuffer& newBuffer) |
|
|
{ |
|
|
int numChannels = (int)newBuffer.size(); |
|
|
|
|
|
if (numChannels <= 0) |
|
|
{ |
|
|
assert (false && "The buffer your are trying to use has no channels"); |
|
|
return false; |
|
|
} |
|
|
|
|
|
size_t numSamples = newBuffer[0].size(); |
|
|
|
|
|
|
|
|
samples.resize (newBuffer.size()); |
|
|
|
|
|
for (int k = 0; k < getNumChannels(); k++) |
|
|
{ |
|
|
assert (newBuffer[k].size() == numSamples); |
|
|
|
|
|
samples[k].resize (numSamples); |
|
|
|
|
|
for (size_t i = 0; i < numSamples; i++) |
|
|
{ |
|
|
samples[k][i] = newBuffer[k][i]; |
|
|
} |
|
|
} |
|
|
|
|
|
return true; |
|
|
} |
|
|
|
|
|
|
|
|
template <class T> |
|
|
void AudioFile<T>::setAudioBufferSize (int numChannels, int numSamples) |
|
|
{ |
|
|
samples.resize (numChannels); |
|
|
setNumSamplesPerChannel (numSamples); |
|
|
} |
|
|
|
|
|
|
|
|
template <class T> |
|
|
void AudioFile<T>::setNumSamplesPerChannel (int numSamples) |
|
|
{ |
|
|
int originalSize = getNumSamplesPerChannel(); |
|
|
|
|
|
for (int i = 0; i < getNumChannels();i++) |
|
|
{ |
|
|
samples[i].resize (numSamples); |
|
|
|
|
|
|
|
|
if (numSamples > originalSize) |
|
|
std::fill (samples[i].begin() + originalSize, samples[i].end(), (T)0.); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
template <class T> |
|
|
void AudioFile<T>::setNumChannels (int numChannels) |
|
|
{ |
|
|
int originalNumChannels = getNumChannels(); |
|
|
int originalNumSamplesPerChannel = getNumSamplesPerChannel(); |
|
|
|
|
|
samples.resize (numChannels); |
|
|
|
|
|
|
|
|
|
|
|
if (numChannels > originalNumChannels) |
|
|
{ |
|
|
for (int i = originalNumChannels; i < numChannels; i++) |
|
|
{ |
|
|
samples[i].resize (originalNumSamplesPerChannel); |
|
|
std::fill (samples[i].begin(), samples[i].end(), (T)0.); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
template <class T> |
|
|
void AudioFile<T>::setBitDepth (int numBitsPerSample) |
|
|
{ |
|
|
bitDepth = numBitsPerSample; |
|
|
} |
|
|
|
|
|
|
|
|
template <class T> |
|
|
void AudioFile<T>::setSampleRate (uint32_t newSampleRate) |
|
|
{ |
|
|
sampleRate = newSampleRate; |
|
|
} |
|
|
|
|
|
|
|
|
template <class T> |
|
|
void AudioFile<T>::shouldLogErrorsToConsole (bool logErrors) |
|
|
{ |
|
|
logErrorsToConsole = logErrors; |
|
|
} |
|
|
|
|
|
|
|
|
template <class T> |
|
|
bool AudioFile<T>::load (std::string filePath) |
|
|
{ |
|
|
std::ifstream file (filePath, std::ios::binary); |
|
|
|
|
|
|
|
|
if (! file.good()) |
|
|
{ |
|
|
reportError ("ERROR: File doesn't exist or otherwise can't load file\n" + filePath); |
|
|
return false; |
|
|
} |
|
|
|
|
|
std::vector<uint8_t> fileData; |
|
|
|
|
|
file.unsetf (std::ios::skipws); |
|
|
|
|
|
file.seekg (0, std::ios::end); |
|
|
size_t length = file.tellg(); |
|
|
file.seekg (0, std::ios::beg); |
|
|
|
|
|
|
|
|
fileData.resize (length); |
|
|
|
|
|
file.read(reinterpret_cast<char*> (fileData.data()), length); |
|
|
file.close(); |
|
|
|
|
|
if (file.gcount() != length) |
|
|
{ |
|
|
reportError ("ERROR: Couldn't read entire file\n" + filePath); |
|
|
return false; |
|
|
} |
|
|
|
|
|
|
|
|
audioFileFormat = determineAudioFileFormat (fileData); |
|
|
|
|
|
if (audioFileFormat == AudioFileFormat::Wave) |
|
|
{ |
|
|
return decodeWaveFile (fileData); |
|
|
} |
|
|
else if (audioFileFormat == AudioFileFormat::Aiff) |
|
|
{ |
|
|
return decodeAiffFile (fileData); |
|
|
} |
|
|
else |
|
|
{ |
|
|
reportError ("Audio File Type: Error"); |
|
|
return false; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
template <class T> |
|
|
bool AudioFile<T>::decodeWaveFile (std::vector<uint8_t>& fileData) |
|
|
{ |
|
|
|
|
|
|
|
|
std::string headerChunkID (fileData.begin(), fileData.begin() + 4); |
|
|
|
|
|
std::string format (fileData.begin() + 8, fileData.begin() + 12); |
|
|
|
|
|
|
|
|
|
|
|
int indexOfDataChunk = getIndexOfChunk (fileData, "data", 12); |
|
|
int indexOfFormatChunk = getIndexOfChunk (fileData, "fmt ", 12); |
|
|
int indexOfXMLChunk = getIndexOfChunk (fileData, "iXML", 12); |
|
|
|
|
|
|
|
|
|
|
|
if (indexOfDataChunk == -1 || indexOfFormatChunk == -1 || headerChunkID != "RIFF" || format != "WAVE") |
|
|
{ |
|
|
reportError ("ERROR: this doesn't seem to be a valid .WAV file"); |
|
|
return false; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
int f = indexOfFormatChunk; |
|
|
std::string formatChunkID (fileData.begin() + f, fileData.begin() + f + 4); |
|
|
|
|
|
uint16_t audioFormat = twoBytesToInt (fileData, f + 8); |
|
|
uint16_t numChannels = twoBytesToInt (fileData, f + 10); |
|
|
sampleRate = (uint32_t) fourBytesToInt (fileData, f + 12); |
|
|
uint32_t numBytesPerSecond = fourBytesToInt (fileData, f + 16); |
|
|
uint16_t numBytesPerBlock = twoBytesToInt (fileData, f + 20); |
|
|
bitDepth = (int) twoBytesToInt (fileData, f + 22); |
|
|
|
|
|
uint16_t numBytesPerSample = static_cast<uint16_t> (bitDepth) / 8; |
|
|
|
|
|
|
|
|
if (audioFormat != WavAudioFormat::PCM && audioFormat != WavAudioFormat::IEEEFloat && audioFormat != WavAudioFormat::Extensible) |
|
|
{ |
|
|
reportError ("ERROR: this .WAV file is encoded in a format that this library does not support at present"); |
|
|
return false; |
|
|
} |
|
|
|
|
|
|
|
|
if (numChannels < 1 || numChannels > 128) |
|
|
{ |
|
|
reportError ("ERROR: this WAV file seems to be an invalid number of channels (or corrupted?)"); |
|
|
return false; |
|
|
} |
|
|
|
|
|
|
|
|
if (numBytesPerSecond != static_cast<uint32_t> ((numChannels * sampleRate * bitDepth) / 8) || numBytesPerBlock != (numChannels * numBytesPerSample)) |
|
|
{ |
|
|
reportError ("ERROR: the header data in this WAV file seems to be inconsistent"); |
|
|
return false; |
|
|
} |
|
|
|
|
|
|
|
|
if (bitDepth != 8 && bitDepth != 16 && bitDepth != 24 && bitDepth != 32) |
|
|
{ |
|
|
reportError ("ERROR: this file has a bit depth that is not 8, 16, 24 or 32 bits"); |
|
|
return false; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
int d = indexOfDataChunk; |
|
|
std::string dataChunkID (fileData.begin() + d, fileData.begin() + d + 4); |
|
|
int32_t dataChunkSize = fourBytesToInt (fileData, d + 4); |
|
|
|
|
|
int numSamples = dataChunkSize / (numChannels * bitDepth / 8); |
|
|
int samplesStartIndex = indexOfDataChunk + 8; |
|
|
|
|
|
clearAudioBuffer(); |
|
|
samples.resize (numChannels); |
|
|
|
|
|
for (int i = 0; i < numSamples; i++) |
|
|
{ |
|
|
for (int channel = 0; channel < numChannels; channel++) |
|
|
{ |
|
|
int sampleIndex = samplesStartIndex + (numBytesPerBlock * i) + channel * numBytesPerSample; |
|
|
|
|
|
if ((sampleIndex + (bitDepth / 8) - 1) >= fileData.size()) |
|
|
{ |
|
|
reportError ("ERROR: read file error as the metadata indicates more samples than there are in the file data"); |
|
|
return false; |
|
|
} |
|
|
|
|
|
if (bitDepth == 8) |
|
|
{ |
|
|
T sample = singleByteToSample (fileData[sampleIndex]); |
|
|
samples[channel].push_back (sample); |
|
|
} |
|
|
else if (bitDepth == 16) |
|
|
{ |
|
|
int16_t sampleAsInt = twoBytesToInt (fileData, sampleIndex); |
|
|
T sample = sixteenBitIntToSample (sampleAsInt); |
|
|
samples[channel].push_back (sample); |
|
|
} |
|
|
else if (bitDepth == 24) |
|
|
{ |
|
|
int32_t sampleAsInt = 0; |
|
|
sampleAsInt = (fileData[sampleIndex + 2] << 16) | (fileData[sampleIndex + 1] << 8) | fileData[sampleIndex]; |
|
|
|
|
|
if (sampleAsInt & 0x800000) |
|
|
sampleAsInt = sampleAsInt | ~0xFFFFFF; |
|
|
|
|
|
T sample = (T)sampleAsInt / (T)8388608.; |
|
|
samples[channel].push_back (sample); |
|
|
} |
|
|
else if (bitDepth == 32) |
|
|
{ |
|
|
int32_t sampleAsInt = fourBytesToInt (fileData, sampleIndex); |
|
|
T sample; |
|
|
|
|
|
if (audioFormat == WavAudioFormat::IEEEFloat) |
|
|
sample = (T)reinterpret_cast<float&> (sampleAsInt); |
|
|
else |
|
|
sample = (T) sampleAsInt / static_cast<float> (std::numeric_limits<std::int32_t>::max()); |
|
|
|
|
|
samples[channel].push_back (sample); |
|
|
} |
|
|
else |
|
|
{ |
|
|
assert (false); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (indexOfXMLChunk != -1) |
|
|
{ |
|
|
int32_t chunkSize = fourBytesToInt (fileData, indexOfXMLChunk + 4); |
|
|
iXMLChunk = std::string ((const char*) &fileData[indexOfXMLChunk + 8], chunkSize); |
|
|
} |
|
|
|
|
|
return true; |
|
|
} |
|
|
|
|
|
|
|
|
template <class T> |
|
|
bool AudioFile<T>::decodeAiffFile (std::vector<uint8_t>& fileData) |
|
|
{ |
|
|
|
|
|
|
|
|
std::string headerChunkID (fileData.begin(), fileData.begin() + 4); |
|
|
|
|
|
std::string format (fileData.begin() + 8, fileData.begin() + 12); |
|
|
|
|
|
int audioFormat = format == "AIFF" ? AIFFAudioFormat::Uncompressed : format == "AIFC" ? AIFFAudioFormat::Compressed : AIFFAudioFormat::Error; |
|
|
|
|
|
|
|
|
|
|
|
int indexOfCommChunk = getIndexOfChunk (fileData, "COMM", 12, Endianness::BigEndian); |
|
|
int indexOfSoundDataChunk = getIndexOfChunk (fileData, "SSND", 12, Endianness::BigEndian); |
|
|
int indexOfXMLChunk = getIndexOfChunk (fileData, "iXML", 12, Endianness::BigEndian); |
|
|
|
|
|
|
|
|
|
|
|
if (indexOfSoundDataChunk == -1 || indexOfCommChunk == -1 || headerChunkID != "FORM" || audioFormat == AIFFAudioFormat::Error) |
|
|
{ |
|
|
reportError ("ERROR: this doesn't seem to be a valid AIFF file"); |
|
|
return false; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
int p = indexOfCommChunk; |
|
|
std::string commChunkID (fileData.begin() + p, fileData.begin() + p + 4); |
|
|
|
|
|
int16_t numChannels = twoBytesToInt (fileData, p + 8, Endianness::BigEndian); |
|
|
int32_t numSamplesPerChannel = fourBytesToInt (fileData, p + 10, Endianness::BigEndian); |
|
|
bitDepth = (int) twoBytesToInt (fileData, p + 14, Endianness::BigEndian); |
|
|
sampleRate = getAiffSampleRate (fileData, p + 16); |
|
|
|
|
|
|
|
|
if (sampleRate == 0) |
|
|
{ |
|
|
reportError ("ERROR: this AIFF file has an unsupported sample rate"); |
|
|
return false; |
|
|
} |
|
|
|
|
|
|
|
|
if (numChannels < 1 ||numChannels > 2) |
|
|
{ |
|
|
reportError ("ERROR: this AIFF file seems to be neither mono nor stereo (perhaps multi-track, or corrupted?)"); |
|
|
return false; |
|
|
} |
|
|
|
|
|
|
|
|
if (bitDepth != 8 && bitDepth != 16 && bitDepth != 24 && bitDepth != 32) |
|
|
{ |
|
|
reportError ("ERROR: this file has a bit depth that is not 8, 16, 24 or 32 bits"); |
|
|
return false; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
int s = indexOfSoundDataChunk; |
|
|
std::string soundDataChunkID (fileData.begin() + s, fileData.begin() + s + 4); |
|
|
int32_t soundDataChunkSize = fourBytesToInt (fileData, s + 4, Endianness::BigEndian); |
|
|
int32_t offset = fourBytesToInt (fileData, s + 8, Endianness::BigEndian); |
|
|
|
|
|
|
|
|
int numBytesPerSample = bitDepth / 8; |
|
|
int numBytesPerFrame = numBytesPerSample * numChannels; |
|
|
int totalNumAudioSampleBytes = numSamplesPerChannel * numBytesPerFrame; |
|
|
int samplesStartIndex = s + 16 + (int)offset; |
|
|
|
|
|
|
|
|
if ((soundDataChunkSize - 8) != totalNumAudioSampleBytes || totalNumAudioSampleBytes > static_cast<long>(fileData.size() - samplesStartIndex)) |
|
|
{ |
|
|
reportError ("ERROR: the metadatafor this file doesn't seem right"); |
|
|
return false; |
|
|
} |
|
|
|
|
|
clearAudioBuffer(); |
|
|
samples.resize (numChannels); |
|
|
|
|
|
for (int i = 0; i < numSamplesPerChannel; i++) |
|
|
{ |
|
|
for (int channel = 0; channel < numChannels; channel++) |
|
|
{ |
|
|
int sampleIndex = samplesStartIndex + (numBytesPerFrame * i) + channel * numBytesPerSample; |
|
|
|
|
|
if ((sampleIndex + (bitDepth / 8) - 1) >= fileData.size()) |
|
|
{ |
|
|
reportError ("ERROR: read file error as the metadata indicates more samples than there are in the file data"); |
|
|
return false; |
|
|
} |
|
|
|
|
|
if (bitDepth == 8) |
|
|
{ |
|
|
int8_t sampleAsSigned8Bit = (int8_t)fileData[sampleIndex]; |
|
|
T sample = (T)sampleAsSigned8Bit / (T)128.; |
|
|
samples[channel].push_back (sample); |
|
|
} |
|
|
else if (bitDepth == 16) |
|
|
{ |
|
|
int16_t sampleAsInt = twoBytesToInt (fileData, sampleIndex, Endianness::BigEndian); |
|
|
T sample = sixteenBitIntToSample (sampleAsInt); |
|
|
samples[channel].push_back (sample); |
|
|
} |
|
|
else if (bitDepth == 24) |
|
|
{ |
|
|
int32_t sampleAsInt = 0; |
|
|
sampleAsInt = (fileData[sampleIndex] << 16) | (fileData[sampleIndex + 1] << 8) | fileData[sampleIndex + 2]; |
|
|
|
|
|
if (sampleAsInt & 0x800000) |
|
|
sampleAsInt = sampleAsInt | ~0xFFFFFF; |
|
|
|
|
|
T sample = (T)sampleAsInt / (T)8388608.; |
|
|
samples[channel].push_back (sample); |
|
|
} |
|
|
else if (bitDepth == 32) |
|
|
{ |
|
|
int32_t sampleAsInt = fourBytesToInt (fileData, sampleIndex, Endianness::BigEndian); |
|
|
T sample; |
|
|
|
|
|
if (audioFormat == AIFFAudioFormat::Compressed) |
|
|
sample = (T)reinterpret_cast<float&> (sampleAsInt); |
|
|
else |
|
|
sample = (T) sampleAsInt / static_cast<float> (std::numeric_limits<std::int32_t>::max()); |
|
|
|
|
|
samples[channel].push_back (sample); |
|
|
} |
|
|
else |
|
|
{ |
|
|
assert (false); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (indexOfXMLChunk != -1) |
|
|
{ |
|
|
int32_t chunkSize = fourBytesToInt (fileData, indexOfXMLChunk + 4); |
|
|
iXMLChunk = std::string ((const char*) &fileData[indexOfXMLChunk + 8], chunkSize); |
|
|
} |
|
|
|
|
|
return true; |
|
|
} |
|
|
|
|
|
|
|
|
template <class T> |
|
|
uint32_t AudioFile<T>::getAiffSampleRate (std::vector<uint8_t>& fileData, int sampleRateStartIndex) |
|
|
{ |
|
|
for (auto it : aiffSampleRateTable) |
|
|
{ |
|
|
if (tenByteMatch (fileData, sampleRateStartIndex, it.second, 0)) |
|
|
return it.first; |
|
|
} |
|
|
|
|
|
return 0; |
|
|
} |
|
|
|
|
|
|
|
|
template <class T> |
|
|
bool AudioFile<T>::tenByteMatch (std::vector<uint8_t>& v1, int startIndex1, std::vector<uint8_t>& v2, int startIndex2) |
|
|
{ |
|
|
for (int i = 0; i < 10; i++) |
|
|
{ |
|
|
if (v1[startIndex1 + i] != v2[startIndex2 + i]) |
|
|
return false; |
|
|
} |
|
|
|
|
|
return true; |
|
|
} |
|
|
|
|
|
|
|
|
template <class T> |
|
|
void AudioFile<T>::addSampleRateToAiffData (std::vector<uint8_t>& fileData, uint32_t sampleRate) |
|
|
{ |
|
|
if (aiffSampleRateTable.count (sampleRate) > 0) |
|
|
{ |
|
|
for (int i = 0; i < 10; i++) |
|
|
fileData.push_back (aiffSampleRateTable[sampleRate][i]); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
template <class T> |
|
|
bool AudioFile<T>::save (std::string filePath, AudioFileFormat format) |
|
|
{ |
|
|
if (format == AudioFileFormat::Wave) |
|
|
{ |
|
|
return saveToWaveFile (filePath); |
|
|
} |
|
|
else if (format == AudioFileFormat::Aiff) |
|
|
{ |
|
|
return saveToAiffFile (filePath); |
|
|
} |
|
|
|
|
|
return false; |
|
|
} |
|
|
|
|
|
|
|
|
template <class T> |
|
|
bool AudioFile<T>::saveToWaveFile (std::string filePath) |
|
|
{ |
|
|
std::vector<uint8_t> fileData; |
|
|
|
|
|
int32_t dataChunkSize = getNumSamplesPerChannel() * (getNumChannels() * bitDepth / 8); |
|
|
int16_t audioFormat = bitDepth == 32 ? WavAudioFormat::IEEEFloat : WavAudioFormat::PCM; |
|
|
int32_t formatChunkSize = audioFormat == WavAudioFormat::PCM ? 16 : 18; |
|
|
int32_t iXMLChunkSize = static_cast<int32_t> (iXMLChunk.size()); |
|
|
|
|
|
|
|
|
|
|
|
addStringToFileData (fileData, "RIFF"); |
|
|
|
|
|
|
|
|
|
|
|
int32_t fileSizeInBytes = 4 + formatChunkSize + 8 + 8 + dataChunkSize; |
|
|
if (iXMLChunkSize > 0) |
|
|
{ |
|
|
fileSizeInBytes += (8 + iXMLChunkSize); |
|
|
} |
|
|
|
|
|
addInt32ToFileData (fileData, fileSizeInBytes); |
|
|
|
|
|
addStringToFileData (fileData, "WAVE"); |
|
|
|
|
|
|
|
|
|
|
|
addStringToFileData (fileData, "fmt "); |
|
|
addInt32ToFileData (fileData, formatChunkSize); |
|
|
addInt16ToFileData (fileData, audioFormat); |
|
|
addInt16ToFileData (fileData, (int16_t)getNumChannels()); |
|
|
addInt32ToFileData (fileData, (int32_t)sampleRate); |
|
|
|
|
|
int32_t numBytesPerSecond = (int32_t) ((getNumChannels() * sampleRate * bitDepth) / 8); |
|
|
addInt32ToFileData (fileData, numBytesPerSecond); |
|
|
|
|
|
int16_t numBytesPerBlock = getNumChannels() * (bitDepth / 8); |
|
|
addInt16ToFileData (fileData, numBytesPerBlock); |
|
|
|
|
|
addInt16ToFileData (fileData, (int16_t)bitDepth); |
|
|
|
|
|
if (audioFormat == WavAudioFormat::IEEEFloat) |
|
|
addInt16ToFileData (fileData, 0); |
|
|
|
|
|
|
|
|
|
|
|
addStringToFileData (fileData, "data"); |
|
|
addInt32ToFileData (fileData, dataChunkSize); |
|
|
|
|
|
for (int i = 0; i < getNumSamplesPerChannel(); i++) |
|
|
{ |
|
|
for (int channel = 0; channel < getNumChannels(); channel++) |
|
|
{ |
|
|
if (bitDepth == 8) |
|
|
{ |
|
|
uint8_t byte = sampleToSingleByte (samples[channel][i]); |
|
|
fileData.push_back (byte); |
|
|
} |
|
|
else if (bitDepth == 16) |
|
|
{ |
|
|
int16_t sampleAsInt = sampleToSixteenBitInt (samples[channel][i]); |
|
|
addInt16ToFileData (fileData, sampleAsInt); |
|
|
} |
|
|
else if (bitDepth == 24) |
|
|
{ |
|
|
int32_t sampleAsIntAgain = (int32_t) (samples[channel][i] * (T)8388608.); |
|
|
|
|
|
uint8_t bytes[3]; |
|
|
bytes[2] = (uint8_t) (sampleAsIntAgain >> 16) & 0xFF; |
|
|
bytes[1] = (uint8_t) (sampleAsIntAgain >> 8) & 0xFF; |
|
|
bytes[0] = (uint8_t) sampleAsIntAgain & 0xFF; |
|
|
|
|
|
fileData.push_back (bytes[0]); |
|
|
fileData.push_back (bytes[1]); |
|
|
fileData.push_back (bytes[2]); |
|
|
} |
|
|
else if (bitDepth == 32) |
|
|
{ |
|
|
int32_t sampleAsInt; |
|
|
|
|
|
if (audioFormat == WavAudioFormat::IEEEFloat) |
|
|
sampleAsInt = (int32_t) reinterpret_cast<int32_t&> (samples[channel][i]); |
|
|
else |
|
|
sampleAsInt = (int32_t) (samples[channel][i] * std::numeric_limits<int32_t>::max()); |
|
|
|
|
|
addInt32ToFileData (fileData, sampleAsInt, Endianness::LittleEndian); |
|
|
} |
|
|
else |
|
|
{ |
|
|
assert (false && "Trying to write a file with unsupported bit depth"); |
|
|
return false; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (iXMLChunkSize > 0) |
|
|
{ |
|
|
addStringToFileData (fileData, "iXML"); |
|
|
addInt32ToFileData (fileData, iXMLChunkSize); |
|
|
addStringToFileData (fileData, iXMLChunk); |
|
|
} |
|
|
|
|
|
|
|
|
if (fileSizeInBytes != static_cast<int32_t> (fileData.size() - 8) || dataChunkSize != (getNumSamplesPerChannel() * getNumChannels() * (bitDepth / 8))) |
|
|
{ |
|
|
reportError ("ERROR: couldn't save file to " + filePath); |
|
|
return false; |
|
|
} |
|
|
|
|
|
|
|
|
return writeDataToFile (fileData, filePath); |
|
|
} |
|
|
|
|
|
|
|
|
template <class T> |
|
|
bool AudioFile<T>::saveToAiffFile (std::string filePath) |
|
|
{ |
|
|
std::vector<uint8_t> fileData; |
|
|
|
|
|
int32_t numBytesPerSample = bitDepth / 8; |
|
|
int32_t numBytesPerFrame = numBytesPerSample * getNumChannels(); |
|
|
int32_t totalNumAudioSampleBytes = getNumSamplesPerChannel() * numBytesPerFrame; |
|
|
int32_t soundDataChunkSize = totalNumAudioSampleBytes + 8; |
|
|
int32_t iXMLChunkSize = static_cast<int32_t> (iXMLChunk.size()); |
|
|
|
|
|
|
|
|
|
|
|
addStringToFileData (fileData, "FORM"); |
|
|
|
|
|
|
|
|
|
|
|
int32_t fileSizeInBytes = 4 + 26 + 16 + totalNumAudioSampleBytes; |
|
|
if (iXMLChunkSize > 0) |
|
|
{ |
|
|
fileSizeInBytes += (8 + iXMLChunkSize); |
|
|
} |
|
|
|
|
|
addInt32ToFileData (fileData, fileSizeInBytes, Endianness::BigEndian); |
|
|
|
|
|
addStringToFileData (fileData, "AIFF"); |
|
|
|
|
|
|
|
|
|
|
|
addStringToFileData (fileData, "COMM"); |
|
|
addInt32ToFileData (fileData, 18, Endianness::BigEndian); |
|
|
addInt16ToFileData (fileData, getNumChannels(), Endianness::BigEndian); |
|
|
addInt32ToFileData (fileData, getNumSamplesPerChannel(), Endianness::BigEndian); |
|
|
addInt16ToFileData (fileData, bitDepth, Endianness::BigEndian); |
|
|
addSampleRateToAiffData (fileData, sampleRate); |
|
|
|
|
|
|
|
|
|
|
|
addStringToFileData (fileData, "SSND"); |
|
|
addInt32ToFileData (fileData, soundDataChunkSize, Endianness::BigEndian); |
|
|
addInt32ToFileData (fileData, 0, Endianness::BigEndian); |
|
|
addInt32ToFileData (fileData, 0, Endianness::BigEndian); |
|
|
|
|
|
for (int i = 0; i < getNumSamplesPerChannel(); i++) |
|
|
{ |
|
|
for (int channel = 0; channel < getNumChannels(); channel++) |
|
|
{ |
|
|
if (bitDepth == 8) |
|
|
{ |
|
|
uint8_t byte = sampleToSingleByte (samples[channel][i]); |
|
|
fileData.push_back (byte); |
|
|
} |
|
|
else if (bitDepth == 16) |
|
|
{ |
|
|
int16_t sampleAsInt = sampleToSixteenBitInt (samples[channel][i]); |
|
|
addInt16ToFileData (fileData, sampleAsInt, Endianness::BigEndian); |
|
|
} |
|
|
else if (bitDepth == 24) |
|
|
{ |
|
|
int32_t sampleAsIntAgain = (int32_t) (samples[channel][i] * (T)8388608.); |
|
|
|
|
|
uint8_t bytes[3]; |
|
|
bytes[0] = (uint8_t) (sampleAsIntAgain >> 16) & 0xFF; |
|
|
bytes[1] = (uint8_t) (sampleAsIntAgain >> 8) & 0xFF; |
|
|
bytes[2] = (uint8_t) sampleAsIntAgain & 0xFF; |
|
|
|
|
|
fileData.push_back (bytes[0]); |
|
|
fileData.push_back (bytes[1]); |
|
|
fileData.push_back (bytes[2]); |
|
|
} |
|
|
else if (bitDepth == 32) |
|
|
{ |
|
|
|
|
|
int32_t sampleAsInt = (int32_t) (samples[channel][i] * std::numeric_limits<int32_t>::max()); |
|
|
addInt32ToFileData (fileData, sampleAsInt, Endianness::BigEndian); |
|
|
} |
|
|
else |
|
|
{ |
|
|
assert (false && "Trying to write a file with unsupported bit depth"); |
|
|
return false; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (iXMLChunkSize > 0) |
|
|
{ |
|
|
addStringToFileData (fileData, "iXML"); |
|
|
addInt32ToFileData (fileData, iXMLChunkSize, Endianness::BigEndian); |
|
|
addStringToFileData (fileData, iXMLChunk); |
|
|
} |
|
|
|
|
|
|
|
|
if (fileSizeInBytes != static_cast<int32_t> (fileData.size() - 8) || soundDataChunkSize != getNumSamplesPerChannel() * numBytesPerFrame + 8) |
|
|
{ |
|
|
reportError ("ERROR: couldn't save file to " + filePath); |
|
|
return false; |
|
|
} |
|
|
|
|
|
|
|
|
return writeDataToFile (fileData, filePath); |
|
|
} |
|
|
|
|
|
|
|
|
template <class T> |
|
|
bool AudioFile<T>::writeDataToFile (std::vector<uint8_t>& fileData, std::string filePath) |
|
|
{ |
|
|
std::ofstream outputFile (filePath, std::ios::binary); |
|
|
|
|
|
if (outputFile.is_open()) |
|
|
{ |
|
|
for (size_t i = 0; i < fileData.size(); i++) |
|
|
{ |
|
|
char value = (char) fileData[i]; |
|
|
outputFile.write (&value, sizeof (char)); |
|
|
} |
|
|
|
|
|
outputFile.close(); |
|
|
|
|
|
return true; |
|
|
} |
|
|
|
|
|
return false; |
|
|
} |
|
|
|
|
|
|
|
|
template <class T> |
|
|
void AudioFile<T>::addStringToFileData (std::vector<uint8_t>& fileData, std::string s) |
|
|
{ |
|
|
for (size_t i = 0; i < s.length();i++) |
|
|
fileData.push_back ((uint8_t) s[i]); |
|
|
} |
|
|
|
|
|
|
|
|
template <class T> |
|
|
void AudioFile<T>::addInt32ToFileData (std::vector<uint8_t>& fileData, int32_t i, Endianness endianness) |
|
|
{ |
|
|
uint8_t bytes[4]; |
|
|
|
|
|
if (endianness == Endianness::LittleEndian) |
|
|
{ |
|
|
bytes[3] = (i >> 24) & 0xFF; |
|
|
bytes[2] = (i >> 16) & 0xFF; |
|
|
bytes[1] = (i >> 8) & 0xFF; |
|
|
bytes[0] = i & 0xFF; |
|
|
} |
|
|
else |
|
|
{ |
|
|
bytes[0] = (i >> 24) & 0xFF; |
|
|
bytes[1] = (i >> 16) & 0xFF; |
|
|
bytes[2] = (i >> 8) & 0xFF; |
|
|
bytes[3] = i & 0xFF; |
|
|
} |
|
|
|
|
|
for (int i = 0; i < 4; i++) |
|
|
fileData.push_back (bytes[i]); |
|
|
} |
|
|
|
|
|
|
|
|
template <class T> |
|
|
void AudioFile<T>::addInt16ToFileData (std::vector<uint8_t>& fileData, int16_t i, Endianness endianness) |
|
|
{ |
|
|
uint8_t bytes[2]; |
|
|
|
|
|
if (endianness == Endianness::LittleEndian) |
|
|
{ |
|
|
bytes[1] = (i >> 8) & 0xFF; |
|
|
bytes[0] = i & 0xFF; |
|
|
} |
|
|
else |
|
|
{ |
|
|
bytes[0] = (i >> 8) & 0xFF; |
|
|
bytes[1] = i & 0xFF; |
|
|
} |
|
|
|
|
|
fileData.push_back (bytes[0]); |
|
|
fileData.push_back (bytes[1]); |
|
|
} |
|
|
|
|
|
|
|
|
template <class T> |
|
|
void AudioFile<T>::clearAudioBuffer() |
|
|
{ |
|
|
for (size_t i = 0; i < samples.size();i++) |
|
|
{ |
|
|
samples[i].clear(); |
|
|
} |
|
|
|
|
|
samples.clear(); |
|
|
} |
|
|
|
|
|
|
|
|
template <class T> |
|
|
AudioFileFormat AudioFile<T>::determineAudioFileFormat (std::vector<uint8_t>& fileData) |
|
|
{ |
|
|
std::string header (fileData.begin(), fileData.begin() + 4); |
|
|
|
|
|
if (header == "RIFF") |
|
|
return AudioFileFormat::Wave; |
|
|
else if (header == "FORM") |
|
|
return AudioFileFormat::Aiff; |
|
|
else |
|
|
return AudioFileFormat::Error; |
|
|
} |
|
|
|
|
|
|
|
|
template <class T> |
|
|
int32_t AudioFile<T>::fourBytesToInt (std::vector<uint8_t>& source, int startIndex, Endianness endianness) |
|
|
{ |
|
|
int32_t result; |
|
|
|
|
|
if (endianness == Endianness::LittleEndian) |
|
|
result = (source[startIndex + 3] << 24) | (source[startIndex + 2] << 16) | (source[startIndex + 1] << 8) | source[startIndex]; |
|
|
else |
|
|
result = (source[startIndex] << 24) | (source[startIndex + 1] << 16) | (source[startIndex + 2] << 8) | source[startIndex + 3]; |
|
|
|
|
|
return result; |
|
|
} |
|
|
|
|
|
|
|
|
template <class T> |
|
|
int16_t AudioFile<T>::twoBytesToInt (std::vector<uint8_t>& source, int startIndex, Endianness endianness) |
|
|
{ |
|
|
int16_t result; |
|
|
|
|
|
if (endianness == Endianness::LittleEndian) |
|
|
result = (source[startIndex + 1] << 8) | source[startIndex]; |
|
|
else |
|
|
result = (source[startIndex] << 8) | source[startIndex + 1]; |
|
|
|
|
|
return result; |
|
|
} |
|
|
|
|
|
|
|
|
template <class T> |
|
|
int AudioFile<T>::getIndexOfString (std::vector<uint8_t>& source, std::string stringToSearchFor) |
|
|
{ |
|
|
int index = -1; |
|
|
int stringLength = (int)stringToSearchFor.length(); |
|
|
|
|
|
for (size_t i = 0; i < source.size() - stringLength;i++) |
|
|
{ |
|
|
std::string section (source.begin() + i, source.begin() + i + stringLength); |
|
|
|
|
|
if (section == stringToSearchFor) |
|
|
{ |
|
|
index = static_cast<int> (i); |
|
|
break; |
|
|
} |
|
|
} |
|
|
|
|
|
return index; |
|
|
} |
|
|
|
|
|
|
|
|
template <class T> |
|
|
int AudioFile<T>::getIndexOfChunk (std::vector<uint8_t>& source, const std::string& chunkHeaderID, int startIndex, Endianness endianness) |
|
|
{ |
|
|
constexpr int dataLen = 4; |
|
|
if (chunkHeaderID.size() != dataLen) |
|
|
{ |
|
|
assert (false && "Invalid chunk header ID string"); |
|
|
return -1; |
|
|
} |
|
|
|
|
|
int i = startIndex; |
|
|
while (i < source.size() - dataLen) |
|
|
{ |
|
|
if (memcmp (&source[i], chunkHeaderID.data(), dataLen) == 0) |
|
|
{ |
|
|
return i; |
|
|
} |
|
|
|
|
|
i += dataLen; |
|
|
auto chunkSize = fourBytesToInt (source, i, endianness); |
|
|
i += (dataLen + chunkSize); |
|
|
} |
|
|
|
|
|
return -1; |
|
|
} |
|
|
|
|
|
|
|
|
template <class T> |
|
|
T AudioFile<T>::sixteenBitIntToSample (int16_t sample) |
|
|
{ |
|
|
return static_cast<T> (sample) / static_cast<T> (32768.); |
|
|
} |
|
|
|
|
|
|
|
|
template <class T> |
|
|
int16_t AudioFile<T>::sampleToSixteenBitInt (T sample) |
|
|
{ |
|
|
sample = clamp (sample, -1., 1.); |
|
|
return static_cast<int16_t> (sample * 32767.); |
|
|
} |
|
|
|
|
|
|
|
|
template <class T> |
|
|
uint8_t AudioFile<T>::sampleToSingleByte (T sample) |
|
|
{ |
|
|
sample = clamp (sample, -1., 1.); |
|
|
sample = (sample + 1.) / 2.; |
|
|
return static_cast<uint8_t> (sample * 255.); |
|
|
} |
|
|
|
|
|
|
|
|
template <class T> |
|
|
T AudioFile<T>::singleByteToSample (uint8_t sample) |
|
|
{ |
|
|
return static_cast<T> (sample - 128) / static_cast<T> (128.); |
|
|
} |
|
|
|
|
|
|
|
|
template <class T> |
|
|
T AudioFile<T>::clamp (T value, T minValue, T maxValue) |
|
|
{ |
|
|
value = std::min (value, maxValue); |
|
|
value = std::max (value, minValue); |
|
|
return value; |
|
|
} |
|
|
|
|
|
|
|
|
template <class T> |
|
|
void AudioFile<T>::reportError (std::string errorMessage) |
|
|
{ |
|
|
if (logErrorsToConsole) |
|
|
std::cout << errorMessage << std::endl; |
|
|
} |
|
|
|
|
|
#if defined (_MSC_VER) |
|
|
__pragma(warning (pop)) |
|
|
#elif defined (__GNUC__) |
|
|
_Pragma("GCC diagnostic pop") |
|
|
#endif |
|
|
|
|
|
#endif |
|
|
|