| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| #include <algorithm> |
| #include <cmath> |
| #include <cstdint> |
| #include <cstdio> |
| #include <fstream> |
| #include <iostream> |
| #include <random> |
| #include <stdexcept> |
| #include <string> |
| #include <vector> |
|
|
| #include "host_chain.h" |
| #include "inflect_tts.h" |
| #include "wav_writer.h" |
|
|
| namespace { |
|
|
| int selftest() { |
| int failures = 0; |
| auto check = [&](bool ok, const char* name) { |
| std::cout << (ok ? "[PASS] " : "[FAIL] ") << name << "\n"; |
| if (!ok) ++failures; |
| }; |
|
|
| |
| { |
| std::mt19937 rng(42); |
| std::normal_distribution<float> norm(0.0f, 1.0f); |
| const int t_total = 16, x_len = 12; |
| std::vector<float> logw(t_total), m_p(inflect::kHiddenChannels * t_total), |
| logs_p(inflect::kHiddenChannels * t_total); |
| for (auto& v : logw) v = norm(rng); |
| for (auto& v : m_p) v = norm(rng); |
| for (auto& v : logs_p) v = norm(rng); |
| const float ls = 1.25f; |
| auto priors = inflect::expand_priors(logw.data(), m_p.data(), logs_p.data(), |
| t_total, x_len, ls); |
|
|
| |
| std::vector<int64_t> cum(t_total); |
| int64_t acc = 0; |
| for (int i = 0; i < t_total; ++i) { |
| const float w = (i < x_len) ? std::exp(logw[i]) * ls : 0.0f; |
| acc += static_cast<int64_t>(std::ceil(w)); |
| cum[i] = acc; |
| } |
| const int t_prime = static_cast<int>(std::max<int64_t>(acc, 1)); |
| check(priors.t_prime == t_prime, "expand_priors: T' == cumsum sum"); |
| double max_diff = 0.0; |
| for (int t = 0; t < t_prime; ++t) { |
| for (int c = 0; c < inflect::kHiddenChannels; ++c) { |
| float ref = 0.0f; |
| for (int i = 0; i < t_total; ++i) { |
| const int64_t lo = (i == 0) ? 0 : cum[i - 1]; |
| if (lo <= t && t < cum[i]) { |
| ref = m_p[static_cast<size_t>(c) * t_total + i]; |
| break; |
| } |
| } |
| max_diff = std::max(max_diff, |
| static_cast<double>(std::fabs( |
| priors.m_p[static_cast<size_t>(c) * t_prime + t] - ref))); |
| } |
| } |
| check(max_diff == 0.0, "expand_priors: gather == attn matmul (exact)"); |
| } |
|
|
| |
| { |
| const int t_total = 8, x_len = 5; |
| std::vector<float> logw(t_total, 0.0f); |
| std::vector<float> m_p(inflect::kHiddenChannels * t_total), |
| logs_p(inflect::kHiddenChannels * t_total); |
| for (size_t i = 0; i < m_p.size(); ++i) { |
| m_p[i] = static_cast<float>(i % 7) * 0.1f; |
| logs_p[i] = -1.0f; |
| } |
| auto priors = inflect::expand_priors(logw.data(), m_p.data(), logs_p.data(), |
| t_total, x_len, 1.0f); |
| bool ok = priors.t_prime == x_len; |
| for (int t = 0; ok && t < x_len; ++t) { |
| for (int c = 0; c < inflect::kHiddenChannels; ++c) { |
| ok = priors.m_p[static_cast<size_t>(c) * priors.t_prime + t] == |
| m_p[static_cast<size_t>(c) * t_total + t]; |
| } |
| } |
| check(ok, "expand_priors: unity durations == identity"); |
| } |
|
|
| |
| { |
| inflect::ExpandedPriors priors; |
| priors.t_prime = 4; |
| priors.m_p.assign(inflect::kHiddenChannels * 4, 0.5f); |
| priors.logs_p.assign(inflect::kHiddenChannels * 4, -1.0f); |
| const auto a = inflect::inject_noise(priors, 0.667f, 7); |
| const auto b = inflect::inject_noise(priors, 0.667f, 7); |
| const auto c = inflect::inject_noise(priors, 0.667f, 8); |
| check(a == b, "inject_noise: same seed reproducible"); |
| check(a != c, "inject_noise: different seed differs"); |
| } |
|
|
| |
| { |
| const auto single = inflect::decoder_chunk_starts(51); |
| check(single.size() == 1 && single[0] == 0, "chunk_starts: T'<=512 single"); |
| const auto multi = inflect::decoder_chunk_starts(812); |
| bool ok = multi.size() >= 2 && multi.front() == 0 && |
| multi.back() + inflect::kDecoderTp >= 812; |
| for (size_t i = 1; ok && i < multi.size(); ++i) { |
| ok = multi[i] < multi[i - 1] + inflect::kDecoderTp - inflect::kDecoderOverlap + 1; |
| } |
| check(ok, "chunk_starts: T'=812 coverage + overlap"); |
| } |
|
|
| |
| { |
| const std::string path = "inflect_selftest_tmp.wav"; |
| std::vector<float> wav(2400, 0.25f); |
| write_wav(path, wav, 24000); |
| std::ifstream f(path, std::ios::binary); |
| char riff[4]; |
| f.read(riff, 4); |
| const bool ok = f && riff[0] == 'R' && riff[1] == 'I' && riff[2] == 'F' && |
| riff[3] == 'F'; |
| f.close(); |
| std::remove(path.c_str()); |
| check(ok, "write_wav: RIFF header"); |
| } |
|
|
| std::cout << (failures ? "SELFTEST FAILED" : "SELFTEST PASS") << "\n"; |
| return failures ? 1 : 0; |
| } |
|
|
| std::vector<int64_t> parse_tokens(const std::string& csv) { |
| std::vector<int64_t> ids; |
| size_t pos = 0; |
| while (pos <= csv.size()) { |
| const size_t comma = csv.find(',', pos); |
| const std::string part = csv.substr(pos, comma - pos); |
| if (!part.empty()) { |
| ids.push_back(std::stoll(part)); |
| } |
| if (comma == std::string::npos) break; |
| pos = comma + 1; |
| } |
| return ids; |
| } |
|
|
| void usage(const char* argv0) { |
| std::cerr |
| << "usage:\n" |
| << " " << argv0 << " --selftest\n" |
| << " " << argv0 << " --encoder ENC.axmodel --decoder DEC.axmodel\n" |
| << " --tokens 81,83,16,53,65,102,53 --output out.wav\n" |
| << " [--speed 1.0] [--variation 0.667] [--seed 0]\n"; |
| } |
|
|
| } |
|
|
| int main(int argc, char** argv) { |
| std::string encoder, decoder, tokens_csv, output; |
| float speed = 1.0f, variation = 0.667f; |
| uint64_t seed = 0; |
| for (int i = 1; i < argc; ++i) { |
| const std::string arg = argv[i]; |
| auto next = [&](const char* name) -> std::string { |
| if (i + 1 >= argc) { |
| throw std::runtime_error(std::string("missing value for ") + name); |
| } |
| return argv[++i]; |
| }; |
| if (arg == "--selftest") return selftest(); |
| if (arg == "--encoder") encoder = next("--encoder"); |
| else if (arg == "--decoder") decoder = next("--decoder"); |
| else if (arg == "--tokens") tokens_csv = next("--tokens"); |
| else if (arg == "--output") output = next("--output"); |
| else if (arg == "--speed") speed = std::stof(next("--speed")); |
| else if (arg == "--variation") variation = std::stof(next("--variation")); |
| else if (arg == "--seed") seed = std::stoull(next("--seed")); |
| else if (arg == "--help" || arg == "-h") { usage(argv[0]); return 0; } |
| else { |
| std::cerr << "unknown argument: " << arg << "\n"; |
| usage(argv[0]); |
| return 2; |
| } |
| } |
| if (encoder.empty() || decoder.empty() || tokens_csv.empty() || output.empty()) { |
| usage(argv[0]); |
| return 2; |
| } |
| try { |
| InflectTTS tts(encoder, decoder); |
| const auto ids = parse_tokens(tokens_csv); |
| auto wav = tts.synthesize_tokens(ids, speed, variation, seed); |
| write_wav(output, wav, inflect::kSampleRate); |
| std::cout << "wrote " << output << ": " << wav.size() << " samples (" |
| << static_cast<double>(wav.size()) / inflect::kSampleRate |
| << " s @ " << inflect::kSampleRate << " Hz)\n"; |
| } catch (const std::exception& exc) { |
| std::cerr << "error: " << exc.what() << "\n"; |
| return 1; |
| } |
| return 0; |
| } |
|
|