Skip to content

Commit 6cc5876

Browse files
erysdrencraftablescience
authored andcommitted
sndpp: VDAT chunk support
1 parent 0a412c1 commit 6cc5876

2 files changed

Lines changed: 160 additions & 0 deletions

File tree

include/sndpp/WAV.h

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#pragma once
22

33
#include <array>
4+
#include <cctype>
45
#include <optional>
56

67
#include <BufferStream.h>
@@ -26,6 +27,7 @@ class WAV : public RIFF {
2627
CHUNK_PAD = sourcepp::parser::binary::makeFourCC("PAD "), // Useless
2728
CHUNK_JUNK = sourcepp::parser::binary::makeFourCC("JUNK"), // Useless
2829
CHUNK_FLLR = sourcepp::parser::binary::makeFourCC("FLLR"), // Useless
30+
CHUNK_VDAT = sourcepp::parser::binary::makeFourCC("VDAT"), // Valve lipsync data
2931

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

149+
struct ChunkVDAT {
150+
struct Emphasis {
151+
float time;
152+
float value;
153+
};
154+
155+
struct Phoneme {
156+
float startTime{};
157+
float endTime{};
158+
float volume{};
159+
uint32_t code{};
160+
std::string name;
161+
};
162+
163+
struct Word {
164+
float startTime{};
165+
float endTime{};
166+
std::string word;
167+
std::vector<Phoneme> phonemes;
168+
};
169+
170+
float version;
171+
std::string plaintext;
172+
std::vector<Word> words;
173+
std::vector<Emphasis> emphasis;
174+
std::unordered_map<std::string, std::string> options;
175+
};
176+
147177
//struct ChunkLISTINFO {
148178
// struct META {
149179
// ChunkType infoType;
@@ -264,6 +294,92 @@ class WAV : public RIFF {
264294
stream >> chunk->md5;
265295
}
266296
return chunk;
297+
} else if constexpr (Type == CHUNK_VDAT) {
298+
std::optional<ChunkVDAT> chunk = std::nullopt;
299+
if (stream.size() > 0) {
300+
chunk = ChunkVDAT{};
301+
const auto getToken = [&stream] {
302+
std::string token;
303+
if (stream.tell() >= stream.size()) {
304+
return token;
305+
}
306+
bool gotToken = false;
307+
char c = stream.read<char>();
308+
while (true) {
309+
if (!gotToken && std::isspace(c)) {
310+
// skip leading whitespace
311+
} else if (!gotToken && !std::isspace(c)) {
312+
gotToken = true;
313+
token += c;
314+
} else if (gotToken && !std::isspace(c)) {
315+
token += c;
316+
} else if (gotToken && std::isspace(c)) {
317+
break;
318+
}
319+
c = stream.read<char>();
320+
}
321+
return token;
322+
};
323+
std::string token = getToken();
324+
while (!token.empty()) {
325+
if (token == "VERSION") {
326+
token = getToken();
327+
chunk->version = std::stof(token);
328+
} else if (token == "PLAINTEXT") {
329+
token = getToken(); // opening brace
330+
token = getToken(); // first token
331+
while (true) {
332+
chunk->plaintext += token;
333+
token = getToken();
334+
if (token.empty() || token == "}")
335+
break;
336+
chunk->plaintext += ' ';
337+
}
338+
} else if (token == "WORDS") {
339+
token = getToken(); // opening brace
340+
token = getToken(); // first token
341+
while (token == "WORD") {
342+
ChunkVDAT::Word word;
343+
word.word = getToken();
344+
word.startTime = std::stof(getToken());
345+
word.endTime = std::stof(getToken());
346+
token = getToken(); // opening brace
347+
token = getToken(); // first token
348+
while (token != "}") {
349+
ChunkVDAT::Phoneme phoneme;
350+
phoneme.code = std::stoi(token);
351+
phoneme.name = getToken();
352+
phoneme.startTime = std::stof(getToken());
353+
phoneme.endTime = std::stof(getToken());
354+
phoneme.volume = std::stof(getToken());
355+
word.phonemes.push_back(phoneme);
356+
token = getToken();
357+
}
358+
chunk->words.push_back(word);
359+
token = getToken();
360+
}
361+
} else if (token == "EMPHASIS") {
362+
token = getToken(); // opening brace
363+
token = getToken(); // first token
364+
while (token != "}") {
365+
ChunkVDAT::Emphasis emphasis{};
366+
emphasis.time = std::stof(token);
367+
emphasis.value = std::stof(getToken());
368+
chunk->emphasis.push_back(emphasis);
369+
token = getToken();
370+
}
371+
} else if (token == "OPTIONS") {
372+
token = getToken(); // opening brace
373+
token = getToken(); // first token
374+
while (token != "}") {
375+
chunk->options[token] = getToken();
376+
token = getToken();
377+
}
378+
}
379+
token = getToken();
380+
}
381+
}
382+
return chunk;
267383
} else {
268384
return this->hasNthChunk(Type, n) ? std::optional{std::move(chunkData)} : std::nullopt;
269385
}

test/sndpp.cpp

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,50 @@ TEST(sndpp, read_wav) {
2525
EXPECT_EQ(data->size(), 34061);
2626
}
2727

28+
TEST(sndpp, read_wav_vdat) {
29+
WAV wav{ASSET_ROOT "sndpp/gman_riseshine.wav"};
30+
ASSERT_TRUE(wav);
31+
EXPECT_EQ(wav.getSignature(), RIFF_SIGNATURE);
32+
EXPECT_EQ(wav.getChunks().size(), 5);
33+
34+
const auto fmt = wav.getFirstWAVChunk<WAV::CHUNK_FMT>();
35+
ASSERT_TRUE(fmt);
36+
EXPECT_EQ(fmt->format, 2);
37+
EXPECT_EQ(fmt->channels, 1);
38+
EXPECT_EQ(fmt->samplesPerSecond, 22050);
39+
EXPECT_EQ(fmt->averageBytesPerSecond, 11155);
40+
EXPECT_EQ(fmt->blockAlign, 512);
41+
EXPECT_EQ(fmt->bitsPerSample, 4);
42+
EXPECT_EQ(fmt->extraCompressionInfo.size(), 34);
43+
44+
const auto data = wav.getFirstWAVChunk<WAV::CHUNK_DATA>();
45+
ASSERT_TRUE(data);
46+
EXPECT_EQ(data->size(), 74240);
47+
48+
const auto vdat = wav.getFirstWAVChunk<WAV::CHUNK_VDAT>();
49+
ASSERT_TRUE(vdat);
50+
EXPECT_EQ(vdat->version, 1.0);
51+
EXPECT_STREQ(vdat->plaintext.c_str(), "Rise and shine, Mr. Freeman. Rise and shine.");
52+
EXPECT_STREQ(vdat->words[0].word.c_str(), "Rise");
53+
EXPECT_EQ(vdat->words[0].startTime, 0.0);
54+
EXPECT_EQ(vdat->words[0].phonemes[0].code, 633);
55+
EXPECT_STREQ(vdat->words[0].phonemes[0].name.c_str(), "r");
56+
EXPECT_EQ(vdat->words[0].phonemes[0].volume, 1.0);
57+
EXPECT_STREQ(vdat->words[1].word.c_str(), "and");
58+
EXPECT_STREQ(vdat->words[2].word.c_str(), "shine,");
59+
EXPECT_STREQ(vdat->words[3].word.c_str(), "Mr.");
60+
EXPECT_STREQ(vdat->words[4].word.c_str(), "Freeman.");
61+
EXPECT_STREQ(vdat->words[5].word.c_str(), "shine");
62+
EXPECT_STREQ(vdat->words[6].word.c_str(), "Mr");
63+
EXPECT_STREQ(vdat->words[7].word.c_str(), "Freeman");
64+
EXPECT_STREQ(vdat->words[8].word.c_str(), "Rise");
65+
EXPECT_STREQ(vdat->words[9].word.c_str(), "and");
66+
EXPECT_STREQ(vdat->words[10].word.c_str(), "shine.");
67+
EXPECT_STREQ(vdat->words[11].word.c_str(), "shine");
68+
EXPECT_EQ(vdat->emphasis.size(), 0);
69+
EXPECT_STREQ(vdat->options.at("voice_duck").c_str(), "0");
70+
}
71+
2872
TEST(sndpp, read_xwv_v0) {
2973
XWV xwv{ASSET_ROOT "sndpp/biohazard_detected.xbox.wav"};
3074
ASSERT_TRUE(xwv);

0 commit comments

Comments
 (0)