Chapter 35

Creating the Voice Phone Application


CONTENTS


The example program in this chapter uses SAPI and TAPI services to create a true "hands-free" telephone. With this program and a pc speaker phone, you can lookup and dial telephone numbers by simply giving voice commands to your pc. You'll be able to initiate database adds, edits, and deletes; issue a voice command that will search the database for a name; and tell the program to Dial Mike. The Voice Phone will locate the record in the database, pull up the phone number, place the call, and then prompt you to begin speaking.

As an added bonus, this program gives audible responses to help requests and speaks the names and telephone numbers of selected records in the database. Even the About box is "read" to you! Figure 35.1 shows a completed version of Voice Phone in action.

Figure 35.1 : The completed version of Voice Phone.

Project Resources

There is one main form for the project and three support forms:

In addition to the forms, there are four support modules for the Voice Phone project:

Before you begin coding the project, you'll need to make sure you have the following resources loaded available on your workstation:

When you first start this project, you'll need to make sure you have loaded the proper support libraries for Visual Basic. Select Tools | References and load the following:

Note
If you don't have these libraries available from your References dialog box, you'll need to use the Browse button to find them. Usually they can be found in the SYSTEM folder of the WINDOWS directory. The Voice libraries can also be found on the CD-ROM that ships with this book. You probably have other resources loaded for your programs. That's fine. Just be sure you have the three resources listed above.

Coding the Library Modules

The first step in the process of building the Voice Phone project is creating the library modules. Three of the modules exist to provide simple support for the Windows extension services (SAPI and TAPI). The fourth module holds most of the project code.

The AssistedTAPI Module

The AssistedTAPI module holds the Telephony API declarations, two type definitions, and two Visual Basic wrapper functions. With this one code module you can provide any Visual Basic or VBA-compatible program with basic TAPI services.

Tip
You might think it a better idea to implement Assisted TAPI support using a Visual Basic class module instead of a simple code module. However, the code module can be loaded into any VBA-compatible environment (including Visual Basic 3.0). The class module could only be used for Visual Basic 4.0.

Add a BAS module to your project (Insert | Module) and set its Name property to AssistedTAPI. Save the module as VTAPI.BAS. Now you're ready to start coding.

The first things to add to this module are the Assisted TAPI API declarations. Listing 35.1 shows the code that imports the tapiRequestMakeCall and tapiGetLocationInfo API calls.


Listing 35.1. Adding the Assisted Telephony API Declarations.
'
'
 declare assisted tapi functions
'
#If Win32 Then
    Declare Function tapiRequestMakeCall Lib "TAPI32.DLL" (ByVal lpszDestAddress As ÂString, ByVal lpszAppName As String, ByVal lpszCalledParty As String, ByVal ÂlpszComment As String) As Long
    Declare Function tapiGetLocationInfo Lib "TAPI32.DLL" (ByVal lpszCountryCode As ÂString, ByVal lpszCityCode As String) As Long
#Else
    Declare Function tapiRequestMakeCall Lib "TAPI.DLL" (ByVal lpszDestAddress As ÂString, ByVal lpszAppName As String, ByVal lpszCalledParty As String, ByVal ÂlpszComment As String) As Long
    Declare Function tapiGetLocationInfo Lib "TAPI.DLL" (ByVal lpszCountryCode As ÂString, ByVal lpszCityCode As String) As Long
#End If

Notice that the code in Listing 35.1 uses the conditional compilation directives along with the code for both 16- and 32-bit Visual Basic 4.0. This ensures that the code will work in either version of Visual Basic 4.0 that you use.

Next you need to add two user-defined types to the module. These types encapsulate the parameters needed for the two TAPI calls. Using UDTs in this way reduces the complexity of your code and makes it easier to read. Add the code shown in Listing 35.2 to the general section of the module.


Listing 35.2. Adding the user-defined types.
'
'
 TAPILocation Type
Type TAPILocation
    Country As String * 1
    City As String * 3
End Type
'
' TAPIMakeCall Type
Type TAPICall
    Address As String
    AppName As String
    CalledParty As String
    Comment As String
End Type

After declaring the API routines and defining the type variables, you're ready to add the wrapper functions that will encapsulate the API calls. Listing 35.3 shows the function that supports the tapiGetLocationInfo API call. Add this to your module.


Listing 35.3. Adding the TAPIGetLocation function.
Public Function TAPIGetLocation() As TAPILocation
    '
    ' returns UDT w/ city and country
    '
    Dim lTapi As Long
    Dim cCountry As String * 1
    Dim cCity As String * 3
    '
    lTapi = tapiGetLocationInfo(cCountry, cCity)
    If lTapi >= 0 Then
        TAPIGetLocation.Country = cCountry
        TAPIGetLocation.City = cCity
    End If
    '
End Function

Notice that this function returns a UDT that contains both the country code and the city code for the workstation.

Note
The TAPIGetLocation function is not called in this project. It is included here for completeness. When you use this module for other TAPI projects, you'll have this function call already defined.

The second API wrapper function is the TAPIMakeCall function. This function accepts the TAPICall user-defined type as input and places an outbound call. Listing 35.4 shows the code for this function.


Listing 35.4. Adding the TAPIMakeCall function.
Public Function TAPIMakeCall(tapiRec As TAPICall) As Long
    '
    ' make an assisted TAPI call
    ' returns 0 if OK, <0 if error
    '
    TAPIMakeCall = tapiRequestMakeCall(tapiRec.Address, tapiRec.AppName, ÂtapiRec.CalledParty, tapiRec.Comment)
    '
End Function

This function returns a value that indicates the results of the TAPI request. If the value is less than 0, you've got an error condition. Only the first parameter is required (Address).

Tip
For a more detailed look at Assisted Telephony, refer to Chapter 29, "Writing TAPI-Assisted Applications."

That is the end of the Assisted TAPI support module. Save this module (VTAPI.BAS) and the project (VPHONE.VBP) before continuing.

The CallBack Modules

Since this project uses the SAPI Voice Command and Voice Text type libraries, you'll need to add a class module to your project for each of the two libraries. These classes were designed to allow high-level languages (like Visual Basic) to register services that require the use of notification callbacks. The callback module for the TTS engine will not be used in this project. However, you'll use the SR callback routines to trap and respond to spoken commands.

Warning
The function names for these callbacks cannot be altered. The OLE libraries for SR and TTS services are looking for these specific function names. If you use some other names, the callbacks will not work and you will get an error report when you request TTS or SR services.

First add the TTS callback functions. In this project, the functions will be empty, but you'll need to register them anyway. Add a class module to your project (Insert | Class Module). Set the class name to TTSCallBack, set its Instancing property to Creatable, MultiUse, and its Public property to TRUE. Now add the two functions from Listing 35.5 to the class.


Listing 35.5. Adding functions to the TTSCallBack class.
Option Explicit

Public Function SpeakingDone()
    '
    ' this method will execute when the
    ' TTS engine stops speaking text
    '
End Function

Public Function SpeakingStarted()
    '
    ' this method will execute when the
    ' TTS engine starts speaking text
    '
End Function

When you've completed the functions, save the module as TTSCBK.CLS.

Now add a second class module to the project. Set its Name property to SRCallBack, its Instancing property to Createable, MultiUse, and its Public property to TRUE. Now add the code shown in Listing 35.6 to the class.


Listing 35.6. Adding functions to the SRCallBack class.
Option Explicit

Function CommandRecognize(szCommand As String, dwID As Long)
    '
    ' fires off each time a command goes by
    '
    Dim cToken As String
    Dim cContent As String
    Dim iSpace As Integer
    Dim cReturn As String
    '
    szCommand = Trim(szCommand) & " "
    iSpace = InStr(szCommand, " ")
    cToken = UCase(Left(szCommand, iSpace - 1))
    cContent = Mid(szCommand, iSpace + 1, 255)
    '
    Select Case cToken
        Case "NEW"
            AddRec 'frmVPhone.cmdPhone_Click 0
        Case "EDIT"
            EditRec cContent
        Case "DELETE"
            DeleteRec cContent
        Case "PLACE"
            PlaceCall frmVPhone.txtDial, ""
        Case "FIND"
            frmVPhone.txtDial = LookUp(cContent)
        Case "DIAL"
            cReturn = LookUp(cContent)
            frmVPhone.txtDial = cReturn
            PlaceCall cReturn, cContent
    End Select
    '
End Function

Function CommandOther(szCommand As String, szApp As String, szState As String)

End Function

You'll notice that the only routine used is the CommandRecognize function. This routine looks at each command that passes through the speech recognition engine, parses the input and, if it's a known command, executes the appropriate code.

The reason the command line must be parsed is that several of the voice commands will be in the form of lists. You may remember that you can register command lists with the SR engine without knowing the members of the list ahead of time. In your project, you'll fill these lists with the names of people in the user's phone directory at run-time.

After you've entered the code, save this module as SRCBK.CLS before continuing with the chapter.

Building the LibVPhone Module

The LibVPhone module contains most of the code for the project. It is here that you'll put the routines that initialize the speech engines, load the database engine, and handle the processes of adding, editing, and deleting records from the phone book.

First, add a code module to the project. Set its Name property to LibVPhone and save it as VPHONE.BAS. Next, add the project-level declarations shown in Listing 35.7 to the general declaration section of the module.


Listing 35.7. Adding the project-level declarations.
Option Explicit
'
' establish voice command objects
Public objVCmd As Object
Public objVMenu As Object
Public lVMenuIdx As Long
Public objVText As Object
Public iVType As Integer
Public cUserMsgs() As String
'
' spoken message handles
Public Const ttsHello = 0
Public Const ttsVPhone = 1
Public Const ttsList = 2
Public Const ttsExit = 3
Public Const ttsEdit = 4
'
' database objects
Public wsPhone As Workspace
Public dbPhone As Database
Public rsPhone As Recordset

The code here declares the required objects for SAPI and for database services. There are also several constants that will be used to point to text messages that will be spoken by the engine at requested times in the program.

Adding the Voice Command Routines

Next add the routine that will start the SAPI service initialization. Add a subroutine called InitVCmd to the project and enter the code from Listing 35.8.


Listing 35.8. Adding the InitVCmd routine.
Public Sub InitVCmd()
    '
    ' init voice command
    Set objVCmd = CreateObject("Speech.VoiceCommand")
    objVCmd.Register ""
    objVCmd.Awake = True
    objVCmd.Callback = App.EXEName & ".SRCallBack"
    '
    ' init voice menu
    BuildVMenu
    objVMenu.Active = True
    '
End Sub

This code initializes the voice command object that will be used to handle speech recognition services for the program. Notice the line that registers the SRCallBack class. The class name is prefixed with the application name. You must set this application name manually. To do this, select Tools | Options | Project and enter VPHONE in the Project Name field.

Warning
You must update the Project Name field to match the App.EXEName value before you attempt to run the project. If you fail to do this, you will receive errors from the SR engine.

The InitVCmd routine calls another routine to handle the creation of the command menu. Add a new subroutine to the module called BuildVMenu and enter the code shown in Listing 35.9.


Listing 35.9. Adding the BuildVMenu routine.
Public Sub BuildVMenu()
    '
    ' build a voice menu
    '
    Dim iType As Integer
    Dim cState As String
    Dim lLangId As Long
    Dim cDialect As String
    Dim cAppName As String
    Dim cVMenu(3) As String
    Dim x As Integer
    '
    ' set params
    cAppName = App.EXEName
    cState = "Voice Phone"
    cDialect = ""
    lLangId = 1033
    iType = vcmdmc_CREATE_TEMP
    '
    ' set menu commands
    cVMenu(1) = "New Record"
    cVMenu(2) = "Place Call"
    cVMenu(3) = "Exit"
    '
    ' now instance menu
    Set objVMenu = objVCmd.MenuCreate( _
        cAppName, cState, lLangId, cDialect, iType)
    '
    ' add simple commands to menu
    For x = 1 To 3
        lVMenuIdx = lVMenuIdx + 1
        objVMenu.Add lVMenuIdx, cVMenu(x), "Voice Phone Commands", "Voice Phone ÂCommands"
    Next x

    '
    ' create list commands
    lVMenuIdx = lVMenuIdx + 1
    objVMenu.Add lVMenuIdx, "Edit <Name>", "Edit Name", "Edit Name"
    '
    lVMenuIdx = lVMenuIdx + 1
    objVMenu.Add lVMenuIdx, "Delete <Name>", "Delete Name", "Delete Name"
    '
    lVMenuIdx = lVMenuIdx + 1
    objVMenu.Add lVMenuIdx, "Dial <Name>", "Dial Name", "Dial Name"
    '
    lVMenuIdx = lVMenuIdx + 1
    objVMenu.Add lVMenuIdx, "Find <Name>", "Find Name", "Find Name"
    '

End Sub

There's a lot going on in this routine. First, internal variables are declared and set to their respective values. Next, the menu object is created using the MenuCreate method. After the menu object is created, the Add method of the menu object is used to add simple voice commands to the menu. Finally, the list commands are added to the menu (Edit, Delete, Dial, and Find). All these list commands refer to the Name list. This list will be filled in at run-time.

Now add the routine that will fill in the name list for the voice commands. Create a new subroutine called LoadNameList and enter the code you see in Listing 35.10.


Listing 35.10. Adding the LoadNameList routine.
Public Sub LoadNameList()
    '
    ' fill list of names for vCmd
    '
    Dim cNameList As String
    Dim iNameCount As String
    '
    cNameList = ""
    iNameCount = 0
    rsPhone.MoveFirst
    Do Until rsPhone.EOF
        cNameList = cNameList & rsPhone.Fields("Name") & Chr(0)
        iNameCount = iNameCount + 1
        rsPhone.MoveNext
    Loop
    '
    objVMenu.ListSet "Name", iNameCount, cNameList
    '
End Sub

As you can see, the LoadNameList routine reads each record in the open database table and adds the names, separated by chr(0), to a single string. This string is the list that is registered with the menu object using the ListSet method.

Adding the Voice Text Routines

Now you're ready to add the routines that will initialize the Voice Text object for TTS services. First, add a new subroutine called InitVTxt and enter the code shown in Listing 35.11.


Listing 35.11. Adding the InitVTxt routine.
Public Sub InitVTxt()
    '
    ' init voice text
    Set objVText = CreateObject("Speech.VoiceText")
    objVText.Register "", App.EXEName
    objVText.Enabled = True
    objVText.Callback = App.EXEName & ".TTSCallBack"
    '
End Sub

This looks a lot like the InitVCmd routine. Notice the callback registration here, too. Again, it is very important that the Project Title property (Tools | Options | Project | Project Title) is set to the same value as the application executable filename. If not, you won't be able to use TTS services in your application.

The only other TTS support routine you'll need is the one that builds a set of messages that will be spoken by the TTS engine at different times in the program. Add a new subroutine called LoadMsgs to the module and enter the code from Listing 35.12.


Listing 35.12. Adding the LoadMsgs routine.
Public Sub LoadMsgs()
    '
    ' build a list of user messages
    '
    ReDim cUserMsgs(5)
    '
    cUserMsgs(0) = "Hello. Welcome to Voice Phone."
    cUserMsgs(1) = "Press New, Edit, or Delete to modify the phone list."
    cUserMsgs(2) = "Select a name from the list and press Place Call to dial the Âphone."
    cUserMsgs(3) = "Press Exit to end this program."
    cUserMsgs(4) = "Enter name and phone number, then press OK or cancel."
    '
End Sub

The public constants declared at the top of this module will be used to point to each of these messages. This will make it easier to read the code.

Adding the Database Engine Routines

Next you need to add several routines to initialize and support database services. First, add the InitDB subroutine to your project and enter the code shown in Listing 35.13.


Listing 35.13. Adding the InitDB routine.
Public Sub InitDB()
    '
    ' set up db stuff
    '
    Dim cDBName As String
    cDBName = App.Path & "\VPHONE.MDB"
    On Error Resume Next
    Open cDBName For Input As 1
    If Err <> 0 Then
        Close 1
        BuildDatabase
    Else
        Close 1
    End If
    On Error GoTo 0
    '
    OpenDatabase
    '
End Sub

Warning
Using the App.Path property to set the location of the MDB file assumes that you have created a project directory. If you attempt to place the project and the MDB files in the root directory of a drive, you'll receive error messages. It's recommended that you create a project directory and store the MDB in that directory.

The only real purpose of this routine is to check for the existence of the database file. If it is not found, the BuildDatabase routine is called before the OpenDatabase routine. Now add the BuildDatabase subroutine to your project as shown in Listing 35.14.


Listing 35.14. Adding the BuildDatabase routine.
Public Sub BuildDatabase()
    '
    ' build new database
    '
    On Error GoTo LocalErr
    '
    Dim ws As Workspace
    Dim db As Database
    Dim cSQL(3) As String
    Dim x As Integer
    '
    cSQL(1) = "CREATE TABLE VPHONE (Name TEXT(20),Phone TEXT(20));"
    cSQL(2) = "INSERT INTO VPHONE VALUES ('Information','1-555-1212');"
    cSQL(3) = "INSERT INTO VPHONE VALUES('SAMS Publishing','1-800-428-5331');"
    '
    Set ws = DBEngine.Workspaces(0)
    Set db = CreateDatabase(App.Path & "\VPHONE.MDB", dbLangGeneral)
    '
    For x = 1 To 3
        db.Execute cSQL(x), dbFailOnError
    Next x
    '
    db.Close
    Set db = Nothing
    Set ws = Nothing
    '
    Exit Sub
    '
LocalErr:
    MsgBox Err.Description & Chr(13) & Err.Source, vbCritical, "BuildDatabase"
    '
End Sub

The code here is quite handy. First, the database file is created. Then, three SQL statements are executed. The first one creates the new VPHONE table. The second two statements add two records to the new table.

Tip
This is a great technique for building databases upon installation of a new application. This way, users don't have to worry about copying data files, confusing older versions of the data, and so on. Even better, if you need to start the database from scratch, all you need to do is remove the database file and start the program-it will create the initial database for you!

After the BuildDatabase routine has been added, you need to add the code that will open the existing database and select the phone records. Create the OpenDatabase subroutine and enter the code from Listing 35.15.


Listing 35.15. Adding the OpenDatabase routine.
Public Sub OpenDatabase()
    '
    ' open phone database
    '
    On Error GoTo LocalErr
    '
    Dim cSelect As String
    '
    cSelect = "SELECT * FROM VPHONE"
    '
    Set wsPhone = DBEngine.Workspaces(0)
    Set dbPhone = wsPhone.OpenDatabase(App.Path & "\VPHONE.MDB")
    Set rsPhone = dbPhone.OpenRecordset(cSelect, dbOpenDynaset)
    '
    Exit Sub
    '
LocalErr:
    MsgBox Err.Description & Chr(13) & Err.Source, vbCritical, "OpenDatabase"
    '
End Sub

Nothing real fancy here. The database is opened and a single SQL SELECT statement is executed to create a Dynaset-type recordset for use in the program.

There are four remaining database service support routines:

The AddRec and EditRec routines use a secondary dialog form (frmVRec), which you'll build in the next section of this chapter.

Create a new subroutine called AddRec and enter the code from Listing 35.16.


Listing 35.16. Adding the AddRec routine.
Public Sub AddRec()
    '
    ' add a new record
    '
    Load frmVRec
    frmVRec.txtName = ""
    frmVRec.txtPhone = ""
    frmVRec.lblaction = "ADD"
    frmVRec.Show vbModal
    '
End Sub

Now add the EditRec subroutine and enter the code from Listing 35.17.


Listing 35.17. Adding the EditRec routine.
Public Sub EditRec(cName As String)
    '
    ' edit an existing record
    '
    Dim iAns As Integer
    '
    cName = Trim(cName)
    rsPhone.FindFirst "Name='" & cName & "'"
    If rsPhone.NoMatch = False Then
        Load frmVRec
        frmVRec.txtName = rsPhone.Fields("Name")
        frmVRec.txtPhone = rsPhone.Fields("Phone")
        frmVRec.lblaction = "EDIT"
        frmVRec.Show vbModal
    End If
    '
End Sub

The DeleteRec subroutine consists of a single message box confirmation and the delete action. Add the code in Listing 35.18 to the module.


Listing 35.18. Adding the DeleteRec routine.
Public Sub DeleteRec(cName As String)
    '
    ' delete record from table
    '
    Dim iAns As Integer
    '
    cName = Trim(cName)
    iAns = MsgBox(cName, vbExclamation + vbYesNo, "Delete Record")
    If iAns = vbYes Then
        rsPhone.FindFirst "Name = '" & cName & "'"
        If rsPhone.NoMatch = False Then
            rsPhone.Delete
            rsPhone.MoveNext
        End If
    End If
    '
End Sub

Finally, add a new function called LookUp to the module. This function takes one parameter (the Name) and returns the corresponding phone number. Enter the code from Listing 35.19.


Listing 35.19. Adding the LookUp function.
Public Function LookUp(cName As String) As String
    '
    ' lookup a name in the list
    ' return the phone number
    '
    Dim cRtn As String
    '
    cName = Trim(cName)
    rsPhone.FindFirst "Name = '" & cName & "'"
    If rsPhone.NoMatch = True Then
        cRtn = ""
    Else
        cRtn = rsPhone.Fields("Phone").Value
    End If
    '
    LookUp = cRtn
    '
End Function

Adding the TAPI and Form Support Routines

Only two support routines are left. The PlaceCall routine is used to perform the Assisted TAPI service request. Listing 35.20 shows you the code for this routine.


Listing 35.20. Adding the PlaceCall routine.
Public Sub PlaceCall(cPhone As String, cName As String)
    '
    Dim tCall As TAPICall
    Dim lRtn As Long
    '
    If Trim(cPhone) <> "" Then
        tCall.Address = cPhone
        tCall.AppName = App.EXEName
        tCall.CalledParty = cName
        tCall.Comment = ""
        lRtn = TAPIMakeCall(tCall)
        If lRtn < 0 Then
            MsgBox lRtn, vbcritical, "TAPI Error!"
        End If
    End If
    '
End Sub

The last routine is one that is used to center dialog boxes on the screen. Add the code from Listing 35.21 to your project.


Listing 35.21. Adding the CenterForm routine.
Public Sub CenterForm(frm As Form)
    '
    frm.Left = (Screen.Width - frm.Width) / 2
    frm.Top = (Screen.Height - frm.Height) / 2
    '
End Sub

That is the end of the LibVPhone module code. Save this module (VPHONE.BAS) and the project (VPHONE.VBP) before you move to the next section.

Laying Out the VPhone Form

The Vphone form is the main dialog box of the project. The first step is to lay out the controls on the form. Then you can add the menu and the code behind the form. Refer to Figure 35.2 and Table 35.1 for details on the size and position of the controls on the form.

Figure 35.2 : Laying out the Vphone form.

Table 35.1. Controls for the VPhone form.
ControlProperty Setting
VB.Form Name frmVPhone
 BorderStyle 3 'Fixed Dialog
 Caption "Form1"
 Height 3795
 Icon "VPHONE.ICO"
 Left 1755
 MaxButton 0 'False
 MinButton 0 'False
 ShowInTaskbar -1 'True
 Top 2160
 Width 6720
VB.CommandButton Name cmdPhone
 Caption "&Help"
 Height 300
 Index 4
 Left 5280
 TabIndex 7
 Top 2580
 Width 1200
VB.CommandButton Name cmdPhone
 Caption "E&xit"
 Height 300
 Index 3
 Left 5280
 TabIndex 6
 Top 2160
 Width 1200
VB.CommandButton Name cmdPhone
 Caption "&Edit"
 Height 300
 Index 2
 Left 5280
 TabIndex 5
 Top 1740
 Width 1200
VB.CommandButton Name cmdPhone
 Caption "&Delete"
 Height 300
 Index 1
 Left 5280
 TabIndex 4
 Top 1320
 Width 1200
VB.CommandButton Name cmdPhone
 Caption "&New"
 Height 300
 Index 0
 Left 5280
 TabIndex 3
 Top 900
 Width 1200
VB.ListBox Name List1
 Font name="MS LineDraw"
  size=8.25
 Height 2370
 Left 120
 TabIndex 2
 Top 540
 Width 4995
VB.CommandButton Name cmdDial
 Caption "&Place Call"
 Height 300
 Left 120
 TabIndex 1
 Top 120
 Width 1200
VB.TextBox Name txtDial
 Height 300
 Left 1440
 TabIndex 0
 Text "Text1"
 Top 120
 Width 3660
VB.Image Name Image1
 Height 615
 Left 5520
 Picture "VPHONE.ICO"
 Stretch -1 'True
 Top 120
 Width 675

Note
The VPHONE.ICO icon file that is used in this project can be found on the CD-ROM that ships with the book. Be sure to copy that file to the application directory before you start the project.

Along with the control layout, there is also a small menu that goes with the VPhone form. Refer to Figure 35.3 and Table 35.2 for details on laying out the Vphone menu.

Figure 35.3 : Laying out the Vphone menu.

Table 35.2. The VPhone menu.
LevelProperty Setting
Top Level Name mnuFile
 Caption "&File"
Level 2 Name mnuFileItem
 Caption "&New..."
 Index 0
Level 2 Name mnuFileItem
 Caption "&Edit..."
 Index 1
Level 2 Name mnuFileItem
 Caption "&Delete"
 Index 2
Level 2 Name mnuFileItem
 Caption "-"
 Index 3
Level 2 Name mnuFileItem
 Caption "&Place Call"
 Index 4
Level 2 Name mnuFileItem
 Caption "-"
 Index 5
Level 2 Name mnuFileItem
 Caption "E&xit"
 Index 6
Top Level Name mnuHelp
 Caption "&Help"
Level 2 Name mnuHelpItem
 Caption "Help..."
 Index 0
Level 2 Name mnuHelpItem
 Caption "&About..."
 Index 1

Note
Be sure to lay out the menu using menu arrays. You'll add code to the menu array in the next section.

After laying out the form, save it as VPHONE.FRM and the save the project as VPHONE.VBP before moving on to the next section.

Coding the VPhone Form

There's not a lot of code for the VPhone form. Most of the important stuff was built in the LibVPhone module. However, you'll need to add code to the control events that call the LibVPhone routines.

First, add the code from Listing 35.22 to the Form_Load event.


Listing 35.22. Coding the Form_Load event.
Private Sub Form_Load()
    '
    ' set form properties
    Me.Caption = "Voice Phone"
    Me.Icon = LoadPicture(App.Path & "\vphone.ico")
    Image1.Picture = LoadPicture(App.Path & "\vphone.ico")
    Image1.Stretch = True
    CenterForm Me
    '
    ' initialize objects
    InitVCmd ' start SR engine
    InitVTxt ' start TTS engine
    InitDB ' state DB engine
    LoadNameList ' fill SR list
    LoadList ' fill onscreen list
    LoadMsgs ' fill TTS list
    '
    ' set variables
    txtDial = ""
    iVType = vtxtst_READING + vtxtsp_NORMAL
    '
    objVText.Speak cUserMsgs(ttsHello), iVType
    '
End Sub

The code in Listing 35.22 sets up some basic form properties and then calls the initialization routines for the various services. Finally, the application sends out a greeting message to the user.

Next, add the code in Listing 35.23 to the Form_Unload event.


Listing 35.23. Coding the Form_Unload event.
Private Sub Form_Unload(Cancel As Integer)
    '
    ' destroy objects
    Set objVMenu = Nothing
    Set objVCmd = Nothing
    Set objVText = Nothing
    Set rsPhone = Nothing
    Set dbPhone = Nothing
    Set wsPhone = Nothing
    '
End Sub

This code destroys the programming objects created at startup. It's always a good idea to do this before exiting your application.

The Form_Load event calls a custom routine called LoadList. This subroutine fills the onscreen list box with the names and phone numbers from the database. Add the LoadList subroutine to your form and enter the code from Listing 35.24.


Listing 35.24. Adding the LoadList routine.
Public Sub LoadList()
    '
    ' fill onscreen list with name/phone
    '
    Dim cLine As String
    List1.Clear
    '
    ' add header line first
    List1.AddItem "NAME" & String(16, ".") & Space(5) & "PHONE" & String(15, ".")
    '
    ' now add database items
    rsPhone.MoveFirst
    Do Until rsPhone.EOF
        cLine = Space(50)
        Mid(cLine, 1, 20) = Left(rsPhone.Fields("Name"), 20)
        Mid(cLine, 26, 20) = Left(rsPhone.Fields("Phone"), 20)
        List1.AddItem cLine
        rsPhone.MoveNext
    Loop
    '
End Sub

Next, add the code from Listing 35.25 to handle the user selections on the main command button array (cmdPhone).


Listing 35.25. Coding the cmdPhone_Click event.
Private Sub cmdPhone_Click(Index As Integer)
    '
    ' handle user selections
    '
    Dim cName As String
    Dim iAns As Integer
    Dim x As Integer
    '
    Select Case Index
        Case 0 ' add a record
            AddRec
        Case 1 ' delete a record
            If List1.ListIndex > 0 Then ' skip first line!
                cName = Left(List1.List(List1.ListIndex), 20)
                DeleteRec cName
            End If
        Case 2 ' edit a record
            If List1.ListIndex > 0 Then
                cName = Left(List1.List(List1.ListIndex), 20)
                EditRec cName
            End If
        Case 3 ' help
            objVText.Speak cUserMsgs(ttsVPhone), iVType
            objVText.Speak cUserMsgs(ttsList), iVType
            objVText.Speak cUserMsgs(ttsExit), iVType
        Case 4 ' exit
            Unload Me
            End
    End Select
    '
    ' update lists
    LoadList
    LoadNameList
    '
End Sub

Notice that help is delivered in the form of three spoken messages to the user.

You also need to code the cmdDial_Click event. This code fires each time the user presses the Place Call command button. Enter the code from Listing 35.26.


Listing 35.26. Coding the cmdDial_Click event.
Private Sub cmdDial_Click()
    '
    ' try to place the call
    '
    If Trim(txtDial) <> "" Then
        PlaceCall txtDial, "" ' call dialer
    Else
        If List1.ListIndex > 0 Then
            List1_DblClick
            PlaceCall txtDial, ""
        Else
            MsgBox "Select a Name from the List", vbExclamation, "Place a Call"
        End If
    End If
    '
End Sub

The cmdDial_Click event will first check the txtDial control to see if a phone number is present. If it is, that number is used to place the call. If no number is present, the routine will see if the user has selected a name from the list box. If so, the routine first calls the List1_DblClick event to force the name into the txtDial text box, then calls the PlaceCall routine to make the call. Finally, if none of this works, a message is displayed telling the user to select a name from the list.

Now add code to the List1_DblClick event to move the phone number from the list box into the txtDial text box. Listing 35.27 shows how this is done.


Listing 35.27. Coding the List1_DblClick event.
Private Sub List1_DblClick()
    '
    ' select name from the list
    '
    Dim cReturn As String
    '
    If List1.ListIndex > 0 Then
        cReturn = Left(List1.List(List1.ListIndex), 20)
        txtDial = LookUp(cReturn)
    End If
    '
End Sub

The only code left to create is the code to handle the menu arrays. Listing 35.28 shows the code for both the mnuFileItem_Click and the mnuHelpItem_Click events. Add these two code modules to your form.


Listing 35.28. Coding the menu array events.
Private Sub mnuFileItem_Click(Index As Integer)
    '
    ' handle user clicks
    '
    Select Case Index
        Case 0 ' new
            cmdPhone_Click 0
        Case 1 ' edit
            cmdPhone_Click 1
        Case 2 ' delete
            cmdPhone_Click 2
        Case 4 ' place call
            cmdDial_Click
        Case 6 ' exit
            Unload Me
    End Select
    '
End Sub

Private Sub mnuHelpItem_Click(Index As Integer)
    '
    Select Case Index
        Case 0 ' help screen
            frmHelp.Show vbModal
        Case 1 ' about
            frmAbout.Show vbModal
    End Select
    '
End Sub

That's the end of the code for the VPhone form. Save the form (VPHONE.FRM) and the project (VPHONE.VBP) before continuing.

Laying Out the Support Forms

There are three small support forms for the VPhone project. The VRec form is used to handle adds and edits to the data table. The Vhelp form displays a set of help strings, and the VAbout dialog box just shows the standard program information.

The VRec Form

The VRec form is used to handle adding new records to the data table and editing existing records. There are two text boxes, three label controls, and three command buttons on the form. Add a new form to the project and lay out the controls on the form as shown in Figure 35.4 and Table 35.3.

Figure.35.4 : Laying out the VRec form.

Table 35.3. The VRec form controls.
ControlProperty Setting
VB.Form Name frmVRec
 Caption "Form2"
 Height 1680
 Left 1725
 Top 2445
 Width 5340
VB.CommandButton Name cmdRec
 Caption "&Help"
 Height 300
 Index 2
 Left 3900
 TabIndex 7
 Top 120
 Width 1200
VB.CommandButton Name cmdRec
 Caption "Cancel"
 Height 300
 Index 1
 Left 3900
 TabIndex 5
 Top 840
 Width 1200
VB.CommandButton Name cmdRec
 Caption "OK"
 Height 300
 Index 0
 Left 3900
 TabIndex 4
 Top 480
 Width 1200
VB.TextBox Name txtPhone
 Height 300
 Left 1380
 TabIndex 3
 Text "Text1"
 Top 480
 Width 2400
VB.TextBox Name txtName
 Height 300
 Left 1380
 TabIndex 1
 Text "Text1"
 Top 120
 Width 2400
VB.Label Name lblAction
 Caption "lblAction"
 Height 255
 Left 120
 TabIndex 6
 Top 900
 Visible 0 'False
 Width 1155
VB.Label Name lblPhone
 Caption "Phone:"
 Height 300
 Left 120
 TabIndex 2
 Top 480
 Width 1200
VB.Label Name lblName
 Caption "Name:"
 Height 300
 Left 120
 TabIndex 0
 Top 120
 Width 1200

Notice that the lblAction label control is an invisible control. This control is used to pass information from the VPhone form to the VRec form, and is not used for direct display at run-time.

There is very little code needed for the VRec form. Listing 35.29 shows the code for the Form_Load and Form_Activate events. Add this code to your form.


Listing 35.29. Coding the Form_Load and Form_Activate events.
Private Sub Form_Activate()
    '
    ' tell them!
    '
    If Len(txtName.Text) <> 0 Then
        objVText.Speak txtName.Text, iVType
    End If
    If Len(txtPhone.Text) <> 0 Then
        objVText.Speak txtPhone.Text, iVType
    End If
    '
End Sub

Private Sub Form_Load()
    '
    Me.Caption = "Add/Edit a Record"
    CenterForm Me
    '
    txtName = "'"
    txtPhone = ""
    '
End Sub

Nothing fancy in the Form-Load event. The Form_Activate event contains code that will read the database record aloud to the user.

The only other code needed on the form is code for the cmdRec_Click event. Enter the code from Listing 35.30 into your project.


Listing 35.30. Coding the cmdRec_Click event.
Private Sub cmdRec_Click(Index As Integer)
    '
    ' handle user selections
    '
    Select Case Index
        Case 0 ' OK
            If Len(Trim(txtName)) <> 0 Then
                If lblaction = "ADD" Then
                    rsPhone.AddNew
                Else
                    rsPhone.Edit
                End If
                '
                rsPhone.Fields("Name") = txtName
                rsPhone.Fields("Phone") = txtPhone
                rsPhone.Update
            End If
            Unload Me
        Case 1 ' cancel
            Unload Me
        Case 2 ' help
            objVText.Speak cUserMsgs(ttsEdit), iVType
    End Select
    '
End Sub

You'll notice that this routine checks the contents of the invisible label control (lblAction) to see if this is an "add" or "edit" form. If this is an "add" form, the contents of the control are used to add a new record to the table.

That's it for this form. Save the form as VREC.FRM and update the project (VPHONE.VBP) before going on to the next section.

The VHelp Form

The VHelp form is a simple one-screen help box. This screen not only displays help tips on using the Voice Phone, it also speaks those tips to the user. Refer to Figure 35.5 and Table 35.4 for help in laying out the form.

Figure 35.5 : Laying out the Vhelp form.

Table 35.4. The VHelp form controls.
ControlProperty Setting
VB.Form Name frmHelp
 BorderStyle 3 'Fixed Dialog
 Caption "Help on Voice Phone"
 Height 3375
 Left 2745
 LinkTopic "Form1"
 MaxButton 0 'False
 MinButton 0 'False
 Top 1875
 Width 4230
VB.CommandButton Name cmdOK
 Caption "OK"
 Default -1 'True
 Height 300
 Left 2760
 TabIndex 1
 Top 2520
 Width 1200
VB.Label Name Label1
 BorderStyle 1 'Fixed Single
 Caption "Label1"
 Height 2175
 Left 120
 TabIndex 0
 Top 180
 Width 3855

The only code needed for the form is the Form_Load event and the one-line cmdOK_Click event. Listing 35.31 shows both these routines. Add this code to your form.


Listing 35.31. Coding the Form_Load and cmdOK_Click events.
Private Sub cmdOK_Click()
    Unload Me
End Sub

Private Sub Form_Load()
    '
    ' setup help dialog
    '
    Dim x As Integer
    '
    Me.Caption = "Help on Voice Phone"
    Me.Icon = LoadPicture(App.Path & "\vphone.ico")
    CenterForm Me
    '
    Label1 = ""
    For x = ttsHello To ttsExit
        Label1 = Label1 & cUserMsgs(x) & Chr(13) & Chr(13)
    Next x
    '
    objVText.Speak Label1.Caption, iVType
    '
End Sub

That's it for the VHelp form. Save the form as VHELP.FRM and update the project (VPHONE.VBP) before moving on to the last coding section of the project.

The VAbout Form

The VAbout form shows the standard application information. This information is read from the properties of the App object. You set these properties using the File | Make EXE | Options menu selection. Table 35.5 shows the App object properties and their settings.

Table 35.5. The App object properties.
PropertySetting
Auto Increment Checked ON
File Description Demonstrates SAPI andTAPI
Legal Copyright Copyright 1998 MCA/Sams Publishing
Product Name Voice Phone

After setting these properties, you're ready to lay out the form and add the code. Refer to Figure 35.6 and Table 35.6 for the size and position of the controls on the form.

Figure 35.6 : Laying out the VAbout form.

Table 35.6. The VAbout form controls.
ControlProperty Setting
VB.Form Name frmAbout
 BorderStyle 3 'Fixed Dialog
 Caption "Form1"
 Height 2610
 Left 2685
 MaxButton 0 'False
 MinButton 0 'False
 Top 1890
 Width 4980
VB.CommandButton Name Command1
 Caption "&OK"
 Height 300
 Left 3540
 TabIndex 1
 Top 1800
 Width 1200
VB.Image Name Image1
 Height 1455
 Left 120
 Top 180
 Width 1395
VB.Label Name Label1
 Caption "Label1"
 Height 1455
 Left 1800
 TabIndex 0
 Top 180
 Width 2895

After laying out the form, you need to add code to the Form_Load and Command1_Click events. Listing 35.32 shows all the code you need for the form.


Listing 35.32. Coding the Form_Load and Command1_Click events.
Private Sub Command1_Click()
    Unload Me
End Sub

Private Sub Form_Load()
    '
    ' set up about dialog
    '
    Dim cVersion As String
    Me.Caption = "About " & App.ProductName
    Me.Icon = LoadPicture(App.Path & "\vphone.ico")
    Image1.Stretch = True
    Image1.Picture = LoadPicture(App.Path & "\vphone.ico")
    '
    cVersion = "Version: " & CStr(App.Major) & "." & CStr(App.Minor) & "." & ÂCStr(App.Revision)
    Label1 = App.ProductName & Chr(13) & Chr(13)
    Label1 = Label1 & App.LegalCopyright & Chr(13) & Chr(13)
    Label1 = Label1 & App.FileDescription & Chr(13) & Chr(13)
    Label1 = Label1 & cVersion
    '
    CenterForm Me
    '
    objVText.Speak Label1.Caption, iVType
    '
End Sub

Notice the use of the App object properties to fill in the Label control. This is a great way to provide up-to-date application version information for your projects.

Save the form as VABOUT.FRM and update the project file (VPHONE.VBP). That's the last of the coding for this project. Before moving on to the next section, compile the project and check for any errors. Once you're sure all the bugs have been ironed out, you're ready to test your Voice Phone.

Testing Voice Phone

When you first start Voice Phone, you'll get a short friendly greeting ("Hello. Welcome to Voice Phone."). Once the program has started, several voice commands have been registered with the Windows operating system. You can view these available commands by asking the workstation, "What can I say? "or by clicking the Microsoft Voice icon in the system tray and selecting "What can I say?" from the context menu. Figure 35.7 shows what your display should look like.

Figure 35.7 : Viewing the available Microsoft voice commands.

New data table records can be added by pressing the New button or by speaking the New Record command. You can also edit an existing record by selecting it from the list and pressing Edit or by speaking the command Edit <Name> where <Name> is the name of the person whose record you wish to edit (see Figure 35.8).

Figure 35.8 : Editing an existing Voice Phone records.

You can place a call by selecting a name from the list and pressing the Place Call button. Or you can simply tell Voice Phone to Dial <Name> where <Name> is the name of the person you wish to call. For example, if you wanted to call Susan, you'd speak the command "Dial Susan." Voice Phone will look up Susan's phone number, and place the call for you.

Since this project is using Assisted TAPI, the actual handling of the call is performed by the application on the workstation that is registered to handle Assisted TAPI requests. If you have Microsoft Phone installed on your workstation, you'll see Microsoft Phone appear and handle the call. If you have not installed any special telephony support applications, the default dialer, DIALER.EXE, will appear.

Finally, you can get help by pressing the Help button. This will force Voice Phone to speak helpful tips to you. If you want to view the help tips, select Help | Help from the menu (see Figure 35.9).

Figure 35.9: Viewing the Help screen.

Summary

In this chapter you built an application that combined SAPI and TAPI services to create a "hands-free" telephone interface. You learned how to register speech recognition and text-to-speech services, how to register Assisted TAPI services, and how to use both services to access database information and place telephone calls using the registered telephony application for handling Assisted TAPI service requests.

In the next chapter, you'll learn how to build an e-mail client that records audio messages instead of handling text messages.