| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| #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 |
|
|