|
Предварительные замечанияКак и в случае с Microsoft Speech API 4.0 ( см. соответствующую статью), для взаимодействия с компонентами Microsoft Speech API 5.1 (MS SAPI 5.1) мы воспользуемся функциями библиотеки DispHelper. Кроме уже известных нам функций dhCallMethod, dhGetValue и dhPutValue, для работы с коллекциями объектов, возвращаемых некоторыми методами компонентов SAPI 5.1, нам потребуются функция dhPutRef и набор макросов FOR_EACH. Эти макросы объявлены в файле disphelper.bi. Макросы FOR_EACH отличаются друг от друга количеством аргументов, что соответствующим образом отражается в имени макроса: FOR_EACH0, FOR_EACH1, FOR_EACH2,.. FOR_EACH9. Все разновидности этого макроса позволяют вызвать метод, возвращающий коллекцию объектов, а затем выполнить определенные действия с каждым из объектов коллекции (например, вызвать метод, получить или присвоить значение свойству объекта). В версии 0.15b компилятора FreeBASIC в файле disphelper.bi макрос FOR_EACH содержит ошибку, из-за которой нельзя было успешно скомпилировать программу. Для того чтобы обойти эту ошибку, в начало исходного кода, где используется макрос FOR_EACH) после строки #include "disphelper/disphelper.bi"следует поместить такой фрагмент: #ifdef FOR_EACH0 #undef FOR_EACH0 #define FOR_EACH0(objName, pDisp, szMember) _ scope :_ dim as IEnumVARIANT ptr xx_pEnum_xx = NULL :_ DISPATCH_OBJ(objName) :_ if (SUCCEEDED(dhEnumBegin(@xx_pEnum_xx, pDisp, szMember))) then :_ do while(dhEnumNextObject(xx_pEnum_xx, @objName) = NOERROR) #endif Суть ошибки в том, что вместо ключевого слова Scope использовано "escope", а приведенный фрагмент исправляет эту ситуацию. Аналогичная ошибка присутствует во всех реализациях макроса FOR_EACH. В версии 0.16b компилятора FreeBASIC эта ошибка устранена. Однако те, кто решил перейти от версии 0.15b к версии 0.16b, должны обратить внимание на то, что в новой версии по-иному обрабатывается унарный оператор NOT. Например, предположим, что функция MyFunc() возвращает логическое значение TRUE или FALSE. Для компилятора версии 0.15b корректным было такое выражение: IF NOT MyFunc() THEN MessageBox (NULL, "Функция вернула FALSE.", "Отчет", 0) ELSE MessageBox (NULL, "Функция вернула TRUE.", "Отчет", 0) END IF В приложении, собранном при помощи компилятора версии 0.16b, вышеуказанная конструкция будет работать с ошибкой. Для правильной работы этого кода необходимо непосредственно сравнивать результат работы функции MyFunc() с константами TRUE или FALSE. Например: IF FALSE = MyFunc() THEN MessageBox (NULL, "Функция вернула FALSE.", "Отчет", 0) ELSE MessageBox (NULL, "Функция вернула TRUE.", "Отчет", 0) END IF Работа с объектом SpVoiceНаиболее полную информацию о методах и свойствах объектов OLE автоматизации, входящих в состав MS SAPI 5.1, можно найти в документации, прилагаемой к MS SAPI SDK 5.1 (раздел Automation). Там же приводятся различные примеры исходного кода для среды Visual Basic. Пакет MS Speech SDK, а также дополнительные голоса можно загрузить со страницы портала Microsoft (см. раздел в конце статьи). Основным объектом, реализующим синтез речи по тексту, является SpVoice. Строго говоря, при использовании функций DispHelper не имеет значения название объекта, потому что в коде программы все объекты являются указателями на интерфейс IDispatch. Однако для удобства мы оставим за объектами те имена, которые назначены им в документации к MS SAPI 5.1. При использовании функций DispHelper необходимо знать названия методов (methods) и свойств (properties) интересующего нас объекта. Вот перечень методов и свойств объекта SpVoice, которые будут использованы в нижеследующих примерах:
Следует обратить внимание на то, что метод .GetVoices объекта SpVoice получает необходимую информацию об установленных голосах ("движках" из системного реестра (ветка "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech\Voices\Tokens"). Однако реестр не всегда содержит корректную информацию. Например, в нем могут оказаться "битые" ветки, оставленные ранее установленными SAPI5-синтезаторами и не удаленные ими при деинсталляции. Если выбрать такой "битый" голос в качестве активного, то возникнет ошибка (в том числе и в Панели управления -> Речь). Итак, теперь уже можно приступать к написанию кода. Мы собираемся использовать голосаMS SAPI 5.1 для синтеза речи в нашем приложении на FreeBASIC. Для этого необходимо выполнить несколько последовательных действий. Первым делом, при помощи функции DHCreateObject мы загрузим в память объект SpVoice, зарегистрированный в системе как "Sapi.SpVoice": DIM object IDispatch ptr IF FAILED(dhCreateObject ("Sapi.SpVoice", NULL, @Object)) THEN '... обработка ошибки END IF ' ... нормальная работа Затем при помощи метода .GetVoices можно получить коллекцию имеющихся голосов и, перебрав их при помощи макроса FOR_EACH0, заполнить названиями этих голосов, например, выпадающий список. Это даст пользователю возможность самостоятельно выбрать нужный голос из списка. DIM szDescription AS WSTRING PTR FOR_EACH0(spVoice, Object, ".GetVoices") ' Получаем строку описания очередного голоса dhGetValue("%S", @szDescription, spVoice, ".GetDescription") ' Помещаем строку с описанием голоса в комбинированный список SendMessage (hCtrl, CB_ADDSTRING, 0, szDescription) NEXT_(spvoice) dhFreeString(szDescription) Следующий фрагмент кода показывает, как установить выбранный голос в качестве активного (свойство .Voice объекта SpVoice). Здесь переменная Index содержит номер выбранного голоса: DHGetValue ("%o", @spVoices, object, ".GetVoices") DHGetValue ("%o", @spVoice, spVoices, ".Item(%u)", Index) dhPutRef(Object, ".Voice = %o", spVoice) Метод .GetVoices объекта SpVoice может в качестве аргумента принимать строку, содержащую параметры отбора голосов из общего списка. Например, для того чтобы отобрать только те голоса, которые предназначены для синтеза русской речи, нужно передать строку "Language=419". Разумеется, в этом случае нужно использовать макрос FOR_EACH1: DIM szDescription AS WSTRING PTR FOR_EACH1(spVoice, Object, ".GetVoices(%s)", "Language=419") dhGetValue("%S", @szDescription, spVoice, ".GetDescription") SendMessage (hCtrl, CB_ADDSTRING, 0, szDescription) NEXT_(spvoice) dhFreeString(szDescription) Соответствующие изменения нужно внести и во фрагмент кода, в котором устанавливается активный голос: DHGetValue ("%o", @spVoices, object, ".GetVoices(%s)", "Language=419") DHGetValue ("%o", @spVoice, spVoices, ".Item(%u)", Index) dhPutRef(Object, ".Voice = %o", spVoice) Аналогичным образом мы можем предоставить пользователю возможность выбрать звуковое устройство, через которое будет звучать синтезированная речь. Это бывает необходимо, когда требуется разделить звуковой и речевой потоки между двумя и более звуковыми картами или, например, между звуковой картой и голосовым модемом. Пример такого решения приведен в исходных кодах демонстрационной программы. Последний шаг -- это непосредственный синтез речи. Для этого достаточно вызвать метод .Speak, передав в качестве аргумента строку текста, который нужно озвучить. dhCallMethod(Object, ".Speak(%S)", Phrase) Пример исходного кодаВ качестве примера ниже приведен исходный код приложения Windows, которое отображает на экране диалоговое окно со списком установленных в системе голосов MS SAPI 5.1 и списком доступных звуковых устройств. Кроме того, в диалоге присутствует текстовое поле, в котором пользователь может набрать текст. При нажатии клавиши Enter или при щелчке по кнопке "Speak" текст из поля редактирования будет озвучен выбранным голосом. Исходный код приложения включает следующие файлы:
Файл speech.bas' speech.bas - Основной файл приложения, демонстрирующего использование ' MS SAPI 5.1 в программах на FreeBASIC. ' Компилировать: fbc.exe" -s gui speech.rc speech.bas sapi5.bas option explicit #define UNICODE #include once "windows.bi" #include "disphelper/disphelper.bi" #include "sapi5.bi" #include "resource.bi" DECLARE FUNCTION DlgProc (byval hwnd as HWND, byval umsg as UINT, byval wparam as WPARAM, byval lparam as LPARAM) as BOOL DIM SHARED as IDispatch ptr tts = NULL dhInitialize( TRUE ) dhToggleExceptions( TRUE ) DialogBoxParam( GetModuleHandle( NULL), cptr(LPCSTR, IDD_DLG1), NULL, @DlgProc, NULL ) SAFE_RELEASE( tts) dhUninitialize( TRUE ) END ' Program end FUNCTION DlgProc (byval hwnd as HWND, byval umsg as UINT, byval wparam as WPARAM, byval lparam as LPARAM) as BOOL DIM as long id, event SELECT CASE uMsg CASE WM_INITDIALOG IF TRUE <> SAPI5_Initialize(tts) THEN EndDialog( hwnd, 0 ) Return TRUE END IF ' Заполняем список голосов SAPI5_GetVoices(tts, GetDlgItem(hWnd, IDC_VOICES_LIST)) SAPI5_SelectVoice (tts, 0) SAPI5_SetSpeed (tts, 5) SAPI5_SetVolume(tts, 100) ' Заполняем список аудиоустройств SAPI5_GetAudioOutputs(tts, GetDlgItem(hWnd, IDC_AUDIOOUTPUTS_LIST)) SAPI5_SelectAudioOutput(tts, 0) CASE WM_CLOSE EndDialog( hwnd, 0 ) CASE WM_COMMAND id = loword( wParam ) event = hiword( wParam ) SELECT CASE id CASE IDOK scope DIM szBuf as WSTRING * 1024 GetDlgItemText(hWnd, IDC_TEXT_TO_SPEAK, szBuf, 1024) SAPI5_Speak (tts, @szBuf) END Scope CASE IDCANCEL EndDialog( hwnd, 0 ) CASE IDC_VOICES_LIST: IF CBN_SELCHANGE = event THEN SAPI5_SelectVoice (tts, SendDlgItemMessage (hWnd, Id, CB_GETCURSEL, 0, 0)) END IF CASE IDC_AUDIOOUTPUTS_LIST: IF CBN_SELCHANGE = event THEN SAPI5_SelectAudioOutput(tts, SendDlgItemMessage (hWnd, Id, CB_GETCURSEL, 0, 0)) End If END SELECT CASE ELSE return FALSE END SELECT return TRUE END FUNCTION Файл speech.rc#include "resource.bi" IDD_DLG1 DIALOGEX 0,0,200,90 CAPTION "SAPI 5: Text-to-speech Example" FONT 8,"MS Sans Serif" STYLE 0x10CC0000 EXSTYLE 0x00000080 BEGIN LTEXT "Voice:", IDC_STATIC, 5, 5, 40, 9 COMBOBOX IDC_VOICES_LIST, 50, 5, 145, 10, WS_TABSTOP| CBS_DROPDOWNLIST | WS_VSCROLL LTEXT "Output:", IDC_STATIC, 5, 5+11, 40, 9 COMBOBOX IDC_AUDIOOUTPUTS_LIST , 50, 5+11, 145, 10, WS_TABSTOP| CBS_DROPDOWNLIST | WS_VSCROLL LTEXT "Text to speak:", IDC_STATIC, 5, 5+22, 100, 10 EDITTEXT IDC_TEXT_TO_SPEAK, 5, 5+33, 190, 10 DEFPUSHBUTTON "Speak", IDOK, 120, 5+55, 35, 10 PUSHBUTTON "Close", IDCANCEL, 120+37, 5+55, 35, 10 END Файл resource.bi#define IDD_DLG1 1000 #define IDC_BTN1 1002 #define IDC_STATIC -1 #define IDC_BTNSPEAK 1001 #define IDC_VOICES_LIST 2001 #define IDC_AUDIOOUTPUTS_LIST 2002 #define IDC_TEXT_TO_SPEAK 2003 #endif Файл sapi5.basoption explicit #define UNICODE #include once "windows.bi" #include "disphelper/disphelper.bi" FUNCTION SAPI5_Initialize (byRef Object as IDispatch PTR) as BOOL DIM as String ProgId = "Sapi.SpVoice" DIM hr as HRESULT IF FAILED(dhCreateObject (ProgId, NULL, @Object)) THEN return FALSE return TRUE END FUNCTION SUB SAPI5_GetVoices (byRef object as IDispatch ptr,byval hCtrl as HWND) DIM szDescription as WString ptr SendMessage (hCtrl, CB_RESETCONTENT, 0, 0) FOR_EACH0(spVoice, Object, ".GetVoices") dhGetValue("%S", @szDescription, spVoice, ".GetDescription") SendMessage (hCtrl, CB_ADDSTRING, 0, szDescription) NEXT_(spvoice) SendMessage (hCtrl, CB_SETCURSEL, 0, 0) dhFreeString(szDescription) END SUB SUB SAPI5_SelectVoice (byRef Object as IDispatch ptr , byVal Index as Integer) DIM spVoices, spVoice as IDispatch ptr DHGetValue ("%o", @spVoices, object, ".GetVoices") DHGetValue ("%o", @spVoice, spVoices, ".Item(%u)", Index) dhPutRef(Object, ".Voice = %o", spVoice) END SUB SUB SAPI5_GetAudioOutputs(byRef object as IDispatch ptr,byval hCtrl as HWND) DIM szDescription as WString ptr SendMessage (hCtrl, CB_RESETCONTENT, 0, 0) FOR_EACH0(spAudioOutput, Object, ".GetAudioOutputs") dhGetValue("%S", @szDescription, spAudioOutput, ".GetDescription") SendMessage (hCtrl, CB_ADDSTRING, 0, szDescription) NEXT_(spAudioOutput) SendMessage (hCtrl, CB_SETCURSEL, 0, 0) dhFreeString(szDescription) END SUB SUB SAPI5_SelectAudioOutput(byRef Object as IDispatch ptr , byVal Index as Integer) DIM spAudioOutputs, spAudioOutput as IDispatch ptr DHGetValue ("%o", @spAudioOutputs, object, ".GetAudioOutputs") DHGetValue ("%o", @spAudioOutput, spAudioOutputs, ".Item(%u)", Index) dhPutRef(Object, ".AudioOutput= %o", spAudioOutput) END SUB SUB SAPI5_SetSpeed (byRef Object as IDispatch ptr , byVal Speed as Integer) dhPutValue(Object, ".Rate=%d", Speed) END SUB SUB SAPI5_SetVolume(byRef Object as IDispatch ptr , byVal Volume as Integer) IF Volume > 100 THEN VOLUME = 100 IF Volume < 0 THEN Volume=0 dhPutValue(Object, ".Volume=%u", Volume) END SUB SUB SAPI5_Speak (byRef Object as IDispatch ptr , byVal Phrase as WString PTR) dhCallMethod(Object, ".Speak(%S)", Phrase) END SUB Файл sapi5.bi#ifndef _SAPI5_BI #define _SAPI5_BI DECLARE FUNCTION SAPI5_Initialize (byRef Object as IDispatch PTR) as BOOL DECLARE SUB SAPI5_GetVoices (byRef Object as IDispatch ptr, byval hCtrl as HWND) DECLARE SUB SAPI5_SelectVoice (byRef Object as IDispatch ptr, byVal Index as Integer) DECLARE SUB SAPI5_GetAudioOutputs(byRef Object as IDispatch ptr, byval hCtrl as HWND) DECLARE SUB SAPI5_SelectAudioOutput(byRef Object as IDispatch ptr, byVal Index as Integer) DECLARE SUB SAPI5_SetSpeed(byRef Object as IDispatch ptr , byVal Speed as Integer) DECLARE SUB SAPI5_SetVolume(byRef Object as IDispatch ptr , byVal Volume as Integer) DECLARE SUB SAPI5_Speak (byRef Object as IDispatch ptr , byVal Phraseas as WString PTR) #endif Ссылки по теме
|
||||||||
Распространение материалов сайта означает, что распространитель принял условия лицензионного соглашения. Идея и реализация: © Владимир Довыденков и Анатолий Камынин, 2004-2021 |
Социальные сети