File size: 5,226 Bytes
71e354e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
/**************************************************************************************************
 *
 * Copyright (c) 2019-2026 Axera Semiconductor (Ningbo) Co., Ltd. All Rights Reserved.
 *
 * This source file is the property of Axera Semiconductor (Ningbo) Co., Ltd. and
 * may not be copied or distributed in any isomorphic form without the prior
 * written consent of Axera Semiconductor (Ningbo) Co., Ltd.
 *
 **************************************************************************************************/
#include "frontend/kokoro_frontend.hpp"

#include "utils/logger.h"
#include "utils/string_utils.hpp"
#include "text_cleaner/text_cleaner.hpp"
#include "text_normalizer/text_normalizer.hpp"
#include "g2p/EnEspeakG2P.hpp"
#include "g2p/LatinEspeakG2P.hpp"
#include "text_processor/jieba_processor.hpp"
#include "g2p/ZhJiebaG2P.hpp"
#include <mutex>

using namespace std;
using namespace utils;

// Impl
class KokoroFrontend::Impl {
public:
    Impl() = default;
    ~Impl() = default;

    bool init(AX_TTS_INIT_CONFIG* config) {
        if (strlen(config->espeak_data_path) == 0) {
            ALOGE("espeak_data_path is not set in config");
            return false;
        }

        espeak_data_path_ = string(config->espeak_data_path);
        jieba_dict_path_ = string(config->jieba_dict_path);

        if (jieba_dict_path_.empty()) {
            ALOGW("jieba_dict_path is empty; zh will be unavailable until set.");
        }

        // Preload g2p: English only; zh is loaded lazily.
        g2p_backends_.insert({"en", make_shared<EnEspeakG2P>(espeak_data_path_.c_str())});
        return true;
    }

    std::vector<int> run(const std::string& input_text, const std::string& language, const std::map<std::string, int>& vocab, int& err) {
        vector<int> tokens;

        if (language == "zh") {
            if (!ensure_zh_ready_()) {
                ALOGE("Zh processor not ready!");
                err = -1;
                return {};
            }
        }

        static const std::string kPhonemePrefix = "__PHONEMES__:";
        std::string phonemes;
        if (input_text.rfind(kPhonemePrefix, 0) == 0) {
            phonemes = input_text.substr(kPhonemePrefix.size());
        } else {
            // 1. Clean text (skip aggressive cleaning for Japanese to preserve fullwidth punctuation)
            auto cleaned_text = (language == "ja") ? input_text : text_cleaner_.run(input_text);
            // 2. Normalize text
            auto normalized_text = text_normalizer_.run(cleaned_text, language);
            // 3. G2P
            auto g2p = load_g2p_(language);
            phonemes = g2p->run(normalized_text, err);
        }

        ALOGD("input_text: %s", input_text.c_str());
        ALOGD("phonemes: %s", phonemes.c_str());
        
        // 4. Phonemes -> Tokens
        tokens.reserve(phonemes.length() + 2);
        tokens.emplace_back(0);

        vector<string> chars = utils::split_utf8(phonemes);
        for (const auto& c : chars) {
            if (vocab.count(c))
                tokens.emplace_back(vocab.at(c));
        }
        tokens.emplace_back(0);
        return tokens;
    }

private:
    shared_ptr<G2P> load_g2p_(const string& language) {
        if (g2p_backends_.find(language) == g2p_backends_.end()) {
            if (language == "zh") {
                if (!ensure_zh_ready_()) {
                    return nullptr;
                }
            }
            shared_ptr<G2P> new_g2p = make_shared<LatinEspeakG2P>(
                espeak_data_path_.c_str(),
                language
            );
            g2p_backends_.insert({language, new_g2p});
            return new_g2p;
        } else {
            return g2p_backends_.at(language);
        }
    }

    bool ensure_zh_ready_() {
        if (zh_ready_) {
            return true;
        }
        std::lock_guard<std::mutex> lock(zh_mutex_);
        if (zh_ready_) {
            return true;
        }
        if (jieba_dict_path_.empty()) {
            ALOGE("jieba_dict_path is empty, cannot init zh processor");
            return false;
        }
        zh_processor_ = make_shared<JiebaProcessor>();
        if (!zh_processor_->load_dict(jieba_dict_path_)) {
            ALOGE("Init zh_processor failed!");
            return false;
        }
        text_normalizer_.set_zh_processor(zh_processor_);
        g2p_backends_.insert({"zh", make_shared<ZhJiebaG2P>(zh_processor_)});
        zh_ready_ = true;
        return true;
    }

private:
    string espeak_data_path_;
    string jieba_dict_path_;
    map<string, shared_ptr<G2P>> g2p_backends_;  
    shared_ptr<TextProcessor> zh_processor_;
    TextCleaner text_cleaner_;
    TextNormalizer text_normalizer_;
    std::mutex zh_mutex_;
    bool zh_ready_ = false;
};

// KokoroFrontend
KokoroFrontend::KokoroFrontend():
    impl_(std::make_unique<Impl>()) {

}

KokoroFrontend::~KokoroFrontend() {
    impl_.reset();
}

bool KokoroFrontend::init(AX_TTS_INIT_CONFIG* config) {
    return impl_->init(config);
}

std::vector<int> KokoroFrontend::run(const std::string& input_text, const std::string& language, const std::map<std::string, int>& vocab, int& err) {
    return impl_->run(input_text, language, vocab, err);
}