From a76b2fe90bc3b80388c8ffd0c5e7584f18231964 Mon Sep 17 00:00:00 2001 From: Dimitri Diakopoulos Date: Sun, 17 May 2015 20:29:39 -0700 Subject: [PATCH] add conformance interface for wav encoding - bit depth supported, channels next, and later on sample rate --- examples/src/Main.cpp | 5 ++-- include/libnyquist/Common.h | 37 ++++++++++++++++++++++++----- include/libnyquist/Dither.h | 25 ++++++++++++++++---- src/Common.cpp | 46 +++++++++++++++++++++++++++++++++++++ src/RiffUtils.cpp | 4 +--- src/WavEncoder.cpp | 32 ++++++++++++++------------ 6 files changed, 118 insertions(+), 31 deletions(-) diff --git a/examples/src/Main.cpp b/examples/src/Main.cpp index 31ace8e..fb17f0d 100644 --- a/examples/src/Main.cpp +++ b/examples/src/Main.cpp @@ -98,7 +98,8 @@ int main() } // Test wav file encoder - encoder.WriteFile({2, PCM_FLT, DITHER_NONE}, fileData, "encoded.wav"); - + int encoderStatus = encoder.WriteFile({2, PCM_24, DITHER_NONE}, fileData, "encoded.wav"); + std::cout << "Encoder Status: " << encoderStatus << std::endl; + return 0; } \ No newline at end of file diff --git a/include/libnyquist/Common.h b/include/libnyquist/Common.h index 4debe25..972b37a 100644 --- a/include/libnyquist/Common.h +++ b/include/libnyquist/Common.h @@ -35,7 +35,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #include "PostProcess.h" +#include "Dither.h" namespace nqr { @@ -116,7 +118,6 @@ inline bool isOdd(const uint32_t x) { return (x & 0x1); } - #ifdef ARCH_CPU_LITTLE_ENDIAN #define Read16(n) (n) @@ -181,6 +182,22 @@ inline int32_t Pack(uint8_t a, uint8_t b, uint8_t c) #endif } +inline std::array Unpack(uint32_t a) +{ + static std::array output; + + #ifdef ARCH_CPU_LITTLE_ENDIAN + output[0] = a >> 0; + output[1] = a >> 8; + output[2] = a >> 16; + #else + output[0] = a >> 16; + output[1] = a >> 8; + output[2] = a >> 0; + #endif + return output; +} + ////////////////////////// // Conversion Utilities // ////////////////////////// @@ -192,11 +209,17 @@ inline int32_t Pack(uint8_t a, uint8_t b, uint8_t c) static const float NQR_BYTE_2_FLT = 1.0f / 127.0f; -#define int8_to_float32(s) ( (float) (s) * NQR_BYTE_2_FLT) -#define uint8_to_float32(s) ( ((float) (s) - 128) * NQR_BYTE_2_FLT) -#define int16_to_float32(s) ( (float) (s) / NQR_INT16_MAX) -#define int24_to_float32(s) ( (float) (s) / NQR_INT24_MAX) -#define int32_to_float32(s) ( (float) (s) / NQR_INT32_MAX) +#define int8_to_float32(s) ((float) (s) * NQR_BYTE_2_FLT) +#define uint8_to_float32(s)(((float) (s) - 128) * NQR_BYTE_2_FLT) +#define int16_to_float32(s) ((float) (s) / NQR_INT16_MAX) +#define int24_to_float32(s) ((float) (s) / NQR_INT24_MAX) +#define int32_to_float32(s) ((float) (s) / NQR_INT32_MAX) + +#define float32_to_int8(s) (int8_t) (s * 127.f) +#define float32_to_uint8(s) (uint8_t)((s * 127.f) + 128) +#define float32_to_int16(s) (int16_t) (s * NQR_INT16_MAX) +#define float32_to_int24(s) (int32_t) (s * NQR_INT24_MAX) +#define float32_to_int32(s) (int32_t) (s * NQR_INT32_MAX) //@todo add 12, 20 for flac enum PCMFormat @@ -219,6 +242,8 @@ void ConvertToFloat32(float * dst, const uint8_t * src, const size_t N, PCMForma // Src data is always aligned to 4 bytes (WavPack, primarily) void ConvertToFloat32(float * dst, const int32_t * src, const size_t N, PCMFormat f); +void ConvertFromFloat32(uint8_t * dst, const float * src, const size_t N, PCMFormat f, DitherType t = DITHER_NONE); + ////////////////////////// // User Data + File Ops // ////////////////////////// diff --git a/include/libnyquist/Dither.h b/include/libnyquist/Dither.h index f3ff662..adc391d 100644 --- a/include/libnyquist/Dither.h +++ b/include/libnyquist/Dither.h @@ -26,9 +26,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef DITHER_OPERATIONS_H #define DITHER_OPERATIONS_H -#include "Common.h" - -//@todo libnyquist will, at some point, allow conversion between arbitrary bit-depths +#include namespace nqr { @@ -36,10 +34,27 @@ namespace nqr enum DitherType { DITHER_NONE, - DITHER_ONE, - DITHER_TWO + DITHER_TRIANGLE }; +class TriDither +{ + std::uniform_real_distribution distribution; + std::mt19937 rndGen; + float prev = 0.0f; +public: + + TriDither() : distribution(-0.5f, +0.5f) {} + + float operator()(float s) + { + const float value = distribution(rndGen); + s = s + value - prev; + prev = value; + return s; + } +}; + } // end namespace nqr #endif diff --git a/src/Common.cpp b/src/Common.cpp index 4b6742c..20f3503 100644 --- a/src/Common.cpp +++ b/src/Common.cpp @@ -146,6 +146,52 @@ void nqr::ConvertToFloat32(float * dst, const int32_t * src, const size_t N, PCM } } +void nqr::ConvertFromFloat32(uint8_t * dst, const float * src, const size_t N, PCMFormat f, DitherType t) +{ + //@todo implement dither + + assert(f != PCM_END); + + if (f == PCM_U8) + { + uint8_t * destPtr = reinterpret_cast(dst); + for (size_t i = 0; i < N; ++i) + destPtr[i] = float32_to_uint8(src[i]); + } + else if (f == PCM_S8) + { + int8_t * destPtr = reinterpret_cast(dst); + for (size_t i = 0; i < N; ++i) + destPtr[i] = float32_to_int8(src[i]); + } + else if (f == PCM_16) + { + int16_t * destPtr = reinterpret_cast(dst); + for (size_t i = 0; i < N; ++i) + destPtr[i] = float32_to_int16(src[i]); + } + else if (f == PCM_24) + { + uint8_t * destPtr = reinterpret_cast(dst); + size_t c = 0; + for (size_t i = 0; i < N; ++i) + { + auto sample = float32_to_int24(src[i]); + auto unpacked = Unpack(sample); // Handles endian swap + destPtr[c] = unpacked[0]; + destPtr[c+1] = unpacked[1]; + destPtr[c+2] = unpacked[2]; + c += 3; + } + } + else if (f == PCM_32) + { + int32_t * destPtr = reinterpret_cast(dst); + for (size_t i = 0; i < N; ++i) + destPtr[i] = float32_to_int32(src[i]); + } +} + int nqr::GetFormatBitsPerSample(PCMFormat f) { switch(f) diff --git a/src/RiffUtils.cpp b/src/RiffUtils.cpp index f8ea6e9..cc175cb 100644 --- a/src/RiffUtils.cpp +++ b/src/RiffUtils.cpp @@ -47,8 +47,6 @@ ChunkHeaderInfo nqr::ScanForChunk(const std::vector & fileData, uint32_ return {0, 0}; }; - - WaveChunkHeader nqr::MakeWaveHeader(const EncoderParams param, const int sampleRate) { WaveChunkHeader header; @@ -65,4 +63,4 @@ WaveChunkHeader nqr::MakeWaveHeader(const EncoderParams param, const int sampleR header.bit_depth = bitdepth; return header; -} \ No newline at end of file +} diff --git a/src/WavEncoder.cpp b/src/WavEncoder.cpp index 9fa9b0c..78f8de0 100644 --- a/src/WavEncoder.cpp +++ b/src/WavEncoder.cpp @@ -36,15 +36,9 @@ inline void toBytes(int value, char * arr) arr[3] = (value >> 24) & 0xFF; } -WavEncoder::WavEncoder() -{ - -} +WavEncoder::WavEncoder() {} -WavEncoder::~WavEncoder() -{ - -} +WavEncoder::~WavEncoder() {} int WavEncoder::WriteFile(const EncoderParams p, const AudioData * d, const std::string & path) { @@ -59,7 +53,7 @@ int WavEncoder::WriteFile(const EncoderParams p, const AudioData * d, const std: } auto maxFileSizeInBytes = std::numeric_limits::max(); - auto samplesSizeInBytes = d->samples.size() * sizeof(float); + auto samplesSizeInBytes = (d->samples.size() * GetFormatBitsPerSample(p.targetFormat)) / 8; // 64 arbitrary if ((samplesSizeInBytes - 64) >= maxFileSizeInBytes) @@ -82,7 +76,7 @@ int WavEncoder::WriteFile(const EncoderParams p, const AudioData * d, const std: char * chunkSizeBuff = new char[4]; - // Initial size + // Initial size (this is changed after we're done writing the file) toBytes(36, chunkSizeBuff); // RIFF file header @@ -98,14 +92,18 @@ int WavEncoder::WriteFile(const EncoderParams p, const AudioData * d, const std: auto sourceBits = GetFormatBitsPerSample(d->sourceFormat); auto targetBits = GetFormatBitsPerSample(p.targetFormat); + //////////////////////////// + //@todo - channel mixing! // + //////////////////////////// + // Write out fact chunk if (p.targetFormat == PCM_FLT) { uint32_t four = 4; uint32_t dataSz = int(d->samples.size() / d->channelCount); fout.write(GenerateChunkCodeChar('f', 'a', 'c', 't'), 4); - fout.write(reinterpret_cast(&four), sizeof(four)); - fout.write(reinterpret_cast(&dataSz), sizeof(dataSz)); // Number of samples (per channel) + fout.write(reinterpret_cast(&four), 4); + fout.write(reinterpret_cast(&dataSz), 4); // Number of samples (per channel) } // Data header @@ -115,13 +113,17 @@ int WavEncoder::WriteFile(const EncoderParams p, const AudioData * d, const std: toBytes(int(samplesSizeInBytes), chunkSizeBuff); fout.write(chunkSizeBuff, 4); - // Apply dithering - if (sourceBits != targetBits && p.dither != DITHER_NONE) + if (targetBits <= sourceBits && p.targetFormat != PCM_FLT) { - + // At most need this number of bytes in our copy + std::vector samplesCopy(samplesSizeInBytes); + ConvertFromFloat32(samplesCopy.data(), d->samples.data(), d->samples.size(), p.targetFormat, p.dither); + fout.write(reinterpret_cast(samplesCopy.data()), samplesSizeInBytes); } else { + // Handle PCM_FLT. Coming in from AudioData ensures we start with 32 bits, so we're fine + // since we've also rejected formats with more than 32 bits above. fout.write(reinterpret_cast(d->samples.data()), samplesSizeInBytes); }