From 33c542f2e8cee892b6a806720fd02a2c2fa96e52 Mon Sep 17 00:00:00 2001 From: Daniel Wolf Date: Tue, 14 Sep 2021 20:11:57 +0200 Subject: [PATCH 1/4] Simplify .gitattributes file --- .gitattributes | 62 -------------------------------------------------- 1 file changed, 62 deletions(-) diff --git a/.gitattributes b/.gitattributes index 1ff0c42..176a458 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,63 +1 @@ -############################################################################### -# Set default behavior to automatically normalize line endings. -############################################################################### * text=auto - -############################################################################### -# Set default behavior for command prompt diff. -# -# This is need for earlier builds of msysgit that does not have it on by -# default for csharp files. -# Note: This is only used by command line -############################################################################### -#*.cs diff=csharp - -############################################################################### -# Set the merge driver for project and solution files -# -# Merging from the command prompt will add diff markers to the files if there -# are conflicts (Merging from VS is not affected by the settings below, in VS -# the diff markers are never inserted). Diff markers may cause the following -# file extensions to fail to load in VS. An alternative would be to treat -# these files as binary and thus will always conflict and require user -# intervention with every merge. To do so, just uncomment the entries below -############################################################################### -#*.sln merge=binary -#*.csproj merge=binary -#*.vbproj merge=binary -#*.vcxproj merge=binary -#*.vcproj merge=binary -#*.dbproj merge=binary -#*.fsproj merge=binary -#*.lsproj merge=binary -#*.wixproj merge=binary -#*.modelproj merge=binary -#*.sqlproj merge=binary -#*.wwaproj merge=binary - -############################################################################### -# behavior for image files -# -# image files are treated as binary by default. -############################################################################### -#*.jpg binary -#*.png binary -#*.gif binary - -############################################################################### -# diff behavior for common document formats -# -# Convert binary document formats to text before diffing them. This feature -# is only available from the command line. Turn it on by uncommenting the -# entries below. -############################################################################### -#*.doc diff=astextplain -#*.DOC diff=astextplain -#*.docx diff=astextplain -#*.DOCX diff=astextplain -#*.dot diff=astextplain -#*.DOT diff=astextplain -#*.pdf diff=astextplain -#*.PDF diff=astextplain -#*.rtf diff=astextplain -#*.RTF diff=astextplain From 5f293cdd3314b796d4a2a89c73f27d0dd7c00eff Mon Sep 17 00:00:00 2001 From: Daniel Wolf Date: Tue, 21 Sep 2021 19:47:53 +0200 Subject: [PATCH 2/4] Extract WAVE format parsing into its own function --- rhubarb/src/audio/WaveFileReader.cpp | 13 +++++++++---- rhubarb/src/audio/WaveFileReader.h | 20 +++++++++++--------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/rhubarb/src/audio/WaveFileReader.cpp b/rhubarb/src/audio/WaveFileReader.cpp index df24ccb..72d4b16 100644 --- a/rhubarb/src/audio/WaveFileReader.cpp +++ b/rhubarb/src/audio/WaveFileReader.cpp @@ -33,10 +33,9 @@ namespace Codec { string codecToString(int codec); -WaveFileReader::WaveFileReader(const path& filePath) : - filePath(filePath), - formatInfo {} -{ +WaveFormatInfo getWaveFormatInfo(const path& filePath) { + WaveFormatInfo formatInfo {}; + auto file = openFile(filePath); file.seekg(0, std::ios_base::end); @@ -139,8 +138,14 @@ WaveFileReader::WaveFileReader(const path& filePath) : } } } + + return formatInfo; } +WaveFileReader::WaveFileReader(const path& filePath) : + filePath(filePath), + formatInfo(getWaveFormatInfo(filePath)) {} + unique_ptr WaveFileReader::clone() const { return make_unique(*this); } diff --git a/rhubarb/src/audio/WaveFileReader.h b/rhubarb/src/audio/WaveFileReader.h index c200f35..c128b0f 100644 --- a/rhubarb/src/audio/WaveFileReader.h +++ b/rhubarb/src/audio/WaveFileReader.h @@ -10,6 +10,17 @@ enum class SampleFormat { Float32 }; +struct WaveFormatInfo { + int bytesPerFrame; + SampleFormat sampleFormat; + int frameRate; + int64_t frameCount; + int channelCount; + std::streampos dataOffset; +}; + +WaveFormatInfo getWaveFormatInfo(const std::filesystem::path& filePath); + class WaveFileReader : public AudioClip { public: WaveFileReader(const std::filesystem::path& filePath); @@ -20,15 +31,6 @@ public: private: SampleReader createUnsafeSampleReader() const override; - struct WaveFormatInfo { - int bytesPerFrame; - SampleFormat sampleFormat; - int frameRate; - int64_t frameCount; - int channelCount; - std::streampos dataOffset; - }; - std::filesystem::path filePath; WaveFormatInfo formatInfo; }; From 3c0befa0703c235ae0fe6bdda9b616f346a99dd4 Mon Sep 17 00:00:00 2001 From: Daniel Wolf Date: Tue, 14 Sep 2021 20:16:15 +0200 Subject: [PATCH 3/4] Add test WAVE files --- .gitattributes | 6 ++++++ .github/workflows/ci.yml | 2 ++ rhubarb/tests/resources/README.adoc | 4 ++++ rhubarb/tests/resources/sine-triangle-flac-ffmpeg.wav | 3 +++ rhubarb/tests/resources/sine-triangle-float32-audacity.wav | 3 +++ rhubarb/tests/resources/sine-triangle-float32-audition.wav | 3 +++ rhubarb/tests/resources/sine-triangle-float32-ffmpeg.wav | 3 +++ .../tests/resources/sine-triangle-float32-soundforge.wav | 3 +++ rhubarb/tests/resources/sine-triangle-float64-ffmpeg.wav | 3 +++ rhubarb/tests/resources/sine-triangle-int16-audacity.wav | 3 +++ rhubarb/tests/resources/sine-triangle-int16-audition.wav | 3 +++ rhubarb/tests/resources/sine-triangle-int16-ffmpeg.wav | 3 +++ rhubarb/tests/resources/sine-triangle-int16-soundforge.wav | 3 +++ rhubarb/tests/resources/sine-triangle-int24-audacity.wav | 3 +++ rhubarb/tests/resources/sine-triangle-int24-audition.wav | 3 +++ rhubarb/tests/resources/sine-triangle-int24-ffmpeg.wav | 3 +++ rhubarb/tests/resources/sine-triangle-int24-soundforge.wav | 3 +++ rhubarb/tests/resources/sine-triangle-int32-ffmpeg.wav | 3 +++ rhubarb/tests/resources/sine-triangle-int32-soundforge.wav | 3 +++ rhubarb/tests/resources/sine-triangle-uint8-audition.wav | 3 +++ rhubarb/tests/resources/sine-triangle-uint8-ffmpeg.wav | 3 +++ rhubarb/tests/resources/sine-triangle-uint8-soundforge.wav | 3 +++ rhubarb/tests/resources/sine-triangle-vorbis-ffmpeg.wav | 3 +++ 23 files changed, 72 insertions(+) create mode 100644 rhubarb/tests/resources/README.adoc create mode 100644 rhubarb/tests/resources/sine-triangle-flac-ffmpeg.wav create mode 100644 rhubarb/tests/resources/sine-triangle-float32-audacity.wav create mode 100644 rhubarb/tests/resources/sine-triangle-float32-audition.wav create mode 100644 rhubarb/tests/resources/sine-triangle-float32-ffmpeg.wav create mode 100644 rhubarb/tests/resources/sine-triangle-float32-soundforge.wav create mode 100644 rhubarb/tests/resources/sine-triangle-float64-ffmpeg.wav create mode 100644 rhubarb/tests/resources/sine-triangle-int16-audacity.wav create mode 100644 rhubarb/tests/resources/sine-triangle-int16-audition.wav create mode 100644 rhubarb/tests/resources/sine-triangle-int16-ffmpeg.wav create mode 100644 rhubarb/tests/resources/sine-triangle-int16-soundforge.wav create mode 100644 rhubarb/tests/resources/sine-triangle-int24-audacity.wav create mode 100644 rhubarb/tests/resources/sine-triangle-int24-audition.wav create mode 100644 rhubarb/tests/resources/sine-triangle-int24-ffmpeg.wav create mode 100644 rhubarb/tests/resources/sine-triangle-int24-soundforge.wav create mode 100644 rhubarb/tests/resources/sine-triangle-int32-ffmpeg.wav create mode 100644 rhubarb/tests/resources/sine-triangle-int32-soundforge.wav create mode 100644 rhubarb/tests/resources/sine-triangle-uint8-audition.wav create mode 100644 rhubarb/tests/resources/sine-triangle-uint8-ffmpeg.wav create mode 100644 rhubarb/tests/resources/sine-triangle-uint8-soundforge.wav create mode 100644 rhubarb/tests/resources/sine-triangle-vorbis-ffmpeg.wav diff --git a/.gitattributes b/.gitattributes index 176a458..3384f57 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,7 @@ * text=auto + +# Use Git LFS for binary files +*.wav filter=lfs diff=lfs merge=lfs -text +*.flac filter=lfs diff=lfs merge=lfs -text +*.ogg filter=lfs diff=lfs merge=lfs -text +*.mp3 filter=lfs diff=lfs merge=lfs -text diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8f6a96e..2ef6204 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,6 +31,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v2 + with: + lfs: true - name: Restore Boost from cache uses: actions/cache@v2 id: cache-boost diff --git a/rhubarb/tests/resources/README.adoc b/rhubarb/tests/resources/README.adoc new file mode 100644 index 0000000..8d9510b --- /dev/null +++ b/rhubarb/tests/resources/README.adoc @@ -0,0 +1,4 @@ +This directory contains test files for the WAVE file reader. + +All files starting with _sine-rect_ contain the same 10-second stereo signal sampled at 48,000 Hz. The left channel contains a 1 kHz sine wave, the right channel contains a 1 kHz triangle wave. As those signals are strictly periodic, Git can compress these files very efficiently. + diff --git a/rhubarb/tests/resources/sine-triangle-flac-ffmpeg.wav b/rhubarb/tests/resources/sine-triangle-flac-ffmpeg.wav new file mode 100644 index 0000000..02d91ff --- /dev/null +++ b/rhubarb/tests/resources/sine-triangle-flac-ffmpeg.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:491fee574896f2da599659b08498ea9b605bb4679dc38ce588490222831a0c31 +size 1937792 diff --git a/rhubarb/tests/resources/sine-triangle-float32-audacity.wav b/rhubarb/tests/resources/sine-triangle-float32-audacity.wav new file mode 100644 index 0000000..9c3ac39 --- /dev/null +++ b/rhubarb/tests/resources/sine-triangle-float32-audacity.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:61a5d297769ac42ffc8e54d0f3923ee0064ce8ecba14ccc874b31c25e5c8c9e1 +size 3840194 diff --git a/rhubarb/tests/resources/sine-triangle-float32-audition.wav b/rhubarb/tests/resources/sine-triangle-float32-audition.wav new file mode 100644 index 0000000..d11a86c --- /dev/null +++ b/rhubarb/tests/resources/sine-triangle-float32-audition.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a5645586937fa012dfc051e2c38126c6221a1937dd2fbceebfddb971be53a44d +size 3845534 diff --git a/rhubarb/tests/resources/sine-triangle-float32-ffmpeg.wav b/rhubarb/tests/resources/sine-triangle-float32-ffmpeg.wav new file mode 100644 index 0000000..7e0c1a6 --- /dev/null +++ b/rhubarb/tests/resources/sine-triangle-float32-ffmpeg.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b4092089069670b6b1259f5cd34f91afa36066678f80464186118ddcff695923 +size 3840114 diff --git a/rhubarb/tests/resources/sine-triangle-float32-soundforge.wav b/rhubarb/tests/resources/sine-triangle-float32-soundforge.wav new file mode 100644 index 0000000..3410566 --- /dev/null +++ b/rhubarb/tests/resources/sine-triangle-float32-soundforge.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:05e91311e5d7a266cb15f057e60f59295b535f0bf3313c4d9cde54037cc6fa3f +size 3840078 diff --git a/rhubarb/tests/resources/sine-triangle-float64-ffmpeg.wav b/rhubarb/tests/resources/sine-triangle-float64-ffmpeg.wav new file mode 100644 index 0000000..4b84ce5 --- /dev/null +++ b/rhubarb/tests/resources/sine-triangle-float64-ffmpeg.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cdf972bf29dc137ff022761260169dbd6e15c668f7cde69f1925de393fa88544 +size 7680114 diff --git a/rhubarb/tests/resources/sine-triangle-int16-audacity.wav b/rhubarb/tests/resources/sine-triangle-int16-audacity.wav new file mode 100644 index 0000000..0d44a08 --- /dev/null +++ b/rhubarb/tests/resources/sine-triangle-int16-audacity.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fd8d91cf1d1593a103b5b43ea08ab46ca1d66e55e10bbe97c097f7e605155904 +size 1920150 diff --git a/rhubarb/tests/resources/sine-triangle-int16-audition.wav b/rhubarb/tests/resources/sine-triangle-int16-audition.wav new file mode 100644 index 0000000..7f9fc8a --- /dev/null +++ b/rhubarb/tests/resources/sine-triangle-int16-audition.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:885f25245b4227c1761828a6365cb60fa36d81d7e636773ab3702621640bf651 +size 1925534 diff --git a/rhubarb/tests/resources/sine-triangle-int16-ffmpeg.wav b/rhubarb/tests/resources/sine-triangle-int16-ffmpeg.wav new file mode 100644 index 0000000..e3e4b68 --- /dev/null +++ b/rhubarb/tests/resources/sine-triangle-int16-ffmpeg.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:63e38d3e7b9e03c08a8f46a25b8d7325d0565130a5b74296cbe64e49eea65696 +size 1920078 diff --git a/rhubarb/tests/resources/sine-triangle-int16-soundforge.wav b/rhubarb/tests/resources/sine-triangle-int16-soundforge.wav new file mode 100644 index 0000000..4825a4a --- /dev/null +++ b/rhubarb/tests/resources/sine-triangle-int16-soundforge.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:13741b9dfbf358485cbbc0076713ee9473be970f8cc4686ca75cf286845b2d20 +size 1920078 diff --git a/rhubarb/tests/resources/sine-triangle-int24-audacity.wav b/rhubarb/tests/resources/sine-triangle-int24-audacity.wav new file mode 100644 index 0000000..9b8bbb5 --- /dev/null +++ b/rhubarb/tests/resources/sine-triangle-int24-audacity.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6b06e78f2ba11722d516b8367b7c847452d55b2936ab2f28b82fa6fd5c97f383 +size 2880150 diff --git a/rhubarb/tests/resources/sine-triangle-int24-audition.wav b/rhubarb/tests/resources/sine-triangle-int24-audition.wav new file mode 100644 index 0000000..8359a18 --- /dev/null +++ b/rhubarb/tests/resources/sine-triangle-int24-audition.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f668649a95293cd031e2d169e444fedfb07aabc6ded2548ce310262aa66c1082 +size 2885534 diff --git a/rhubarb/tests/resources/sine-triangle-int24-ffmpeg.wav b/rhubarb/tests/resources/sine-triangle-int24-ffmpeg.wav new file mode 100644 index 0000000..2ab556e --- /dev/null +++ b/rhubarb/tests/resources/sine-triangle-int24-ffmpeg.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:93248779904023c1e22bd4970f507980a6496e8327dd8a49ce81616375c96f09 +size 2880102 diff --git a/rhubarb/tests/resources/sine-triangle-int24-soundforge.wav b/rhubarb/tests/resources/sine-triangle-int24-soundforge.wav new file mode 100644 index 0000000..af729a0 --- /dev/null +++ b/rhubarb/tests/resources/sine-triangle-int24-soundforge.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:706c94020f6fe8368383ef28720e10c701e239a9665213eee831e28d59a7a753 +size 2880078 diff --git a/rhubarb/tests/resources/sine-triangle-int32-ffmpeg.wav b/rhubarb/tests/resources/sine-triangle-int32-ffmpeg.wav new file mode 100644 index 0000000..cc051f7 --- /dev/null +++ b/rhubarb/tests/resources/sine-triangle-int32-ffmpeg.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:374e6a01afc020355abfffb4b8bfbfefee9ace878081dc265134ae79ccfaf820 +size 3840102 diff --git a/rhubarb/tests/resources/sine-triangle-int32-soundforge.wav b/rhubarb/tests/resources/sine-triangle-int32-soundforge.wav new file mode 100644 index 0000000..d885aec --- /dev/null +++ b/rhubarb/tests/resources/sine-triangle-int32-soundforge.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4f88ac09446b567afa0783a275ee2d3f15e3a05749c08b7bdc75b6f4bbfa1acb +size 3840078 diff --git a/rhubarb/tests/resources/sine-triangle-uint8-audition.wav b/rhubarb/tests/resources/sine-triangle-uint8-audition.wav new file mode 100644 index 0000000..168a106 --- /dev/null +++ b/rhubarb/tests/resources/sine-triangle-uint8-audition.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8e8fbc6809850523bc274863f07d13b258647b496977ffbf62f2d9ef20f1f40d +size 965534 diff --git a/rhubarb/tests/resources/sine-triangle-uint8-ffmpeg.wav b/rhubarb/tests/resources/sine-triangle-uint8-ffmpeg.wav new file mode 100644 index 0000000..57fbdab --- /dev/null +++ b/rhubarb/tests/resources/sine-triangle-uint8-ffmpeg.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bff9c1914f12e3bb1f8c1de515bb4d0b38a44dd5d3e5ba7e8e5f9406fe3dfd04 +size 960078 diff --git a/rhubarb/tests/resources/sine-triangle-uint8-soundforge.wav b/rhubarb/tests/resources/sine-triangle-uint8-soundforge.wav new file mode 100644 index 0000000..e46e807 --- /dev/null +++ b/rhubarb/tests/resources/sine-triangle-uint8-soundforge.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c50aa57ea11921d4be417d5339a91ecfa095e755be06adbfba0d4bb52bac56c6 +size 960078 diff --git a/rhubarb/tests/resources/sine-triangle-vorbis-ffmpeg.wav b/rhubarb/tests/resources/sine-triangle-vorbis-ffmpeg.wav new file mode 100644 index 0000000..6b84312 --- /dev/null +++ b/rhubarb/tests/resources/sine-triangle-vorbis-ffmpeg.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e3ffb9e1aaf3b06cb02f07b8aeeb9c9f400e40e265618991e027b59295f7e562 +size 72652 From 77588fb40e41bda85dc4763d2838fc2e148b0806 Mon Sep 17 00:00:00 2001 From: Daniel Wolf Date: Tue, 21 Sep 2021 20:42:33 +0200 Subject: [PATCH 4/4] Improve WAVE file reader to handle more formats Fixes #101 --- CHANGELOG.md | 1 + rhubarb/CMakeLists.txt | 23 ++++ rhubarb/src/audio/WaveFileReader.cpp | 86 ++++++++---- rhubarb/src/audio/WaveFileReader.h | 4 +- rhubarb/tests/WaveFileReaderTests.cpp | 186 ++++++++++++++++++++++++++ 5 files changed, 276 insertions(+), 24 deletions(-) create mode 100644 rhubarb/tests/WaveFileReaderTests.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index d13260d..ffd6281 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +* **Added** support for more WAVE file features ([issue #101](https://github.com/DanielSWolf/rhubarb-lip-sync/issues/101)) * **Changed** Rhubarb Lip Sync for Spine so that it works with any modern JRE ([issue #97](https://github.com/DanielSWolf/rhubarb-lip-sync/issues/97)) * **Changed** Windows build from 32 bit to 64 bit ([issue #98](https://github.com/DanielSWolf/rhubarb-lip-sync/issues/98)) diff --git a/rhubarb/CMakeLists.txt b/rhubarb/CMakeLists.txt index 0e20ed3..596b303 100644 --- a/rhubarb/CMakeLists.txt +++ b/rhubarb/CMakeLists.txt @@ -520,6 +520,7 @@ set(TEST_FILES tests/tokenizationTests.cpp tests/g2pTests.cpp tests/LazyTests.cpp + tests/WaveFileReaderTests.cpp ) add_executable(runTests ${TEST_FILES}) target_link_libraries(runTests @@ -528,6 +529,7 @@ target_link_libraries(runTests gmock_main rhubarb-recognition rhubarb-time + rhubarb-audio ) # Copies the specified files in a post-build event, then installs them @@ -555,9 +557,30 @@ function(copy_and_install sourceGlob relativeTargetDirectory) endforeach() endfunction() +# Copies the specified files in a post-build event +function(copy sourceGlob relativeTargetDirectory) + # Set `sourcePaths` + file(GLOB sourcePaths "${sourceGlob}") + + foreach(sourcePath ${sourcePaths}) + if(NOT IS_DIRECTORY ${sourcePath}) + # Set `fileName` + get_filename_component(fileName "${sourcePath}" NAME) + + # Copy file during build + add_custom_command(TARGET rhubarb POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy "${sourcePath}" "$/${relativeTargetDirectory}/${fileName}" + COMMENT "Creating '${relativeTargetDirectory}/${fileName}'" + ) + endif() + endforeach() +endfunction() + copy_and_install("lib/pocketsphinx-rev13216/model/en-us/*" "res/sphinx") copy_and_install("lib/cmusphinx-en-us-5.2/*" "res/sphinx/acoustic-model") +copy_and_install("tests/resources/*" "tests/resources") + install( TARGETS rhubarb RUNTIME diff --git a/rhubarb/src/audio/WaveFileReader.cpp b/rhubarb/src/audio/WaveFileReader.cpp index 72d4b16..3cb04c5 100644 --- a/rhubarb/src/audio/WaveFileReader.cpp +++ b/rhubarb/src/audio/WaveFileReader.cpp @@ -13,22 +13,27 @@ using std::unique_ptr; using std::make_unique; using std::make_shared; using std::filesystem::path; +using std::streamoff; #define INT24_MIN (-8388608) #define INT24_MAX 8388607 // Converts an int in the range min..max to a float in the range -1..1 float toNormalizedFloat(int value, int min, int max) { - return (static_cast(value - min) / (max - min) * 2) - 1; + const float fMin = static_cast(min); + const float fMax = static_cast(max); + const float fValue = static_cast(value); + return ((fValue - fMin) / (fMax - fMin) * 2) - 1; } -int roundToEven(int i) { +streamoff roundUpToEven(streamoff i) { return (i + 1) & (~1); } namespace Codec { constexpr int Pcm = 0x01; constexpr int Float = 0x03; + constexpr int Extensible = 0xFFFE; }; string codecToString(int codec); @@ -39,11 +44,11 @@ WaveFormatInfo getWaveFormatInfo(const path& filePath) { auto file = openFile(filePath); file.seekg(0, std::ios_base::end); - std::streamoff fileSize = file.tellg(); + const streamoff fileSize = file.tellg(); file.seekg(0); auto remaining = [&](int byteCount) { - const std::streamoff filePosition = file.tellg(); + const streamoff filePosition = file.tellg(); return byteCount <= fileSize - filePosition; }; @@ -51,34 +56,46 @@ WaveFormatInfo getWaveFormatInfo(const path& filePath) { if (!remaining(10)) { throw runtime_error("WAVE file is corrupt. Header not found."); } - auto rootChunkId = read(file); + const auto rootChunkId = read(file); if (rootChunkId != fourcc('R', 'I', 'F', 'F')) { throw runtime_error("Unknown file format. Only WAVE files are supported."); } read(file); // Chunk size - uint32_t waveId = read(file); + const uint32_t waveId = read(file); if (waveId != fourcc('W', 'A', 'V', 'E')) { throw runtime_error(format("File format is not WAVE, but {}.", fourccToString(waveId))); } // Read chunks until we reach the data chunk - bool reachedDataChunk = false; - while (!reachedDataChunk && remaining(8)) { - uint32_t chunkId = read(file); - int chunkSize = read(file); + bool processedFormatChunk = false; + bool processedDataChunk = false; + while ((!processedFormatChunk || !processedDataChunk) && remaining(8)) { + const uint32_t chunkId = read(file); + const streamoff chunkSize = read(file); + const streamoff chunkEnd = roundUpToEven(file.tellg() + chunkSize); switch (chunkId) { case fourcc('f', 'm', 't', ' '): { // Read relevant data uint16_t codec = read(file); formatInfo.channelCount = read(file); - formatInfo.frameRate = read(file); + formatInfo.frameRate = read(file); read(file); // Bytes per second - int frameSize = read(file); - int bitsPerSample = read(file); - - // We've read 16 bytes so far. Skip the remainder. - file.seekg(roundToEven(chunkSize) - 16, std::ios_base::cur); + const int bytesPerFrame = read(file); + const int bitsPerSampleOnDisk = read(file); + int bitsPerSample = bitsPerSampleOnDisk; + if (chunkSize > 16) { + const int extensionSize = read(file); + if (extensionSize >= 22) { + // Read extension fields + bitsPerSample = read(file); + read(file); // Skip channel mask + const uint16_t codecOverride = read(file); + if (codec == Codec::Extensible) { + codec = codecOverride; + } + } + } // Determine sample format int bytesPerSample; @@ -96,11 +113,14 @@ WaveFormatInfo getWaveFormatInfo(const path& filePath) { } else if (bitsPerSample <= 24) { formatInfo.sampleFormat = SampleFormat::Int24; bytesPerSample = 3; + } else if (bitsPerSample <= 32) { + formatInfo.sampleFormat = SampleFormat::Int32; + bytesPerSample = 4; } else { throw runtime_error( format("Unsupported sample format: {}-bit PCM.", bitsPerSample)); } - if (bytesPerSample != frameSize / formatInfo.channelCount) { + if (bytesPerSample != bytesPerFrame / formatInfo.channelCount) { throw runtime_error("Unsupported sample organization."); } break; @@ -108,6 +128,9 @@ WaveFormatInfo getWaveFormatInfo(const path& filePath) { if (bitsPerSample == 32) { formatInfo.sampleFormat = SampleFormat::Float32; bytesPerSample = 4; + } else if (bitsPerSample == 64) { + formatInfo.sampleFormat = SampleFormat::Float64; + bytesPerSample = 8; } else { throw runtime_error( format("Unsupported sample format: {}-bit IEEE Float.", bitsPerSample) @@ -121,24 +144,30 @@ WaveFormatInfo getWaveFormatInfo(const path& filePath) { )); } formatInfo.bytesPerFrame = bytesPerSample * formatInfo.channelCount; + processedFormatChunk = true; break; } case fourcc('d', 'a', 't', 'a'): { - reachedDataChunk = true; formatInfo.dataOffset = file.tellg(); formatInfo.frameCount = chunkSize / formatInfo.bytesPerFrame; + processedDataChunk = true; break; } default: { - // Skip unknown chunk - file.seekg(roundToEven(chunkSize), std::ios_base::cur); + // Ignore unknown chunk break; } } + + // Seek to end of chunk + file.seekg(chunkEnd, std::ios_base::beg); } + if (!processedFormatChunk) throw runtime_error("Missing format chunk."); + if (!processedDataChunk) throw runtime_error("Missing data chunk."); + return formatInfo; } @@ -177,11 +206,22 @@ inline AudioClip::value_type readSample( sum += toNormalizedFloat(raw, INT24_MIN, INT24_MAX); break; } + case SampleFormat::Int32: + { + const int32_t raw = read(file); + sum += toNormalizedFloat(raw, INT32_MIN, INT32_MAX); + break; + } case SampleFormat::Float32: { sum += read(file); break; } + case SampleFormat::Float64: + { + sum += static_cast(read(file)); + break; + } } } @@ -196,13 +236,13 @@ SampleReader WaveFileReader::createUnsafeSampleReader() const { filePos = std::streampos(0) ](size_type index) mutable { const std::streampos newFilePos = formatInfo.dataOffset - + static_cast(index * formatInfo.bytesPerFrame); + + static_cast(index * formatInfo.bytesPerFrame); if (newFilePos != filePos) { file->seekg(newFilePos); } const value_type result = readSample(*file, formatInfo.sampleFormat, formatInfo.channelCount); - filePos = newFilePos + static_cast(formatInfo.bytesPerFrame); + filePos = newFilePos + static_cast(formatInfo.bytesPerFrame); return result; }; } @@ -454,4 +494,4 @@ string codecToString(int codec) { default: return format("{0:#x}", codec); } -} \ No newline at end of file +} diff --git a/rhubarb/src/audio/WaveFileReader.h b/rhubarb/src/audio/WaveFileReader.h index c128b0f..0680844 100644 --- a/rhubarb/src/audio/WaveFileReader.h +++ b/rhubarb/src/audio/WaveFileReader.h @@ -7,7 +7,9 @@ enum class SampleFormat { UInt8, Int16, Int24, - Float32 + Int32, + Float32, + Float64 }; struct WaveFormatInfo { diff --git a/rhubarb/tests/WaveFileReaderTests.cpp b/rhubarb/tests/WaveFileReaderTests.cpp new file mode 100644 index 0000000..c73e236 --- /dev/null +++ b/rhubarb/tests/WaveFileReaderTests.cpp @@ -0,0 +1,186 @@ +#include +#include "audio/WaveFileReader.h" +#include "tools/platformTools.h" + +using namespace testing; + +TEST(getWaveFormatInfo, float32FromAudacity) { + auto formatInfo = getWaveFormatInfo(getBinDirectory() / "tests/resources/sine-triangle-float32-audacity.wav"); + EXPECT_EQ(formatInfo.frameRate, 48000); + EXPECT_EQ(formatInfo.frameCount, 480000); + EXPECT_EQ(formatInfo.channelCount, 2); + EXPECT_EQ(formatInfo.sampleFormat, SampleFormat::Float32); + EXPECT_EQ(formatInfo.bytesPerFrame, 2 * 4); + EXPECT_EQ(formatInfo.dataOffset, 88); +} + +TEST(getWaveFormatInfo, float32FromAudition) { + auto formatInfo = getWaveFormatInfo(getBinDirectory() / "tests/resources/sine-triangle-float32-audition.wav"); + EXPECT_EQ(formatInfo.frameRate, 48000); + EXPECT_EQ(formatInfo.frameCount, 480000); + EXPECT_EQ(formatInfo.channelCount, 2); + EXPECT_EQ(formatInfo.sampleFormat, SampleFormat::Float32); + EXPECT_EQ(formatInfo.bytesPerFrame, 2 * 4); + EXPECT_EQ(formatInfo.dataOffset, 92); +} + +TEST(getWaveFormatInfo, float32FromFfmpeg) { + auto formatInfo = getWaveFormatInfo(getBinDirectory() / "tests/resources/sine-triangle-float32-ffmpeg.wav"); + EXPECT_EQ(formatInfo.frameRate, 48000); + EXPECT_EQ(formatInfo.frameCount, 480000); + EXPECT_EQ(formatInfo.channelCount, 2); + EXPECT_EQ(formatInfo.sampleFormat, SampleFormat::Float32); + EXPECT_EQ(formatInfo.bytesPerFrame, 2 * 4); + EXPECT_EQ(formatInfo.dataOffset, 114); +} + +TEST(getWaveFormatInfo, float32FromSoundforge) { + auto formatInfo = getWaveFormatInfo(getBinDirectory() / "tests/resources/sine-triangle-float32-soundforge.wav"); + EXPECT_EQ(formatInfo.frameRate, 48000); + EXPECT_EQ(formatInfo.frameCount, 480000); + EXPECT_EQ(formatInfo.channelCount, 2); + EXPECT_EQ(formatInfo.sampleFormat, SampleFormat::Float32); + EXPECT_EQ(formatInfo.bytesPerFrame, 2 * 4); + EXPECT_EQ(formatInfo.dataOffset, 44); +} + +TEST(getWaveFormatInfo, float64FromFfmpeg) { + auto formatInfo = getWaveFormatInfo(getBinDirectory() / "tests/resources/sine-triangle-float64-ffmpeg.wav"); + EXPECT_EQ(formatInfo.frameRate, 48000); + EXPECT_EQ(formatInfo.frameCount, 480000); + EXPECT_EQ(formatInfo.channelCount, 2); + EXPECT_EQ(formatInfo.sampleFormat, SampleFormat::Float64); + EXPECT_EQ(formatInfo.bytesPerFrame, 2 * 8); + EXPECT_EQ(formatInfo.dataOffset, 114); +} + +TEST(getWaveFormatInfo, int16FromAudacity) { + auto formatInfo = getWaveFormatInfo(getBinDirectory() / "tests/resources/sine-triangle-int16-audacity.wav"); + EXPECT_EQ(formatInfo.frameRate, 48000); + EXPECT_EQ(formatInfo.frameCount, 480000); + EXPECT_EQ(formatInfo.channelCount, 2); + EXPECT_EQ(formatInfo.sampleFormat, SampleFormat::Int16); + EXPECT_EQ(formatInfo.bytesPerFrame, 2 * 2); + EXPECT_EQ(formatInfo.dataOffset, 44); +} + +TEST(getWaveFormatInfo, int16FromAudition) { + auto formatInfo = getWaveFormatInfo(getBinDirectory() / "tests/resources/sine-triangle-int16-audition.wav"); + EXPECT_EQ(formatInfo.frameRate, 48000); + EXPECT_EQ(formatInfo.frameCount, 480000); + EXPECT_EQ(formatInfo.channelCount, 2); + EXPECT_EQ(formatInfo.sampleFormat, SampleFormat::Int16); + EXPECT_EQ(formatInfo.bytesPerFrame, 2 * 2); + EXPECT_EQ(formatInfo.dataOffset, 92); +} + +TEST(getWaveFormatInfo, int16FromFfmpeg) { + auto formatInfo = getWaveFormatInfo(getBinDirectory() / "tests/resources/sine-triangle-int16-ffmpeg.wav"); + EXPECT_EQ(formatInfo.frameRate, 48000); + EXPECT_EQ(formatInfo.frameCount, 480000); + EXPECT_EQ(formatInfo.channelCount, 2); + EXPECT_EQ(formatInfo.sampleFormat, SampleFormat::Int16); + EXPECT_EQ(formatInfo.bytesPerFrame, 2 * 2); + EXPECT_EQ(formatInfo.dataOffset, 78); +} + +TEST(getWaveFormatInfo, int16FromSoundforge) { + auto formatInfo = getWaveFormatInfo(getBinDirectory() / "tests/resources/sine-triangle-int16-soundforge.wav"); + EXPECT_EQ(formatInfo.frameRate, 48000); + EXPECT_EQ(formatInfo.frameCount, 480000); + EXPECT_EQ(formatInfo.channelCount, 2); + EXPECT_EQ(formatInfo.sampleFormat, SampleFormat::Int16); + EXPECT_EQ(formatInfo.bytesPerFrame, 2 * 2); + EXPECT_EQ(formatInfo.dataOffset, 44); +} + +TEST(getWaveFormatInfo, int24FromAudacity) { + auto formatInfo = getWaveFormatInfo(getBinDirectory() / "tests/resources/sine-triangle-int24-audacity.wav"); + EXPECT_EQ(formatInfo.frameRate, 48000); + EXPECT_EQ(formatInfo.frameCount, 480000); + EXPECT_EQ(formatInfo.channelCount, 2); + EXPECT_EQ(formatInfo.sampleFormat, SampleFormat::Int24); + EXPECT_EQ(formatInfo.bytesPerFrame, 2 * 3); + EXPECT_EQ(formatInfo.dataOffset, 44); +} + +TEST(getWaveFormatInfo, int24FromAudition) { + auto formatInfo = getWaveFormatInfo(getBinDirectory() / "tests/resources/sine-triangle-int24-audition.wav"); + EXPECT_EQ(formatInfo.frameRate, 48000); + EXPECT_EQ(formatInfo.frameCount, 480000); + EXPECT_EQ(formatInfo.channelCount, 2); + EXPECT_EQ(formatInfo.sampleFormat, SampleFormat::Int24); + EXPECT_EQ(formatInfo.bytesPerFrame, 2 * 3); + EXPECT_EQ(formatInfo.dataOffset, 92); +} + +TEST(getWaveFormatInfo, int24FromFfmpeg) { + auto formatInfo = getWaveFormatInfo(getBinDirectory() / "tests/resources/sine-triangle-int24-ffmpeg.wav"); + EXPECT_EQ(formatInfo.frameRate, 48000); + EXPECT_EQ(formatInfo.frameCount, 480000); + EXPECT_EQ(formatInfo.channelCount, 2); + EXPECT_EQ(formatInfo.sampleFormat, SampleFormat::Int24); + EXPECT_EQ(formatInfo.bytesPerFrame, 2 * 3); + EXPECT_EQ(formatInfo.dataOffset, 102); +} + +TEST(getWaveFormatInfo, int24FromSoundforge) { + auto formatInfo = getWaveFormatInfo(getBinDirectory() / "tests/resources/sine-triangle-int24-soundforge.wav"); + EXPECT_EQ(formatInfo.frameRate, 48000); + EXPECT_EQ(formatInfo.frameCount, 480000); + EXPECT_EQ(formatInfo.channelCount, 2); + EXPECT_EQ(formatInfo.sampleFormat, SampleFormat::Int24); + EXPECT_EQ(formatInfo.bytesPerFrame, 2 * 3); + EXPECT_EQ(formatInfo.dataOffset, 44); +} + +TEST(getWaveFormatInfo, int32FromFfmpeg) { + auto formatInfo = getWaveFormatInfo(getBinDirectory() / "tests/resources/sine-triangle-int32-ffmpeg.wav"); + EXPECT_EQ(formatInfo.frameRate, 48000); + EXPECT_EQ(formatInfo.frameCount, 480000); + EXPECT_EQ(formatInfo.channelCount, 2); + EXPECT_EQ(formatInfo.sampleFormat, SampleFormat::Int32); + EXPECT_EQ(formatInfo.bytesPerFrame, 2 * 4); + EXPECT_EQ(formatInfo.dataOffset, 102); +} + +TEST(getWaveFormatInfo, int32FromSoundforge) { + auto formatInfo = getWaveFormatInfo(getBinDirectory() / "tests/resources/sine-triangle-int32-soundforge.wav"); + EXPECT_EQ(formatInfo.frameRate, 48000); + EXPECT_EQ(formatInfo.frameCount, 480000); + EXPECT_EQ(formatInfo.channelCount, 2); + EXPECT_EQ(formatInfo.sampleFormat, SampleFormat::Int32); + EXPECT_EQ(formatInfo.bytesPerFrame, 2 * 4); + EXPECT_EQ(formatInfo.dataOffset, 44); +} + +TEST(getWaveFormatInfo, uint8FromAudition) { + auto formatInfo = getWaveFormatInfo(getBinDirectory() / "tests/resources/sine-triangle-uint8-audition.wav"); + EXPECT_EQ(formatInfo.frameRate, 48000); + EXPECT_EQ(formatInfo.frameCount, 480000); + EXPECT_EQ(formatInfo.channelCount, 2); + EXPECT_EQ(formatInfo.sampleFormat, SampleFormat::UInt8); + EXPECT_EQ(formatInfo.bytesPerFrame, 2 * 1); + EXPECT_EQ(formatInfo.dataOffset, 92); +} + +TEST(getWaveFormatInfo, uint8FromFfmpeg) { + auto formatInfo = getWaveFormatInfo(getBinDirectory() / "tests/resources/sine-triangle-uint8-ffmpeg.wav"); + EXPECT_EQ(formatInfo.frameRate, 48000); + EXPECT_EQ(formatInfo.frameCount, 480000); + EXPECT_EQ(formatInfo.channelCount, 2); + EXPECT_EQ(formatInfo.sampleFormat, SampleFormat::UInt8); + EXPECT_EQ(formatInfo.bytesPerFrame, 2 * 1); + EXPECT_EQ(formatInfo.dataOffset, 78); +} + +TEST(getWaveFormatInfo, uint8FromSoundforge) { + auto formatInfo = getWaveFormatInfo(getBinDirectory() / "tests/resources/sine-triangle-uint8-soundforge.wav"); + EXPECT_EQ(formatInfo.frameRate, 48000); + EXPECT_EQ(formatInfo.frameCount, 480000); + EXPECT_EQ(formatInfo.channelCount, 2); + EXPECT_EQ(formatInfo.sampleFormat, SampleFormat::UInt8); + EXPECT_EQ(formatInfo.bytesPerFrame, 2 * 1); + EXPECT_EQ(formatInfo.dataOffset, 44); +} +