From b1ce204542b166bbca507b45e828c5ef9e75a2ee Mon Sep 17 00:00:00 2001 From: Leo Date: Sun, 12 Mar 2023 18:18:28 -0400 Subject: [PATCH 01/16] Validate chunk size --- AudioFile.h | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/AudioFile.h b/AudioFile.h index c4a8bbf..2c90f2c 100644 --- a/AudioFile.h +++ b/AudioFile.h @@ -1318,7 +1318,13 @@ int AudioFile::getIndexOfChunk (std::vector& source, const std::stri if ((i + 4) >= source.size()) return -1; - auto chunkSize = fourBytesToInt (source, i, endianness); + uint32_t chunkSize = fourBytesToInt (source, i, endianness); + // Assume chunk size is invalid if it's greater than the number of bytes remaining in source + if (chunkSize > (source.size() - i - dataLen)) + { + assert (false && "Invalid chunk size"); + return -1; + } i += (dataLen + chunkSize); } From 9159df0104ecca01b94b5f80fa46ec4f4487314a Mon Sep 17 00:00:00 2001 From: David Medlock Date: Wed, 29 Mar 2023 18:46:13 -0500 Subject: [PATCH 02/16] Replace cout with cerr for info/error reporting --- AudioFile.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/AudioFile.h b/AudioFile.h index 99e6923..2ae8413 100644 --- a/AudioFile.h +++ b/AudioFile.h @@ -357,13 +357,13 @@ double AudioFile::getLengthInSeconds() const 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; + std::cerr << "|======================================|" << std::endl; + std::cerr << "Num Channels: " << getNumChannels() << std::endl; + std::cerr << "Num Samples Per Channel: " << getNumSamplesPerChannel() << std::endl; + std::cerr << "Sample Rate: " << sampleRate << std::endl; + std::cerr << "Bit Depth: " << bitDepth << std::endl; + std::cerr << "Length in Seconds: " << getLengthInSeconds() << std::endl; + std::cerr << "|======================================|" << std::endl; } //============================================================= @@ -1307,7 +1307,7 @@ template void AudioFile::reportError (std::string errorMessage) { if (logErrorsToConsole) - std::cout << errorMessage << std::endl; + std::cerr << errorMessage << std::endl; } #if defined (_MSC_VER) From b68a0a2add4e2f90c22ecedb470cf22adb3ee4e3 Mon Sep 17 00:00:00 2001 From: Adam Stark Date: Tue, 4 Apr 2023 23:57:43 +0100 Subject: [PATCH 03/16] README tweaks --- README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f6eec4e..b35eab8 100644 --- a/README.md +++ b/README.md @@ -131,9 +131,9 @@ Please see the `examples` folder for some examples on library usage. A Note On Types ----------------- -AudioFile is a template class and so it can be instantiated using floating point precision: +AudioFile is a template class and so it can be instantiated using different types to represent the audio samples. -For example +For example, we can use floating point precision... AudioFile audioFile; @@ -147,13 +147,15 @@ For example This simply reflects the data type you would like to use to store the underlying audio samples. -When you use an integer type to store the samples (e.g. `int` or `int8_t` or `int16_t` or `uint32_t`), the library will read in the integer sample values directly from the audio file. A couple of notes on integer types: +When you use an integer type to store the samples (e.g. `int` or `int8_t` or `int16_t` or `uint32_t`), the library will read in the integer sample values directly from the audio file. + +A couple of notes on integer types: * The range of samples is designed to be symmetric. This means that for (e.g.) an signed 8-bit integer (`int8_t`) we will use the range `[-127, 127]` for storing samples representing the `[-1., 1.]` range. The value `-128` is possible here given the `int8_t` type, but this is interpreted as a value slightly lower than `-1` (specifically `-1.007874015748`). * In the case of unsigned types, we obviously can't store samples as negative values. Therefore, we used the equivalent range of the unsigned type in use. E.g. if with a 8-bit signed integer (`int8_t`) the range would be `[-127, 127]`, for an 8-bit unsigned integer we would use the range `[1, 255]`. Note that we don't use `-128` for `int8_t` or `0` in `uint8_t`. -* If you try to read an audio file with a larger bit-depth than the type you are using to store samples, the attempt to read the file will fail. Put more simply, you can't read a 16-bit audio file into an 8-bit integer. +* If you try to read an audio file with a larger bit-depth than the type you are using to store samples, the attempt to read the file will fail. Put more simply, you can't read a 16-bit audio sample into an 8-bit integer. * If you are writing audio samples in integer formats, you should use the correct sample range for both a) the type you are using to store samples; and b) the bit depth of the audio you want to write. From 0874b47be772f6398478979ef337c0844a255f0b Mon Sep 17 00:00:00 2001 From: Adam Stark Date: Wed, 5 Apr 2023 08:50:37 +0100 Subject: [PATCH 04/16] Update contributors on README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b35eab8..4886e94 100644 --- a/README.md +++ b/README.md @@ -247,6 +247,7 @@ Many thanks to the following people for their contributions to this library: * [Abhinav1997](https://github.com/Abhinav1997) * [alxarsenault](https://github.com/alxarsenault) * [BenjaminHinchliff](https://github.com/BenjaminHinchliff) +* [BesselJ](https://github.com/BesselJ) * [emiro85](https://github.com/emiro85) * [heartofrain](https://github.com/heartofrain) * [helloimmatt](https://github.com/helloimmatt/) From 9a1ef429d09a7129a00f0585ce9ba0772d887f87 Mon Sep 17 00:00:00 2001 From: Adam Stark Date: Fri, 7 Apr 2023 08:47:09 +0100 Subject: [PATCH 05/16] Make arguments const and const & where possible --- AudioFile.h | 102 +++++++++++++++++++++------------------------------- 1 file changed, 40 insertions(+), 62 deletions(-) diff --git a/AudioFile.h b/AudioFile.h index 984dea0..67f7d05 100644 --- a/AudioFile.h +++ b/AudioFile.h @@ -83,29 +83,29 @@ class AudioFile public: //============================================================= - typedef std::vector > AudioBuffer; + typedef std::vector> AudioBuffer; //============================================================= /** Constructor */ AudioFile(); /** Constructor, using a given file path to load a file */ - AudioFile (std::string filePath); + AudioFile (const 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); + bool load (const 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 save (const std::string& filePath, AudioFileFormat format = AudioFileFormat::Wave); //============================================================= /** Loads an audio file from data in memory */ - bool loadFromMemory (std::vector& fileData); + bool loadFromMemory (const std::vector& fileData); //============================================================= /** @Returns the sample rate */ @@ -137,26 +137,26 @@ class AudioFile /** 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); + bool setAudioBuffer (const 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); + void setAudioBufferSize (const int numChannels, const 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 setNumSamplesPerChannel (const int numSamples); /** Sets the number of channels. New channels will have the correct number of samples and be initialised to zero */ - void setNumChannels (int numChannels); + void setNumChannels (const int numChannels); /** Sets the bit depth for the audio file. If you use the save() function, this bit depth rate will be used */ - void setBitDepth (int numBitsPerSample); + void setBitDepth (const int numBitsPerSample); /** Sets the sample rate for the audio file. If you use the save() function, this sample rate will be used */ - void setSampleRate (uint32_t newSampleRate); + void setSampleRate (const uint32_t newSampleRate); //============================================================= /** Sets whether the library should log error messages to the console. By default this is true */ @@ -185,26 +185,25 @@ class AudioFile }; //============================================================= - AudioFileFormat determineAudioFileFormat (std::vector& fileData); - bool decodeWaveFile (std::vector& fileData); - bool decodeAiffFile (std::vector& fileData); + AudioFileFormat determineAudioFileFormat (const std::vector& fileData); + bool decodeWaveFile (const std::vector& fileData); + bool decodeAiffFile (const std::vector& fileData); //============================================================= - bool saveToWaveFile (std::string filePath); - bool saveToAiffFile (std::string filePath); + bool saveToWaveFile (const std::string& filePath); + bool saveToAiffFile (const 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); + int32_t fourBytesToInt (const std::vector& source, int startIndex, Endianness endianness = Endianness::LittleEndian); + int16_t twoBytesToInt (const std::vector& source, int startIndex, Endianness endianness = Endianness::LittleEndian); + int getIndexOfChunk (const 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); + uint32_t getAiffSampleRate (const std::vector& fileData, int sampleRateStartIndex); + bool tenByteMatch (const std::vector& v1, int startIndex1, std::vector& v2, int startIndex2); void addSampleRateToAiffData (std::vector& fileData, uint32_t sampleRate); //============================================================= @@ -213,10 +212,10 @@ class AudioFile void addInt16ToFileData (std::vector& fileData, int16_t i, Endianness endianness = Endianness::LittleEndian); //============================================================= - bool writeDataToFile (std::vector& fileData, std::string filePath); + bool writeDataToFile (const std::vector& fileData, const std::string& filePath); //============================================================= - void reportError (std::string errorMessage); + void reportError (const std::string& errorMessage); //============================================================= AudioFileFormat audioFileFormat; @@ -328,7 +327,7 @@ AudioFile::AudioFile() //============================================================= template -AudioFile::AudioFile (std::string filePath) +AudioFile::AudioFile (const std::string& filePath) : AudioFile() { load (filePath); @@ -401,7 +400,7 @@ void AudioFile::printSummary() const //============================================================= template -bool AudioFile::setAudioBuffer (AudioBuffer& newBuffer) +bool AudioFile::setAudioBuffer (const AudioBuffer& newBuffer) { int numChannels = (int)newBuffer.size(); @@ -499,7 +498,7 @@ void AudioFile::shouldLogErrorsToConsole (bool logErrors) //============================================================= template -bool AudioFile::load (std::string filePath) +bool AudioFile::load (const std::string& filePath) { std::ifstream file (filePath, std::ios::binary); @@ -545,7 +544,7 @@ bool AudioFile::load (std::string filePath) //============================================================= template -bool AudioFile::loadFromMemory (std::vector& fileData) +bool AudioFile::loadFromMemory (const std::vector& fileData) { // get audio file format audioFileFormat = determineAudioFileFormat (fileData); @@ -567,7 +566,7 @@ bool AudioFile::loadFromMemory (std::vector& fileData) //============================================================= template -bool AudioFile::decodeWaveFile (std::vector& fileData) +bool AudioFile::decodeWaveFile (const std::vector& fileData) { // ----------------------------------------------------------- // HEADER CHUNK @@ -726,7 +725,7 @@ bool AudioFile::decodeWaveFile (std::vector& fileData) //============================================================= template -bool AudioFile::decodeAiffFile (std::vector& fileData) +bool AudioFile::decodeAiffFile (const std::vector& fileData) { // ----------------------------------------------------------- // HEADER CHUNK @@ -881,7 +880,7 @@ bool AudioFile::decodeAiffFile (std::vector& fileData) //============================================================= template -uint32_t AudioFile::getAiffSampleRate (std::vector& fileData, int sampleRateStartIndex) +uint32_t AudioFile::getAiffSampleRate (const std::vector& fileData, int sampleRateStartIndex) { for (auto it : aiffSampleRateTable) { @@ -894,7 +893,7 @@ uint32_t AudioFile::getAiffSampleRate (std::vector& fileData, int sa //============================================================= template -bool AudioFile::tenByteMatch (std::vector& v1, int startIndex1, std::vector& v2, int startIndex2) +bool AudioFile::tenByteMatch (const std::vector& v1, int startIndex1, std::vector& v2, int startIndex2) { for (int i = 0; i < 10; i++) { @@ -918,7 +917,7 @@ void AudioFile::addSampleRateToAiffData (std::vector& fileData, uint //============================================================= template -bool AudioFile::save (std::string filePath, AudioFileFormat format) +bool AudioFile::save (const std::string& filePath, AudioFileFormat format) { if (format == AudioFileFormat::Wave) { @@ -934,7 +933,7 @@ bool AudioFile::save (std::string filePath, AudioFileFormat format) //============================================================= template -bool AudioFile::saveToWaveFile (std::string filePath) +bool AudioFile::saveToWaveFile (const std::string& filePath) { std::vector fileData; @@ -1051,7 +1050,7 @@ bool AudioFile::saveToWaveFile (std::string filePath) //============================================================= template -bool AudioFile::saveToAiffFile (std::string filePath) +bool AudioFile::saveToAiffFile (const std::string& filePath) { std::vector fileData; @@ -1156,7 +1155,7 @@ bool AudioFile::saveToAiffFile (std::string filePath) //============================================================= template -bool AudioFile::writeDataToFile (std::vector& fileData, std::string filePath) +bool AudioFile::writeDataToFile (const std::vector& fileData, const std::string& filePath) { std::ofstream outputFile (filePath, std::ios::binary); @@ -1244,7 +1243,7 @@ void AudioFile::clearAudioBuffer() //============================================================= template -AudioFileFormat AudioFile::determineAudioFileFormat (std::vector& fileData) +AudioFileFormat AudioFile::determineAudioFileFormat (const std::vector& fileData) { std::string header (fileData.begin(), fileData.begin() + 4); @@ -1258,7 +1257,7 @@ AudioFileFormat AudioFile::determineAudioFileFormat (std::vector& fi //============================================================= template -int32_t AudioFile::fourBytesToInt (std::vector& source, int startIndex, Endianness endianness) +int32_t AudioFile::fourBytesToInt (const std::vector& source, int startIndex, Endianness endianness) { if (source.size() >= (startIndex + 4)) { @@ -1280,7 +1279,7 @@ int32_t AudioFile::fourBytesToInt (std::vector& source, int startInd //============================================================= template -int16_t AudioFile::twoBytesToInt (std::vector& source, int startIndex, Endianness endianness) +int16_t AudioFile::twoBytesToInt (const std::vector& source, int startIndex, Endianness endianness) { int16_t result; @@ -1294,28 +1293,7 @@ int16_t AudioFile::twoBytesToInt (std::vector& source, int startInde //============================================================= 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) +int AudioFile::getIndexOfChunk (const std::vector& source, const std::string& chunkHeaderID, int startIndex, Endianness endianness) { constexpr int dataLen = 4; @@ -1348,7 +1326,7 @@ int AudioFile::getIndexOfChunk (std::vector& source, const std::stri //============================================================= template -void AudioFile::reportError (std::string errorMessage) +void AudioFile::reportError (const std::string& errorMessage) { if (logErrorsToConsole) std::cerr << errorMessage << std::endl; From 3160f43f5eb137e69aadedfae4a7c12087030d27 Mon Sep 17 00:00:00 2001 From: Adam Stark Date: Fri, 7 Apr 2023 09:25:43 +0100 Subject: [PATCH 06/16] Add space between closing > > brackets for compatability --- AudioFile.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AudioFile.h b/AudioFile.h index 67f7d05..4e33651 100644 --- a/AudioFile.h +++ b/AudioFile.h @@ -83,7 +83,7 @@ class AudioFile public: //============================================================= - typedef std::vector> AudioBuffer; + typedef std::vector > AudioBuffer; //============================================================= /** Constructor */ From cb5941ce86182e658c8a3df205e37843c65c53cf Mon Sep 17 00:00:00 2001 From: Leo Date: Fri, 14 Apr 2023 13:46:44 -0400 Subject: [PATCH 07/16] make chunkSize signed --- AudioFile.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AudioFile.h b/AudioFile.h index 2c90f2c..483dedb 100644 --- a/AudioFile.h +++ b/AudioFile.h @@ -1318,9 +1318,9 @@ int AudioFile::getIndexOfChunk (std::vector& source, const std::stri if ((i + 4) >= source.size()) return -1; - uint32_t chunkSize = fourBytesToInt (source, i, endianness); + int32_t chunkSize = fourBytesToInt (source, i, endianness); // Assume chunk size is invalid if it's greater than the number of bytes remaining in source - if (chunkSize > (source.size() - i - dataLen)) + if (chunkSize > (source.size() - i - dataLen) || (chunkSize < 0)) { assert (false && "Invalid chunk size"); return -1; From 2a96219c5c3bc6d0ca61bd70ac8b66d657e807cc Mon Sep 17 00:00:00 2001 From: Adam Stark Date: Mon, 17 Apr 2023 23:00:37 +0100 Subject: [PATCH 08/16] Update contributors --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4886e94..4c08937 100644 --- a/README.md +++ b/README.md @@ -251,6 +251,7 @@ Many thanks to the following people for their contributions to this library: * [emiro85](https://github.com/emiro85) * [heartofrain](https://github.com/heartofrain) * [helloimmatt](https://github.com/helloimmatt/) +* [leocstone](https://github.com/leocstone) * [MatthieuHernandez](https://github.com/MatthieuHernandez) * [mrpossoms](https://github.com/mrpossoms) * [mynameisjohn](https://github.com/mynameisjohn) From f46a4e497b551b97d5c4a214e3b62b477edd4022 Mon Sep 17 00:00:00 2001 From: Adam Stark Date: Thu, 18 May 2023 21:34:29 +0100 Subject: [PATCH 09/16] fix potential crash where empty data array can be passed to the library --- AudioFile.h | 3 +++ tests/GeneralTests.cpp | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/AudioFile.h b/AudioFile.h index aa33f50..36bb41d 100644 --- a/AudioFile.h +++ b/AudioFile.h @@ -1245,6 +1245,9 @@ void AudioFile::clearAudioBuffer() template AudioFileFormat AudioFile::determineAudioFileFormat (const std::vector& fileData) { + if (fileData.size() < 4) + return AudioFileFormat::Error; + std::string header (fileData.begin(), fileData.begin() + 4); if (header == "RIFF") diff --git a/tests/GeneralTests.cpp b/tests/GeneralTests.cpp index cc0ff39..705a5b5 100644 --- a/tests/GeneralTests.cpp +++ b/tests/GeneralTests.cpp @@ -107,4 +107,13 @@ TEST_SUITE ("General Tests") checkFilesAreExactlyTheSame (a, b); } + + //============================================================= + TEST_CASE ("GeneralTests::Empty Data") + { + AudioFile a; + a.shouldLogErrorsToConsole (false); + bool result = a.loadFromMemory (std::vector()); + CHECK_EQ (result, false); + } } From 431cc2d7b15d76c236055c0dfe2117260f8fde79 Mon Sep 17 00:00:00 2001 From: Alexandre Arsenault Date: Sat, 28 Oct 2023 14:01:54 -0400 Subject: [PATCH 10/16] Use ofstream::write in writeDataToFile --- AudioFile.h | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/AudioFile.h b/AudioFile.h index fcd3fd5..3b21479 100644 --- a/AudioFile.h +++ b/AudioFile.h @@ -1159,21 +1159,15 @@ template bool AudioFile::writeDataToFile (std::vector& fileData, std::string filePath) { std::ofstream outputFile (filePath, std::ios::binary); - - if (outputFile.is_open()) + + 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; } - - return false; + + outputFile.write ((const char*)fileData.data(), fileData.size()); + outputFile.close(); + return true; } //============================================================= From 5080af28a3f176eccd960b2c9a6a5a100ce173a9 Mon Sep 17 00:00:00 2001 From: Alexandre Arsenault Date: Sat, 28 Oct 2023 17:06:10 -0400 Subject: [PATCH 11/16] Make some methods static and add const to some vector ref --- AudioFile.h | 53 +++++++++++++++++++++++++++-------------------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/AudioFile.h b/AudioFile.h index fcd3fd5..e554196 100644 --- a/AudioFile.h +++ b/AudioFile.h @@ -105,7 +105,7 @@ class AudioFile //============================================================= /** Loads an audio file from data in memory */ - bool loadFromMemory (std::vector& fileData); + bool loadFromMemory (const std::vector& fileData); //============================================================= /** @Returns the sample rate */ @@ -185,9 +185,8 @@ class AudioFile }; //============================================================= - AudioFileFormat determineAudioFileFormat (std::vector& fileData); - bool decodeWaveFile (std::vector& fileData); - bool decodeAiffFile (std::vector& fileData); + bool decodeWaveFile (const std::vector& fileData); + bool decodeAiffFile (const std::vector& fileData); //============================================================= bool saveToWaveFile (std::string filePath); @@ -197,23 +196,25 @@ class AudioFile 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); + static inline AudioFileFormat determineAudioFileFormat (const std::vector& fileData); + + static inline int32_t fourBytesToInt (const std::vector& source, int startIndex, Endianness endianness = Endianness::LittleEndian); + static inline int16_t twoBytesToInt (const std::vector& source, int startIndex, Endianness endianness = Endianness::LittleEndian); + static inline int getIndexOfString (const std::vector& source, std::string s); + static inline int getIndexOfChunk (const 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); + static inline uint32_t getAiffSampleRate (const std::vector& fileData, int sampleRateStartIndex); + static inline bool tenByteMatch (const std::vector& v1, int startIndex1, const std::vector& v2, int startIndex2); + static inline 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); + static inline void addStringToFileData (std::vector& fileData, std::string s); + static inline void addInt32ToFileData (std::vector& fileData, int32_t i, Endianness endianness = Endianness::LittleEndian); + static inline void addInt16ToFileData (std::vector& fileData, int16_t i, Endianness endianness = Endianness::LittleEndian); //============================================================= - bool writeDataToFile (std::vector& fileData, std::string filePath); + static inline bool writeDataToFile (const std::vector& fileData, std::string filePath); //============================================================= void reportError (std::string errorMessage); @@ -545,7 +546,7 @@ bool AudioFile::load (std::string filePath) //============================================================= template -bool AudioFile::loadFromMemory (std::vector& fileData) +bool AudioFile::loadFromMemory (const std::vector& fileData) { // get audio file format audioFileFormat = determineAudioFileFormat (fileData); @@ -567,7 +568,7 @@ bool AudioFile::loadFromMemory (std::vector& fileData) //============================================================= template -bool AudioFile::decodeWaveFile (std::vector& fileData) +bool AudioFile::decodeWaveFile (const std::vector& fileData) { // ----------------------------------------------------------- // HEADER CHUNK @@ -726,7 +727,7 @@ bool AudioFile::decodeWaveFile (std::vector& fileData) //============================================================= template -bool AudioFile::decodeAiffFile (std::vector& fileData) +bool AudioFile::decodeAiffFile (const std::vector& fileData) { // ----------------------------------------------------------- // HEADER CHUNK @@ -881,7 +882,7 @@ bool AudioFile::decodeAiffFile (std::vector& fileData) //============================================================= template -uint32_t AudioFile::getAiffSampleRate (std::vector& fileData, int sampleRateStartIndex) +uint32_t AudioFile::getAiffSampleRate (const std::vector& fileData, int sampleRateStartIndex) { for (auto it : aiffSampleRateTable) { @@ -894,7 +895,7 @@ uint32_t AudioFile::getAiffSampleRate (std::vector& fileData, int sa //============================================================= template -bool AudioFile::tenByteMatch (std::vector& v1, int startIndex1, std::vector& v2, int startIndex2) +bool AudioFile::tenByteMatch (const std::vector& v1, int startIndex1, const std::vector& v2, int startIndex2) { for (int i = 0; i < 10; i++) { @@ -1156,7 +1157,7 @@ bool AudioFile::saveToAiffFile (std::string filePath) //============================================================= template -bool AudioFile::writeDataToFile (std::vector& fileData, std::string filePath) +bool AudioFile::writeDataToFile (const std::vector& fileData, std::string filePath) { std::ofstream outputFile (filePath, std::ios::binary); @@ -1244,7 +1245,7 @@ void AudioFile::clearAudioBuffer() //============================================================= template -AudioFileFormat AudioFile::determineAudioFileFormat (std::vector& fileData) +AudioFileFormat AudioFile::determineAudioFileFormat (const std::vector& fileData) { std::string header (fileData.begin(), fileData.begin() + 4); @@ -1258,7 +1259,7 @@ AudioFileFormat AudioFile::determineAudioFileFormat (std::vector& fi //============================================================= template -int32_t AudioFile::fourBytesToInt (std::vector& source, int startIndex, Endianness endianness) +int32_t AudioFile::fourBytesToInt (const std::vector& source, int startIndex, Endianness endianness) { if (source.size() >= (startIndex + 4)) { @@ -1280,7 +1281,7 @@ int32_t AudioFile::fourBytesToInt (std::vector& source, int startInd //============================================================= template -int16_t AudioFile::twoBytesToInt (std::vector& source, int startIndex, Endianness endianness) +int16_t AudioFile::twoBytesToInt (const std::vector& source, int startIndex, Endianness endianness) { int16_t result; @@ -1294,7 +1295,7 @@ int16_t AudioFile::twoBytesToInt (std::vector& source, int startInde //============================================================= template -int AudioFile::getIndexOfString (std::vector& source, std::string stringToSearchFor) +int AudioFile::getIndexOfString (const std::vector& source, std::string stringToSearchFor) { int index = -1; int stringLength = (int)stringToSearchFor.length(); @@ -1315,7 +1316,7 @@ int AudioFile::getIndexOfString (std::vector& source, std::string st //============================================================= template -int AudioFile::getIndexOfChunk (std::vector& source, const std::string& chunkHeaderID, int startIndex, Endianness endianness) +int AudioFile::getIndexOfChunk (const std::vector& source, const std::string& chunkHeaderID, int startIndex, Endianness endianness) { constexpr int dataLen = 4; From e575bf880d384a289f408d6dd60ab6b82d788964 Mon Sep 17 00:00:00 2001 From: Cosimo <46655466+encoded@users.noreply.github.com> Date: Mon, 1 Jul 2024 18:20:43 +0100 Subject: [PATCH 12/16] Included for uint types. --- AudioFile.h | 1 + 1 file changed, 1 insertion(+) diff --git a/AudioFile.h b/AudioFile.h index fcd3fd5..b96032f 100644 --- a/AudioFile.h +++ b/AudioFile.h @@ -47,6 +47,7 @@ #include #include #include +#include // disable some warnings on Windows #if defined (_MSC_VER) From dca19084edc2d17d0962e293ddbd70fda4deb82c Mon Sep 17 00:00:00 2001 From: Adam Stark Date: Mon, 18 Nov 2024 00:09:21 +0000 Subject: [PATCH 13/16] Remove AIFF sample rate table and add functions to calculate them directly --- AudioFile.h | 136 ++++++++++++++++++++++++------------- tests/AiffLoadingTests.cpp | 63 +++++++++++++++++ 2 files changed, 152 insertions(+), 47 deletions(-) diff --git a/AudioFile.h b/AudioFile.h index 9869b89..06b094c 100644 --- a/AudioFile.h +++ b/AudioFile.h @@ -205,7 +205,6 @@ class AudioFile //============================================================= static inline uint32_t getAiffSampleRate (const std::vector& fileData, int sampleRateStartIndex); - static inline bool tenByteMatch (const std::vector& v1, int startIndex1, const std::vector& v2, int startIndex2); static inline void addSampleRateToAiffData (std::vector& fileData, uint32_t sampleRate); //============================================================= @@ -271,27 +270,14 @@ struct AudioSampleConverter }; //============================================================= -// Pre-defined 10-byte representations of common sample rates -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}} +struct AiffUtilities +{ + //============================================================= + /** Decode an 80-bit (10 byte) sample rate to a double */ + static inline double decodeAiffSampleRate (const uint8_t* bytes); + + /** Encode a double as an 80-bit (10-byte) sample rate */ + static inline void encodeAiffSampleRate (double sampleRate, uint8_t* bytes); }; //============================================================= @@ -884,37 +870,17 @@ bool AudioFile::decodeAiffFile (const std::vector& fileData) template uint32_t AudioFile::getAiffSampleRate (const 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 (const std::vector& v1, int startIndex1, const std::vector& v2, int startIndex2) -{ - for (int i = 0; i < 10; i++) - { - if (v1[startIndex1 + i] != v2[startIndex2 + i]) - return false; - } - - return true; + double sampleRate = AiffUtilities::decodeAiffSampleRate (&fileData[sampleRateStartIndex]); + return static_cast (sampleRate); } //============================================================= 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]); - } + std::array sampleRateData; + AiffUtilities::encodeAiffSampleRate (static_cast (sampleRate), sampleRateData.data()); + fileData.insert (fileData.end(), sampleRateData.begin(), sampleRateData.end()); } //============================================================= @@ -1561,6 +1527,82 @@ T AudioSampleConverter::clamp (T value, T minValue, T maxValue) return value; } +//============================================================= +inline double AiffUtilities::decodeAiffSampleRate (const uint8_t* bytes) +{ + // Note: Sample rate is 80 bits made up of + // * 1 sign bit + // * 15 exponent bits + // * 64 mantissa bits + + // ---------------------------------------------- + // Sign + + // Extract the sign (most significant bit of byte 0) + int sign = (bytes[0] & 0x80) ? -1 : 1; + + // ---------------------------------------------- + // Exponent + + // byte 0: ignore the sign and shift the most significant bits to the left by one byte + uint16_t msbShifted = (static_cast (bytes[0] & 0x7F) << 8); + + // calculate exponent by combining byte 0 and byte 1 and subtract bias + uint16_t exponent = (msbShifted | static_cast (bytes[1])) - 16383; + + // ---------------------------------------------- + // Mantissa + + // Extract the mantissa (remaining 64 bits) by looping over the remaining + // bytes and combining them while shifting the result to the left by + // 8 bits each time + uint64_t mantissa = 0; + + for (int i = 2; i < 10; ++i) + mantissa = (mantissa << 8) | bytes[i]; + + // Normalize the mantissa (implicit leading 1 for normalized values) + double normalisedMantissa = static_cast (mantissa) / (1ULL << 63); + + // ---------------------------------------------- + // Combine sign, exponent, and mantissa into a double + + return sign * std::ldexp (normalisedMantissa, exponent); +} + +//============================================================= +inline void AiffUtilities::encodeAiffSampleRate (double sampleRate, uint8_t* bytes) +{ + // Determine the sign + int sign = (sampleRate < 0) ? -1 : 1; + + if (sign == -1) + sampleRate = -sampleRate; + + // Set most significant bit of byte 0 for the sign + bytes[0] = (sign == -1) ? 0x80 : 0x00; + + // Calculate the exponent using logarithm (log base 2) + int exponent = (log (sampleRate) / log (2.0)); + + // Add bias to exponent for AIFF + uint16_t biasedExponent = static_cast (exponent + 16383); + + // Normalize the sample rate + double normalizedSampleRate = sampleRate / pow (2.0, exponent); + + // Calculate the mantissa + uint64_t mantissa = static_cast (normalizedSampleRate * (1ULL << 63)); + + // Pack the exponent into first two bytes of 10-byte AIFF format + bytes[0] |= (biasedExponent >> 8) & 0x7F; // Upper 7 bits of exponent + bytes[1] = biasedExponent & 0xFF; // Lower 8 bits of exponent + + // Put the mantissa into byte array + for (int i = 0; i < 8; ++i) + bytes[2 + i] = (mantissa >> (8 * (7 - i))) & 0xFF; +} + #if defined (_MSC_VER) __pragma(warning (pop)) #elif defined (__GNUC__) diff --git a/tests/AiffLoadingTests.cpp b/tests/AiffLoadingTests.cpp index 90f71c9..493dba3 100644 --- a/tests/AiffLoadingTests.cpp +++ b/tests/AiffLoadingTests.cpp @@ -350,3 +350,66 @@ TEST_SUITE ("AiffLoadingTests - Integer Types - 24-bit File") test24Bit44100WithInteger (true); } } + +//============================================================= +TEST_SUITE ("AiffLoadingTests - Sample Rates") +{ + //============================================================= + TEST_CASE ("AiffLoadingTests - Sample Rates - Common Sample Rates") + { + // Pre-defined 10-byte representations of common sample rates + 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}} + }; + + std::vector sampleRateBytes (10); + + for (const auto& pair : aiffSampleRateTable) + { + uint32_t sampleRate = pair.first; + double inputSampleRate = sampleRate; + + // encode into bytes + AiffUtilities::encodeAiffSampleRate (static_cast (sampleRate), sampleRateBytes.data()); + + for (int i = 0; i < 10; i++) + CHECK_EQ (sampleRateBytes[i], aiffSampleRateTable[sampleRate][i]); + + double outputSampleRate = AiffUtilities::decodeAiffSampleRate (aiffSampleRateTable[sampleRate].data()); + + CHECK_EQ (inputSampleRate, outputSampleRate); + } + } + + //============================================================= + TEST_CASE ("AiffLoadingTests - Sample Rates - Round Trip Encode/Decode Tests") + { + std::vector sampleRateBytes (10); + + for (int i = -100000; i < 100000; i += 237) // + odd number to reduce cycles but check odd and even values + { + double input = static_cast (i); + AiffUtilities::encodeAiffSampleRate (input, sampleRateBytes.data()); + double output = AiffUtilities::decodeAiffSampleRate (sampleRateBytes.data()); + CHECK_EQ (input, output); + } + } +} From d62731feb71fe9a80151ac59751d0457a0fa1d31 Mon Sep 17 00:00:00 2001 From: Adam Stark Date: Mon, 18 Nov 2024 00:11:48 +0000 Subject: [PATCH 14/16] Include --- AudioFile.h | 1 + 1 file changed, 1 insertion(+) diff --git a/AudioFile.h b/AudioFile.h index 29449dd..b5d2991 100644 --- a/AudioFile.h +++ b/AudioFile.h @@ -48,6 +48,7 @@ #include #include #include +#include // disable some warnings on Windows #if defined (_MSC_VER) From dbc49d72602cba4be2a0d14ab74f40f6f100fe0f Mon Sep 17 00:00:00 2001 From: Adam Stark Date: Mon, 18 Nov 2024 00:14:02 +0000 Subject: [PATCH 15/16] Add #include --- AudioFile.h | 1 + 1 file changed, 1 insertion(+) diff --git a/AudioFile.h b/AudioFile.h index b5d2991..39746ad 100644 --- a/AudioFile.h +++ b/AudioFile.h @@ -49,6 +49,7 @@ #include #include #include +#include // disable some warnings on Windows #if defined (_MSC_VER) From 80152bf5b7a72e37413e919eba3e26f84cbd13bc Mon Sep 17 00:00:00 2001 From: Adam Stark Date: Mon, 18 Nov 2024 00:26:17 +0000 Subject: [PATCH 16/16] fix: wrong output file type in examples --- examples/examples.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/examples.cpp b/examples/examples.cpp index 6662839..7823520 100644 --- a/examples/examples.cpp +++ b/examples/examples.cpp @@ -141,6 +141,6 @@ namespace examples // 4. Write audio file to disk std::string outputFilePath = "quieter-audio-file.wav"; // change this to somewhere useful for you - a.save (outputFilePath, AudioFileFormat::Aiff); + a.save (outputFilePath, AudioFileFormat::Wave); } }