Merge branch 'feature/101-wave-file-reader-improvements'

This commit is contained in:
Daniel Wolf 2021-10-06 21:02:50 +02:00
commit c4317245b1
28 changed files with 367 additions and 98 deletions

66
.gitattributes vendored
View File

@ -1,63 +1,7 @@
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto * text=auto
############################################################################### # Use Git LFS for binary files
# Set default behavior for command prompt diff. *.wav filter=lfs diff=lfs merge=lfs -text
# *.flac filter=lfs diff=lfs merge=lfs -text
# This is need for earlier builds of msysgit that does not have it on by *.ogg filter=lfs diff=lfs merge=lfs -text
# default for csharp files. *.mp3 filter=lfs diff=lfs merge=lfs -text
# 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

View File

@ -31,6 +31,8 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v2 uses: actions/checkout@v2
with:
lfs: true
- name: Restore Boost from cache - name: Restore Boost from cache
uses: actions/cache@v2 uses: actions/cache@v2
id: cache-boost id: cache-boost

View File

@ -2,6 +2,7 @@
## Unreleased ## 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** 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)) * **Changed** Windows build from 32 bit to 64 bit ([issue #98](https://github.com/DanielSWolf/rhubarb-lip-sync/issues/98))

View File

@ -520,6 +520,7 @@ set(TEST_FILES
tests/tokenizationTests.cpp tests/tokenizationTests.cpp
tests/g2pTests.cpp tests/g2pTests.cpp
tests/LazyTests.cpp tests/LazyTests.cpp
tests/WaveFileReaderTests.cpp
) )
add_executable(runTests ${TEST_FILES}) add_executable(runTests ${TEST_FILES})
target_link_libraries(runTests target_link_libraries(runTests
@ -528,6 +529,7 @@ target_link_libraries(runTests
gmock_main gmock_main
rhubarb-recognition rhubarb-recognition
rhubarb-time rhubarb-time
rhubarb-audio
) )
# Copies the specified files in a post-build event, then installs them # Copies the specified files in a post-build event, then installs them
@ -555,9 +557,30 @@ function(copy_and_install sourceGlob relativeTargetDirectory)
endforeach() endforeach()
endfunction() 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}" "$<TARGET_FILE_DIR:rhubarb>/${relativeTargetDirectory}/${fileName}"
COMMENT "Creating '${relativeTargetDirectory}/${fileName}'"
)
endif()
endforeach()
endfunction()
copy_and_install("lib/pocketsphinx-rev13216/model/en-us/*" "res/sphinx") 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("lib/cmusphinx-en-us-5.2/*" "res/sphinx/acoustic-model")
copy_and_install("tests/resources/*" "tests/resources")
install( install(
TARGETS rhubarb TARGETS rhubarb
RUNTIME RUNTIME

View File

@ -13,38 +13,42 @@ using std::unique_ptr;
using std::make_unique; using std::make_unique;
using std::make_shared; using std::make_shared;
using std::filesystem::path; using std::filesystem::path;
using std::streamoff;
#define INT24_MIN (-8388608) #define INT24_MIN (-8388608)
#define INT24_MAX 8388607 #define INT24_MAX 8388607
// Converts an int in the range min..max to a float in the range -1..1 // Converts an int in the range min..max to a float in the range -1..1
float toNormalizedFloat(int value, int min, int max) { float toNormalizedFloat(int value, int min, int max) {
return (static_cast<float>(value - min) / (max - min) * 2) - 1; const float fMin = static_cast<float>(min);
const float fMax = static_cast<float>(max);
const float fValue = static_cast<float>(value);
return ((fValue - fMin) / (fMax - fMin) * 2) - 1;
} }
int roundToEven(int i) { streamoff roundUpToEven(streamoff i) {
return (i + 1) & (~1); return (i + 1) & (~1);
} }
namespace Codec { namespace Codec {
constexpr int Pcm = 0x01; constexpr int Pcm = 0x01;
constexpr int Float = 0x03; constexpr int Float = 0x03;
constexpr int Extensible = 0xFFFE;
}; };
string codecToString(int codec); string codecToString(int codec);
WaveFileReader::WaveFileReader(const path& filePath) : WaveFormatInfo getWaveFormatInfo(const path& filePath) {
filePath(filePath), WaveFormatInfo formatInfo {};
formatInfo {}
{
auto file = openFile(filePath); auto file = openFile(filePath);
file.seekg(0, std::ios_base::end); file.seekg(0, std::ios_base::end);
std::streamoff fileSize = file.tellg(); const streamoff fileSize = file.tellg();
file.seekg(0); file.seekg(0);
auto remaining = [&](int byteCount) { auto remaining = [&](int byteCount) {
const std::streamoff filePosition = file.tellg(); const streamoff filePosition = file.tellg();
return byteCount <= fileSize - filePosition; return byteCount <= fileSize - filePosition;
}; };
@ -52,34 +56,46 @@ WaveFileReader::WaveFileReader(const path& filePath) :
if (!remaining(10)) { if (!remaining(10)) {
throw runtime_error("WAVE file is corrupt. Header not found."); throw runtime_error("WAVE file is corrupt. Header not found.");
} }
auto rootChunkId = read<uint32_t>(file); const auto rootChunkId = read<uint32_t>(file);
if (rootChunkId != fourcc('R', 'I', 'F', 'F')) { if (rootChunkId != fourcc('R', 'I', 'F', 'F')) {
throw runtime_error("Unknown file format. Only WAVE files are supported."); throw runtime_error("Unknown file format. Only WAVE files are supported.");
} }
read<uint32_t>(file); // Chunk size read<uint32_t>(file); // Chunk size
uint32_t waveId = read<uint32_t>(file); const uint32_t waveId = read<uint32_t>(file);
if (waveId != fourcc('W', 'A', 'V', 'E')) { if (waveId != fourcc('W', 'A', 'V', 'E')) {
throw runtime_error(format("File format is not WAVE, but {}.", fourccToString(waveId))); throw runtime_error(format("File format is not WAVE, but {}.", fourccToString(waveId)));
} }
// Read chunks until we reach the data chunk // Read chunks until we reach the data chunk
bool reachedDataChunk = false; bool processedFormatChunk = false;
while (!reachedDataChunk && remaining(8)) { bool processedDataChunk = false;
uint32_t chunkId = read<uint32_t>(file); while ((!processedFormatChunk || !processedDataChunk) && remaining(8)) {
int chunkSize = read<uint32_t>(file); const uint32_t chunkId = read<uint32_t>(file);
const streamoff chunkSize = read<int32_t>(file);
const streamoff chunkEnd = roundUpToEven(file.tellg() + chunkSize);
switch (chunkId) { switch (chunkId) {
case fourcc('f', 'm', 't', ' '): case fourcc('f', 'm', 't', ' '):
{ {
// Read relevant data // Read relevant data
uint16_t codec = read<uint16_t>(file); uint16_t codec = read<uint16_t>(file);
formatInfo.channelCount = read<uint16_t>(file); formatInfo.channelCount = read<uint16_t>(file);
formatInfo.frameRate = read<uint32_t>(file); formatInfo.frameRate = read<int32_t>(file);
read<uint32_t>(file); // Bytes per second read<uint32_t>(file); // Bytes per second
int frameSize = read<uint16_t>(file); const int bytesPerFrame = read<uint16_t>(file);
int bitsPerSample = read<uint16_t>(file); const int bitsPerSampleOnDisk = read<uint16_t>(file);
int bitsPerSample = bitsPerSampleOnDisk;
// We've read 16 bytes so far. Skip the remainder. if (chunkSize > 16) {
file.seekg(roundToEven(chunkSize) - 16, std::ios_base::cur); const int extensionSize = read<uint16_t>(file);
if (extensionSize >= 22) {
// Read extension fields
bitsPerSample = read<uint16_t>(file);
read<uint32_t>(file); // Skip channel mask
const uint16_t codecOverride = read<uint16_t>(file);
if (codec == Codec::Extensible) {
codec = codecOverride;
}
}
}
// Determine sample format // Determine sample format
int bytesPerSample; int bytesPerSample;
@ -97,11 +113,14 @@ WaveFileReader::WaveFileReader(const path& filePath) :
} else if (bitsPerSample <= 24) { } else if (bitsPerSample <= 24) {
formatInfo.sampleFormat = SampleFormat::Int24; formatInfo.sampleFormat = SampleFormat::Int24;
bytesPerSample = 3; bytesPerSample = 3;
} else if (bitsPerSample <= 32) {
formatInfo.sampleFormat = SampleFormat::Int32;
bytesPerSample = 4;
} else { } else {
throw runtime_error( throw runtime_error(
format("Unsupported sample format: {}-bit PCM.", bitsPerSample)); format("Unsupported sample format: {}-bit PCM.", bitsPerSample));
} }
if (bytesPerSample != frameSize / formatInfo.channelCount) { if (bytesPerSample != bytesPerFrame / formatInfo.channelCount) {
throw runtime_error("Unsupported sample organization."); throw runtime_error("Unsupported sample organization.");
} }
break; break;
@ -109,6 +128,9 @@ WaveFileReader::WaveFileReader(const path& filePath) :
if (bitsPerSample == 32) { if (bitsPerSample == 32) {
formatInfo.sampleFormat = SampleFormat::Float32; formatInfo.sampleFormat = SampleFormat::Float32;
bytesPerSample = 4; bytesPerSample = 4;
} else if (bitsPerSample == 64) {
formatInfo.sampleFormat = SampleFormat::Float64;
bytesPerSample = 8;
} else { } else {
throw runtime_error( throw runtime_error(
format("Unsupported sample format: {}-bit IEEE Float.", bitsPerSample) format("Unsupported sample format: {}-bit IEEE Float.", bitsPerSample)
@ -122,25 +144,37 @@ WaveFileReader::WaveFileReader(const path& filePath) :
)); ));
} }
formatInfo.bytesPerFrame = bytesPerSample * formatInfo.channelCount; formatInfo.bytesPerFrame = bytesPerSample * formatInfo.channelCount;
processedFormatChunk = true;
break; break;
} }
case fourcc('d', 'a', 't', 'a'): case fourcc('d', 'a', 't', 'a'):
{ {
reachedDataChunk = true;
formatInfo.dataOffset = file.tellg(); formatInfo.dataOffset = file.tellg();
formatInfo.frameCount = chunkSize / formatInfo.bytesPerFrame; formatInfo.frameCount = chunkSize / formatInfo.bytesPerFrame;
processedDataChunk = true;
break; break;
} }
default: default:
{ {
// Skip unknown chunk // Ignore unknown chunk
file.seekg(roundToEven(chunkSize), std::ios_base::cur);
break; 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;
} }
WaveFileReader::WaveFileReader(const path& filePath) :
filePath(filePath),
formatInfo(getWaveFormatInfo(filePath)) {}
unique_ptr<AudioClip> WaveFileReader::clone() const { unique_ptr<AudioClip> WaveFileReader::clone() const {
return make_unique<WaveFileReader>(*this); return make_unique<WaveFileReader>(*this);
} }
@ -172,11 +206,22 @@ inline AudioClip::value_type readSample(
sum += toNormalizedFloat(raw, INT24_MIN, INT24_MAX); sum += toNormalizedFloat(raw, INT24_MIN, INT24_MAX);
break; break;
} }
case SampleFormat::Int32:
{
const int32_t raw = read<int32_t>(file);
sum += toNormalizedFloat(raw, INT32_MIN, INT32_MAX);
break;
}
case SampleFormat::Float32: case SampleFormat::Float32:
{ {
sum += read<float>(file); sum += read<float>(file);
break; break;
} }
case SampleFormat::Float64:
{
sum += static_cast<float>(read<double>(file));
break;
}
} }
} }
@ -191,13 +236,13 @@ SampleReader WaveFileReader::createUnsafeSampleReader() const {
filePos = std::streampos(0) filePos = std::streampos(0)
](size_type index) mutable { ](size_type index) mutable {
const std::streampos newFilePos = formatInfo.dataOffset const std::streampos newFilePos = formatInfo.dataOffset
+ static_cast<std::streamoff>(index * formatInfo.bytesPerFrame); + static_cast<streamoff>(index * formatInfo.bytesPerFrame);
if (newFilePos != filePos) { if (newFilePos != filePos) {
file->seekg(newFilePos); file->seekg(newFilePos);
} }
const value_type result = const value_type result =
readSample(*file, formatInfo.sampleFormat, formatInfo.channelCount); readSample(*file, formatInfo.sampleFormat, formatInfo.channelCount);
filePos = newFilePos + static_cast<std::streamoff>(formatInfo.bytesPerFrame); filePos = newFilePos + static_cast<streamoff>(formatInfo.bytesPerFrame);
return result; return result;
}; };
} }

View File

@ -7,9 +7,22 @@ enum class SampleFormat {
UInt8, UInt8,
Int16, Int16,
Int24, Int24,
Float32 Int32,
Float32,
Float64
}; };
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 { class WaveFileReader : public AudioClip {
public: public:
WaveFileReader(const std::filesystem::path& filePath); WaveFileReader(const std::filesystem::path& filePath);
@ -20,15 +33,6 @@ public:
private: private:
SampleReader createUnsafeSampleReader() const override; SampleReader createUnsafeSampleReader() const override;
struct WaveFormatInfo {
int bytesPerFrame;
SampleFormat sampleFormat;
int frameRate;
int64_t frameCount;
int channelCount;
std::streampos dataOffset;
};
std::filesystem::path filePath; std::filesystem::path filePath;
WaveFormatInfo formatInfo; WaveFormatInfo formatInfo;
}; };

View File

@ -0,0 +1,186 @@
#include <gmock/gmock.h>
#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);
}

View File

@ -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.

BIN
rhubarb/tests/resources/sine-triangle-flac-ffmpeg.wav (Stored with Git LFS) Normal file

Binary file not shown.

BIN
rhubarb/tests/resources/sine-triangle-float32-audacity.wav (Stored with Git LFS) Normal file

Binary file not shown.

BIN
rhubarb/tests/resources/sine-triangle-float32-audition.wav (Stored with Git LFS) Normal file

Binary file not shown.

BIN
rhubarb/tests/resources/sine-triangle-float32-ffmpeg.wav (Stored with Git LFS) Normal file

Binary file not shown.

BIN
rhubarb/tests/resources/sine-triangle-float32-soundforge.wav (Stored with Git LFS) Normal file

Binary file not shown.

BIN
rhubarb/tests/resources/sine-triangle-float64-ffmpeg.wav (Stored with Git LFS) Normal file

Binary file not shown.

BIN
rhubarb/tests/resources/sine-triangle-int16-audacity.wav (Stored with Git LFS) Normal file

Binary file not shown.

BIN
rhubarb/tests/resources/sine-triangle-int16-audition.wav (Stored with Git LFS) Normal file

Binary file not shown.

BIN
rhubarb/tests/resources/sine-triangle-int16-ffmpeg.wav (Stored with Git LFS) Normal file

Binary file not shown.

BIN
rhubarb/tests/resources/sine-triangle-int16-soundforge.wav (Stored with Git LFS) Normal file

Binary file not shown.

BIN
rhubarb/tests/resources/sine-triangle-int24-audacity.wav (Stored with Git LFS) Normal file

Binary file not shown.

BIN
rhubarb/tests/resources/sine-triangle-int24-audition.wav (Stored with Git LFS) Normal file

Binary file not shown.

BIN
rhubarb/tests/resources/sine-triangle-int24-ffmpeg.wav (Stored with Git LFS) Normal file

Binary file not shown.

BIN
rhubarb/tests/resources/sine-triangle-int24-soundforge.wav (Stored with Git LFS) Normal file

Binary file not shown.

BIN
rhubarb/tests/resources/sine-triangle-int32-ffmpeg.wav (Stored with Git LFS) Normal file

Binary file not shown.

BIN
rhubarb/tests/resources/sine-triangle-int32-soundforge.wav (Stored with Git LFS) Normal file

Binary file not shown.

BIN
rhubarb/tests/resources/sine-triangle-uint8-audition.wav (Stored with Git LFS) Normal file

Binary file not shown.

BIN
rhubarb/tests/resources/sine-triangle-uint8-ffmpeg.wav (Stored with Git LFS) Normal file

Binary file not shown.

BIN
rhubarb/tests/resources/sine-triangle-uint8-soundforge.wav (Stored with Git LFS) Normal file

Binary file not shown.

BIN
rhubarb/tests/resources/sine-triangle-vorbis-ffmpeg.wav (Stored with Git LFS) Normal file

Binary file not shown.