#pragma once #ifndef _AS_AudioFile_h #define _AS_AudioFile_h #if defined (_MSC_VER) #undef max #undef min #define NOMINMAX #endif #include #include #include #include #include #include #include #include #include #include #include "Utils/Utils.h" #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 AudioFile { public: typedef std::vector > AudioBuffer; AudioFile(); AudioFile(std::string filePath); /** Loads an audio file from a given file path. * @Returns true if the file was successfully loaded */ bool load(std::string filePath); /** Saves an audio file to a given file path. * @Returns true if the file was successfully saved */ bool save(std::string filePath, AudioFileFormat format = AudioFileFormat::Wave); bool loadFromMemory(std::vector& fileData); uint32_t getSampleRate() const; int getNumChannels() const; bool isMono() const; bool isStereo() const; int getBitDepth() const; int getNumSamplesPerChannel() const; double getLengthInSeconds() const; std::string getCurrentTimeString(); void printSummary() const; /** Set the audio buffer for this AudioFile by copying samples from another buffer. * @Returns true if the buffer was copied successfully. */ bool setAudioBuffer(AudioBuffer& newBuffer); /** Sets the audio buffer to a given number of channels and number of samples per channel. This will try to preserve * the existing audio, adding zeros to any new channels or new samples in a given channel. */ void setAudioBufferSize(int numChannels, int numSamples); /** Sets the number of samples per channel in the audio buffer. This will try to preserve * the existing audio, adding zeros to new samples in a given channel if the number of samples is increased. */ void setNumSamplesPerChannel(int numSamples); void setNumChannels(int numChannels); void setBitDepth(int numBitsPerSample); void setSampleRate(uint32_t newSampleRate); void shouldLogErrorsToConsole(bool logErrors); /** A vector of vectors holding the audio samples for the AudioFile. You can * access the samples by channel and then by sample index, i.e: * * samples[channel][sampleIndex] */ AudioBuffer samples; /** An optional iXML chunk that can be added to the AudioFile. */ std::string iXMLChunk; private: enum class Endianness { LittleEndian, BigEndian }; AudioFileFormat determineAudioFileFormat(std::vector& fileData); bool decodeWaveFile(std::vector& fileData); bool decodeAiffFile(std::vector& fileData); bool saveToWaveFile(std::string filePath); bool saveToAiffFile(std::string filePath); void clearAudioBuffer(); int32_t fourBytesToInt(std::vector& source, int startIndex, Endianness endianness = Endianness::LittleEndian); int16_t twoBytesToInt(std::vector& source, int startIndex, Endianness endianness = Endianness::LittleEndian); int getIndexOfString(std::vector& source, std::string s); int getIndexOfChunk(std::vector& source, const std::string& chunkHeaderID, int startIndex, Endianness endianness = Endianness::LittleEndian); uint32_t getAiffSampleRate(std::vector& fileData, int sampleRateStartIndex); bool tenByteMatch(std::vector& v1, int startIndex1, std::vector& v2, int startIndex2); void addSampleRateToAiffData(std::vector& fileData, uint32_t sampleRate); void addStringToFileData(std::vector& fileData, std::string s); void addInt32ToFileData(std::vector& fileData, int32_t i, Endianness endianness = Endianness::LittleEndian); void addInt16ToFileData(std::vector& fileData, int16_t i, Endianness endianness = Endianness::LittleEndian); bool writeDataToFile(std::vector& fileData, std::string filePath); void reportError(std::string errorMessage); AudioFileFormat audioFileFormat; uint32_t sampleRate; int bitDepth; bool logErrorsToConsole{ true }; }; template struct AudioSampleConverter { static T signedByteToSample(int8_t sample); static int8_t sampleToSignedByte(T sample); static T unsignedByteToSample(uint8_t sample); static uint8_t sampleToUnsignedByte(T sample); static T sixteenBitIntToSample(int16_t sample); static int16_t sampleToSixteenBitInt(T sample); static T twentyFourBitIntToSample(int32_t sample); static int32_t sampleToTwentyFourBitInt(T sample); static T thirtyTwoBitIntToSample(int32_t sample); static int32_t sampleToThirtyTwoBitInt(T sample); static T clamp(T v1, T minValue, T maxValue); }; static std::unordered_map > 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 AudioFile::AudioFile() { bitDepth = 16; sampleRate = 44100; samples.resize(1); samples[0].resize(0); audioFileFormat = AudioFileFormat::NotLoaded; } template AudioFile::AudioFile(std::string filePath) : AudioFile() { load(filePath); } template uint32_t AudioFile::getSampleRate() const { return sampleRate; } template int AudioFile::getNumChannels() const { return (int)samples.size(); } template bool AudioFile::isMono() const { return getNumChannels() == 1; } template bool AudioFile::isStereo() const { return getNumChannels() == 2; } template int AudioFile::getBitDepth() const { return bitDepth; } template int AudioFile::getNumSamplesPerChannel() const { if (samples.size() > 0) return (int)samples[0].size(); else return 0; } template double AudioFile::getLengthInSeconds() const { return (double)getNumSamplesPerChannel() / (double)sampleRate; } template std::string AudioFile::getCurrentTimeString() { double SoundTime = getLengthInSeconds(); int hours = SoundTime / 3600; int remainingSeconds = fmod(SoundTime, 3600); int minutes = remainingSeconds / 60; int seconds = remainingSeconds % 60; int mseconds = fmod(SoundTime, 1) * 1000; return StringHelper::Format("%02d:%02d:%02d,%03d", hours, minutes, seconds, mseconds ); } template void AudioFile::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 bool AudioFile::setAudioBuffer(AudioBuffer& newBuffer) { int numChannels = (int)newBuffer.size(); if (numChannels <= 0) { assert(false && "The buffer you 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 void AudioFile::setAudioBufferSize(int numChannels, int numSamples) { samples.resize(numChannels); setNumSamplesPerChannel(numSamples); } template void AudioFile::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 void AudioFile::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 void AudioFile::setBitDepth(int numBitsPerSample) { bitDepth = numBitsPerSample; } template void AudioFile::setSampleRate(uint32_t newSampleRate) { sampleRate = newSampleRate; } template void AudioFile::shouldLogErrorsToConsole(bool logErrors) { logErrorsToConsole = logErrors; } template bool AudioFile::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 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 (fileData.data()), length); file.close(); if (file.gcount() != length) { reportError("ERROR: Couldn't read entire file\n" + filePath); return false; } if (fileData.size() < 12) { reportError("ERROR: File is not a valid audio file\n" + filePath); return false; } else { return loadFromMemory(fileData); } } template bool AudioFile::loadFromMemory(std::vector& fileData) { 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 bool AudioFile::decodeWaveFile(std::vector& 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); if (bitDepth > sizeof(T) * 8) { std::string message = "ERROR: you are trying to read a "; message += std::to_string(bitDepth); message += "-bit file using a "; message += std::to_string(sizeof(T) * 8); message += "-bit sample type"; reportError(message); return false; } uint16_t numBytesPerSample = static_cast (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 ((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 = AudioSampleConverter::unsignedByteToSample(fileData[sampleIndex]); samples[channel].push_back(sample); } else if (bitDepth == 16) { int16_t sampleAsInt = twoBytesToInt(fileData, sampleIndex); T sample = AudioSampleConverter::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 = AudioSampleConverter::twentyFourBitIntToSample(sampleAsInt); samples[channel].push_back(sample); } else if (bitDepth == 32) { int32_t sampleAsInt = fourBytesToInt(fileData, sampleIndex); T sample; if (audioFormat == WavAudioFormat::IEEEFloat && std::is_floating_point_v) { float f; memcpy(&f, &sampleAsInt, sizeof(int32_t)); sample = (T)f; } else { sample = AudioSampleConverter::thirtyTwoBitIntToSample(sampleAsInt); } 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 bool AudioFile::decodeAiffFile(std::vector& 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 (bitDepth > sizeof(T) * 8) { std::string message = "ERROR: you are trying to read a "; message += std::to_string(bitDepth); message += "-bit file using a "; message += std::to_string(sizeof(T) * 8); message += "-bit sample type"; reportError(message); return false; } 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(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) { T sample = AudioSampleConverter::signedByteToSample(static_cast (fileData[sampleIndex])); samples[channel].push_back(sample); } else if (bitDepth == 16) { int16_t sampleAsInt = twoBytesToInt(fileData, sampleIndex, Endianness::BigEndian); T sample = AudioSampleConverter::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 = AudioSampleConverter::twentyFourBitIntToSample(sampleAsInt); 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 (sampleAsInt); else sample = AudioSampleConverter::thirtyTwoBitIntToSample(sampleAsInt); 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 uint32_t AudioFile::getAiffSampleRate(std::vector& fileData, int sampleRateStartIndex) { for (auto it : aiffSampleRateTable) { if (tenByteMatch(fileData, sampleRateStartIndex, it.second, 0)) return it.first; } return 0; } template bool AudioFile::tenByteMatch(std::vector& v1, int startIndex1, std::vector& v2, int startIndex2) { for (int i = 0; i < 10; i++) { if (v1[startIndex1 + i] != v2[startIndex2 + i]) return false; } return true; } template void AudioFile::addSampleRateToAiffData(std::vector& fileData, uint32_t sampleRate) { if (aiffSampleRateTable.count(sampleRate) > 0) { for (int i = 0; i < 10; i++) fileData.push_back(aiffSampleRateTable[sampleRate][i]); } } template bool AudioFile::save(std::string filePath, AudioFileFormat format) { if (format == AudioFileFormat::Wave) { return saveToWaveFile(filePath); } else if (format == AudioFileFormat::Aiff) { return saveToAiffFile(filePath); } return false; } template bool AudioFile::saveToWaveFile(std::string filePath) { std::vector fileData; int32_t dataChunkSize = getNumSamplesPerChannel() * (getNumChannels() * bitDepth / 8); int16_t audioFormat = bitDepth == 32 && std::is_floating_point_v ? WavAudioFormat::IEEEFloat : WavAudioFormat::PCM; int32_t formatChunkSize = audioFormat == WavAudioFormat::PCM ? 16 : 18; int32_t iXMLChunkSize = static_cast (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 = AudioSampleConverter::sampleToUnsignedByte(samples[channel][i]); fileData.push_back(byte); } else if (bitDepth == 16) { int16_t sampleAsInt = AudioSampleConverter::sampleToSixteenBitInt(samples[channel][i]); addInt16ToFileData(fileData, sampleAsInt); } else if (bitDepth == 24) { int32_t sampleAsIntAgain = AudioSampleConverter::sampleToTwentyFourBitInt(samples[channel][i]); 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 (samples[channel][i]); else sampleAsInt = AudioSampleConverter::sampleToThirtyTwoBitInt(samples[channel][i]); 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 (fileData.size() - 8) || dataChunkSize != (getNumSamplesPerChannel() * getNumChannels() * (bitDepth / 8))) { reportError("ERROR: couldn't save file to " + filePath); return false; } return writeDataToFile(fileData, filePath); } template bool AudioFile::saveToAiffFile(std::string filePath) { std::vector 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 (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 = static_cast (AudioSampleConverter::sampleToSignedByte(samples[channel][i])); fileData.push_back(byte); } else if (bitDepth == 16) { int16_t sampleAsInt = AudioSampleConverter::sampleToSixteenBitInt(samples[channel][i]); addInt16ToFileData(fileData, sampleAsInt, Endianness::BigEndian); } else if (bitDepth == 24) { int32_t sampleAsIntAgain = AudioSampleConverter::sampleToTwentyFourBitInt(samples[channel][i]); 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 = AudioSampleConverter::sampleToThirtyTwoBitInt(samples[channel][i]); 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 (fileData.size() - 8) || soundDataChunkSize != getNumSamplesPerChannel() * numBytesPerFrame + 8) { reportError("ERROR: couldn't save file to " + filePath); return false; } return writeDataToFile(fileData, filePath); } template bool AudioFile::writeDataToFile(std::vector& 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 void AudioFile::addStringToFileData(std::vector& fileData, std::string s) { for (size_t i = 0; i < s.length(); i++) fileData.push_back((uint8_t)s[i]); } template void AudioFile::addInt32ToFileData(std::vector& 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 void AudioFile::addInt16ToFileData(std::vector& 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 void AudioFile::clearAudioBuffer() { for (size_t i = 0; i < samples.size(); i++) { samples[i].clear(); } samples.clear(); } template AudioFileFormat AudioFile::determineAudioFileFormat(std::vector& 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 int32_t AudioFile::fourBytesToInt(std::vector& source, int startIndex, Endianness endianness) { if (source.size() >= (startIndex + 4)) { 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; } else { assert(false && "Attempted to read four bytes from vector at position where out of bounds access would occur"); return 0; } } template int16_t AudioFile::twoBytesToInt(std::vector& 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 int AudioFile::getIndexOfString(std::vector& 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 (i); break; } } return index; } template int AudioFile::getIndexOfChunk(std::vector& 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; if ((i + 4) >= source.size()) return -1; auto chunkSize = fourBytesToInt(source, i, endianness); i += (dataLen + chunkSize); } return -1; } template void AudioFile::reportError(std::string errorMessage) { if (logErrorsToConsole) std::cout << errorMessage << std::endl; } template typename std::make_unsigned::type convertSignedToUnsigned(SignedType signedValue) { static_assert (std::is_signed::value, "The input value must be signed"); typename std::make_unsigned::type unsignedValue = static_cast::type> (1) + std::numeric_limits::max(); unsignedValue += signedValue; return unsignedValue; } enum SampleLimit { SignedInt16_Min = -32768, SignedInt16_Max = 32767, UnsignedInt16_Min = 0, UnsignedInt16_Max = 65535, SignedInt24_Min = -8388608, SignedInt24_Max = 8388607, UnsignedInt24_Min = 0, UnsignedInt24_Max = 16777215 }; template T AudioSampleConverter::thirtyTwoBitIntToSample(int32_t sample) { if constexpr (std::is_floating_point::value) { return static_cast (sample) / static_cast (std::numeric_limits::max()); } else if (std::numeric_limits::is_integer) { if constexpr (std::is_signed_v) return static_cast (sample); else return static_cast (clamp(static_cast (sample + 2147483648), 0, 4294967295)); } } template int32_t AudioSampleConverter::sampleToThirtyTwoBitInt(T sample) { if constexpr (std::is_floating_point::value) { if constexpr (std::is_same_v) { if (sample >= 1.f) return std::numeric_limits::max(); else if (sample <= -1.f) return std::numeric_limits::lowest() + 1; else return static_cast (sample * std::numeric_limits::max()); } else { return static_cast (clamp(sample, -1., 1.) * std::numeric_limits::max()); } } else { if constexpr (std::is_signed_v) return static_cast (clamp(sample, -2147483648LL, 2147483647LL)); else return static_cast (clamp(sample, 0, 4294967295) - 2147483648); } } template T AudioSampleConverter::twentyFourBitIntToSample(int32_t sample) { if constexpr (std::is_floating_point::value) { return static_cast (sample) / static_cast (8388607.); } else if (std::numeric_limits::is_integer) { if constexpr (std::is_signed_v) return static_cast (clamp(sample, SignedInt24_Min, SignedInt24_Max)); else return static_cast (clamp(sample + 8388608, UnsignedInt24_Min, UnsignedInt24_Max)); } } template int32_t AudioSampleConverter::sampleToTwentyFourBitInt(T sample) { if constexpr (std::is_floating_point::value) { sample = clamp(sample, -1., 1.); return static_cast (sample * 8388607.); } else { if constexpr (std::is_signed_v) return static_cast (clamp(sample, SignedInt24_Min, SignedInt24_Max)); else return static_cast (clamp(sample, UnsignedInt24_Min, UnsignedInt24_Max) + SignedInt24_Min); } } template T AudioSampleConverter::sixteenBitIntToSample(int16_t sample) { if constexpr (std::is_floating_point::value) { return static_cast (sample) / static_cast (32767.); } else if constexpr (std::numeric_limits::is_integer) { if constexpr (std::is_signed_v) return static_cast (sample); else return static_cast (convertSignedToUnsigned(sample)); } } template int16_t AudioSampleConverter::sampleToSixteenBitInt(T sample) { if constexpr (std::is_floating_point::value) { sample = clamp(sample, -1., 1.); return static_cast (sample * 32767.); } else { if constexpr (std::is_signed_v) return static_cast (clamp(sample, SignedInt16_Min, SignedInt16_Max)); else return static_cast (clamp(sample, UnsignedInt16_Min, UnsignedInt16_Max) + SignedInt16_Min); } } template uint8_t AudioSampleConverter::sampleToUnsignedByte(T sample) { if constexpr (std::is_floating_point::value) { sample = clamp(sample, -1., 1.); sample = (sample + 1.) / 2.; return static_cast (1 + (sample * 254)); } else { if constexpr (std::is_signed_v) return static_cast (clamp(sample, -128, 127) + 128); else return static_cast (clamp(sample, 0, 255)); } } template int8_t AudioSampleConverter::sampleToSignedByte(T sample) { if constexpr (std::is_floating_point::value) { sample = clamp(sample, -1., 1.); return static_cast (sample * (T)0x7F); } else { if constexpr (std::is_signed_v) return static_cast (clamp(sample, -128, 127)); else return static_cast (clamp(sample, 0, 255) - 128); } } template T AudioSampleConverter::unsignedByteToSample(uint8_t sample) { if constexpr (std::is_floating_point::value) { return static_cast (sample - 128) / static_cast (127.); } else if (std::numeric_limits::is_integer) { if constexpr (std::is_unsigned_v) return static_cast (sample); else return static_cast (sample - 128); } } template T AudioSampleConverter::signedByteToSample(int8_t sample) { if constexpr (std::is_floating_point::value) { return static_cast (sample) / static_cast (127.); } else if constexpr (std::numeric_limits::is_integer) { if constexpr (std::is_signed_v) return static_cast (sample); else return static_cast (convertSignedToUnsigned(sample)); } } template T AudioSampleConverter::clamp(T value, T minValue, T maxValue) { value = std::min(value, maxValue); value = std::max(value, minValue); return value; } #if defined (_MSC_VER) __pragma(warning(pop)) #elif defined (__GNUC__) _Pragma("GCC diagnostic pop") #endif #endif