333 lines
10 KiB
C++
Raw Normal View History

2024-01-08 23:37:00 +08:00
#include "ebyroid.h"
#include "api_adapter.h"
#include "api_settings.h"
#include "ebyutil.h"
2024-04-02 15:36:52 +08:00
namespace ebyroid
{
using std::function;
using std::pair;
using std::string;
using std::vector;
namespace
{
ApiAdapter *NewAdapter(const string &, const string &, const string &, float, float);
int __stdcall HiraganaCallback(EventReasonCode, int32_t, IntPtr);
int __stdcall SpeechCallback(EventReasonCode, int32_t, uint64_t, IntPtr);
inline pair<bool, string> WithDirecory(const char *dir, function<pair<bool, string>(void)> yield);
} // namespace
Ebyroid::~Ebyroid()
{
delete api_adapter_;
}
Ebyroid *Ebyroid::Create(const string &base_dir, const string &dllpath, const string &voice, float volume, float speed)
{
ApiAdapter *adapter = NewAdapter(base_dir, dllpath, voice, volume, speed);
Ebyroid *ebyroid = new Ebyroid(adapter);
return ebyroid;
}
2025-01-02 22:56:39 +08:00
int Ebyroid::Hiragana(const char *inbytes, std::vector<char> &output)
2024-04-02 15:36:52 +08:00
{
2025-01-02 22:56:39 +08:00
Response<char> response{api_adapter_};
2024-04-02 15:36:52 +08:00
TJobParam param;
param.mode_in_out = IOMODE_PLAIN_TO_AIKANA;
2025-01-02 22:56:39 +08:00
param.user_data = &response;
2024-04-02 15:36:52 +08:00
int32_t job_id;
2025-01-02 22:56:39 +08:00
ResultCode result = api_adapter_->TextToKana(&job_id, &param, inbytes);
2024-04-02 15:36:52 +08:00
if (result != ERR_SUCCESS)
{
static const char *format = "TextToKana failed with the result code %d\n"
"Given inbytes: %s";
char m[0xFFFF];
std::snprintf(m, 0xFFFF, format, result, inbytes);
throw std::runtime_error(m);
}
2025-01-02 22:56:39 +08:00
WaitForSingleObject(response.event, INFINITE);
2024-04-02 15:36:52 +08:00
// finalize
result = api_adapter_->CloseKana(job_id);
if (result != ERR_SUCCESS)
{
throw std::runtime_error("wtf");
}
// write to output memory
2025-01-02 22:56:39 +08:00
output = response.End();
2024-04-02 15:36:52 +08:00
return 0;
2024-01-08 23:37:00 +08:00
}
2025-01-02 22:56:39 +08:00
int Ebyroid::Speech(const char *inbytes, std::vector<int16_t> &output, uint32_t mode)
2024-04-02 15:36:52 +08:00
{
2025-01-02 22:56:39 +08:00
Response<int16_t> response{api_adapter_};
2024-04-02 15:36:52 +08:00
TJobParam param;
param.mode_in_out = mode == 0u ? IOMODE_AIKANA_TO_WAVE : (JobInOut)mode;
2025-01-02 22:56:39 +08:00
param.user_data = &response;
2024-04-02 15:36:52 +08:00
int32_t job_id;
2025-01-02 22:56:39 +08:00
ResultCode result = api_adapter_->TextToSpeech(&job_id, &param, inbytes);
2024-04-02 15:36:52 +08:00
if (result != ERR_SUCCESS)
{
static const char *format = "TextToSpeech failed with the result code %d\n"
"Given inbytes: %s";
char m[0xFFFF];
std::snprintf(m, 0xFFFF, format, result, inbytes);
throw std::runtime_error(m);
}
2025-01-02 22:56:39 +08:00
WaitForSingleObject(response.event, INFINITE);
2024-01-08 23:37:00 +08:00
2024-04-02 15:36:52 +08:00
// finalize
result = api_adapter_->CloseSpeech(job_id);
if (result != ERR_SUCCESS)
{
throw std::runtime_error("wtf");
}
// write to output memory
2025-01-02 22:56:39 +08:00
output = response.End();
2024-01-08 23:37:00 +08:00
2024-04-02 15:36:52 +08:00
return 0;
2024-01-08 23:37:00 +08:00
}
2024-04-02 15:36:52 +08:00
namespace
{
ApiAdapter *NewAdapter(const string &base_dir, const string &dllpath, const string &voice, float volume, float speed)
{
SettingsBuilder builder(base_dir, voice);
Settings settings = builder.Build();
2025-01-02 22:56:39 +08:00
std::unique_ptr<ApiAdapter> adapter{ApiAdapter::Create(dllpath.c_str())};
2024-04-02 15:36:52 +08:00
TConfig config;
config.hz_voice_db = settings.frequency;
config.msec_timeout = 1000;
config.path_license = settings.license_path;
config.dir_voice_dbs = settings.voice_dir;
config.code_auth_seed = settings.seed;
config.len_auth_seed = kLenSeedValue;
ResultCode result = adapter->Init(&config);
if (result != ERR_SUCCESS)
{
config.code_auth_seed = "PROJECT-VOICeVIO-SFE";
result = adapter->Init(&config);
}
if (result != ERR_SUCCESS)
{
string message = "API initialization failed with code ";
message += std::to_string(result);
throw std::runtime_error(message);
}
SetDllDirectoryA(settings.base_dir);
result = adapter->LangLoad(settings.language_dir);
result = adapter->VoiceLoad(settings.voice_name);
if (result != ERR_SUCCESS)
{
string message = "API Load Voice failed (Could not load voice data) with code ";
message += std::to_string(result);
throw std::runtime_error(message);
}
uint32_t param_size = 0;
result = adapter->GetParam((void *)0, &param_size);
if (result != ERR_INSUFFICIENT)
{ // NOTE: Code -20 is expected here
string message = "API Get Param failed (Could not acquire the size) with code ";
message += std::to_string(result);
throw std::runtime_error(message);
}
2025-01-02 22:56:39 +08:00
if (param_size == sizeof(TTtsParam))
2024-04-02 15:36:52 +08:00
{ // voiceroid2
2025-01-02 22:56:39 +08:00
TTtsParam param;
2024-04-02 15:36:52 +08:00
// TTtsParam* param = (TTtsParam*) param_buffer;
2025-01-02 22:56:39 +08:00
param.size = param_size;
result = adapter->GetParam(&param, &param_size);
2024-04-02 15:36:52 +08:00
if (result != ERR_SUCCESS)
{
2024-01-08 23:37:00 +08:00
string message = "API Get Param failed with code ";
message += std::to_string(result);
throw std::runtime_error(message);
2024-04-02 15:36:52 +08:00
}
2025-01-02 22:56:39 +08:00
param.extend_format = BOTH;
param.proc_text_buf = HiraganaCallback;
param.proc_raw_buf = SpeechCallback;
param.proc_event_tts = nullptr;
param.len_raw_buf_bytes = kConfigRawbufSize;
param.volume = volume;
param.speaker[0].volume = volume;
/*param.speaker[0].pitch = 1.111;
param.speaker[0].pause_middle = 80;
param.speaker[0].pause_sentence = 200;
param.speaker[0].pause_long = 100;
param.speaker[0].range = 0.893;*/
param.speaker[0].speed = speed;
result = adapter->SetParam(&param);
2024-04-02 15:36:52 +08:00
if (result != ERR_SUCCESS)
{
2024-01-08 23:37:00 +08:00
string message = "API Set Param failed with code ";
message += std::to_string(result);
throw std::runtime_error(message);
2024-04-02 15:36:52 +08:00
}
}
2025-01-02 22:56:39 +08:00
else if (param_size == sizeof(AITalk_TTtsParam))
2024-04-02 15:36:52 +08:00
{ // voiceroid+
2025-01-02 22:56:39 +08:00
AITalk_TTtsParam param;
2024-04-02 15:36:52 +08:00
// TTtsParam* param = (TTtsParam*) param_buffer;
2025-01-02 22:56:39 +08:00
param.size = param_size;
result = adapter->GetParam(&param, &param_size);
2024-04-02 15:36:52 +08:00
if (result != ERR_SUCCESS)
{
2024-01-08 23:37:00 +08:00
string message = "API Get Param failed with code ";
message += std::to_string(result);
throw std::runtime_error(message);
2024-04-02 15:36:52 +08:00
}
2025-01-02 22:56:39 +08:00
param.proc_text_buf = HiraganaCallback;
param.proc_raw_buf = SpeechCallback;
param.proc_event_tts = nullptr;
param.lenRawBufBytes = kConfigRawbufSize;
param.volume = volume;
result = adapter->SetParam(&param);
2024-04-02 15:36:52 +08:00
if (result != ERR_SUCCESS)
{
2024-01-08 23:37:00 +08:00
string message = "API Set Param failed with code ";
message += std::to_string(result);
throw std::runtime_error(message);
2024-04-02 15:36:52 +08:00
}
2024-01-08 23:37:00 +08:00
}
2025-01-02 22:56:39 +08:00
auto _ = adapter.get();
adapter.release();
return _;
2024-04-02 15:36:52 +08:00
}
inline pair<bool, string> WithDirecory(const char *dir, function<pair<bool, string>(void)> yield)
{
static constexpr size_t kErrMax = 64 + MAX_PATH;
char org[MAX_PATH];
DWORD result = GetCurrentDirectoryA(MAX_PATH, org);
if (result == 0)
{
2024-01-08 23:37:00 +08:00
char m[64];
std::snprintf(m, 64, "Could not get the current directory.\n\tErrorNo = %d", GetLastError());
return pair<bool, string>(true, string(m));
2024-04-02 15:36:52 +08:00
}
BOOL result1 = SetCurrentDirectoryA(dir);
if (!result1)
{
2024-01-08 23:37:00 +08:00
char m[kErrMax];
std::snprintf(m,
2024-04-02 15:36:52 +08:00
kErrMax,
"Could not change directory.\n\tErrorNo = %d\n\tTarget path: %s",
GetLastError(),
dir);
2024-01-08 23:37:00 +08:00
return pair<bool, string>(true, string(m));
2024-04-02 15:36:52 +08:00
}
bool is_error = yield().first;
string what = yield().second;
result1 = SetCurrentDirectoryA(org);
if (!result1 && !is_error)
{
2024-01-08 23:37:00 +08:00
char m[kErrMax];
std::snprintf(m,
2024-04-02 15:36:52 +08:00
kErrMax,
"Could not change directory.\n\tErrorNo = %d\n\tTarget path: %s",
GetLastError(),
org);
2024-01-08 23:37:00 +08:00
return pair<bool, string>(true, string(m));
2024-04-02 15:36:52 +08:00
}
if (is_error)
{
2024-01-08 23:37:00 +08:00
return pair<bool, string>(true, what);
2024-04-02 15:36:52 +08:00
}
return pair<bool, string>(false, string());
2024-01-08 23:37:00 +08:00
}
2024-04-02 15:36:52 +08:00
int __stdcall HiraganaCallback(EventReasonCode reason_code, int32_t job_id, IntPtr user_data)
{
2025-01-02 22:56:39 +08:00
auto response = (Response<char> *)user_data;
2024-04-02 15:36:52 +08:00
ApiAdapter *api_adapter = response->api_adapter();
if (reason_code != TEXTBUF_FULL && reason_code != TEXTBUF_FLUSH && reason_code != TEXTBUF_CLOSE)
{
// unexpected: may possibly lead to memory leak
return 0;
}
2024-01-08 23:37:00 +08:00
2024-04-02 15:36:52 +08:00
static constexpr int kBufferSize = 0x1000;
2025-01-02 22:56:39 +08:00
char buffer[kBufferSize];
2024-04-02 15:36:52 +08:00
while (true)
{
uint32_t size, pos;
ResultCode result = api_adapter->GetKana(job_id, buffer, kBufferSize, &size, &pos);
if (result != ERR_SUCCESS)
{
break;
}
response->Write(buffer, size);
if (kBufferSize > size)
{
break;
}
}
if (reason_code == TEXTBUF_CLOSE)
{
2025-01-02 22:56:39 +08:00
response->event.Set();
2024-04-02 15:36:52 +08:00
}
return 0;
2024-01-08 23:37:00 +08:00
}
2024-04-02 15:36:52 +08:00
int __stdcall SpeechCallback(EventReasonCode reason_code,
int32_t job_id,
uint64_t tick,
IntPtr user_data)
{
2025-01-02 22:56:39 +08:00
auto response = (Response<int16_t> *)user_data;
2024-04-02 15:36:52 +08:00
ApiAdapter *api_adapter = response->api_adapter();
if (reason_code != RAWBUF_FULL && reason_code != RAWBUF_FLUSH && reason_code != RAWBUF_CLOSE)
{
// unexpected: may possibly lead to memory leak
return 0;
}
2024-01-08 23:37:00 +08:00
2024-04-02 15:36:52 +08:00
static constexpr int kBufferSize = 0xFFFF;
2025-01-02 22:56:39 +08:00
int16_t buffer[kBufferSize];
2024-04-02 15:36:52 +08:00
while (true)
{
uint32_t size, pos;
ResultCode result = api_adapter->GetData(job_id, buffer, kBufferSize, &size);
if (result != ERR_SUCCESS)
{
break;
}
2025-01-02 22:56:39 +08:00
response->Write(buffer, size);
2024-04-02 15:36:52 +08:00
if (kBufferSize > size)
{
break;
}
}
if (reason_code == RAWBUF_CLOSE)
{
2025-01-02 22:56:39 +08:00
response->event.Set();
2024-04-02 15:36:52 +08:00
}
return 0;
2024-01-08 23:37:00 +08:00
}
2024-04-02 15:36:52 +08:00
} // namespace
2024-01-08 23:37:00 +08:00
2024-04-02 15:36:52 +08:00
} // namespace ebyroid