Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 116 additions & 0 deletions include/sndpp/WAV.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include <array>
#include <optional>
#include <cctype>

#include <BufferStream.h>

Expand All @@ -26,6 +27,7 @@ class WAV : public RIFF {
CHUNK_PAD = sourcepp::parser::binary::makeFourCC("PAD "), // Useless
CHUNK_JUNK = sourcepp::parser::binary::makeFourCC("JUNK"), // Useless
CHUNK_FLLR = sourcepp::parser::binary::makeFourCC("FLLR"), // Useless
CHUNK_VDAT = sourcepp::parser::binary::makeFourCC("VDAT"), // Valve lipsync data

//CHUNK_LIST = sourcepp::parser::binary::makeFourCC("LIST"), // Metadata
//CHUNK_LIST_WAVL = sourcepp::parser::binary::makeFourCC("WAVL"), // Wave list
Expand Down Expand Up @@ -144,6 +146,34 @@ class WAV : public RIFF {
std::array<uint8_t, 16> md5;
};

struct ChunkVDAT {
struct Emphasis {
float time;
float value;
};

struct Phoneme {
float startTime;
float endTime;
float volume;
uint32_t code;
std::string name;
};

struct Word {
float startTime;
float endTime;
std::string word;
std::vector<Phoneme> phonemes;
};

float version;
std::string plaintext;
std::vector<Word> words;
std::vector<Emphasis> emphasis;
std::unordered_map<std::string, std::string> options;
};

//struct ChunkLISTINFO {
// struct META {
// ChunkType infoType;
Expand Down Expand Up @@ -264,6 +294,92 @@ class WAV : public RIFF {
stream >> chunk->md5;
}
return chunk;
} else if constexpr (Type == CHUNK_VDAT) {
std::optional<ChunkVDAT> chunk = std::nullopt;
if (stream.size() > 0) {
chunk = ChunkVDAT{};
auto getToken = [&stream]() {
std::string token;
if (stream.tell() >= stream.size()) {
return token;
}
bool gotToken = false;
char c = stream.read<char>();
while (true) {
if (!gotToken && std::isspace(c)) {
// skip leading whitespace
} else if (!gotToken && !std::isspace(c)) {
gotToken = true;
token += c;
} else if (gotToken && !std::isspace(c)) {
token += c;
} else if (gotToken && std::isspace(c)) {
break;
}
c = stream.read<char>();
}
return token;
};
std::string token = getToken();
while (token.length() > 0) {
if (token == "VERSION") {
token = getToken();
chunk->version = std::stof(token);
} else if (token == "PLAINTEXT") {
token = getToken(); // opening brace
token = getToken(); // first token
while (true) {
chunk->plaintext += token;
token = getToken();
if (!token.length() || token == "}")
break;
chunk->plaintext += ' ';
}
} else if (token == "WORDS") {
token = getToken(); // opening brace
token = getToken(); // first token
while (token == "WORD") {
ChunkVDAT::Word word;
word.word = getToken();
word.startTime = std::stof(getToken());
word.endTime = std::stof(getToken());
token = getToken(); // opening brace
token = getToken(); // first token
while (token != "}") {
ChunkVDAT::Phoneme phoneme;
phoneme.code = std::stoi(token);
phoneme.name = getToken();
phoneme.startTime = std::stof(getToken());
phoneme.endTime = std::stof(getToken());
phoneme.volume = std::stof(getToken());
word.phonemes.push_back(phoneme);
token = getToken();
}
chunk->words.push_back(word);
token = getToken();
}
} else if (token == "EMPHASIS") {
token = getToken(); // opening brace
token = getToken(); // first token
while (token != "}") {
ChunkVDAT::Emphasis emphasis;
emphasis.time = std::stof(token);
emphasis.value = std::stof(getToken());
chunk->emphasis.push_back(emphasis);
token = getToken();
}
} else if (token == "OPTIONS") {
token = getToken(); // opening brace
token = getToken(); // first token
while (token != "}") {
chunk->options[token] = getToken();
token = getToken();
}
}
token = getToken();
}
}
return chunk;
} else {
return this->hasNthChunk(Type, n) ? std::optional{std::move(chunkData)} : std::nullopt;
}
Expand Down
44 changes: 44 additions & 0 deletions test/sndpp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,50 @@ TEST(sndpp, read_wav) {
EXPECT_EQ(data->size(), 34061);
}

TEST(sndpp, read_wav_vdat) {
WAV wav{ASSET_ROOT "sndpp/gman_riseshine.wav"};
ASSERT_TRUE(wav);
EXPECT_EQ(wav.getSignature(), RIFF_SIGNATURE);
EXPECT_EQ(wav.getChunks().size(), 5);

const auto fmt = wav.getFirstWAVChunk<WAV::CHUNK_FMT>();
ASSERT_TRUE(fmt);
EXPECT_EQ(fmt->format, 2);
EXPECT_EQ(fmt->channels, 1);
EXPECT_EQ(fmt->samplesPerSecond, 22050);
EXPECT_EQ(fmt->averageBytesPerSecond, 11155);
EXPECT_EQ(fmt->blockAlign, 512);
EXPECT_EQ(fmt->bitsPerSample, 4);
EXPECT_EQ(fmt->extraCompressionInfo.size(), 34);

const auto data = wav.getFirstWAVChunk<WAV::CHUNK_DATA>();
ASSERT_TRUE(data);
EXPECT_EQ(data->size(), 74240);

const auto vdat = wav.getFirstWAVChunk<WAV::CHUNK_VDAT>();
ASSERT_TRUE(vdat);
EXPECT_EQ(vdat->version, 1.0);
EXPECT_STREQ(vdat->plaintext.c_str(), "Rise and shine, Mr. Freeman. Rise and shine.");
EXPECT_STREQ(vdat->words[0].word.c_str(), "Rise");
EXPECT_EQ(vdat->words[0].startTime, 0.0);
EXPECT_EQ(vdat->words[0].phonemes[0].code, 633);
EXPECT_STREQ(vdat->words[0].phonemes[0].name.c_str(), "r");
EXPECT_EQ(vdat->words[0].phonemes[0].volume, 1.0);
EXPECT_STREQ(vdat->words[1].word.c_str(), "and");
EXPECT_STREQ(vdat->words[2].word.c_str(), "shine,");
EXPECT_STREQ(vdat->words[3].word.c_str(), "Mr.");
EXPECT_STREQ(vdat->words[4].word.c_str(), "Freeman.");
EXPECT_STREQ(vdat->words[5].word.c_str(), "shine");
EXPECT_STREQ(vdat->words[6].word.c_str(), "Mr");
EXPECT_STREQ(vdat->words[7].word.c_str(), "Freeman");
EXPECT_STREQ(vdat->words[8].word.c_str(), "Rise");
EXPECT_STREQ(vdat->words[9].word.c_str(), "and");
EXPECT_STREQ(vdat->words[10].word.c_str(), "shine.");
EXPECT_STREQ(vdat->words[11].word.c_str(), "shine");
EXPECT_EQ(vdat->emphasis.size(), 0);
EXPECT_STREQ(vdat->options.at("voice_duck").c_str(), "0");
}

TEST(sndpp, read_xwv_v0) {
XWV xwv{ASSET_ROOT "sndpp/biohazard_detected.xbox.wav"};
ASSERT_TRUE(xwv);
Expand Down
Loading