
![]() |
Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
|
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[18.97.9.175] |
![]() |
|
Страницы: (2) [1] 2 все ( Перейти к последнему сообщению ) |
Сообщ.
#1
,
|
|
|
В настоящее время в Интернете доступен проект SALB. Описание с его главной стр.
Проект SALB - это sapi-интерфейс для синтезаторов речи, использующих марковские модели голосов, созданных в HTS. Вы можете посмотреть Релиз на https://github.com/m-toman/SALB/releases и страницу проекта на github-е по адресу https://github.com/m-toman/SALB. И тд. Проект использует новые возможности компиляторра 11-й версии (auto и тд), поэтому собирается только в VC++ 2012 и выше. Я собрал в 2008Express свой проект Flite+hts на основе Flite 2001 года. sapi-драйвер одновременно воспроизводит английские голоса от RHVoice c разной частотой дескритизации (16 и 48кГц). "Допипиливается". Предлагаю исправленный вариант register_vox.cpp для обоих проектов (имя в Flite) и командные файлы для регистрации dll, а также для установки и удаления голосов. _register-alan.bat ![]() ![]() rem запрос прав администратора, см. во вложении. cd %~dp0 echo %CD% @echo on regsvr32 "%CD%\FliteCMUKalDiphone.dll" "%CD%\register-vox.exe" "%CD%\data\voices\Alan" "HTS Voice Alan" "en-us" "Female" "Adult" "%CD%\alan.log" @echo off pause _unregister_Alan.bat ![]() ![]() rem запрос прав администратора, см. во вложении. cd %~dp0 echo %CD% @echo on regsvr32 "/u" "%CD%\FliteCMUKalDiphone.dll" "%CD%\register-vox.exe" "/u" "HTS Voice Alan" @echo off pause Прикреплённый файл ![]() |
Сообщ.
#2
,
|
|
|
Обновление для flite+hts_engine-1.02 до flite+hts_engine-1.02.02. Исходный код проекта flite+hts_engine можно взять на гитхабе.
Командная строка для запуска: flite_hts_engine -v alan -p 240 -s 48000 -a 0.52 input.txt Прикреплённый файл ![]() Прикреплённый файл ![]() |
Сообщ.
#3
,
|
|
|
webcoder88, ты ты не забыл, что без ключа /D команда cd диск не меняет?
Вторую строчку в обоих файлах надо писать ![]() ![]() cd /D %~dp0 |
Сообщ.
#4
,
|
|
|
amk
Я взял файлы из проекта SALB. ![]() Есть желающие добавить русский язык в парсер Flite. Код можно взять в RHVoice, но есть некоторые нюансы: -разработчица, Linux-разработчица, в Линуксе ничего нет видимо, но с др. стороны разработчики не могут разобраться; -разработчица переписала часть функций, чем упростила алгоритм обработки; -модель длительности берется из фестивалевской модели языка (каталог languages), без к-го не работают даже английские голоса; -используется "рыжий" фикс длительности; -можно сделать воспроизведение анси-текста на кодовой странице 1251, без уникода; -в RHVoice отсутствует обработка интонации, поэтому русские голоса похожи на монотонное бормотание. В RHVoice проделана большая работа, но результат еще не достигнут. Можно обойтись без файлов Фестиваля, доделать модель русского языка на с++, интонацию. Русские голоса должны воспроизводиться так же как и английские с качеством, близким к оригиналу (хотя бы для тренировочных предложений). Добавлено Я создавал метки RHVoice для русского голоса и воспроизводил их в hts_engine, который одинаково хорошо воспроизводи метки любого языка. Почти все гласные восп-ся с увеличенной длительностью. Замена исходного файла длительности на мой ничего не изменила. Из чего следует, что парсер RHVoice выдает некорректные метки. Вся необходимая информация для генерации голоса содержиться в файле меток. Это длительность, ударение, интонация. Можно попытаться подбрать вручную. ![]() Остальное зависит от качества тренированной HMM-модели. Добавлено Sapi-драйвер для английских голосов, без исх. кода. Может отсутствовать синхронизация выделенного и произносимого текста (в панели управления->речь), пока подделка. Тестировался только на x86, проверен на антивирусе, но лучше запускать на виртуалке. https://www.sendspace.com/file/hp1gbh |
Сообщ.
#5
,
|
|
|
Драйвер SAPI своими руками.
Здесь приводится описание готового sapi-драйвера. На Харбар пока не тянет. Шаг 1. Прежде чем что-то создавать, стоит поискать готовое или почти готовое решение в Интернете. Это позволит съэкономить очень много времени. В настоящее время в Интернете можно легко найти проекты с открытым исходным кодом: FLite, RHVoice, SALB. Проект SALB - это sapi-интерфейс для синтезаторов речи, использующих марковские модели голосов и созданных в HTS. Вы можете посмотреть релиз на https://github.com/m-toman/SALB/releases и странице проекта на github-е по адресу https://github.com/m-toman/SALB. Данный проект работает с файлом голоса, сохраненным в формате hts_engine_API версии 1.08...1.10 (модель голоса одним файлом), но для совместимости с голосами RHVoice нужно применить версию 1.05..06. Для сборки проекта потребуются следующие компоненты: - WinDDK версии 7.1.0 или выше для сборки sapi-драйвера. - ATL (входит в состав WinDDK); - Студия VC++ 2012...2015. Поддериваются аглийские и немецкие голоса (на уровне языковой спецификации, есть документация). RHVoice - русский синтезатор речи с открытым кодом, но разработан в среде LInux. Для сборки потребуются следующие компоненты: - база русской речи и спецификация языка, https://developer.berlios.de/projects/festlang; - HTS скрипты для тренировки голоса, http://hts.sp.nitech.ac.jp; - hts_engine API для синтеза речи в реальном времени, http://hts-engine.sourceforge.net/; - анализатор текста Flite, http://www.speech.cs.cmu.edu/flite; - пакет русской лексики от Игоря Порецкого, http://poretsky.homelinux.net/packages/ - библиотека libunistring для работы с уникодом, http://www.gnu.org/software/libunistring/; - библиотека sonic для преобразования форматов звуковых файлов и синтеза быстрой речи, git://vinuxproject.org/sonic - парсер expat XML для поддержки SSML, http://expat.sourceforge.net - библиотека PCRE (версии 8.10 или выше), http://pcre.org; - libsox для некоторых задач постобработки, http://sox.sourceforge.net; - WinDDK версии 7.1.0 или выше для сборки sapi-драйвера. - Студия VC++ 2008...2015 - sygwin для эмуляции командного процессора Linux; - совместимые версии phiton и scons для запуска скриптов сборки проекта и установки голоса. Скорее всего, это не предел по кол-ву библиотек и компонентов и могло быть еще больше, просто у разработчицы не хватило фантазии. FLIte - это проект 2001 года с открытым исходным кодом и проектом sapi-драйвера на ATL. Максимальная совместимость и минимум классов. К недостаткам относятся: механический голос и поддержка спецификации английского языка на уровне кода. Для сборки проекта потребуются следующие компоненты: - WinDDK версии 7.1.0 или выше для сборки sapi-драйвера. - ATL (входит в состав WinDDK); - Студия VC++ 6.0...2015. Я остановил свой выбор на проекте sapi Flite. Для начала стоит Загрузить архив flite-1.2-release.tar.bz2 со страницы проекта и разархивировать в любой каталог. Далее необходи открыть проект flite+hts_engine-1.02/sapi в студии VC++ 2008...2015. Новая студия предложит преобразование проекта, тк он создан в VC++ 6.0. После преобразования проект можно собрать и проверить работу полученного дифонного движка. Регистрацию драйвера выполяет сама студия. См. события после построения. Следующий шаг - интеграция hts_engine_API версии 1.06 и удаление ненужных файлов. |
Сообщ.
#6
,
|
|
|
Шаг 2.
Преже всего, необходимо переделать проект flite+hts_engine-1.02 в flite+hts_engine-1.02.02, т.е добавить в него новую функцию LoadeVoice(), далее скопировать каталог Sapi из проекта Flite в каталог проекта flite+hts_engine-1.02.02. Тк каталоги проектов имеют разную структуру, то вам потребуется исправить пути к файлам во всех проектах решения. Это можно сделать проще. Все файлы Flite компактно расположены в одноименном каталоге. Например, берете проект cmu_us_kal. Удалите все Source-файлы проекта, а потом выделите файлcmu_us_kal.c в каталоге \flite\lang\cmu_us_kal (Кроме него там должен быть файл voxdefs.h), перенесите и отпустите на значек каталога Source Files в Обозревателе решения. Аналогично исправьте все проекты в Обозревателе решения сверху вниз, исключая все файлы в Обозревателе решения и перенося все Header- и Source-файлы из соответствующих каталогов. Состав каталога flite\lang: cmu_us_kal cmulex usenglish Проект register-vox можно оставить без изменения. Далее в решение следует добавить новый пустой проект hts_engine_API, сняв галочку создать отдельный каталог проекта. В корневом каталоге Sapi должен появиться еще один каталог проекта hts_engine_API. Добавьте в проект заголовочные и исходные файлы через Обозреватель решения, путем переноса из каталога flite+hts_engine-1.02.02s_ok\hts_engine_API\lib Далее необходимо заменить 1 include-файл, тк драйвер использует более старую версию файла \flite\include\cst_tokenstream.h. Переименуйте его в cst_tokenstream_new.h и скопируйте одноименный файл из исходного проекта Flite 2001 года. Для сборки драйвера Свойства всех проектов должны иметь одинаковую настройку компилятора: Создание кода-библиотека времени выполнения-Многопоточная(MT), тначе не соберется из-за конфликта библиотек (иногда можно собрать, если исключать конфликтные библиотеки в настройках Линкера, но это лишнее). Проекты, относящиеся к flite и hts_engine_API должны собраться без проблем. В проекте FliteTTSEngineObj необходимо исправить код, в определении класса и самом классе. Самый простой путь - закоментировать все строки, вызывающие ошибку. В каждом проекте нужно прописать пути к заголовочным файлам, указать библиотеку времени выполнения MT и выходные каталоги для готовых библиотек. Начнем с поекта cmulex, тк он не зависит от других проектов. Идете в свойства проекта cmulex (правый щелчек в Обозревателе), Компилятор(С/С++) - Общие - Дополнительные каталоги включения: ..\..\flite\include; ..\..\flite\lang\usenglish\;..\..\flite\src\synth Это относительные пути, что намного короче и профессиональнее, чем абсолютные пути. ..\ говорит компилятору поднятся на один каталог выше, а ..\..\ - поднятся на два уровня выше. А как узнать, насколько нужно подниматься? Все просто. Каталог этого проекта расположен в flite+hts_engine-1.02.02\sapi\usenglish. Чтобы добраться до файлов включения, нужно подняться на 1 уровень, в каталог flite+hts_engine-1.02.02\sapi, где находятся каталоги всех проектов. Это только проекты, а их заголовочные и исходные файлы расположены в соответствующих подкаталогах в flite+hts_engine-1.02.02. Таким образом, параметр ..\..\flite\include; говорит компилятору поднятся от текущего пути на 2 уровня, зайти в каталог flite, далее зайти в его подкаталог include и искать здесь заголовочные файлы. Теперь параметры Библиотекаря. Общие - Выходной файл - .\Release\usenglish.lib .\ - текущий каталог. Аналогично изменяются параметры остальных проектов. hts_engine_API: Компилятор. ..\..\hts_engine_API\include Линкер. .\Release\$(ProjectName).lib flite: ..\..\include;..\..\flite\include .\Release\flite.lib cmulex: ..\..\include;..\..\flite\include .\Release\cmulex.lib.\Release\cmulex.lib cmu_us_kal: ..\..\flite\include,..\..\flite\lang\cmulex,..\..\flite\lang\usenglish .\Release\cmu_us_kal.lib FliteTTSEngineObj: Компилятор. ..\..\flite\include;..\..\flite\src\utils;..\..\hts_engine_API\include;E:\WinDDK\7600.16385.1\inc\atl71;..\..\include Линкер. Выходной файл .\Release\FliteTTSEngineObj.lib Дополнительные зависимости cmu_us_kal.lib ..\flite\Release\flite.lib ..\hts_engine_API\Release\hts_engine_API.lib Дополнительные каталоги библиотек "..\..\lib";"..\cmu_us_kal\Release";E:\WinDDK\7600.16385.1\lib\ATL\i386 FliteCMUKalDiphone: Компилятор. ..\..\include;..\..\hts_engine_API\include;E:\WinDDK\7600.16385.1\inc\atl71;..\..\flite\include;..\..\flite\lang\cmu_us_kal;..\flitettsengineobj Линкер. Выходной файл .\Release\FliteTTSEngineObj.lib Дополнительные зависимости cmu_us_kal.lib ..\flite\Release\flite.lib ..\hts_engine_API\Release\hts_engine_API.lib Дополнительные каталоги библиотек "..\..\lib";"..\cmu_us_kal\Release";E:\WinDDK\7600.16385.1\lib\ATL\i386 Путь E:\WinDDK\7600.16385.1\lib\ATL\i386 исправьте на свой. |
Сообщ.
#7
,
|
|
|
register-vox:
Компилятор. E:\WinDDK\7600.16385.1\inc\atl71 Компоновщик. ../Release/register-vox.exe E:\WinDDK\7600.16385.1\lib\ATL\i386 Новый драйвер будет читать параметры и путь к файлам голоса из реестра, поэтому начнем с проекта register-vox. Заменим код файла register-vox.сpp на немного исправленный код из проекта SALB, тк лучше алгоритм. Кроме того, запись параметров производится одновременно и в ключ реестра и в его подключ Attributes. Это сильно облегчит извлечение пути во время инициализации sapi-драйвера. После сборки и запуска _register-alan.bat в реестре появится новый ключ HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech\Voices\Tokens\HTS Voice Alan Экспорт ключа в текстовый файл: ![]() ![]() Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech\Voices\Tokens\HTS Voice Alan] "CLSID"="{72CEF72B-809E-4CB4-A63C-ADC7938CFD5B}" @="HTS Voice Alan" "409"="HTS Voice Alan" "Language"="en-us" "Voxpath"="С:\\flite+hts_engine-1.02.02\\sapi2008\\FliteCMUKalDiphone\\Release\\data\\voices\\Alan" "Textrules"="" "Logfile"="С:\\_flite+hts_engine-1.02.02\\sapi2008\\FliteCMUKalDiphone\\Release\\alan.log" [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech\Voices\Tokens\HTS Voice Alan\Attributes] "Gender"="Female" "Name"="HTS Voice Alan" "Language"="409" "Age"="Adult" "Vendor"="FTW" Далее можно переходить к сборке FliteTTSEngineObj. В FliteTTSEngineObj.h добавляется строка #include "flite_hts_engine.h" и коментируется строка flite_init(); --> //flite_init(); В опр. класса после деструктора ~ добавляются новые методы и данные: ![]() ![]() // Flite+HTS methods public: static void Flite_HTS_Engine_create_label(Flite_HTS_Engine * f, cst_item * item, char *label); void Flite_HTS_Engine_initialize(Flite_HTS_Engine * f, HTS_Boolean use_lpf, int sampling_rate, int fperiod, double alpha, int stage, double beta, int audio_buff_size, double uv_threshold, HTS_Boolean use_log_gain, double gv_weight_mgc, double gv_weight_lf0, double gv_weight_lpf); void Flite_HTS_Engine_load_voice(Flite_HTS_Engine * f, char* voice_path); void Flite_HTS_Engine_clear(Flite_HTS_Engine * f); /* ISpObjectWithToken methods */ public: STDMETHODIMP SetObjectToken(ISpObjectToken * pToken); STDMETHODIMP GetObjectToken(ISpObjectToken** ppToken) { return SpGenericGetObjectToken(ppToken, vox_token); } /* ISpTTSEngine methods */ public: STDMETHOD(Speak)(DWORD dwSpeakFlags, REFGUID rguidFormatId, const WAVEFORMATEX * pWaveFormatEx, const SPVTEXTFRAG* pTextFragList, ISpTTSEngineSite* pOutputSite); STDMETHOD(GetOutputFormat)(const GUID * pTargetFormatId, const WAVEFORMATEX * pTargetWaveFormatEx, GUID * pDesiredFormatId, WAVEFORMATEX ** ppCoMemDesiredWaveFormatEx); void get_actions_and_do_them(); HTS_Boolean HTS_Engine_create_gstream2(HTS_Engine * engine); HTS_Boolean HTS_GStreamSet_create2(HTS_GStreamSet * gss, HTS_PStreamSet * pss, int stage, HTS_Boolean use_log_gain, int sampling_rate, int fperiod, double alpha, double beta, HTS_Boolean * stop, double volume, HTS_Audio * audio); /* Implementation stuff */ protected: /* These get set by a subclass's constructor. That's not the proper C++ way to do this, but I do not care. */ cst_voice *(*regfunc)(const char *); void (*unregfunc)(cst_voice *); int (*phonemefunc)(cst_item *); int (*visemefunc)(cst_item *); int (*featurefunc)(cst_item *); cst_val *(*pronouncefunc)(SPPHONEID *); /* SAPI's use of the term "token" is quite unfortunate. */ CComPtr<ISpObjectToken> vox_token; CComPtr<ISpObjectToken> attr_token; cst_voice *curr_vox; /* Synthesis state variables */ cst_utterance *curr_utt; cst_relation *tok_rel; const SPVTEXTFRAG *curr_frag; ISpTTSEngineSite *site; ULONGLONG bcount; int sentence_start; int sentence_skip; int aborted; const char *sztoken; cst_tokenstream *ts; FILE *file_list[num_hts_data_files]; /* engine */ //static Flite_HTS_Engine engine; private: //... static int open_hts_data_files(const char *path,FILE *file_list[num_hts_data_files]); static void close_hts_data_files(FILE *file_list[num_hts_data_files]); В начале FliteTTSEngineObj.cpp после существующих инклудов нужно добавить код ![]() ![]() #include <clocale> #define MAXBUFLEN 1024 #include "config.c" char buff[30]; //for debug output typedef struct { const char *name; const char *open_mode; int is_optional; } hts_data_file_info; static const hts_data_file_info hts_data_list[num_hts_data_files]={ {"dur.pdf","rb",0}, {"tree-dur.inf","r",0}, {"mgc.pdf","rb",0}, {"tree-mgc.inf","r",0}, {"mgc.win1","r",0}, {"mgc.win2","r",0}, {"mgc.win3","r",0}, {"lf0.pdf","rb",0}, {"tree-lf0.inf","r",0}, {"lf0.win1","r",0}, {"lf0.win2","r",0}, {"lf0.win3","r",0}, {"lpf.pdf","rb",0}, {"tree-lpf.inf","r",0}, {"lpf.win1","r",0}, {"gv-mgc.pdf","rb",0}, {"tree-gv-mgc.inf","r",0}, {"gv-lf0.pdf","r",0}, {"tree-gv-lf0.inf","r",0}, {"gv-mgc-lpf.pdf","r",0}, {"tree-gv-lpf.inf","r",0}, {"gv-switch.inf","r",0}, {"voice.params","r",0}, {"voice.info","r",0}}; static cst_val *sapi_tokentowords(cst_item *i); int CFliteTTSEngineObj::open_hts_data_files(const char *path,FILE *file_list[num_hts_data_files]) { int result=1; int i,l; //printf("*****111***************\n\n"); #ifdef WIN32 char sep='\\'; #else char sep='/'; #endif char *full_path; char *name; memset(file_list,0,num_hts_data_files*sizeof(FILE*)); l=strlen(path); if(l==0) return 0; //printf("****222****************\n\n"); full_path=(char *)calloc(l+20,sizeof(char)); if(full_path==NULL) return 0; memcpy(full_path,path,l); if(full_path[l-1]!=sep) { full_path[l]=sep; name=full_path+l+1; } else name=full_path+l; for(i=0;i<num_hts_data_files;i++) { strcpy(name,hts_data_list[i].name); //printf("lib.c filename = %s\n", name); //printf("lib.c full_path = %s\n", full_path); file_list[i]=fopen(full_path,hts_data_list[i].open_mode); //printf("lib.c file_list[%d] = %d\n", i, file_list[i]); if (i<15){ // error if no any file from first 14 files if((file_list[i]==NULL)&&(!hts_data_list[i].is_optional)) { result=0; break; } } } free(full_path); if(!result) close_hts_data_files(file_list); return result; } void CFliteTTSEngineObj::close_hts_data_files(FILE *file_list[num_hts_data_files]) { int i; for(i=0;i<num_hts_data_files;i++) { if(file_list[i]!=NULL) { fclose(file_list[i]); file_list[i]=NULL; } } } typedef struct { int id; HTS_Engine *engine; int is_free; } engine_resource; void CFliteTTSEngineObj::Flite_HTS_Engine_create_label(Flite_HTS_Engine * f, cst_item * item, char *label) { char seg_pp[8]; char seg_p[8]; char seg_c[8]; char seg_n[8]; char seg_nn[8]; char endtone[8]; int sub_phrases = 0; int lisp_total_phrases = 0; int tmp1 = 0; int tmp2 = 0; int tmp3 = 0; int tmp4 = 0; /* load segments */ strcpy(seg_pp, ffeature_string(item, "p.p.name")); strcpy(seg_p, ffeature_string(item, "p.name")); strcpy(seg_c, ffeature_string(item, "name")); strcpy(seg_n, ffeature_string(item, "n.name")); strcpy(seg_nn, ffeature_string(item, "n.n.name")); /* load endtone */ strcpy(endtone, ffeature_string(item, "R:SylStructure.parent.parent.R:Phrase.parent.daughtern.R:SylStructure.daughtern.endtone")); if (strcmp(seg_c, "pau") == 0) { /* for pause */ if (item_next(item) != NULL) { sub_phrases = ffeature_int(item, "n.R:SylStructure.parent.R:Syllable.sub_phrases"); tmp1 = ffeature_int(item, "n.R:SylStructure.parent.parent.R:Phrase.parent.lisp_total_syls"); tmp2 = ffeature_int(item, "n.R:SylStructure.parent.parent.R:Phrase.parent.lisp_total_words"); lisp_total_phrases = ffeature_int(item, "n.R:SylStructure.parent.parent.R:Phrase.parent.lisp_total_phrases"); } else { sub_phrases = ffeature_int(item, "p.R:SylStructure.parent.R:Syllable.sub_phrases"); tmp1 = ffeature_int(item, "p.R:SylStructure.parent.parent.R:Phrase.parent.lisp_total_syls"); tmp2 = ffeature_int(item, "p.R:SylStructure.parent.parent.R:Phrase.parent.lisp_total_words"); lisp_total_phrases = ffeature_int(item, "p.R:SylStructure.parent.parent.R:Phrase.parent.lisp_total_phrases"); } sprintf(label, "%s^%s-%s+%s=%s@x_x/A:%d_%d_%d/B:x-x-x@x-x&x-x#x-x$x-x!x-x;x-x|x/C:%d+%d+%d/D:%s_%d/E:x+x@x+x&x+x#x+x/F:%s_%d/G:%d_%d/H:x=x^%d=%d|%s/I:%d=%d/J:%d+%d-%d", strcmp(seg_pp, "0") == 0 ? "x" : seg_pp, strcmp(seg_p, "0") == 0 ? "x" : seg_p, seg_c, strcmp(seg_n, "0") == 0 ? "x" : seg_n, strcmp(seg_nn, "0") == 0 ? "x" : seg_nn, ffeature_int(item, "p.R:SylStructure.parent.R:Syllable.stress"), ffeature_int(item, "p.R:SylStructure.parent.R:Syllable.accented"), ffeature_int(item, "p.R:SylStructure.parent.R:Syllable.syl_numphones"), ffeature_int(item, "n.R:SylStructure.parent.R:Syllable.stress"), ffeature_int(item, "n.R:SylStructure.parent.R:Syllable.accented"), ffeature_int(item, "n.R:SylStructure.parent.R:Syllable.syl_numphones"), ffeature_string(item, "p.R:SylStructure.parent.parent.R:Word.gpos"), ffeature_int(item, "p.R:SylStructure.parent.parent.R:Word.word_numsyls"), ffeature_string(item, "n.R:SylStructure.parent.parent.R:Word.gpos"), ffeature_int(item, "n.R:SylStructure.parent.parent.R:Word.word_numsyls"), ffeature_int(item, "p.R:SylStructure.parent.parent.R:Phrase.parent.lisp_num_syls_in_phrase"), ffeature_int(item, "p.R:SylStructure.parent.parent.R:Phrase.parent.lisp_num_words_in_phrase"), sub_phrases + 1, lisp_total_phrases - sub_phrases, endtone, ffeature_int(item, "n.R:SylStructure.parent.parent.R:Phrase.parent.lisp_num_syls_in_phrase"), ffeature_int(item, "n.R:SylStructure.parent.parent.R:Phrase.parent.lisp_num_words_in_phrase"), tmp1, tmp2, lisp_total_phrases); } else { /* for no pause */ tmp1 = ffeature_int(item, "R:SylStructure.pos_in_syl"); tmp2 = ffeature_int(item, "R:SylStructure.parent.R:Syllable.syl_numphones"); tmp3 = ffeature_int(item, "R:SylStructure.parent.R:Syllable.pos_in_word"); tmp4 = ffeature_int(item, "R:SylStructure.parent.parent.R:Word.word_numsyls"); sub_phrases = ffeature_int(item, "R:SylStructure.parent.R:Syllable.sub_phrases"); lisp_total_phrases = ffeature_int(item, "R:SylStructure.parent.parent.R:Phrase.parent.lisp_total_phrases"); sprintf(label, "%s^%s-%s+%s=%s@%d_%d/A:%d_%d_%d/B:%d-%d-%d@%d-%d&%d-%d#%d-%d$%d-%d!%d-%d;%d-%d|%s/C:%d+%d+%d/D:%s_%d/E:%s+%d@%d+%d&%d+%d#%d+%d/F:%s_%d/G:%d_%d/H:%d=%d^%d=%d|%s/I:%d=%d/J:%d+%d-%d", strcmp(seg_pp, "0") == 0 ? "x" : seg_pp, strcmp(seg_p, "0") == 0 ? "x" : seg_p, seg_c, strcmp(seg_n, "0") == 0 ? "x" : seg_n, strcmp(seg_nn, "0") == 0 ? "x" : seg_nn, tmp1 + 1, tmp2 - tmp1, ffeature_int(item, "R:SylStructure.parent.R:Syllable.p.stress"), ffeature_int(item, "R:SylStructure.parent.R:Syllable.p.accented"), ffeature_int(item, "R:SylStructure.parent.R:Syllable.p.syl_numphones"), ffeature_int(item, "R:SylStructure.parent.R:Syllable.stress"), ffeature_int(item, "R:SylStructure.parent.R:Syllable.accented"), tmp2, tmp3 + 1, tmp4 - tmp3, ffeature_int(item, "R:SylStructure.parent.R:Syllable.syl_in") + 1, ffeature_int(item, "R:SylStructure.parent.R:Syllable.syl_out") + 1, ffeature_int(item, "R:SylStructure.parent.R:Syllable.ssyl_in") + 1, ffeature_int(item, "R:SylStructure.parent.R:Syllable.ssyl_out") + 1, ffeature_int(item, "R:SylStructure.parent.R:Syllable.asyl_in") + 1, ffeature_int(item, "R:SylStructure.parent.R:Syllable.asyl_out") + 1, ffeature_int(item, "R:SylStructure.parent.R:Syllable.lisp_distance_to_p_stress"), ffeature_int(item, "R:SylStructure.parent.R:Syllable.lisp_distance_to_n_stress"), ffeature_int(item, "R:SylStructure.parent.R:Syllable.lisp_distance_to_p_accent"), ffeature_int(item, "R:SylStructure.parent.R:Syllable.lisp_distance_to_n_accent"), ffeature_string(item, "R:SylStructure.parent.R:Syllable.syl_vowel"), ffeature_int(item, "R:SylStructure.parent.R:Syllable.n.stress"), ffeature_int(item, "R:SylStructure.parent.R:Syllable.n.accented"), ffeature_int(item, "R:SylStructure.parent.R:Syllable.n.syl_numphones"), ffeature_string(item, "R:SylStructure.parent.parent.R:Word.p.gpos"), ffeature_int(item, "R:SylStructure.parent.parent.R:Word.p.word_numsyls"), ffeature_string(item, "R:SylStructure.parent.parent.R:Word.gpos"), tmp4, ffeature_int(item, "R:SylStructure.parent.parent.R:Word.pos_in_phrase") + 1, ffeature_int(item, "R:SylStructure.parent.parent.R:Word.words_out"), ffeature_int(item, "R:SylStructure.parent.parent.R:Word.content_words_in") + 1, ffeature_int(item, "R:SylStructure.parent.parent.R:Word.content_words_out") + 1, ffeature_int(item, "R:SylStructure.parent.parent.R:Word.lisp_distance_to_p_content"), ffeature_int(item, "R:SylStructure.parent.parent.R:Word.lisp_distance_to_n_content"), ffeature_string(item, "R:SylStructure.parent.parent.R:Word.n.gpos"), ffeature_int(item, "R:SylStructure.parent.parent.R:Word.n.word_numsyls"), ffeature_int(item, "R:SylStructure.parent.parent.R:Phrase.parent.p.lisp_num_syls_in_phrase"), ffeature_int(item, "R:SylStructure.parent.parent.R:Phrase.parent.p.lisp_num_words_in_phrase"), ffeature_int(item, "R:SylStructure.parent.parent.R:Phrase.parent.lisp_num_syls_in_phrase"), ffeature_int(item, "R:SylStructure.parent.parent.R:Phrase.parent.lisp_num_words_in_phrase"), sub_phrases + 1, lisp_total_phrases - sub_phrases, strcmp(endtone, "0") == 0 ? "NONE" : endtone, ffeature_int(item, "R:SylStructure.parent.parent.R:Phrase.parent.n.lisp_num_syls_in_phrase"), ffeature_int(item, "R:SylStructure.parent.parent.R:Phrase.parent.n.lisp_num_words_in_phrase"), ffeature_int(item, "R:SylStructure.parent.parent.R:Phrase.parent.lisp_total_syls"), ffeature_int(item, "R:SylStructure.parent.parent.R:Phrase.parent.lisp_total_words"), lisp_total_phrases); } } Парметр "Voxpath" читается в функции ![]() ![]() STDMETHODIMP CFliteTTSEngineObj::SetObjectToken(ISpObjectToken* pToken) { USES_CONVERSION; CSpDynamicString voxpath; char *avoxpath; HRESULT hr; const cst_val *ttwv; if (!SUCCEEDED(hr = SpGenericSetObjectToken(pToken, vox_token))) return hr; if (!SUCCEEDED(vox_token->GetStringValue(L"Voxpath", &voxpath))) avoxpath = NULL; /* It isn't always necessary */ else avoxpath = W2A(voxpath); //Unicode to Ansi if (avoxpath==NULL){ MessageBoxA(0,(char*)"Не указан каталог голоса в реестре (Voxpath). Переустановите программу.",0,0); return E_INVALIDARG; } //MessageBoxW(0,voxpath,0,0); /* initialize */ if (curr_vox){ (*unregfunc)(curr_vox); } if ((curr_vox = (*regfunc)(avoxpath)) == NULL) return E_INVALIDARG; /* or something */ // otherwise darts out mistake after select of other voice in SAPI5 TTSAPP if (curr_vox){ (*unregfunc)(curr_vox); } if ((curr_vox = (*regfunc)(avoxpath)) == NULL) return E_INVALIDARG; // or something /* initialize */ if (engine.engine.global.sampling_rate>0){ HTS_Engine_refresh(&engine.engine); Flite_HTS_Engine_clear(&engine); } Flite_HTS_Engine_initialize(&engine, use_lpf, sampling_rate, fperiod, alpha, stage, beta, audio_buff_size, uv_threshold, use_log_gain, gv_weight_mgc, gv_weight_lf0, gv_weight_lpf); //*/ /* load */ Flite_HTS_Engine_load_voice(&engine, avoxpath); // Здесь для проверки работы можно добавить код для синтеза речи по любой англ. текстовой строке //if ((ttwv = feat_val(curr_vox->features, "tokentowords_func"))) { // feat_set(curr_vox->features, "old_tokentowords_func", ttwv); // feat_set(curr_vox->features, "tokentowords_func", // itemfunc_val(sapi_tokentowords)); //} return hr; } Если все сделано правильно, то можно проверить работу драйвера в апплете Речь Панели управления Windows. Синтез нельзя будет остановить кнопкой Стоп, нельзя поменять темп речи, но можно закрыть окно и подождать пока закончится синтезированная речь. Далее постараюсь описать назначение некоторых функций старого драйвера, обработку событий от интерфейсных элементов в новом драйвере. Прикреплённый файл ![]() |
Сообщ.
#8
,
|
|
|
Шаг 3
Краткий обзор драйвера Flite. Функции приводятся в порядке их вызова. Файл FliteTTSEngineObj.cpp STDMETHODIMP CFliteTTSEngineObj::SetObjectToken(ISpObjectToken* pToken) - Инициализация драйвера: чтение параметров реестра, инициализация структур вокодера и структуры текущего высказывания. STDMETHODIMP CFliteTTSEngineObj::GetOutputFormat(const GUID * pTargetFormatId, const WAVEFORMATEX * pTargetWaveFormatEx, GUID * pDesiredFormatId, WAVEFORMATEX ** ppCoMemDesiredWaveFormatEx) - функция инициализации структуры WAVEFORMATEX, определяющей формат аудиоданных. STDMETHODIMP CFliteTTSEngineObj::Speak(DWORD dwSpeakFlags, REFGUID rguidFormatId, const WAVEFORMATEX * pWaveFormatEx, const SPVTEXTFRAG* pTextFragList, ISpTTSEngineSite* pOutputSite) - основная функция, содержащая цикл обработки событий драйвера: SPVA_Speak, SPVA_SpellOut, SPVA_Pronounce, SPVA_Silence, SPVA_Bookmark, а также цикл обрабоки событий, поступивших от пользователя. Эти события обрабатываются функциями: speak_frag() - создание потока токенов (слов) и синтез речи по полученному тексту (фрагу); spell_frag() - создание потока токенов(букв) для синтеза по буквам; pronounce_frag() - создание потока токенов(фонем) для синтеза по фонемам; silence_frag() - записать паузу в выходной звуковой поток, длит-ть паузы задается в теге, в мсек; set_bookmark() - создание потока токенов(закладок) для синтеза по закладкам; get_actions_and_do_them - функция с циклом обработки событий от интерфейсных элементах (действиях пользователя): SPVES_ABORT, SPVST_SENTENCE, SPVES_RATE, SPVES_VOLUME. Два последних события возникают при изменении пользователем темпа речи и громкости соответственно. Здесь, Фраг - весь текст, переданный из приложения: предложение или текст из набора предложений. speak_frag() - функция для создания потока токенов полученного текста и синтеза речи в цикле по токенам. Я особо не вникал, как складываются потоки токенов, но синтезируются они с помощью этой функции. Здесь же устанавливается значения позиции токена в тексте и его длина и обрабатываются действия пользователя (снова вызывается get_actions_and_do_them()). Далее эти значения копируются в структуру Высказывания curr_utt и заполняются остальные параметры Высказывания. Синтез заполненной структуры для текущего токена происходит в synth_one_utt(); synth_one_utt() - функция синтеза речи по заполненной структуре текущего Высказывания; Пока следует отметить, что в новом драйвере можно и нужно исключить функцию synth_one_utt(), а синтез функциями HTS_engine сделать непосредственно в speak_frag(). Однако очистка потоков движка занимает много времени и приводит к задержкам между произносимыми токенами (словами) в 1-3 сек. В конечном варианте, пришлось отказаться от цикла формирования потока токенов и их раздельного синтеза и вернуться к циклу создания меток и их синтеза. Действия пользователя можно обрабатывать в цикле синтеза меток функцией get_actions_and_do_them() или кодом из нее. Темп и громкость речи регулируется путем установки 2-х переменных, объявленных в классе. Их значения обрабатыаются непосредственно перед синтезом меток. Сложнее всего описать алгоритм формирования событий для синхронизации подстветки проговариваемых слов. |
Сообщ.
#9
,
|
|
|
Вариант кода синтеза речи и обработки действий пользователя
![]() ![]() /* HTS_GStreamSet_create: generate speech */ /* (stream[0] == spectrum && stream[1] == lf0) */ HTS_Boolean CFliteTTSEngineObj::HTS_GStreamSet_create2(HTS_GStreamSet * gss, HTS_PStreamSet * pss, int stage, HTS_Boolean use_log_gain, int sampling_rate, int fperiod, double alpha, double beta, HTS_Boolean * stop, double volume, HTS_Audio * audio) { //... int lab_duration = 0; int nstate = engine.engine.sss.nstate; //=5 for (int j=0; j<nstate; j++){ lab_duration = lab_duration + engine.engine.sss.duration[i*nstate+j]; } //itoa( lab_duration, buff, 10); // MessageBoxA(0,buff,(char*)"lab_duration",0); //*** for (j = 0; j < lab_duration && (*stop) == FALSE; j++){ if (gss->nstream >= 3) lpf = &gss->gstream[2].par[i][0]; get_actions_and_do_them(); if (aborted) goto stop_synth; if (sentence_skip == 0) { //send_sentence_event(ts->token_pos // + curr_frag->ulTextSrcOffset); HTS_Vocoder_synthesize(&v, gss->gstream[0].static_length - 1, gss->gstream[1].par[k][0], &gss->gstream[0].par[k][0], nlpf, lpf, alpha, beta, volume, &gss->gspeech[k * fperiod], audio); k++; // next frame } else { --sentence_skip; site->CompleteSkip(1); } } // end for on lab_duration //... } Алах Акбар! |
Сообщ.
#10
,
|
|
|
Хотя я писал драйвер под тибетские мантры, Аллах больше Тибета.
Аллах Велик, поэтому кому-то в Аллахе это обязательно пригодится. Шаг 4 Если вы сделали все правильно, то ваш драйвер должен проговаривать английский текст любым зарегистрированным английским голосом. Но остался один баг, который проявляется только при смене голоса в аплете Речь Панели управления и приводит к ошибке приложения. Я нашел один способ пофиксить его, но предлагаю вам самостоятельно найти более правильное решение. Теперь осталось доделать синхронное подсветку проговариваемого текста. Хотя Майкрософт предлагает специальное приложение TTSApp.exe, будем проверять драйвер в том же апплете Речь. Отмечу, что приложение TTSApp.exe позволяет выбрать голос, проговорить любой текст, сохранить синтезированную речь в файл, вывести события TTS в спец. окне, менять значение темпа речи и громкости. Для лучшего понимания работы sapi-драйвера лучше почитать статьи, посвященные SAPI-интерфейсу. В одной замечательной статье (сохраненной где-то на жестком диске) описана модель событий драйвера, хотя с первого раза не совсем ясно, как добавленные события подсвечивают нужные слова. Просто следует запомнить, что нужно правильно добавить события, а драйвер сам асинхронно в удобное время обработает весь набор добавленных событий. Механизм добавления и обработки событий будем проверять в функции speak_frag. Здесь я привожу измененный вариант функции. ![]() ![]() void CFliteTTSEngineObj::speak_frag() { //cst_tokenstream *ts; // global now const char *token; // global now cst_item *t; char *text; size_t len; int i; cst_item *s = NULL; char **label_data = NULL; int label_size = 0; setlocale(LC_CTYPE,(const char*)""); len = wcstombs(NULL, curr_frag->pTextStart, curr_frag->ulTextLen); text = cst_alloc(char, len+1); wcstombs(text, curr_frag->pTextStart, curr_frag->ulTextLen); //MessageBoxA(0,text,(char*)"text",0); //*** ////v 1 utt_set_input_text(curr_utt,text); ts = my_ts_open_string(curr_utt, text); //cst_free(text); // move to func. end while (!ts_eof(ts)) { sztoken = ts_get(ts); t = append_new_token(ts, token, tok_rel); //item_set_int(t, "token_pos", // ts->token_pos + curr_frag->ulTextSrcOffset); item_set_int(t, "token_pos", curr_frag->ulTextSrcOffset); item_set_int(t, "token_length", strlen(sztoken)); set_local_prosody(t, &curr_frag->State); // itoa(item_feat_int(t, "token_pos") + curr_frag->ulTextSrcOffset, buff, 10); //если не устанавливалась, то сброс программы //MessageBoxA(0,buff,(char*)"token_length",0); //*** SPEVENT evt; SpClearEvent(&evt); evt.eEventId = SPEI_WORD_BOUNDARY; evt.elParamType = SPET_LPARAM_IS_UNDEFINED; evt.ullAudioStreamOffset = 0; //bcount + offset; evt.wParam = strlen(sztoken); evt.lParam = ts->token_pos + curr_frag->ulTextSrcOffset; site->AddEvents(&evt, 1); //Sleep(1000); if (relation_head(tok_rel)) { //if (relation_head(tok_rel) && utt_break(ts, token, tok_rel)) { if (aborted) goto pod_bay_doors; if (sentence_skip == 0) { //send_sentence_event(ts->token_pos // + curr_frag->ulTextSrcOffset); //MessageBoxA(0,(char*)"need_synth_one_utt",0,0); //*** //synth_one_utt(); //start_new_utt(); } else { --sentence_skip; site->CompleteSkip(1); //start_new_utt(); } } //itoa(ts->token_pos + curr_frag->ulTextSrcOffset, buff, 10); //MessageBoxA(0,buff,token,0); //*** } pod_bay_doors: /* Open the POD bay doors, KAL... */ cst_free(text); delete_tokenstream(ts); //<---- delete_utterance(curr_utt); curr_utt = NULL; tok_rel = NULL; return; } Следует отметить, что поток токенов создается и работает независимо от функции высказывания utt и подключается к ней в случае необходимости. Если вы соберете и проверите код, то не заметите никаких изменений, хотя драйвер правильно обрабатывает события и подсвечивает текст. Для того, чтобы заметить подсветку, необходимо ввести временную задержку, например в 1 сек. Для этого раскоментируйте строку //Sleep(1000); и снова проверьте его работу. драйвера. Теперь видно, что подсветка работает, но текст не проговаривается. В конечном варианте я заменил этот цикл на типичный код синтеза текста из flite+hts. Здесь привожу почти законченный код. ![]() ![]() void CFliteTTSEngineObj::speak_frag() { //cst_tokenstream *ts; // global now const char *token; // global now cst_item *t; char *text; size_t len; int i; cst_item *s = NULL; char **label_data = NULL; int label_size = 0; setlocale(LC_CTYPE,(const char*)""); len = wcstombs(NULL, curr_frag->pTextStart, curr_frag->ulTextLen); text = cst_alloc(char, len+1); wcstombs(text, curr_frag->pTextStart, curr_frag->ulTextLen); //MessageBoxA(0,text,(char*)"text",0); //*** ////v 1 utt_set_input_text(curr_utt,text); // flite_do_synth(curr_utt, curr_vox, utt_synth); ts = my_ts_open_string(curr_utt, text); if (curr_utt == NULL) return; for (s = relation_head(utt_relation(curr_utt, "Segment")); s; s = item_next(s)) label_size++; if (label_size <= 0) return; label_data = (char **) calloc(label_size, sizeof(char *)); for (i = 0, s = relation_head(utt_relation(curr_utt, "Segment")); s; s = item_next(s), i++) { label_data[i] = (char *) calloc(MAXBUFLEN, sizeof(char)); Flite_HTS_Engine_create_label(&engine, s, label_data[i]); } if (phoneme_alignment) // modify label HTS_Label_set_frame_specified_flag(&engine.engine.label, TRUE); if (speech_speed != 1.0) // modify label HTS_Label_set_speech_speed(&engine.engine.label, speech_speed); if (volume!=100) // set volume hts_volume = volume*0.01; //hts_volume = hts_volume/100; //err--->hts_volume=0 HTS_Engine_set_volume(&engine.engine, hts_volume ); // speech synthesis part HTS_Engine_load_label_from_string_list(&engine.engine, label_data, label_size); HTS_Engine_create_sstream(&engine.engine); HTS_Engine_create_pstream(&engine.engine); HTS_Engine_create_gstream(&engine.engine); pod_bay_doors: /* Open the POD bay doors, KAL... */ HTS_Engine_refresh(&engine.engine); for (i = 0; i < label_size; i++) free(label_data[i]); free(label_data); cst_free(text); delete_tokenstream(ts); //<---- delete_utterance(curr_utt); curr_utt = NULL; tok_rel = NULL; return; } Обработку событий от интефейса и подсветку текста можно выполнить только в функции HTS_Engine_create_gstream(&engine.engine); Для этого потребуется объявить ее в классе и внести в код сооответстующие изменения. |
Сообщ.
#11
,
|
|
|
Несколько слов о регулировке темпа речи.
Если перенести таблицу преобразования из исходного кода Flite, то регулятор будет работать с точностью до наоборот. При движении влево темп речи будет увеличиваться, при движении вправо - уменьшаться. Самое простое решение, которое приходит в голову, это взять обратную величину от значения в таблице и отправить его в движок flite+hts. Но такое решение не оптимально, тк график функции 1/x сильно отличается от прямой линии. Поэтому лучшим вариантом будет испавить таблицу вручную, те перевернуть числовой ряд на 180 градусов. После замены проверка регулятора показала, что темп речи изменяется в более широком диапазоне, чем необходимо на практике. Уменьшаем шаг приращения темпа речи на меньшее значение и методом подбора получаем окончательный вариант таблицы. ![]() ![]() /* Each step is prev +(1/20) */ static const double sapi_ratetab_foo[21] = { 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95, 1.0, 1.05, 1.1, 1.15, 1.2, 1.25, 1.3, 1.35, 1.4, 1.45, 1.5 }; static const double *sapi_ratetab = sapi_ratetab_foo + 10; Функции преобразования значения регулятора в значение темпа речи: ![]() ![]() static double convert_sapi_rate(int r) { if (r < -10) r = -10; else if (r > 10) r = 10; return sapi_ratetab[r]; } Таблица static const double sapi_pitchtab_foo[21] для преобразования pitch используется без изменения. Далее понадобится дудочка как фильме "Прометей" и похоже, что Сирианская. |
Сообщ.
#12
,
|
|
|
Шаг 5
Следует исключить внесение каких-либо изменений в библиотеки сторонних разработчиков, а вместо этого переносить изменяемые функции в код своего проекта. Поэтому для обработки действий пользователя и подсветки текста одновременно с синтезом речи следует исправить код функции speak_frag(), те заменить строку HTS_Engine_create_gstream(&engine.engine); на HTS_Engine_create_gstream2(&engine.engine); // <--- синтез речи и обработка событий. Объявляем функцию HTS_Engine_create_gstream2 в секции public класса CFliteTTSEngineObj и копируем код функци HTS_Engine_create_gstream из hts_engine_API в файл FliteTTSEngineObj.cpp с заменой имени на новое HTS_Engine_create_gstream2. Эта функция ничего не делает кроме вызова др. функцию HTS_GStreamSet_create, поэтому также меняем ее имя на HTS_GStreamSet_create2. Далее аналогично объявляем HTS_GStreamSet_create2 в секции public класса CFliteTTSEngineObj, копируем ее код из hts_engine_API в файл FliteTTSEngineObj.cpp и добавляем в конце имени цифру 2 ![]() ![]() /* HTS_Engine_create_gstream: synthesis speech */ HTS_Boolean CFliteTTSEngineObj::HTS_Engine_create_gstream2(HTS_Engine * engine) { return HTS_GStreamSet_create2(&engine->gss, &engine->pss, engine->global.stage, engine->global.use_log_gain, engine->global.sampling_rate, engine->global.fperiod,engine->global.alpha, engine->global.beta, &engine->global.stop, engine->global.volume, engine->global.audio_buff_size > 0 ? &engine->audio : NULL); } ![]() ![]() /* HTS_GStreamSet_create: generate speech */ /* (stream[0] == spectrum && stream[1] == lf0) */ HTS_Boolean CFliteTTSEngineObj::HTS_GStreamSet_create2(HTS_GStreamSet * gss, HTS_PStreamSet * pss, int stage, HTS_Boolean use_log_gain, int sampling_rate, int fperiod, double alpha, double beta, HTS_Boolean * stop, double volume, HTS_Audio * audio) { int i, j, k; int msd_frame; HTS_Vocoder v; int nlpf = 0; double *lpf = NULL; /* check */ if (gss->gstream || gss->gspeech) { //HTS_error(1, "HTS_GStreamSet_create: HTS_GStreamSet is not initialized.\n"); MessageBoxW(0, L"HTS_GStreamSet_create: HTS_GStreamSet is not initialized.\n",L"HTS_Error",0); return FALSE; } /* initialize */ gss->nstream = HTS_PStreamSet_get_nstream(pss); gss->total_frame = HTS_PStreamSet_get_total_frame(pss); gss->total_nsample = fperiod * gss->total_frame; gss->gstream = (HTS_GStream *) HTS_calloc(gss->nstream, sizeof(HTS_GStream)); for (i = 0; i < gss->nstream; i++) { gss->gstream[i].static_length = HTS_PStreamSet_get_static_length(pss, i); gss->gstream[i].par = (double **) HTS_calloc(gss->total_frame, sizeof(double *)); for (j = 0; j < gss->total_frame; j++) gss->gstream[i].par[j] = (double *) HTS_calloc(gss->gstream[i].static_length, sizeof(double)); } gss->gspeech = (short *) HTS_calloc(gss->total_nsample, sizeof(short)); /* copy generated parameter */ for (i = 0; i < gss->nstream; i++) { if (HTS_PStreamSet_is_msd(pss, i)) { /* for MSD */ for (j = 0, msd_frame = 0; j < gss->total_frame; j++) if (HTS_PStreamSet_get_msd_flag(pss, i, j)) { for (k = 0; k < gss->gstream[i].static_length; k++) gss->gstream[i].par[j][k] = HTS_PStreamSet_get_parameter(pss, i, msd_frame, k); msd_frame++; } else for (k = 0; k < gss->gstream[i].static_length; k++) gss->gstream[i].par[j][k] = LZERO; } else { /* for non MSD */ for (j = 0; j < gss->total_frame; j++) for (k = 0; k < gss->gstream[i].static_length; k++) gss->gstream[i].par[j][k] = HTS_PStreamSet_get_parameter(pss, i, j, k); } } /* check */ if (gss->nstream != 2 && gss->nstream != 3) { //HTS_error(1, "HTS_GStreamSet_create: The number of streams should be 2 or 3.\n"); MessageBoxW(0, L"HTS_GStreamSet_create: The number of streams should be 2 or 3.\n",L"HTS_Error",0); HTS_GStreamSet_clear(gss); return FALSE; } if (HTS_PStreamSet_get_static_length(pss, 1) != 1) { //HTS_error(1, "HTS_GStreamSet_create: The size of lf0 static vector should be 1.\n"); MessageBoxW(0, L"HTS_GStreamSet_create: The size of lf0 static vector should be 1.\n",L"HTS_Error",0); HTS_GStreamSet_clear(gss); return FALSE; } if (gss->nstream >= 3 && gss->gstream[2].static_length % 2 == 0) { //HTS_error(1, "HTS_GStreamSet_create: The number of low-pass filter coefficient should be odd numbers."); MessageBoxW(0, L"HTS_GStreamSet_create: The number of low-pass filter coefficient should be odd numbers.", L"HTS_Error",0); HTS_GStreamSet_clear(gss); return FALSE; } /* synthesize speech waveform */ HTS_Vocoder_initialize(&v, gss->gstream[0].static_length - 1, stage, use_log_gain, sampling_rate, fperiod); if (gss->nstream >= 3) nlpf = (gss->gstream[2].static_length - 1) / 2; cst_item *s = NULL; int sps; sps = get_param_int(curr_vox->features, "sample_rate", sampling_rate); //16000 // цикл синтеза по меткам for (i = 0, k = 0, s = relation_head(utt_relation(curr_utt, "Segment")); s; s = item_next(s), i++) { //k-номер фрейма int offset; SPEVENT evt; cst_item *token; //const cst_val *bmark; offset = (int) (ffeature_float(s, "p.end") * sps * sizeof(short)); for (int j=0; j<nstate; j++){ if (gss->nstream >= 3) lpf = &gss->gstream[2].par[i][0]; get_actions_and_do_them(); if (aborted) goto stop_synth; if (sentence_skip == 0) { //send_sentence_event(ts->token_pos // + curr_frag->ulTextSrcOffset); HTS_Vocoder_synthesize(&v, gss->gstream[0].static_length - 1, gss->gstream[1].par[k][0], &gss->gstream[0].par[k][0], nlpf, lpf, alpha, beta, volume, &gss->gspeech[k * fperiod], audio); k++; // next frame } else { --sentence_skip; site->CompleteSkip(1); } } // end for on item stop_synth: HTS_Vocoder_clear(&v); if (audio) HTS_Audio_flush(audio); return TRUE; } Последняя функция тянет за собой HTS_calloc(). Копируем ее в файл FliteTTSEngineObj.cpp. ![]() ![]() // snarfed from HTS_Engine /* HTS_calloc: wrapper for calloc */ char *HTS_calloc(const size_t num, const size_t size) { #ifdef FESTIVAL char *mem = (char *) safe_wcalloc(num * size); #else char *mem = (char *) calloc(num, size); #endif /* FESTIVAL */ if (mem == NULL) MessageBoxW(0, L"HTS_calloc: Cannot allocate memory.\n",L"HTS_Error",0); return mem; } Если собрать проект, то драйвер может обрабатывать события от элементов интерфейса одновременно с синтезом речи. Осталось добавить подсветку произносимого текста. Для этого необходимо организовать цикл по токенам, в котором будем определять общую длительность метки в фреймах, а затем синтезировать речь по этим фреймам в цикле по длительности. ![]() ![]() for (i = 0, k = 0, s = relation_head(utt_relation(curr_utt, "Segment")); s; s = item_next(s), i++) { //k-номер фрейма //... } Каждая метка имеет 5 состояний с разной длительностью (в фреймах), дающих при суммирровании общую длительность фонемы в фреймах. ![]() ![]() // цикл синтеза по меткам // необходимо определить общую длительность метки в фреймах // и синтезировать речь по этим фреймам, тоже в цикле по длительности for (i = 0, k = 0, s = relation_head(utt_relation(curr_utt, "Segment")); s; s = item_next(s), i++) { //k-номер фрейма // get label duration int lab_duration = 0; int nstate = engine.engine.sss.nstate; //=5 for (int j=0; j<nstate; j++){ lab_duration = lab_duration + engine.engine.sss.duration[i*nstate+j]; } //itoa( lab_duration, buff, 10); // MessageBoxA(0,buff,(char*)"lab_duration",0); //*** for (j = 0; j < lab_duration && (*stop) == FALSE; j++){ // синтез речи } // подсветка токена } Законченный вариант функции: ![]() ![]() /* HTS_GStreamSet_create: generate speech */ /* (stream[0] == spectrum && stream[1] == lf0) */ HTS_Boolean CFliteTTSEngineObj::HTS_GStreamSet_create2(HTS_GStreamSet * gss, HTS_PStreamSet * pss, int stage, HTS_Boolean use_log_gain, int sampling_rate, int fperiod, double alpha, double beta, HTS_Boolean * stop, double volume, HTS_Audio * audio) { int i, j, k; int msd_frame; HTS_Vocoder v; int nlpf = 0; double *lpf = NULL; /* check */ if (gss->gstream || gss->gspeech) { //HTS_error(1, "HTS_GStreamSet_create: HTS_GStreamSet is not initialized.\n"); MessageBoxW(0, L"HTS_GStreamSet_create: HTS_GStreamSet is not initialized.\n",L"HTS_Error",0); return FALSE; } /* initialize */ gss->nstream = HTS_PStreamSet_get_nstream(pss); gss->total_frame = HTS_PStreamSet_get_total_frame(pss); gss->total_nsample = fperiod * gss->total_frame; gss->gstream = (HTS_GStream *) HTS_calloc(gss->nstream, sizeof(HTS_GStream)); for (i = 0; i < gss->nstream; i++) { gss->gstream[i].static_length = HTS_PStreamSet_get_static_length(pss, i); gss->gstream[i].par = (double **) HTS_calloc(gss->total_frame, sizeof(double *)); for (j = 0; j < gss->total_frame; j++) gss->gstream[i].par[j] = (double *) HTS_calloc(gss->gstream[i].static_length, sizeof(double)); } gss->gspeech = (short *) HTS_calloc(gss->total_nsample, sizeof(short)); /* copy generated parameter */ for (i = 0; i < gss->nstream; i++) { if (HTS_PStreamSet_is_msd(pss, i)) { /* for MSD */ for (j = 0, msd_frame = 0; j < gss->total_frame; j++) if (HTS_PStreamSet_get_msd_flag(pss, i, j)) { for (k = 0; k < gss->gstream[i].static_length; k++) gss->gstream[i].par[j][k] = HTS_PStreamSet_get_parameter(pss, i, msd_frame, k); msd_frame++; } else for (k = 0; k < gss->gstream[i].static_length; k++) gss->gstream[i].par[j][k] = LZERO; } else { /* for non MSD */ for (j = 0; j < gss->total_frame; j++) for (k = 0; k < gss->gstream[i].static_length; k++) gss->gstream[i].par[j][k] = HTS_PStreamSet_get_parameter(pss, i, j, k); } } /* check */ if (gss->nstream != 2 && gss->nstream != 3) { //HTS_error(1, "HTS_GStreamSet_create: The number of streams should be 2 or 3.\n"); MessageBoxW(0, L"HTS_GStreamSet_create: The number of streams should be 2 or 3.\n",L"HTS_Error",0); HTS_GStreamSet_clear(gss); return FALSE; } if (HTS_PStreamSet_get_static_length(pss, 1) != 1) { //HTS_error(1, "HTS_GStreamSet_create: The size of lf0 static vector should be 1.\n"); MessageBoxW(0, L"HTS_GStreamSet_create: The size of lf0 static vector should be 1.\n",L"HTS_Error",0); HTS_GStreamSet_clear(gss); return FALSE; } if (gss->nstream >= 3 && gss->gstream[2].static_length % 2 == 0) { //HTS_error(1, "HTS_GStreamSet_create: The number of low-pass filter coefficient should be odd numbers."); MessageBoxW(0, L"HTS_GStreamSet_create: The number of low-pass filter coefficient should be odd numbers.", L"HTS_Error",0); HTS_GStreamSet_clear(gss); return FALSE; } /* synthesize speech waveform */ HTS_Vocoder_initialize(&v, gss->gstream[0].static_length - 1, stage, use_log_gain, sampling_rate, fperiod); if (gss->nstream >= 3) nlpf = (gss->gstream[2].static_length - 1) / 2; cst_item *s = NULL; int sps; sps = get_param_int(curr_vox->features, "sample_rate", sampling_rate); //16000 //ts = my_ts_open_string(curr_utt, text); // цикл синтеза по меткам // необходимо определить общую длительность метки в фреймах // и синтезировать речь по этим фреймам, тоже в цикле по длительности for (i = 0, k = 0, s = relation_head(utt_relation(curr_utt, "Segment")); s; s = item_next(s), i++) { //k-номер фрейма int offset; SPEVENT evt; cst_item *token; //const cst_val *bmark; offset = (int) (ffeature_float(s, "p.end") * sps * sizeof(short)); token = path_to_item(s, "R:SylStructure.parent.parent.R:Token.parent"); // get label duration int lab_duration = 0; int nstate = engine.engine.sss.nstate; //=5 for (int j=0; j<nstate; j++){ lab_duration = lab_duration + engine.engine.sss.duration[i*nstate+j]; } //itoa( lab_duration, buff, 10); // MessageBoxA(0,buff,(char*)"lab_duration",0); //*** for (j = 0; j < lab_duration && (*stop) == FALSE; j++){ if (gss->nstream >= 3) lpf = &gss->gstream[2].par[i][0]; get_actions_and_do_them(); if (aborted) goto stop_synth; if (sentence_skip == 0) { //send_sentence_event(ts->token_pos // + curr_frag->ulTextSrcOffset); HTS_Vocoder_synthesize(&v, gss->gstream[0].static_length - 1, gss->gstream[1].par[k][0], &gss->gstream[0].par[k][0], nlpf, lpf, alpha, beta, volume, &gss->gspeech[k * fperiod], audio); k++; // next frame } else { --sentence_skip; site->CompleteSkip(1); } } // end for on lab_duration /* Word boundaries */ if (token && item_parent(item_as(s, "SylStructure")) && item_prev(item_as(s, "SylStructure")) == NULL && item_prev(item_parent(item_as(s, "SylStructure"))) == NULL && (token != path_to_item(s, "R:SylStructure.parent.parent.p." "R:Token.parent"))){ //MessageBoxA(0,label_data[i],0,0); //*** //MessageBoxA(0,item_feat_string(token, "name"),(char*)"token_name",0); sztoken = ts_get(ts); //item_set_int(token, "token_pos", ts->token_pos); //item_set_int(token, "token_length", strlen(sztoken)); //itoa(item_feat_int(token, "token_pos") + curr_frag->ulTextSrcOffset, buff, 10); //MessageBoxA(0,buff,(char*)"token_length",0); //*** //itoa( ts->token_pos, buff, 10); //MessageBoxA(0,buff,(char*)"token_pos",0); //*** if (sampling_rate==16000) Sleep(210); //synchronizing, need thread :) SpClearEvent(&evt); evt.eEventId = SPEI_WORD_BOUNDARY; evt.elParamType = SPET_LPARAM_IS_UNDEFINED; evt.ullAudioStreamOffset = 0; //bcount + offset; evt.wParam = strlen(item_feat_string(token, "name")); //item_feat_int(token, "token_length"); evt.lParam = ts->token_pos; //item_feat_int(token, "token_pos"); site->AddEvents(&evt, 1); } } // end for on item stop_synth: HTS_Vocoder_clear(&v); if (audio) HTS_Audio_flush(audio); return TRUE; } Добавлено if (sampling_rate==16000) Sleep(210); //synchronizing, need thread ![]() Без этой задержки для 16кГц-вых голосов проскакиваются первые 2 токена (слова). Для 48кГц задержка не нужна. Предлагаю самостоятельно устранить баг при смене голосов. Подсказка: при выборе голоса др. TTS ошибки нет, ошибка возникает при последовательной смене HTS-голосов. |
Сообщ.
#13
,
|
|
|
Недавно нашлось немного времени на синтезатор. Лучший вариант для Windows https://github.com/m-toman/SALB + русская лексика из RHVoice.
На данный момент английские и немецкие голоса и воспроизводятся также как в оригинальном проекте, для русских голосов только метки, тк нет голосов в новом формате. Метки можно прослушать в hts_engine. Новый формат пдф-файлов сильно отличается от предыдущих версий. На создание утилит конвертации форматов потребуется время и программисты. Ссылка для скачивания бетта-версии с интегрированной русской лексикой: salb_tts Постлексика и интонация осталась от англ. языка, поэтому речь с акцентом. Для исследователей cmu_us_arctic_slt.htsvoice, открываете в винхексе, копируете в текстовый редактор, открываете в вордпале или VS2012. Можем видеть, что голос с GV для кепстральных коэффициентов и основного тона: USE_GV[MCP]:1 USE_GV[LF0]:1 USE_GV[LPF]:0 Их можно отключить, изменив 1 на 0 в винхексе. Дальше можете проверить синтез голоса. Дальше вырезать эти файлы из файла голоса. Будет работать. Формат всех файлов, кроме пдф-ок совпадает с файлами модели голоса RHVoice. ![]() ![]() [GLOBAL] HTS_VOICE_VERSION:1.0 SAMPLING_FREQUENCY:48000 FRAME_PERIOD:240 NUM_STATES:5 NUM_STREAMS:3 STREAM_TYPE:MCP,LF0,LPF FULLCONTEXT_FORMAT:HTS_TTS_ENG FULLCONTEXT_VERSION:1.0 GV_OFF_CONTEXT:"*-pau+*","*-h#+*","*-brth+*" COMMENT: [STREAM] VECTOR_LENGTH[MCP]:35 VECTOR_LENGTH[LF0]:1 VECTOR_LENGTH[LPF]:31 IS_MSD[MCP]:0 IS_MSD[LF0]:1 IS_MSD[LPF]:0 NUM_WINDOWS[MCP]:3 NUM_WINDOWS[LF0]:3 NUM_WINDOWS[LPF]:1 USE_GV[MCP]:1 USE_GV[LF0]:1 USE_GV[LPF]:0 OPTION[MCP]:ALPHA=0.55 OPTION[LF0]: OPTION[LPF]: [POSITION] DURATION_PDF:0-22203 DURATION_TREE:22204-91164 STREAM_WIN[MCP]:91165-91170,91171-91185,91186-91200 STREAM_WIN[LF0]:91201-91206,91207-91221,91222-91236 STREAM_WIN[LPF]:91237-91242 STREAM_PDF[MCP]:91243-1141262 STREAM_PDF[LF0]:1141263-1241550 STREAM_PDF[LPF]:1241551-1242810 STREAM_TREE[MCP]:1242811-1373734 STREAM_TREE[LF0]:1373735-1741482 STREAM_TREE[LPF]:1741483-1741587 GV_PDF[MCP]:1741588-1742151 GV_PDF[LF0]:1742152-1742179 GV_TREE[MCP]:1742180-1742332 GV_TREE[LF0]:1742333-1742639 [DATA] |
Сообщ.
#14
,
|
|
|
Вся информация о форматах содержится в исходных кодах hts_engine 1.05 и 1.07
Открываете и изучаете файлы HTS_model.c. Как сделать из версии hts_engine 1.07 утилиту распаковки? Очень просто. Надо инициализировать движок и загрузить модель голоса, вызов синтеза комментируется, удаляются ненужные ключи и код их обработки, а также добавить код сохранения файлов на диск. Все необходимое нужно искать в HTS_model.c по строке /* load duration */ Исходный вариант для длительности: ![]() ![]() /* load duration */ pdf_fp = NULL; tree_fp = NULL; matched_size = 0; if (HTS_get_token_from_string_with_separator(temp_duration_pdf, &matched_size, buff2, '-') == TRUE) { s = (size_t) atoi(buff2); e = (size_t) atoi(&temp_duration_pdf[matched_size]); HTS_fseek(fp, (long) s, SEEK_CUR); pdf_fp = HTS_fopen_from_fp(fp, fsize); HTS_fseek(fp, start_of_data, SEEK_SET); } Новая версия: ![]() ![]() // в начале функции. int sw; FILE* fw; char* buff; int fsize; // file size ... /* load duration */ pdf_fp = NULL; tree_fp = NULL; matched_size = 0; if (HTS_get_token_from_string_with_separator(temp_duration_pdf, &matched_size, buff2, '-') == TRUE) { s = (size_t) atoi(buff2); e = (size_t) atoi(&temp_duration_pdf[matched_size]); fsize = e - s + 1; HTS_fseek(fp, (long) s, SEEK_CUR); pdf_fp = HTS_fopen_from_fp(fp, fsize); fw = fopen((char*)"dur.pdf","wb"); buff = (char*)HTS_calloc(fsize, sizeof(char HTS_fread_little_endian( (void*)(buff), 1, fsize, (HTS_File*) pdf_fp); //HTS_byte_swap(buff, 4, (fsize)/4); sw = fwrite(buff, 1, fsize, fw); //<<<-------------- fclose(fw); free(buff); HTS_fseek(fp, start_of_data, SEEK_SET); } Аналогично правится весь код загрузки файлов. После извлечения файлов голоса можно приступать к их изучению. Откройте в винхексе распакованный dur.pdf и аналогичный файл из голоса arctic_slt. Содержимое файлов отличается. В новом файле нет заголовка, хотя первое слово может быть кол-вом пдфок в файле. Порядок следования байтов прямой. Если присмотреться еще лучше, то можете заметить, что все слова перевернуты и не хватает части заголовка. Добавляете часть заголовка, переворачивате все слова вручную или программно и проверяете файл на валидность. Теперь надо решить вопрос быстрой проверки файлов на валидность. Лучше всего использовать тренированный голос на 48000 Гц, в котором файлы заменяются парами (пдф и дерево) или по одному. Добавлено Дальше файл фильтра. Также не хватает части заголовка, перевернуты слова и векторы мин и вариации длиной 31 (см параметры) идут один за друг, а в старом формате их значения чередуются. Файл mgc.pdf также не очень сложный, но в звуке вного искажений. М/б неправильное чередование векторов и тд. Реально сделать, голос распакован и протестирован. Ссылка на архив с распаковщиком и распакованным голосом: https://drive.google.com/file/d/1EjGxn_oHrP...iew?usp=sharing Предыдущий вариант голоса неправильно произносит часть слов. Архив с правильным файлом продолжительности и исправленной утилитой. Можете скопировать только pdf и inf в каталог с голосом, не запуская утилиту. В командных файлах значение коэф-та а изменено на 0.55. В прикрепленном архиве исправленная версия утилиты(создает правильный файл dur.pdf). Добавлен ключ для распаковки, см. встроенную справку. Исходники залью в конце 2018 года. Прикреплённый файл ![]() |
Сообщ.
#15
,
|
|
|
Голос Alan,16кГц в новом формате https://drive.google.com/open?id=1VTAQsviK7...VkvyimCjnvQ55M2
|