voice_audio_multi/tests/unittests/test_inflect_v2_frontend.cpp
Fabio c36510c21e
Some checks failed
Linux Build / Linux cpu (push) Has been cancelled
Linux Build / Linux cpu-clang (push) Has been cancelled
Linux Build / Linux vulkan (push) Has been cancelled
mac-build / macOS CPU (push) Has been cancelled
Nix Build / Nix (cpu) (push) Has been cancelled
Nix Build / Nix (vulkan) (push) Has been cancelled
windows-build / Windows CPU (push) Has been cancelled
Initial import: audio.cpp with Orange Pi documentation
2026-11-04 19:05:22 +01:00

190 lines
6.9 KiB
C++

#include "engine/community_models/inflect_v2/frontend.h"
#include "engine/community_models/inflect_v2/runtime.h"
#include "engine/community_models/inflect_v2/session.h"
#include "test_assert.h"
#include <cmath>
#include <filesystem>
#include <iostream>
#include <string>
#include <utility>
#include <vector>
int main() try {
using engine::models::inflect_v2::InflectV2Frontend;
using engine::models::inflect_v2::apply_inflect_v2_edge_fade;
engine::test::require_eq(
InflectV2Frontend::normalize(
"Dr. Smith paid $12.50 on 3/4/2026 at 9:05 PM."),
std::string(
"doctor Smith paid twelve dollars and fifty cents on March fourth "
"two thousand and twenty six at nine oh five p m."),
"normalization");
engine::test::require_eq(
InflectV2Frontend::normalize("Call 555-0123 at 8 PM."),
std::string("Call five five five, zero one two three at eight p m."),
"phone normalization");
engine::test::require_eq(
InflectV2Frontend::normalize("Version 2.10.3 costs $1.05."),
std::string(
"Version two point ten point three costs one dollar and five cents."),
"version and money normalization");
engine::test::require_eq(
InflectV2Frontend::normalize(
"NASA uses RTX 5090 and USB-C."),
std::string(
"en ay ess ay uses ar tee ex fifty ninety and you ess bee see."),
"acronym and pronunciation override normalization");
const std::string phoneme_golden =
std::string("h") + "\xC9\x99" + "l" + "\xCB\x88" + "o" +
"\xCA\x8A" + ".";
const auto token_ids =
InflectV2Frontend::tokens_from_phonemes(phoneme_golden);
engine::test::require(
token_ids ==
std::vector<int32_t>(
{0, 50, 0, 83, 0, 54, 0, 156, 0, 57, 0, 135, 0, 4, 0}),
"phoneme ids with blanks");
bool unknown_phoneme_rejected = false;
try {
(void)InflectV2Frontend::tokens_from_phonemes("#");
} catch (const std::runtime_error &) {
unknown_phoneme_rejected = true;
}
engine::test::require(
unknown_phoneme_rejected,
"unknown phoneme must be rejected");
const auto missing_library =
std::filesystem::temp_directory_path() /
"audio_cpp_inflect_v2_missing_espeak_library";
engine::test::require(
!std::filesystem::exists(missing_library),
"missing eSpeak test path must not exist");
bool missing_library_rejected = false;
try {
InflectV2Frontend frontend(missing_library, {});
} catch (const std::runtime_error &) {
missing_library_rejected = true;
}
engine::test::require(
missing_library_rejected,
"missing eSpeak library must be rejected");
auto assets =
std::make_shared<engine::models::inflect_v2::InflectV2Assets>();
auto contract =
std::make_shared<engine::model_spec::ModelContract>();
contract->session_option_keys = {
"inflect_v2.espeak_library_path",
"inflect_v2.espeak_data_path",
};
engine::runtime::SessionOptions invalid_task_options;
invalid_task_options.options["inflect_v2.espeak_library_path"] =
missing_library.string();
bool invalid_task_reported_first = false;
try {
engine::models::inflect_v2::InflectV2Session session(
{
engine::runtime::VoiceTaskKind::Vad,
engine::runtime::RunMode::Offline,
},
invalid_task_options,
assets,
contract);
} catch (const std::runtime_error & error) {
invalid_task_reported_first =
std::string(error.what()).find("offline TTS") != std::string::npos;
}
engine::test::require(
invalid_task_reported_first,
"task validation must run before eSpeak initialization");
engine::runtime::SessionOptions invalid_option_options;
invalid_option_options.options["inflect_v2.espeak_library_path"] =
missing_library.string();
invalid_option_options.options["inflect_v2.unknown"] = "1";
bool invalid_option_reported_first = false;
try {
engine::models::inflect_v2::InflectV2Session session(
{
engine::runtime::VoiceTaskKind::Tts,
engine::runtime::RunMode::Offline,
},
invalid_option_options,
assets,
contract);
} catch (const std::runtime_error & error) {
invalid_option_reported_first =
std::string(error.what()).find("unknown Inflect v2 session option") !=
std::string::npos;
}
engine::test::require(
invalid_option_reported_first,
"session option validation must run before eSpeak initialization");
const auto missing_data =
std::filesystem::temp_directory_path() /
"audio_cpp_inflect_v2_missing_espeak_data";
engine::test::require(
!std::filesystem::exists(missing_data),
"missing eSpeak data test path must not exist");
bool missing_data_rejected = false;
try {
InflectV2Frontend frontend({}, missing_data);
} catch (const std::runtime_error &) {
missing_data_rejected = true;
}
engine::test::require(
missing_data_rejected,
"missing eSpeak data must be rejected");
const auto chunks = InflectV2Frontend::split_text(
"First sentence. Second sentence! Last one?",
280);
engine::test::require_eq(chunks.size(), size_t{3}, "sentence chunk count");
engine::test::require_eq(
chunks[0],
std::string("First sentence."),
"first sentence chunk");
engine::test::require_eq(
chunks[2],
std::string("Last one?"),
"last sentence chunk");
engine::test::require(
InflectV2Frontend::split_text(" \n\t", 280).empty(),
"whitespace-only text must not produce chunks");
for (const auto & [text, expected] :
std::vector<std::pair<std::string, double>>{
{"Question?", 0.28},
{"Exclamation!", 0.24},
{"Sentence.", 0.22},
{"Clause;", 0.16},
{"Label:", 0.13},
{"Phrase,", 0.09},
{"Words", 0.08},
}) {
engine::test::require(
std::abs(InflectV2Frontend::boundary_pause_seconds(text) - expected) <
1.0e-12,
"punctuation pause");
}
std::vector<float> samples(480, 1.0F);
apply_inflect_v2_edge_fade(samples, 24000);
engine::test::require_eq(samples.front(), 0.0F, "fade-in starts at zero");
engine::test::require_eq(samples[119], 1.0F, "fade-in reaches unity");
engine::test::require_eq(samples[360], 1.0F, "fade-out starts at unity");
engine::test::require_eq(samples.back(), 0.0F, "fade-out ends at zero");
std::cout << "inflect_v2_frontend_test passed\n";
return 0;
} catch (const std::exception & error) {
std::cerr << "inflect_v2_frontend_test failed: " << error.what() << "\n";
return 1;
}