Использование MS Speech API 5.1 в программах на FreeBASIC

Дата публикации:2007
Twitter Facebook Vkontakte

Предварительные замечания

Как и в случае с 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" текст из поля редактирования будет озвучен выбранным голосом.

Исходный код приложения включает следующие файлы:

  1. speech.bas -- основное приложение, формирующее диалоговое окно и реализующее функцию обработки сообщений.
  2. speech.rc -- файл описания ресурсов.
  3. resource.bi -- заголовочный файл с определением идентификаторов ресурсов.
  4. sapi5.bas -- набор функций-"оберток" для взаимодействия с компонентами MS SAPI 5.1.
  5. sapi5.bi -- заголовочный файл с объявлениями функций и процедур из sapi5.bas.

Файл 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.bas


option 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-2017
Rambler's Top100