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); + } + } +}