»спользование 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, которые будут использованы в нижеследующих примерах:

  • .Speak (Text As String, [Flags As SpeechVoiceSpeakFlags) AS LONG -- метод, который в зависимости от значени€ своего второго параметра, позвол€ет озвучить строку текста в синхронном или асинхронном режиме. ¬торой параметр этого метода представл€ет собой набор флагов, определ€ющих опции озвучивани€. ѕо умолчанию второй параметр можно не указывать, тогда в синхронном режиме будет озвучена строка текста, переданна€ в качестве первого параметра, при этом знаки препинани€ читатьс€ не будут и XML теги речевой разметки обрабатыватьс€ тоже не будут. ћетод возвращает номер речевого потока.
  • .GetVoices -- метод, позвол€ющий в соответствии с заданными критери€ми составить коллекцию объектов, соответствующих установленным в системе голосам в стандарте MS SAPI 5.1. ѕри вызове этого метода без параметров возвращаетс€ коллекци€ всех имеющихс€ в системе голосов.
  • .GetAudioOutputs -- метод, позвол€ющий в соответствии с указанными критери€ми составить коллекцию объектов, соответствующих имеющимс€ в системе звуковоспроизвод€щим устройствам. ѕри вызове этого метода без параметров возвращаетс€ коллекци€ всех имеющихс€ устройств.
  • .Voice -- свойство, которое хранит объект SpObjectToken, соответствующй активному голосу, то есть тому голосу из коллекции установленных голосов, которым будет озвучен текст.
  • .AudioOutput -- свойство, которое хранит объект SpObjectToken, соответствующй текущему звуковоспроизвод€щему устройству.
  • .Rate AS LONG -- свойство, которое хранит значение темпа речи ( от -10 до 10). Ёто свойство позвол€ет сохранить значение, выход€щее за указанные пределы, но, как правило, на темп речи запредельные значени€ не вли€ют.
  • .Volume AS LONG -- свойство, хран€щее значение громкости речи (от 0 до 100). Ёто свойство не позвол€ет сохранить значени€, выход€щие за пределы указанного диапазона. ¬ случае присваивани€ некорректного значени€ возникает ошибка.

—ледует обратить внимание на то, что метод .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-2021