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

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

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

Эта статья предназначена для тех, кто уже знаком с FreeBASIC и хотел бы использовать в своих программах технологии синтеза речи, доступные посредством MS SAPI 4. Для понимания материала необходимо знать язык программирования Бейсик, назначение директив компилятора FreeBASIC, а также владеть базовыми знаниями о том, как создавать приложения в MS Windows. Приветствуется знание основ работы технологий COM и OLE автоматизации, однако это требование не является обязательным и отсутствие этих знаний не должно сильно затруднить понимание материала (ведь пользуются же многие люди микроволновой печью, не имея представления о работе магнетрона).Вполне достаточно будет вашего опыта использования функции CreateObject() в среде Visual Basic, Visual Basic for Application или скриптовом языке VBScript.

О чем пойдет разговор

Несмотря на ряд неоспоримых достоинств, присущих такому средству разработки программ, как компилятор FreeBASIC, есть один существенный момент в работе с ним, который ставит в тупик многих программистов, пришедших к FreeBASIC после некоторого опыта программирования в среде Visual Basic. Этим моментом является работа с серверами OLE автоматизации (OLE Automation). Поскольку компоненты MS Speech API 4 (MS SAPI 4) реализованы как серверы OLE автоматизации, то, взяв их в качестве примера, мы разберемся в том, как работать с подобными компонентами в программах на FreeBASIC. Еще одной причиной, по которой в качестве примера выбран набор компонентов SAPI 4, является то обстоятельство, что среди примеров исходного кода в каталоге, где установлен FreeBASIC, в папке "examples\disphelper\" отсутствуют именно примеры работы с компонентами речевых технологий (SAPI 4 и SAPI 5).

В средах Visual Basic, VBA и др., а также в скриптовом языке VBScript программисту достаточно использовать функцию CreateObject(), чтобы, ни о чем больше не задумываясь, получить доступ к нужному OLE-серверу. В терминах Visual Basic этот OLE-сервер называется "объектом". В дальнейшем программист использует полученный объект, вызывая методы (methods) и получая/устанавливая свойства (properties) этого объекта. На стороне OLE-сервера этот механизм поддерживается при помощи интерфейса IDispatch, на который возложена обязанность вызывать нужную функцию в соответствии с именем метода, которое указал программист в программе на Visual Basic. Такой механизм взаимодействия прикладной программы (OLE-клиента) и сервера OLE автоматизации называется "поздним" связыванием из-за того, что адрес функции, которая будет вызвана по запросу прикладной программы, определяется не на этапе компиляции программы, а во время ее исполнения. Этот механизм в программах на Visual Basic поддерживается самой средой разработки и библиотеками, которые должны быть включены в дистрибутив с программой.

В отличие от Visual Basic, FreeBASIC является только лишь компилятором, а не средой разработки, поэтому встроенных функций для работы с серверами OLE автоматизации в нем не предусмотрено. Однако этот недостаток можно восполнить, обратившись к библиотеке Disphelper, которая свободно распространяется в открытом коде и которая входит в состав пакета FreeBASIC. Программисты на языках C/C++ могут более подробно ознакомиться с Disphelper на официальном сайте этого проекта (все необходимые ссылки приведены в соответствующем разделе этой статьи).

Таким образом, для использования синтеза речи в программах на FreeBASIC нам необходимо, во-первых, освоить работу с функциями Disphelper, а во-вторых, познакомиться с методами и свойствами SAPI 4 и научиться взаимодействовать с ними при помощи Disphelper.

Быстрый старт

Приведем два фрагмента программ на Visual Basic и FreeBASIC, выполняющих схожие действия.

' Visual Basic
SET tts = CreateObject("ActiveVoice.ActiveVoice.1")
tts.Speak ("Hello, world!")

' FreeBASIC dhCreateObject ("ActiveVoice.ActiveVoice.1", NULL, @tts) DHCallMethod (tts, ".Speak(%S)", "Hello, world!")

Если при взгляде на эти фрагменты кода Вам стало все совершенно понятно и Вы горите желанием приступить к программированию, то Вы можете не читать дальше и дать волю своим желаниям. Мы же познакомимся поближе с возможностями Disphelper и SAPI 4.

Основы Disphelper

Библиотека Disphelper предназначена для облегчения работы с интерфейсом IDispatch OLE-компонентов . Для вызова методов OLE-компонента используется синтаксис, напоминающий синтаксис функции printf() языка C. Правильнее будет сказать, что речь идет не о синтаксисе самой функции printf(), а о символах строки формата. При вызове функций Disphelper используются похожие соглашения для указания типа аргумента, что и в функции printf().

Вот небольшой перечень некоторых спецификаций аргументов из тех, что поддерживает Disphelper (более полный список следует изучать по файлу readme.htm, входящем в дистрибутив Disphelper):

Перечислим и охарактеризуем те функции Disphelper, которые мы будем использовать в дальнейшем.

  1. DISPATCH_OBJ() - макрос, служащий для объявления переменной типа IDispatch PTR.
  2. DHCreateObject () - создает экземпляр OLE-компонента и устанавливает взаимодействие с интерфейсом IDispatch. Действие этой функции схоже с действием функции CreateObject () в Visual Basic.
  3. DHCallMethod() - вызывает определенный метод (method) OLE-компонента.
  4. DHPutValue() - присваивает значение определенному свойству (property) OLE-компонента.
  5. DHGetValue () - получает значение определенного свойства (property) OLE-компонента.

Ниже будет приведен исходный код, демонстрирующий использование этих функций. Здесь же чуть подробнее остановимся на функциях DHCallMethod, DHPutValue и DHGetValue. Описанине прототипов этих функций можно найти в файле disphelper.bi, который расположен в каталоге компилятора FreeBASIC в папке "inc\disphelper\"..

Функция DHCallMethod имеет переменное число параметров, зависящее от количества параметров того метода, который нужно вызвать. D любом случае в качестве первого параметра передается переменная типа IDispatch PTR (или, по терминологии Visual Basic, объект), вторым параметром передается строка, содержащая имя вызываемого метода с точкой в начале и списком форматов аргументов. Далее идут сами аргументы, которые будут переданы вызываемому методу. Например, так выглядит вызов метода Method1 у объекта Object1, причем методу передается целочисленный аргумент:

DHCallMethod (Object1, ".Method1(%d)", 100)

Похожим образом вызывается функция DHPutValue? только вместо метода указывается в виде строки имя свойства. Например, так выглядит присваивание свойству Property1 целочисленного значения:

DHPutValue (Object1, ".Property1=%d", 100)

Несколько иной порядок параметров у функции DHGetValue. Здесь первым идет строка формата получаемого значения, затем указатель на переменную, в которую полученное значение будет сохранено, а затем уже идет объект и строка с именем свойства, значение которого нужно прочитать:

Dim Value As Integer
DHGetValue ("%d", @Value, Object1, ".Property1")

Функции Disphelper возвращают значение типа HRESULT, которое содержит код завершения операции с OLE-компонентом. Это может быть код успешного завершения или код ошибки. Анализируя этот код, можно определить причину возникновения ошибок. Если установлен режим перехвата ошибок (например, в целях отладки), то в случае ошибки на экране появится сообщение с кодом возврата и текстовым описанием ее причины.

Компонент Direct Speech Synthesis

Подготовка SAPI 4 к работе

Direct Speech Synthesis - это один из компонентов, входящих в состав MS SAPI 4. Этот компонент предназначен для непосредственного синтеза речи по тексту и позволяет не только озвучивать текст, но и управлять параметрами синтеза речи. Если Вы используете MS Windows 2000, то компоненты SAPI 4 входят в состав этой операционной системы и, скорее всего, уже установлены на Вашем компьютере. Для операционных систем MS Windows 9x и XP Вам придется загрузить пользовательский дистрибутив SAPI или весь MS SAPI 4 SDK4. Однако после установки компонентов SAPI 4 Вы можете столкнуться с ситуацией, когда приведенный ниже программный код не будет работать. Если при запуске откомпилированной программы на экране появляется сообщение:

Ошибка
Member:  ActiveVoice.ActiveVoice.1 Function:  CreateObject
Error In:  CreateObjectEx
Error:  Недопустимая строка с указанием класса
Code:  800401f3
Source:  Application

То это означает, что в системном реестре в соответствующем разделе не сопоставлены строковое имя компонента, называемое ProgId (в нашем случае это "ActiveVoice.ActiveVoice.1") и уникальный идентификатор компонента CLSID, по которому операционная система находит нужную информацию для загрузки компонента.

В таком случае необходимо дополнить реестр информацией вручную. Для Windows XP нужно создать текстовй файл "activevoice.reg" следующего содержания:

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\ActiveVoice.ActiveVoice.1]
@="Direct Speech Synthesis"

[HKEY_CLASSES_ROOT\ActiveVoice.ActiveVoice.1\Clsid]
@="{EEE78591-FE22-11D0-8BEF-0060081841DE}"

Затем в Проводнике Windows запустить этот файл на выполнение (нажать клавишу Enter) и согласиться с внесением изменений в реестр. На этом подготовка SAPI 4 к работе завершена.

Если Вы знакомы с функциями Win32 API, предназначенными для работы с системным реестром, то, при разработке полноценного приложения, проверку и модификацию вышеописанного ключа в системном реестре можно выполнить программно.

Свойства и методы компонента Direct Speech Synthesis

В рамках этой статьи нам будет достаточно изучить те методы и свойства, которые используются чаще других при создании приложений с поддержкой синтеза речи. За более подробной информацией следует обращаться к документации, входящей в комплект MS SAPI 4 SDK.

Ниже имена свойств и методов указываются с точкой в начале, потому что именно в таком виде эти имена нужно передавать функциям Disphelper.

Начнем с методов:

Теперь перейдем к свойствам, их будет несколько больше:

Говорящее приложение на FreeBASIC

Прежде всего отметим некоторые особенности использования функций Disphelper в программах на FreeBASIC. Во-первых, все строковые параметры передаются функциям Disphelper в кодировке Unicode. По этой причине в тексте программы необходимо указать:

#define UNICODE

Во-вторых, следует подключить заголовочный файл "disphelper.bi", который входит в пакет FreeBASIC. Это делается директивой:

#include "disphelper/disphelper.bi"

В-третьих, для нормальной работы Direct Speech Synthesis необходимо создать оконное приложение. В консольном варианте с синтезом речи могут возникнуть сложности.

Ниже приведен исходный код приложения Windows, которое отображает на экране диалоговое окно. В этом окне можно в комбинированном списке выбрать голос, ввести текст в поле редактирования и нажать кнопку, чтобы преобразовать введенный текст в речь.

Весь проект включает пять файлов:

  1. speech.bas - исходный код основной программы, реализующей вывод диалогового окна и обработку сообщений.
  2. sapi4.bas - исходный код библиотеки функций, реализующих взаимодействие с Direct Speech Synthesis при помощи Disphelper.
  3. sapi4.bi - заголовочный файл с объявлениями функций sapi4.bas.
  4. resource.bi - заголовочный файл с идентификаторами ресурсов.
  5. speech.rc - файл описания ресурсов диалогового окна.

Поясним назначение файла sapi4.bas. Можно было поместить вызовы функций Disphelper непосредственно в процедуру обработки сообщений диалогового окна. Однако мы вынесли эти вызовы в отдельную библиотеку функций sapi4.bas. Функции этой библиотеки условно можно назвать "обертками", так как они как бы "оборачивают" вызовы функций Disphelper. Такая организация исходного кода позволяет нам менять код функций-оберток, но при этом не менять (или почти не менять) исходный код основного файла speech.bas. В дальнейшем, когда будет обсуждаться работа с MS SAPI 5 в приложениях FreeBASIC, мы воспользуемся этой возможностью, подставив вместо sapi4.bas другую библиотеку, а именно sapi5.bas. Помимо этого, Вы можете использовать sapi4.bas в других проектах, при этом вам не придется "вырезать" нужные фрагменты кода из основного приложения.

Еще следует обратить внимание, что мы не применяем макрос DISPATCH_OBJ(), потому что используемый нами объект (то есть указатель на интерфейс IDispatch OLE-компонента) является глобальной переменной, а синтаксис FreeBASIC не позволяет совместить указанный макрос и объявление глобальной переменной. По указанной причине мы используем конструкцию:

 
dim SHARED 	  as IDispatch ptr tts = NULL

Файл speech.bas

' Файл speech.bas
' Пример использования SAPI 4 для синтеза речи 
' в программах на FreeBASIC
'' Компилировать: fbc -s gui speech.rc speech.bas sapi4.bas 
'' 
'' 
option explicit 
#define UNICODE
#include once "windows.bi" 
#include "disphelper/disphelper.bi"

' Заголовочный файл функций для работы с SAPI 4
#include "sapi4.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

' Инициализация Disphelper
	dhInitialize( TRUE )

' Разрешаем перехват и отображение сообщений об ошибках
	dhToggleExceptions( TRUE )

' Отображаем диалоговое окно
  DialogBoxParam( GetModuleHandle( NULL ), cptr( LPCSTR, IDD_DLG1 ), NULL, @DlgProc, NULL ) 

' Освобождаем то, что занимали
	SAFE_RELEASE( tts)

' Завершаем работу с Disphelper
	dhUninitialize( TRUE )
  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
' Пытаемся создать Direct Speech Synthesis 
If TRUE <> SAPI4_Initialize(tts) Then
  	EndDialog( hwnd, 0 ) 
Return TRUE
End If

' Заполняем комбинированный список доступными голосами
SAPI4_GetVoices(tts, GetDlgItem(hWnd, IDC_VOICES_LIST))

' Выбираем первый из голосов
SAPI4_SelectVoice (tts, 1)

' Устанавливаем темп речи
SAPI4_SetSpeed (tts, 80)
  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)
SAPI4_Speak (tts, @szBuf)
End Scope

case IDCANCEL
    	EndDialog( hwnd, 0 )

case IDC_VOICES_LIST:
' Реагируем на смену голоса
If (event = CBN_SELCHANGE) Then
SAPI4_SelectVoice (tts, SendDlgItemMessage (hWnd, Id, CB_GETCURSEL, 0, 0)+1)
End If
  	end select
    
  case else
  	return FALSE
  
  end select
  
  return TRUE
end function 

Файл sapi4.bas


' Файл sapi4.bas
' Набор функций для взаимодействия с компонентом Direct Speech 
' при помощи функций Disphelper

option explicit 

#define UNICODE
#include once "windows.bi" 
#include "disphelper/disphelper.bi"
' SAPI4_Initialize  создает объект ActiveVoice.ActiveVoice.1 и инициализирует его.
' Возврат: TRUE, если ошибок не было; FALSE - в противном случае.
FUNCTION SAPI4_Initialize (byRef Object As IDispatch PTR) As BOOL
Dim As String ProgId = "ActiveVoice.ActiveVoice.1"
IF FAILED(dhCreateObject (ProgId, NULL, @Object) THEN
RETURN FALSE
END IF
dhPutValue(Object, ".Initialized = %u", 1)
RETURN TRUE
END FUNCTION

' Процедура SAPI4_GetVoices  заполняет комбинированный список, 
'  дескриптор которого передается во втором параметре, названиями доступных голосов.
SUB SAPI4_GetVoices (byRef object As IDispatch PTR,byval hCtrl as HWND)
Dim MaxVoice AS INTEGER
Dim szBuf AS WSTRING * 1024
Dim szSpeaker AS WSTRING PTR
Dim szProduct AS WSTRING PTR
Dim i AS INTEGER

SendMessage (hCtrl, CB_RESETCONTENT, 0, 0)
DHGetValue("%d", @MaxVoice, Object, ".CountEngines")
FOR i = 1 TO MaxVoice
DHGetValue("%S", @szSpeaker, Object, ".Speaker(%d)", i)
DHGetValue("%S", @szProduct, Object, ".ProductName (%d)", i)
szBuf = *szSpeaker + ": " +*szProduct
SendMessage (hCtrl, CB_ADDSTRING, 0, @szBuf)
NEXT
SendMessage (hCtrl, CB_SETCURSEL, 0, 0)

			dhFreeString(szSpeaker)
			dhFreeString(szProduct)
END SUB

' Процедура SAPI4_SelectVoice  устанавливает в качестве активного 
' голос с указанным номером.
SUB SAPI4_SelectVoice (byRef Object As IDispatch PTR, byVal Index As INTEGER)
Dim MaxVoice AS INTEGER
DHGetValue("%d", @MaxVoice, Object, ".CountEngines")
IF Index > 0 AND Index <= MaxVoice THEN 
dhCallMethod(Object, ".Select(%u)", Index)
END IF
END SUB

' Процедура SAPI4_SetSpeed  устанавливает темп речи.
' Проверка на допустимость значения темпа речи не производится.
SUB SAPI4_SetSpeed (byRef Object As IDispatch PTR, byVal Speed As INTEGER)
dhPutValue(Object, ".Speed=%u", Speed) 
END SUB

' Процедура SAPI4_Speak  вызывает метод, озвучивающий текст.
SUB SAPI4_Speak (byRef Object As IDispatch PTR, byVal Phrase As WString PTR)
dhCallMethod(Object, ".Speak(%S)", Phrase)
END SUB

Файл sapi4.bi

' Файл sapi4.bi
' Объявление функций sapi4.bas
#ifndef __SAPI4_BI__
#define __SAPI4_BI__

DECLARE FUNCTION SAPI4_Initialize (byRef Object As IDispatch PTR) As BOOL
DECLARE SUB SAPI4_GetVoices (byRef Object As IDispatch PTR, byval hCtrl as HWND)
DECLARE SUB SAPI4_SelectVoice (byRef Object As IDispatch PTR, byVal Index As Integer)   
DECLARE SUB SAPI4_SetSpeed (byRef Object As IDispatch PTR, byVal Speed As Integer)
DECLARE SUB SAPI4_Speak (byRef Object As IDispatch PTR, byVal Phrase As WString PTR)
#endif

Файл resource.bi


#ifndef IDC_STATIC 
#define IDC_STATIC -1
#endif
#define IDD_DLG1 1000 
#define IDC_VOICES_LIST	1001
#define IDC_TEXT_TO_SPEAK	1002

Файл speech.rc

#include "resource.bi"

IDD_DLG1 DIALOGEX 0,0,200,100 
CAPTION "Text-to-speech" 
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 "Text to speak:", IDC_STATIC, 5, 5+11, 100, 10
	EDITTEXT IDC_TEXT_TO_SPEAK, 5, 5+22, 190, 10

	 DEFPUSHBUTTON "Speak", IDOK, 120, 5+44, 35, 10 
	PUSHBUTTON  "Закрыть", IDCANCEL, 120+37, 5+44, 35, 10 
END


Распространение материалов сайта означает, что распространитель принимает условия лицензионного соглашения.
Идея и реализация: © Владимир Довыденков и Анатолий Камынин,  2004-2017
Rambler's Top100