Login| Sign Up| Help| Contact|

Patent Searching and Data


Title:
VOICE RESPONSE SYSTEM WITH PROGRAMMING LANGUAGE EXTENSION
Document Type and Number:
WIPO Patent Application WO/1996/016500
Kind Code:
A1
Abstract:
A telephony voice response system includes a database language sequencer (206-N), a database control module (208-N) having a plurality of procedures callable by the database language for performing database operations, and a telephony control module (210-N) having a plurality of procedures callable by the database language sequencer for performing telephony operations. The telephony operations include speaking predefined prompts onto a telephony channel, receiving and storing DTMF-encoded input from the telephony channel, and recording audio input from the telephony channel. The database language sequencer calls the database control module procedures and the telephony control module procedures in a sequence defined by a program prepared according to a database language. The telephony voice response system can control multiple telephony chanels by running a separate task (204-N) for each such channel under a multitasking operating system. A common channel server task is provided which manages the resources of the telephony card for all of the individual channel tasks.

Inventors:
LOFGREN DAN M
DIETRICH WILLIAM A
Application Number:
PCT/US1995/015537
Publication Date:
May 30, 1996
Filing Date:
November 22, 1995
Export Citation:
Click for automatic bibliography generation   Help
Assignee:
VOYSYS CORP (US)
International Classes:
H04M3/493; (IPC1-7): H04M1/57; H04M1/64; H04M1/50
Foreign References:
US5354069A1994-10-11
US5113430A1992-05-12
US4695977A1987-09-22
US5255305A1993-10-19
Download PDF:
Claims:
1. l.
2. Telephony server apparatus, for use with a plurality of telephony channels, comprising: a processor structure; channel control hardware coupled to said telephony channels and to said processor structure; and a memory structure having stored therein a plurality of channel programs each associated with a respective one of said telephony channels, xnultitasking operating system software instructions executable by said processor structure, database engine software instructions executable by said processor structure under a different task of said operating system for each of said telephony channels, and a database, said database engine software instructions including instructions which, when executing under a given task of said operating system, interpret the channel program associated with the telephony channel of said given task and perform both database and telephony operations in response to such channel program associated with the telephony channel of said given task, said database operations including at least one operation from the group consisting of reading data from and writing data to said database, and said telephony operations including at least one operation from the group consisting of speaking a predefined prompt onto the telephony channel associated with the given task, receiving and storing DTMFencoded input from the telephony channel associated with the given task, and recording audio input from the telephony channel associated with the given task.
3. Apparatus according to claim 1, wherein said processor structure includes no more than one processor.
4. Apparatus according to claim l, wherein said memory structure includes both semiconductor memory and rotating memory.
5. Apparatus according to claim 5, wherein the readwrite data portion of the task associated with each particular one of said telephony channel includes the channel program associated with said particular telephony channel, and wherein all of said channel programs are the same.
6. Apparatus according to claim 1, wherein each of said tasks includes an executable portion and a read write data portion, the readwrite data portion being separate for each of said tasks, and the executable portion being common to all of said tasks.
7. Apparatus according to claim 1, wherein said database operations include all operations from said group consisting of reading data from and writing data to said database, and wherein said telephony operations include all operations from said group consisting of speaking a predefined prompt onto the telephony channel associated with the given task, receiving and storing DTMFencoded numerical input from the telephony channel associated with the given task, and digitally recording audio input from the telephony channel associated with the given task.
8. Apparatus according to claim 1, wherein said telephony operations further include the operation of waiting for a ring signal from the telephony channel associated with the given task.
9. Apparatus according to claim 1, wherein said telephony operations further include the operation of answering a call from the telephony channel associated with the given task.
10. Apparatus according to claim 1, wherein said telephony operations further include the operation of hanging up a call on the telephony channel associated with the given task.
11. Apparatus according to claim 1, wherein said telephony operations further include the operation of dialing a call on the telephony channel associated with the given task.
12. Telephony server apparatus, for use with a first telephony channel, comprising: a processor structure; channel control hardware coupled to said first telephony channel; and a memory βtructure having stored therein a database and software instructions executable by said processor structure, said software instructions including: a database language sequencer; a database control module having a plurality of procedures callable by said database language sequencer for performing at least one of the database operations from the group consisting of reading selected data from and writing specified data to said database; and a telephony control module having a plurality of procedures callable by said database language sequencer for performing at least one of the telephony operations from the group consisting of speaking a predefined prompt onto the first telephony channel, receiving and storing DTMFencoded input from the first telephony channel, and recording audio input from the first telephony channel, said database language sequencer calling said database control module procedures and said telephony control module procedures in a sequence defined by a program which satisfies predefined syntax rules of a predefined database language.
13. Apparatus according to claim 11, wherein said database language sequencer comprises: said program; and an interpreter which interprets said program to develop said sequence in which said database language sequencer calls said database control module procedures and said telephony control module procedures.
14. Apparatus according to claim 11, wherein said database language sequencer comprises a product produced by the method comprising the steps of: providing said program; and converting said program to software instructions executable by said processor structure.
15. Apparatus according to claim 11, wherein said processor structure includes no more than one processor.
16. Apparatus according to claim 11, wherein said memory structure includes both semiconductor memory and rotating memory.
17. Apparatus according to claim 11, wherein said database operations include all operations from said group consisting of reading data from and writing data to said database, and wherein said telephony operations include all operations from said group consisting of speaking a predefined prompt onto said first telephony channel, receiving and storing DTMFencoded numerical input from said first telephony channel, and digitally recording audio input from said first telephony channel.
18. Apparatus according to claim 11, wherein said telephony operations further include the operation of waiting for a ring signal from said first telephony channel.
19. Apparatus according to claim 11, wherein said telephony operations further include the operation of answering a call from said first telephony channel.
20. Apparatus according to claim 11, wherein said telephony operations further include the operation of hanging up a call on said first telephony channel.
21. Apparatus according to claim 11, wherein said telephony operations further include the operation of dialing a call on said first telephony channel.
22. Apparatus according to claim 11, for use further with a second telephony channel, wherein said database language sequencer, said database control module and said telephony control module comprise first instantiations of said database language sequencer, said database control module and said telephony control module, respectively, and wherein said memory structure further has stored therein: a second instantiation of each of said database language sequencer, said database control module and said telephony control module, the procedures of said second instantiation of said telephony control module speaking a predefined prompt onto said second channel, receiving and storing DTMF encoded numerical input from said second channel, and digitally recording audio input from said second channel.
23. Apparatus according to claim 21, wherein said first and second instantiations of said database language sequencer share a common set of software instructions and have different instance data, wherein said first and second instantiations of said database control module share a common set of software instructions and have different instance data, and wherein said first and second instantiations of said telephony control module share a common set of software instructions and have different instance data, the instance data for said first instantiation of said telephony control module identifying said first telephony channel and the instance data for said second instantiation of said telephony control module identifying said second telephony channel.
24. Apparatus according to claim 22, wherein the common set of software instructions shared by said first and second instantiations of said database language sequencer includes an interpreter which interprets a database language program to develop said sequence in which said database language sequencer calls said database control module procedures and said telephony control module procedures, wherein the instance data of said first instantiation of said database language sequencer identifies a first program as the database language program to interpret, and wherein the instance data of said second instantiation of said database language sequencer identifies a second program as the database language program to interpret.
25. Apparatus according to claim 22, wherein said software instructions stored in said memory structure further include: a channel βerver common to both said firβt and second instantiations of said telephony control module, said channel server performing said telephony operations on said firβt telephony channel in response to communications from said first instantiation of said telephony control module, and performing said telephony operations on said second telephony channel in response to communications from said second instantiation of said telephony control module.
Description:
VOICE RESPONSE SYSTEM WITH

PROGRAMMING LANGUAGE EXTENSION

A portion of the disclosure of this patent document contains material which is subject to copyright protection. The copyright owner has no objection to the facsimile reproduction by anyone of the patent document or the patent disclosure as it appears in the U.S. Patent and Trademark Office patent file or records, but otherwise reserves all copyright rights whatsoever.

BACKGROUND

1. Field of the Invention

The invention relates to telephony voice response systems, and more particularly, to the extension of database languages to handle telephony voice response functions.

2. Description of Related Art An interactive voice response (IVR) system is a system which allows callers to use a telephone to interact with a remote computer and retrieve data from, or enter data into, one or more databases. Usually callers enter information and commands by pressing buttons on a tone-generating telephone. The telephone generates a DTMF-encoded (dual-tone multi-frequency) signal in response to such buttons, and transmits the tones to the voice response system. The voice response system decodes the tones to determine which buttons were pressed, and proceeds accordingly. In other systems, callers enter information and commands by speaking into the telephone. In such a situation, the voice response system recognizes the words spoken and proceeds accordingly. IVR systems can be as simple as ordinary voice mail systems, or can be highly complex, with

multiple menus and caller-data-entry facilities. They can support either a single telephony channel or multiple simultaneously active telephony channels.

IVR systems are typically developed by programming a general purpose computer system that has telephony hardware installed. For example, IVR systems often include a DOS-based personal computer with a telephony expansion card installed, such as a TyIN 4000 Pro Personal Communication Assistant, available from National Semiconductor Corporation, Santa Clara, California, or a Model D/41 available from Dialogic Corporation, Parsippany, New Jersey.

IVR systems usually need to have a high degree of flexibility for customization by value-added resellers (VARs) and by the MIS departments of end-user customers. VARs will often customize an IVR system for the needs of a particular customer, and many customers need to be able to modify their IVR systems themselves to meet changing requirements for their callers. In the past, many IVR systems were difficult to customize because they were programmed in an ordinary, general purpose program language, such as C or C++. In order to speed application development and customization, some IVR system suppliers have developed proprietary libraries of C-language procedures which could manage both the control of the telephony hardware and also the data that the caller is accessing. Other suppliers have developed proprietary scripting languages, and provide an interpreter program written in C (or another general purpose programming language) . The interpreter follows a script prepared by the developer. Still other suppliers have developed form, table or graphical (GUI) programming environments for IVR system development or customization. VARs and end-

user customers have found all of these mechanisms difficult to learn and use, however, and this has restricted the growth of the IVR market.

One of the problems with the above mechanisms for IVR system development is that while they may be well- suited to managing the telephony aspects of the system, they are not as well suited to managing the database aspects of the system. Database management is best performed by facilities which are designed for that purpose, namely database management systems. A database meinagement system (DBMS) is a software package designed to operate on a collection of one or more computer- stored files, or what is referred to as a database. Its primary operation is to select database records that have user-specified common characteristics, and retrieve those records for further processing and display. The database management system also adds new records to the database, and modifies existing records as desired. A typical database management system can include a non- procedural user interface through which a user at a terminal can cause the DBMS to perform desired operations on the database. A typical DBMS also includes a high-level programming language (referred to herein as a database language or a DBMS language) that can be used procedurally to operate on the data in the database. Often both the non-procedural interface and the procedural interface call a common set of procedures, referred to herein as the database engine, to perform the desired operations on the database. The simple, high-level commands supported by DBMSs to manage a database, such as "seek", "replace", "sort", and so on, are quite powerful. However, DBMSs do not support telephony functions.

It is desirable to use DBMSs in IVR applications also because they inherently manage their databases in the "native format" of the DBMS. Unlike general purpose programming languages which provide enormous flexibility to programmers in the formation of data structures, database management systems organize their databases in a predefined format which users rarely, if ever, need to understand. The high-level commands of a DBMS obviate any necessity for the programmer to be concerned with the underlying format in which the data is actually stored.

Once the data is already maintained in the DBMS native format, a host of additional applications become possible. DBMS language programs can be written easily to operate on the data independently of the telephony connections. For example, a bank might create and manage all of its account information using a DBMS, and have an IVR system for customers to call to retrieve such information. Such an IVR system would need to be able to obtain the desired information from the database in the DBMS native format. As another example, an IVR system might be designed to obtain information from callers and place the information in a database; reports can then be easily generated from the data using the various user interfaces of the DBMS. Thus an IVR system which maintains its data in the native format of a DBMS can be much more tightly integrated with the remainder of the customer's business.

In one conventional attempt to integrate native format database management in IVR systems, the IVR system and a database management system were set up to operate independently on two separate computer systems. The IVR system communicated with the DBMS system via terminal emulation, in which the IVR system acted as a

user terminal communicating and receiving individual characters from a non-procedural terminal interface of the DBMS. The software for the IVR system was written in a general purpose programming language, and the character stream to and from the DBMS system took place through an ordinary I/O port of the IVR system computer. As might be expected, the terminal emulation technique can be extremely slow, inflexible, and arcane.

Another way that IVR system developers have sought to operate on a database in a native DBMS format, as part of an IVR system, was to provide a procedure library, written in a general purpose programming language such as C, which could be compiled with or linked to the main program module of the IVR application, also written in C. However, this technique required a detailed understanding of the DBMS native format and, in large part, duplicated all of the effort that DBMS manufacturers had already expended in the development of their own database engines and tools. It is also rare for IVR system developers to have the expertise necessary to optimize database management software to run as efficiently as that available from the DBMS manufacturer.

Accordingly, as can be seen, prior attempts to integrate IVR systems with native format DBMS databases lhave left much to be desired. The present invention achieves such integration much more effectively.

SUMMARY OF THE INVENTION

Many existing DBMS languages can be extended using library extension modules. Thus a developer of a database language program can create a proprietary library for operating on the database or for performing

other functions, and can then access the procedures of the library in the same manner as the ordinary procedures of the database engine are accessed. For example, the FoxPro ® database management system, available from Microsoft*, includes a "library construction kit" which allows developers to create external libraries of C-language routines that can be integrated into any FoxPro application through a predefined external FoxPro application programming interface (API) . Once the library is installed, a FoxPro language program invokes an extension procedure merely by calling it in the same manner that it calls FoxPro built-in procedures. The FoxPro library construction kit is described in Microsoft, "FoxPro* Library Construction Kit, Developer's Guide" (1993), incorporated herein by reference.

Another example of a DBMS which allows language extensions is Informix 4GL. Extensions for this database language are described in Informix, "Informix- 4GL Rapid Development System, Unix Products Installation Guide and Release Notes", Rev. A, pp. 1-59 - 1-76 (19ΘΘ) , incorporated herein by reference.

These DBMS systems can be thought of as being organized into three components: (l) a database language sequencer module, which follows a database language script (either as an interpreter or as compiled code) ;

(2) a database engine module which contains the DBMS's built-in procedures for operating on the database; and

(3) the extension library module, which contains the developer's extension library procedures. The database language sequencer traverses the script, and calls the procedures in either the database engine module or the extension module as required by the script. The interface to these procedures is the same for both the

database engine module and the extension module, as set forth in the above-incorporated references.

Although extension library capabilities have been available in DBMS languages for a long time, they have not heretofore been used for telephony operations. One possible reason for this is that those working in the telephony field developing IVR applications, are used to working in either general purpose programming languages such as C, or in specialized languages developed specifically for telephony operations only.

Accordingly, the invention involves the addition of a telephony library extension to a DBMS that has a programming language, with the resulting combination executing on computer hardware to form a telephony server. In one embodiment, telephony server apparatus includes a database and software instructions executable by a processor structure, the software instructions including a database language sequencer, a database control module having a plurality of procedures callable by the database language for performing at least one database operation, and a telephony control module having a plurality of procedures callable by the database language sequencer for performing at least one telephony operation. The database operations include at least the operations of reading and writing data to a database, and the telephony operations include at least the operations of speaking a predefined prompt onto a telephony channel, receiving and storing DTMF-encoded input from the telephony channel, and recording audio input from the telephony channel. The database language sequencer calls the database control module procedures and the telephony control module procedures in a sequence defined by a program prepared according to the rules of a database language. The database language

sequencer can include either an interpreter or compiled code.

The telephony server apparatus can control multiple telephony channels by running a separate task for each such channel under a multitasking operating system. The above software can be instantiated separately for each task, or parts can be separated out and provided in a common task communicating with the individual channel tasks via inter-process communication (IPC) , shared memory, or by another mechanism. In one embodiment, a cααuion channel server task is provided which manages the resources of the telephony card for all of the individual channel tasks.

BRIEF DESCRIPTION OF THE DRAWINGS

The invention will be described with respect to particular embodiments thereof, and reference will be made to the drawings, in which:

Fig. 1 is a symbolic block diagram of an IBM PC/AT- compatible personal computer incorporating features of the invention;

Fig. 2 is a block diagram of the software architecture used in the system of Fig. 1;

Fig. 3 is a detail of a sequencer of Fig. 2; Fig. 4 is a flowchart illustrating the creation of a sequencer in Fig. 2; and

Fig. 5 is another block diagram of the software architecture used in the system of Fig. 1.

DETAILED DESCRIPTION

I. HARDWARE ARCHITECTURE

Fig. l is a symbolic block diagram of an IBM PC/AT- compatible personal computer incorporating features of the invention. It comprises a CPU 102, which may be an

Intel 80486 compatible CPU or an Intel Pentium processor, for example. The CPU 102 has address, data and control lines which are connected to a CPU bus 104. The CPU bus 104 is also connected to a cache memory 106 and to DRAM memory 108, both of which are controlled by system control logic 110. The system control logic 110 is connected to the CPU bus 104 and also to control, address and data lines of an ISA bus 112. Connected to the ISA bus 112 is a ROM 114 containing the system BIOS, a disk controller 116 for floppy and hard-disk drives 118, and one or more telephony cards 120 connected to a plurality of telephony channels 122. The telephony card 120 is a model D/41, available from Dialogic Corporation, Parsippany, New Jersey. In another embodiment, the telephony card is a TyIN 4000 Pro, available from National Semiconductor, Santa Clara, California. The Dialogic board is described in Dialogic, "Voice Hardware Reference" (Dialogic Ref. No. OS-0147-002) (1994), incorporated by reference herein. The TyIN 4000 Pro is described in National Semiconductor, "Tyln 4000 Pro, Getting Started" (1994) , incorporated herein by reference. The system of Fig. 1 illustrates only one platform which can run software according to the invention. Numerous other platforms can also suffice, such as Macintosh-based platforms available from Apple Computer, Inc., platforms with different local bus configurations, networked platforms, multi-processor platforms, and- so on.

The telephony channels 122 represent separate analog phone lines for each channel. However, a wide variety of other implementations are possible. For example, the telephony channels 122 could represent separate logical channels all carried on one or more telephone-company- provided Tl connections or higher. As another example,

the telephony channels 122 may be carried on one or more ISDN BRI or PRI links into the public switched telephone network. In either case, the telephony cards 120, together with any software drivers, are responsible for presenting the appearance of separate, individual channels (also referred to herein as telephony lines) to higher level software.

Because of the numerous types of hardware platforms which can run software according to the invention, the term "processor structure" as used herein will refer to the CPU or CPUs of single or multiple processor arrangements, whether located in a single box or distributed across a network. Similarly, due to the possibility of paging and overlay mechanisms of different operating systems and application programs as well as memory, disk and network caching, the term "memory structure" includes both volatile and non¬ volatile memory, mass storage and cache memories, whether these types of memory are all located in a single box or distributed across a network.

II. SOFTWARE ARCHITECTURE

Fig. 2 is a block diagram of the software architecture used in the present embodiment. The software runs under a multitasking operating system such as Microsoft Windows, Windows NT or UNIX. As used herein, the term "multitasking operating system" includes permissive multitasking operating systems as well as preemptive multitasking operating systems. Preferably, the operating system 202 in Fig. 2 is Microsoft Windows. All of the software and data illustrated in Fig. 2 is present in the memory structure of Fig. 1, although because of paging, memory caching and disk caching and other memory management mechanisms,

different parts may at different times be located physically in one or more of the different components of the memory structure.

In the architecture of Fig. 2, each of the telephony channels is associated with a separate concurrent task (also called a concurrent process) running under the operating system 202. The term "concurrent task" does not require that their instructions be executing simultaneously, although this may be possible on a multi-processor system. In Fig. 2, N tasks are shown, 204-1, .. ,204-N (collectively, 204). In one embodiment, these tasks are all created upon initialization of the IVR system, whereas in another embodiment, the tasks are created only as their associated telephony channels become active.

In a multitasking operating system, a task (or process) can be thought of as being divided into two components: software instructions which are executable by the processor structure, and data. Data is further divisible into read-only data and read-write data. When two tasks are running the same software, the read-write data for each task is maintained in a separate region of the memory structure, whereas, depending on the operating system, there may either be a separate copy of the software instructions and read-only data for each task or the different tasks can share the same copy of the software instructions and read-only data. In the present embodiment, a single copy of the software instructions and read-only data is shared. See Pietrek, "Windows Internals," Addison-Wesley, pp. 216-218 (1993). The entire Pietrek text is incorporated by reference herein. Whether or not two tasks running the same program share their software instructions and read-only

data, the program is considered herein to be separately "instantiated" for each of the tasks.

Each of the tasks in Fig. 2 can be thought of as including three modules 206-i, 208-i and 210-i. The module 206-i is a database language sequencer. The sequencer is the portion which the IVR system developer creates or modifies in order to define the caller's experience with the IVR system. Voice prompts are set up in the sequencer module, as are menu hierarchies, actions in response to caller input, and so on. In order to facilitate the extensive data manipulation and data access which sophisticated IVR systems usually require, the IVR system developer creates the sequencer 206-i using a feature-rich database language rather than with a minimal IVR language or general purpose programming language. Any database language can be used in different embodiments of the invention. The embodiment described herein uses FoxPro, but the languages of many other database systems can be used instead, such as dBase, Paradox, Oracle/SQL, Clipper, and so on.

The DBMS-language sequencer 206-i can include either a fully compiled version of the developer's DBMS- language code, or can include the combination of the DBMS-language code in text form plus an interpreter. Other variations are also possible, such as the combination of a tokenized version of the developer's DBMS code plus a traverser for sequencing through the tokens. Fig. 3 illustrates the interpreter variation. As can be seen, the sequencer 206-i includes the FoxPro code 302 in text form as written by a developer, and a FoxPro language interpreter 304 which parses the FoxPro code 302 in order to determine which actions to take. In this variation, the interpreter 304 includes software

instructions executable by the processor structure, which may be shared between tasks, while the FoxPro code 302 is considered read/write data and is not shared between the tasks. However, in another embodiment, the FoxPro code 302 may be shared between tasks. Note that it will typically be desired that most, if not all, of the telephony channels should present the same user experience to callers. In this case, if the FoxPro code 302 is not actually shared, it can at least be identical among all the channels that require the same handling.

Fig. 4 is a flowchart illustrating the creation of the sequencer 206-1 in the variation in which the sequencer constitutes compiled code. In a step 402, the developer prepares the FoxPro code in text form. This may be the same code as 302 in Fig. 3. In step 404, the FoxPro code is compiled using the FoxPro DBMS compiler in the FoxPro Distribution Kit, available from Microsoft Corporation, Redmond, WA. This compiler is described in Microsoft, "FoxPro User's Guide" pp. U6-12 - U6-15 (1993) . (The entire User's Guide is incorporated herein by reference.) In step 406 the compiled sequencer 206-1 has been created.

Since each task 204-i in Fig. 2 corresponds to a separate telephony channel 122, the IVR system developer can prepare the FoxPro code used to create the sequencer 206-i, as if only one telephony channel was to be handled. The developer need not be concerned with any other telephony channel, or even with the possibility that more than one telephony channel may exist, since all consequences of these possibilities are handled by the multitasking operating system 202 and/or the channel server process 214 described below.

Returning to Fig. 2, the module 208-i represents a database engine for the particular DBMS in which the IVR

system's developer's code was prepared. In the presently described embodiment, database engine 208-i is the FoxPro database engine, available from Microsoft Corporation and incorporated herein by reference. All of the database engines 208-i contain a set of database management procedures which are callable by the sequencer 206-i in order to manage the data in a common database 212. The database 212 is stored in the memory structure of the computer system of Fig. 1, and for the most part on the disks 118 of such memory structure.

The module 210-i contains a library of telephony- related procedures callable by the sequencer 206-i. The module 210-i can be provided to the IVR system developer in compiled form, so the developer need not be concerned at all with its contents (except to know the identity of, and syntax for, the procedures which may be called from the FoxPro language code) . In the situation where the DBMS language used is FoxPro, and the operating system 202 is Microsoft Windows, then the procedures of the module 210-i are preferably written in C and compiled and linked together to create an .FLL (FoxPro Linked Library) . The procedures of FLL 210-i are described in more detail below.

The telephony modules 210, each instantiation of which is associated with a different telephony channel 122, corrcπunicate with a channel server process 214 which is charged with controlling the telephony cards 120 and allocating their resources i a manner which avoids conflicts among the different tasks 204. The channel server process 214 is described in more detail below as well.

Fig. 5 is another diagram of the software architecture used in the present embodiment. For simplicity, only one task 204 is illustrated, but

greater detail is provided throughout the hierarchy. In addition, since the database engine modules 208 and the database 212 are conventional, they are omitted from Fig. 5. As can be seen in Fig. 5, the software architecture of the present embodiment is organized into layers. It will be seen that by substituting one module at one layer, different database management systems can be accommodated. By substituting modules at two other layers, different operating systems can be accommodated. Finally, by substituting modules at two further layers, different telephony cards can be accommodated.

Referring to Fig. 5, the top two levels 302 and 304 together form the FoxPro language sequencer 206-1 (Fig. 2) . The example of Fig. 3, specifically the combination of FoxPro code in text (layer 302) and a FoxPro language interpreter (layer 304) is illustrated. The FoxPro code in layer 302 is prepared by the IVR system developer, whereas the FoxPro language interpreter 304 is supplied by the DBMS manufacturer (Microsoft, in the case of FoxPro) .

The telephony FLL module 210-1 (Fig. 2) comprises three layers: a DBMS interface layer 502, charged with the task of converting parameters between the form in which they are passed to and from the sequencer 206-1, and a generic form used by lower layers in the hierarchy. The layer 502 thus isolates lower layers from having to know which DBMS- is running in the higher layers. The module 502 is specific to the particular DBMS used; other versions of this module can be substituted in order to support sequencers from other DBMSs.

Below the DBMS interface layer 502 in the telephony FLL 210-1 is a compute layer 504. Roughly, the compute

layer 504 is charged with performing any computations that are both generic (not specific to a particular DBMS) and not appropriate for the channel server 214 to perform. For example, certain operations should not be performed by the channel server 214 because they may create a bottleneck; these operations are performed in the compute layer 504 instead. Examples of operations performed in the compute layer 504 include retrying an operation if no input is received before a time-out expires; converting a date value input parameter to a list of individual voice prompts to speak; and so on. Different versions of the compute layer 504 may be substituted to meet the needs of different market segments, for example to accommodate different human languages or the customs of different countries.

Below the compute layer 504 in telephony FLL 210-1 is an IPC client layer 506. The IPC client layer 506 communicates with an IPC server layer 508 at the top of the channel server 214 using an inter-process communication (IPC) protocol or mechanism of the underlying operating system 202 (Fig. 2) . In Windows implementations, the IPC mechanism can be Dynamic Data Exchange (DDE) . Whereas there are N instantiations of the sequencer 206-i and the telephony FLL 210-i, including the IPC client layer 506, there is only one instantiation of the channel server 214, including an IPC server layer 508. The purpose of IPC client layer 506 is to perform the client Bide of the inter-process communication mechanism for the compute layer 504 in a manner which isolates the compute layer 504 from all details of the IPC communication mechanism. The layer 506 also isolates higher layers from having to know whether the IVR system is serving only a single channel, or multiple channels. The IPC server layer 508 performs

the server side of the inter-process communication mechanism. Note that in systems that support only a single telephony channel, the IPC client layer 506 and the IPC server layer 508 can be combined into a single very simple layer.

Below the IPC server layer 508 in the channel server 214 is a test mode layer 509. When test mode is activated, this layer simulates the operation of the telephony hardware using user dialogs, so that an application can be tested without requiring actual use of telephony hardware. In normal operation, test mode is disabled and the test mode layer 509 simply becomes transparent.

Below the test mode layer 509 is a line driver interface layer 510. The layer 510 implements standard voice/telephony primitives for the particular telephony card(s) installed in the system. In some embodiments, the line driver interface 510 might also control a voice recognition card (not shown) . The layer 510 implements such primitives as "answer incoming call", "hang up line", "get DTMF keys", and "play list of prompts". Since the line driver interface layer 510 isolates higher layers from having to know how to control the particular telephony card(s) installed in the system, different versions of the layer 510 may be substituted to handle different telephony cards.

Below the line driver interface layer 510 in the channel server 214 is the telephony card driver 512 supplied by the telephony card manufacturer. For the Dialogic D/41 card, the driver 512 is the D40Drv DOS TSR ("Terminate and Stay Resident") program, available from Dialogic. For the National Semiconductor TyIN 4000 Pro telephony card, the driver layer 512 is the TAPI service provider (TSP) program available from National

Semiconductor. For the Rhetorex telephony card (Model RDSP 9432, available from Rhetorex, Inc., Campbell, CA) , the driver 512 is the RhetDrv DOS TSR program available from Rhetorex. All three of these driver programs are incorporated herein by reference in their entirety. Such drivers may also include a number of procedures to be compiled with the channel server 214, which properly call the TSR or TAPI routines,

III. EXAMPLE FOXPRO PROGRAM

Set forth in Appendix A is an example FoxPro program for layer 302 (Fig. 5) . The programming language in which the FoxPro code in Appendix A is written, is described in Microsoft, "FoxPro Language Reference" (1993) , incorporated herein by reference.

The example program in Appendix A represents the telephony component of an interactive voice response system which schedules part-time workers at a plant. It demonstrates a solution to the need for a large retail outlet to schedule temporary labor, and have temporary employees be able to call in and obtain their work schedule. The voice response system permits workers to enter their worker number, and be told which days of the current week, and which work shift (early or late) they should report to work. They have a chance to accept or reject each individual shift scheduled. As will be seen, the program uses ordinary FoxPro commands (verbs and procedure calls) to access the database. The telephony functions are available by way of new procedure calls, but the syntax for calling these procedures is the same as the ordinary FoxPro language syntax for calling database management procedures. In another embodiment, access to the telephony functions

can be provided by way of newly defined verbs, instead of or in addition to newly defined procedure calls.

The program in Appendix A operates on two databases: a control database called BASEDATA and a working database called WORKERS. The control database contains only two values: a labor rate and an hours-per-shift value. The WORKERS database contains 100 rows, each for a respective temporary worker. The fields in each entry of WORKERS are as follows: Worker ID Number 1001-1100

Address Street

Address City

Address State

Address Zip Code Home Phone

Work Schedule Fields Boolean values for early esl-es7, lsl-ls7 shifts 1-7 and late shifts

1-7, indicating whether the worker is scheduled to work the specified shift

In a portion of FoxPro code prior to that set forth in Appendix A, an initial work schedule was set up in a WORKERS database.

At line 10 of the program, a SET LIBRARY TO command is issued, which loads in a FoxPro linked library called voysaccs. This is the telephony FLL 210-1 (Fig. 5) . When FoxPro loads a library, it also runs a start-up procedure of that library which, among other things, registers a procedure for the task 204-1 to handle events in substitution for the default FoxPro event handler.

In line 11, the program calls a telephony FLL function VSET to establish a directory in which

application prompt files are to be found. In line 12, the program calls the same function to establish a directory in which recording files are to be found.

In lines 18-22, certain variables are loaded with base data from the BASEDATA database. In line 28, the actual working database table, WORKERS, is selected.

The FoxPro command SET ORDER TO is then used to establish an index based on the field ID.

Lines 36-52 constitute a continuous loop which simply waits for the phone to ring, answers it after one ring, and then calls a procedure pt_call. The function to wait for the phone to ring and answer it after one ring, is VWaitRingO, at line 42, and this is a procedure in the telephony FLL 210-1. The VWaitRingO procedure takes two arguments, both of which are optional. The first argument indicates the number of rings to wait before answering; the default is two. The second argument indicates the number of seconds to wait before timing out; the default is 0, indicating no time- out. Return values indicate success (answered a phone call), time-out, or invalid parameters.

The pt_call function is described below. After pt_call returns, the example program in Appendix A calls another telephony FLL procedure VChkHangupO to determine whether the call is still active (line 47) and, if not, it calls yet another telephony FLL procedure VSpeakO to speak a "good-bye" prompt over the telephony channel. The VSpeakO procedure allows a program to speak a sequence of prompts or values. It can take up to 10 arguments. These arguments can be values (numbers or character strings which contain dates or voice file names) , or "speak modes" that regulate how numeric values should be spoken. Speak modes apply only to numeric values, and the default is a standard numeric

format in which, for example, 432 is spoken as "four hundred thirty-two". The speak mode DIGITS causes subsequent numbers to speak out the digits, for example "4 3 2". DOLLARS speaks the numbers as a dollar amount. For example, 432.54 would be spoken as "four hundred thirty-two dollars and fifty-four cents". DAY speaks numbers in the range 1-7 as days of the week; this speak mode is designed to work hand-in-hand with the FoxPro DOW() function. The speak mode specified in an argument list remains active for all subsequent numbers in the argument list until changed. Prompt names given as arguments are in the form of voice file names in the current "AppPrompts" directory, and are in one of several standard digitized audio file formats. The VSpeakO procedure returns codes indicating success or failure, or code indicating success but interrupted by DTMF key press.

In the case of the VSpeakO call on line 48 of the example program in Appendix A, only one argument is provided, specifically the name of a file which contains the prompt "Good-bye, and thank you for calling." In line 51, still another telephony FLL procedure VHangupO is called in order to disconnect the telephony channel.

The pt_call procedure begins on line 62 of the example FoxPro program in Appendix A. In line 71, it calls the VSpeak procedure to speak the prompt in the data file pthello, specifically a hello prompt.

In lines 77-108, the pt_call procedure enters a loop which attempts to obtain a valid 4-digit ID from the caller. The caller is given three tries. In line 78, the procedure calls the telephony FLL function VChkHangupO in order to determine whether the caller has hung up. If so, the procedure returns to line 44. A typical database language program using the telephony

FLL will make frequent calls to VChkHangupO because a caller may hang up at any time.

At line 86, the pt_call procedure calls the VSpeakO telephony FLL procedure to speak the prompt in file ptid, which asks the caller to enter a 4-digit worker ID. In line 87, the pt_call procedure calls another telephony FLL function VGetTonesO. The VGetTones0 procedure allows the caller to provide information to the program, including menu choices, worker identifications, answers to questions, PIN numbers, and so on. The function takes up to four arguments, beginning with a character variable which upon return will contain the sequence of digits entered by the caller. The second argument is optional and indicates the number of digits to terminate on. The default is 1, and 0 accepts any number of digits. The third argument is optional and indicates a string containing a single character representing a key to terminate on (0-9, * or #) . The default is "space", which indicates no key- specific termination. The fourth argument indicates an inter-digit time-out - the number of seconds to wait between digits before timing out. 0 indicates no inter- digit time-out and the default is 10 seconds. Thus, three different termination mechanisms are available: fixed number of digits, an end-of-input key, and an inter-digit time-out. If more than one mechanism is used, then the first to occur terminates the input. Additionally, VGetTonesO will terminate upon a "first- digit time-out" if the caller has not pressed any keys at all within the first five seconds. The database language program can adjust the first-key time-out using a VSetTimeOutO telephony FLL procedure.

On successful termination, VGetTonesO returns a value indicating whether termination occurred on inter-

digit time-out, specified key, or number of digits. On failure, VGetTonesO returns a value indicating whether termination occurred on first-key time-out, on hang up, or because of bad arguments. In line 87, the arguments passed to VGetTones are βloc_id, 4, "space" and 15. Thus, it will return after four digits have been entered or after an inter-digit time-out of 15 seconds. There is no key on which it will terminate. The number entered by the caller will be in the FoxPro variable loc__id.

In line 94, the program uses the FoxPro SEEK verb to search the WORKERS database table for the worker ID in loc_id, using the ORDER previously established at line 29. In line 95, the FoxPro function FOUND() is used to determine whether the worker ID entered by the caller exists in the database. If so, then at line 96, the program exits the FOR loop. If not, then at line 104, the program calls VSpeakO with the name of a file ptnoid containing a prompt such as "Sorry, I couldn't find that ID. Please try again." The program allows three attempts for the caller to enter a valid ID; after the third attempt, at line 106, pt_call simply returns.

Once a valid worker ID has been entered, lines 115-

183 check each shift individually and for each one, if the worker is scheduled to work, requests confirmation. A count of the number of shifts confirmed is maintained in a FoxPro variable var_num_shifts.

Referring to line 115, pt_call first determines whether the worker is scheduled to work in the early shift on day 1 (esl) . If so, then in line 116, pt_call calls another FoxPro procedure query_shift, passing in two parameters: .T. and datel. The first parameter for query_shift indicates whether this is the early or late shift (.T. means TRUE and .F. means FALSE), and the

second parameter is a date string in the form MM DD/YY. Accordingly, the call in line 116 specifies to query_shift that this is the early shift of the date indicated in datel. The query_shift procedure uses the telephony FLL functions to ask the caller about working the specified shift, and returns true (.T.) or false (.F.) in a variable var_confirm to indicate whether the caller has confirmed that shift. In line 117, the pt_call procedure uses the FoxPro verb REPLACE to update esl with the Boolean value returned in var_confirm. The procedure pt_call performs this operation for each shift of each date in the week.

After all shifts have been queried, the program determines at line 188 whether or not the worker has agreed to work any shifts at all. If not, then in line 189, the program calls VSpeakO with the name of a file ptnoshft containing the prompt, "You are not scheduled to work any shifts in the next week."

Otherwise, at line 198, the program calls VSpeakO with a series of arguments which give the worker an earnings prediction for the coming week. The statement contains five arguments which together assemble the prompt as follows:

Argument Meaning "ptsigned" "Thank you. You are signed up to work..." var_num_shifts (The number of shifts confirmed, to be spoken in the standard numeric format)

"ptshifts" " shifts this week, for a total earnings of... "

"Dollars" (Subsequent numbers are to be spoken as dollars)

Argument Meaning var_num_shifts (Earnings prediction for the week, * var__hours_ spoken as dollars) per_shift * var_hourly

In step 203, pt_call returns to line 44 of the main FoxPro code.

The query_shift procedure used in pt_call begins on line 210 of the example program in Appendix A. In lines 215-217, query_shift first uses the VChkHangup() procedure to exit if the caller has hung up.

In line 224, if this is an early shift, query_shift speaks a prompt by passing arguments to the VSpeakO procedure as follows:

Argument Meaning "ptearly" "You are scheduled to work the early shift on..."

"Day" (Subsequent numbers are to be spoken as days (Mon., Tues., Wed ) )

DOW(param_date) Uses the FoxPro DOW() function to convert the specified date to a number 1-7; the number will be spoken as a day.

DTOC(param_date) Uses the FoxPro DTOCO function to convert the specified date to a character-type date (e.g. "01/01/94") .

In line 227, if the specified shift is a late shift, then the same prompt is spoken except that it begins with the language in the prompt file ptlate, which is "You are scheduled to work the late shift on...".

In lines 234-268, the caller is given three attempts to enter 1 to confirm, or 2 to reject. Specifically, after checking for hangup (lines 235-237) , query_shift calls VSpeakO to speak the prompt "To confirm that you

will be available for this shift, press 1. If you will not be available, press 2." In line 242, query_shift calls VGetTonesO with arguments indicating termination after one digit or a 15-second inter-digit time-out, with the caller's input to be placed in a variable loc_confirm. In lines 247-251, if the caller pressed the numeral 1 key, then var_confirm is set to .T. and var_num-shifts is incremented. If not, then in lines 253-256, var_confirm is set to .F. If the caller pressed a number that was neither 1 nor 2, then on the first two tries, query_shift calls VSpeakO to speak the prompt, "Please press 1 or 2," ( line 263) and the loop repeats. After the third attempt, query_shift sets var_confirm to .F. (line 265) and returns. It can be seen that the availability of telephony function calls in a database programming language creates a powerful combination which permits interactive voice response system developers to easily allow callers to interact with a database in a very complex manner.

IV. TELEPHONY FLL

The telephony FLL 210 will now be described. For illustration purposes, one of the procedures of the telephony FLL 210 which are callable from the FoxPro language sequencer 206, specifically VSpeakO, will be followed all the way down to the telephony card driver layer 512. A number of additional telephony FLL procedures are described in Appendix B, and their internal operation can be gleaned from Appendices C-G hereto.

A. FoxPro Interface Laver Procedures Initially, note that FoxPro requires a library extension module to include a structure which defines the entry points and various other characteristics of

each procedure which is callable from the FoxPro language code. In the FoxPro interface layer 502 in telephony FLL 210-1, this structure is as follows:

Foxlnfo myFoxInfo.] ■ {

START", ( FPPI ) Start, CA LO LQAD, ""}, "STOP", ( FPFI ) Btpp, CALLONDN OAD, ""}, "VWAITRING", ( FPFI ) vwaitring, 2, ".I,.I"}, "VHA GUP", ( FPFI ) vhangup, 0, ""}, "VCHKHANGUP", ( FPFI ) vch hangup, 0, ""}, "VSPEAK", ( FPFI ) vβpeak, 10,

• * « * f • * _ • * _" • * • * # • * # • * # • ■ # • • J /

"VGETTONBS", { FPFI ) vgettones, 4, "R, .1, .C, .1"}, "VGBTTO BSM", ( FPFI ) vgettonββm, 7,

"C I R .1 .C . ._"} "VGBTWORDS"', ( FPFI ) vgetwor'dβ, 5, "R, .C, .1, .C, .1"), "VRBCORD", ( FPFI ) vrecord, 2, » ?,.I » }, "VDIA ", ( FPFI ) vdial, 3, "C, .I,.R"}, "VDBBUG", ( FPFI ) vdβbug, 1, "I"}, "VSETDIR", ( FPFI ) vβetdir, 2, "C,C"},

"VSETDSPEAK", ( FPFI ) vββtdatβspββύt, 1, "C"}, "VSETTMODT", ( FPFI ) vset imeout, 2, "C, I"},

// "VFAXCOVBR", ( FPPI ) vfaxcovβr, 2, "C, .?"}, // "VFAXDOC", ( FPFI ) vfaxdoc, 1, "C"}, // "VFAXSK D", ( FPFI ) vfaxβend, 2, "C,.C"}, // "VFAXSBTDP", ( FPFI ) vfaxββtup, 2, "C,?"}, "VSBT", ( FPFI ) vββt, 2, "C,?"},

};

FoxTable FoxTablβ _ { ( Fox able FAR * ) 0, sizeof ( πryFoxInfo ) / βizβof ( Foxlnfo ) , myFoxInfo

};

Pertinent parts of the source code for the FoxPro interface layer are set forth in Appendix C. Pertinent parts of the VSpeakO procedure will now be described.

VSpeakO begins by obtaining a handle to the current task and passing it to a function FindSlotO. The

FindSlotO function returns, in a specified pointer variable sp, a pointer to a DBMS interface layer telephony line status table line[]. The DBMS interface layer 502 and the compute layer 504 each maintain such a table for their own purposes, and the structure of an entry in the line[] table of the DBMS interface layer is as follows: struct line_t { boolean blnUβe; // is this table slot in use ? unsigned int hTaβk; // Windows task handle int nLineNutn; // line number (not really used)

unsigned int handlerid; // FoxPro event handler ID HANDLE _handledientDDE; // FoxPro handle of hidden client window HWND hwndClientDDE; // Windows handle of hidden client window

} static struct line_t line[_3___LINES] _ {0};

Each process 204-i maintains a copy of this table, but looks at only the data for itself.

The FindSlotO function merely loops through the above line table until it finds an entry whose hTask field matches the specified task number, and whose blnUse field is true. A pointer to the resulting entry of lineO is returned in sp.

Returning to VSpeakO , a variable play_type is then initialized to play numbers "as numbers". A FOR loop then loops through all of the arguments in the call to VSpeakO . FoxPro passes parameters to extension library procedures using a parameter block consisting of an integer that represents the number of parameter, immediately followed by an array of parameter unions. The first byte of each parameter union indicates that the parameter is passed by reference if it contains the character R, otherwise it is passed by value. If the parameter is passed by value, then the first byte contains a character indicating whether the parameter is a string (C) , numeric (N) , an integer (I) , a date (D) , a logical (L) , or another type of data. The entire parameter has the following structure:

VSpeakO begins the parameter loop by determining whether the first parameter is a string. If so, then it copies the length of the string from the ev_length field into a variable nCount, and copies the text of the string into the variable 'text' from the array pointed to by the ev_handle field of the parameter. The procedure then adds a terminating NULL character, trims any trailing blanks, and capitalizes the string.

If the parameter text is one of the predefined numeric format flags "digits", "dollars" or "day", then the VSpeakO procedure sets the flag play_type to indicate how subsequent numeric parameters are to be spoken. It also turns off a play_flag to indicate that the current parameter is not to be spoken. If the first byte of the current parameter was not a C, but rather was an I, then VSpeakO converts the integer parameter is converted to a string and places it into the variable 'text'. The flag play_flag remains true. If the first byte of the current parameter is N, then the number contained in the parameter is also converted to text and stored in the variable 'text'.

If play_flag is false with the current parameter, then the loop continues at this point with the next parameter (lines 180, 207). Otherwise, VSpeakO calls a procedure com_Play() either to start a "play list" with the current parameter, or to add the current parameter to an existing play list. The procedure com_Play and related procedures are part of the compute layer 504 and are described in more detail below. Briefly, however, com_Play is called with five arguments: a task handle, which indicates the current Windows task; a call type, which can be either START_PLAY_LIST or ADD_PLAY_LIST or PLAY_PLAY_LIST; a

string which can be either a string of digits or the name of an audio file; a play__type, which indicates how numeric parameters are to be spoken; and an interrupt_mode specifying whether to terminate the prompt if the caller presses a DTMF key.

After the entire parameter list has been parsed in the loop, VSpeakO calls cαm_Play() one more time in line 213 with the call type PLAY_PLAY_LIST. VSpeakO then prepares a result parameter, which has the same format as an argument list parameter and returns it to the FoxPro sequencer 206.

B. Compute Lavβr Procedures

The compute layer 504 procedure com_Play() will now be described, as will the compute layer procedures which are called by com_Play() . Pertinent procedures of the compute layer source code are set forth in Appendix D.

1. coϊt_Play()

The cαm_Play () procedure is as follows: int com_Play ( const unsigned int hTask, // task handle (Windows only) const int call_type, const char *voice_file, // one filename, a num, or a date const int play_typβ, const int interrupt_mode

int nVRetVal _ RC_INTER____; // Voysyβ return value struct line_t *βp; // ptr to slot in line table // find slot in the line table nVRetVal - FindSlot( hTask, _sp ) ; if (nVRetVal 1- RC_SDCCEED) goto done; // Note: to support speak for online and not online

// should not check for bOnLine here and it will be checked // in the line driver layer of the server if ( lsp->-Connected ) { nVRetVal - RC_N0TC0NNEC__D; goto done; ~ } if ( ( play type < AS_N0MB_R ) | | { play type > AS DAY ) ) { nVRetVar - -10; goto done;

switch ( call_type ) {

//

else nVRetVal _ play_play_liβt ( sp, interrupt_mode ) ; break; ~

rea ; case ADD_PLAY_LIST: sp->nNumItemsInPlayList++; if ( add to_play_liβt ( sp, voice_file, play_type ) ]• RC SUCCEED ) ~ nVRetVal - RC_SPRAKPR0MPTFILBNOTFODND - sp->nNumltemsInPlayList; break;

/* * PLAY_ r PLAY_LIST no longer adds to the play list -

* 3uβt plays the list

*/ case PLAY_PLAY_LIST: nVRetVal > play_play_list ( sp, interrupt_mo_e ) ; break; default: nVRetVal > -12;

done: return ( nVRetVal ) ;

} /* function com_Play */

As can be seen, com_Play() is relatively simple. It merely checks for parameter errors and then calls either init_play_l ist ( ) , add_,to_play_l is t ( ) or play_play_list 0 , depending on the call type. Additionally, if the call_type was START_PLAY_LIST, then after calling init_play_list ( ) , the routine also calls add_to_play_list ( ) .

2. init plav list

The play list is not actually maintained in the compute layer, but rather it is maintained by the channel server process 214. The entire function init_play_list() is merely to assemble an IPC "command" and to pass it to an IPC client layer 506 procedure ipc_Do_Command() .

At this point it will be useful to understand that inter-process communications are accomplished in the present embodiment by a client 210-i preparing a command data block and passing it down to the channel server process 214, or by the channel server 214 preparing an event data block and passing it back up to a client process 210-i. The procedure init_jplay-list0 uses only the command block, and this structure is defined as follows:

/* format of a comnand from a higher layer to lower layer */ struct command

{ int nCommand; /* command number */ int nLine; /* line number */

/* * syntax inside dialing string is:

*

* 0123_56789#*ABCD digit to dial

* T use tone dialing (default)

* P use pulse dialing * W wait for dial tone

* , pause 2 seconds

* ! do a switch hook

*/ char string[MAX_DIGITS + 1]; /* dialing string */ struct pitem play_item; /* item to play, record to, or delete */ int path [M_X_DIRS] ; /* dire to search for file

(for PLAY) */ int dir_num; /* dir num (for SETDIR,

DELETE, RECORD) */

/*

* overused field: directory name for setdir, phrase map file

* name for setifile, syntax map file name for βetvocab,

* syntax name for getwords,

*/ char name[MAX_DN + M_X_FN] ;

/* for CALLODT, max rings to wait before giving up */

/* for HAITFORCALL, rings to wait before answering incoming

* call */ int answer_ringβ; /* for CALLODT, max rings to wait */

/* for CALLODT, after answer wait for silence */ boolean wait_for_silβnce;

/* DTMF digit-string stuff for RECORD, PLAY, GETDTMF */ boolean fluβh_digits_at_start; /* flush input at start of operation? */ boolean end_voice_on_any_digit; /* stop voice if get any DTMF digit ?*/ boolean end_voice_on_digit_end; /* stop voice if get end-of-digitβ ?*/ boolean end_oper_on_any_digit; /* stop operation if get any DTMF digit? */ boolean end_øpβr_on_digit_end; /* stop operation if get end-of-digits? */ boolean end_pper_on_voice_end; /* stop operation if voice ends? */

* Values of max digits:

* __0 unlimited (limited only by line card) .

* >0 limited to max_digits digits.

* ~~

* Also used to signify max number of words for

* voice-reco nition.

/* end-of-digits if this many received */

/* end-of-digitβ if this digit received */

/* timeout after voice bef

1st dig (msec) */

/* timeout for whole op

(msec) ; 0 -_ inf */

/* timeout after 1st dig betw digs (msec) */ /* get input digits at end of operation ? */

/* limits for RECORD */ ulong end_silence; /* this much silence ends recrding (msec) */ int total_recording; /* max length of recording (sec) */

/* following are for initializing line in CONNECT command */ boolean do_double_keys; /* detect and report double-keys? ulong doub_key_time ; /- double-key max spacing (msec)

*/ boolean al low_rate_gain ; I* allow user to change rate + gain ? */ int initial_rate ; /* initial playback rate */ int initial ' gain; /* initial playback gain */ boolean do_pause_coιnpr ; /* compress silence to save space? */ boolean pause_pure_sil; /* expand comprd pauses to pure silence ? */

boolean do_AGC; /* do automatic gain control on input ? */

/* voice-recognition stuff */

/* reject scores > this (lower ■_ better) */

/* reject ambig < this (lower __ worse) */ /* input gain (0 - 0x7FFF; def a

0x1000) *7

/* set commands */ int io_location; /* input source or output destination - should merge with nSetParm */

/* fax commands */ int nFaxOpCode; /* numeric operation code */ int nFaxPaxm; /* integer parameter */ char cFaxPaxm[MAX_FAXPARM+1] ; /* string parameter */

/* set/get commands */ int nSetGetOpCode; /* numeric operation code for set and get commands */ ulong dwSetParm; /* unsigned long parameter */ char cSetParm.___._SET_ARM+l. ; /* string parameter */

}

The different commands which can be sent to the lower layer using this command structure are as follows:

/* commands from higher layer to lower layer */

/* all must be consecutive + ascending for debug funcs to work */

#define DVRC_N0NB 0 /* no command (placeholder) */ ♦define DVRC_CONNECT 1 /* connect Line task to line N */ ♦define DVRC DISCONNECT 2 / disconnect Line task from line N*7 ♦define DVRC~RBCORD 3 /* record voice or FAX from user */ #define DVRC~INITPIAY 4 /* initialize playlist to empty */ ♦define DVRC ADDPLAY 5 /* add file to playlist */ -define DVRC~PLAY 6 /* play list of voice/FAX files out to user */

#define DVRC_CALLODT /* initiate a call and wait for answer */

#define DVRC GETDTMF 8 /* get DTMF digit string from user*/ #define DVRC~SENDDTMF 9 /* send DTMF digit string out on line */

♦define DVRC_ABORT 10 /* abort any operation in progress 4 '/ ♦define DVRC DELETEFILE 11 /* delete user voice or FAX file */ ♦define DVRC~HANGOP 12 /* hang.up (go on-hook) */ ♦define DVRC~WAITFORCALL 13 /* wait for incoming call and answer it */

♦define DVRC.SETDIR 14 /* set prompt/message directory name

*/

♦define DVRC GETSTATE 15 /* get information about state of line */

♦define DVRC_GETSYSSTAT 16 /* get info about state of entire system */

♦define DVRC SETIFILE 17 /* open indexed prompt file */

♦define DVRC~SETVOCAB 18 /* open vocabulary map files */

♦define DVRC GETWORDS 19 /* get voice-recognized words from user */

♦define DVRC_SETINPUT 20 /* set the sound input source */ ♦define DVRC SETOUTPUT 21 /* set the sound output destination

*/

♦define DVRC_FAXCOVER 22 /* set fax cover page */ ♦define DVRC FAXDOC 23 /" set fax document pages */

♦define DVRC~FAXSBND 24 /* send a fax document */

♦define DVRC FAXSETDP 25 /* setup fax options and parameters

*/ ♦define DVRC SET 26 /* set options and parameters */ ♦define DVRC~GET 27 /* get options and parameters */

The structure pitem referred to in the definition of struct command is as follows:

/* format of a "playable item"; it specifies a single prompt or phrase */

/*

* tricky: fname[] may contain partial name down to IPC layer,

* but is converted to full name in that layer. */ struct pitem

/* stuff passed down from higher levels */ int type; /* playable item type */ char fname [MAXJDN + MAX_FN] ; /" voice or FAX file name, dir name */ void far *vdata; /* ptr to voice data in RAM +/

/* stuff created and used only at lowest level */ int ifnum; /* indexed voice file num (-1 = none) */ int index; /* index into indexed voice file */

};

Given the above, the init_play_list0 procedure is self-explanatory and is as follows: static // ptr to slot in line table

// command to send

// event received // Voysys return value cmd.nCommand _ DVRC_INITPLA ; cmd.nLine _ sp- > nLineNum; cmd.play_i tern. type ■ PI_N0NE ; sp - >ι__um_temsInPlayLiβt " ~- 0 ; nVRetVal ■ ipc_Do_Cαmmand ( sp- >hTask, cmd, &event ) ;

} /* function init_play_list */

3 . add_to_Dlaγ_list ( ) The compute layer 504 procedure add_to_play_list ( ) is as follows :

static int add_to_play_liβt ( const struct line_t *βp, // ptr to slot in line table const char *voice_f ile, const int play_type

int nVRetVal ; int i, vfile_len; char non_digit_char _ ( char ) NULL;

* Find the first non-digit, non-blank symbol in the string.

* If there isn't any, speak it as a number. If there is

* one, and it's a slash (/) , speak the string as a date. Otherwise, treat the string as a file name.

*/ vfile_len ■ βtrlen ( voice_file ) ; for ( i ■ 0; i < vfile len i++ ) if ( ( (voice_file_ . &_

( voice_file[i]

( voice~file[i]

( voice^file[i] non_digit_char vo ce_ e ; break;

if ( non_digit char __ ( char ) NULL ) {

/* voice file contains a number value */ /* Adding number type */ switch ( play_type ) { case AS_N_MBER: nVRetVal - speak_as_number ( sp, voice_file ) ; break; ~ case AS_DIGITS: nVRetVal ■ βpeak_aβ_digits ( sp, voice_file ) ; break; case AS_DOLI_M_S: nVRetVal * spβak_aβ_dollars ( sp, voice_file ) ; break; case AS DAY: nVRetVal βpea_._as_day ( sp, voice_file ) ; break; def ult: nVRetVal -2; break;

}

} else if ( non_digit_char ■■ '/' ) { nVRetVal _ " speak as date ( sp, voice_file ) ; e }lse {

/* voice file contains the name of the voice file to play */ nVRetVal _ addu ( sp, voice_file ) ;

} return ( nVRetVal ) ;

} /* function add_tojplay_liβt */

add_to__play_list() can be called with either a number, a date or the name of a voice file to play in the field *voice_file. The field play_type applies only if *voice_file is numeric, so add_to_play_list0 first checks to determine whether *voice_file is numeric. If it is, then it calls one of the routines speak_as_number() , speak_as_digits() , speak_as_dollars() or speak_as_day() , depending on play_type. These routines are discussed below. If *voice_file contains a date, recognized by the presence of the slash (/) , add_to_play_list 0 calls the routine speak_as_date0. Otherwise, *voice_file is assumed to be the name of a voice file to play. The function add_to_play_list() passes this on to yet another function, adduO .

The compute layer function adduO is as follows: static int addu ( const struct line_t *sp, // ptr to slot in line table const char *voice file

path .O] _ DIR APPPROMPTS ; path [l] « DIR~USERVOICE ; path [2] - DIR~TEMPFILES ; for ( i - 3 ; T < ___C_DIRS ; i++ ) path (i] _ DIR_END; nVRetVal _ add_voice ( sp, voice_file , path ) ,- return ( nVRetVal ) ; } /* function addu */

As can be seen, the adduO routine merely creates an array of paths where the specified voice file might be found, and then calls yet another compute layer procedure add_voice(). The routine add_voice0 is as follows: static int add_voice ( const struct line_t *sp, // ptr to slot in line table const char *voice_file.

if ( strlen ( voice file ) » ( MAX DN + MAX FN ) ) { nVRetVal * RC_BADFII___A__. ; goto done; } αnd.nCαmmand - DVRC_ADDPIAY ; αnd.nLine - sp- _ nLineNum; for ( i - 0 ; i < MAX_DIRS; i++ ) and. pat [i] - path li] ; and. play_i tern. type « PI_SINGLB_FILB; βtrcpy (~ and. play_i tern. f name, voice_file ) ; and. play_i tern. index _ -1 ; and. play_i tern. vdata « ( void * ) OxFFFFFFFF; nVRetVal * ipc_Do_Cαmmand ( βp->hTaβk, and, tevent ) ; done : return ( nVRetVal ) ;

} /* function add_voice */ This routine takes as arguments a pointer to the present task's slot in the line table, a pointer to the name of the voice file to be spoken, and an array of paths where the voice file might be found. It sets up a command block using this information and passes it on to ipc_Do_Cαmmand0.

The routines speak_as_number() , speak_as_digits0 , speak_as_dollars0 , speak_as_day0 and speak_as_date, all essentially dissect the string of characters in *voice_file into individual atomic prompts which are present in voice files in a system prompts directory. These routines all call another compute layer routine addpO with the voice file name for each of the atomic prompts, which merely prepends the path to the system prompts directory and passes the information on to add_voice(). add_voice() sets up the appropriate command block as described above, and passes the individual voice file names on to ipc_Do_Command() . The

compute layer speak_as__... () routines and addpO are set forth in Appendix D, together with any utility routines that they call.

4. play__play_.ligt()

The play_play_list() compute layer procedure is as follows: static int play_play_liβt ( const struct line_t *sp, // ptr to slot in line table const boolean interrupt_mode // end play if DTMF received?

struct command and; // command to send struct event event; // event received int nVRetVal « RC_INTERNAL; // Voysys return value αnd.nCαmmand * DVRC_PLAY; cmd.nLine ■ sp->nLineNum and.play_item.type - PI_N0NB; cmd. flush_digits_at_βtart _ TRUE ; and . end_voice_on_any digit ■ interrupt node ; and . end_voice_on~digTt_end ■ FALSE; ~ and . end_oper_on_any digit ■> FALSE ; αnd.βnd_σper_on_digTt_end - TRUE; cmd.end_oper_on_voice_end _ ( 1 interrupt_mode ) _md.m_x_digit8 _ 0; ~ cmd.end_digit _ ' ♦' ; cmd.get_digits_at_end _ FALSE; and . start_timβ out _ 0 ; and. total timeout > 0 ; αnd. interdigit_to * 0 , cmd.end_silence _ ( unsigned ) NOT_APPLIC; αnd. total_recording _ ( unsigned ) ~N0T_APPLIC; nVRetVal _ ipc_Do_Command ( sp- >hTask, and, tevent ) ; return ( nVRetVal ) ; } /* function play_play_liβt */ As can be seen, this routine merely sets up a command block with the command DVRC_PLAY and various other parameters , and passes it on to ipc_Do_Command 0 . ipc_Do_Command ( ) returns the event structure from the channel server 214 in ' event ' , which play_play_list ( ) ignores since it contains no useful information . C. IPC Client Lavβr Procedures

Pertinent procedures of the IPC client layer 506 are set forth in Appendix E . The layer includes initialization and termination functions , as well as

procedures to handle commands being passed down from the IPC client layer to the IPC server layer 508, and events being passed up from the IPC server layer 508 to the IPC client layer 506. As can be seen, the ipc_Do_Cαm__u_d0 procedure first initializes an event data structure to values which indicate that no event has been received yet. It then obtains the current client task's slot in the telephony line table line[] for the IPC client layer 506, and calls the IPC client layer 506 send_message0 procedure with the command data structure cmd.

The send_message0 procedure merely calls another IPC client layer 506 procedure SendPokeO, which calls the Windows functions to 8end the command cmd via dynamic data exchange (DDE) in a conventional manner to the DDE window for the present client in server task 214. SendPokeO then awaits a Windows return message using the IPC client layer 506 procedure wait_for_wmessage() . Absent an error, the channel server 214 will always send a return acknowledge message (WM_DDE_ACK) .

The wait_for_wmessage0 procedure uses conventional Windows functions to periodically poll the Windows event queue until a specified DDE message has been received for the current client task DDE window. It then returns to the calling routines, SendPokeO, send_message() , and, ultimately, ipc_Do_Command0.

In the present embodiment, after acknowledging a command, the channel server process 214 always responds to DDE commands with an event structure also sent by DDE. Accordingly, after returning from sendjmessage() , ipc_Do_Command() calls yet another IPC client layer 506 procedure get_message0 to wait for and obtain the returned event structure. get__message() is set forth in

Appendix E, and as can be seen, it calls wait_for_wmessage() to await a WM_DDE_DATA message for this task. It then calls another IPC client layer 506 procedure ClientReceiveDataO, which sends a WM_DDE_ACK acknowledge message back to the channel server process and places the event structure in the current task's event queue sp->eventq[] . ClientReceiveDataO then returns to get_message() , which returns to ipc_Do_Command0 , which returns to the compute layer 504 routine which called it.

V. CHANNEL SERVER

As previously described, the channel server 214 is responsible for allocating the telephony and resources of the IVR system in a manner that avoids conflicts. To do this, the IPC server layer and the line driver interface layer of the channel server 214 each maintain a respective static linet] table which contains a large amount of status information about each telephony channel. Each entry in the table corresponds to a different channel. The structure of a single entry in line[] in the IPC server layer 508 is: struct line_t { struct command and; // command boolean bCmdlnProgreββ; // is there a command in progress? boolean bConnected; // is there a task attached to line? HWND serverwnd; // server window handle for line HWND clientwnd; // client window handle for line boolean bTestMode; // is the test mode on for this line?

/* stuff set from dvrc_setdir commands */ /* dire voice files are in */ /* tricky: these names have 'V on the end */ char dirname[MAX_DIRS] [MAX DN+1] ;

} ;

The structure of a single entry in line t] in the line driver interface layer 510 is: struct line_t { boolean bExists; /* is this entry used for a line ? */

int xiLineNum; /* line number (0 to MAX_AL-1) */ boolean bConnected; is a client connected to line? */ boolean bCmdlnProgress; /* is there a and in progress? */ int nCmdStage; /+ current step of command being executed */ struct command cmd; /* command that is in progress */ struct event event; /* event being constructed */ clock_t timeout; /* if 1- 0, time to abort and +/ int ~ nMaxDTMF; /* end play or record if this many digits received */ int nTempVRetVal; ultimate value of ep-.result */ boolean bSendEvent; / event filled in; ready to return*/ int num_even s; // number of queued events for line struct { boolean blβWinEvent; // is this a Windows event ?

// fields for Windows event

DWORD dwMβg;

DWORD dwParaml;

DWORD dwParaπ__;

DWORD dwDevice;

// fields for Dialogic event int nDialBvent; // Dialogic event number int nDialCallState; // Dialogic call state number

) queued_ev[MAX_EVENTS_PBR_LINB] ; boolean bUserlβOffHook; /* is user/caller off-hook ? */

int num_openifile; /* for play, num open indexed files

_

/* stuff set from initplay, addplay, play commands */ struct pitem plist [MAX_PF] ; /* play list items */ int pliβt_count; /* num files in play list */

};

A. IPC Server Laver Procedures Pertinent routines of the IPC server layer 508 are set forth in Appendix F. The IPC server layer 508 runs a polling loop in the procedure WinMainO , and when a new message has been received, passes it on to either MainWndProcO or DDEWndProc depending on the specified destination window. When each client starts up, it sends a WM_DDE_INITIATE message by DDE to the server process's main window. This message is received in the IPC server layer's polling loop. In response, the server calls ServerlnitiateO , which opens up a hidden window for the particular client and establishes a "conversation" between server and client by placing a correspondence between the two windows in the next available entry of an array Conv[] . Eventually the polling loop will receive a WM_DDE_CONNECT command from the client, with the client requesting telephony line 0. If telephony line 0 is already assigned to a client, the server polling loop will return a "busy" event code. The client will then request connection to line 1, and so on until the first free line is found. When this occurs, the IPC server layer will update the specified entry of its linet] table with an identification of the client and server DDE windows now associated with the channel, and mark the channel as in-use. It then passes the WM_DDE_CONNECT command down to the tm_Do_Command0 procedure of the test mode layer 509. Assuming the system is not in test mode, the test mode layer 509 simply passes the command on down to an ld_Do_Command() procedure of the line driver interface layer 510. (It

is assumed herein that test mode is disabled, in which case all calls to the test mode layer are passed directly to the line driver layer. The procedure names are the same, except for a "tm__" prefix for test mode layer procedures and as "ld_" prefix for line driver layer procedures. The two versions of each procedure name are therefore used interchangeably hereinafter and in the appendices.)

If the polling loop receives a WM_DDE_POKE command from a client, then MainWndProcO or DDEWndProcO pass the command on to an IPC server layer routine ServerPokeO, which extracts the command from the message, checks for errors, and calls do_command() . The do_command{) procedure begins by setting up a default event structure for return to the client, and then performs various preliminary procedures depending on the command. For the command DVRC_ADDPLAY, do_command() locates the specified voice file in the memory structure of Fig. 1 and assembles a complete path name to the file. It then places the command in the client's entry in the linet] array and sets a "command-in-progress" bit in that entry. The same happens for DVRC_INITPLAY and DVRC_PLAY. do_command{) then calls the ld_Do_CommandO procedure in the line driver interface layer 510, passing the command structure and an event structure for return information. When ld_Do_Command0 returns, do_cαmmand() returns the event back to the client using the IPC server layer 508 send_event0 procedure. do_command() then unsets the "command-in-progress" bit for the current client, and returns to ServerPokeO. ServerPokeO then sends a WM_DDE_ACK back to the client to acknowledge receipt of the command.

If the polling loop in WinMainO receives a time-out event, then it calls do_polling(), which calls the line

driver layer to see if any command currently in progress has completed. If so, then the return event structure is transmitted to the client and the command-in-progress flag for that client's entry in the IPC server layer's linet] array is cleared.

B. Line Driver Interface Lavβr Procedures

The line driver interface layer 510 has four procedures which are callable from the IPC server layer 508: ld_Initialize() (called to start up the layer); ld_Shutdown() (called to shut down the layer); ld_Do_Cαmmand0 (execute a new command from a client or the next stage of a command already in progress) ; and ld_Process_Event0 (handle an event that came from a telephony card driver using a Windows TAPI service provider) . Pertinent ones of these procedures, as well as pertinent support procedures, are set forth in Appendix G. l. IςLIni-ialiS- O Referring to Appendix G, when the ld_Initialize() procedure is called by the IPC server layer 508, it starts up the Dialogic driver 512, finds out how many telephony channels exist, and initializes them. The Dialogic driver 512 includes a terminate-and-stay- resident (TSR) facility which has been pre-loaded into memory. It is initialized by calling a Dialogic procedure startsysO, which calls a Windows function to generate a software interrupt. The software interrupt vectors to the TSR facility which communicates with the telephony cards 120 via predefined system hardware address ports in a conventional manner. The startsysO function returns the number of telephony channels available. Initialization of the telephony channels

takes place by initializing the line driver interface's array linet] of active line states to initial values. 2. Id Process Event()

The routine is pertinent only to certain kinds of telephony card driver software. Events generated by the Dialogic telephony card and driver are passed "up" from the driver layer 512 to the line driver interface layer 510 as described herein. Other kinds of telephony card driver software, however, such as those that use a Windows TAPI service provider, pass events "down" from the top of the server 214 into the IPC server layer 508. These events would be handled by ld_Process_Event() . The present embodiment uses the former technique, so ld_Process_Event0 is never used. 3. Id Do Command()

This procedure first uses a routine Check_For_Event0 to obtain all events which are waiting in the Dialogic event queue, and transfers them into the server 214's event queue for the appropriate channel numbers. All events pending in the Dialogic event queue are transferred, regardless of the channel number to which they pertain. The server 214's event queues are serviced by the periodic invocation of the IPC server layer 508 do_polling() routine as previously described. The ld_Do_Cc____nd() procedure can be called to start a new command or to perform the next stage of a staged command. The IPC server layer 508 indicates in the argument list to ld_Do_Comman () whether the present invocation of the procedure is to start or continue a command, the current stage of a staged command being maintained by the line driver layer 510 in its own static line table. In the case of the new command, ld_Do_Command() copies the command into the current channel's entry in the line driver layer's static line

table, sets the command stage to 0, and sets a flag for the entry to indicate that a command is now in progress. Whether or not this is a new command, the procedure then 'switch'es on the command number. If the command is DVRC_CONNECT, then a

DoConnectCommand0 procedure is called which checks whether the specified channel is available, updates the line driver layer's linet] entry for the channel, and invokes the telephony card driver TSR routines to establish the connection.

If the command is DVRC_INITPLAY, then the procedure merely initializes a play list in the line table entry for the current channel. Since this is a single-stage command, the routine then turns off the command-in- progress bit in the entry of the line table, copies an "EV_INITPLAY" code into an event structure, and returns to the IPC server layer 508.

If the command is DVRC_ADDPLAY, then the routine merely adds the provided voice file name to a play list within the line table entry for the current channel. Again, it turns off the command-in-progress flag in the entry, copies the code EV_ADDPLAY into the return event structure, and returns to the IPC server layer 508.

The DVRC_PLAY command is a multi-stage command. A number of different stages are set out in Appendix G, but only stages 0, 11 and 12 need be discussed herein. Stage 0 indicates the start of a command, and in this stage, the procedure calls another line driver interface layer 510 procedure, Play_Playlist0. This is also a staged procedure, and ld_Do_Cαmmand sets the stage to 10 prior to calling Play_Playlist0. Play_Playlist0 changes the command stage for the current channel to stage 11 as described hereinafter.

The first time Play_Playlist0 is called at the start of a command (command stage 10) , it loops through all of the items on the play list in the current channel's entry in the static line table, opening each voice file, and determining the DOS file handle, the starting position in the file of the voice prompt, and the length of the voice prompt. Note that the starting position will not always be at the beginning of the file, since the line driver layer 510 supports the presence of multiple indexed prompts in a single file. Play_Playlist() then sets up a table Files_Index[] listing the DOS file handle, the starting position and the length, for each voice prompt in the play list. The format of this table is defined by Dialogic. Play_Playlist() then builds another Dialogic-defined structure, called a read/write block (My_rwb) . Specifically, a pointer to Files_Index[] is written into My_rwb, as are a number of other parameters and pointers, including certain flags which were set by the client in compute layer 504. Such flags include whether speaking should terminate if the caller presses a DTMF key, and if so, which key. Play_Playlist0 then passes My_rwb and the current channel number to the Dialogic card TSR driver via the Dialogic xplayf0 function. This function will return after the information is transmitted to the telephony cards 120 and will not wait for all of the speaking to complete. In the background, the telephony cards obtain the actual voice data from the voice files via interrupts to the TSR driver as needed. In the meantime, Play_Playlist0 sets the command stage in the current channel's entry in the static line table to stage 11 and returns to ld_Do_Cαmmand() , which merely returns to the IPC server

layer 508. The Dialogic TSR will add an event to a Dialogic event queue when speaking has terminated.

As previously described, the IPC server layer 508 do_polling() routine periodically calls ld_Do_Command0 for each channel for which a command is in progress. In can be seen in Appendix G that if ld_Do_Command() is called before the Dialogic telephony card has sent an event indicating termination of speaking, then ld__Do_Command0 simply returns to the polling routine. If a Dialogic end-of-playing event has been placed in the event queue for the present channel during the pendency of a DVRC_PLAY command, then the command stage will still be 11. ld_Do_Cαmmand() will again call Play_Playlist0 which, in stage 11, closes all files that contain only one prompt, and examines the Dialogic event to determine why speaking terminated. Assuming it terminated normally, the procedure sets the command stage to 12 in the current channel's entry in the static line table, sets a flag to indicate whether termination occurred because all prompts were spoken or because of appropriate entry of DTMF digits, and returns to ld_Do_Command() . ld_Do_Co__αand() returns to the IPC server level do_polling() which, because the command is now done, sends an event back to the client to indicate as much.

Accordingly, a telephony voice response system has been described which includes a database language sequencer, a database control module having a plurality of procedures callable by the database language for performing database operations, and a telephony control module having a plurality of procedures callable by the database language sequencer for performing telephony operations. The database operations in the embodiment include reading and writing data to a database. The

telephony operations in the embodiment include speaking a predefined prompt onto a telephony channel, receiving and storing DTMF-encoded input from the telephony channel, and recording audio input from the telephony channel. The database language sequencer calls the database control module procedures and the telephony control module procedures in a sequence defined by a program prepared according to the rules of the database language. The telephony voice response system can control multiple telephony channels by running a separate task for each such channel under a multitasking operating system. A common channel server task is provided which manages the resources of the telephony card for all of the individual channel tasks. The foregoing description of preferred embodiments of the present invention has been provided for the purposes of illustration and description. It is not intended to be exhaustive or to limit the invention to the precise forms disclosed. Obviously, many modifications and variations will be apparent to practitioners skilled in this art. The embodiments were chosen and described in order to best explain the principles of the invention and its practical application, thereby enabling others skilled in the art to understand the invention for various embodiments and with various modifications as are suited to the particular use contemplated. It is intended that the scope of the invention be defined by the following claims and their equivalents.

APPENDIX A

Example FoxPro Code

Copyright 1994 Vovsvs Corporation

SET EXACT ON SET NEAR OFF * * Exit from the program if ESC is pressed. * ON ESCAPE RETURN * * Set the library to "voysaccf" * SET LIBRARY TO \voyβaccs\core\voyβaccf retval * VSet("AppPrompts", "\voysaccs\apps\parttime\prompts") retval « VSet("Recordings", "\voysaccB\apps\parttime\record") * * Get the hourly rate and the shift length out of the first * record of the * BASEDATA table. * SELECT 1 USB BASEDATA GOTO 1 var_hourly - laborrate var_hours_per_shift _ hrspershft * * Move on to the WORKERS table, which is the one we'll use for * the phone * conversations. * USE WORKERS SET ORDER TO TAG ID * * To handle calls continuously, go into an infinite loop of * receiving calls. * * DO WHILE .T. * Wait for the phone to ring. Answer it after one ring. Then * call the * pt_call procedure. * retval _ VWaitRingd, 60) DO pt_call * * If the phone has not been hung up,' speak the "goodbye" prompt. * IF VChkHangupO - 0 retval ■ VSpeakCptbye") ENDIF retval - VHangupO * ENDDO SET ECHO OFF SET LIBRARY TO RETURN

************************************************************ ***** * * pt call: process one call from a part-time worker. * PROCEDURE pt call loc_id - " " var_num_shifts _ 0 var_confirm - .F. * * Say hello. * retval - VSpeak("pthello") * * Loop: ask for a 4-digit worker ID until we get a good one. * Give the * caller three tries, then hang up. * FOR var_strikes « 1 TO 3 IF VChkHangupO « 1 RETURN BNDIF * * Get a 4-digit ID. Set an inter-digit timeout of 15 seconds * just in * case the caller decides to just sit there. * retval - VSpeak("ptid") retval - VGetTonβs(βloc_id, 4, " ", 15) IF retval < 0 RETURN ENDIF * * Check the ID in the WORKERS table. * SEEK loc_id IF FOUND{) EXIT ENDIF * * If it wasn't found, tell the caller so and try again on the * first two * "strikes"; on the third one, hang up. * IF var_βtrikes < 3 retval - VSpea ("ptnoid") ELSE RETURN ENDIF ENDFOR * * Go through each of the shifts individually. For each one, if * the worker is scheduled to work, ask them to confirm. For each * one that * they confirm, add 1 to var_num_shifts. * IF eel DO query_shift WITH .T., datel REPLACE esl WITH var_confirm ENDIF

119 IF ls

120 DO query_shift WITH .F., datel

121 REPLACE lsl WITH var_confirm

122 ENDIF 123

125 DO query_βhift WITH .T., date2

126 REPLACE es2 WITH var_confirm

127 ENDIF 128

129 IF ls2

130 DO query_βhift WITH .F., date2

131 REPLACE ls2 WITH var_confirm

132 ENDIF 133

134 IF es3

135 DO query_βhift WITH .T., date3

136 REPLACE es3 WITH var_confiπn

137 ENDIF 138

139 IF ls3

140 DO query shift WITH .F., date3

141 REPLACE Ϊs3 WITH var_confirm

142 ENDIF 143

144 IF es4

145 DO query_βhift WITH .T., date4

146 REPLACE es4 WITH var_confirm

147 ENDIF 148

149 IF ls4

150 DO query_shift WITH -F., date4

151 REPLACE ls4 WITH var_confirm

152 ENDIF 153

154 IF es5

155 DO query_βhift WITH .T., date5

156 REPLACE eβ5 WITH var_confirm

157 ENDIF 158

159 IF ls5

160 DO query_shift WITH .F., date5

161 REPLACE ls5 WITH var_Cθnfirm

162 ENDIF 163

165 DO query_shift WITH .T., date6

166 REPLACE es6 WITH var confirm

167 ENDIF 168

169 IF ls6

170 DO query_shift WITH .F., date6

171 REPLACE lβ6 WITH var_confirm

172 ENDIF ~ 173

174 IF es7

175 DO query_shift WITH .T., date7

176 REPLACE eβ7 WITH var_confirm

177 ENDIF

180 DO query_βhift WITH .F., date7

181 REPLACE ls7 WITH var_confirm

182 ENDIF

183 *

184 * Now that we've checked all the shifts, give the output result.

185 * If there are no shifts, tell the worker so.

186 *

187 IF var num_shiftβ _ 0

188 retval~s VSpea ("ptnoβhft")

189 *

190 * On the other hand, if there are shifts scheduled, tell the

191 * worker so

192 * and give the earnings prediction. The DOLLARS flag is used to

193 * speak

194 * the earnings in dollar format.

195 *

196 ELSE

197 retval - VSpeak("ptsigned", var_num_shifts, "ptshiftβ", ;

198 "Dollars", ;

199 var num_shiftβ * var hours_per_βhift * var_hourly)

200 ENDIF 201

202 RETURN 203

204 __-_--*-_-_-*-_------*_--_-_-_*_---_-_-__-_----*_--*--_-*-__ _--_*

205 *

206 * Query_βhift: ask the worker about working a particular shift.

207 * If p_r_m_early is TRUE, it is an early shift.

208 *

209 PROCEDURE query_shift

210 PARAMETERS param early, param_date 211

212 loc_confinn _ " " 213

214 IF VChkHangupO - 1

215 RETURN

216 ENDIF

217 *

218 * Tell the caller about the shift. Note that the date must be

219 * converted to character format before it can be spoken. The DAY

220 * flag is used to speak the DOW() as a day of the week.

221 *

222 IF param_early

223 retval - VSpeak("ptearly", "Day", DOW(param_date) ,

224 DTOC(param date))

225 ELSE

226 retval - VSpeak("ptlate", "Day", DOW(param_date) ,

227 DTOC(param date))

228 ENDIF

229 *

230 * Now do a loop until we get a 1 (_αonfirm) or 2 (-won't work)

231 * from the caller. Give the caller three chances.

232 *

233 FOR var_strikeβ _ 1 TO 3

234 IF VChkHangupO - 1

235 RETURN

236 ENDIF

237 *

238 * Ask for confirmation.

239 *

240 retval _ VSpeak("ptconfπn")

241 retval - VGetTones(βloc confirm, 1, " ", 15)

242 *

243 * Check if it's a 1 or a 2. If so, set var_confirtn and exit.

244 * If the shift is confirmed, also increment~var num shifts.

245 * 246 247 248 249 250 251 252 253 254 255

256 *

257 * If not, remind the caller to press 1 or 2, and

258 * loop again on the first two tries; on the third, assume

259 * non-confirmation.

260 *

261 IF var strikes < 3

262 retval - VSpeak("ptlor2")

263 ELSE

264 var_confiπn _ . F .

265 RETURN

266 ENDIF

267 ENDFOR 268 269 RETURN

APPENDIX B TELEPHONY MODULE PROCEDURES

Copyright 1994 Voysys Corporation

2.0 The voys ccess Library - Introduction

The voysAccess library is a set of FoxPro functions created using the FoxPro Library Construction Kit (LCK) .

The library is called VOYSACCF.FLL. This library must be loaded at the beginning of a voice response program.

To do this, the user's program calls: SET LIBRARY TO \ \voysaccf

2.1 The Functions (in alphabetical order)

The following is a list of the voysAccess functions. They are implemented in FoxPro as functions that return an integer return code. All of the functions, and their argviments, are case-independent (VSpeak("HELLO.wav") is the same as vspeak("hello.wav") ) . The return codes from the functions are coded as follows:

Positive integers Function-specific flavors of success Zero Standard "success" code

-X or -XX Function-specific flavors of failure -XXX Standard failure codes

2.1.1 Check for hangup - VChkHangup

Syntax: retval ■ VChkHangupO

Arguments: none Return Codes: 1 Success, hangup detected

0 Success, no hangup detected

Description: Check whether the caller has hung up on your program. This is a good thing to do at regular intervals. One frequent cause of caller hang-ups is rotary-dial phones, which cannot produce tones and therefore are not useful with voysAccess applications. If the caller has hung up, you should do a VHangup on your side before waiting for the next call.

2.1.2 Dial out - VDial

Syntax: retval - VDial ( <phone_number> [ , <num_rings> ] )

Arguments:

_phone_number> A character string containing the phone number. The characters "0123456789*#ABCD" will be dialed if included. Special characters allowed in a dialing string are:

T use tone dialing (default)

P use pulse dialing W wait for dial tone

, pause for 2 seconds

1 do a switch hook

All other characters ("-", " (", and ") ", for example) in a dialing string will be ignored.

<num_rings> The number of times to let the phone ring before deciding to give up (default is 6) .

Return Codes:

0 Success -3 Busy -4 Rings, but no answer in <num_rings> rings (RNA) -9 Other failure

-11 Bad "rings" parameter; must be a positive integer

Description: Call out to any phone number. This and VWaitRing are the two ways to start a call. The phone number must be in a character format. Example: Dial the digit "9" to ask for an outside line, wait two seconds to make sure you get it, then dial Voysys' phone number. Notice that extraneous characters will be ignored. retval - VDial("9, (510) 252-1100")

2.1.3 Get Tone (DTMF) Input - VGetTones

Syntax: retval - VGetTones( ®<variable> [,<number_of_digits>

[,<key_to_terminate_on>

[,<interdigit_timeout>] ] ] )

Arguments: β .variable. A character variable passed by reference. After a successful return, this variable will contain the sequence of digits entered by the caller.

<number_of_digits> Integer; the number of digits to terminate on (0 - any number of digits). Default number of digits is 1.

<key_to_terminate_on> A string containing a single character, which should be a key (0..9, *, or #) to terminate input upon.

(" " - no key; this is the default) .

<in erdigit_timeout> Integer; maximum number of seconds between keys (0 « no interdigit timeout) . The default here is 10 seconds.

Description: Get one or more tones (DTMF digits) from the caller. This is the caller's primary way of coπmunicating with your application. You must pass in a character variable by reference. The rest of the arguments specify how VGetTones knows when the callers input is done. You have three choices: 1) you may specify a set number of digits (this is good for menu choices and fixed-length data like account numbers) , 2) you may specify a key that signals end-of-input (as in "please enter your account number and then press pound"), or 3) you may specify an interdigit timeout (wait N seconds after each digit - if there is a timeout after the first digit, then the input is complete) . The last method is a good way of getting variable-length

input out of novice users. Note that in case (2) , the key you specify as the termination key does not get included in the string passed back to you by VGetTones. In rarer cases, you may elect to use more than one of these termination methods; put together, they operate on an OR basis - if any of the termination methods is found, input is terminated. The defaults for the termination methods are <number_of_digits> - 1, <key_to_terminate_on> - " ", and

<interdigit_timeout> - 0; in other words, the complete default is to get one digit and then stop. All examples below assume you have a character variable named "MYVAR". Note that to use the <key_to_terminate_on> argument, you must specify a <number_of_digits> parameter; to use the <interdigit_timeout> argument, you must specify both preceding parameters. VGetTones will return a -1 if the caller has not pressed any keys at all within the first five seconds (5 seconds is a default; this value can be reset with VSetTmOut) ; this usually indicates a rotary phone or a brain-dead caller. The timeout issue needs to be carefully considered by the programmer. When prototyping, it is easiest to work without timeouts; this model is closest to PC use, where the system waits patiently for you to finish your input. In voice systems, things are more complicated.

One scenario to think about, for example: your program has asked for a 7-digit phone number. If the caller punches in 6 digits, thinking he/she has punched in 7, your program will now wait forever for that seventh digit unless you have a timeout set. For that reason, we have timeouts set (5 seconds for the first key, 10 for inter-digit timeout) on as defaults. This case also shows that you should actively check the return codes from VGetTones. If you had asked specifically for seven digits (as in the second example below) , ' you would get a 0 return code if the caller put in seven digits, or a 2 return code if there was an inter-digit termination timeout. In this case, you want exactly 7 digits, so a return code of 2 should cause you to do the VGetTones again.

Example: Get a single-digit menu choice from the caller (assumes you have a prompt 'menu.wav' recorded that speaks your menu) .

retval « VSpeak("menu.wav") retval - VGetTones(βMYVAR)

Example: Get a seven-digit account number from the caller. retval - VGetTones(βMYVAR, 7)

Example: Get a credit card number of arbitrary length, terminated with a # sign, from the caller. Notice that you use 0 (don't care) for the number of digits. retval - VGetTones(βMYVAR, 0, "#")

Example: Get a credit card number of arbitrary length, terminated when the user pauses for two seconds between digits (two seconds has been found to be a good choice with normal callers; higher values would be appropriate for complete novices). Notice that you use 0 (don't care) for the number of digits and " " (no terminating key) for the key to terminate on. retval « VGetTones(βMYVAR, 0, " ", 2)

2.1.4 Get Tone (DTMF) Input (Macro) - VGetTonesM

Syntax: retval « VGetTonesM( <voice__prompt>, <tries>, β<variable> t,< ___ber__of_digits>

[,<:key_to_terminate_on> t,<interdigit_timeout> [,<valid_list>] ] ] ] )

Arguments:

_voice_prαmpt> A character string containing a file name or memo field name. See the description of the VSpeak function.

<tries> The number of total "tries" to make. In each try, the prompt is spoken, then

VGetTonesM waits for input. If it does not get correct input, it tries again, until <tries> is exhausted.

«<variable> A character variable passed by reference. After a successful return, this variable will contain the sequence of digits entered by the caller.

- 61 -

<number_of_digits> Integer; the number of digits to terminate on (0 ■ any number of digits) . Default: 1 digit. <key_to_terminate_on> A string containing a single character, which should be a key (0..9, *, or #) to terminate input upon. (" " _ no key) . Default: no key. <interdigit_timeout> Integer; maximum number of seconds between keys (0 ■ no interdigit timeout) . Default: 10.

<valid_list> Character; a comma-separated list of legal choices. The default for this argument is no validation list.

Return Codes:

2 Success; terminated on inter-digit timeout 1 Successj terminated on specified key

0 Success; terminated on number of digits -1 Failure; terminated on initial timeout -2 Failure; terminated on hangup -3 Failure; ran out of tries -11 Failure; bad termination digits (must be

0-50) -12 Failure; bad termination key (must be 0-9,

*, #, or blank) -13 Failure; bad interdigit timeout (must be no -negative)

Description: VGetTonesM is a "macro" command that combines features of VSpeak and VGetTones. It is meant to address simple demands like "If you're using VISA, press 1. If you're using

Mastercard, press 2. " and to spare you the necessity of writing loops and input checking. VGetTones allows you to specify multiple "tries"; for each try, this function will speak the prompt cmd then wait for input. If there is no input, or if the input does not match one of the possibilities given in the optional <valid_list>, the function will try again, provided the total number of tries has not been exhausted.

Example: Get a four-digit account number into the variable MYVAR. If the user does not enter any keys, or does not enter enough keys, try one more time before reporting failure. This example assumes that you have a prompt file

"getacct". retval - VGetTonesM( "getacct", 2, βMYVAR, 4)

Example: Ask the user "For VISA, press 1. For Mastercard, press 2. For Discover, press 3" (assume this is prompt "getcard"), and verify that the input is in fact 1, 2, or 3. Try this three times before giving up. retval - VGetTonesM("getcard", 3, βMYVAR,

1, " ", 0, "1,2,3")

For more information, please see the descriptions of VSpeak and VGetTones.

Design Notes: Can anyone suggest a better name for this function? 2.1.5 Get Word (Voice Recognition) Input - VGetWords

IMPORTANT NOTE: This function will not be included in the initial developers kit. Also, this design is based on our prototype integration with the VPC board; it may be modified in the future.

Syntax: retval ■

VGetWords( β<variable> [, -Sub-vocabulary_na e> t,<number__of_words> t,<word_to_terminate_on>

[,<interword_timeout>]]]] )

Arguments: β<variable> A character variable passed in by reference. After a successful return, this variable will contain the words spoken by the caller. Numbers will be returned in numeric ("1" not "one") form. If there are multiple words, they will not be separated by spaces.

<sub-vocabulary_name> This is the name of the sub-vocabulary set which contains the words to be recognized. The current vocabularies are "yesno" (the words"yes" and "no"), "numbers" (the ten digits, plus "oh"which is interpreted as "zero"), and "all", which has"yes", "no", "oh", and 0-9. If no sub-vocabulary is specified, "all" is used.

<number_of_words> Integer; the number of words to get.

0 any number of words.

The default is one word.

<word_to_terminate_on> Character; this is a word to be used as a termination marker

(this word is not included in the returned string) . " " - no word to terminate on; this is the default.

<interword_timeout> Integer; the maximum number of seconds between words . 0 - no timeout ; the default is 10 seconds .

Return Codes:

2 Success; terminated on inter-word timeout

1 Success; terminated on specified word 0 Success; terminated on number of words -1 Failure; terminated on first-word timeout -2 Failure; terminated on hangup

-11 Failure; bad number of words (must be

0-50) -13 Failure; bad interword timeout (must be non-negative) -XXX Failure; 50-character word buffer filled up

Description: Get one or more words by voice recognition from the caller. You must pass in a character variable by reference. The second argument lets you choose a sub-vocabulary name from which words will be recognized. The last three arguments specify how VGetWords knows that input is finished. The standard way is to specify a given number of words. You also have the choice, however, of choosing to terminate on a given word,or to terminate if there is a sufficient pause between words. The default first-word timeout is five seconds.

In other words, if the caller hasn't said anything within the first five seconds after a

VGetWords call, the function will return a -l

(failure) error code.

Example: Get a "yes" or a "no" from the caller. Note that this example uses the default termination condition: get one word. retval - VGetWords(βMYVAR, "yesno")

Example: Get a 4-digit account number from the caller. retval - VGetWords(βMYVAR, "numbers", 4)

Example: Get a number of arbitrary length from the caller. Assume the caller has finished reciting the number if there is a 4-second pause after any word. Note that this example uses number of words-0 (any number of words) and terminating word-" " (do not terminate on a particular word) . retval - VGetWords(βMYVAR, "numbers", 0, " ", 4)

Design Notes: In the future, we will want to think about a separator option for this function (having the words un-separated is good for numbers and letters, but bad for real words) .

2.1.6 Hang up - VHangup Syntax: retval - VHangup0

Arguments: none

Return codes: 0 Success

Description: Hang up the line; terminates the call. It is important to remember to do this once a call is complete, to better prepare the system to make or receive the next call.

2.1.7 Record a message - VRecord

Syntax: retval -

VRecord( <filename> | <memo_name> | β<variable> t, _max_recording_length>] )

Arguments:

.filename. Character; the file name for the recording. Unless a full path is specified, this file will be placed

in the recordings directory set with VSetDir (see below) .

_memo_na___> Character; the name of a memo field where the recording is to be stored. The name of the memo field must be enclosed in quotes.

®.variable- Character variable passed by reference. Generate a unique name for the recording and pass it back in the variable. The file will be placed in the directory set with VSetDir. <max_recording_length> Integer; maximum length of the recording in seconds. Default is 120 seconds.

Return Codes: 0 Success

-1 Failure; timeout

-11 Failure; invalid input (not a character string or character variable passed by reference) -12 Invalid file name

-XX Invalid recording length; must be a positive integer

Description: Record a message (voice file) from the caller. The file this is to go to can be explicitly specified, or the voysAccess system can generate a unique name.

The second method is generally more appropriate; it is done by passing in a character variable passed by reference. The recording will be terminated after a two-second silence, or if the caller presses "#". Unless otherwise specified, the files will be placed in the RECORDINGS directory set with VSetDir. Recordings can be

"embedded" in a database by putting the file name in a character field, or, more directly, by putting the recording into a memo field. Notice that there is the possibility of a naming conflict between memo fields and file names. For each name passed in quotes that is not obviously a file name, the voysAccess software will first check to see whether it is a valid memo field.

The final parameter to VRecord is the (optional) maximum recording length. The recording will terminate if the length of the message reaches the set maximum. The default is 120 seconds.

Example: Record a message into the file "newmsg.wav"; allow it to be no more than 30 seconds long. retval « VRecord("newmsg.wav", 30)

Example: Record a message into a file name chosen by the system. Get name back in the character variable MYVAR.

MYVAR - " " retval - VRecord(βMYVAR)

Example: Record a message into the memo field MΪMEMO. retval - VRecord("MYMEMO")

2.1.8 Send Tones (DTMF) - VSendTones

Syntax: retval - VSendTones( <tone_string> )

Arguments:

<tone_string> A character string containing tones to be sent over the phone line.

Valid tone characters are 0..9, *, #, and A..D.

Return codes: 0 Success

-XX Invalid tone in tone string

Description: The VSendTones function sends Touch- tone (DTMF) presses over the phone line. It has the same effect as if a human user pressed those keys on his or her phone. This command is mostly useful for creating automated computer- to-computer applications; testing or data transfer, for example.

Example: Play the tones "1", "2", and "3" (analogous to a human user pressing these same keys on their phone) : retval ■= VSendTones("123")

2.1.9 Set voysAccess parameters - VSet

Syntax: retval « VSet( <parameter> , <value> ) Arguments:

Parameter Allowable Values AppPrompts <directory that application prompts are stored in> Recordings <directory that recordings are stored in>

SysPrompts <directory that system prompts are stored in>

DateSpeak "mmddyy", "mmdd", "ddmmyy", "ddmm"

VGetToneslstKey <positive integer>

TestMode "on", "off"

Return Codes:

0 Success -71 Failure; bad parameter (first argument) -72 Failure; bad value (second argument)

Description: The VSet function allows the user to set various parameters that affect the behavior of the voysAccess tool kit. All of these settings are also available in the "VOYSACCS.INI" file, with the same names. The rule of precedence is: any use of VSet with a particular parameter overrides the .INI setting of that parameter, but only for the session in which the VSet is used (the .INI file is not changed) .

AppProπrots. Recordings. SvsPronrots:

These set the directories in which voice files are expected to be found (we use "prompts" to mean voice files that are part of your application, like "for sales, press 1..." and "messages" or "recordings" to indicate voice files recorded by callers) . "SysPrompts" sets the directory in which system prompts (like "thirteen" or "April") are to be found.

"AppPrompts" sets the directory in which you've placed your application's prompts. "Recordings" sets the directory in which your user messages are recorded. We suggest "\VOYSACCS\APPS\<yourapp>\PROMPTS" for your application's prompts, and

"\VOYSACCS\APPS\<yourapp>\RECORD" for the recordings. "RECORDINGS" has a direct effect on where the files recorded with VRecord are placed; all three settings are used to set VSpeaks search order. We suggest that you set your AppPrompts directory (and your Recordings directory, if your application does any recordings) in each of your voysAccess applications. You would do this with the following commands: retval « VSet("AppPrompts",

"c:\voysaccs\apps\myapp\prompts") retval « VSet("Recordings",

"c:\voysaccs\apps\myapp\record") assuming that you followed our suggested directory structure, that your files were on the C: drive, and that the directory name for your application was "myapp". It is generally not necessary to specify where your system promptβ are stored; since this is normally fixed, it can be specified in the .INI file.

DateSpeak:

This sets the way dates are to be spoken by VSpeak. The default is " mddyy". The allowable settings are:

"mmddyy": speak dates as month, day, year

("January 25th, 1994") "mmdd": speak dates as month and day only

("January 25th") "ddmmyy": speak dates as day, month, year ("the

25th of January 1994") "mmdd": speak dates as day and month only

("the 25th of January") VGetTonesIstKev:

This sets the first-key timeout for the VGetTones function. This is how long, in seconds, the function will wait if no keys are pressed before declaring a failure. Note that this is different from the interdigit timeout that you can set in VGetTones itself: the inter-digit timeout is how long the function will wait between digit presses. TestMode:

Test Mode allows the programmer to test their program without being connected to a phone line. When TestMode is set to "on", the core telephony functions put up a screen in Windows allowing the user control of their results. For example,

VWaitRing function puts up a screen that allows the user to say whether someone called in, or whether there was a timeout. 2.1.10 Speak prompt(s) - VSpeak

Syntax: retval -

VSpeak( <value> t,<value>] ... [,<value>] ) Arguments:

VSpeak can take up to 10 <value> parameters.

<value> can be either a value to be spoken, or a code word indicating a "speak mode" for the following argument(s).

Valid values to be spoken: any numeric value any date (converted to a character string) a character string containing a voice file name a character string containing the name of a memo field that contains voice data

Valid speak modes:

DOLLARS: speak numeric amounts as dollars and cents

DIGITS: speak numeric amounts as digits DAY: speak integer amounts as days of the week (1-Sunday) Return Codes:

1 Success, but interrupted by DTMF keypress

0 Success

-2X Failure: prompt file not found (-21 means first arg, etc.)

Description: VSpeak allows you to speak a sequence of prompts or values. It can take up to 10 arguments. The arguments can be values

(numbers, or character strings containing dates or prompt names) or 'speak modes' that regulate how numeric values should be spoken.

Example: To speak the string "We have <number> widgets in our inventory, " you would record a voice file containing "We have" (say maybe 'wehave.wav') and another containing "widgets in our inventory" ('widgets.wav'). If WIDGETS was an integer variable or data field containing the number of widgets in inventory, you would then use the FoxPro expression: retval - VSpeak("wehave.wav", WIDGETS,

"widgets.wav")

The "speak modes" only apply to numeric values. The default is a standard numeric format: 432 is spoken as "four hundred thirty two". DIGITS says speak out the digits: 432 would be spoken as "four three two" (this is useful for account numbers and suchlike) . DOLLARS speaks numbers as dollar amounts: 432.54 would be spoken as "four hundred thirty-two dollars and fifty-four cents". DAY speaks numbers in the range 1..7 as days of the week; this is designed to work hand-in-hand with the FoxPro DOW( ) function. The speak mode must precede its target(s) in the VSpeak function; putting in a speak mode says

"speak all the rest of the numbers in this VSpeak in this format unless I tell you otherwise". The current version allows you to either specify the file name and the extension ("pname.wav") or just the name ("pname"), assuming that the extension is ".wav". We usually use the latter, the idea being to facilitate portability to other file formats in the future (you could have one version of the file in each format, and tell us once which file format you were using) . You may also use the file name plus the dot, but without the extension ("pname.") to tell the voysAccess software that it is a file name.

Notice that there are possible conflicts between voysAccess code words (such as DOLLARS, file names, and memo field names. If a string passed in to VSpeak has letters of the alphabet, but not the dot (.), that would signal a file name, the voysAccess software will first check to see if it's a code word, then if it is a memo, before assuming that it's a file name.

Example: To speak "Today is <day of the week> <date>" you would execute the following FoxPro code (assuming that you have recorded a voice file 'today.wav' with the phrase "Today is..."): retval - VSpeak("today", "DAY", DOW(DATE0 ) , DTOC(DATE() ) )

Example: To speak the contents of the memo field NAME (if name has voice data inside it) : retval « VSpeak{"name")

2.1.11 Wait for the phone to ring and answer it - VWaitRing

Syntax: retval -

VWaitRing( [<num_rings> [, <timeout> ] ] ) Arguments:

<num_rings> The number of rings to wait before answering.

Optional argument; default is 2 rings.

Must be a positive integer.

<timeout> Number of seconds to wait before timing out; 0 means don't time out.

Optional argument (you must specify a

<num_rings> if you want to set a <timeout>) ; default is 0 (no timeout) .

Must be a non-negative integer.

Return Codes:

0 Success (answered a phone call) -1 Timeout

-11 Invalid <num_rings> argument

-12 Invalid <timeout> argument

Description: Wait for the phone to ring and answer it. The <num_rings> parameter sets the number of rings to wait before answering the phone (l or 2 are good choices) ." The <timeout> parameter sets the number of seconds to wait before timing out (0 - no timeout - is easiest to work with at the beginning) .

APPENDIX C DB_Fox. C

CqpYT -h. 199?-1,99* vς»γfy§ Corpg iga

File: DB_Fox.c

*

-

* Purpose:

♦ Set of routines for FLL interface (FoxPro 2.0, 2.5, 2.6)

*

* $Log: /VoysAccess/core/_bi/DB_FOX.C $

♦include <dos.h> ♦include -errno.h. ♦include <math.h> ♦include <fcntl.h> ♦include // _mktemp ♦include ♦include ♦include <βtdlib.h> // Definition of _MAX_PATH ♦include <βtring.h> ♦include <windows.h> ♦include <dde.h>

♦include "pro__ext.h"

♦define IN_MAIN 1

// if define equals to 1, enable fax support

// remember to go the end of this file to uncommented the fax

// functions in Foxlnfo structure

// need to define this before the .h files as it could be used in

// the .h header files

♦define DO_FAX 0

♦include "OS.h"

♦include "dVR.h"

♦include "Command.h"

♦include "Compute.h"

♦define MAX_PN 20 // maximum number of characters allowed for program name ♦define MAX LN 127 // maxiittum number of characters allowed for library name

♦define ___?BUFF_SIZE ( 8 * 1024 ) // 8 KB ♦define TMP TEMPLATE "-vXXXXXX"

• Information about state of each active task. *

- This data is shared by all client processes, but each process

* looks only at the data for itself.

_

* Processes find a free slot by looking at the blnUse field. */ struct line_t { boolean blnUse; // is this table slot in use ? unsigned int hTask; // Windows task handle int nLineNum; // line number (not really used) unsigned int handlerid; // FoxPro event handler ID

WHANDLE whandleClientDDE; // FoxPro handle of hidden client window HWND hwndClientDDE; // Windows handle of hidden client window

}; static struct line_t line[MAX_LINES] _ {0};

__---____**-______**_*_*_* ♦.♦*♦/

// Find line table slot that is being used by task hTask static int FindSlot ( const unsigned int hTask, // task handle (Windows only) struct line_t **pβp // ptr to ptr to slot in line table

int nVRetVal; // Voysys return value int nSlot; // slot in line table

// find slot in the line table by using hTask for ( nSlot - 0 ; nSlot < ___X_LINES ; nSlot++ ) { if ( line[nSlot] .blnUse &.&

( line[nSlot] .hTask _■ hTask ) ) break; if ( nSlot » MAX_LINES ) {

Debug ( 1, sprintf ( Debug_buf, "DB_Fox/FindSlot: couldn'

~ find slot in line table" ) nVRetVal _ RC_N0TINITIALIZBD^ // best guess goto done;

pep _ _line[nSlot] ; nVRetVal - RC_SUCCEED; done: return nVRetVal;

} /* function FindSlot */

/*** *** ********************************

Trim trailing blanks: trim_trailing This function trims trailing blanks in strings by replacing them with '\0's. It is needed because FoxPro passes strings filled with blanks to the end of the field, and because several of our routines (like "dollars" etc options, plus play of file names) do not check for trailing blanks. ********* *********************************************/ void trim_trailing ( char " ~ *buff er

char *buf_ptr; for ( bufjptr _ buffer + strlen ( buffer ) - 1; *buf_ptr ■■

' ' ; bufjptr-- ) *buf_ptr _ ( char ) NULL;

/* function trim_trailing */

GetProgramName Get filename of currently executing FoxPro program.

Gives base file name, in all caps, without PRG extension.

static void GetProgramName ( char far βzProgramName // result - name of PRG file ) {

FoxPro result structure FoxPro return value nPRetVal - Evaluate( tresult, "PROGRAM() " ),- szProgramName[0] - ( char ) NULL; if (nFRetVal — 0) { nCount - min ( MAX_PN, result.ev_length ) ; _fstrncpy ( ~ ~~ szProgramName,

_HandToPtr ( resul .ev_handle ) , nCount ) ; ~ szProgramName[nCount] _ ( char ) NULL; trim trailing ( szProgramName ) ; } " else {

Debug ( 1, sprintf ( Debug buf, "DB Fox/GetProgramName:

" ^Evaluate " ret %d", nFRetVal ) ) ; Debug ( 3, sprintf ( Debug_buf, "DB_Fox/GetProgramName: ret, szProgramName '%s'", βzProgramName ) ),- } /* function GetProgramName */

********************************************** *******

GetLibraryName

Get filename of currently executing library. **************************** ***************/ static void GetLibraryName ( char far *szLibraryName // result - name of LIBRARY file

FoxPro result structure FoxPro return value nFRetVal « _Evaluate( firesult, "SET('LIBRARY') " ); szLibraryName[0] _ ( char ) NULL; if (nFRetVal >_ 0) { nCount _ min ( MAX_LN, result.ev_length ); _fstrncpy ( ~ " szLibraryName,

_HandToPtr ( result.ev_handle ), nCount ) ; ~ szLibraryName[nCount] * ( char ) NULL; trim_trailing ( szLibraryName ) ; else {

Debug ( 1, sprintf ( Debug buf, "DB_Fox/GetLibraryName:

~Evaluate ret %d", nFRetVal ) ) ; }

Debug ( 3, sprintf ( Debug_buf, "DB_Fox/GetLibraryName: ret, szLibraryName '%β'", szLibraryName ) );

FoxPro event handler: EvtHandler

int FAR EvtHandler (

// event to send down // component of Windows message

// component of Windows message // component of Windows message // ptr to slot in line table // Voysys return value

// find slot in the line table nVRetVal _ FindSlot( GetCurrentTask ( ) , &βp ) ;

if ( nVRetVal 1« RC_SUCCEED ) return NO; // FoxPro should handle the event if ( theWindow !_ sp->whandleClientDDB ) return NO; // FoxPro should handle the event

// extract fields from FoxPro form to Windows form message * ( UINT ) ( theEvent--message ) ; wParam _ ( WPARAM ) ( theEvent->miβc ) ; lParam * ( LPARAM ) ( theEvent->miβc2 ) ; switch ( message ) {

NOT handle the event event.nBvent - EV_DDB; event.nLine » 0; event.C_error ■ 0; event.DV_error ■ 0; event.DOS error ■ 0; event.string[0] - ( char ) NULL; event.filename[0] > ( char ) NULL;

// pack fields from Windows form to voysAccess form event.dde[0] _ message; event.ddβilϊ » wParam; event.dde[2] - 1Param; nVRetVal ■ com ProceβsEvent ( sp->hTask, event ) ; if ( nVRetVal < RC_SUCCEBD ) {

Debug ( 1, sprintf ( Debugjbuf, "DB_Fox/BvtHandler: event

~ " handling failed\n" ) ) ; } return YES; // FoxPro should NOT handle the event /* function EvtHandler */

Put memo field data into a temporary file and return the filename if success.

int IβMemoField ( char •field, boolean •bMemoField, Value *val, Locator *loc

int nVRetVal;

NTI nNameTblIndex,-

*bMemoField _ FALSE; // try to find out if this is a memo field nNameTblIndex _ NameTablelndex ( ( char far * ) field ) ; if ( nNameTbllndex 1- -1 ) {

// name is in the name table if ( _FindVar( nNameTbllndex, 0, // database area number

( Locator FAR * ) loc

) ) { // variable with the name exist if ( _Load ( ( Locator FAR * ) loc, ( Value FAR * ) val ) I- 0 ) { nVRetVal _ RC_FOXPR0ERROR; goto done; } if ( val->ev_type _= 'M' ) { // memo field

// this is a memo field if ( val->ev width <_ 0 ) { nVRetVal " _ RCJFQXPROERROR; goto done;

bMemoField _ TRUE;

nVRetVal _ RC_SUCCEED ; done : return ( nVRetVal ) ;

} /* function IsMemoField */

Get temporary filename.

int GetTmpFilename ( const unsigned int hTask, // task handle (Windows only) const int nFileType, char *pTπτpFile

int nVRetVal; ulong dwDum y; char szTπtpName[MAX_FN] ; // temporary filename

// Assumption: pTmpFile has the size of MAX_DN + MAX_FN

// Attempt to find a unique filename strcpy ( βzTmpName, TMP_TEMPLATE ) ; if( mktemp( szTmpName ) __ NULL ) { nVRetVal - RC_OSBRROR; goto done;

}

// get the temporary file path from server nVRetVal _ cαm_Get ( hTask, nFileType, ( char far * ) "* pTmpFile, &dwDummy ) ,- if ( nVRetVal 1- RC_SUCCEBD ) goto done; ~ βtrcat ( pTmpFile, szTmpName ) ; strcat ( pTmpFile, ".TMP" ); nVRetVal - RC_SUCCBED; done: return ( nVRetVal ) ;

} /* function GetTmpFilename /

********************************************************* ******

Put memo field data into a temporary file. ** ** *** * * * * ** * * *** * * **** *** * * * * * * * * ********* * * * ********/ int PutMemoDatalnT pFile ( const unsigned int hTask, // task handle (Windows only) char filename, const Value val

lMemoSize _> val.ev_long; if ( lMemoSize <■ ό " ) { nVRetVal ■ RC_FQXPROERR0R; goto done;

}

MHandle - ( FCHAN ) val.ev_width; // position the file pointer on the beginning of the βection // that belongs to the current record lStartPos s ( long ) val.ev real; _FSeek ( MHandle, IStartPosT FS_FR0MB0F ) ; mh « _AllocHand ( TMPBUFF_SIZE ) ; if ( mh » 0 ) { nVRetVal _ RC_RAMFULL; goto done;

pBuff « ( char * ) _HandToPtr ( mh ) ;

nVRetVal - GetTmpFilename ( hTask, GET 1MPFILES, ( char ♦ ) tszTmpFile ) ; if ( nVRetVal !« RC_SUCCEED ) goto finiβh2;

// open the temporary file

FHandle « _FCreate ( ( char far * ) &szTmpFile, FC_N0RMAL ) ; if ( FHandle ■« -1 ) { nVRetVal - RC_F0XPR0ERR0R; goto finish2;~

while ( lMemoSize > 0 ) { nReadSize _ (int) min ( (long) ___PBUFF_SIZE, lMemoSize ) ; nReadSize - _FRead ( MHandle, ( char FAR ♦ ) pBuff, nReadSize ) ,* lMemoSize -■= nReadSize; nWriteSize _ _FWrite ( FHandle, ( char FAR ♦ ) pBuff, nReadSize ) ; if ( nReadSize !_ nWriteSize ) { nVRetVal - RC_DISKFULL; goto finishl;-

} strcpy ( filename, ( char ) _sz_tnpFile ) ; nVRetVal • RC_SUCCEED; finishl:

_FClose ( FHandle ) ; finiβh2: _FreeHand ( mh ) ; done: return ( nVRetVal ) ; } /♦ function PutMemoDatalnTmpFile ♦/

Get memo field data from a temporary file.

int GetMemoDataFromTpFile ( char filename, const Value val, const Locator loc

int nVRetVal; // Voysys return value long lFileSize;

FCHAN MHandle, FHandle; long lStartPos;

// open the temporary file

FHandle * _F0pen ( ( char far ) filename, F0_RRAD0NLY ) ;

if ( FHandle « -1 ) { nVRetVal - RC_FOXPROERROR; goto done; »

// find the size of the file lFileSize - _FSeek ( FHandle, 0L, FS_FROMEOF ) ;

// get the file channel for memo field MHandle _ ( FCHAN ) val.ev_width; if ( ( lStartPos * AllocMemo ( ( Locator FAR ) &loc, lFileSize ) ) «- -1 ) { nVRetVal _ RC_FOXPROERROR; goto finish;

}

// position the file pointer on the beginning of the section // that belongs to the current record _FSeek ( MHandle, lStartPos, FS_FRΩMBOF ) ;

// do the copy from temporary file to memo field nVRetVal - _FCopy(MHandle, lStartPos, FHandle, 0L, lFileSize) ; if ( nVRetVal .. 0 ) { nVRetVal « RC_FOXPROERROR; goto finish;

nVRetVal « RC_SUCCEED ; finish:

FClose ( FHandle ) ; done: return ( nVRetVal ) ;

} /♦ function GetMemoDataFromTmpFile ♦/

/ ************************************************

Startup the system: START This routine is called automatically when the library iβ loaded. It does the "init" and "connect" calls. _&_ We may want to leave "connect" to the user.

7 void FAR start ( ) int nVRetVal; // Voysys return value struct line_t sp; // ptr to slot in line table int nSlot; // slot in line table char szProgramName[MAX_PN + 1] ; ulong dwFileFormat; ~ char szDir[MAX_DN] ; // send a beep to the PC speaker to indicate this "start" //function has been called MessageBeep ( (UINT)-l ) ;

♦if !NO_DEBDGGING_AT_ALL

♦define DEBUG INTO_FILE 1

♦if DEBUG_INTO_FILE

/* delete, create and open debug file */ int i, j; char name[13] ; int filehdl; i a 0; j - 0;

// debug file name: vcdbug00.txt, vcdbug01.txt, ... ,\ // vcdbug98.txt, vcdbug99.txt

// 012345678901 βtrcpy ( name, "vcdbug00.txt" ); while ( TRUE ) ( if ( i > 9 ) { // check to βee there is a need to increment the

// let digit of the filename i ■ 0; // reset the 2nd digit to 0 j++; // increment 1st digit name[6] - '0' + j; } name[7] _ '0' + i;

// if exists, use another name filehdl - open ( name,

O TEXT I 0_RDONLY, // |θ_EXCL,

S~IREAD

) ; if ( filehdl l_ -1 ) {

// open success, the file exist and can be ppenned // BO we should try the next sequence number close ( filehdl ) ; if ( ( i 1- 9 ) || ( j 1- 9 ) ) {

// not reach the last file "vcdbug99.txt" in // the sequence yet

// so go back and try the next sequence number i++; // increment the 2nd digit of the flname continue;

} else {

// reach the last file "vcdbug99.txt" // so start from "vcdbug00.txt" again name[6] _ '0'; name[7] _ '0' ; i - 0; j = 0;

, «

// open fail, the file does not exist OR // file exist but we already reach the last file // "vcdbug99.txt" // BO we try to create/truncate the file and start a

// new debug file debugfile _ open { name, 0_CREAT | 0_TRUNC | 0_TEXT |

O WRONLY,// |0_EXCL, S_IWRITE ); if ( debugfile !_ -1 ) break; // success, file is created if ( errno 1- EACCES ) { // failure other than "busy" nVRetVal - RC_OSERROR; goto done; ~ " } // failure is "busy" so that means the file is being

// used right now

// go back to try the next sequence number i++; // increment the 2nd digit of the filename , ' ♦endif

Debug flag - 3; ♦endif

GetProgramName( szProgramName ) ;

// find Blot in the line table nVRetVal ■ FindSlot( GetCurrentTaβk ( ) , fcsp ) ; if ( nVRetVal >_ RC_SUCCEED ) {

Debug ( 1, sprintf ( Debug_buf, "DB_Fox/star : task has

~ already started" ) ) ; nVRetVal - RC_ALREADYINITIALIZED; goto done; }

// find a free slot in the line table for ( nSlot - 0 ; nSlot < MAX LINES ; nSlot++ ) { if (llinetnSlotl .blnUse) break; if ( nSlot » MAX_LINBS ) {

Debug ( 5, sprintf ( Debug buf, "DB_Fox/βtart: line table is full" ) ) ; nVRetVal * RC_SWLIMIT; goto done; ~

sp - tline[nSlot] ; sp->blnUβe _ TRUE; sp->hTask m GetCurrentTask0 ; sp->nLineNum _ MAX_LINES; sp-_handlerid _ ActivateHandler ( EvtHandler ) ; if ( sp->handlerTd __ 0 ) { nVRetVal - RC_OSERROR; sp->blnϋse = FALSE; goto done,- }

Bp->whandleClientDDE - _WOpenP (

10, 10, // top, left

20, 20, // bottom, right WEVENT, // gets events ?

WINDOW_SCHEME, // default color scheme

( Scheme FAR ) 0, 11 ignored

WO DOUBLEBOX // border type ) ; * if ( sp->whandleClientDDE __ 0 ) {

Debug ( 1, sprintf ( Debug_buf, "DB_Fox/start: _WOpenP failed" ) ) ; nVRetVal - RC_OSERROR; _DeActivateHandler ( sp->handlerid ) ; sp->handlerid _ 0; sp->bInUse _ FALSE; goto done;

} sp->hwndClientDDE _ WhToHwnd ( sp->whandleClientDDE ) ; if ( lsp->hwndClientDDE ) { nVRetVal - RC_OSERROR;

_DeActivateHandler ( sp->handlerid ) ; sp->handlerid * 0; _WCloβe ( sp->whandleClientDDE ) ; sp->whandleClientDDE « 0; βp->bInUβe - FALSE; goto done;

/* Call the init routine.

*/ nVRetVal « com_Initialize ( sp->hTask, 0, sp->hwndClientDDE ) ; if ( nVRetVal 1- RC_SUCCBED ) {

Debug ( 1, sprintf ( Debugjbuf, "DB_Fox/start: init routine failed" ) ) ;

_DeActivateHandler ( sp->handlerid ) ; sp->h_ndlerid * 0; _WCloβe ( sp--whandleClientDDE ) ; sp->whandleClientDDE - 0; βp->bInUβe _ FALSE; goto done;

/* Call the "connect" routine.

*/ nVRetVal _ com_Connect ( sp->hTask,

MAX_LINES, /* request any line ♦/ &(sp->nLineNum) /♦ retval -- line we got /

) ; if ( nVRetVal 1- RC_SUCCEED ) {

Debug ( 1, sprintf ( Debug_buf, "DB_Fox/start: connect

routine failed" ) ) ; _DeActivateHandler ( βp->handlerid ) ; sp->handlerid _ 0; _WCloβe ( sp->whandleClientDDE ) ; sp->whandleClientDDE _ 0; βp->bInUse - FALSE; goto done;

}

// find out the file format βzDir[0] _ (char) NULL; nVRetVal - com_Get ( βp->hTaβk, GET_FILEFORMATS, ( char far *) tβzDir, &dwFileFormat ) ; if ( nVRetVal !_ RC SUCCEED )

Debug ( 1, sprintf ( Debug_buf, "DB_Fox/start: cannot get

FILEFORMATS" ) ) ; βtrcpy ( szDir, DBF DIR_SYSPROMPTS ) ; if ( dwFileFormat _~FM VOX ) 8treat ( βzDir, FM_VOX_STR ) ; else if ( dwFileFormat & FM_WAV )

Btrcat ( βzDir, FM_WAV_STR ) ; /*

♦ Initialize the default voice directories.

♦ Ignore any failures.

/ nVRetVal ■ com_Set_Directory (βp->hTask, "SYSPROMPTS", βzDir) ; if ( nVRetVal 1- RC_SUCCEED )

Debug ( 1, sprintf ( Debug_buf, "DB_Fox/βtart: cannot set

SYSPROMPTS to DBF_DIR_SYSPROMPTS" ) ) ; nVRetVal * com Set_Directory ( βp->hTask, "APPPROMPTS",

DBF DIR_APPPROMPTS ) ; if ( nVRetVal !« RC_SUCCEED )

Debug ( 1, sprintf ( Debug buf, "DB Fox/start: cannot set

APPPROMPTS to DBF DIR APPPROMPTS" ) ) ; nVRetVal - com_Set_Directory ( βp->hTaβk,~"RECORDINGS", "." ); if ( nVRetVal 1- RC_SUCCEED ) Debug ( 1, sprintf ( Debug buf, "DB Fox/start: cannot set

RECORDINGS to ." ) ) ; nVRetVal - com Set Directory ( sp->hTaβk, "FAXFILES", "." ); if ( nVRetVal T- RC_SUCCEED )

Debug ( 1, sprintf ( Debug buf, "DB Fox/start: cannot set ~ FAXFILES to ." ) ); nVRetVal * com Set Directory ( βp->hTask, "TEMPFILBS", "." ); if ( nVRetVal T» RC_SUCCEED )

Debug ( 1, sprintf ( Debug buf, "DB Fox/start: cannot set

TEMPFILBS to ." ) ) ; nVRetVal _ cam Set Vocabulary ( βp->hTask, DEF_DIR VOCABS ) ; if ( nVRetVal T_ RC_SUCCEED )

Debug ( 1, sprintf ( Debug buf, "DB Fox/start: cannot set

Vocabulary " to DBF_DIR_VOC___S" ) ); /*

♦ Set the voice recognition vocabulary.

♦ Ignore any failure. */

// nVRetVal _ com_Sβt_Vocabulary ( sp-_hTask, "demo" ) ; nVRetVal _ RC_SUCCEED;

// send a beep to the PC speaker to indicate this "start" // function has been initialized MesβageBeep ( (UINT) -1 ) ; done:

// No return code; return back to FoxPro, return;

} / function start /

/********************* * * *** <

Shut down the system: STOP

This routine is called automatically when the library is unloaded or a QUIT is done out of FoxPro. It does the "disconnect" and "shutdown" calls. ___ We may want to leave "disconnect" to the user.

7 void FAR stop ( ) struct line_t sp; // ptr to slot in line table int nVRetVal; // Voysys return value

// find slot in the line table nVRetVal - FindSlot( GetCurrentTask ( ) , _sp ) ; if ( nVRetVal !« RC_SUCCEED ) goto done; com_Disconnect ( βp-_hTask ) ; com_Shutdown ( sp--hTask ) ; if ( sp->handlerid 0 ) {

_DeActivateHandler ( sp->handlerid ); sp-.handlerid _ 0;

} if ( sp-.whandleClientDDE !_ 0 ) { _WClose ( sp-.whandleClientDDE ) ; sp->whandleClientDDE _ 0;

sp->nLineNum _ _____L_NES; sp->blnϋβe _ FALSE; done: ♦if 1NO DEBUGGING_AT_AI__ ♦if DEBUG_INTO FILE close ( debugfile ) ; ♦endif ♦endif

// No return code; return back to FoxPro.

// no debug stmt; debug file is closed return; } / function stop */

/ * * **** ** Wait for Ring: VWAITRING retcode _ VWAITRING( [<num_rings>(I)] [,< imeout>(I)>] )

Returns: 0 _ Success

-1 _ Timeout

-11 _ Invalid number of rings (must be positive integer)

-12 _ Invalid timeout (must be non-negative integer)

-10X B standard error codes (from dVR.h)

void FAR vwaitring (

ParamBlk FAR parm

)

{ int num_rιngβ; // number of rings before answer int timeout; // timeout value in seconds int nVRetVal; // Voysys return value

Value result; // FoxPro return structure struct line_t *βp; // ptr to slot in line table

// find Blot in the line table nVRetVal « FindSlot( GetCurrentTaβk ( ) , _sp ) ; if ( nVRetVal !_ RC_SUCCEED ) goto done;

/*

♦ Extract the arguments to the function. Remember that both

arguments are optional. &&& This does not yet take

♦ into account the default settings. */ if ( parm->pCount >■ 1 ) num_rings _ ( ( int ) parm->p[0] .val.ev_long ); else ~ " ~ num rings - DBF WAITRING NUM RINGS; if ( parm->pCount >_ 2 ) timeout • ( (int) parm->p[l] .val.ev_long) ; else timeout ■ DEF_WAITRING_TIMEOUT;

/* ♦ Call the "answer" function.

*/ nVRetVal _ com Answer ( βp->hTask, nυm_rings, timeout ) ; if (nVRetVal ■_ RC TOTALTIMEODT) nVRetVal - RC_WAITRINGTIMEOUT; done:

// Send the return code back to FoxPro. _MemFill ( tresult, 0, βizeof ( result ) ) ; result.ev_type _ 'I'; result.ev_long _ nVRetVal; _RetVal ( tresult ) ; return;

} /♦ function vwaitring */

/********** * ************************ ** ****** Hang up: VHANGUP retcode _ VHANGUP0 Returns:

0 - Success

-10X B Standard error codes (from dVR.h)

void FAR vhangup ( ) {

int nVRetVal; // Voysys return value

Value result; // FoxPro return structure struct line_t sp; // ptr to slot in line table

// find slot in the line table nVRetVal - FindSlot( GetCurrentTask ( ) &sp ) ; if ( nVRetVal !_ RC_SUCCEED ) goto done;

/

* Call the hangup routine.

*/ nVRetVal _ com_Hangup ( βp->hTaβk ) ; done:

// Send the return code back to FoxPro. _M__αFill ( _reβult, 0, sizeof ( result ) ) ; result.ev_type ■ 'I' ; result.ev_long _ nVRetVal; _RetVal ( iresult ) ; return;

} /* function vhangup /

Check for hangup: VCHKHANGUP retcode - VCHKHANGUP()

Returns: 1 ■ Success, hangup detected 0 B Success, no hangup detected -10X Standard error codes (from dVR.h)

void FAR vchkhangup ( ) int nVRetVal; // Voysys return value

Value result; // FoxPro return structure struct line_t *βp; // ptr to slot in line table

// find slot in the line table nVRetVal - FindSlot( GetCurrentTask ( ) , £_sp ) ; if ( nVRetVal !« RC_SUCCEED ) goto done;

* Call the check for hangup routine.

*/ nVRetVal - com_Hangup_Detect ( sp->hTask ) ; done:

// Send the return code back to FoxPro. ______ill ( &result, 0, sizeof ( result ) ) result.ev_type 'I'; result.ev_long nVRetVal; _RetVal { S esult ) ; return;

} /♦ function vchkhangup /

/********** ******* * *************************************

Speak: VSPEAK retcode _ VSPEAK(< alue>(?) [,<value>(?)] ... [,<value>(?)] ) Takes up to 10 parameters. Parameters may be character strings (names of prompt files), numbers, or dates. The strings "DIGITS", "DOLLARS", or "DAY" may be put into the parameter list to indicate SPEAK variations. Returns:

1 B Speak interrupted by DTMF (still a success)

0 Success

-21..-30 Prompt file not found (argnum is -(errcode + 20))

-10X B standard error codes (from dVR.h)

void FAR vspeak (

ParamBlk FAR *parm ) { int nVRetVal; // Voyβyβ return value Value result; // use to check memo field and

FoxPro return structure <«<< int play_type; // Play type (DIGITS, DOLLARS, etc int interrupt_mode; // 0«no interrupt, 1-interrupt OK // char text[MAX DN + MAX_FN] ; char text[MAX~FN + 1] ; int arg_count; int playJElag; // Should this argument be played? int liβt_8tarted; // have we started a play list yet? double dvalT long lvall, lval2; struct line_t *βp; // ptr to slot in line table int nCount; boolean bMemoField B FALSE;

Locator loc; int nNumMemoField B 0; int nPlayLiβtLen ■ 1; char *pFileName;

MHANDLE mh[MAX_PLISTLBNGTH] ; char FAR *lpName;

// find slot in the line table nVRetVal » FindSlot( GetCurrentTask ( ) , &.sp ) ; if ( nVRetVal 1- RC_SUCCEED ) goto done;

/* ♦ Initialize the play type to AS_NUMBER (the default) . pl 7ay_type AS_NUMBER;

♦ Set the interruptibility (default is ON) _&& Need to check

♦ a table here */

interrupt_mode l ;

/*

Set list_started to FALSE; we have not yet initialized a play list.

Set the return code to 0 (success) . */ list_started = FALSE; /*

Process the arguments one at a time. */ for ( arg_count 0; arg_count < parm->pCount arg_count++ ) { if ( nPlayListLen > MAX_PLISTLENGTH ) { nVRetVal - RC_PLISTLENGTH; goto done; } pFileName « text;

/*

Set the "play flag" for this argument to TRUE.

*/ play_flag _ TRUE;

/*

For each argument, check the type. If it is a string,

we need to extract it, which involves conversion to a far pointer followed by a copy to our 'text'

buffer. */ if ( pa_m->p [arg_count] .val .ev_type ' C ) { nCount m min (MAX_FN, parm- >p~[arg_count] .val . ev_length) ; _fβtrncpy ( ( char far ) textT

_HandToPtr ( parm- >p [arg_count] . val . ev_handle ) , nCount ) ; text [nCount] m ( char ) NULL; trim_trailing ( text ) ;

/*

Now capitalize the string.

/ βtrupr ( text ) ;

/*

Check if the string is really one of our modifiers - (DOLLARS, DIGITS, DAY) . If it is, set the

♦ "play_type" to the correct value, and turn off the "play flag".

*/ if ( lstrαπp ( text, "DIGITS" ) ) { play_flag « FALSE; play type ■ AS DIGITS; } else if ( lβtrcmp ( text, "DOLLARS" ) ) { play_flag = FALSE; play_type B AS_DOLLARS; else if ( Istrαnp ( text, "DAY" ) ) { play_flag ■= FALSE; play_type B AS_DAY;

else {

// try to find out if this is a memo field nVRetVal « IsMemoField ( text, _bMemoField, ireβult, _loc ) ; if ( nVRetVal !- RC_SUCCEED ) goto done; ~ if ( bMemoField ) { // allocate memory for filename mh[nNumMemoField] > _AllocHand ( MAX DN +

MAX FN ) ; if ( mh[nNumMemoField] —~0 ) { nVRetVal - RC_RAMFULL; goto done;

lpName - _HandToPtr ( mh[nNumMemoField] ) ; nVRetVal * PutMemoDatalnTmpFile ( sp->hTask,

( char ) lpName, result ) ; if ( nVRetVal 1- RC_SUCCEED ) goto done; ~ pFileName _ ( char ) _fstrrchr ( lpName, 'W ); // get the filename only pFileName++; // skip the ' ' char

_NumMe_toField++;

else if ( parm->p[arg count] .val.ev type mm 'I' ) {

/* * If the argument is of some other type, convert it

♦ to text. _& Always using two decimal places

for type N. __&

*/ sprintf (text, "%ld" , parm- > p [arg_count] .val .ev long) ; } else if ( parm- >p [arg_count] ,val .ev_type a« Η' ) {

/*

♦ The Fox LCK has a bug that freezes if "sprintf" is

♦ used with a " * __" . This code is a work-around. / dval paπn- >p [arg_count] . val . ev_real ; lvall * ( long ) floor ( dval ) ; lval2 B ( long ) (100. ( dval - floor ( dval ) ) + .5); sprintf ( text, "%ld.%ld", lvall, lval2 );

/* * If the play flag is on, play this argument. If it is

♦ the first one (list_βtarted * FALSE) , then we need

♦ to start a play list.

/ if ( play flag ) { if ( llist_started ) { nVRetVal ■ com_Play (

~ sp--hTask,

START_PLAY_LIST, pFileName, play_type, interrupt mode ); if ( nVRetVal 1- RC_SUCCEED ) goto done; list_started = TRUE; else { nVRetVal B com_Play ( βp->hTask, ADD_PLAY_LIST, pFileName, play_type, interrupt mode ); if ( nVRetVal 1= RC_SUCCEED ) goto done; ~ } nPlayListLen++,-

} / for loop - go to next argument /

/*

Now play the whole list, as long as the return code is

βtill zero. */ nVRetVal - com_Play ( sp->hTask,

PLAY_PLAY_LIST,

"NONE", play_type, interrupt_mode

); done: if ( nNumMemoField > 0 ) { // delete the temporary file(s) for ( nCount « 0; nCount < nNumMemoField; nCount++ ) { if ( remove ( ( char * ) HandToPtr ( mh[nCount] ) ) <

0 ) { // may not need to return error code Debug ( 1, sprintf ( Debugjbuf,

"DB_Fox/vspβak: cannot remove temporary file '%s'", ( char ) _HandToPtr ( mh[nCount] ) ) ) ; // goto done; just continue FreeHand( mh[nCount] );

) > "

// Send the return code back to FoxPro. _MemFill ( ..result, 0, sizeof ( result ) ); result.ev_type * 'I' ; result.ev_long « nVRetVal; _RetVal ( ..result ) ; return;

} / function vspeak /

/ ******************** * * ** ****

Get touch tones (DTMF) : VGETTONES retcode ■ VGETTONES( ..variable- (C) [,<nυmber_of_digits>(I)]

[,<key_to_teπninate on>(C)] ~ [,<interdigit_t meout>(I)] )

The first (mandatory) parameter is a character variable to put the received keys (0-9, ♦, *) into. The next three parameters (optional) set the termination conditions. This can be one of more of: a set number of digits (0-any number of digits) - a key to terminate on (like ♦) which is not to be included in the output (" " ■ no key) an interdigit timeout (0_no interdigit timeout)

The absolute defaults are 1 keystroke, no terminating key, 10 second interdigit timeout. These defaults can be overridded by the use of another function. THOSE defaults, in turn, are overridden by any use of the optional arguments to this function.

Returns: 2 Terminated on interdigit timeout (success)

1 a Terminated on specified key (success)

0 Terminated on number of digits (success)

-1 B Terminated on timeout (failure)

-2 Terminated on hangup (failure) -11 B Bad termination digits (must be 0-MAX_DIGITS)

-12 Bad termination key (must be 0-9, ♦, or , or blank)

-13 Bad interdigit timeout (must be non-negative)

-10X standard error codes (from dVR.h)

void FAR vgettones (

ParamBlk FAR *parm ) { // number of digits to terminate on

// key to terminate on

// interdigit timeout to terminate on in seconds // number of retries 1] ; // digits buffer // value struct for digits buf // Voysys return value // FoxPro return structure // ptr to slot in line table

// find slot in the line table nVRetVal - FindSlot( GetCurrentTask ( ) , _sp ) ; if ( nVRetVal !■ RC_SUCCBED ) goto done;

♦ Set the default values of the parameters here

*/ term_digitβ « DEF_GETTONES NUMJDIGITS; term_key » DEF_GETTONES_KEΫ_TO_TE____NATE; term_id_timeout * DEF_GETTONES_INTBRDIGIT_TIMEOUT;

retries « 1;

/*

Check to see if any of the local overrides have been used. If ANY of the three conditions has been used, this

overrides ALL of the defaults. */ if ( par_ι-_pCount > 1 ) { term digits 0; termjey * ' ' ; term id timeout - 0;

} " "

/* Now get the values of the parameters if they exist. Start

with the number of digits. */ if ( parm->pCount >_ 2 ) { term digits * ( ( int ) ( parm->p[l] .val.ev long ) ); }

/*

Now get the termination key (if any) . This involves

resolving the handle to a string and copying the value * to the 'text' buffer.

*/ if ( parm->pCount >_ 3 ) {

_fstrncpy ( ( char far ) text,

_HandToPtr ( parm->p[2] .val.ev_handle ), min ( MAX_DIGITS, parm-.p[2] .val.ev_length ) ); term key » text[0] ; }

/* Get the inter-digit timeout if any.

*/ if ( parm-.pCount >■ 4 ) { term id timeout ■ (int) parm->p [3] .val .ev long;

/*

Call get tones with our arguments . */ nVRetVal = com_Get_Toneβ ( βp- . hTask,

FALSE, term_digits, term_key, term~id_timeout, retries, text ); if (nVRetVal >. RC_DATALENGTH) nVRetVal - RC SUCCEED; else if (nVRetVal-— RC_ENDFLAG) nVRetVal - RC_TERMONSPECKEY;

/*

♦ if the operation was successful, put the string in "text" ♦ into the variable that was passed in.

*/ if ( nVRetVal >« 0 ) {

/*

♦ Allocate space for the "Value" structure to be put into

variable, and for the text (handle) . &.&_ Check for

♦ bad retcodes! */ val.ev_type β 'C; val.ev_handle » _AllocHand ( βtrlen ( text ) + 1 ) ;

/* * Put the text value into the handle, and set the length

♦ in the value buffer.

*/ _StrCpy ( _HandToPtr ( val.ev_handle ), ( char far ♦ )

~ ~ text ) ; val.ev_length _StrLen ( ( char far * ) text );

/*

♦ Now store the value into the variable.

*/ Store ( ( Locator FAR * ) _ ( parm->p[0] .loc ), frval );

} " done:

// Send the return code back to FoxPro. _MemFill ( fcresult, 0, sizeof ( result ) ) ; result.ev_type B 'I' ; result.ev_long ■ nVRetVal; _RetVal ( fcreβult ) ; return;

} / function vgettoneβ /

** * ******** ** ** * ***** **** ** *****« * ******* * * * * **** * ** * * * * ** ** * *

Get touch tones (DTMF) macro: vgettoneβm retcode « vgettonesmt <voicejprαmpt> (C) , <trieβ> (I), β-variable>(C) [,<number_of_digits>(I)] [,<key_to terminate_on>(C)] t,<interdTgit timeout>(I)] [,<valid_liet (O) vgettoneβm is a "macro" combination of VSpeak and VGetTones. It allows the programmer to specify a prompt, a number of retries, and a validation list in addition to the standard parameters of VGetTones.

The first parameter, which is mandatory, specifies a voice prompt to be spoken to the caller. The second parameter (also mandatory) specifies the total number of "tries" (including the first try) that are to be done.

The third (mandatory) parameter is a character variable to put the received keys (0-9, ♦, *) into. The next three parameters (optional) set the termination conditions. This can be one of more of:

- a set number of digits (0«any number of digits) - a key to terminate on (like ♦) which is not to be included in the output (" " * no key)

- an interdigit timeout (0=no interdigit timeout)

The absolute defaults are 1 keystroke, no terminating key, 10 second interdigit timeout. These defaults can be overridded by the use of the _____ function. THOSE defaults, in turn, are overriden by any use of the optional arguments to this function.

The final parameter (optional) is a validation list, This list, in character form, contains all the valid choices, separated by commas.

Returns:

2 = Terminated on interdigit timeout (success)

1 B Terminated on specified key (success)

0 B Terminated on number of digits (success)

-2 B Terminated on hangup (failure)

-3 B Terminated; ran out of tries (failure)

-11 B Bad termination digits (must be 0-MAX_DIGITS)

•12 B Bad termination key (must be 0-9, ♦, or , or blank)

-13 B Bad interdigit timeout (must be non-negative)

-10X B Standard error codes (from dVR.h)

7 void FAR vgettoneβm (

ParamBlk FAR parm ) {

This procedure is not complete.

int term_digits; // number of digits to terminate on char term_key; // key to terminate on int term_id_timeout; // interdigit timeout to terminate on in seconds int retries; // number of retries char text[MAX_DIGITS + 1] ; // digits buffer

Value val; // value struct for digits buf int nVRetVal; // Voysys return value

Value result; // FoxPro return structure struct line_t *sp; // ptr to slot in line table

// find slot in the line table nVRetVal ■ FindSlot( GetCurrentTask ( ) , &βp ) ; if ( nVRetVal !« RC_SUCCEBD ) goto done;

♦ Set the default values of the parameters here

*/ term digits « DEF_GETTONES_NUM_DIGITS; termjcey - DBF GBTTONES_KEY_T0_TERMINATB; term_id_timeout _ DEF_GETTONES_INTERDIGIT_TIMEOUT; retrieβ~B l;

/<

Check to see if any of the local overrides have been used.

♦ If ANY of the three conditions haβ been used, this

♦ overrides ALL of the defaults.

*/ if ( parm->pCount > l ) { term_digitβ « 0;

term_key ■ ' ' ; ter Tid timeout ■ 0;

*

♦ Now get the values of the parameters if they exist. Start

♦ with the number of digits.

*/ if ( parm->pCount >- 2 ) { term digits * ( ( int ) ( parm->p[l] .val.ev long ) ),-

}

/*

♦ Now get the termination key (if any) . This involves ♦ resolving the handle to a string and copying the value

♦ to the 'text' buffer. */ if { parm- >pCount >B 3 ) {

_fβtrncpy ( ( char far ) text, ~ _HandToPtr ( parm->p [2] .val .ev_handle ) , min ( MAX_DIGITS, parm- >p [2] .val .ev_length ) ) ; term_key ■ text [0] ;

/*

Get the inter -digit timeout if any. */ if ( parm- . pCount >_ 4 ) { term id timeout _ (int) parm- >p [31 .val .ev long; )

/*

Call get tones with our arguments.

*/ nVRetVal m com_Get_Tones ( βp->hTask,

FALSE, term_digits, term_key, term id_timeout, retrTββT text

); if (nVRetVal « RC_DATALBNGTH) nVRetVal - RC SUCCEED; else if (nVRetVal-« RC_ENDFLAG) nVRetVal « RC TERMONSPECKBY; /

♦ If the operation waβ successful, put the string in "text"

♦ into the variable that was pasβed in.

*/ if ( nVRetVal >B 0 ) {

/*

♦ Allocate space for the "Value" structure to be put into

♦ variable,

♦ and for the text (handle) . _&& Check for bad retcodes! / val.ev_type « 'C; val.ev~handle AllocHand ( strlen ( text ) + 1 );

/*

Put the text value into the handle, and set the length

in the value buffer. */ _StrCpy ( _HandToPtr ( val.ev_handle ) , ( char far ♦ ) text ) ; val.ey_length ■ _StrLen ( ( char far ♦ ) text ) ;

/* * Now store the value into the variable.

*/ _Store ( ( Locator FAR ) & ( parm->p[0] .loc ), frval );

done:

// Send the return code back to FoxPro.

_______ill ( tresult, 0, sizeof ( result ) ) ; reβult.ev_type 'I' ; result . ev_long β nVRetVal ; _RetVal ( tresult ) ; return;

} / function vgettonesm /

/***************************************< Get words (voice recognition) : VGETWORDS retcode > VGETWORDS( β<variable>(C)

[, -8ub-vocabulary_name>(C)] [,<number_of_words>(I)] [, -word_to_terminate_αn_ (C)] [,<interword_timeout- (I)] )

The first (mandatory) parameter is a character variable to put the received words (0-9, yes, no) into. The second (optional) parameter specifies the sub-vocabulary name. Valid choices are: all (default - the list of 0-9, oh, yes, no) numbers (0-9 and oh) yes/no (yes and no only)

The next three parameters (optional) set the termination conditions. This can be one of more of:

- a set number of words (θB_uαy number of words)

- a word to terminate on which is not to be included in the output (" " B no word) - an interword timeout (0-no interword timeout)

The absolute defaults are 1 word, no terminating word, no interword timeout. These defaults can be overridded by the use of another function. THOSE defaults, in turn, are overriden by any use of the optional arguments to this function.

Returns:

2 Terminated on interword timeout (success)

1 Terminated on specified word (success) 0 _ Terminated on number of words (success)

-1 Terminated on timeout (failure)

-2 Terminated on hangup (failure)

■11 B Bad termination number of words (must be

0-MAX_DIGITS) ■13 Bad interword timeout (must be non-negative) ■10X Standard error codes (from dVR.h)

void FAR vgetwords (

ParamBlk FAR par ) int num_words; / number of words to terminate on / char term_word; / word to terminate on */ ulong term iw_timeout; // interword timeout to terminate on int retries " /* number of retries */ char subvocab[10] ; / buffer for sub-vocab name &&& NEED

CONSTANT / Char text[MAX_DIGITS + 1]; / digits buffer / Value val; ~ /* value struct for digits buf */ int nVRetVal; // Voysys return value

Value result; // FoxPro return structure struct line_t sp; // ptr to slot in line table int nCount; // find slot in the line table nVRetVal ■ FindSlot( GetCurrentTask ( ) , _sp ) ; if ( nVRetVal 1« RC_SUCCBED ) goto done; /*

♦ Set the default valueB of the parameters _&_ Need to

consult a table here */ num words ■ DBF GETWORDS_NUM_WORDS; term_word * DBF~GBTWORDS_WORD_TO_TERMINATB; term iw_timeout " ~B DBF_GBTWORDS_INTBRWORD_TIMEOXJT; retries • 1; ~ " ~

/* ♦ Get the sub-vocab name if one has been used. Otherwise,

♦ use "ALL"

*/ if ( parm- > pCount >» 2 ) {

_fstrncpy ( ( char far ) βubvocab, _HandToPtr ( parm- >p [l] .val .ev_handle ) , parm- >p [l] .val . ev_length ) ; - subvocab[parm->p [l] .val .ev_length] ■ ( char ) NULL; trim_trailing ( subvocab ) ; βtrupr ( subvocab ) ; } else βtrcpy ( subvocab, "ALL" ) ;

/* ♦ Check to see if any of the local overrides have been used.

♦ If ANY of the three conditions has been used, this

♦ overrides ALL of the defaults.

*/ if ( parm->pCount > 2 ) { num_words » 0; term_word * ' ' ; term iw timeout B 10; /* ___ KLUDGE */

}

/*

Now get the values of the parameters if they exist. Start with the number of words. Also check the values of the

parameters as we go. */ if ( parm->pCount >= 3 ) { num words B ( ( int ) ( parm->p[2] .val.ev_long ) ); if T ( num_words < 0 ) | | ( num_words > MAX_DIGITS ) ) { nVRetVal B -II ; goto done;

• '

/*

Now get the termination word (if any) . This involves

resolving the handle to a string and copying the value

to the 'text' buffer. */ if ( parm->pCount > * 4 ) { nCount min ( MAX_DIGITS, parm-.p[3] .val.ev_length ); _fβtrncpy ( ( char far ) text,

_HandToPtr ( parm->p[3] .val.ev_handle ), nCount ) ; text[nCount] ■ ( char ) NULL; trim_trailing ( text ) ; term~word text[0] ; if ( lβtrchr ( " 0123456789oynOYN", tβrm_word ) ) {

Debug ( 1, sprintf ( Debugjbuf, "DB_Fox/vgetwords: bad termination word" ) ) ; nVRetVal - -12; goto done; } )

/*

Get the inter- word timeout if any. ♦/ if ( parm-. pCount >« 5 ) term_iw_timeout ■ parm->p[4] .val.ev_long;

/* Convert the timeouts to milliseconds. */ term_iw_timeout * * 1000;

/* * Call get words with our arguments. __& NOTE:

termination word not used! */ nVRetVal ■ com_Get_Words ( sp->hTask, FALSE, subvocab, num_words, term iw_timeout, retrTes, text

) ;

/*

♦ If the operation was successfu , put the string in "text"

♦ into the variable that was passed in.

*/ if ( nVRetVal >« 0 ) {

/*

Allocate space for the text (handle) . ttt Check for bad

retcode1 / val.ev_type ■ 'C ; val.ev~handle - _AllocHand ( βtrlen ( text ) + 1 );

/* ♦ Put the text value into the handle, and set the length

♦ in the value buffer.

*/

StrCpy ( _HandToPtr ( val.ev handle ), ( char far ) text ) ; val.ev_length a _StrLen ( ( char far ♦ ) text );

/*

Now store the value into the variable.

*/ Store ( ( Locator FAR ) __ ( parm->p[0] .loc ), tval );

} " done:

// Send the return code back to FoxPro. _MemFill ( treβult, 0, sizeof ( result ) ); result.ev_type a 'I'; result.ev long a nVRetVal,- _RetVal ( " tresult ) ; return;

} / function vgetwords /

/______________.

Record: VRECORD retcode a VRECORD( <string_or_variable>(C/R)

[,<max_recording_length>(I)] )

Play a beep and record a message from the caller. The first parameter must be either a character string containing the file name to be recorded to, or a character variable pasBed by reference into which a unique file name will be placed.

The timeout and termination keypress will be gotten from the defaults table.

Returns: 0 a Successful record

-1 a Failure - timeout

-11 a invalid input (not a char or char variable)

-12 a invalid file name

-13 a invalid recording length; must be a positive integer -10X a standard error codes (from dVR.h)

*************************** ********************************/

void FAR vrecord (

ParamBlk FAR parm ) char filename[MAX_DN + MAX_FN] ; ulong timeout; // silence timeout value for record char teπn_key; // termination key for record Value val; // value struct for filename int nVRetVal; // Voysys return value Value result; // FoxPro return structure t used by

MemoField operation too <<<<< int max_recording_len; // maximum length of the recording in seconds struct line_t sp; // ptr to slot in line table int nCount; boolean bMemoField a FALSE;

Locator loc; char pFileName; // find slot in the line table nVRetVal a FindSlot( GetCurrentTask ( ) , tβp ) ; if ( nVRetVal 1- RC_SUCCEBD ) goto done; / Check that the first parameter is a string, or a variable

passed by reference. */ if ( parm->p[0] .val.ev_type la Ό tt parm->p[0] .loc.l_type la 'R' ) { nVRetVal - RC_RECORDINVALIDINPUT;

Debug ( 1, sprintf ( Debug_buf, "DB_Fox/ record: invalid input (not a character string or character~variable passed by reference) " ) ) ; goto done;

}

/*

Set the defaults for the timeout and termination keys ttt Need to check a table here.

*/ timeout β DEF_RBCORD_TIMBOUT; term_key a DEF_RECORD_TERM_KEY; max_recording_len ■ DBF_RECORD____._LENGTH; pFileName a filename,-

/*

Now get the file name into the proper buffer. If there is ♦ no filename (call by reference) , set a zero-length

string) . */ if { parm->p[0] .val.ev_type aa 'C ) { nCount a min( MAX_FN, parm->p[0] .val.ev_length ); _fstrncpy ( ( char far ) filename, -

_HandToPtr ( parm->p[0] .val.evjhandle ), nCount ) ; - filename[nCount] a ( char ) NULL; trim_trailing ( filename ) ;

// try to find out if this is a memo field nVRetVal a IsMemoField ( filename, tbMemoField, tresult,

__loc ) * if ( nVRetVal 1- RC_SUCCEED ) goto done; if ( bMemoField ) { nVRetVal a GetTmpFilename ( sp->hTask, GET_RECORDINGS,

~ filename ) ; if ( nVRetVal !« RC_SUCCEED ) goto done; ~

// put data into temporary file filename

// get the filename only pFileName a βtrrchr ( filename, '' ),- pFileName++; // skip the 'V char

else {

♦filename a ( char ) NULL; >

/*

Get the "max recording_length" parameter if there is one. */ if ( parm->pCount > 1 ) { max recording len a ( ( int ) parm->p[l] .val.ev long );

)

/*

♦ Call the 'record' function. /

/♦ ttt timeout is not used 11! / nVRetVal s com_Record ( βp->hTaβk, pFileName,

FALSE, termjkey, timeout 1000, max rβcording_len ) ; " if ( nVRetVal >- 0 ) {

. // successful record to filename /

♦ If the argument was a variable passed by reference, we

♦ need to put "filename" back into it .

*/ if ( parm-.p tO] . loci type aa 'R' ) {

/*

♦ Allocate space for the filename (handle) . ttt Check

♦ for bad retcode!

*/ val.ev_type a 'C ; val.ev_handle a _AllocHand ( strlen ( filename ) + 1) ;

/*

Put the filename into the handle, and set the * length in the value buffer.

*/ _StrCpy ( _HandToPtr ( val.ev_handle ), ( char far )

filename ) ; val.ev_length a _strLen ( ( char far ♦ ) filename );

/* Now store the value into the variable.

*/ _Store (( Locator FAR ) i ( parm->p[0] .loc ), tval) ; else if ( bMemoField ) { nVRetVal a GetMemoDataFromTmpFile ( filename, result, loc ) ; if ( nVRetVal 1- RC_SUCCEED ) goto finish; ~ Debug { 2, sprintf ( Debug_buf, "DB_Fox/vrecord: fill memo field with data from file ~%s'", filename ) );

// always return success, no matter how the recording is done nVRetVal « RC SUCCEED; } finish: if ( bMemoField ) {

// delete the temporary file if ( remove ( filename ) < 0 ) {

// may not need to return error code

Debug ( 1, sprintf ( Debug_buf, "DB_Fox/vrecord: cannot remove temporary file '%β'", filename ) ); goto done; } ,

done:

// Send the return code back to FoxPro. _MemFill ( treβult, 0, sizeof ( result ) ) ; result.ev_type * 'I'; result.ev_long a nVRetVal; _RetVal ( tresult ) ; return;

} / function vrecord */

Dial an outside number: VDIAL retcode a VDIAL( <phone_number> (C) [,<num_rings> (I)] [,_<greeting_length . (R)] )

Go offhook, wait for dial tone, and dial the number passed (as a character string) . Only the characters "0123456789*#ABCD" will be dialed; special chars allowed are:

T use tone dialing (default) P use pulse dialing W wait for dial tone , pause 2 seconds ! do a switch hook

The "rings" parameter indicates the number of rings to wait until deciding there is no answer, "rings" is optional, defaults to 6. If a "predictive dialing" effect is desired, the third parameter may be used. It must contain a numeric variable passed by reference. Upon exit, this variable will contain the length (in seconds) of the user's greeting.

-11 a Bad "rings" parameter; must be greater than 0 -10X a Standard error codes (from dVR.h)

void FAR vdial (

ParamBlk FAR parm

char text[MAX DIGITS + 1] / text from Foxpro for digits ♦/ char digits [MAX_DIGITS + 1] / digitB buffer / int rings ,- /* number of rings to wait / boolean do_predictive ; /* do predictive dialing? / ulong greeting_lβngt ; /* if BO, length of greeting in millisecs /

/* value Btruct for greeting len / // Voysys return value /* FoxPro return structure /

// ptr to slot in line table

// find slot in the line table nVRetVal a FindSlot( GetCurrentTask ( ) , tsp ) ; if ( nVRetVal 1« RC_SUCCEED ) goto done;

/*

Set the default values of the parameters ttt Need to

consult a table here / rings « DBF_DIAL_NDM_RINGS; do_predictive a FALSE; /

♦ Get the dial string (first parameter) into "text", then

♦ convert it to upper-case.

*/ nCount a min ( MAX DIGITS - 1, parm->p[0] .val.ev_length ); I! we always add one character as the 1st one

_fβtrncpy ( ( char far ) text, _HandToPtr ( parm->p[0] .val.ev_handle ), nCount ) ; text[nCount] a ( char ) NULL; trim_trailing ( text ) ; strupr ( text ) ;

/*

Start the digits string with a "W" (wait for ring) . Then

move only those digits that are recognized into the

digits string. */ digits[0] = 'W'; count a 1; chrptr a text; while ( chrptr a strpbrk ( chrptr, "0123456789^ABCDTPW, !" ) ) digits [count++] a chrptr++; digits[count] a ( char ) NULL;

/*

Get the "rings" parameter if there is one. / if ( parm->pCount > 1 ) { rings a { ( int ) parm- >p [l] .val .ev_long ) ;

/*

Check if there is a "predictive" parameter.

*/ if ( paπn- >pCount > 2 ) de- predict ive a TRUE ;

/*

Call the compute-layer routine. */ nVRetVal = cαm_Callout ( sp->hTask, digits, rings, do_predictive, tgreeting_length ) ;

/*

If the operation was successful, and predictive dialing was

on, we want to put the result of the operation back into the variable passed in by reference.

*/ if ( do_predictive tt ( nVRetVal >a o ) ) { val.ev_type a ' !' ; /*

Put the greeting length into the structure (dividing by

1000, rounded up, to convert the milliseconds to

♦ seconds) .

*/ val.ev_long * { g_eeting_length + 500 ) / 1000;

/*

Now store the value into the variable . */ _Store ( ( Locator FAR ) t ( parm- >p [2] . loc ) , frval ) ;

done:

// Send the return code back to FoxPro. _MemFill ( tresult, 0, sizeof ( result ) ) ; result.ev_type a 'I'; result.ev_long = nVRetVal; _RetVal ( tresult ) ; return;

} / function vdial /

/*************************************, Set Debug Level: VDBBUG retcode a VDBBUG(<level>(I) )

Debug levels:

0 a No debug printouts

1 a Standard debug printouts 2 a Bxtended debug printouts

Returns:

0 a Success

/ void FAR vdebug (

ParamBlk FAR parm ) { int level; / debug level / int nVRetVal; // Voysys return value

Value result; / FoxPro return structure */ struct line_t sp; // ptr to slot in line table

// find slot in the line table nVRetVal a FindSlot( GetCurrentTask ( ) , tsp ) ; if ( nVRetVal 1- RC_SUCCBED ) goto done; ~

♦if 1NO DEBUGGING AT ALL /* " " " Extract the -level> argument, ttt Should we validate this param?

*/ level a ( ( int ) parm->p[0] .val.βv_long ) ;

/* Set the debug flag to this level. */ Debug_flag a level; ♦else nVRetVal ■ RC JNSUPCOMMAND; ♦endif done:

// Send the return code back to FoxPro. _MemFill ( tresult, 0, sizeof ( result ) ) ; result.ev_type • 'I'; result.ev-long a nVRetVal; _RetVal ( " tresult ) ; return; } / function vdebug /

****************** ********* ********* Set the directories for voice files: VSETDIR retcode a VSETDIR(<voice_file_type>(C) , -directory- (C))

Valid voice file types: SysPrompts (old form: sys_prcmpts) system prompts AppPrompts (old form: app_prompts) application prompts Recordings user recordings

Returns: 0 a Success -71 a failure: invalid voice file type

void FAR vsetdir (

ParamBlk FAR parm

) { int nVRetVal; // Voysys return value

Value result; // FoxPro return structure char dir_type[MAX_DN + 1] ; char text[MAX_DN + 1] ; struct line_t βp; // ptr to slot in line table int nCount; char szLibraryName[___X_LN + 1] ;

// find slot in the line table nVRetVal a FindSlot( GetCurrentTask ( ) , tsp ) ; if ( nVRetVal !« RC_SUCCEED ) goto done;

// Note: cannot call this in start as the library is not // finished loading BO the library name is not available yet GetLibraryName( szLibraryName ) ;

/*

Get the text of the first argument . */ nCount s min ( MAX_DN, parm ->p [0] .val . ev_length ) ; _fβtrnqpy ( { char~far ) dir_type, ~

_HandToPtr ( parm- >p~[0] .val . ev_handle ) , nCount ) ; " dir_type [nCount] » ( char ) NULL; trim_trailing ( dir_type ) ;

/*

Get the text of the second argument, and capitalize it. /

_fstrncpy ( ( char far ) text,

_HandToPtr ( parm- >p [l] .val . ev_handle ) , parm->p [l] .val .ev_length ) ; ~ text [parm- >p [l] .val . ev_length] * ( char ) NULL; trim_trailing ( text ) ;

/*

♦ Call com_Set_Directory to set the directory.

*/ nVRetVal a com_Set_Directory ( sp->hTask, dir_type, text ) ; done:

// Send the return code back to FoxPro. _MemFill ( tresult, 0, sizeof ( result ) ) ; result.ev_type a 'I'; result.ev~long a nVRetVal; RetVal ( tresult ) ;

return; } /♦ function vβetdir /

****************************************** *******

Set the speak mode for dates: VSetDateSpeak retcode a VSetDateSpeak ("mmddyy" | "mmdd" | "ddmmyy" | "ddmm" )

Returns: 0 a Success

-72 a failure: invalid date speak type

void FAR vsetdateβpeak ( ParamBlk FAR parm ) int nVRetVal; // Voysys return value Value result; // FoxPro return structure char text[MAX_DN + 1] ; struct line_t sp; ~ // ptr to slot in line table int nCount; // find slot in the line table nVRetVal a FindSlot( GetCurrentTask ( ) , tsp ) ; if ( nVRetVal 1« RC_SUCCEED ) goto done; ~ / Get the text of the first argument. */ nCount a min( MAX_DN, parm->p[0] .val.ev_length ); _fβtrncpy ( ( char far ) text, _HandToPtr ( parm->p[0] .val.ev_handlβ ), nCount ) ; text[nCount] a ( char ) NULL; trim_trailing ( text ) ; /*

♦ Set the global speak type

*/ nVRetVal a com_Set_Date_Speak ( sp->hTask, text ) ; done:

// Send the return code back to FoxPro.

_MemFill ( tresult, 0, sizeof ( result ) ) ; result.ev_type a 'I'; result.ev~long a nVRetVal; _RetVal ( " tresult ) ; return;

} /♦ function vsetdatespeak /

/***************************************************** *

_

Set default timeouts: VSetTimeout retcode a VSetTimeout("VGetTones" | "VGetWords, <timeout> )

Sets the default first-key/first-word timeouts for GetTones and Get ords Returns:

0 a Success

-76 a failure: invalid keyword (first argument)

-77 a failure: invalid timeout (must be positive integer)

void FAR vβettimeout ( ParamBlk FAR parm

int nVRetVal; // Voysys return value Value result; // FoxPro return structure int timeout; char text[MAX_DN + 1] ; struct line_t •sp; // ptr to slot in line table int nCount;

// find slot in the line table nVRetVal a FindSlot( GetCurrentTask ( ) , tsp ) ; if ( nVRetVal 1- RC_SUCCEED ) goto done;

/*

Get the text of the first argument . / nCount a min t MAX_DN, parm- >p [0] .val . ev_length ) ; _fβtrncpy ( ( char far ) text,

_HandToPtr ( parm- >p [0] .val .ev_handle ) , nCount ) ; text [nCount] a ( char ) NULL; trim_trailing ( text ) ;

/*

Get the timeout value */ timeout a (int) parm->p[1] .val.ev_long; nVRetVal a cαm_Set_Timeout ( sp-.hTaβk, text, timeout ) ,- done:

// Send the return code back to FoxPro.

_MemFill ( tresult, 0, sizeof ( result ) ) ; result.ev_type a 'I' ,- result.ev_long a nVRetVal; _RetVal ( tresult ) ; re urn;

) / function vsettimeout /

♦if DO FAX

/ * ************

*

Setup Fax Cover Page : VFaxCover retcode » VFaxCover{ C, <string_or_variable>(C/R) )

Specify the cover page being used for faxes. Returns:

0 a Success

-11 a invalid input

-10X a Standard error codes (from dVR.h)

void FAR vfaxcover (

ParamBlk FAR arm

i];

+ 1] ; // Buffer for file name // Voyβyβ return value // FoxPro return structure // fax cover page command

// temporary counter // command parameter pointer // ptr to slot in line table // find slot in the line table nVRetVal a FindSlot( GetCurrentTask ( ) , tsp ) ; if ( nVRetVal !« RC_SUCCBED ) goto done; // Verify if the first parameter iβ of proper type if ( parm-.ptO] .val.ev type la 'C ) { nVRetVal - -11;

Debug ( 1, sprintf ( Debug_buf, "DB_Fox/vfaxcover: invalid input (not a character string)" ) ) ; goto done;

// Get the text of the first argument and make the string

// uppercase. nCount a min( MAX_FAXCN, parm->p[0] .val.ev_length );

_fstrncpy ( (char~far ) cText, HandToPtr( parm-.pFo] .val.ev_handle ), nCount ); cText[nCount] a ( char ) NULL; trim_trailing ( cText ) ; strupr ( cText ) ;

// Pa se the fax cover page setup command, if ( lstrαnp ( cText, "NEW" ) ) nFaxCoverCmdType a FAX COVER NEW; else if ( lβtrcmp ( cText, " "NOTEXT" ) ) nFaxCoverCmdType a FAX COVER_NOTEXT; else if ( Iβtrαnp ( cText, " "LOGOTOP" ) ) nFaxCoverCmdType a FAX COVER LOGOTOP; else if ( lstrcmp ( cText, " "LOGOBOTTOM" ) ) nFaxCoverCmdType a FAX COVER LOGOBOTTOM; else if ( lstrcmp ( cText, " "RBCNAME" ) ) nFaxCoverCmdType a FAX_COVBR_RECNAME;

- Ill - else if ( lstrcmp ( cText, "RECCONAME" ) ) nFaxCoverCmdType - FAX_COVER RECCONAME; else if ( lstrcmp ( cText, "SENDERNAME" ) ) nFaxCoverCmdType - FAX_COVER SENDERNAME; else if ( lstrcmp ( cText, "SENDERPHONE" ) ) nFaxCoverCmdType ■ FAX_COVER SENDERPHONE; else if ( lstrcmp ( cText, "SENDERCONAME" ) ) nFaxCoverCmdType - FAX_C0VER_SENDERCONAME; else if ( lstrcmp ( cText, "COVERTEXT" ) ) nFaxCoverCmdType - FAX_COVER COVERTEXT; else if { lstrcmp ( cText, "COVE " RTEXTFILE" ) ) nFaxCoverCmdType « FAX_COVER_COVERTEXTFILE;

// Get 2nd parameter if there need to be one switch ( nFaxCoverCmdType ) { case FAX_COVER NEW: case FAX_COVER~NOTEXT: IpParm a o, " break; case FAX_COVER_LOGOTOP: case FAX COVER_LOGOBOTTOM: case FAX~COVER COVERTEXTFILE: case FAX_C0VER " RECNAME: case FAX COVER ' RECCONAME: case FAX~COVER~SENDERNAME: case FAX COVER SENDERPHONE: case FAX~C0VBR " SENDERC0NAME: case FAX~COVER_COVBRTEXT: if ( " parm->p[l] .val.ev_type ■■ 'C ) { nCount a min(MAX_FAXPARM,parm->p[l] .val.ev_length) ;

_fβtrncpy ( ( char far ) cParnt, _HandToPtr ( parm->p[l] .val.ev_handle ), nCount ); cParm[nCount] a ( char ) NULL; ~ trim_trailing ( cPar ) ; IpParm a (void far )cParm; else { nVRetVal a -11;

Debug ( 1, sprintf ( Debug_buf, "DB_Fox/vfaxcover: invalid second ' parameter" ) ) ; goto done;

} break; default: nVRetVal a -ιι ;

Debug ( 1, sprintf ( Debug_buf, "DB Fox/vfaxcover: invalid command" ) ) ; goto done;

// Call the faxcover function. nVRetVal a com_Faxcover (βp->hTaβk, nFaxCoverCmdType, IpParm) ; if ( nVRetVal » 0 ) Debug ( 3, sprintf ( Debug_buf, "DB_Fox/vfaxcover: successful set cover page" ) ) ; done:

// Send the return code back to FoxPro. ______ill ( tresult, 0, sizeof ( result ) ) ; result.ev_type a 'I' ; result.ev~long a nVRetVal;

_RetVal ( treβult ) ; return;

} /♦ function vfaxcover /

Add pages to fax document to be sent : VFaxDoc retcode a VFaxDoc( C )

Specify the fax pages being uβed for faxes. Returns:

0 a Success

-11 a Invalid input

-10X a standard error codes (from dVR.h)

void FAR vfaxdoc (

ParamBlk FAR parm

; // Buffer for file name // Voysys return value // FoxPro return structure // fax document command // temporary counter

// command parameter pointer // ptr to slot in line table

// find slot in the line table nVRetVal - FindSlot( GetCurrentTask ( ), tsp ); if ( nVRetVal 1- RC_SUCCEBD ) goto done; ~

// Verify if the first parameter is of proper type if ( parm->p[0] .val.ev type la 'C ) { nVRetVal a -l ; " ~ Debug ( 1, sprintf ( Debug_buf, "DB_Fox/vfaxdoc: invalid input (not a character string) " ) ) ; goto done; }

// Get the text of the first argument and make the string // uppercase. nCount a min( MAX_FN, paπn->p[0] .val.ev_leng h ); _fstrncpy ( (char far ) cPageName,_HandToPtr(

~ parm->p[0]7val.ev_handle ), nCount ); cPageName[nCount] a ( char ) NULL; trim_trailing ( cPageName ) ; βtrupr ( cPageName ) ;

// Parse the fax cover page setup command, if ( lstrcmp ( cPageName, "NEW" ) ) { nFaxDocCmdType - FAX_DOC_NEW;

IpParm a 0; ~ } else if ( lstrcmp ( cPageName, "PAGEBREAK" ) ) { nFaxDocCmdType a FAX_DOC_PAGEBREAK;

IpParm a 0 ;

} else { nFaxDocCmdType « FAX_DOC_FILE_BY_EXT; IpParm ■ cPageName; — — —

}

// Call the fax doc function. nVRetVal a com_Faxdoc ( sp->hTask, nFaxDocCmdType, IpParm ) ; if ( nVRetVal >= 0 )

Debug ( 3, sprintf ( Debug_buf, "DB_Fox/vfaxdoc: successfully set document page" ) ) ; done:

// Send the return code back to FoxPro.

_MemFill ( tresult, 0, sizeof ( result ) ) ; result.ev_type a 'I' ; result.ev_long a nVRetVal; _RetVal ( tresult ) ; return;

} /♦ function vfaxdoc /

/ ************ ****

*

Send fax document to the specified number : VFaixSend retcode a VFaxSend( C [,C] )

Send the active fax document or the specified document. Returns:

0 a Success

-11 a invalid input

-10X a standard error codes (from dVR.h)

void FAR vfaxsend (

ParamBlk FAR parm

char cDocName[MAX_FN + 1] ; // Buffer for file name char cDialStr[MAX_DIGITS + 1] ; int nVRetVal; // Voysys return value

Value result; // FoxPro return structure int nFaxSendCmdType; ..// fax send command int nCount; // temporary counter

LPVOID IpParm; // command parameter pointer struct line_t sp; // ptr to slot in line table

// find slot in the line table nVRetVal a FindSlot( GetCurrentTask ( ) , tsp ) ; if { nVRetVal !« RC_SUCCEED ) goto done;

// Verify if the first parameter is of proper type if ( parm->p[0] .val.ev_type !a 'C ) { nVRetVal a -ll;

Debug ( 1, sprintf ( Debug_buf, "DB_Fox/vfaxβend: invalid input (not a " character string) " ) ) ; goto done;

}

// Get the text of the first argument and make the string

// uppercase. nCount a min( __ύ__DIGITS, parm->p[0] .val.ev_length );

_fβtrncpy ( (char " far )cDialStr, _HandToPtr(

~ parm->p[0] .val.ev_handle ), nCount ); cDialStr[nCount] a ( char ) NULL; - trim_trailing ( cDialStr ) ;

Btrupr ( cDialStr ) ;

// Get the optional document file name if ( parm->pCount •> 1 ) { nFaxSendCmdType a FAX_SEND_ACTIVE_DOC;

IpParm a 0; else { nCount a min( MAX_FN, parm-.p[1] .val.ev_le_gth ); _fstrncpy ( (char-far )cDocName, _HandToPtr( parm->p[l] .val.ev_handle ), nCount ); cDocName[nCount] a ( char ) NULL; trim_trailing ( cDocName ) ; strupr ( cDocName ) ; nFaxSendCmdType a FAX_SEND_FILB_BY_EXT; IpParm a cDocName; ~ ~ ~

// Call the fax send function. nVRetVal a com_Faxβend ( sp->hTask, cDialStr, nFaxSendCmdType,

IpParm ) ; if ( nVRetVal >- 0 )

Debug ( 3, sprintf ( Debugjuf, "DB_Fox/vfaxsend: successfully queue fax document for sending out" ) ) ; done:

// Send the return code back to FoxPro. _MemFill ( tresult, 0, sizeof ( result ) ) ; result.ev_type β 'I'; result.ev long a nVRetVal; _RetVal ( " tresult ) ; return;

} / function vfaxβend / **********************************************************, Setup fax options and parameters : VFaxSetup retcode a VFaxSetup( C [,?] )

Setup fax options such as fax channel, send time, retry counts, etc.

Returns: 0 a Success

•11 a Invalid input

•10X a Standard error codes (from dVR.h)

void FAR vfaxsetup (

ParamBlk FAR ♦parm )

{ buffer for option name // buffer for string option value integer option value Voyβyβ return value FoxPro return structure fax send command temporary counter command parameter pointer ptr to slot in line table

// find slot in the line table nVRetVal a FindSlot ( GetCurrentTask ( ) , tsp ) ; if ( nVRetVal !« RC_SUCCEED ) goto done; ~

// Verify if the first parameter is of proper type if ( (parm->p[0] .val.ev_type !a 'C') || (parm->pCount la 2)) { nVRetVal a -ll;

Debug ( 1, sprintf ( Debugjbuf, "DB_Fox/vf xβetup: invalid

~ input" ) ) ; goto done;

// Get the text of the option name and make the string // uppercase. nCount a min( MAX_FAXCN, parm->p[0] .val.ev_length ); _fstrncpy ( (char far )cOptName, HandToPtr( parm->p[θT.val.ev_handle ), nCount ) ; cOptName[nCount] a ( char ) NULL; trim_ railing ( cOptName ) ; βtrupr ( cOptName ) ;

// Convert the option name to option code

etup: invalid Option) " ) ) ;

goto done

}

// Get the option value according to the option type switch ( nFaxSetupCmdType ) { case FAX SETUP_TRYCOUNT: case FAX~S_TUP_TRYINTERVAL: case FAX~SETUP_FAXCHANNEL: if ( " parm->p[l] .val.ev_type la 'I' ) { nVRetVal a -ιι ;

Debug ( 1, sprintf ( Debug_buf, "DB_Fox/vfaxsetup: invalid input (option must be INT) " ) ) ; goto done; nOptVal a (int) (parm->p[l] .val.ev_long) ;

IpParm a 0; ~ break; case FAX SETUP -IMEFROM: case FAX~SBTUP TIMETO: case FA 3SETϋP " FINE_ODB; case FAX " SETUP_PAGBSIZB: case FAX " SETUP STATIONID: case FAX-SETUP " SENDHEADER: if (~paπn->p[l] .val.ev_type la 'C ) { nVRetVal a -11;

Debug ( 1, sprintf ( Debug_buf, "DB_Fox/vfaxsetup: invalid input (option muβ. be CHAR) " ) ) ; goto done; nCount a min( MAX FAXPARM, parm->p[l] .val.ev_length ) ;

_fβtrncpy ( (char~far )cOptVal, _HandToPtr( parm->p[l] .val.ev_handle ), nCount ); cOptVal[nCount] a ( char ) NULL; trim trailing ( cOptVal ) ; strupr ( cOptVal ) ;

IpParm a cOptVal; nOptVal a 0; break; default: // Impossible 1 break; }

// Call the fax setup function. nVRetVal a com Paxsetup ( βp-.hTaβk, nFaxSetupCmdType, IpParm, " nOptVal ) ; if ( nVRetVal >- 0 ) Debug ( 3, sprintf ( Debugjuf., "DB_Fox/vfaxsetup: successfully setup fax options" ) ) ; done:

// Send the return code back to FoxPro. _MemFill ( tresult, 0, sizeof ( result ) ) ; result.ev_type a 'I' ; reβult.ev_long a nVRetVal; _RetVal ( tresult ) ; return;

} /♦ function v axsetup /

♦endif / DO_FAX /

/________-_-______.___.___--_ ********

Set options and parameters : VSet retcode - VSet( C [,?] )

Set options such as test mode, etc.

Returns:

0 a Success

-11 a Invalid input

-10X a Standard error codes (from dVR.h)

void FAR vβet ( "parm

char COptName[MAX_S_TCN + 1] ; // buffer for option name char COptVal[MAX_SETPARM + 1] ; // buffer for string option value ulong dwOptVal; // unsigned long option value int nVRetVal; // Voysys return value

Value result; // FoxPro return structure int nSetCmdType; // set command int nCount; // temporary counter

LPVOID IpParm; // command parameter pointer struct line_t *βp; // ptr to slot in line table

// find slot in the line table nVRetVal a FindSlot( GetCurrentTask ( ) , tsp ) ; if ( nVRetVal 1- RC_SUCCBBD ) goto done;

// Verify if the first parameter is of proper type if ( (parm->p[0] .val.ev_type la 'C') || (parm->pCount la 2)) { nVRetVal « -11;

Debug ( 1, sprintf ( Debugjbuf, "DB_Fox/vβet: invalid input" ) ) ; goto done;

}

// Get the text of the option name and make the string // uppercase. nCount a min( MAX_SETCN, parm->p[0] .val.ev_length ); _fβtrncpy ( (char far )cOptName, _HandToPtr( parm->p[0] .val.ev_handle ), nCount ); cOptName[nCount] a ( char ) NULL; trim_trailing ( cOptName ) ; strupr ( cOptName ) ;

/♦ Note: to support a new option, add the defines for command type and option value in command.h also you may use some of the defines in compute.h (or add new ones) for option values (e.g. for DateSpeak • DS_MMDDYY, DS_MMDD, etc.) ♦/

// Convert the option name to option code if ( lstrcm ( cOptName, "TESTMDDE" ) ) nSetCmdType a

SET_TESTMODE;

else { nVRetVal « -11;

Debug ( 1, sprintf ( Debug_buf, "DB_Fox/vβet: invalid

~ input " (Unknown Option) " ) ) ; goto done;

}

// Get the option value according to the option type switch ( nSetCmdType ) { case SET TESTMODE: if (~parm->p[l] .val.ev type la 'C ) { nVRetVal a -11; Tl need a new error code (LML) Debug ( 1, sprintf ( Debug_buf, "DB_Fox/vβet: invalid input (option must be CHAR) " ) ) ; goto done; nCount a min( MAX_SETPARM, parm->p[1] .val.ev_lβngth ); _fβtrncpy ( (char " far JcOptVal, _HandToPtr( parm->p[l] .val.evjhandle ), nCount ) ; cOptVal[nCount] a ( char ) NULL; ~ trim_ railing ( cOptVal ) ; strupr ( cOptVal ) ;

/* Check through the list of allowable test mode

types. Return an error if the user specified

an illegal test mode type.

*/ if ( lstrcmp ( cOptVal, "ON" ) ) { dwOptVal a SET_TM_0N; else if ( lstrcmp ( cOptVal, "OFF" ) ) { dwOptVal - SET_TM_OFF; else { nVRetVal a -81; // error code

Debug ( 1, sprintf ( Debugjbuf, "DB_Fox/vse :

Illegal test mode " type >%β " <", cOptVal ) ) ; goto done; }

IpParm a 0; break; default:

// Impossible 1 break; } // Call the set function nVRetVal a com_Set (sp->hTask, nSetCmdType, IpParm, dwOptVal) ; if ( nVRetVal >- 0 ) Debug ( 3, sprintf ( Debug_buf, "DB_Fox/vset: successfully

" set options" ) ) ; done:

// Send the return code back to FoxPro. _MemFill ( tresult, 0, sizeof ( result ) ) ; result.βv_type a » i » ; result.ev^long a nVRetVal;

_RetVal ( tresult ) ; return;

} /♦ function vset ♦/

Linkage information for FoxPro.

/ ttt this should be static; next one can't be ? /

/♦ function names (the 1st column of data) must be in all ♦/

/ capitals •/

Foxlnfo myFoxInfo[] a {

•START", ( FPFI ) start, CALLONLQAD, ""}, •STOP", ( FPFI ) βtqp, CALLONUNLOAD, ""}, "VWAITRING", ( FPPI ) vwaitring, 2, ".I,.I"}, "VHANGUP", ( FPPI ) vhangup, 0, ""}, "VCHKHANGUP", ( FPFI ) vchkhangup, 0, ""}, "VSPEAK", ( FPFI ) vspeak, 10,

"VGETTONES", ( FPFI ) vgettoneβ, 4, "R, .1, .C, .1"), \"VGETTONESM", ( FPFI ) vgettoneβm, 7,

"VGETWORDS"', ( FPFl' ) vgetwordβ, 5, "R, .C, .1, .C, .1"}, "VRECORD", ( FPFI ) vrecord, 2, "?,.!"}, •VDIAL", ( FPFI ) vdial, 3, "C,.I,.R"}, "VDEBUG", ( FPFI ) vdebug, 1, "I"}, "VSETDIR", ( FPFI ) vsetdir, 2, "C,C"}, "VSETDSPEAK", ( FPFI ) vβetdatespeak, 1, "C" "VSETTMOUT", { FPFI ) vsettimeout, 2, "C, I" 1

// "VFAXCOVER", ( FPFI ) vfaxcover, 2, "C, .?"}, :i: // "VFAXDOC", ( FPFI ) vfaxdoc, 1, "C"}, // "VFAXSEND", ( FPFI ) vfaxsend, 2, "C, .C"}, // "VFAXSETUP", ( FPFI ) vfaxsetup, 2, "C,?"}, "VSET", { FPFI ) vset, 2, "C,?"},

};

FoxT__ble _FoxTable a {

( FoxTable FAR ) 0, sizeof ( yFoxInfo ) / sizeof ( Foxlnfo ) , myFoxInfo »

/*** *

APPENDIX D

Compute . c

Copyright 1992-1994 Vovsvs Corporation

_ File: Compute.c

Purpose:

Computation layer.

Does generic stuff needed for most applications, such as

* translating numbers and dates into play-lists. * Independent of OS, line card type.

♦ $Log: /VoysAccess/core/compute/COMPUTE.C $

_ _

♦include <stdio.h> ♦include <βtdlib.h> ♦include <string.h>

// if define equals to 1, enable fax support

// need to define this before the .h files as it could be used in

// the .h header files

♦define DO_FAX 0

♦include "OS.h" ♦include "dVR.h" ♦include "Command.h" ♦include "Compute.h" ♦include "IPC.h"

♦define BASEDIALTIME W ♦define BASEDIALTIME~COMMA ♦define BASEDIALTIME-DEFADLT

♦define MAX_CHRBUF 20

// list of prompt files defines

Information about state of each active instance.

This data is βhared by all client processes, but each process

looks only at the data for itself.

_

Processes find a free slot by looking at the blnUse field. struct line_t { boolean blnUse; // is this table slot in use? unsigned int hTask; // Windows task handle int nLineNum; // line number boolean bConnected; // connected to a line ? boolean bOnLine; // com_anβwer called ? int nDateSpeakType; // Current Set_Date_Speak setting

/ Start (first key/first word) timeouts for GetTones and

GetWords, in milliseconds ♦/ ulong lGetTonesStartTimeout; ulong lβetWordβStartTimeout; short nNumltemsInPlayList; // number of items added to play list (used by com play)

} ; static struct line_t line [MAX_LINES] « { 0} ;

/********************** **************/

II Find line table slot that is being used by task handle hTask static int FindSlot (

const unsigned int hTask, // task handle (Windows only) struct line_t ♦♦ pep // ptr to ptr to slot in line table

int nVRetVal; // Voysyβ return value int nSlot; // slot in line table

// find slot in the line table by using hTask for ( nSlot - 0 ; nSlot < MAX LINES ; nSlot++) { if ( linetnSlot] .blnUse tt "

( linetnSlot] .hTask aa hTask ) ) break; } if ( nSlot >- MAX LINES ) { nVRetVal - RC NOTINITIALIZED; // best guess goto done; *psp a tline[nSlot] ; nVRetVal a RC_SUCCEED; done: return nVRetVal;

} / function FindSlot /

static int add_voicβ ( const struct line_t βp, // ptr to slot in line table const char voice file, const int path[MAX 3IRS]

int nVRetVal; // Voyβyβ return value struct command and; // command to send struct event event; // event received int i;

Debug ( 4, sprintf ( Debug_buf, "Compute/add_voice: called, voice_file '%β'", voice_file ) ); if ( strlen ( voice_file ) >- ( MAX_DN + MAX_FN ) ) { nVRetVal - RC_BADFILENAME; goto done; ~ } αnd.nCommand a DVRC_ADDPLAY; cmd.nLine a sp-.nLineNum; for ( i - 0; i _ MAX_DIRS; i++ ) and.path[i] a path[i] ; cand.play_item.type - PI_SINGLE_FILE; βtrcpy (~αnd.play_item.fname, voice_file ) ; and.play_itern.index a -l; αnd.play_item.vdata a ( void ) OxFFFFFFFF; nVRetVal * ipc_Do_Coπ____d ( sp->hTask, and, tevent ) ;

done:

Debug ( 4, sprintf ( Debugjuf, "Compute/add voice: ret %d", nVRetVal ) ) ; return ( nVRetVal ) ;

} /♦ function add_voice /

static int addp ( const struct line_t *sp, // ptr to slot in line table const char voice file

int nVRetVal; int path[MAX_DIRS] int i; pat fO] - DIR SYSPROMPTS; for ( i - 1; I < MAX DIRS; i++ ) path[i] a DIR_END~; nVRetVal a add_voice ( sp, voice_file, path ) ; return ( nVRetVal ) ; / function addp /

static int addu ( const struct line_t βp, // ptr to slot in line table const char voice file

)

{ int nVRetVal ; int path [MAX_DIRS] ; int i ;

// Note : remember to add more DIR_ if new ones have been created path [0] - DIR.APPPROMPTS ; path [l] a DIR_USERVOICE ; path [2] - DIR TEMPFILBS ; for ( i - 3 ; I < MAX DIRS ; i++ ) path [i] - DIR END. nVRetVal a add_voice ( sp, voice_file , path ) ; return ( nVRetVal ) ; } / function addu /

/ *** * ***** ******************* The "speak as" routines assume the existence of the following promptB:

1996 1997 1998 1999 2000 nesdy thursday friday Saturday

march april july august november december 1st 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st 22nd 23rd 24th 25th 26th 27th 28th 29th 30th 31st ************* *********************/

/*

Special prompt lists. Remember that in C, the first element

in an array has the index 0. */ static const char Days[] a {

"Sunday",

"monday",

"tuesday", "wednesdy",

"thursday",

"friday",

"Saturday" } ' ' static const char Months[] - {

January",

"february", "march",

"april",

"may",

"June",

"july", "august",

"βeptembr",

"October",

"november",

"december" }; static const char ♦Ordinals[] a {

"1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th",

"11th", "12th", "13th", "14th", "15th",

"16th", "17th", "18th", "19th", "20th",

"21st" , "22nd" , " 23rd" , "24th" , "25th" , "26th" , "27th" , "28th" , "29th" , "30th" ,

speak_as_digits: Take the character input, and speak out the different digit prompts. If preceded with a minus, speak that. Also allow for decimal points and digits after that. Ignore all characters other than '0'-'9', '-', and ' . ' . This procedure can handle any number of digits. This procedure will ignore any characters that are not digits. returns: 0 a success

static int βpeak_as_digitβ ( const struct line_t sp, // ptr to slot in line table const char numbuf

) int nVRetVal; const char chrptr; chrptr a numbuf; while ( chrptr !« ( char ) NULL ) { switch ( chrptr ) { case '0' : nVRetVal a addp ( sp, PF_0 ) ; break; case '1' : nVRetVal - addp ( sp, PF_1 ) ; break; case '2' : nVRetVal - addp ( sp, PF_2 ) ; break; case '3' : nVRetVal a addp ( sp, PF_3 ) ; break; ~ case '4' : nVRetVal « addp ( sp, PF_4 ) ; break; case '5' : nVRetVal « addp ( sp, PF_5 ) ; break; case '6' : nVRetVal a addp ( βp, PF_6 ) ; break; case '7' : nVRetVal a addp ( sp, PF_7 ) ; break; case '8' : nVRetVal a addp ( βp, PF_8 ) ; break; case '9' : nVRetVal = addp ( sp, PF_9 ) ; break; case ' -' :

nVRetVal a addp ( βp, PF_MINUS ) ,- break; case ' • ' : nVRetVal a addp ( sp, PF_POINT ) ; break; default: nVRetVal a -1;

Debug ( 1, sprintf ( Debug uf, "Compute/spe__._as_digits: invalid character ♦%d '%C (0x%02X) ", ( int) ( chrptr - " numbuf ) , chrptr, chrptr ) ) ; brea_k; } if ( nVRetVal 1= RC_SUCCEED ) goto done; chrptr++; } done: return ( nVRetVal ) ;

} /♦ function speak_as_digits ♦/

βpeak_as day: Take the input, and convert it to integer. If the integer is in the range 1..7, speak the corresponding day of the week (where Sundayal) . Otherwise, return an error. returns: 0 a success

-l a improper day number

7 static int spβak_as day ( const struct " l_ e_t sp, // ptr to slot in line table const char ~ numbuf

int dayval; int nVRetVal; // Voysys return value dayval a atoi ( numbuf ) ; if ( ( dayval < 1 ) | | ( dayval > 7 ) ) { nVRetVal a -l; // need a error code ?

Debug ( 1, sprintf ( Debug_buf, "Compute/speak_as_day: value for day must be between _ " _____ 7" ) ) ; goto done;

} nVRetVal a addp ( sp , Days [dayval - 1] ) ; done: return ( nVRetVal ) ;

} /♦ function speak_as_day /

speak_0_to_999: Speak numbers in the range of 0-999. This iβ a utility routine for "spe__c_as_number" . Note that this routine takes an integer as an argument, not a character string. ***************************** ************* / static int βpeak_0_to_999 ( const struct line_t sp, // ptr to slot in line table const int intval

int nVRetVal; // Voysys return value int hundreds, tens, ones; char chrbuf[MAX CHRBUF] ;

/*

Break the number into hundreds, tens, and ones. */ hundreds a intval / 100; tens a ( intval - ( hundreds 100 ) ) / 10; ones a intval % 10;

/*

Speak the hundreds if they are greater than zero. ♦/ if ( hundreds > 0 ) { sprintf ( chrbuf, "%ld", hundreds ) ; nVRetVal a addp ( sp, chrbuf ) ; if ( nVRetVal la RC_SUCCEED ) goto done; nVRetVal - addp ( sp, PF_HUNDRED ) ; if ( nVRetVal !« RC_SUCCEED ) goto done; " }

/*

If the tens digit is one, speak the appropriate "teens"

prompt. / if ( tens BB ι ) { sprintf ( chrbuf, "l.ld", ones ) ; nVRetVal a addp ( sp, chrbuf ) ; if ( nVRetVal !- RC_SUCCEBD ) goto done;

}

/*

Otherwise, look for both a tens prompt and a ones prompt. */ else {

/* ♦ If the tens digit is greater than 1, speak the ♦ appropriate tens prompt.

*/ if ( tens > 1 ) { sprintf ( chrbuf, "%ld0", tens ); nVRetVal a addp ( sp, chrbuf ) ; if ( nVRetVal la RC_SUCCEED ) goto done; }

/*

♦ Then speak the ones prompt, even if it is zero but only

if tens and hundreds are zero.

*/ if (( ones la 0 ) || (( tens «» 0 ) tt ( hundreds — 0))) { sprintf ( chrbuf, "%ld", ones ); nVRetVal a addp ( sp, chrbuf ) ; if ( nVRetVal 1- RC_SUCCEED ) goto done; , ' done: return ( nVRetVal ) ;

} /♦ function βpeak_0_to_999 /

βpeak_as_number: TaJe in a character string, and speak it as a number. If it has a decimal point, speak that and speak the numbers after it as digits. This procedure can handle the range:

-999999999999 to 999999999999 (999 billion)

This procedure will ignore all extraneous characters before the first number (or minus or point) .

Returns: 0 a success

•l a no numeric characters -2 a number is too large ****** ** *** * ******* * ***** ** * * ********** * ******** * * * *** * *****/ static int Bpea_k_aβ_number ( const struct line_t sp, // ptr to slot in line table const char numbuf )

{ int numlen, count; char chrbuf[MAX_CHRBUF] ; int nVRetVal; // Vσyβyβ return value char chrptr;

/*

♦ Find the first character that is a minus, point, or 0-9.

♦ Return an error if there weren't any. */ chrptr a strpbrk ( numbuf, "0123456789- ." ); if ( chrptr aa ( char ) NULL ) { nVRetVal - -1; // may need an error cod- Debug ( 1, sprintf ( Debugjbuf, "Compute/speak_aβ_number: cannot find any valid character" ) ) ; goto done;

/ ♦ If the first character is a minus, add that prompt to the

♦ list, and move on. */

if ( ♦chrptr -a ' -' ) { nVRetVal * addp ( sp, PF_MINUS ) ; if ( nVRetVal != RC_SUCCEED ) goto done; chrptr++;

/* Find the number of consecutive numeric characters. If the

number of digits is greater than twelve (999 billion)

return an error.

*/ numlen a strspn ( chrptr, "0123456789" ) ; if ( ( numlen > 12 ) | | ( numlen > MAX_CHRBUF ) ) { nVRetVal a -2; // may need an error code Debug ( 1, sprintf ( Debugjbuf, "Compute/βpeak_as_number: number is too " large" ) ); goto done;

/*

If the number of digits is greater than 9, we have a billions value. Break off the billions digits and

speak them, as long as they aren't all zeros. / if ( numlen > 9 ) { count a numlen - 9; chrbuf[count] a (char) NULL; strncpy ( chrbuf, chrptr, count ) ; if ( atoi ( chrbuf ) > 0 ) { nVRetVal a speak_0 to_999 ( sp, atoi ( chrbuf ) ) ; if ( nVRetVal 1- RCJSUCCEED ) goto done; ~ nVRetVal - addp ( sp, PF_BILLION ) ; if ( nVRetVal 1- RC_SUCCEED ) goto done; ~ chrptr +a count; numlen a 9 ;

If the number of digits remaining is greater than 6, we

have a millions value. Break off the millions digits

♦ and speak them, as long as they aren't all zeros. / if ( numlen > 6 ) { count a numlen - 6; chrbuf[count] a (char) NULL; strncpy ( chrbuf, chrptr, count ) ; if ( atoi ( chrbuf ) > 0 ) { nVRetVal a speak_0 to_999 ( sp, atoi ( chrbuf ) ) ; if ( nVRetVal !- RC_SUCCEED ) goto done; nVRetVal a addp ( sp, PF_MILLION ) ; if ( nVRetVal != RC SUCCEED )

goto done;

} chrptr +a count; numlen a 6; }

/*

♦ If the number of digits remaining is greater than 3, we

♦ have a thousands value. Break off the thousands

♦ digits and speak them, as long as they aren't all zeros.

*/ if ( numlen > 3 ) { count a numlen • 3; chrbuf[count] a (char) NULL; strncpy ( chrbuf, chrptr, count ) ; if ( atoi ( chrbuf ) > 0 ) { nVRetVal a βpeak_0_to_999 ( βp, atoi ( chrbuf ) ) ; if ( nVRetVal !a RC_SUCCEED ) goto done; ~ nVRetVal « addp ( βp, PF THOUSAND ) ; if ( nVRetVal !« RC_SUCCEED ) goto done; } chrptr +a count; numlen a 3; } / *

Now speak the remaining digits. If the value is zero, and

♦ there were thousands* digits, do not speak the zero.

♦ If the value iβ zero, and there were no thousands

♦ digits, do speak the zero. */ chrbuf[numlen] a (char) NULL; strncpy ( chrbuf, chrptr, numlen ) ; if ( ( atoi ( chrbuf ) > 0 ) | | ( numlen < 3 ) ) { nVRetVal ■ βpeak_0_to_999 ( βp, atoi ( chrbuf ) ) ; if ( nVRetVal 1- RC_SUCCEED ) goto done; } chrptr +a numlen;

/*

♦ If the next character iβ a point ('.'), speak it and the

♦ next digits. Speak the digits after the decimal point ♦ as digits (10.23 a "ten point two three") .

*/ if ( ♦chrptr a« ' .' ) { nVRetVal a addp ( sp, PF_P0INT ) ; if ( nVRetVal !- RC_SUCCEED ) goto done; ~ chrptr++; nVRetVal a βpe_k_aβ_digitβ ( βp, chrptr ) ; if ( nVRetVal !« RC_SUCCEED ) goto done;

done: return ( nVRetVal ) ;

} /♦ function βpeak_aβ_number ♦/

spea_k_as_dollars: Speak a numeric amount as dollars and cents (or dollars only, if the cents value is zero.) This procedure can handle the range:

-999999999 to 999999999 (999 million)

This procedure will ignore all extraneous characters before the first number (or minus or point) .

Returns: 0 a success -1 a no numeric characters

-2 a number iβ too large

static int speak_as_dollars ( const struct line_t sp, // ptr to slot in line table const char numbuf

// Voyβyβ return value

I*

Find the firβt character that iβ a minus, point, or 0-9. Return an error if there weren't any.

*/ chrptr = strpbrk ( numbuf, numβ ) ; if ( chrptr — ( char ) NULL ) { nVRetVal » -ι ; Debug ( 1, sprintf ( Debugjbuf, "Co_πpute/speak_as_number: cannot find any valid character" ) ) ; goto done; } /*

♦ Find the decimal point, if any. If there is one, stick a

♦ null there.

*/ decptr a βtrchr ( chrptr, ' .' ) ; if ( decptr ) {

♦decptr a (char) NULL;

/* ♦ If there are any digitβ before the decimal point, send them

♦ off to 'βpea_k_aβ_number' . If the return code iβ

♦ non-zero, exit with the same return code.

*/ if ( chrptr !a decptr ) { nVRetVal a speak as_nu_r__er ( sp, chrptr ) ; if { nVRetVal 1»~RC_SUCCEED ) goto done; dollars a atoi( chrptr ) ,-

} else dollars a 0;

/*

♦ Now find out if there is a 'centβ' value to speak. We want

♦ to speak a 'cents' if there is a decimal point, and if

♦ the two digits after it are non-zero. Remember to ♦ restore the decimal point to the buffer, so as not to

♦ modify it.

*/ centβ a 0; if ( decptr ) { ♦decptr a ' .' ; decptr++; chrbuf[2] a (char) NULL; strncpy ( chrbuf, decptr, 2 ) ; cents a atoi ( chrbuf ) ; }

/*

If there is a centβ value, we now want to speak the prompt

♦ "dollars and" (or "dollar and") . Otherwise, just speak ♦ "dollars" (or "dollar") . If there iβ a centβ value,

♦ βpeak it, and speak the "cents" (or "cent") prompt.

/ if ( ce if ) ; nVRetVal a addp ( βp, PF DOLLARSAND ) ; if ( nVRetVal !« RC_SUCCEED ) goto done;

} nVRetVal a βpβak_0 to 999 ( βp, centβ ) ; if ( nVRetVal !- RC_SUCCEED ) goto done; ~ if (centβ aa ι) nVRetVal « addp ( βp, PF_CENT ) ; nVRetVal a addp ( βp, PF CENTS ) ; if ( nVRetVal 1- RC_SUCCEED 1 goto done; else if ( dollars > 0 ) { if (dollarβ -« 1) nVRetVal . addp ( βp, PF_DOLLAR ) ; nVRetVal a addp ( βp, PF_DOLLARS ) ; if ( nVRetVal !- RC_SUCCEED ) goto done;

done: return ( nVRetVal ) ;

} / function βpeak_aβ_dollars /

speak_aβ_date: Speak a date. Aββume the date iβ in the format Mm/Dd YY (lower caβes not required) . Speak the date in the format specified by the current setting of nDateSpeakType: MMDDYY: "July 14th, 1980" (month name, ordinal number,

19XX; only 20th Century dates)

MMDD: "July 14th" DDMMYY: "the 14th of July, 1980"

DDMM: "the 14th of July"

Returns: 0 βucceββ -1 improper format for a date (month)

-2 improper format for a date (day)

-3 improper format for a date (year)

-4 improper format (cannot find '/') Note: only MMDDYY and MMDD iβ supported for now 111!! **************************************** ***/ static int speak_as date ( const struct lι e_t sp, // ptr to slot in line table const char numbuf

)

{ int month, day, year; char βtartptr; int nVRetVal; // Voysys return value

/*

♦ Extract the month, day, and year from the date. Return -l ♦ if anything at all goeβ wrong. The month muβt be at

♦ the beginning of the date. */ startptr a ( char ) numbuf; month a atoi ( startptr ) ; if ( ( month < 1 ) | | ( month > 12 ) ) { nVRetVal « -ι ; Debug ( 1, sprintf ( Debug ouf, "Cαmpute/spe__k_aβ_date: invalid month %d", month ) ) ; goto done; }

/*

♦ The day βhould start after the first slash

*/ βtartptr a βtrchr ( βtartptr, '/' ); if ( βtartptr a« ( char ) NULL ) { nVRetVal a -4;

Debug ( 1, βprintf ( Debugjbuf, "Compute/speak_aβ_date:

~ cannot find '/'" ) ) ; goto done;

1 βtartptr++; day a atoi ( βtartptr ) ; if ( ( day < 1 ) 11 ( day > 31 ) ) { nVRetVal « -2; Debug ( 1, sprintf ( Debugjbuf, "Compute/speak_as_date:

~ invalid day %d", day ) ) ; goto done;

/

♦ The year should start after the next slash.

*

ttt Note that dBase date is the same as DOS format, which

♦ means no provision is made for dates not matching 19** . When they get around to fixing this, we will

too.

*/ βtartptr a βtrchr ( startptr, '/' ); if ( βtartptr a« ( char ) NULL ) { nVRetVal a -4;

Debug ( 1, βprintf ( Debu J_uf, "Compute/speak_aβ_date: cannot find '/'" ) ); goto done;

βtartptr++; year a atoi ( βtartptr ) ; if ( ( year < 0 ) |] < year > 99 ) ) { nVRetVal a -3; Debug ( 1, βprintf ( Debugjbuf, "Compute/speak_as_date:

~ invalid year %d", year ) ) ; goto done; } /*

Now add promptβ to the prompt liβt. Start by adding the

♦ month and ordinal day prompts, in the order specified

♦ by nDateSpeakType.

*/ if ( t βp->nDateSpeakType « DS_MMDDYY ) || ( sp->nDateSpeakType aa DS_MMDD ) ) { nVRetVal a addp ( βp, Months[month - 1] ); if ( nVRetVal I- RC_SUCCEBD ) goto done; ( sp, Ordinals[day - 1] RC_SUCCEED )

( sp, PF THE ) ; RC_SUCCEBD ) goto one; nVRetVal a addp ( βp, Ordinals[day - l] ); if ( nVRetVal !- RC_SUCCEED ) goto done;

nVRetVal a addp ( βp, PF_OF ) ; if ( nVRetVal !- RC_SUCCEED ) goto done; nVRetVal a addp ( βp, Months[month - l] ) ; if ( nVRetVal 1- RC_SUCCEED ) goto done; } /

♦ Now add the year prompts, if the nDateSpeakType specifies

♦ it. Start with "nineteen", and then speak the year

♦ (XX) number as a number - three caβeβ: 00 - βpeak the

prompt "hundred" only OX • speak the prompt "zero" ♦ then speak the number. XX - simply speak the number.

If the year iβ in the range 1994..2000 and 1990/ we

♦ have a βpecial prompt for it.

*/ // Note: year 2000 not currently supported. if ( ( βp->nDateSpeakType «- DS_MMDDYY ) || ( sp->nDateSpeakType a. DS_DDMMYY ) ) {

0 ) 4 ) 5 ) 6 ) 7 ) 8 ) 9 ) ) ; ) ;

ar ) ;

, if ( nVRetVal !« RC_SUCCEED ) goto done; } done:

return ( nVRetVal ) ; } /♦ function βpeak_aβ_date /

βtatic void init_play_liβt ( struct line_t sp // ptr to slot in line table ) { struct command cmd; // command to send struct event event; // event received int nVRetVal; // Voyβyβ return value αnd.nCommand « DVRC_INITPLAY; αnd.nLine a sp->nLiήeNum; αnd.play_itern.type a PI_NONE; sp->nNumIternsInPlayLiβt a 0; nVRetVal a ipc_Do_Command ( sp->hTaβk, and, tevent ) ;

/ function init_play_list /

7 βtatic int add_to_play_liβt ( const struct line_t ♦sp. // ptr to slot in line table const char voice_file, const int play_type

int nVRetVal; int i, vfile_len; char non_digit_char a ( char ) NULL;

♦ Find the first non-digit, non-blank βymbol in the βtring.

If there iβn't any, speak it aβ a number. If there iβ

one, and it's a βlaβh (/) , βpeak the βtring aβ a date.

Otherwise, treat the βtring aβ a file name. 7 vfile_len a strlen ( voice_file ) ; for (~i a 0; i _ vfile len " i++ ) if ( ( (voice fileli] < '0' tt

( voice_file[i] la '

( voice_file[i] !» '

( voice~file[i] la ' non_digit_char a voice_ e break;

if ( non_digit_char a = ( char ) NULL ) { /♦ voice file contains a number value / /♦ Adding number type / βwitch ( play_type ) { case AS_NUMBER: nVRetVal a speak_as_number ( sp, voice_file ) ;

break; case AS DIGITS : nVRetVal a βpeak_as_digits ( sp, voice_file ) ; break; case AS_DOLLARS : nVRetVal a speak_as_dollars ( βp, voice_file ) ; break; "" case AS_DAY: nVRetVal a βpeak_aβ_day ( sp, voice_file ) ; brea ; default: nVRetVal a -2; break;

}

} else if ( non_digit_char BB '/' ) { nVRetVal a " speak_as_date ( sp, voice_file ) ; else {

/ voice file containβ the name of the voice file to play ♦/ nVRetVal * addu ( βp, voice file ) ; } return ( nVRetVal ) ;

/ function add_to_ >lay_liβt /

/ < βtatic int play_play_liβt ( const struct line_t sp, // ptr to slot in line table const boolean interrupt mode // end play if DTMF received?

)

{

// command to send

// event received // Voysyβ return value αnd.nCommand a DVRC_PLAY; cmd.nLine a βp->nLineNum and.play_itern.type a Pl_NONE; αnd.flush_digits_at_βtart a TRUE; and.end_voice_on_any digit a interrupt_mode; cmd.end_voice__on_digTt_end a FALSE; and.end_oper_on_any digit a FALSE; and.end_pper_on_digι__end TRUE; and.end_oper_on_voice_end ( !interrupt_mode ) ; and.max_digits a 0; αnd.end_digit a '♦' cmd.get_digitβ_at_end a FALSE; αnd.βtart_timeout a 0; and.total_timeout a 0; αnd.interdigit_to * 0; αnd.end_βilence a ( unsigned ) NOT_APPLIC; αnd. otal_recording a ( unsigned )-NOT_APPLIC; nVRetVal * ipc_Do_Command ( βp->hTask, and, tevent ) ; return ( nVRetVal ) ;

} /♦ function play_play_list /

/ **** * __-________________/ int com_Initialize ( const unsigned int hTask, // task handle (Windows only) const unsigned int hinβtance, // instance handle

(Windows only) const unsigned long hWnd // hidden window handle (Windows only)

) int nVRetVal; // Voyβys return value int nSlot; // slot in line table struct line_t βp; // ptr to slot in line table nVRetVal a CheckDebugSanity( ) ; if (nVRetVal 1« RC^SUCCEED) {

Debug ( 1, sprintf ( Debug_buf, "Compute/com_Initialize: debug sanity failure" ) ) ; goto done;

}

// find slot in the line table nVRetVal - FindSlot( hTask, tsp ); if ( nVRetVal «- RC SUCCEED ) { nVRetVal - RC_ALREADYINITIALIZED; goto done; ~ >

// find a free slot in the line table for ( nSlot a 0 ; nSlot < MAX LINES ; nSlot++ ) { if (!line[nSlot] .blnUse) break; } if ( nSlot >- _____LINES ) {

Debug ( 5, βprintf ( Debug_buf, "Compute/cαm_Initialize:

~ line table iβ full" ) ) ; nVRetVal - RC_SWLIMIT; goto done; βp a tline[nSlot] ; βp->bInUβe a TRUE; sp->hTask a hTask; sp->bConnected a FALSE; sp->nLineNum a MAX_LINES; sp->bOnLine a FALSE; /♦ initialize lower layer(s) / nVRetVal a ipc_Initialize ( βp-.hTaβk, hlnstance, hWnd ) ; if ( nVRetVal < RC_SUCCEED ) { sp->bInUβe a FALSE; goto done; }

/*

♦ Set defaults for the various functions .

*/ sp- >nDateSpeakType a DS_MMDDYY; βp- > lGetWordβStartTimeout a DEF_GETWORDS_START_TIME OUT ; Bp->lGetTonesStartTimeout a DEF_GETTONES_START_TIMEOUT ;

nVRetVal - RC_SUCCEED; done : return ( nVRetVal ) ;

} / function com_Initialize /

/******************* ***,

♦ arguments have following meaning:

*

♦ - if rline_num is < MAX_LINES, means try for that line only. - if rline~num aa MAX_LINES, means try for any line.

*

♦ - if oline_num is < MAX_LINES, means got that line.

- if oline num m MAX_LΪNES, means did not get a line.

******************* * ************/ int com_Connect ( const unsigned int hTask, // task handle (Windows only) const int rline_num, // requested line number int oline num // obtained line number )

{ int nVRetVal a RC_INTERNAL; // Voysys return value struct command cmd; // command to send struct event event; // event received int curlinenum; // line number struct line_t sp; // ptr to βlot in line table

oline_num a MAX_LINES; // find slot in the line table nVRetVal a FindSlot( hTask, tsp ) ; if (nVRetVal 1« RC_SUCCEED) goto done; if ( βp->bConnected ) {

/ we are already connected to a line; disconnect ♦/ cmd.nCommand a DVRC_DISCC____CT; αnd.nLine a sp->nLineNum and.piay_itern.type a PI_N0NE; nVRetVal a ipc_Do_Command ( sp->hTask, and, tevent ) ; if { nVRetVal >« RC_SUCCEED ) βp->bConnected a FALSE; goto done;

} if ) { /♦ try juβt one line /

/ connect to a line / cmd.nCommand a DVRC_CONNECT; αnd.nLine a rline_num; cmd.play_item.type a PI_N0NE; nVRetVal a ipc_Do_Command ( sp->hTask, and, tevent ) ;

if ( nVRetVal >« RC_SUCCEED ) { sp-.bConnected a TRUE; >nLineNum = rline_num; ine_num a sp->nLineNum ine_num a MAX_LINES; else {

/♦ find first free line / for (curlinenum a 0; curlinenum. MAX_LINES; curlinenum++) {

/ connect to a line / cmd.nCommand a DVRC_CONNECT; αnd.nLine a curlinenum; and.play_itβm.type a PI_NONE; nVRetVal a ipc_Do_Command ( sp->hTask, and, tevent ) ; if ( nVRetVal >« RC_SUCCEED ) { sp->bConnected a TRUE; sp->nLineNum a curlinenum; ♦oline_num a sp->nLineNum goto done;

, *

/ failed to find a line / ♦oline num a MAX LINES;

} done: return ( nVRetVal ) ;

} /♦ function cc__Connect /

/**** * *************************************************<

*

♦ Tricky: if dir_name m ".", this means client current directory, not βerver current directory.

_

int com_Set_Directory ( const unβigned int hTask, // task handle (Windows only) char dir_type_βtr, // directory type βtring char dir "" name~ // name (no 'V on end)

) int nVRetVal a RC_INTERNAL; // Voyβyβ return value struct command and;- // command to βend struct event event; // event received struct line_t βp; // ptr to βlot in line table int dir_type;

// find βlot in the line table nVRetVal a FindSlot( hTask, tsp ); if (nVRetVal !« RC_SUCCEED) goto done; tricky: muβt be connected to a line before can do a SETDIR / if ( !βp--bConnected ) {

nVRetVal - RC_NOTCONNECTED; goto done;

if ( βtrlen ( dir_name ) > MAX DN ) { nVRetVal a RC_BADFILENAME; " goto done;

} /*

Now capitalize the βtring.

*/ strupr ( dir_type_βtr ) ;

strupr ( dir_name ) ;

/ convert directory name to full path name ♦/ dname_partial_to_full ( dir_name, and.name ) ;

/♦ better late than never ... ♦/ if ( βtrlen ( and.name ) > MAX_DN ) { nVRetVal « RCJBADFILBNAME; goto done; } cmd.nCommand a DVRC_SETDIR; αnd.nLine a βp->nLineNum cmd.dir_num a dir_type; cmd.play_itern.type a PI_NONE; nVRetVal a ipc_Do_Command ( βp->hTaβk, cmd, tevent ) ; done: return ( nVRetVal ) ;

} /♦ function com_Set_Directory ♦/

* I*

* com_Set_Input: Set the input device (phone/microphone) *

int com_Set_Input ( const unsigned int hTask, // task handle (Windows only) const int dev num // device number

int nVRetVal « RC_INTERNAL; // Voysyβ return value struct command and; // command to send struct event event; // event received struct line_t βp; // ptr to slot in line table

// find βlot in the line table nVRetVal - FindSlot( hTask, tsp ); if (nVRetVal 1« RC_SUCCBED) goto done; if ( lβp->bConnected ) { nVRetVal - RC_NOTCONNECTED; goto done;

if ( sp->bOnLine ) { nVRetVal - RC_OFFLINBONLY; goto done; } cmd.nCommand a DVRC_SETINPUT; αnd.nLine a βp-_nLineNum; αnd.io_location a dev_num; and.play_itern.type a Pl_NONE; nVRetVal a ipc_Do_Command { βp->hTaβk, and, tevent ) ; done: return ( nVRetVal ) ;

/ function co__Set_Input */

com_Set_Output: Set the output device (phone/βpeaker)

int com_Set_Output ( const unsigned int hTask, // task handle (Windows only) const int dev num // device number

int nVRetVal = RC_INTERNAL; // Voysys return value struct command and; // command to send struct event event; // event received struct line_t βp; // ptr to βlot in line table

// find βlot in the line table nVRetVal a FindSlot( hTask, tβp ); if (nVRetVal !« RC_SUCCEED) goto done; if ( lsp->bConnected ) { nVRetVal « RC_NOTCONNECTED; goto done;

if ( sp->bOnLine ) { nVRetVal » RC_OFFLINEONLY; goto done,-

} cmd.nCommand a DVRC_SETOUTPUT; cmd.nLine a βp-_r__ine_uπt; cmd.io_location a dev_num; and.play_itern.type a PI_NONE; nVRetVal a ipc_Do_Command ( sp->hTask, and, tevent ) ; done: return ( nVRetVal ) ;

} / function com_Set_Output /

/< com_Set_Date_Speak: Set the date speak type.

int cαm_Set_Date_Speak ( const unsigned int hTask, // task handle (Windowβ only) char date_type // date βpeak type

int nVRetVal a RC_INTBR___L; // Voysys return value struct line_t sp; // ptr to βlot in line table int speak_type; // find βlot in the line table nVRetVal - FindSlot( hTask, tsp ) ; if (nVRetVal !- RC_SUCCEED) goto done; if ( lsp--bConnected ) { nVRetVal » RC_NOTCONNECTED; goto done;

/*

♦ Now capitalize the βtring. */ strupr ( date_type ) ; /

♦ Check through the list of allowable date speak types.

♦ Return an error if the uβer βpecified an illegal type.

*/ if ( lstrcmp ( date_type, "MMDDYY" ) ) { βpeak_type a DS_MMDDYY; else if ( lstrcmp ( date_type, "MMDD" ) ) { βpeak_type β DSJMMDD; else if ( Iβtrαπp ( date_type, "DDMMYY" ) ) { βpeaJc type a DS DDMMYY; } elβe if ( Iβtrαnp ( date_type, "DDMM" ) ) { βpeak_type a DS_DDMM; else ( nVRetVal a RC_SETDSPEAKINVALIDARG; goto done; }

/* Assume that the nDateSpeakType sent by the upper level ii ♦ legitimate.

*/ sp->nDateSpeakType a speak_type; nVRetVal - RC SUCCEED; done: return ( nVRetVal ) ;

} /♦ function com_Set_Date_Speak ♦/

/********************* ***************************** cc*n_Set_Timeout: set various timeouts _ """ * ~ **************************************************** / int com_SetJTimeout ( const unsigned int hTaβk, // taβk handle (Windowβ only) char timeout_type_βtr, // timeout type const int timeout // timeout in seconds

) int nVRetVal a RC_INTERNAL; // Voyβyβ return value struct line_t sp; // ptr to slot in line table int timeout_type; ulong dwTimeout; // find Blot in the line table nVRetVal a FindSlot( hTaβk, tsp ) ; if (nVRetVal 1- RC_SUCCEED) goto done; if ( lsp->bConnected ) { nVRetVal - RC_NOTCONNECTED; goto done; ~ "

} /*

♦ Make sure the timeout is valid (poβitive integer) . /

if ( timeout <« 0 ) { nVRetVal a RC_SETTMOUTBADTIMEOUT; goto done ;

}

/*

Convert the timeout to milliseconds.

*/ dwTimeout a (ulong) timeout ♦ 1000;

/*

♦ Now capitalize the βtring. */ strupr ( timeout_type_str ) ;

/*

Check through the liβt of allowable timeout types. Return

an error if the user specified an illegal output type. */ if ( lstrcmp ( timeout_type_str, "VGETTONES" ) ) { timeout_type a TO_VGETTONES; sp->lGetTonesStartTimeout a dwTimeout; elβe if ( Iβtrcmp ( timeout_type_βtr, "VGETWORDS" ) ) { timeout_type a T0_VGETW0RDS; βp->lGetWordβStartTimeout a dwTimeout; else { nVRetVal » RC_SETTMOUTBADKEYWORD; goto done;

nVRetVal a RCJSUCCEED; done: return ( nVRetVal ) ;

} / function com_Set_Timeout ♦/

Open an indexed prompt file

int com_Set_Indexed_File ( const unsigned int hTask, // task handle (Windows only) const char base name // base filename (no

' .ext' on end)

) int nVRetVal . RC_INTERNAL; // Voysyβ return value int i; struct command and; // command to send struct event event; // event received struct line_t sp; // ptr to βlot in line table

// find βlot in the line table nVRetVal - FindSlot( hTask, tsp ) ; if (nVRetVal != RC SUCCEED)

goto done ; if ( l βp- - bConnected ) { nVRetVal » RC_NOTCONNEC__D ; goto done ;

} if ( βtrlen ( base name ) >- MAX_FN - 4 ) { nVRetVal a RC_B__DCC___AND ; goto done ; ~

} cmd.nCommand a DVRC_SETIFILE ; αnd.nLine a sp- > nLineNum; and. play_item. type a Pl_NONE ; strcpy ( αnd.play_item. fname, base_name ) ; βtrcpy ( αnd.nameT baβe_name ) ; ~ strcat ( and. name , " .map" ) ; cmd. play item. index a - 1 ; cmd.play " item.vdata » ( void ) OxFFFFFFFF; and. path [0] - DIR_SYSPROMPTS ; for ( i - 1 ; i < MAX_DIRS ; i++ ) and . ath [i] a DIR_END; nVRetVal a ipc__Do__Command ( βp- >hTaβk, and, tevent ) ; done : return ( nVRetVal ) ; } / function com_Set_Indexed < _File /

Tell lower levels what vocabulary we are using for

♦ voice-recognition. There are 3 relevant data files, all with

the same base name:

* __.sename.va Vocabulary file. Rhetorex proprietary format.

♦ Name iβ βpecified in driver config file; driver

♦ readβ in this file.

_

baβename.vmp Voca_oulary "word map" file. One word-name per ♦ line. Line number aa word index in vocabulary

♦ file (first line aa l) . File must be in

♦ SYSPROMPTS directory.

_

♦ baβename.vβm Vocabulary "syntax map" file. One syntax-name ♦ per line. Line number aa syntax mask value

♦ (firβt line aa 0) . File muβt be in SYSPROMPTS

♦ directory.

7 int cc__Set_Vocabulary ( const unsigned int hTask, // task handle (Windows only) const char ♦base_name // base filename (no ' .ext' on end) ) { int nVRetVal a RC_INTERNAL; // Voysyβ return value int i;

struct command and; // command to send struct event event; // event received struct line_t sp; // ptr to βlot in line table

// find βlot in the line table nVRetVal a FindSlot( hTaβk, tsp ); if (nVRetVal !- RC_SUCCEED) goto done; if ( !sp--bConnected ) { nVRetVal « RC_NOTCONNEC___D; goto done;

} if ( βtrlen ( baβe_name ) >a MAX :_FFNN - 4 ) { nVRetVal a RC_BADCQMMAND; goto done;

} cmd.nCommand a DVRC_SETVOCAB; αnd.nLine a sp->nLiήeNum; and.play_item.type a PI_NONE; βtrcpy (-cmd.play_item.fname, base_name ) ; 8treat ( and.play_itern.fname, ".vmp" ); strcpy ( and.name, baβe_name ) ; strcat ( and.name, ".vβm" ); cmd.play_itern.index a -ι ; and.play itβm.vdata a ( void ) OxFFFFFFFF; αnd.pathTO] a DIR_VOCABS; for ( i - 1; i < MAX_DIRS; i++ ) and. ath[i] a DIR_END; nVRetVal a ipc_Do_Command ( βp->hTask, and, tevent ) ; done: return ( nVRetVal ) ;

} / function com_Set_Vocabulary */

/* int com_Diβconnect ( const unsigned int hTaβk // task handle (Windows only) )

{ int nVRetVal RC_INTERNAL; // Voysys return value struct command cmd; // command to send struct event event; // event received struct line t sp; // ptr to slot in line table

// find slot in the line table nVRetVal a FindSlot( hTask, tsp ) ; if (nVRetVal !- RC_SUCCEED) goto done; if ( !sp->bConnected ) { nVRetVal - RC_N0TCONNECTED; goto done;

αnd.nCommand a DVRC_DISCONNECT; αnd.nLine a βp->nLineNum and.play_itern.type a Pi røNE; nVRetVal a ipc_Do_Command ( sp-_hTask, cmd, tevent ) ; if ( nVRetVal >■ RCJSUCCEED ) { βp->bConnected _T FALSE; sp->nLineNum a MAX_LINES; sp->bOnLine a FALSE;

done: return ( nVRetVal ) ;

} / function com_Disconnect /

int com_Shutdown ( const unsigned int hTask // task handle (Windows only)

struct command cmd; // command to send struct event event; // event received int nVRetVal a RC_INTERNAL; // Voysys return value struct line_t βp; - // ptr to βlot in line table

// find βlot in the line table nVRetVal a FindSlot( hTaβk, tsp ) ; if (nVRetVal la RC_SUCCEED) goto done; ~ if ( sp->bConnected ) { if ( sp->bOnLine ) {

// βhould hang up here 111 / we are connected to a line; disconnect / cmd.nCommand a DVRCJDISCONNECT; cmd.nLine a sp->nLineNum and.play_itern.type a Pl_NONE; nVRetVal a ipc_Do_Command ( βp->hTaβk, and, tevent ) ;

/♦ ignore return code / βp--bConnected a FALSE; sp-_bOnLine a FALSE; sp->nLineNum a MAX LINES; } nVRetVal a ipc_Shutdown ( sp->hTask ) ; βp->b!nUβe a FALSE; done: return ( nVRetVal ) ;

} / function com_Shutdown /

int com_Answer ( const unsigned int hTask, // taβk handle (Windowβ only) const int num_rings, // anβwer incoming after X rings const int total timeout // seconds

) struct command and; // command to send struct event event; // event received int nVRetVal - RC_INTER_IAL; // Vσyβyβ return value struct line_t sp; // ptr to slot in line table

// find βlot in the line table nVRetVal a FindSlot( hTaβk, tsp ) ; if (nVRetVal I- RC_SUCCEED) goto done; ~ " if ( lβp-.bConnected ) { nVRetVal a RC_NOTCONNECTED; goto done;

if ( βp->bOnLine ) { nVRetVal * RC_CARDOFFH0OK; goto done;

/* * Validate the parameters. */ if ( num_ringβ < 1 ) { nVRetVal a RC_WAITRINGINVALIDNUMRINGS; goto done;

} if ( total_timeout < 0 ) { nVRetVal - RC_WAITRINGINVALIDTIMEOUT; goto done;

cmd.nCommand - DVRC_WAITFORCALL; αnd.nLine a sp->nLineNum ^ - cmd.play..tem.type a Pl_N0NE; and.answer_ringβ a num_rings; cmd.tota__ti_.eout a total_timeout. 1000; // convert to milliseconds nVRetVal a ipc_Do_Command ( sp->hTask, cmd, tevent ) ; if ( nVRetVal «a RC_SUCCEED ) βp->bOnLine a TRUE; done: return ( nVRetVal ) ;

} /♦ function com_Anβwer /

********************************************/ I* Go off-hook, dial number, wait for anβwer ♦/

♦ syntax inside outdialnum iβ:

♦ 0123456789^*ABCD digit to dial

T uβe tone dialing (default)

P use pulse dialing

♦ W wait for dial tone, pause 2 seconds

♦ i flash the switch-hook

*/ ' int com_Callout ( const unsigned int hTaβk, // taβk handle (Windowβ only)

/* ♦ syntax inside outdialnum is:

♦ 0123456789^ _ABCD digit to dial

T use tone dialing (default)

P use pulse dialing

♦ w wait for dial tone pause 2 seconds

1 flash the switch-hook

*/ const char outdialnum, const int num_ _rings // give up after X rings const boolean wait_for_silence, // TRUE if predictive

- dialing ulong voice after answer // noiβe after anβwer (msec)

struct command cmd; // command to send struct event event; // event received int nVRetVal a RC_INTERNAL; // Voyβyβ return value ulong basedialtime; // time (msec) to dial dialβtring const char *p struct line_t *βp; // ptr to βlot in line table

// find βlot in the line table nVRetVal a FindSlot( hTaβk, tsp ) ; if (nVRetVal la RC_SUCCEED) goto done; if ( lβp-..Connected ) { nVRetVal a RC_N0TCONNECTED; goto done;

if ( sp->bOnLine ) { nVRetVal - RC_CARDOFFHOOK; goto done;

if (num rings < 1) { nVRetVal a RC_DIALBADNUMRINGS; goto done;

if ( βtrlen ( outdialnum ) >= MAX DIGITS ) { nVRetVal a RC_BADCOMMAND; // Note: need a new error code goto done; }

/ βee if there are any illegal characterβ in outdialnum ♦/ if ( βtrβpn ( outdialnum, "0123456789#*ABCDTPW, !" ) la strlen ( outdialnum ) ) { nVRetVal a RC_BADCQMMAND; // Note: need a new error code goto done;

basedialtime a BASEDIALTIME_W; p a outdialnum; while ( ^p !« '\0' ) { switch ( ?++ ) { case 'W' : basedialtime +_ BASEDIALTIME_W; break; // wait for dialtone aa 3 sees ? case ' , ' : basedialtime +a BASEDIALTIME_CQMMA; breaϋc; // pause aa 2 " sees default: basedialtime += BASEDIALTIME_DEFAULT; break; // digit a« o. * 2 sees ?

, » cmd.nCommand a DVRC_CALLOUT; cmd.nLine a βp-__ιLineNum; cmd.play_itern.type a PI_NONE; and.anβwer_ringβ a num_ringβ; αnd.wait_for_βilence a-wait_for_βilence; strcpy ( cmd.string, outdialnum-); cmd.total_timeout a baβedialtime + ( 6000L ♦ ( ( long )

(num_ringβ + 1) ) ) ; nVRetVal B ipc_Do_Command ( βp->hTaβk, cmd, tevent ) ; voice_after_anβwer a event.voice after_anβwer; if (nVRetVal >- RC_SUCCEBD) sp->bOnLine a TRUE; // convert return code if (nVRetVal a. RC_BUSYDEST) nVRetVal - RC_DIALGOTBUSYDEST; elβe if (nVRetVal aa RC_NOANSWER) nVRetVal a RC_DIALGOTN0ANSWER; done: return ( nVRetVal ) ;

} / function com_Callout /

/____.___._.___-_._- ********* ._ /♦ Just send digitβ out; no fauicy stuff / int com Dial (

const unsigned int hTask, // task handle (Windows only) const char digits

) βtruct command cmd; // command to βend struct event event; // event received int nVRetVal a RC_INTERNAL; // Voyβyβ return value βtruct line_t sp; // ptr to βlot in line table // find βlot in the line table nVRetVal a FindSlot( hTask, tsp ); if (nVRetVal l« RC_SUCCEED) goto done; if ( !βp--bConnected ) { nVRetVal - RC_NOTCONNECTED; goto done;

} if ( lsp->bOnLine ) { nVRetVal - RC_CARDONH0OK; goto done;

if ( strlen ( digits ) >- MAX_DIGITS ) { nVRetVal a RC_BADCOMMA___; - // Note: need a new error

~ code goto done;

/ see if there are any illegal characters in digitβ /

/♦ note: can not do "W" in thiβ function ♦/ if ( βtrβpn ( digitβ, "0123456789#*ABCDTP, !" ) !« βtrlen ( digitβ ) ) { nVRetVal a RC_BADCOM____); // Note: need a new error code goto done;

} cmd.nCommand a DVRC_SBNDDTMF; αnd.nLine a βp->nLineNum cmd.play_itern.type a PI_NONE; βtrcpy ( and.βtring, digitβ ),- nVRetVal a ipc_Do_Command ( βp->hTaβk, and, tevent ) ; done: return ( nVRetVal ) ; ) /♦ function com_Dial /

/ *************************************/

/*

♦ Get DTMF digits entered by uβer.

*

♦ Doeβ not fluβh digitβ at βtart, βo any digits left around from ♦ a previous operation will be seen.

*/

int com_Get_Toneβ ( const unsigned int hTask, // taβk handle (Windowβ only) const boolean play_playlist, // play playlist ? /*

Values of term_digitβ:

BBO unlimited (limited only by line card) .

>0 limited to term digits digitβ. */ const int term digits, // end op after X keys recvd /*

Values of termjcey:

'?' any digit terminates operation.

' ' no term digit; end by timeout or term_af er_x_keys.

'x' terminate operation if digit 'x' input. - */ const cchhaarr termjcey, // end op if this key recvd const int interdigit_timeout, // seconds const int retries, // if timeout, retry N times char outval // retval -- digits received

βtruct command cmd; // command to send βtruct event event; // event received int nVRetVal a RCJINTERNAL; // Voyβyβ return value int i; char cptr; // used to strip off terminating key βtruct line_t βp; // ptr to βlot in line table ulong term_id_timeout; // interdigit timeout to terminate on in msec

outval « '\0'

// find slot in the line table nVRetVal - FindSlot( hTaβk, tβp ) ; if (nVRetVal la RC_SUCCEBD) goto done; if ( lβp-.bConnected ) { nVRetVal - RC_NOTCONNECTED; goto done; ~

if ( lsp->bOnLine ) { nVRetVal - RC_CARDONHOOK; goto done; -

if ( retries < 0 ) { nVRetVal - RC_BADCOMMAND; goto done;

if ( ( term_digitβ < 0 ) | | ( term_digits > MAX_DIGITS ) ) { Debug ( 1, βprintf ( Debugjbuf, "Cαmpute/ccπ_Get_Toneβ: bad number " of digitβ for termination" ) ) ; nVRetVal a RC_GETTONESBADTERMDIGITS; goto done;

if ( Iβtrchr ( "0123456789^ ", termjcey ) ) {

Debug ( 1, βprintf ( Debug_buf, "Cαmpute/coαι_Get_Toneβ: bad termination key" ) ) ;

nVRetVal » RC_GETTONESBADTERMKEY; goto done;

if ( interdigit_timeout < 0 ) {

Debug ( 1, βprintf ( Debug_buf, "Compute/com_Get_Tones: baid interdigit timeout value" ) ) ; nVRetVal ■ RC_GETTONESBADINTDIGITTIMEOUT; goto done; ~ " }

/ Convert the timeoutβ to milliβecondβ.

*/ term_id_timeout a interdigit_timeout 1000; for ( i a 0; i < retrieβ + 1; i++ ) { if ( playjplayliβt ) { cmd.nCommand a DVRC_PLAY; cmd.nLine a sp->nLineNum and.play_itern.type a Pl_NONE; αnd.flush_digits_at_8tart a FALSE; αnd.end_voice_on-any digit a TRUE; cmd.end-voice-on~digTt_end a FALSE; cmd.end_oper_on_any digit a TRUE; and.end-oper-on-digTt_end a FALSE; and.end-oper_on_voice-end β TRUE; cmd.max_digits a term-digitβ; cmd.end_digit a termjcey; cmd.get_digitβ_at_end a FALSE; and.βtart_timeout~B sp->lGetTonesStartTimeout; αnd.total_timeout a 0; cmd.interdigit_to a term_id_timeout; cmd.end_silence a ( unsigned ) NOT_APPLIC; cmd.total_recording a ( unsigned )-NOT_APPLIC; nVRetVal a ipc_Do_Command ( βp->hTask, and, tevent ) ; if ( nVRetVal < RC_SUCCBED ) break; } emd.nCotranand a DVRC_GETDTMF; αnd.nLine a βp->nLineNum and.play_item.type a PI_NONE; cmd.flu8h_digits_at_start a FALSE; αnd.end_voice_on any digit a FALSE; αnd.end-voice~on_digTt_end a FALSE; αnd.end_oper_on_a ιy digit a ( term_key aa ' ?' ) ; αnd.end_cper_on_digTt_end a ( ( term_key ! a ' ? ' ) tt ( termjcey ! ' ' ) ) ; cmd.end_oper_on_voice_end a FALSE; αnd.max_digits a term_digits; α_d.end_digit a term_key; αnd.get_digits_at_end a TRUE; cmd.Btart_timeout~a sp->lGetTonesStartTimeout; and.total-timeout a 0; an .interdigit_to a term_id_timeout; cmd.end_βilence a ( unsigned ) NOT_APPLIC; αnd.total_recording a ( unsigned )-NOT_APPLIC;

nVRetVal ipc_Do Command ( βp-.hTaβk, cmd, tevent ) ; if ( nVRetVal < RC_SUCCEED ) break; if ( event.string[0] != '\0' ) { /* if βtring ends with termjcey, βtrip it off /

/ thiβ alβo could be done by checking for RCJSNDFLAG ♦/ cptr a event.string; while ( ♦cptr 1- '\0' ) cptr++; cptr--; if ( cptr a= term key )

cptr '\0' ; "" breaϋc,- , ' if ( nVRetVal >- RC_SUCCEED ) strcpy ( outval, event.string ) ,* done: return ( nVRetVal ) ;

} / function com_Get_Tones /

/* Get wordβ spoken by the uβer and voice-recognized. */

/* Values of teπn_after_x_wordβ: aaθ unlimited (limited only by line card) . >0 limited to term after x wordβ words.

_/ - int com_Get_Words ( const unsigned int hTaβk, // taβk handle (Windowβ only) const boolean play_playliβt, // play playlist ? const char βub_vocab name, /*

♦ Values of terπ_after x_wordβ:

aaθ unlimited (limited only by line card) . >0 limited to teπn_after x wordβ wordβ.

*/ const int term_after_x_wordβ, // end op after X wordβ recvd const ulong interword_timeout, // msec const int retrieβ, /•/ if timeout, retry N timeβ char outval // retval -- wordβ received

)

{ βtruct command and; // command to βend βtruct event event; // event received int nVRetVal a RC_INTERNAL; // Voyβyβ return value int i; βtruct line_t βp; // ptr to βlot in line table outval « '\0' ;

// find βlot in the line table

nVRetVal a FindSlot( hTask, tsp ); if (nVRetVal 1« RC_SUCCEED) goto done; if ( Iβp-...Connected ) { nVRetVal a RC_NOTCONNECTED; goto done;

if ( !sp->bOnLine ) { nVRetVal a RC_CARDONHO0K; goto done;

if ( retrieβ < 0 ) { nVRetVal a RC_BADCC______; goto done;

for ( i a 0; i < retrieβ + 1; i++ ) { if ( play playlist ) { cmd.nCommand a DVRC_PLAY; αnd.nLine a βp->nLineNum and.play_itern.type a PI_NONE; cmd.flush_digits_at_start a FALSE; αnd.βnd_ oice_on_any_digit a TRUE; αnd.end_voice on_digit_end a FALSE; and.end_oper_on_any digit a TRUE; αnd.end_oper_on_digTt_end a FALSE; αnd.end_oper_on_voice_βnd a TRUE; αnd.max_digits a l; "" αnd.end_digit a '?'; αnd.get_digits_at_end a FALSE; cmd.βtart_timeout-β 0; σnd.total-timeout 0; cmd.interdigit_to a 0; αnd.end_βilence a ( unsigned ) NOT_APPLIC; cmd.total_recording a ( unsigned ) NOT_APPLIC; nVRetVal a ipc_Do_Command ( βp->hTaβk, and, tevent ) ,- if ( nVRetVal < RC_SUCCEED ) break; ~

cmd.nCommand a DVRC_GETWORDS; cmd.nLine a sp->nLineNum and. lay_itern.type a PI_NONE; αnd.max_digits a term_af er_x_words; αnd.8tart_timβout a βp->lGetWOrdβStartTimeout; cmd.total-timeout a 0; αnd.interdigit_to * interword_timeout; αnd.enάjsilence a ( unsigned T NOT_APPLIC; cmd.total_recording = ( unsigned )~NOTJ_?PLIC; cmd.max_score a 400; cmd.min-ambiguity a 20; αnd.input_gain 0x7000; strcpy ( and.name, βub_voc_b_name ) ; nVRetVal ipc Do_Command ( sp->hTask, cmd, tevent ) ; if ( nVRetVal RC SUCCEED )

goto done; for ( i . 0; i < MAX_WORDS; i++ ) { βtrcat ( outval, event.wordβ[i] ); // βtrcat(outval, " ") ; / βeparate the words with spaces /

} goto done;

} done: return ( nVRetVal ) ;

} / function com_Get_Wordβ /

//****** ***** ****/ int cc_t_Hangup ( conβt unsigned int hTaβk // taβk handle (Windowβ only)

{ βtruct command cmd; // command to send βtruct event event; // event received int nVRetVal a RC_INTERNAL; // Voysys return value struct line_t βp; // ptr to βlot in line table

// find βlot in the line table nVRetVal - FindSlot( hTaβk, tβp ); if (nVRetVal la RC_SUCCEED) goto done; if ( !sp->bConnected ) { nVRetVal a RCJNOTCONNECTED; goto done; " ~

if ( !sp->bOnLine ) { nVRetVal a RC_SUCCEBD; goto done;

cmd.nCommand a DVRC_HANGUP; αnd.nLine a βp->nLineNum and.play_itern.type a Pl_NONE; nVRetVal a ipc_Do_Command ( βp--hTask, cmd, tevent ) ; if ( nVRetVal >» RC_SUCCEED ) sp->bOnLine a FALSE; done: return ( nVRetVal ) ; } / function com_Hangup /

/* ********** ************/ int com_Hangup_Detect ( const unsigned int hTask // taβk handle (Windowβ only)

) βtruct command cmd; // cαmmauid to send βtruct event event; // event received int nVRetVal a RC_INTERNAL; // Voyβyβ return value struct line_t βp; // ptr to βlot in line table

// find βlot in the line table nVRetVal - FindSlot( hTaβk, tsp ); if (nVRetVal != RC_SUCCEED) goto done; if ( !sp->bConnected ) { nVRetVal « RC_NOTCONNECTED; goto done;

} cmd.nCommand a DVRCJ3ETSTATE; cmd.nLine a βp-.nLineNum; and.play_itern.type Pl_NONE; nVRetVal a ipc_Do_Command ( βp->hTaβk, and, tevent ) ; if ( nVRetVal >« RCJSUCCEED ) { if ( event.uβer " ff iook ) nVRetVal - RC_0FFH00K; nVRetVal - RC ONHOOK; } done : return ( nVRetVal ) ;

} / function cαm_Hangup_Detect ♦/

I* int com_Play ( const unβigned int hTask, // taβk handle (Windowβ only) const int call_type, const char voice_file, // one filename, a num, or a date const int play_type, const int interrupt_mode

int nVRetVal a RC_INTERNAL; // Voyβyβ return value βtruct line_t sp; - // ptr to βlot in line table

// find βlot in the line table nVRetVal a FindSlot( hTaβk, tsp ); if (nVRetVal 1= RC_SUCCEED) goto done;

// Note: to βupport speak for online and not online

// βhould not check for bOnLine here and it will be checked

// in the line driver layer of the βerver if ( lsp->bConnected ) { nVRetVal - RC_NOTCONNECTED; goto done;

} if ( ( play_type < AS NUMBER ) | | ( play_type > AS DAY ) ) { nVRetVal « -10; goto done;

} switch ( call_type ) {

LE ) 1> ED ) nVRetVal a play_play_liβt ( βp, interrupt_mode ) ; break; ~ * caβ if ( add_to_play_liβt ( βp, voice_file, play_type ) la

RC_SUCCEED ) nVRetVal - RC_SPEAKPROMPTFILEN0TFOUND; break;

iβt++; t ( βp, voice_file, play_type ) la

RC_SUCCEED ) nVRetVal a RC_SPEAKPROMPTFILENOTFOUND -

βp->nNumltemβlnPlayLiβt; break,-

/*

PLAY_PLAY_LIST no longer adds to the play liβt -

juβt plays the liβt */ case PLAY_PLAY_LIST: nVRetVal a play_play_list ( βp, interrupt_mode ) ; break; default: nVRetVal - -12;

done: return ( nVRetVal ) ;

} /♦ function com_Play /

int com_Record ( const unsigned int hTask, // task handle (Windowβ only) char msgname, // null aa generate name; also retval const boolean play playlist, // play playlist before record ?

/*

♦ Values of termjcey:

'?' any digit terminates operation.

♦ ' ' no term digit; end by timeout or term_after_x_keyβ.

♦ 'x' terminate operation if digit 'x' input. -

*/ const char term ce end o if this ke recvd

// find slot in the line table nVRetVal - FindSlot( hTask, tsp ); if (nVRetVal 1- RC_SUCCEED) goto done;

// Note: to support record for online and not online // βhould not check for bOnLine here and it will be checked // in the line driver layer of the βerver if ( !βp--bConnected ) { nVRetVal RC_N0TCONNECTED; goto done; ~

if ( βtrlen ( mβgname ) >= ( MAX DN + MAX FN ) ) { nVRetVal RC_RECORDINVALIDFfLENAME; goto done;

}

/* Validate the "max recording length" parameter. / - if ( max recording < 1 ) { nVRetVal - RC_RECORDINVALIDRBCLEN; // need to match whatever is defined in the BRS goto done ;

if ( playjplaylist ) { cmd.nCommand a DVRC_PLAY; cmd.nLine a sp- >nLineNum cmd. play_item. type a PI_NONE; and. flush_digitβ_at_B tart a TRUE; cmd.end_vδice_on_any_digit a ( termjcey aa ' ? ' ) ; and. end voice- on_digit_end a ( ( termjcey ! = ' ? ' ) tt ( - termjcey ! « ' ' ) ) ,- αnd.end_oper_on_a_ιy digit a ( termjcey aa ' "" ?' ) ; αnd.end~oper_on digTt_end a ( ( termjcey l a ' ?' ) tt (

- termjcey l a ' ) ) • αnd.end_oper_on_voice_end a TRUE; ~ αnd.max~digits a 8 ; cmd.end_digit a termjcey; cmd.get_digitβ_at_end a FALSE;

and . start_t imeout a 0 cmd. total~t imeout a 0 αnd. interdigit_to a o cmd.end_silence a ( unsigned ) NOT_APPLIC; and . total_recording a ( unsigned ) NOT_APPLIC; nVRetVal a ipc_Do_C_mmand ( sp- >hTask, and, tevent ) ; if ( nVRetVal < RC_SUCCEED ) goto done ;

} cmd.nCommand a DVRC_RECORD ; αnd.nLine βp- > nLineNum; and. path [0] * DIR_USERVOICE ; for ( i « 1 ; i < MAX_DIRS; i++ ) cmd. path [i] a DIR_END; and. play_i tern. type a PI_SINGLE_FILE; βtrcpy (-αnd. play_i tern. f name, mβgname ) ; αnd. flush_digits_at_βtart a TRUE; cmd.end_voice_on_a_ay_digit a ( term_key a, » . » ) cmd.en__voice_on_digit_end a ( ( termjcey 1 - ' ? ' ) tt ( termjcey 1 « ' ' ) ) ; and . end_σper_on_auιy digit ( termjcey a« ' ?' ) ; and . end_oper on_digϊt_end ( ( termjcey ! « ' ? ' ) tt { termjcey 1 ■ ' ' ) ) ; and . end_oper_on_yoice_end ( termjcey aa ' ' ) ; and.m_- _digits a 8 ; αnd.end_digit a termjcey; cm d.get-digitβ_at_end a FALSE ; cmd. start_t imeout a 0 and. total- imeout a 0 αnd. interdigit_to a 0 αnd.end_βilence a βilence_timeout ; cmd . total_recor ding a max_recording ; nVRetVal a ipc_Do_Command ( βp- >hTask, and, tevent ) ; done : msgname [0] a ' \0' ; if ( nVRetVal >■ RC_SUCCEED ) βtrcpy ( mβgname " , event . filename ) ; return ( nVRetVal ) ;

} / function com_Record /

♦if DO FAX

int com_Faxcover ( const unsigned int hTaβk, // task handle (Windows only) const int op_type, // cover page options const void far IpParm // parameter string

) struct command and; // command to send βtruct event event; // event received int nVRetVal a RC_INTERNAL; // Voyβyβ return value

βtruct line_t βp; // ptr to slot in line table

// find βlot in the line table nVRetVal - FindSlot( hTaβk, tβp ); if (nVRetVal 1- RC_SUCCEED) goto done; if ( !βp->bConnected ) { nVRetVal « RC_NOTCONNECTED; goto done;

} αnd.nCαmmand a DVRC_FAXCOVER; αnd.nLine a βp-.nLine um; αnd.nFaxOpCode a op_type; and.nFa__Parm a 0; if ( IpParm )

_fβtrncpy( cmd.cFaxParm, IpParm, MAX FAXPARM ) ; else - αnd.cFaxParm[0] a '\0'; nVRetVal a ipc_Do_Command ( βp->hTaβk, and, tevent ) ; done: return ( nVRetVal ) ;

} /♦ function co__Faxcσver ♦/

I* int com_Faxdoc ( const unsigned int hTask, // taβk handle (Windows only) const int op_type, // document page options const void far IpParm // parameter string ) struct command cmd; // command to send βtruct event event; // event received int nVRetVal RC_IN__RNAL; // Voysys return value

Btruct line_t βp; - // ptr to βlot in line table // find βlot in the line table nVRetVal a FindSlot( hTaβk, tβp ); if (nVRetVal !- RC_SUCCEED) goto done; if ( !βp->bConnected ) { nVRetVal « RC_NOTCONNECTED; goto done;

cmd.nCommand a DVRC_FAXDOC; αnd.nLine a βp--nLineNum,- cmd.nFaxOpCode a σp_type; cmd.nFaxParm a 0; if ( IpParm )

_fβtrncpy( cmd.cFaxParm, IpParm, MAX_FAXPARM ) ; else

αnd. -FaxParm [0] a ' \0' ,- nVRetVal a ipc_Do_Cc_nm___d ( sp- >hTask, cmd, tevent ) ; done : return ( nVRetVal ) ;

} / function com_Faxdoc /

int cc__Faxsend ( const unsigned int hTaβk, II taβk handle (Windowβ only) conβt char far ♦IpDialStr, // the destination fax number const int op_type, // document page options conβt void far IpParm // parameter string

βtruct command and; // cαmmauid to send βtruct event event; // event received int nVRetVal - RC_INTERNAL; // Voyβyβ return value βtruct line_t βp; // ptr to βlot in line table

// find βlot in the line table nVRetVal a FindSlot( hTask, tβp ); if (nVRetVal !« RC_SUCCEED) goto done; if ( !sp--bConnected ) { nVRetVal ■ RC_NOTCONNECTED; goto done;

cmd.nCommand DVRC_FAXSEND; cmd.nLine a βp-.nLineNum; αnd.nFaxOpCode a op_type; cmd.nFaxParπt a 0;

) ; nVRetVal a ipc_Do_Command ( βp->hTaβk, cmd, tevent ) ; done: return ( nVRetVal ) ;

/♦ function com_Faxβend /

I* int com_Faxβetup ( const unsigned int hTask, // taβk handle (Windowβ only) const int op_type, // fax βetup options conβt void far IpParm, // string parameter conβt int nParm // integer parameter

βtruct command and; // command to send struct event event; // event received int nVRetVal a RC_INTERNAL; // Voysys return value βtruct line_t βp; // ptr to βlot in line table

// find βlot in the line table nVRetVal a FindSlot( hTaβk, tβp ); if (nVRetVal !« RC_SUCCEED) goto done; if ( lβp->bConnected ) { nVRetVal a RC_NOTCONNECTED; goto done;

} cmd.nCommand DVRC_FAXSETUP; αnd.nLine a sp->nLineNum cmd.nFaxOpCode a op_type; αnd.nFaxParm a nParm; if ( IpParm )

_fβtrncpy( cmd.cFaxParm, IpParm, MAX_FAXPARM ) ; else cmd.cFaxParmlO] a '\0',- nVRetVal ipc_Do_Cctπmand ( βp-.hTaβk, cmd, tevent ) ; done: return ( nVRetVal ) ;

} /♦ function com_F_xsetup /

♦endif / DOJFAX /

int com_Set ( const " unsigned int hTask, II taβk handle (Windowβ only) conβt int op_typβ, // βet options conβt void far IpParm, // string parameter conβt ulong dwParm // unsigned long parameter

) struct ccmmand and; // command to send struct event event; // event received int nVRetVal a RC_INTERNAL; // Voysyβ return value βtruct line t *βp; ~ // ptr to βlot in line table

// find βlot in the line table nVRetVal a FindSlot( hTaβk, tβp ); if (nVRetVal 1- RC_SUCCEED) goto done; if ( !sp-.bConnected ) { nVRetVal = RC_NOTCONNECTED; goto done;

cmd.nCommand a DVRC_SET; cmd.nLine a sp-.nLineNum;

cmd.nSetGetOpCode a op_type ; cmd.dwSetParm a dwParm; if ( IpParm )

_f βtrcpy ( αnd.cSetParm, IpParm ) ,- else αnd. cSetPaπn [0] a ( char ) NULL; nVRetVal ipc_Do_Command ( sp- >hTaβk, and, tevent ) ; done: return ( nVRetVal ) ;

} / function com_Set /

int hTaβk, // taβk handle (Windowβ only) op_type, // get options IpParm, // βtring parameter dwParm // unsigned long parameter

and; // command to send struct event event; // event received int nVRetVal - RC_INTERNAL; // Voyβyβ return value struct line_t βp; // ptr to βlot in line table

// find βlot in the line table nVRetVal a FindSlot( hTaβk, tβp ) ; if (nVRetVal !« RC_SUCCEED) goto done; if ( lβp->bConnected ) { nVRetVal - RC_NOTCONNECTED; goto done;

cmd.nCommand a DVRC_GET; αnd.nLine a sp-.nLineNum; cmd.nSetGetOpCode a op_type; and.dwSetParm a dwParm; αnd.cSetParm[0] a ( char ) NULL; βwitch ( op_type ) { caβe GET_RBCORDINGS: αnd.dir_num DIR_USERVOICB; break; case GET_TMPFILES: αnd.dir_num a DIR_TEMPFILES; break,* } nVRetVal a ipc_Do_Command ( sp->hTask, cmd, tevent ) ; if ( nVRetVal >» RC_SUCCEED ) { ♦dwParm a event.dwGetParm; if ( event.cGetParm[0] la ( char ) NULL ) _fstrcpy( IpParm, event.cGetParm ) ;

else

IpParm a ( char ) NULL;

done: return ( nVRetVal ) ;

} /♦ function com_Get /

FUNCTION: com ProceββEvent

PURPOSE: Handles all messages received by the client application.

int com_ProcesβEvβnt ( const unsigned int hTaβk, // taβk handle (Windowβ only) conβt βtruct event event // event from above

int nVRetVal; // Voyβyβ return value

// don't even try to find table βlot ! nVRetVal a ipc_ProcesβEvent ( hTaβk, event ) ; return ( nVRetVal ) ;

} / function com_ProceβsEvent ♦/ I*

APPENDIX E

IPC_WD_C . C

Copyright 1992-1994 Vovsv Corporation

♦ File: IPC_WD_C.c

* ♦ Top part of InterProceββ Communication layer for multiple-line

♦ βyβtem. Independent of line card type.

*

♦ Bottom layer of Client Task for multiple-line system using

♦ Windowβ + DDE. Sendβ commands from upper layers to Server Task (via DDE), and returns events to the upper layers.

$Log: /VoyβAccesβ/core/ipc/IPC_WD_C.C $

♦include <βtdio.h> ♦include <βtring.h> ♦include .windows.h> ♦include <windowsx.h> ♦include <dde.h>

♦include "OS.h"

♦include "dVR.h"

♦include "Command.h"

♦include "IPC.h"

I*

/ frequency at which to poll for incoming DDE meββageβ (events)*/ ♦define RESPONSE_POLL_DELAY 200 / msec between polls ♦/

♦define MY_TIMER_ID 1

// The timeout value for client to wait for VoysAccess βerver to // respond with any event.

♦define CLIENT_WAIT_LIMIT 0 // wait forever

/******************* ************************/

/* ♦ Information about state of each active taβk.

*

♦ Thiβ data iβ βhared by all client processes, but each process

♦ looks only at the data for its line.

* ♦ Processes find a free slot by looking at the blnUse field. */ βtruct line_t { BOOL blnUβe; // iβ thiβ table βlot in uβe ?

unsigned int hTask; // Windows task handle

♦define EVBNTQ_SIZE 10 struct event eventq[EVENTQ_SIZE] ; int nBQNextFree,- // next empty slot int nEQNextUnread; // next full (unread) βlot

HWND hwndClientDDE; // hidden client window (in thiβ process)

HWND hwndServerDDB; // hidden server window (in βerver proceβs)

BOOL bConvInTerminateState; BOOL bCWinCreatedlnlPC; // client window created in IPC layer ?

UINT timerlD; char βzCWClaβsName[40] ; // name of client window claβs };

// ptr to array[MAX_AL] of line_t structureβ βtatic - βtruct line_t line;

// has thiβ layer been initialized yet ? βtatic boolean blnitialized a FALSE;

// Find line table βlot that iβ being uβed by taβk handle hTask static int FindSlot ( const unsigned int hTask, // task handle (Windows only) struct line_t ♦♦ pep // ptr to ptr to slot in line table

)

{ int nVRetVal; // Voysyβ return value int nSlot; // slot in line table

// find slot in the line table by uβing hTaβk for (nSlot-0 ; nSlot<MAX_AL ; nSlot++) { if (line[nSlot] .blnUse tt (line[nSlot] .hTaβk a hTaβk) ) break; if (nSlot >- MAX_AL) { nVRetVal - RC_NOTINITIALIZED; // beet gueββ goto done; ~

♦pep a tline[nSlot] ; nVRetVal « RC_SUCCEED; done: return nVRetVal;

♦ wait for_wmeβsage wait for Windowβ message (DDE_ACK ♦ or DDE DATA) >********************************/

static int wait_for wmesβage ( struct line t const sp, // ptr to slot in line table const UINT- wmsgtype , /* ¥_*_ value / const int timelimit, /* msec / MSG theMesg /* meβsage gotten

) unsigned long endtime;

BOOL notquit; int nVRetVal; // Voyβyβ return value

BOOL bSucceββ; int nShuffleCount; βp->timerID a SetTimer ( sp->hwndClientDDE, MY_TIMER_ID,

RBSPONSE_POLL_DELAY, NULL ) ; if (βp->timerID ** 0) { nVRetVal = RC_OSERROR; goto done; } endtime a GetCurrentTime + timelimit; nShuffleCount a 0; while ( GetCurrentTime0 < endtime ) { notquit a GetMeββage ( theMesg,

(HWND)NULL, // any window

0, // all messages

0

) ; if (theMesg->hwnd la βp->hwndClientDDE) // not for us; send to owner { TranβlateMeβsage ( theMeβg ) ; // Translates virtual key codeβ

DiβpatchMeββage ( theMesg ) ; // Dispatches meββage to window continue; if (theMeβg->meβsage aa WM_TIMER) { // ignore; we juβt want thiβ to βend us around the loop again continue; if (theMesg--message !a wmsgtype) {

// not the one we are waiting for; stuff back into queue // dangerous ? reorders events bSuccess a PostMeββage( βp->hwndClientDDE, theMeβg-.-message, theMesg->wParam, theMesg->lParam

); if (!bSuccess)

Debug ( 1, βprintf ( Debug_buf,

"IPC_WD_C/wait_for_wmeββage: lost meββage 0x%04X", theMesg--message ) ) ; if (theMeβg->message a. WM_DDE_TERMINATE) { /♦ remove timer ♦/

KillTimer ( βp->hwndClientDDE, MY_TIMER_ID ); // βerver has gone away sp->hwndServerDDE a (HWND)NULL; nVRetVal a RC SERVERSHUTDOWN;

goto done;

} nShuffleCount++; if (nShuffleCount > 20) { / remove timer /

KillTimer ( sp-_hwndClientDDE, MY TIMER_ID ); nVRetVal - RC_INTERNAL; goto done; continue;

/ }/ got it

/ remove timer /

KillTimer ( βp->hwndClientDDE, MY TIMER_ID ) ; nVRetVal a RC_SUCCEED; goto done;

/♦ remove timer / KillTimer ( βp->hwndClientDDE, MY TIMER_ID ); nVRetVal - RC TOTALTIMBOUT; done: return nVRetVal; }

FUNCTION: ClientReceiveData

PURPOSE: Called when client application receiveβ WM_DDE_DATA meββage. ~ *********************************** _- __._ βtatic int ClientReceiveData ( βtruct line_t ♦ const sp, // ptr to βlot in line table conβt LONG- lParam )

{

DDEDATA FAR lpDDEData;

BOOL bReleaβe;

BOOL bAck; int nVRetVal // Voysys return value boolean bSuccess

HGLOBAL GFRetVal // return value from GlobalFree bAck a TRUE; if ( sp->bConvInTerminateState ) { / Terminate in progreββ: do not receive data / GFRetVal a GlobalFree ( LOWORD ( lParam ) ) ; // hData from Server if (GFRetVal 1- (HGLOBAL)NULL) nVRetVal - RC_INTERNAL; goto done;

} if (! (lpDDEData a (DDEDATA FAR ) GlobalLock (LOWORD(lParam) ) )

|| ( lpDDEData->cfFormat != CFJTEXT ) ) { bSuccess a PostMeββage (

βp->hwndServerDDE, WM_DDE_ACK, βp->hwndClientDDE, MAKELONG ( 0, HIWORD ( lParam ) ) ) ; / Negative ACK / if (IbSucceβs)

Debug ( 1, βprintf ( Debug_buf,

"IPC_WD_C/ClientReceiveData: PoβtMeββage(hwndServerDDE 0x%04X) failed", sp->hwndServerDDE ) );

}

♦if 0

/♦ check for correct length / if ( mlng la sizeof ( struct event ) ) { sprintf ( info,

"got msg with bad length %d (should be %d) ", mlng, sizeof ( βtruct command )

) ; if ( logging_on ) print_info (

-1, / not associated with particular line number ♦/ info, / info to print / logfile / file to print to /

) ; if ( tracing_on ) print_info (

-i~, / not associated with particular line number / info, / info to print / tracefile / file to print to / ) ; bSucceββ a PostMeββage ( βp-.hwndServerDDE, WM_DDE_ACK, βp->hwndClientDDE, MAKELONG ( 0, 0 )

) ; / Negative ACK / if (1bSucceββ) nVRetVal ■ RC_INTERNAL; /♦ Pos-Message() failed / goto done;

} ♦endif

/♦ do something with data received / / put event in queue to be sent up / sp->eventq[sp->nEQNextFree] a

♦( ( struct event ♦ ) ( lpDDEData->Value ) ) ; sp->nEQNextFree++; if ( sp-> EQNextFree >= EVENTQ_SIZE ) sp->nE NextFree a 0; if ( lpDDEData->fAckReq ) { /♦ return ACK or NACK / bSuccess PostMeββage ( sp--hwndServerDDE,

WM_DDE_ACK, sp->hwndClientDDE,

MAKELONG ( ( bAck ? 0x8000 : 0 ) , 0 )

) ; if (lbSuccesβ) Debug ( 1, βprintf ( Debugjauf,

"IPC_WD C/ClientReceiveData: PoβtMeββage(hwndServerDDE 0x%04X) failed", βp->hwndServerDDE ) ); bReleaβe a lpDDEData->fRelease lpDDEData a NULL;

GlobalUnlock ( LOWORD ( lParam ) ) ; if ( bReleaβe ) {

GFRetVal a GlobalFree ( LOWORD ( lParam ) ) ; // hData from Server if (GFRetVal la (HGLOBAL)NULL)

Debug ( 1, βprintf ( Debugjbuf,

"IPC WD-C/ClientReceiveData: GlobalFree(0x%04X) failed", LOWORD ( lParam ) ) ) ;

} nVRetVal » RC_SUCCEED; done: return nVRetVal;

)

FUNCTION: ClientTerminate

PURPOSE: Called when client application receiveβ WM_DDE_TERMINATE meββage.

7 βtatic int ClientTerminate ( βtruct line_t conβt βp // ptr to βlot in line table

) int nVRetVal; // Voyβyβ return value boolean bSuccess; if ( (Iβp->bConvInTerminateState) tt (sp-.hwndServerDDE la (HWND) ULL) ) { /♦ Server haβ recrueβted terminate: respond with terminate / bSucceββ a PoβtMeββage ( βp-.hwndServerDDE,

WM_DDE_TERMINATE, βp->hwndClientDDE, 0L ); if (!bSuccess)

Debug ( 1, βprintf ( Debugjauf,

"IPC_WD_C/ClientTeπninate: PoβtMeββage(hwndServerDDE 0x%04X) failed", βp->hwndServerDDB ) ); sp->hwndServerDDE a (HWND)NULL;

nVRetVal a RC_SUCCEED ;

//done: return nVRetVal; }

get meββage _*____* - **** / βtatic int get_meββage ( βtruct line_t conβt βp, // ptr to βlot in line table βtruct event event / event received ♦/

)

{

MSG mesg; int nVRetVal; // Voyβyβ return value int limit; // prevent infinite waiting

/ wait until there iβ an event in the queue / limit a CLIENT_WAIT_LIMIT; while ( βp->nEQNextUnread aa βp->nEQNextFree ) { / handle any DDE_DATA meββageβ coming up from βerver ♦/ nVRetVal a wait_for_wmeββage ( βp, WM_DDE_DATA, 10000, tmeβg )

// forever )

/ get event from queue /

♦event a βp->eventq[sp->nEQNextUnread] ; sp->nEQNextUnread++; if ( sp->nEQNextUnread >= EVENTQ_SIZE ) sp->nEQ_extUnread a 0; nVRetVal a RC SUCCEED; done: return ( nVRetVal ) ; }

FUNCTION: SendPoke

PURPOSE: Send poke message to βerver.

Tricky: We don't care about the "item name", βo we will violate DDB protocol by sending 0 for the name.

βtatic int SendPoke (

// ptr to βlot in line table

// Voyβyβ return value

// return value from GlobalFree if ( sp--hwndServerDDE aa (HWND)NULL ) { nVRetVal - RC_SERVERSHDTDOWN; goto done; }

/♦ Allocate βize of DDE data header, pluβ the data / hPokeData a GlobalAlloc ( ( GMBM MOVEABLE | GMEM_DDESHARB ) ,

( LONG ) sizeof ( DDEPOKE ) + ItemSize ) ; if ( hPokeData — (HANDLE)NULL ) { nVRetVal a RC_RAMFULL; goto done; ""

} lpPokeData - ( DDEPOKE FAR ) GlobalLock hPokeData ) if ( lpPokeData » (DDEPOKE FAR )NULL ) {

GFRetVal a GlobalFree ( hPokeData ) ; if (GFRetVal la (HGLOBAL)NULL) nVRetVal a RCJDSERROR; goto done; } lpPokeData->fRelease a TRUE;/* deβtination βhould free mem / lpPokeData->cfFormat a CFJTEXT; memcpy ( lpPokeData--Value, ItemValue, ItemSize ) ; lpPokeData a NULL; GlobalUnlock ( hPokeData ) ; bSuccesβ a PoβtMeββage ( βp->hwndServerDDE, WM_DDE_POKE, βp--hwndClientDDE, MAKELONG ( hPokeData, 0 ) ); if (1bSucceββ) {

Debug ( 1, βprintf ( Debugjbuf, .IPC_WD_C/SendPoke:

PoβtMeββage (hwndServerDDE 0x%04X) failed" , βp-.hwndServerDDE ) ) ; GFRetVal GlobalFree ( hPokeData ) ; if (GFRetVal la (HGLOBAL)NULL)

Debug ( 1, βprintf ( Debug buf, "IPC WD_C/SendPoke:

GlobalFree(0x%081X) failed", hPokeData ) ) ;

l a RC_SERVERCOMMFAIL; l a wait_for_wmesβage ( βp, WM_DDE_ACK, 10000,

- - tmeβg ) ; if ( nVRetVal !« RC_SUCCEED ) nVRetVal - RC SERVERCOMMFAIL; ) done: return nVRetVal;

}

/**************** ********

♦ βend_meββage

** ****** * ******* ****/ static int βend_message ( βtruct line-t conβt βp, // ptr to βlot in line table const struct command and / command to send / ) int retval; retval a SendPoke ( ,

//done: return ( retval ) ; }

FUNCTION: SendTeπninate

PURPOSE: Send terminate meββage to βerver.

This iβ called when client decideβ to terminate conversation.

βtatic int SendTerminate ( βtruct line_t conβt βp •// ptr to βlot in line table

)

{ boolean bSucceββ; βp->bConvInTerminateState a TRUE; if (βp--hwndServerDDE la (HWND)NULL) { bSucceββ a PoβtMeββage ( βp->hwndServerDDE, WM_DDEJ__-___KA__, βp•-hwndClientDDE, 0L

) ; if ( IbSucceβs)

Debug ( 1, βprintf ( Debug_buf,

"IPC_WD_C/SendTerminate: PoβtMeββage(hwndServerDDE 0x%04X) failed", βp->hwndServβrDDE ) ) ; βp->hwndServerDDE a (HWND)NULL; // there will not be am acknowledge from βerver } return RC_SUCCEED;

FUNCTION: DDEWndProc PURPOSE: Handleβ all DDE meββageβ received by the client application. Tricky: βhould be virtually identical to ipc_ProceββEvβnt. This function is uβed if client DDE window was created by this layer; ipc_ProcessEvent is used if window created by higher level.

7 long FAR PASCAL export DDEWndProc (

HWND hwnd, UINT message,

WPARAM wParam, LPARAM lParam ) int nSlot; // slot in line table struct line_t βp; // ptr to βlot in line table

// find βlot in the line table by uβing hwnd for (nSlot-0 ; nSlot<MAX_AL ; nSlot++) { if (line[nSlot] .blnUβe tt (line[nSlot] .hwndClientDDE -. hwnd) ) break; i }f (nSlot >- MAX VL) { Debug ( 5, βprintf ( Debugjbuf, "IPC_WD_C/DDEWndProc: couldn't find βlot in line table" ) ),- return ( DefWindowProc ( hwnd, meββage, wParam, lParam )

) ; } sp a tline[nSlot] ; switch ( message ) { case WM_DDE ACK: / shouldn't happen / return . 0L ) ; case WM_TIMER: return ( 0L ) ; case WM_DDE_DATA:

ClientReceiveData ( βp, lParam ) ; return ( 0L ) ;

caβe WM_DDE_TBRMINATE:

ClientTerminate ( βp ) ; return ( 0L ) ,- default: return ( DefWindowProc ( hwnd, message, wParam, lParam

FUNCTION: ipc ProcessEvent

PURPOSE: Handles all async DDE meβsages received by the client application. Tricky: βhould be virtually identical to DDEWndProc. Thiβ function iβ uβed if client DDE window waβ created by higher level; DDEWndProc iβ uβed if window waβ created by thiβ layer.

********************** ***************************** int ipc_ProceββEvent ( conβt unsigned int hTaβk, // taβk handle (Windowβ only) conβt βtruct event event // event from above )

{ int nVRetVal; // Voyβyβ return value UINT meββage; // component of Windowβ meββage

WPARAM wParam; // component of Windowβ meββage

LPARAM lParam; // component of Windows message struct line_t sp; // ptr to slot in line table nVRetVal - RC_SUCCEED; if ( event.nEvent la EV_DDE ) { nVRetVal RC_INTERNAL; goto done; }

// find slot in the line table nVRetVal - FindSlot( hTaβk, tβp ) ; if (nVRetVal la RCJSUCCEBD) goto done;

// extract fields from VA form to Windows form meβsage a ( UINT ) ( event.dde[0] ); wParam . ( WPARAM ) ( event.dde[1] ); lParam a ( LPARAM ) ( event.dde[2] ); βwi

ClientReceiveData ( βp, lParam ) ; goto done; caβe WM_DDE_T_RMINA.__:

ClientTerminate ( sp ) ; goto done;

default : nVRetVal - RC_INT_R___L; goto done;

} done: return ( nVRetVal ) ;

}

FUNCTION: Sendlnitiate

PURPOSE: Sendβ initiate meββage to all windowβ. By the time thiβ function returns, all servers matching the app/tqpic will have acknowledged, and thiβ client application will have temporarily registered the new converβationβ. If more than one βerver reβponded, then thiβ client application asks the uβer which converβation to keep; all other converβationβ will then be terminated. Thiβ function returns the handle of the hidden DDE window uβed to initiate the converβation with βerver(β) . Note: Deletes the atoms it creates and the ones βent to it. ************************************************************ ***/ βtatic HWND Sendlnitiate ( βtruct line_t conβt βp, // ptr to βlot in line table conβt char- ♦βzApplication, conβt char βzTopic ) {

ATOM atcαiApplicatiαn; ATOM atomTopic; int nVRetVal; // Voyeye return value MSG meβg; ATOM GDARetVal; // GlobalDelβteAtom return value nVRetVal - RC_SUCCEED; atoαiApplication a GlobalAddAtom ( ( LPSTR ) szApplication ) ; if (atomApplication aa 0) { nVRetVal « RC_OSBRROR; goto done; } atomTopic a GlobalAddAtom ( ( LPSTR ) szTopic ) ; if (atomTopic aa 0) {

Debug ( 1, βprintf ( Debug_buf, "IPC_WD_C/Sendlnitiate:

GlobalAddAtom failed" ) ) ; GDARetVal a GlobalDeleteAtα ( atomApplication ) ; if (GDARetVal 1« 0)

Debug ( 1,βprintf ( Debug buf, "IPC_WD C/Sendlnitiate:

GlobalDeleteAtαm(Ox_C_X) failed", atomApplication ) ) ; nVRetVal - RC_OSERROR; goto done;

SendMeββage(

( HWND ) - 1, WM_DDE_INITIATE, βp->hwndClientDDE, MAKELONG ( atomApplication, atomTopic )

) ;

GDARetVal a GlobalDeleteAtom ( atomApplication ) ; if (GDARetVal 1- 0) Debug ( 1, βprintf ( Debug buf, "IPC_WD_C/Sendlnitiate:

GlobalDeleteAtαm(0x%04X) failed", atomApplication ) ) ; GDARetVal a GlobalDeleteAtαm ( atomTopic ) ; if (GDARetVal I- 0) Debug ( 1, βprintf ( Debugjbuf, "IPC_WD_C/Sendlnitiate:

GlobalDeleteAtom(0x%04X) failed", atomTopic ) ) ; nVRetVal a wait_for_wmeββage ( βp, WM_DDE_ACK, 10000, tmeβg ) ; if ( nVRetVal 1- RC_SUCCEED ) { nVRetVal a RC_NOSERVER; goto done;

βp->hwndServerDDB a meeg.wParam;

// delete atαmApplicationReturn and atomTopicRetura from Server GDARetVal a GlobalDeleteAtαm ( LOWORD ( meβg.lParam ) ); if (GDARetVal 1- 0) Debug ( 1, sprintf ( Debug_buf, "IPC_WD_C/Sendlnitiate:

GlobalDeleteAtom(0x%04X) failed", LOWORD ( meβg.lParam ) ) ) ; GDARetVal a GlobalDeleteAtom ( HIWORD ( meβg.lParam ) ) ; if (GDARetVal 1- 0) Debug ( 1, βprintf ( Debugjbuf, "IPC_WD_C/Sendlnitiate:

GlobalDeleteAtom(0x%04X) failed", HIWORD ( meβg.lParam ) ) ); done: return ( nVRetVal ) ;

7 int ipc_Initialize ( const unsigned int hTaβk, // taβk handle (Windowβ only) conβt unβigned int hinβtance, // inβtance handle

(Windowβ only) conβt unsigned long hWnd // hidden window handle

(Windowβ only)

)

{

// Voyβyβ return value

// βlot in line table βtruct l ne_t βp; // ptr to βlot in line table nVRetVal a RC_SUCCEED;

if (Iblnitialized) { line (void ) GlobalAllocPtr(

GMEM_MOVEABLE | GMEM ZEROINIT, (MAX AL βizeof(βtrύct line t) ) ); - if ( line « NULL ) { nVRetVal - RC_RAMFULL; goto done; ~ blnitialized a TRUE;

}

// find βlot in the line table nVRetVal a FindSlot( hTaβk, tβp ); if (nVRetVal «- RC SUCCEED) { nVRetVal - RC_ALREADYINITIALIZED; goto done; } // find a free βlot in the line table for (nSlotaO ; nSlot<MAX AL ; nSlot++) { if (lline[nSlot] .blnϋβe) break,- if (nSlot >- MAX_AL) {

Debug ( 5, βprintf ( Debug buf, "IPC WD C/ipc Initialize: line " table " iβ full" ) ); nVRetVal - RC_SWLIMIT; goto done; } βp a tline[nSlot] ; βp->bInUβe a TRUE; βp->hTaβk hTask; if ( hWnd 1- ( long ) NULL ) {

// client window was created up in Database Interface layer βp->hwndClientDDB a ( HWND ) hWnd; sp->bCWinCreatedInIPC a FALSE;

} else { // we have to create client window in this layer wc.style a 0; wc.lpfnWndProc a DDEWndProc; wc.cbClβBxtra a 0; wc.cbWndBxtra a 0; wc.hinβtance a hinβtance; wc.hlcon - ( HICON ) NULL; wc.hCurβor ( HCURSOR ) NULL; wc.hbrBackground a ( HBRUSH ) NULL; wc.lpβzMenuName a NULL; βprintf ( sp->βzCWClaββName, "ClientDDEWndClasstru",

GetCurrentTask ( ) ) ; wc.lpβzClaββName a Bp->βzCWClaββName; wrval a RegisterClasβ ( twc ) ; if ( wrval — 0 ) { sp->bInUse a FALSE;

nVRetVal - RC_OSERROR; goto done;

} sp->hwndClientDDE a CreateWindow ( βp->βzCWClaββName,

"ClientDDE", 0, / not viβible /

0, 0, 0, 0, / no poβition or dimensions ♦/ ( HWND ) NULL, / no parent ♦/ ( HMENU ) NULL, / no menu ♦/

( HINSTANCE ) hlnstance, NULL ); if ( Iβp-.hwndClientDDE ) { Debug ( 1, sprintf ( Debugjbuf,

*IPC_WD_C/ipc_Initialize: CreatewTndow failed hwndCliβnt 0x%04X", βp->hwndClientDDE ) ); sp->bInUse a FALSE; nVRetVal - RC_INTERNAL; goto done;

βp->bCWinCreatedlnlPC a TRUE;

nVRetVal a Sendlnitiate ( , done: return ( nVRetVal ) ; } *_,________________________________--________.__**- int ipc_Shutdown ( conβt unβigned int hTaβk // taβk handle (Windowβ only)

) int nVRetVal; // Voyβyβ return value int wrval; int nSlot; // βlot in line table βtruct line_t βp; // ptr to βlot in line table if (Iblnitialized) { nVRetVal - RCJTOTINITIALIZED; goto done;

// find βlot in the line table nVRetVal a FindSlot( hTaβk, tβp ) ; . if (nVRetVal !» RC_SUCCEED) goto done; nVRetVal a SendTerminate(βp) ; if ( 8p->bCWinCreatedInIPC ) {

// client window waβ created in thiβ layer wrval a DeβtroyWindow ( sp->hwndClientDDE ) ; wrval a UnregiβterClaββ ( sp->szCWClassName, βp->hTaβk ) ;

} sp->nEQNextFree a 0; βp->nBQNextUnread a 0; sp->hwndClientDDE a (HWND)NULL,• βp->hwndSβrverDDE a (HWND) ULL; 8p->bConvInTerminateState a FALSE; sp->timerID a 0; sp->bInUβe a FALSE; // if there are no more used slots, really shut down for (nSlot-0 ; nSlot<MAX AL ; nSlot++) { if (linetnSlot] .blnϋβe) goto done; GlobalFreePtr ( line ) ; line NULL; blnitialized a TRUE; done: return ( nVRetVal ) ;

}

int ipc_Do_Command ( const " unsigned int hTaβk, // task handle (Windows only) const βtruct command and, // command to execute βtruct event event // rββult of command

) int nVRetVal; // Voyβyβ return value βtruct line_t βp; // ptr to βlot in line table

♦if 1N0_DEBUGGING_AT_ALL if Debug_flag >a 2 ) print_cceπmand ( and, debugfile ) ; ♦endif if (!blnitialized) { nVRetVal - RCJTOTINITIALIZED; goto done; ~

event->nBvent a EV_NONE; event->nLine a αnd.nLine; event->C_error a 0; event->DV_error a 0; event--DOS error a 0; event-, string[0] a '\0'; event->filename[0] a '\0';

// find slot in the line table nVRetVal a FindSlot( hTask, tsp ); if (nVRetVal la RC_SUCCEED) goto done,-

nVRetVal a send_meβsage ( βp, and ) ; if { nVRetVal < RC_SUCCEED ) { event--reβult a nVRetVal; goto done; }

/ wait for event back / nVRetVal a get_meβsage ( βp, event ) ; if ( nVRetVal < RC_SUCCEED ) { event--result nVRetVal; goto done; } done:

♦if 1N0_DEBUGGING AT_ALL if ( Debug_flag >« 2 ) print_event ( event, debugfile ) ; ♦endif return ( event->result ) ;

.

APPENDIX F

IPC_WD_S . C

Copyright 1992 -1994 Vovsvs Corporation

♦ Name: IPC_WD_S.c

-

♦ Description:

♦ Server Task for multiple-line system using Windows + DDE.

♦ Accepts commands from Client Tasks (sent via DDE) ,

♦ calls Driver Interface layer to execute commands on phone

♦ lines, and returns events to the Line Tasks (via DDE) .

*

♦ Usage:

♦ IPC_WD_S [ /debugN ] [ /ldhiX ] [ /IdsiXX ]

*

$Log: /VoyβAcceββ/core/ipc/IPC_WD_S.C $

♦include <ctype.h> ♦include <dos.h> ♦include <fcntl.h> ♦include <io.h> ♦include <βyβ\βtat.h> ♦include ♦include ♦include

♦include -windows.h> ♦include <dde.h> ♦include mmsyβtem.h>

♦define IN MAIN

♦include "OS.h"

♦include "dVR.h"

♦include "Command.h"

♦include "IPC.h"

♦include "TM.h"

♦include "srv wrc.h"

I* 7

/♦ default interrupt level and number for line driver card ♦/ ♦define DEF_LD_HW_IRQ 3 ♦define DBF LD SW INT 0x62

/* frequency at which to poll for events from below ♦/ /♦ only polls on lines that have a command in progress ♦/ ♦define EV_POLL_DELAY 400 / msec between polls /

7

/♦ interrupt level and number for line driver card / βtatic int ld_hw_irq DEF_LD_HW_IRQ; βtatic int ld~βw_int a DEF_LD_SW_INT;

/ filename extensions / βtatic char promptext[4] /* extension for prompt fileβ ♦/ βtatic char ifileext[4] ; / extenβion for indexed prompt fileβ / βtruct line_t { βtruct command and; // command boolean bCmdlnProgreββ; // iβ there a cαmmamd in progress? iβ there a taβk attached to line? βerver window handle for line client window handle for line iβ the test mode on for this line?

/ stuff set from dvrc_βetdir commands / / dire voice fileβ are in / / tricky: theβe nameβ have 'V on the end */ char dirname[MAX DIRS] [MAX DN+1] ; }; βtatic βtruct line_t line[MAX_AL] ; / βtuff uβed to generate unique uβer file nameβ / βtatic int next_uβer_filename;

♦define USER_FNAME_FORMAT "V%07d"

♦define WINMAIN_TIMER_ID 1

♦define SERVER_TITLB "voysAccess Server" βtatic boolean bShuttingDown a FALSE;

/ variables uβed by all functionβ in thiβ file / / they are βet to point to current line[] */ βtatic βtruct line_t Λ lp a 0; /* ptr to "line!]" structure /

/♦**** ********************

II DDE converβation βtuff

/♦ Maximum values ♦/ ♦define APP MAX_SIZE 18

♦define TOPIC MAX_SIZE 11

♦define ITEM_NAME_MAX_SIZE 8

♦define ITEM_VALUE_MAX_SIZE 8

♦define CONV MAX COUNT MAX AL ~ " typedef struct CONV {

HWND hwndServerDDE; HWND hWndClientDDE; BOOL blnClientRequestedTerminate;

}

βtatic βtruct CONV Conv[CONV_MAX_COUNT] ; βtatic int nConvCount a 0;

♦define DBF ACK TIME OOT MILLISSC 10000 ♦define PEEK MSG COUNT ~ 200

parβe_argumentB parse command- ine

βtatic int parβe_argumentβ ( conβt char *αndline ) { boolean bad argβ; conβt char *p; ~ bad_argβ a FALSE; p αndline; while ( *p la » \0' )

{ switch ( *p ) p++; if ( βtrnicmp ( p, "debug", 5 ) _a o

{ βingle digit, 0-9 / 5 ) ) )

+ 5 ) ) - '0' ) ;

p +a 6;

Debug ( 1, βprintf ( Debug_buf,

"IPC_WD_S/parβe_argumentβ: Debug_flag %d", Debug_flag ) ) ; ) elβe if ( βtrniαnp ( p, "ldhi", 4 ) aa o ) t βingle digit, 0-9 */ + 4 ) ) )

+ 4 ) ) - '0' ); } elβe if ( βtrniαπp ( p, "ldβi", 4 ) aa o )

( ( ( p + 5 ) ) - '0' ) ;

P++; brea ; default:

, if ( bad_argβ )

return 0;

} / function parβe_arguments /

void ShowTeβtMode ( void

HDC hDC ; char βzBuffer [30] ; βhort nY ; int nLine ; hDC a GetDC ( hWndMain ) ;

SelectObject ( hDC, GetStockObject ( SYST_M_FIXED_FONT ) ) ; for ( nLine a 0 ; nLine < MAX AL; nLine++ )

{ if ( line [nLine] .bTeβtMode )

{ nY a nLine nCharH;

TextOut ( hDC, 0, nY, βzBuffer, wβprintf ( βzBuffer, "Line _%c iβ in Test Mode.", ( (char) ( (int) '0' - nLine ) ) ) ) ;

}

ReleaβeDC ( hWndMain, hDC ) ; // ValidateRect ( hWndMain, NULL ) ;

} / function ShowTeβtMode /

FUNCTION : SendData

PURPOSE: Send data to client.

Tricky: We don't care about the "item name", βo we will violate DDE protocol by sending 0 for the name.

7 static int SendData ( conβt HWND hwndServerDDE, conβt HWND hWndClientDDE, conβt void ItemValue, conβt int ItemSize, conβt BOOL bAckRequeβt, const BOOL bRequeβtData

)

HANDLE hData; DDEDATA FAR lpData; int nVRetVal; // Voysys return value boolean bSuccess; HGLOBAL GFRetVal; // return value from GlobalFree

/♦ Allocate size of DDE data header, plus the data. /

hData a GlobalAlloc ( GMEM_M VEABLE | GMEM_DDESHARE,

( LONG ) sizeof ( DDEDATA ) + ItemSize ) ; if ( hData aa (HANDLE)NULL ) { nVRetVal « RC_RAMFULL; goto done;

} lpData « ( DDEDATA FAR ) GlobalLock ( hData ) ; if ( lpData aa NULL ) {

Debug ( 1, sprintf ( Debug_buf, "IPC_WD_S/SendData: GlobalLock failed" ) ) ;

GFRetVal - GlobalFree ( hData ) ; if (GFRetVal la (HGLOBAL)NULL)

Debug { 1, sprintf ( Debug_buf, "IPC_WD_S/SendData:

GlobalFree(0x*04X) failed", hData ) ); nVRetVal a RC_OSERROR; goto done; } lpData--fAckReq a hAckRequeβt; lpData->cfFormat a CF_TEXT; lpData->fResponse a bRequestData; lpData->f elease a TRUE; / client releases memory ♦/ memcpy ( lpData->Value, ItemValue, ItemSize ) ; lpData a NULL; GlobalUnlock ( hData ) ; bSucceββ a PoβtMeββage ( hWndClientDDE, WM_DDE_DATA, hwndServerDDE, MAKELONG ( hData, 0 )

); if (1bSucceββ) {

/ poβtmeββage failed, βo we have to deallocate data ♦/ GFRetVal « GlobalFree ( hData ) ; if (GFRetVal != (HGLOBAL)NULL)

Debug ( 1, βprintf ( Debugjbuf, "IPC_WD_S/SendData:

GlobalFree(0x_04X) failed", hData ) ); nVRetVal a RC_0SERR0R; / ! ! ! / goto done; }

/ client will deallocate data / nVRetVal « RC_SUCCEED; done: return nVRetVal;

) /♦ function SendData /

FUNCTION: AtLeaβtOneConvActive

PURPOSE: Uβed during termination of application, to determine whether any conversations are still active while the conversations are being terminated. ********************************* * ********************* / βtatic

BOOL AtLeaβtOneConvActive ( void ) return ( nConvCount ? TRUE : FALSE ) ;

} /♦ function AtLeastOneConvActive /

FUNCTION: FindConv PURPOSE: Find the conversation for a βpecified βerver DDE window.

7 βtatic βtruct CONV NEAR FindConv ( const HWND hWndServerDDB ) struct CONV pConv; int nConvIndex; for ( nCon Index a 0, pConv a Conv; nConvIndex < nConvCount; nConvIndex++, pConv++ ) if ( pCc_v-.hwndServerDDE aa hWndServerDDB ) return ( pConv ) ; return ( NULL ) ;

} /♦ function FindConv /

FUNCTION: GetNextConv

PURPOSE: Get next client in liβt of converβationβ. To get the firβt hWndServerDDB in the converβation list, pass in a NULL value for hWndServerDDB.

7 static

HWND GetNextConv ( const HWND hWndServerDDB ) struct CONV pConv; int nConvIndex; if ( hWndServerDDB ) for ( nConvIndex a o, pConv a Conv; nConvIndex < nConvCount; nConvIndex++, pConv++ )

{ if ( pConv->hwndServerDDE aa hWndServerDDB ) if ( ++nConvIndex < nConvCount ) return ( ++pConv ) ->hWndServerDDB; else return ( ( HWND ) NULL ) ;

return ( ( HWND ) NULL ) ;

} if ( nConvCount > 0 ) return ( Conv[0] .hWndServerDDB ); else return ( ( HWND ) NULL ) ;

} /♦ function GetNextConv ♦/

/'

FUNCTION: AddConv PURPOSE: Register a new conversation with a client window * * -____--_/ static BOOL AddConv ( const HWND hWndServerDDB, const HWND hWndClientDDE ) struct CONV pConv; if ( nConvCount >» CONV_MAX_CODNT ) { return ( FALSE ) ; } if ( FindConv ( hWndServerDDB ) !» NULL ) { return ( FALSE ) ; / conversation already added /

pConv a Conv + nConvCount++; pConv->hWndServerDDE a hWndServerDDB,- pConv->hWndClientDDE a hWndClientDDE,- pConv->bInClientRequestedTerminate a FALSE; return ( TRUE ) ;

} /* function AddConv ♦/

FUNCTION: GetHwndClientDDE PURPOSE: Get the hWnd of the client in conversation with a specified server DDE window.

βtatic

HWND GetHwndClientDDE ( conβt HWND hWndServerDDB

)

{ βtruct CONV pConv; if ( ! ( pConv = FindConv ( hWndServerDDB ) ) ) return ( ( HWND ) NULL ) ; return { pConv-.hWndClientDDE ) ;

} /* function GetHwndClientDDE /

/** * FUNCTION: IβConvInTerminateState

PURPOSE: Terminate whether converβation with βpecified client iβ in process of being terminated.

βtatic

BOOL IβConvInTerminateState ( const HWND hwndServerDDE

struct CONV pConv; if ( pConv a FindConv ( hwndServerDDE ) ) return ( pConv->bInClientRequestedTerminate ) return ( FALSE ) ;

} /♦ function IβConvInTerminateState /

/ *********** __.-_ FUNCTION: RemoveConv

PURPOSE: Remove converβation from converβation liβt.

βtatic void RemoveConv ( conβt HWND hwndServerDDE ) { βtruct CONV pConv; int nConvIndex; for ( nConvIndex * 0, pConv a Conv; nConvIndex < nConvCount; nConvIndex++, pConv++ )

{

if ( pConv->hWndServerDDE aa hWndServerDDB ) break,-

} nConvCount--; while ( nConvIndex < nConvCount )

{ ♦pConv a ( pConv + 1 ) ; nConvIndex++; pConv++; } return,-

} /♦ function RemoveConv ♦/

/************** * * FUNCTION: SetConvlnTerminateState

PURPOSE: Set conversations's terminate βtate to TRUE. ** * / βtatic void SetConvlnTerminateState ( conβt HWND hWndServerDDB

) { βtruct CONV pConv; if ( pConv a FindConv ( hWndServerDDB ) ) pConv->bInClientRequeβtedTerminate a TRUE; return;

} / function SetConvlnTerminateState /

I*

FUNCTION: InitAckTimeOut PURPOSE: Get DDE timeout value from win.ini. Value is in milliseconds.

7 βtatic void InitAckTimeOut ( void ) {

/♦ Finds value in win.ini βection corresponding to application name / xiAckTimeOut GetPrivateProfilelnt ( "voyβAccess Server",

"DDETimeOut", DBF ACK TIME OUT MILLISEC,

♦if defined(TYIN4000)

"voyervt.ini" // may need to use different name for different βerver

"voyervd.ini" // may need to use different name for different server ♦endif

); return;

} /* function InitAckTimeOut /

/**************** * ************************* * ** * ******

♦ βend_event

♦ Note: deliberately don't check lined .bConnected, becauβe if we

♦ juβt had a DVRC_DISCONNECT, it will be βet to FALSE even though ♦ all of the other lined info iβ still valid. **** / βtatic void βend_event ( conβt βtruct event event, / event to βend / int nVRetVal / return value; 0 aa okay

*/ )

{ if ( Debug_flag >- 2 ) print_event ( event, debugfile ) ;

/♦ βend the reβulting message to the other taβk / if (line[event.nLine] .βerverwhd aa (HWND) ULL) { ♦nVRetVal - RC_SERVERCOMMFAIL; // could uβe better error code here goto done;

)

♦nVRetVal a SendData ( line[event.nLine] .βerverwnd, line[event.nLine] .clientwnd, tevent, βizeof ( event ) , TRUE, /♦ request an ACK ♦/ FALSE / client did not request this data /

) ; done:

} /♦ function send_event /

/ * -------- *

* do_polling - call line driver to have it check for events. * * * ** ** • / static void dojpolling ( void ) βtruct event ev; // outgoing event allocation

int nVRetVal; // Voysys return value int nLine; for ( nLine a 0; nLine < MAX AL; nLine++ ) { if ( line [nLine] .bCmdlnProgreβs )

{ nVRetVal a tm_Do_Cαπ___ιd (

"~ line[nLine] .and, FALSE, / not start of command ♦/ tev

) ; if ( nVRetVal !- RC_CMDN0TD0NE ) { / send event back to Client / βend_event ( ev, tnVRetVal ) ; line [nLine] .bCmdlnProgress a FALSE; }

} } /♦ function do_polling /

/ *

do command - execute a command.

static void do_command ( struct command temd, // incoming command (βame name aβ in IPC_1) const HWND hWndClientDDE, conβt HWND hWndServerDDB

βtruct event event; // outgoing event (same name as in

IPC_1) βtruct event ev; // outgoing event allocation int i; int nVRetVal; // Voyβyβ return value int nFRetVal; // file operation return value int nLine; char errmβg[140] ; βtruct stat statbuf; // buffer of information about a directory char tmpbname[MAX_FN + 5]; // base filename (include the dot and 3 chars for file extension char tmpfname[MAX_DN + MAX_FN + 5] ; // full filename

(include the dot and 3 chars for file extension boolean foundfile; // was file found ? if ( Debug_flag >β 2 ) print_command ( tαnd, debugfile ) ; event tev; / to match IPC 1 /

nLine a tαnd.nLine ; event->nEvent a EV_NONE; event->nLine a nLine; event->C_error a 0; event->DV_βrror a 0; event->DOS_error a 0; event->βtring[0] a '\0'; event--filename[0] a '\0'; event->bInTestMode[nLine] a line[nLine] .bTestMode; if ( ( tαnd.nCommand la DVRC_CONNECT ) tt { tαnd.nCommand !« DVRC_SETDIR ) tt ( tcmd.nCommand la DVRC_GETSTATE ) tt ( tc d.nCccπmand !« DVRC_GBTSYSSTAT ) tt ( lline[nLine] .bConnected ) ) { event->nBvent a EV_NONE; / lazy / event->reβult a RC~NOTCONNECTED; nVRetVal a event->resuit goto finish; } βwitch ( tcmd.nCommand ) { case DVRC CONNECT: if (line[nLine] .serverwnd la (HWND)NULL) {

// previous owner did a disconnect, but DDE conversation // has not been torn down yet. event-.rββult a RC_BUSYLINE; nVRetVal a event--result; goto finish;

} line[nLine] .bConnected a TRUE; line[nLine] .clientwnd a hWndClientDDE,- line[nLine] .serverwnd a hWndServerDDB; line[nLine] .bTestMode a FALSE; goto βenddown,- caβe DVRC_DISCONNECT: line[nLine] .bConnected a FALSE; line[nLine] .bTββtMode - FALSE;

UpdateWindow ( hWndMain ) ; /♦ note: other line[] info still valid, for βend_event ♦/ // note: DDE converβation still active (will be torn // down when get DDEJTERMIKATE event) . goto senddown; ~ cas

,-

goto n s ;

}

/♦ never get here / caβe DVRC_GETSYSSTAT: event~>nEvent EV_GOTS SSTAT; for ( i » 0; i < MAX LINES; i++ ) { if (i < MAX_AL) { " even ->has_task[i] line[i] .bConnected; event->bInTeβtMode [i] line[i] .bTeβtMode; event->has_taβk[i] a FALSE; event->bInTeβtMode[i] a FALSE;

for ( i a 0; i < MAX LINES; i++ ) { if (i < MAX_AL) { " if ( l line [i] . bCmdlnProgress ) event - >nCαmmand [i] a DVRC_NONE ; vent - >nCommand [i] line [i] . cmd.nCommand ; event->nCommand[i] a DVRC_NONE;

event->resuit a RC_SUCCEED; goto finish;

/*

TRICKY: FROM HERE ON DOWN, CODE IS IDENTICAL IN IPC 1 AND IPC S

*/

/ command that can be executed in thiβ layer / caβe DVRC_SETDIR: event->nBvβnt a EV_SETT.IR; if ( ( tαnd.dir_num < 0 ) | | ( tαnd.dir_num >a

MAX_DIRS ) ) { event--result a RC_BADCOMMAND; nVRetVal a event->result goto finish; if ( βtrlen ( tand.name ) > MAX_DN ) { event->resuit a RC_BADCOMMAND; nVRetVal a event-, result; goto finish;

}

/*

♦ tricky: even if directory does not exiβt or iβ not ♦ accessible, record its name (and return error code,

♦ of course) .

*/ strcpy (line [nLine] .diroame [tαnd.dir_num] , tαnd.name) ; βtrcat ( line [nLine] .dirname [tαnd.dir_num] , "\\" ) ; /♦ βee if dir exists and iβ readable and writable / ined (E_F) nFRetVal a open ( t cmd. name, 0_RDONLY ) ; if ( nFRetVal >= 0 ) { cloβe ( nFRetVal ) ; event - > re suit a RC_BADCOMMAND ; nVRetVal a event- - result ; goto finish;

eisdir not found in watcom ERRNO.H if (errno la EISDIR) { event-,reβult a RC_BADCOMMAND; nVRetVal a event->resuit goto finish;

nFRetVal a βtat ( tand.name, tstatbuf ) ,- if ( nFRetVal !- 0 ) { event->C_error a errno; event->DOS_error _doβerrno; sprintf ( errmsg,

"Could not get statue of directory %β' tand.name

) ; event-.result a RC_BADCOMMAND; nVRetVal a event-,result; goto finish;

} if ( ( statbuf.Bt_mode t S_IFDIR ) -_ 0 ) { Debug ( 6, βprintf ( Debugjauf, "IPC: '%s' is not a directory", tand.name ) ); event->rββult a RC_BADCOMMAND; nVRetVal a event->result goto finish; apparently directories are never "writable" in DOS / if ( ( ( statbuf.st_mode t S IREAD ) ««= 0 ) | |

FALSE / ((statbuf.βt_mode t S_IWRIT__) »a0) / ) { Debug ( 6, βprintf ( Debug_buf7 "IPC: Directory '%β' is not readable and " writable", tand.name ) ) ; event->result a RC_BADCOMMAND; nVRetVal a event->reeult goto finiβh;

}

♦endif event->reβult a RC_SUCCEED; nVRetVal a event->reβult; goto finish; case DVRC_DELETEFILE: event~> Event a EV_JDEL___FILE; if ( ( tαnd.dir_num < 0 ) | | ( t nd.dir num >=

MAX DIRS ) )

{ event->result a RC_BADCC_JMAND; nVRetVal a event->resuit goto finish; i }f ( strlen ( tand.play_item.fname ) > MAX_FN )

{ event->result a RC_BADCOMMAND; nVRetVal a event->resuit goto finish;

}

/♦ prepend appropriate directory name to filename */ strcpy ( tmpfname , line [nLine] .dirname [tαnd. dir_num] ) ,-

βtrcat ( tmpfname, tand.play_itern.fname ) ,- if ( !fname_haβ_extenβion ( tαnd.play_itern.fname ) )

/ append appropriate extenβion to filename ♦/ βtrcat ( tmp name, " ." ) ; βtrcat ( tmpfname, promptext ) ; event->nBvent a EV_DELETEFILE,- nFRetVal a remove ( tmpfname ) ; if ( nFRetVal < 0 )

{

Debug ( 6, βprintf ( Debug buf, "IPC: remove failed

%d", nFRetVal ) ) ; event->reβult a RC_NOFILE; event->C_error a errno;

♦if !defined(E_F) event->DOS error doserrao; ♦endif nVRetVal a event-.result; goto finish;

} event->result a RC_SUCCEED; nVRetVal a event-- result; goto finish;

/ Note: command that has to be βent down to lower layer ♦/ /*

βprintf ( tαnd .play_i tern . f name , USER_FNAME_FORMAT , next- βer filename

) ; next_uβer_filename++; /♦ prepend appropriate directory name to filename / strcpy( tmpfname, line[nLine] .diraame[tαnd.dir_num] ) ,- βtrcat ( tmpfname, tαnd.play_itern.fname ) ; street ( tmpfname, " ." ) ; street ( tmpfname, promptext ) ; /♦ see if the file exists / ♦if defined(E_F) nFRetVal a open ( tmpfname, O RDONLY ) ; if ( nFRetVal a. -l ) break; / file doeβ not exist / close ( nFRetVal ) ; nFRetVal a stat ( tmpf______, tβtatbuf ) ; if ( nFRetVal 1- 0 ) break; / file doeβ not exist /

♦endif

) s }trcpy ( tαnd.play_itern.fname, tmpfname ) ;

Debug ( 6, sprintf ( Debug_buf, "IPC: full filename

'%β'", tαnd.play_itern.fname ) ); goto senddown;

) ) /

case DVRC_SBTINPU : caβe DVRC_SETOUTPUT: case DVRC FAXCOVER: caβe DVRC~FAXDOC: caβe DVRC_FAXSEND: caβe DVRC_FAXSETUP: goto βenddown;

/ Note: commamd that has to be or not to be sent down to lower layer /

UpdateWindow ( hWndMain ) ; goto finish;

} / end of switch for tcmd.nSetGetOpCode ♦/ goto senddown,-

) { caβe GET_RECORDINGS: caβe GETJIMPFILES: event->nBvent a EV_GET; if ( ( tcmd.dir_num < 0 ) | | ( tand. ir num >-

MAX_DfRS ) ) { event->reβult a RC_BADCOMMAND; ~ nVRetVal a event->resuit goto finish; } strcpy ( event->cGetParm, line[nLine] .dirname[tcmd.dir_num] ); event->resuit a RC_SUCCEED; nVRetVal a event->result goto finish; } / end of switch for tCmd.nSetGetOpCode / goto senddown; default: goto senddown;

/ TRICKY: FROM HERE UP, CODE IS IDENTICAL IN IPC 1 AND IPC_^_S /

/♦ never get here / senddown : line [nLine] .cmd a tαnd; line [nLine] .bCmdlnProgrββs a TRUE; nVRetVal a tm_Do_Cαmmand (

- ~ tend, TRUE, event

) ; finish: if ( nVRetVal !« RC_CMDN0TDONE )

/♦ send event back to Client / send_βvent (

event, tnVRetVal

); line[nLine] .bCmdlnProgress a FALSE; )

}

FUNCTION: SendTerminate

PURPOSE: Post terminate message and indicate that converβation iβ in process of being terminated.

βtatic int SendTerminate ( conβt HWND hWndServerDDB, conβt HWND hWndClientDDE

boolean bSucceββ;

SetConvlnTerminateState ( hWndServerDDB ) ; bSucceββ a PoβtMeββage ( hWndClientDDE, WM_DDB_TERMINATE, hWndServerDDB, OL ) ; if (IbSucceββ) { Debug ( 1, sprintf ( Debugjbuf, "IPC_WD_S/SendTerminate:

PoβtMeββage(hWndClientDDE 0x%04X) failed", hWndClientDDE ) ) ;

} return RC_SUCCEED; } / function SendTerminate /

/♦** * **** ** * FUNCTION: ServerAcknowledge

PURPOSE: Called when βerver application receiveβ ACK or NACK, or when βerver receiveβ time out waiting for response to WM DDE DATA.

7 static int ServerAcknowledge ( const HWND hWndServerDDB, const HWND hWndClientDDE, const LONG lParam )

{ char szltemName[ITEM NAME_MAX_SIZE + 1] ;

ATOM GDARetVal; / GlobalDeleteAtom return value if ( ! ( LOWORD ( lParam ) t 0x8000 ) ) { Debug ( 1, sprintf ( Debug_buf, "IPC_WD_S/ServerAcknowϊedge: DDE send data failed" ) ),-

/♦ timeout after DDE_SBND_DATA / if (HIWORD ( lParam ) != 0) {

GlobalGetAtomName ( HIWORD ( lParam ), szltemName, ITEM_NAME_MAX_SIZE ) ;

/♦ free the data here /

if ( HIWORD ( lParam ) !« 0 ) { / 0 if time-out, so don't try to delete ♦/

GDARetVal a GlobalDeleteAtom ( HIWORD ( lParam ) ) ; if (GDARetVal != 0)

Debug(1,βprintf(Debug_buf, "IPC WD S/ServerAcknowledge:

GlobalDeleteAtom(0x%04X) failed", HIWORD ( lParam ) ) ) ;

} return RC_SUCCEED; } /♦ function ServerAcknowledge /

FUNCTION: Serverlnitiate

PURPOSE: Called when βerver application receiveβ WM_DDE_INITIATE meββage.

Note: Client will delete atoms it created and the ones we create here. * ***********/ static int Serverlnitiate ( const HWND hWndClientDDE, const LONG lParam ) HWND hWndServerDDB;

ATOM atomApplicationRcvd;

ATOM atomTopicRcvd;

ATOM atomApplicationReturn;

ATOM atαmTopicRetura; char szApplication[APP_MAX_SIZE + 1]; char szTopic[TOPIC_MAX_SIZE + 1] ; int nVRetVal; -// " Voyβyβ return value boolean bSucceββ;

ATOM GDARetVal; // GlobalDeleteAtαm return value atomApplicationRcvd a LOWORD ( lParam ) ; if ( atomApplicationRcvd la o ) {

GlobalGetAtomName ( atomApplicationRcvd, szApplication,

APP MAX SIZE ) ; } - - if ( atomApplicationRcvd tt Strαπpi ( szApplication, VA_DDE_SERVER_APP_NAME ) )

/♦ application was specified but it wasn't right / nVRetVal = RC_BADCOMMAND; goto done; } atomTopicRcvd a HIWORD ( lParam ) ; if ( atomTopicRcvd !« 0 ) {

GlobalGetAtomName (atomTopicRcvd, szTopic,TOPIC MAX SIZE) ; if ( βtrαπpi ( szTopic, VA_DDE_TOPIC ) ) { /♦ topic wasn't right / nVRetVal - RC_BADCOMMAND; goto done;

hWndServerDDB a CreateWindow (

"vσyβServerDDEWndClaββ" , "voyβServerDDE",

WS_CHILD, / not viβible / 0, 0, 0, 0, / no poβition or dimensions ♦/ hWndMain, / parent ♦/ ( HMENU ) NULL, / no menu / hlnst, NULL ); if ( hWndServerDDB .a (HWND)NULL) {

Debug ( 1, βprintf ( Debug_buf, "IPC_WD_S/Serverlnitiate:

- CreateWindow failed" ) ) ; nVRetVal = RCJOSERROR; goto done;

} bSucceβs a AddConv ( hWndServerDDB, hWndClientDDE ) ; if ( !bSucceββ ) { DeβtroyWindow ( hwndServerDDE ) ; nVRetVal a RC_SWLIM_T; goto done; } atomApplicationRetu a GlobalAddAtom(VA_DDE_SERVER_APP_NAME) ; if (atomApplicationRetum aa 0) {

Debug ( 1, sprintf ( Debug_buf, "IPC WD S/Serverlnitiate:

GlobalAddAtom failed" ) ) ; RemoveConv ( hWndServerDDB ) ; DeβtrσyWindow ( hWndServerDDB ) ; nVRetVal a RC_OSERROR; goto done;

} atomTopicReturn a GlobalAddAtom ( VA_DDE_TOPIC ) ; if (atomTopicReturn «a 0) {

Debug ( 1, sprintf ( Debug buf, "IPC_WD S/Serverlnitiate:

GlobalAddAtom failed" ) ) ; GDARetVal a GlobalDeleteAtom ( atomApplicationRetum ) ; if (GDARetVal 1- 0) Debug (1,βprintf ( Debug buf, "IPC_WD_S/Serverlnitiate:

GlobalDeleteAtom(Ox 04X) failed", atomApplicationRetum ) ) ; RemoveConv ( hWndServerDDB ) ; DestrσyWindow ( hWndServerDDB ) ; nVRetVal - RC_OSERROR; goto done; }

// Send ACK to hWndClientDDE from hwndServerDDE // βuppoβed to uβe SendMessage here, but if we do client won't // unblock, because it is in SendMessage sending to us ! bSucceββ a PoβtMeββage ( hWndClientDDE, WM_DDE_ACK, hWndServerDDB,

MAKELONG ( atomApplicationRetum, atomTopicReturn ) ); if (!bSucceββ) {

Debug ( 1, sprintf ( Debug_buf, "IPC_WD_S/Serverlnitiate: ~ PostMessage failed" ) ) ;

GDARetVal a GlobalDeleteAtom ( atomApplicationRetum ) ,- if (GDARetVal !- 0)

Debug (1,βprintf( Debug buf, "IPC_WD S/ServerInitiate:

GlobalDeleteAto (0x%04X) failed", atomApplicationRetum ) ) ; GDARetVal a GlobalDeleteAtom ( atomTopicReturn ) ; if (GDARetVal !» 0)

Debug (1,βprintf( Debugjauf, "IPC_WD_S/Serverlnitiate:

GlobalDeleteAtom(0x%04X) failed", atomTopicReturn ) ) ; RemoveConv ( hWndServerDDB ) ; DeβtroyWindow ( hwndServerDDE ) ; nVRetVal a RC_OSERROR; goto done;

} nVRetVal a RCJSUCCEED; done: return nVRetVal;

} /♦ function Serverlnitiate /

FUNCTION: ServerPoke

PURPOSE: Called when βerver application receiveβ WM_DDE_POKB mesβage, which is a command from a client application.

Tricky: Ignore "item name" portion of message; it will be 0.

7 βtatic int ServerPoke ( conβt HWND hWndServerDDB, conβt HWND hWndClientDDE, conβt LONG lParam

HANDLE hPokeData;

DDEPOKE FAR lpPokeData;

BOOL bReleaβe; βtruct command tαnd; /♦ incoming commamd / βtruct event event; / event to βend / char info[80] ; /* info message /

BOOL sendevent; int nVRetVal // Voysys return value boolean bSuccess

HGLOBAL GFRetVal // return value from GlobalFree hPokeData a LOWORD ( lParam ) ; / hamdle to command / if ( ! ( lpPokeData a ( DDEPOKE FAR ) GlobalLock (hPokeData))

|| ( lpPokeData->cfFormat != CFJTEXT ) ) { Debug ( 1, βprintf ( Debugjauf, "IPC_WD_S/ServerPoke: data lock failed, or wrong data format" ) ) ; bSuccess a PostMessage ( hWndClientDDE, WM_DDE_ACK, hWndServerDDB,

MAKELONG ( 0, 0 )

) ; / negative acknowledgement / if (1bSucceββ) {

Debug ( 1, βprintf ( Debugjbuf, "IPC_WD_S/ServerPoke:

PostMeββage failed" ) ) ; nVRetVal « RC_OSERROR; goto done;

memcpy ( itand, lpPokeData--Value, sizeof ( tcmd ) ) ; βendevent a FALSE; /♦ check for valid line number ♦/ if ( ( tcmd.nLine < 0 ) | | ( tαnd.nLine >« MAX_AL ) ) sprintf ( info, "got msg with bad line num %d (should be in range

%d-%d)", tand.nLine, 0,

MAX LINES - 1 ) ;

/ send an event back to the sender ♦/ memβet ( tevent, 0, βizeof ( event ) ) ,- event.nEvent a EV_BADCOMMAN ; event.nLine a -i; event.result a RC__NTERNAL; βendevent a TRUE; goto βkipαnd;

}

/ check for taβk A connected to line but command iβ from taβk

B */ if ( line[tcmd.nLine] .bConnected tt

( line[tαnd.nLine] .client nd la hWndClientDDE ) ) { βprintf ( info,

"got meg for line num %d from taβk la owner of line", tαnd.nLine );

/ βend an event back to the sender ♦/ memβet ( tevent, 0, βizeof ( event ) ) ; event.nEvent a EV_BADCQMMAND; . event.nLine a tcmd.nLine; event.resuit a RC_BUSYLINE; sendevent a TRUE; goto βkipαnd;

1

// check for server shutting down if ( bShuttingDown tt

( t cmd.nCommand l a DVRC_HANGUP ) tt ( tαnd.nCαmmamd ! = DVRC_DISCONNECT ) ) { sprintf ( info,

"got msg for line num %d during shutdown",

tcmd.nLine

);

/ βend am event back to the βender / memβet ( tevent, 0, βizeof ( event ) ); event.nEvent a EV_BADCOMMAND; event.nLine a tand.nLine; event.result a RC_SERVERSHUTDOW ; sendevent a TRUE;- goto skipαnd;

} do command ( tcmd, hWndClientDDE, hWndServerDDB ) ; βkipαnd:

/♦ Save value of fReleaβe, βince ptr invalidated by

GlobalUnlockO ♦/ bReleaβe a lpPokeData->f elease; lpPokeData a NULL; GlobalUnlock ( hPokeData ) ; if ( bReleaβe ) {

GFRetVal a GlobalFree ( hPokeData ) ; if (GFRetVal 1- (HGLOBAL)NULL) Debug ( 1, βprintf ( Debug_buf, "IPC WD_S/ServerPoke:

GlobalFree(0x%04X) failed", hPokeData ) );

} bSucceββ a PoβtMeββage ( hWndClientDDE,

WM_DDE_ACK, hWndServerDDB, MAKELONG ( 0x8000, 0 ) ) ; / poβitive acknowledgement / if (1bSucceββ) {

Debug ( 1, βprintf ( Debugjbuf, "IPC_WD_S/ServerPoke:

- PoβtMeβsage failed" ) ) ;

} if ( βendevent )

/♦ βend the reβulting mβββage to the other taβk / SendData ( hWndServerDDB, hWndClientDDE, tevent, βizeof ( event ) , TRUE, / request am ACK /

FALSE / client did not request thiβ data / );

} nVRetVal « RC_SUCCEED; done: return nVRetVal;

} /♦ function ServerPoke /

FUNCTION: ServerTerminate

PURPOSE : Called when server application receives WM_DDE_TERMINATE meββage .

βtatic int ServerTerminate ( conβt HWND hWndServerDDB, conβt HWND hWndClientDDE )

{ int nLine; int nVRetVal; // Voyβyβ return value nVRetVal « RC_SUCCEED; if ( FindConv ( hWndServerDDB ) aa NULL ) goto done;

RemoveConv ( hWndServerDDB ) ,- DestroyWindow ( hwndServerDDE ) ; for ( nLine a 0; nLine < MAX_AL; nLine++ ) { if ( line[nLine] .serverwnd a hWndServerDDB ) { line[nLine] .serverwnd a (HWND) ULL; line[nLine] .client nd a (HWND)NULL; goto done; }

) nVRetVal ■ RC_INTERNAL; done: return nVRetVal;

} / function ServerTerminate /

FUNCTION: TeπninateConversations PURPOSE: Processes WM_DESTROY mesβage, terminates all conversations.

static int TerminateConverβationβ ( void

) HWND hWndServerDDB;

LONG ITimeOut;

MSG meg;

/ Terminate each active converβation / hWndServerDDB a ( HWND ) NULL; while ( hwndServerDDE a GetNextConv ( hwndServerDDE ) ) SendTerminate ( hwndServerDDE, GetHwndClientDDE (

hWndServerDDB ) ) ;

}

/ Wait for all converβationβ to terminate OR for time out / ITimeOut a GetTickCount0 + ( LONG ) nAckTimeOut; while ( PeekMeββage ( tmβg, ( HWND ) NULL, WM_DDE_FIRST,

WM_DDE_LAST, PM_RBMDVE ) ) { DiβpatchMeββage ( tmsg ) ; ~ if ( msg.message == WM_DDE_TBRMINATE ) { if ( lAtLeaβtOneConvActive ( ) ) break; if ( GetTickCount ( ) > ( DWORD ) ITimeOut ) break; } return RC_SUCCEED;

/ function TezminateConverβationβ /

FUNCTION: DDEWndProc

PURPOSE: Handles all DDE messages received by the server application.

long FAR PASCAL export DDEWndProc (

HWND hWnd, UINT meββage, WPARAM wParam, LPARAM lParam

switch ( message ) {

line driver to handle t(message, Param,lParam) ; case WM_DDE_ACK:

ServerAcknowledge ( hWnd, ( HWND ) wParam, lParam ) ,- return ( 0L ) ; case WM_TIMER:

// paβs down to line driver to handle tm_Proceβs_Event(message,wParam,lParam) ,- return ( 0L ) ; case WM_DDE_ADVISE:

ServerAcknowledge ( hWnd, ( HWND ) wParam, OL ) ;

/* simulates NACK /

return ( 0L ) ; caβe WM_DDE_POKE: / client βending command to βerver ♦/ ServerPoke ( hWnd, ( HWND ) wParam, lParam ) ; return ( 0L ) ; caβe WM_DDE_TERMINATE:

ServerTerminate ( hWnd, ( HWND ) wParam ) ; return ( 0L ) ; caβe WM_DDE_UNADVISE:

ServerAcknowledge ( hWnd, ( HWND ) wParam, 0L ) ;

/ βimulateβ NACK / return ( 0L ) ; caβe WM_DDE_REQUEST:

ServerAcknowledge ( hWnd, ( HWND ) wParam, 0L ) ;

/ βimulateβ NACK / return ( 0L ) ; caβe WM_DDE_EXECUTE:

ServerAcknowledge ( hWnd, ( HWND ) wParam, 0L ) ;

/ βimulateβ NACK / return ( 0L ) ; default: return (DefWindowProc(hWnd, meββage, wParam, lParam));

} } / function DDEWndProc /

I*

FUNCTION: AboutDlgProc(HWND, UINT, WPARAM, LPARAM) PURPOSE: Processes meββageβ for "About" dialog box

BOOL FAR PASCAL export AboutDlgProc (

HWND hDlg, UINT meββage, WPARAM wParam, LPARAM lParam )

{ switch ( meββage ) { caβe WM_INITDIALOG: return ( TRUE ) ; caβe WM COMMAND: if f ( wParam -a IDO ) | | ( wParam «= IDCANCEL ) )

{ EndDialog ( hDlg, TRUE ) ; return ( TRUE ) ; break,-

} return ( FALSE ) ;

} /♦ function AboutDlgProc /

FUNCTION: MainWndProc(HWND, UINT, WPARAM, LPARAM) PURPOSE: Processes messages for βerver

long FAR PASCAL export MainWndProc ( HWND hWnd,

UINT meββage, WPARAM wParam, LPARAM lParam

switch ( meββage ) {

line driver to handle ( meββage, wParam, lParam ) ; caβe WM_CREATE: hDC - GetDC ( hWnd ) ; SelectObject (hDC, GetStockObject(SYSTEM_FIXED FONT) ) ;

GetTextMetricβ ( hDC, ttm ) ; ~ nCharW a tm.tmAveCharWidth; nCharH a tm.tmHeight; ReleaβeDC ( hWnd, hDC ) ; rect.top a 3 nCharH / 2; break;

a LOWORD ( lParam ) ,- rect.bottom HIWORD ( lParam );

UpdateWindow ( hWnd ) ; break,- caβe WM_SETFOCUS:

SetFocus ( GetDlgltem ( hWnd, IDOK ) ) ; break; caβe WM_PAINT:

InvalidateRect ( hWnd, NULL, TRUE ) ,-

hDC a BeginPaint ( hWnd, tpβ ) ,- /♦ do any painting here ♦/ // SetBkMode ( hDC, TRANSPARENT ) ;

ShowTeβtMode ( ) ;

EndPaint ( hWnd, tpβ ) ; break; case WM_COMMAND: βwitch ( wParam )

{ caβe IDM_ABOUT:

DialόgBox ( hlnβt,

MAKEINTRESOURCB(IDD_ABOUT) , // "ABOUT", // thiβ name muβt match the one used in the RC file hWnd,

MaϋceProcInβtance ( AboutDlgProc, hlnst ) ) ; break; case ID TAB: case ID-SHIFT_TAB: break;

} break,- caβe WM_DDE_INITIATB:

Serverlnitiate ( ( HWND ) wParam, lParam ) ,- break; caβe WM_DESTROY: break; caβe WM_SYSCOMMAND: if f ( wParam a SC_CLOSE ) tt ( IbShuttingDown ) ) { // tricky: don't let Windowβ proceββ thiβ message yet // we will shut down first, then tell Windowβ about it

PoβtQuitMesβage ( 0 ) ; // to pop us out of main meββage loop

( DefWindowProc ( hWnd, message, wParam, lParam ) ) ; case WM TIMER: if (wParam «= WINMAIN_TIMER_ID) { // time to do polling do_polling() ; } else {

// paββ down to line driver to handle tm Proceβs Event(message,wParam,lParam) ; } break,- default: return (DefWindowProc(hWnd, message, wParam, lParam)) } return ( 0L ) ;

} /♦ function MainWndProc /

I*

FUNCTION: InitApplication(HANDLE)

PURPOSE: Initializeβ window data and regiβterβ window claββ * *********** **********/ βtatic

BOOL InitApplication ( conβt HANDLE hinstance

WNDCLASS wc; wc.style a 0; wc.lpfnWndProc a MainWndProc; wc.cbClsExtra a 0; wc.cbWndExtra a 0; wc.hinβtance a hinβtance; // wc.hlcon ■ Loadlcon((HINSTANCE)NULL, IDI_APPLICATION) ; wc.hlcon a Loadlcon ( hinstance, MAKEINTRESOURCE (IDI ICONl)); wc.hCurβor a LoadCurβor ( ( HINSTANCE ) NULL, IDC ARROW ) ; wc.hbrBackground a COLOR_WINDOW + 1; wc.lpβzMenuName a "ServerMenu"; // thiβ name muβt match the wc.lpszClaββName a "voysServerWClaββ"; if ( 1RegieterClaββ ( twc ) ) return ( FALSE ) ; wc.βtyle 0; wc.lpfnWndProc a DDEWndProc; wc.cbClsExtra a 0; wc.cbWndExtra a 0; wc.hinβtance a hinβtance; wc.hlcon > ( HICON ) NULL; wc.hCursor a ( HCURSOR ) NULL; wc.hbrBackground a 0; wc.lpszMenuName a NULL; wc.lpszClaββName a "voyβServerDDEWndClaββ* return ( RegiβterClaββ ( twc ) ) ,- /♦ function InitApplication /

FUNCTION: Initlnβtance(HANDLE, int)

PURPOSE: Saves instance handle, creates main window, and creates 3 child edit controls with id's 1, 2, and 3. ******************************************************* ***** / βtatic

BOOL Initlnstance ( const HANDLE hlnstance, const int nCmdShow

InitAckTimeOut ( ) ; / in module SERVDDE ♦/ hlnst hinβtance; hWndMain a CreateWindow (

"voyβServerWClaββ", SERVER_TITLE, WS_OVBRLAPPEDWINDOW I WS_CLIPCHILDREN, CW_USBDEFADLT, CW_USEDEFADLT, CW_USEDEFAULT, CW_USEDEFAULT, ( HWND ) NULL, ( HMENU ) NULL, hinβtance, NULL ) ; if ( !hWndMain ) return ( FALSE ) ; hDC a GetDC ( hWndMain ) ; hWinDisplay a hDC;

GetTextMetries ( hDC, ( LPTEXTMETRIC ) t tm ) ; xDelta a tm.tmAveCharWidth; yDelta a tm.tmHeight + tm.tmExteraalLeading; nHorzRes a GetDeviceCaps ( hDC, HORZRES ) ; nVertReβ a GetDeviceCaps ( hDC, VERTRES ) ;

ReleaβeDC ( hWndMain, hDC ) ;

MoveWindow ( hWndMain, nHorzRes / 2 + xDelta 6, nVertReβ / 2 + yDelta, xDelta 35, yDelta 12, FALSE ) ;

// ShowWindow(hWndMain, nCmdShow) ;

// ShowWindow(hWndMain, SW_SHOWMINIMIZED) ;

ShowWindow ( hWndMain, SW_SHOWMINNQACTIVE ) ;

UpdateWindow ( hWndMain ) ; return ( TRUE ) ; } /♦ function Initlnstaσice /

/ ** * * ♦app_startup - parse command-line, initialize data, regiβter DDE

βtatic int app_βtartup ( const char *emdline, const HINSTANCE hinβtance, conβt HWND hWnd

// Voyβyβ return value

nVRetVal a parβe_argumentB ( emdline ) ; if ( nVRetVal !« RC_SUCCEED ) goto done; nVRetVal a CheckDebugSanity0 ; if { nVRetVal !- RC_SUCCEED ) {

Debug ( 1, sprintf { Debug_buf, "IPC_WD_S/app_startup:

- debug sanity failure" ) ) ; goto done; } dwVersion a GetVerβionO ,- / get Windows, DOS versions ♦/

/♦ initialize array of line βtates / for ( nLine a 0; nLine < MAX AL; nLine++ )

{ line[nLine] .bConnected a FALSE; line[nLine] .bCmdlnProgreββ a FALSE; line[nLine] .bTestMode a FALSE;

/♦ initialize voice file directory names / for ( dir ium a 0; dir_num < MAXJ3IRS; dir_num++ ) line[nLine] .dirname~[dir num] TO) a '\0' , r } next_uβer_filename a 0; nVRetVal = RC SUCCEED; done: return nVRetVal;

} /♦ function app_βtartup /

app_βhutdown

static int app_shutdown ( void )

{ int nVRetVal; // Voyβyβ return value int nLineNum,- βtruct commamd and; // commamd to execute βtruct event event; // event received int count; boolean gotone; MSG meg;

HANDLE hAccel; boolean bSucceββ; bShuttingDown a TRUE; nVRetVal a tm Shutdown(); if (nVRetVal T« RCJBUSYLINE) goto finish;

// some line is connected or has a command in progreββ // give it some time to abort or disconnect count - PEEK_MSG_COUNT; while ( count-- ) { gotone a PeekMeβsage ( tmβg, ( HWND ) NULL, 0, 0,

PM_REMOVE ) ; if ( gotone ) { if ( !TramβlateAccelerator ( hWndMain, hAccel, tmβg) ) { TranβlateMeββage ( tmsg ) ; DiβpatchMeββage ( tmsg ) ;

} d }o_polling() ;

} nVRetVal a tm Shutdown() ; if (nVRetVal T- RCJBUSYLINE) goto finiβh; ""

// for any lineβ connected but not in progreββ, inject a disconnect commamd for ( nLineNum a 0; nLineNum < MAX_AL; nLineNum++ ) { lp a tline [nLineNum] ; ~ if ( lp->bConnected ) { cmd.nCommand a DVRC_DISCONNECT; αnd.nLine a nLineNum; lp->bCmdlnProgreββ a TRUE; nVRetVal - tm_Do_Command ( and,

TRUE, // βtart of command tevent

); if ( nVRetVal != RC_CMDNOTDONE ) { / βend event back to Client */ βendjevent ( event, tnVRetVal

) ;

lp- > bCmdlnProgreBS a FALSE ;

}

// give commands time to work count a PEEK_MSG_COUNT; while ( count-- ) { gotone * PeekMesβage ( tmsg, ( HWND ) NULL, 0, 0, PM_REM0VE ) ; if ( gotone ) { if ( IhDlgCurrent | | lIsDialogMessage ( hDlgCurrent, tmsg ) ) { if ( ITramslateAccelerator ( hWndMain, hAccel, tmsg ) ) {

TranslateMeβsage ( tmβg ) ; DiβpatchMeββage ( tmsg ) ;

} do_polling() ;

// try one more time // if it doesn't work, give up nVRetVal a tm_Shutdown() ; finish:

/♦ Terminate all DDE conversations before destroying client window /

TerminateConverβationβ ( ) ,-

// now tell Windowβ to close our windowβ bSucceββ a PoβtMeββage ( hWndMain,

WM SYSCQMMAND, SC-CLOSE, 0 ) ; if (!bSuccess) {

Debug ( 1, sprintf ( Debugjbuf, "IPC_WD_S/app_βhutdown:

PoβtMeββage ailed" ) ) ; } // chew up meββageβ that close windowβ count - PBEK_MSG_COUNT; while ( count-- ) ( gotone a PeekMeββage ( tmβg, ( HWND ) NULL, 0, 0,

PM_RBM0VE ) ; if ( gotone ) { if ( IhDlgCurrent | | lIsDialogMessage ( hDlgCurrent, tmsg ) ) ( if ( ITra βlateAccelerator ( hWndMain, hAccel, tmsg

) ) { TranslateMesβage ( tmsg ) ;

DiβpatchMeββage ( tmsg ) ; }

}

Debug ( 5, sprintf (Debugjbuf, "IPC_WD_S/app_shutdown: ret"));

return RC_SUCCBED ; } /♦ function app_βhutdown /

****************** ******

FUNCTION: WinMain(HANDLE, HANDLE, LPSTR, int)

PURPOSE: Calls initialization function, proceββeβ meββage loop ************** * ********* / int PASCAL WinMain (

HANDLE hinβtance, HANDLE hPrevInβtance, LPSTR lpCmdLine, int nCmdShow

)

// Voyβyβ return value

if ( 1hPrevInβtance ) {

/♦ delete, create and open debug file / debugfile a open (

vedbug.txt",

0_C___VT I 0_TRUNC I OJTEXT I 0_WRONLY, S_IWRITE

) ; if ( debugfile a. -l ) return ( 0 ) ; // error to open the debug file if ( !InitApplication ( hinβtance ) ) { TRUE;

} else { /♦ Another Server is running; abort / goto Btop4; ) if ( !Initlnstance ( hlnstamce, nCmdShow ) ) { bAnyError a TRUE; goto stop3;

hAccel a LoadAccelerators( hinβtance,

"ServerAcc" // must match name in RC file ) ;

nVRetVal a app_βtartup ( lpCmdLine, hinβtance, hWndMain ) ; - RC_SUCCEED ) { TRUE * ;

/♦ βtart polling timer / timerlD a SetTimer ( hWndMain, WINMAIN_TIMER_ID,

EV_POLL_DELAY, NULL ) ; if ( timerlD 1- WINMAIN_TIMER_ID ) { TRUE; done a FALSE; while ( Idone ) { gotone a PeekMeββage ( tmβg, ( HWND ) NULL, 0, 0,

PM_REMDVE ) ; if ( gotone ) { if ( IhDlgCurrent | | llβDialogMeββage ( hDlgCurrent, tmsg ) ) { if(ITranslateAccelerator(hWndMain, hAccel, tmsg)) { TranslatβMessage ( tmβg ) ; DiβpatchMeββage ( tmβg ) ; } if ( msg.message aa WM_QUIT ) done a TRUE; ~

} retval a msg.wParam;

//stopl:

/ remove timer / KillTimer ( hWndMain, timerlD ) ; stop2: app_shutdown ( ) ; stop3: close ( debugfile ) ; if ( bAnyError )

MeββageBoxt hWndMain, "Server cannot be started.\nPlease , see output debug file for more error message.",

SERVER TITLE, MB_ICONSTOP | MB OK ) ; stop4: return ( retval ) ;

} /♦ function WinMain /

APPENDIX G D_Dial . c

Copyright 1992-1994 Vovsvs Corporation

Name : LD_Dial . c

* ~ * Description:

Line Driver layer for 4-channel Dialogic line card.

Accepts commands from InterProceββ Communication layer and executes them on the Dialogic lines.

Supports Dialogic line card models D4x (D41B, D41D, D21D tested so far) .

$Log: /VoysAccesβ/core/ld/LDJDIAL.C $

♦include <ctype .h>

♦include <doβ .h> ♦include <errao.h>

♦include <fcntl.h>

♦include <io.h>

♦include <sys\stat.h>

♦include <stdio.h> ♦include <stdlib.h>

♦include <βtring.h>

♦include <time.h>

// stuff from windows.h; needed for ld_Procesβ_Bvent // can't juβt include the file; would have conflict defining struct DCB // Dialogic Control Block typedef unsigned long DWORD; typedef unsigned int UINT; typedef signed long LONG; typedef UINT WPARAM; typedef LONG LPARAM;

♦define WM_TIMER 0x0113 ♦define PROMPT_FEXT "VOX" ♦define INDEX_FEXT "VOI"

♦include "d40.h" ♦include "d401ib.h" ♦include "vfcns.h" extern int int_level; / software interrupt level ♦/

♦include "OS.h"

♦include "dVR.h" ♦include "Command.h"

♦include "LD.h"

♦include "VR.h"

♦define DO_SETXPARM 0

// after determining real lines, fill up array with fake lines ?

♦define DO FAKE_LINES 0

/_______-_____.--__- _/

/ we number lines from 0 thru N-l; Dialogic numbers channels 1 thru N / ♦define CHANNEL(LINE) ((LINE)+1)

♦define LINE(CHANNEL) ( (CHANNEL) -1)

/ actual number of lines present / static unβigned int nActualLineβ a 0;

/ /

/♦ in-memory βtruct to access an indexed prompt file ♦/ βtruct ifmap { int ifhandle; / handle to indexed file ♦/ βtruct { char fname [MAX_FN] ; / baβe file name ♦/ long βtart; /* starting byte in file ♦/ long length; / length in bytes ♦/

} indextab[MAX_PHRASE] ; int num phraβeβ; / number of phrases in indextab ♦/

} / struct that specifieβ a play-liβt, to xplayf ♦/ βtruct findex { int type; / type of i/o transfer ♦/ int FileHandle; / DOS file handle ♦/ long BlockPosition; / offset of block relative to start of file / long BlockLength; / length of block ♦/ ) * / /

♦define SET_TIMEOUT_TO(N) lp--timeout a (N)

// Timeout for operations that βhould never fail. // Actually, timeout for eventβ that βhould come almost // immediately; if they don't, something is broken, ♦define TIMBOUT_FOR_BROKEN (time_in_mβec() + (10 β 1000) )

// Timeout for N βecondβ from now. ♦define TIMEOUT_FOR_SEC(N) (time_in_mβec() + ((N)^1000))

// Timeout after going on-hook. // Makes sure that CO sees that we are on-hook.

// If we didn't wait, there is a chance that we would immediately // try to initiate another call, and CO might see such a quick // on-and-then-off-hook that it looks like a wink or something, ♦define TIMEOUT_AFTER_ONHOOK <time_in_mβec0 + (3^1000) )

/ ***** ** * * */

/*

Information about βtate of each active line.

*/ βtruct lιne_t { boolean bBxiβtβ; /* is thiβ entry uβed for a line ? ♦/ int nLineNum; / line number (0 to MAX_AL-1) ♦/ boolean bConnected; / s a client connected to line? / boolean bCmdlnProgreβs,- / iβ there a and in progreββ? / int nCmdStage; / current step of command being executed / βtruct commamd and; command that is in progress ♦/ βtruct event event; event being constructed ♦/ clock_t timeout ; if la o, time to abort cmd ♦/ int xiMaxDTMF ; end play or record if thiβ many digitβ received / int nTempVRetVal; ultimate value of ep->reβult / boolean bSendEvent; int num_events; // number of queued events for line βtruct { boolean blβWinBvent; // iβ thiβ a Windowβ event ?

// fields for Windowβ event

DWORD dwMβg;

DWORD dwParaml;

DWORD dwParam2;

DWORD dwDevice;

// fields for Dialogic event int nDialBvent; // Dialogic event number int nDialCallState; // Dialogic call βtate number

} queued_e [MAX_EVENTS_PER_LINE] ;

♦define IS_AN_EVENT (lp->num_events > 0) ♦define NEXT_EVENT (lp->queϋed_ev[0] ) ♦define TIMEOUT EVENT ((lp->queued_ev[0] .blβWinBvent) tt(lp->queued_ev[0] .dwMβg a. WM TIMER))

♦define REMOVE EVENT {int i; \ for(ia0 ; i<(lp->num_events) -1 ; i++) \ lp->queued_ev[i] a lp->queued_ev[i+ι] ; \ lp->num_eventβ--; } * ~

♦define REMOVE_ALL_EVENTS lp->num_events a 0 ♦define EVENTQ_FULL (lp->num_events >a

MAX_EVENTS_PER_LINE)

♦define ADD_EVENT(MSG,PI,P2,DEV)

{lp->queued_ev[lp->num_eventβ] .blβWinBvent a TRUE; \ lp->queu_d_ev[lp->num_eventβ] .dwMβg a (MSG) ; \ lp->queued_ev[lp->num_eventβ] .dwParaml a (PI) ; \ lp->queued_ev[lp->num_eventβ] .dwParam2 (P2) ; lp->queued_ev[lp->num_events] .dwDevice a (DEV) \ lp->num eventβ++; }

♦define ADD_DIALOG___BVENT(EV,CS) \

{lp->queύed_ev[lp->num_eventβ] .blβWinBvent aFALSE;\ lp->queued_ev[lp->num_eventβ] .nDialBvent a (EV) ; \ lp->queued~ev[lp->num events] .nDialCallState

(CS) ; \ lp->num_eventβ++; } boolean bUβerlβOffHook; / is user/caller off-hook 1 *1 boolean bCardlβOffHook; / card has line off-hook ? ♦/ boolean silent; / for callout, iβ perβon silent ? ♦/ ♦if defined(E_W) / Windowβ /

// The following pointer variableβ βhould be initialized to

// all zeros. long rwb_DosPointer; short rwb ProtectedSelector,-

long rwb_ProtectedPointer; long xrwb_DosPointer; short xrwb-Protected-,elector; long xrwb " ProtectedPointer; long f_____Pointer; short fi_ProtectedSelector; long fi~ProtectedPointer; long dtmf_DoβPointer; βhort dtmf_ProtectedSelector; long dtmf_ProtectedPointer; long cpb_DosPointer; βhort cpb ' ProtectedSelector; long cpbJProtectedPointer; long csb_DosPointer; short cβbJ?rotecte-Selector; long cβb~ProtectβdPointer; ♦endif int open_file; / for play/rec, voice file handle ♦/ struct findex Files_Index[MAX PF] ; / for play, for playing " playlist / boolean close_after[MAX_PF] ; / close file after play finishes ♦/ boolean voice has_ended; / for play/rec, voice data done?

/ boolean digite_have_ended; / for play/rec, DTMF string done ? / βtruct ifmap ifile[MAX_IFILE] ; / for play, indexed voice fileβ / int num openifile; / for play, num open indexed fileβ ~ ♦/

/ βtuff βet from initplay, addplay, play commands ♦/ struct pitem plist[MAX_PF] ; / play liβt itemβ ♦/ int plist count; / num files in play list ♦/ }; static struct line_t line[MAX_AL] - {0}; / variables uβed by all functionβ in this file ♦/ / they are βet to point to current line[] / βtatic βtruct line_t *lp a 0; / ptr to "line[aline_num] " βtructure ♦/ static βtruct commamd cp a 0; / ptr to "line[aline_num] .and" - βtruct ♦/ βtatic βtruct event *vp a 0; / ptr to "line[aline_num] .event" βtruct /

/♦ buffere uβed for DTMF input / /♦ muβt be in global (can't be βtatic ?) / /♦ muβt not be inβide a βtructure /

/♦ must be compiling program in large memory model ♦/ char dial_dtmfbuf [MAX_AL] [MAX_DIGITS + 1] = {0}; static boolean Initialized a FALSE; / ld_initialize called ? ♦/

♦if defined(E_W) / Windows /

/♦ variableβ for Check_For_Bvent / long cfe_DoβPointer a 0L; βhort cfe_ProtectedSelector a 0; long cfe_ProtectedPointer a 0L;

char cDosMemErr[] a " failed to allocate DOS memory

♦endif

/ /

/♦ standard βetting for Call Status Transition mask ♦/ ♦define CEM STANDARD (C LC + C LCON + C OFFH + C ONH) /♦ Dialogic channel statue block LineState mask bite ♦/

♦define LS_WE_ON_HOOK 0x08/* we have the line on-hook ♦/ ♦define LS_NO_RINGING 0x10/* no in-bound ring detected */ ♦define LS LOOP_CURR_ON 0x20/* caller haβ the line on-hook*/ ♦define LS-DTMF_DETECTED 0x40/* DTMF detected */ ♦define LS-SILENCE DETECTED 0x80/* βilence detected / **********************************/

II βtuff to map Dialogic event numbers to names // dangerous: if numbering in d40.h changeβ, thiβ iβ affected // will do limited sanity-check in ld_Initialize char ♦DialEventNumToName [] a {

"T_N0TERM",

"T MAXDT", "T TERMDT",

"TJSTOP",

"T DOSERR",

"T~MAXBYT",

"T_HFAIL" , "T_TIMB",

T_OFFH",

"T DIAL",

"T-SIL",

"T-EOF", "T_LCT_RM",

"T_DFULL",

"T_0NH",

"15",

"16", "T_MDTERM",

"T_CATERM",

" .LCREV",

"T LC",

"T-RING", "T SILOFF",

"T_SIL0N",

"T_AMXCON",

"T_AMXDIS",

"T_LCON", "T_MAXRNG",

"T_MCTERM",

"T_MDTMF",

"T_IDTIME",

"T_NSIL", "T_BUFFUL",

"T_BUFBMP",

"T_EMSERR",

"T_EMSL0W",

"T_EMSHI", "T_STPARM",

"T_WINK",

"T WKRECV",

"T DTMF" , "T-TONEON" ,

"TJTONEOFF" , "T_BADTERM" , "T_MTONEON" ,

"T_KΓONEOFF" , "T CAERROR" ,

"TJTGERR" , "T_TGCMPLT" , "T ADSIERR"

/ *

♦ Find_Phrase_In_IFile - - See if phrase can be obtained from an indexed file . Convert the play item if possible . ***** * / static void Find_Phraβe_In_IFile ( βtruct pitem playitem / play item to convert / ) int ifile num; /* indexed file number (0 to

MAX_IFILE-1) */ int pcount; /* phrase count */ char basename[MAX FN] ,- / baβe file name /

/♦ extract baβe filename from full filename / fname_full_to_base ( playitem->fname, / input -- full filename ♦/ basename / retval -- base fname ♦/ ); playitem->ifnum a { -l ) ; / not found in ifile ♦/ for ( ifile_num a 0; ifile_num < lp-_nu__openifile; ifile_num++ ) { for ( pcount a 0; pcount < lp->ifile[ifile_num] .num_phraβeβ; pcount++ ) { if ( strcmp ( basename, lp->ifile[ifile_num] .indextab[pcount] .fname ) aa o ) {

/♦ found prompt in indexed file / playitem->ifnum a ifile_num; playitern->index a pcount,- return; }

/* didn't find prompt in indexed file */ '

/ **** ***********************

* Get Line Status -- See if current line is on or off hook, * etc " . * /

βtatic int Get_Line_Statuβ ( CSB *lpCSB )

CSB My cβb; /* Channel status block */ int nDΪalRetVal,- // Dialogic return value int nVRetVal - 0;

/* * Get Channel Status */

♦if defined(E_W) / Windowβ / alloc_DOS_mem ( βizeof ( My_cβb ) , t( lp->cβb_DoβPointer ), t( lp->csb_ProtectedSelector ), t( lp->csb_ProtectedPointer ) ) if { lp->csb_ProtectedSelector aa o ) { nVRetVal a RC_RAMFULL; goto done;

} ♦endif nDialRetVal a getcβtat ( /* Get Channel Statue */

CHANNEL ( lp->nLineNum ) , ♦if defined(E_W)

( CSB * ) ( lp-_c8b_DosPointer ) /* Windowβ */

/ Windowβ / void ) lp->cβb_ProtectedPointer, βizeof ( My_cβb ) ) ; free_DOS_mem ( (lp->csb_DosPointer) , ~ - t(lp->cβb_ProtectedSelector) , t(lp->cβb_ProtectedPointer) ),- ♦endif switch ( nDialRetVal ) { caβe 0: / request complete / break; case 4: / system not initialized / case 9: / invalid channel number / nVRetVal a RC_BADLINE; break; default: /* βαme unknown error */ nVRetVal - RC_BADLINE; brea ;

} done:

♦lpCSB - My_cβb; return( nVRetVal ) ; }

/ **

♦ Fluβh All DTMF -- fluβh all DTMF on current line. ******** τ **** * ** ** / static void Fluβh All DTMF ( void )

int nDialRetVal; // Dialogic return value

/* ♦ Flush all DTMF on thiβ channel.

*/ nDialRetVal - clrdtmf (

CHANNEL ( lp-.nLineNum ) ); switch ( nDialRetVal ) { case E_SUCC: / Function was βuccesβful. / caβe E_NODT: /* Dtmf buffer empty. / /• " success / lp->bSendBvent - FALSE; break; caβe E_BADDL: / D4x hardware error. / ep->result RC_BADLINB; lp->bSendEvent a TRUE,- break; default: / βαme unknown error */ ep->reβult - RC_INTERNALLINE; lp->bSendBvent = TRUE; breaϋc; }

/ ********** * ** ** ****************** * ****************** * *

* Set Channel Maβk -- set event mask on a line. **************************************** / static void Set_Cha__nel_Maβk ( int maβk, / event maβk (sum of CBM_*'β) / int rings / number of rings for inbound call / ) int nDialRetVal; // Dialogic return value nDialRetVal a βetcβt (

CHANNEL ( lp--nLineNum ), maβk, ringβ

) ; switch ( nDialRetVal ) { caβe E_SUCC: / Function waβ successful. */ / ♦" success / lp->bSendBvent FALSE; break,- case E_BADDL: / D4x hardware error. */ ep->reβult a RC_BADLINE; lp->bSendBvent _ TRUE; break,- default: /* βαme unknown error */ ep-.result a RC_INT_R_____INE,- lp->bSend_vent a TRUE; break;

. }

/ - - - - - - ****** *

* Check_Uβer_Connection - - check if user iβ βtill on-line ****** *****************************/ βtatic int Check_Uβer Connection(

) int nLineStatuβ; int nDialRetVal; // Dialogic return value nDialRetVal Get_Line_Status( _My_csb ) ,- if ( nDialRetVal ~ return( nDialRetVal ) ; nLineStatuβ a My_cβb.lineβtat; if ( nLineStatuβ " t LS_LOOP ) { Debug ( 3, βprintf ( Debug_buf, "%d:

LD_Dial/Check_Uβer_Connection: uβer on hook

(lineβtat a _x) ", nLineStatus ) ); lp->bUβerIβOffHook - FALSE; nDialRetVal a RC USERONHOOK; i }f ( nLineStatuβ t LS_HOOK ) {

Debug ( 3, βprintf " ( Debugjbuf,

"%d: I_D_Dial/Check_U8er_Connection: card on hook " (lineβtat a x)", nLineStatuβ )

) ; lp->bCardIβOffHook FALSE; nDialRetVal RC CARDONHOOK;

return( nDialRetVal ) ;

* Macros that only work inside the Do*Command functions * * ****** * *************** ** ****************************/

♦define IF_NO_EVENT THEN_BREAK if (1IS_AN_EVENT) \ break;

♦define IF_TIMEOUT_THEN_INTBRNALFAIL \ if (TIMEOUT_EVENT) { \

REMOVE_EVENT; \ ep->result a RC_INTERNALLINE; \ lp->bSendEvent = TRUE; \ goto done; \

}

♦define IF_WRONG_EVENT_THEN_INTERNALFAIL(EV) \ if (NEXT EVENT.dwMsg != (EV) ) { \

REMOVE_EVENT; \ ep->result a RC_INTERNALLINE; \

lp->bSendBvent TRUE; \ goto done; \

}

♦define IF_WINDOWS EVENT_THEN_INTERNALFAIL \ if (NEXT_EVENT.blβWinBvent) { \

REMOVE_EVENT; \ ep->reβult « RC_INTERNALLINE; \ lp->bSendBvent _ TRUE; \ goto done; \

}

♦define REMOVB_TIMEOUT EVENTS {while (TRUE) {\ IF_NO_EVENT THEN_BREAK; \ if (ITIMEOUT EVENT) break; \ REMOVE EVENT? } }

// βtage value that meamβ we want to abort the commamd

♦define ABORT_STAGE 98

// βtage value that means we want to abort the command because of

// βerver shutdown

♦define SHUTDOWN STAGE 99

* Check For Event βee if driver haβ an event for any line.

βtatic void Check_For_Event ( boolean •GotBvent

) int nDialRetVal,- // Dialogic return value int nLineNum; /* line number (0 to MAX AL-1) int channel; int nDialBvent; // Dialogic event number int nDialCallState;// Dialogic call βtate number char info[80]; / info meββage /

EVTBLK My_event; / event block / boolean got_vr_event;

♦GotBvent > FALSE; nDialCallState a 0;

/ try to get an event */ ♦if defined(B_W) if ( cfe_DoβPointer •> 0 I {

Debug ( 1, sprintf ( Debugjauf, "LDJ_ial/Check_For_Event: cfe_DosPointer corrupted" ) ) ,- goto done;

} ♦endif nDialRetVal = gtevtblk ( ♦if defined(E_W)

( EVTBLK * ) cfe DosPointer ♦else tMy event ♦endif

); ♦if defined(B_W) memcpy ( tMy_event, ( void * ) cfe_ProtectedPointer, βizeof { My event ) ) ; ♦endif if ( nDialRetVal > 0 ) {

/* "get event" function failed 1 /

Debug ( 1, βprintf ( Debug_buf, "LD_Dial/Check_For_Bvent: gtevtblkO ret %d", nDialRetVal ) ); goto done;

}

/♦ if we didn't get a line event, check for a voice ♦ recognition event / if < nDialRetVal .. 0 ) { vr_Check_For_Bvent (

- tgot_vr_event, tnLineNum, tnDialEvent, tnDialCallState ); }

/ if we didn't get an event, return / if ( ( nDialRetVal a 0 ) tt ( !got_vr_event ) ) goto done; / we got an event, handle it / if ( nDialRetVal !- 0 ) { nLineNum a LINE ( My_event.devchan ) ; nDialBvent a My_event.evtcode; nDialCallState a My_evβnt.evtdata; } /* no elβe clause; already put info in

variableβ /

/* if event for diβallowed line, ignore */ if ( nLineNum >= MAX_AL ) goto done; lp a tline [nLineNum] if ( Debu fla > 2 ) if

"rece ved D a og c event %d ('%s') callstate %d" nDialBvent, DialEventNumToName[nDialBvent] , nDialCallState elβe βprintf ( info,

"received Dialogic event %d ('%s')", nDialBvent,

DialBventNumToName [nDialBvent] ) ; if ( nLineNum >« ( int ) nActualLineβ ) { if ( Debug_flag > 0 ) { βprintf " ( info,

"received Dialogic event %d from out-of-range channel %d", nDialBvent, chauinel

/

(nDialBvent > T VR_LAST) ) (nDialBvent > MAXTERM) ) ) {

range Dialogic event %d", nDialBvent

/♦ eventβ that tell whether caller iβ on- or off-hook; don't pass in / if ( nDialBvent -a T_LC ) {

/♦ call_βtate giveβ duration in 10 mβ ticks / /♦ on-hook for more than 4.8 seconds ? / if ( nDialCallState > 480 ) lp->bUβerIβOffHθθk - FALSE;

} elβe if ( nDialBvent -a T_LCON ) / do nothing / ( nDialBvent — T ONH ) lp->bCardIsOffHooic « FALSE; /* put info into lineU array */ ADD_DIALOGIC_EVENT(nDialBvent,nDialCallState) *GotBvent a TRUE; goto done; goto done-

done: return; }

/ *** * ** ***

*

* Set_End_Conditions -- Check and βet up end conditions for * play or " record, according to spec in command. *** **** /

βtatic void Set_End_Conditions ( void

if (

( cp->end_voice_on_any_digit tt cp->end_voice_on_digit_βnd ) I I ( cp-> end_oper_on_any_digit tt cp->end_oper on_digit_end )

) {

Debug ( 4, sprintf ( Debug_buf,

"LD_Dial/Set_End_Coιϊditions: bad digit mixture" ) ); ep-.result a RC_B___COMMAND; lp->bSendEvent TRUE; goto done;

) if ( cp->m_x_digits < 0 ) { ep->reβult a RC_BADMAXDIGS; lp->bSendBvent = TRUE; goto done;

} if ( cp- > end_voice_on_digit__end ) { error */

( char ) cβtr ) NULL) { ;

}

/* tricky: 0—unlimited(MAXJDIGITS) ; alβo handle too big */ lp->nMaxDTMF a

( ( ( cp-.max digitβ - 0 ) | | ( cp->max_digite > MAX_DIGITS T ) ? MAX_DIGITS : cp->max_digitβ ); done: return;

} /* function Set_End_Conditionβ */

DoPlayCommand -- Play the playlist asβociated with the current line.

7 static void DoPlayCommand (void) { int ifile_num; // indexed file number (0 to

MAX_IFILE-1) int pcount; // phrase count RWB My xrwb; // read/write block RWB My-rwb; // read/write block int item,- long size; // file size, for single-prompt file

struct findex far *fptr; int nDialRetVal; // Dialogic return value int nDialBvent; // Dialogic event number switch (lp->nCmdStage) { case 0: // start of command ep->nBvent a EV_PLAY; ep->βtring[0] a~'\0'; /*

* Do error -che eking .

/ if ( lp->pliβt_count a 0 ) { ep- .reβult a RC_EMPTYPLIST; lp- >bSendBvent TRUE; goto done ;

} if ( Up- >bCardlBθffHook ) { ep- >reβult RC_CARDONHOOK; lp - >bSendBvent a TRUE ; goto done; i }f ( !lp->bUserl8θffHook ) { ep->result a RC_USERONHOOK; lp->bSendBvent " TRUE; goto done;

/ nDialRetVal a Check Uβer Connection ; if ( nDialRetVal ) J ep-.reβult a nDialRetVal; lp->bSendEvent a TRUE; goto done; }

*/

Set_Bnd_Conditions ( ) ; if 1 lp->bSendBvent ) goto done; if ( cp->fluβh digits_at_start ) {

Fluβh_All_DTMF ( ) ; if ( ϊp-.bSendEvent ) goto done;

)

/* * Build play-list in lp->Fileβ_Index[] . */ for ( item a 0 ; item < lp- _pliβt_count ; item++ ) { /* βee if play- item iβ indexed file or βingle -prompt file */ ifile_num lp- >pliβt [item] . ifnum; pcount a lp- >plist [item] . index; if ( ifile_num >a 0 ) { /* indexed file */ lp- _ Files_Index[item] .type a 0 ; lp- >Files~Index [item] . FileHandle a lp- >ifile [if ile_num] . if handle ,- lp->Fileβ_Index [item] .BlockPoβition a lp- >ifile [ifile_num] . indextab [pcount] . βtart ;

lp->Files_Index[lp->plist_count] .BlockPoβition a (-1) ; lp->Fileβ~Index[lp->pliβt~count] .BlockLength ( -l ) ,- /

♦ Build read/write Block.

*/ clrrwb ( tMy_rwb ) ; (E_W) /* Windowβ / alloc_DOS_mem (

( (_p-_pliβt_count + 1 ) βizeof ( βtruct findex) ) , t( lp->fiJ__sPointer ), t( lp->fi_ProtectedSelector ), t( lp->fi_ProtectedPointer ) ); if ( lp->fi_ProtectedSelector aa o ) { ep->reβult a RC_RAMFULL;

lp- >bSendBvent TRUE ; goto done; memcpy (

( void ) lp-_fi_ProtectedPointer, deβt / ( void ♦ ) lp->Fileβ_Index, / βource */ ( (lp->plist_count + ϊ ) * βizeof ( βtruct findex) )

) ; fptr a ( void ) ( lp->fi_DoβPointer ) ,- fptr a lp->Fileβ_Index; ♦endif

My_rwb.indexβeg a FP_SEG ( fptr ) ; My_rwb.indexoff a FP_OFF ( fptr ) ;

/♦ βet parameters / if ( cp->βnd_voice_on_any_digit )

My_rwb.termdtmf a~β'~ else if ( cp->end_voice_on_digit_end ) My_rwb.termdtmf a cp~->eήd_digit;

My_rwb.termdtmf « '\0'; My_rwb.maxdtmf a lp-.nMaxDTMF; My-rwb.rwbflags a 0; My_rwb.loopsig a l; / terminate on hangup /

♦if defined(E_W) / Windowβ */ alloc_DOS_mem (

" - βizeof ( My_rwb ) , t( lp->rwb_DoβPointer ), t( lp->rwb_ProtectedSelector ), t( lp->rwb~ProtectedPointer )

) ; if ( lp- >r _rb_ProtectedSelector aa o ) { ep- .result a RC_RAMFULL; lp->bSendEvent a TRUE; goto done; memcpy ( ( void ♦ ) lp->rwb_ProtectedPointer,

( void ) tMy rwb, βizeof ( My_rwb ) ) ; ♦endif

/* Start playing play-liβt.

*/ nDialRetVal - xplayf (

CHANNEL ( lp--nLine um ) , ( PM_FILES I PM NDX ) , ♦if defined(B_W) ~ / Windowβ /

( RWB ) lp->rwb_DoβPθinter ♦else / non-Windows / tMy rwb ♦endif

); switch ( nDialRetVal ) { case E_SUCC: /♦ Function was successful. / break; case E_BADDL: /* D4x hardware error. / ep~>reβult a RC_BADLINE; lp->bSendEvent _ TRUE; goto done; caβe E_BADPAR: /♦ Bad parameter. / ep~>reβult a RC_INTERNAL;

lp->bSendEvent a TRUE; goto done,- default: / some unknown error ♦/ ep-.reβult a RC_INTER_____INE,- lp->bSendEvent a TRUE; goto done; lp->voice_has_ended a FALSE; lp->digitβ_have_ended FALSE; lp->nCmdStage a~i; break; case 1: / we expect an end-of-playing event /

IF_NO_EVENT_THEN BREAK; IF WINDOWS_EVENT~THEN_INTER____FAIL; nDialBvent a NEXT_EVENT.nDialBvent; REMOVE_EVENT; ~ switch " ( nDialBvent ) { caβe T SILOFF: / Silence off. / caβe T~SILON: / Silence on. / goto done; // ignore; remain in βame βtate

/ cloβe all fileβ (except indexed fileβ) in play-liβt / for ( item a 0; item < lp->pliβt_count; item++ ) if { lp->cloβe_after[item] ) cloβe ( lp~>Fileβ_Index[item] .FileHandle ) ; ♦if defined(B_W) free_DOS_mβm ( t(lp->fi_DosPointer) , t(lp->fi_ProtectedSelector) , t(lp->fi_ProtectedPointer) ) ,- free_DOS_mem ( t(lp->rwb_DoβPointe_) , t(lp->rwb_ProtectedSelector) , t(lp->rwb_ProtectedPointer) ) ; ♦endif βwitch ( nDialBvent ) { case T_MAXDT: / Maximum DTMF digitβ received. / caβe T TERMDT:/* Terminating DTMF digit received.

*/ caβe T MDTMF: / Terminated by masked DTMF digit */ lp->nTempVRetVal a RCJSNDFLAG; break; ~ case T_EOF: /* Eof reached on playback. / // Get_Line_Statuβ ; lp->nTβmpVRetVal « RC_SUCCEBD; break; caβe T_LCTERM: / Terminate by drop in loop

~ signal / ep--result a RC_USERHUNGUP; // don't bother getting any digits they may have entered (?) lp->bSendEvent a TRUE; goto done; case T STOP: /♦ Rec/play/getdtmf βtopped. ♦/ case T~HFAIL: / Hardware failure. / ep * -.result - RC_BADLINE; lp->bSendEvent a TRUE; goto done; default: / βαme unknown event / ep->reβult a RC_INTERNALLINE; lp-.bSendEvent a TRUE; goto done; }

if ( !cp-_get_digitβ_at_end ) { ep->reβult " a lp->nTempVRetVal; lp->bSendBvent a TRUE,- goto done; }

/♦ tri /

/ ) ;

/* ♦ maxβil a add up all silence between digitβ -- not what we want

*/ /♦ My xrwb.maxβil a MSEC TO SEC(cp->βtart_timeout) ; / My_xrwb.intrdig - ( byte " ) MSEC_TO_SEC (

~cp->interdigit_to ) ; My_xrwb.rwbflags a 0;

My_xrwb.loopsig a l; /* terminate on hangup */ ♦if defined(B_W) /♦ Windows ♦/ alϊoc_DOS mem ( ~ MAX_DIGITS + 1, t( lp->dtmf_DoβPointer ), t( lp->dtmf_ProtectedSelector ), t( lp->dtmf_ProtectedPointer )

); if ( lp->dtmf_ProtectedSβlector a. o ) { ep->reβult a RC_RAMFULL; lp->bSendBvent a TRUE; goto done; fptr a ( void ♦ ) lp->dtmf_DosPointer; fptr a dial dtmfbuf[lp-.nLineNum] ; ♦endif

My xrwb.xferβeg a FP SEG ( fptr ) ; My~xrwb.xferoff a FP~OFF ( fptr );

My_xrwb.maxbyteh a o~ My_xrwb.maxbyte a MAX DIGITS; ♦if defined(B_W) / Windowβ / alloc_DOS_mem ( βizeof ( My_xrwb ) , t( lp->xrwbJ_>oβPointer ) , t( lp->xrwb~ProtectedSelector ), t( lp->xrwb_ProtectedPointer )

); if ( lp->xrwb_Protecte_Selector aa o ) { ep->reβult a RC_RAMFULL; lp->bSendE ent a TRUE;

goto done;

} memcpy ( ( void ♦ ) lp->xrwb_ProtectedPointer,

( void ) tMy_xrwb, βizeof ( My_xrwb ) ) ;

♦endif

/♦ βtart recording data ♦/ nDialRetVal getdtmfβ (

CHANNEL ( lp--nLineNum ), ♦if define (B_W) ( RWB ) lp->xrwb_DoβPointer / Windowβ /

_My_xrwb /* non-Windowβ / ♦endif

) ; βwitch ( nDialRetVal ) { caβe E_SUCC: / Function waβ βucceββful. / break; default: / some unknown error / ep->result > RC_INTERNALLINB; lp->bSen_Event a TRUE; goto done;

/* now wait until we get an event / lp->nCmdStage a 2; goto done; caβe 2: / we expect an end-of-input event / IF_NO_EVENT_THEN_BREAK; IF WINDOWS_EVBNT_THEN_INTERNALFAIL; nDialBvent~a NEXTJEVENT.nDialBvent;

REMOVE_BVENT; βwitch ( nDialBvent ) { caβe T_SILOFF: / Silence off. */ caβe T_SIL0N: /* Silence on. / goto done; // ignore; remain in βame βtate ♦if defined }(B_W) memcpy ( dial_dtmfbuf[lp - line] , ( void ) lp->dtmf ProtectedPointer, ~MAX_DIGITS + 1 ) ; free_DOS_mβm ( t(lp->xrwb_DoβPointer) , t(lp->xrwb_ProtectedSelector) , t(lp->xrwb_ProtectedPointer) ) ; free_DOS_mem ( t(lp->dtmf_DoβPointβr) , t(lp->dtmf_ProtectedSelector) , t(lp->dtmf ProtectedPointer) ) ; ♦endif βwitch ( nDialBvent ) { caβe T MAXDT: / Maximum DTMF digitβ received. / ep~>reβult - RC iATALENGTH; βtrcpy ( ep--string, dial_dtmfbuf[lp - line] ); lp->bSendEvent a TRUE; ~ goto done; caβe TJTERMDT: /♦ Terminating DTMF digit received. / caβe T_MDTMF: /♦ Terminated by maβked DTMF digit*/ ep->reβult a RCJSNDFLAG; βtrcpy ( ep->βtring, dial_dtmfbuf[lp - line] ); lp->bSendBvent a TRUE; goto done; caβe TJTIME: / Rec/play/getdtmf timed out. / caβe T SIL: /♦ Maximum silence received. /

caβe T_IDTIME: / Interdigit delay exceeded. / βtrcpy ( ep->βtring, dial_dtmfbuf [lp - line] ); if ( ep--βtring[0] aa '\o T ) ep-.reβult a RC_STARTTIMEOUT,- ep-.reβult a RC_INTERDIGITTIMEOUT; lp->bSendEvent a TRUE; goto done; caβe T_LCTERM: / Terminate by drop in loop - βignal / ep->reβult a RCJJSERHUNGUP; βtrcpy ( ep->string, dial_dtmfbuf[lp - line] ); lp->bSendEvent a TRUE; goto done; caβe T_STOP: / Rec/play/getdtmf stopped. / caβe T_HFAIL: / Hardware failure. / ep->reβult a RC_BADLINE; lp->bSendBvent a TRUE; goto done; default: / βome unknown event / ep->reβult - RC_INTERNALLINB; lp->bSendEvent a TRUE; goto done; / never get here / bread.; case SHUTDOWN_STAGE: // βerver iβ βhutting down ep-.result a RC_SERVERSHUTDOWN; lp-.bSendEvent _ TRUE; goto done; default: // unknown command stage ep->reβult RC_INTERNAL; lp-.bSendEvent a TRUE; goto done; } done: // if complete, check to βee if we have to do clean up if (lp-.bSendEvent) {

// If exception happenβ while we are proceββing command, // free allocated DOS memory blocks. If the pointer // variableβ point to zeroβ, free_DOS_mem() will juβt // return without doing anything.- free_DOS_mem ( t(lp->fiJDoβPointer) , t(lp->fi_ProtectedSelector) , t(lp->fi_ProtectedPointer) ) ; free_DOS_mem ( t(lp->rwb_DoβPointer) , "" t(lp->rwb_ProtectedSelector) , t(lp->rwb_ProtectedPointer) ) ,- free_DOS_mem { t(lp->xrwb_DoβPointer) , t(lp-_xrwb_ProtectedSelector) , t(lp->xrwb_ProtectedPointer) ); free_DOS_mem ( t(lp->dtmf_DoβPointer) ,- — — t(lp->dt-if_ProtectedSelector) , t(lp->dtmf ProtectedPointer) ) ,-

} return ; } // end function (DoPlayCommand)

/ ****** * ********************

*

* DoHangupCommand -- Hang up the current line.

βtatic void DoHangupCommand (void) int nDialRetVal; // Dialogic return value int nDialBvent; // Dialogic event number βwitch (lp->nCmdStage) { caβe 0: / βtart of command /

REMOVE_ALL_EVENTS; ep->nEvent a EV_HUNGUP; if ( llp-_bCardIβOffHook ) { ep-.reβult a RC_CARDONHOOK; lp-.bSendEvent a TRUE; goto done,-

}

/ Ask channel to go on-hook.

*/ nDialRetVal βethook ( CHANNEL ( lp->nLineNum ),

H_ONH ) ; βwitch ( nDialRetVal ) { caβe E_SUCC: / Function waβ successful. /

/ βucceββ / break; caβe E_BADDL: /♦ D4x hardware error. / ep->reβult - RC_BADLINB; lp->bSendBvβnt a TRUE; goto done; defaul :/♦ βoroe unknown error / ep->reβult a RC_INT_RNALLINB; lp--bSendEvent a TRUE; goto done;

} lp->nCmdStage a l;

/♦ now wait until we get an on-hook-complete event / goto done;

C&θβ _» *

IF_NO_EVENT_THEN BREAK; IF_WINDOWS_EVENT-THEN_INTERNALFAIL; nDialBvent a NEXT_EVENT.nDialBvent; REMDVEJEVENT; βwitch ( nDialBvent ) { caβe TJSILOFF: / Silence off. */ caβe T_SILON: / Silence on. / goto done; // ignore; remain in βame βtate caβe T_0NH: /♦ Onhook complete. / break; default: / some unknown event /

ep-.result > RC_INT_RNALLINE; lp--bSendEvent a TRUE; goto done; // hung up βucceβsfully

// wait a few βecondβ to maϋce βure CO βeeβ we are hung up lp->timeout TIME0UT_AFTER_O_H0OK; REMOVE_TIMEOUT_EVENTS; lp->nCmdStage a 2; / now wait until we get a timeout event ♦/ goto done;

IF_NO EVENTJTHENJBREAK; if ( ITIMEOUTJSVENT) {

REMOVE_BVENT;

// ignore it and keep waiting goto done; R____VB_EVENT;

/* make assumption */ lp->bϋβerIβOffHook a FALSE;

/ βucceββ */ lp->bCardIβOffHθθk « FALSE; ep->reβult - RC_SUCCEED; lp->bSendBvent a TRUE; goto done; caβe SHUTDOWN_STAGE: // βerver iβ shutting down ep->reβult a RC_SERVERSHUTDOWN; lp->bSendBvent a TRUE; goto done; default: // unknown command βtage ep-.result - RC_INTERNAL; lp->bSendBvent a TRUE; goto done;

} done: return; } // end function (DoHangupCommand)

I*

*

* DoSendDTMFCommand -- Send DTMF digitβ out on the current

♦ line. ** ** ♦ ♦ ****** */ βtatic void DoSendDTMFCommand (void) int nDialRetVal; // Dialogic return value char *ptr; int nDialBvent; // Dialogic event number ♦if defined(E_W) // Windowβ / long call_DoβPointer a 0L; βhort call~ProtectedSelector a 0; long call~ProtectedPointer a 0L; ♦endif

βwitch (lp->nCmdStage) { caβe 0: / βtart of command */ ep-_nEvent a EV_SENTDTMF; if ( Up->bCardIβOffHook ) { ep->reβult - RC_CARDONHOOK; lp->bSendEvent a TRUE; goto done; if ( !lp->bUserIβOffHook ) { ep->reβult » RCJJSERONHOOK; lp->bSendEvent a TRUE; goto done;

} / nDialRetVal a Check Uβer Connection0 ; if ( nDialRetVal ) J ~ ep-.result a nDialRetVal; lp-.bSendEvent a TRUE; goto done;

*/

* Aβk channel to send d g tβ */ ♦if defined(E_W) / Windows / alϊoc_DOS_mem ( strlen ( cp->βtring ) + 1, tcallJ3oβPointer, teall_ProtectedSelector, tcallJProtectedPointer

); if ( call_ProtectedSelector aa 0 ) { ep->result a RC_RAMFULL; lp->bSendBvent a TRUE; goto done;

} memcpy (

( void ♦ ) call_ProtectedPointer, / dest / ( void ) cp-.String, / source / strlen ( cp->string ) + 1

) ; ♦endif

nDialRetVal a dial (

CHANNEL ( lp-.nLineNum ) , ♦if defined(B_W)

( void ) callJ-OβPointer ♦else cp-.βtring /♦ dial βtring ♦/ ♦endif

); ♦if defined(B_W) / Windowβ / free_DOS_mem ( tcall_DoβPointer, tcall_Protecte_Selector, tcall ProtectedPointer ) ; ♦endif βwitch ( nDialRetVal ) { caβe E_SUCC: / Function waβ βuccessful. ♦/ /♦ βucceββ */ break; case E_BADDL: / D4x hardware error. ♦/ ep->reβult a RC_BADLINE; lp->bSendBvent _ TRUE; goto done; default:/* some unknown error */ ep->reβult a RC_INTERNALLINB; lp-.bSendEvent _ TRUE; goto done; lp->nCmdStage a l;

/ now wait until we get an event ♦/ goto done; caβe 1: / we expect a digit-βtring-dialed event / IF NO_BVENT THEN_BRBAK; IF-WINDOWS_EVENT_THEN_I________FAIL; nD_alBvent-a NEXT_EVENT.nDialBvent;

REMOVB_EVENT; switch ( nDialBvent ) { caβe T_SIL0FF: / Silence off. / caβe T_SIL0N: /* Silence on. */ goto done; // ignore; remain in βame state caβe T_DIAL: / Dialing complete. / / ♦ " βuccβββ */ ep-.reβult a RC_SUCCEED; lp->bSendEvent TRUE,- goto done; default:/ βome unknown event / ep->reβult a RC_INTERNALLINE; lp->bSendEvent TRUE; goto done; }

/ never get here / break; caβe SHUTDOWN_STAGE: // βerver iβ βhutting down ep->reβult a RC_SERVERSHUTDOWN; lp->bSendEvent a TRUE; goto done; default: // unknown commamd stage ep->reβult a RC_INTBRNAL; lp->bSendBvent _ TRUE; goto done;

} done:

I I it complete, check to βee if we have to do clean up if (lp->bSendEvent) {

// do βomething here 111 return; } // end function (DoSendDTMFCommand)

* I* ♦ DoCallOutCommand -- Dial out on the current line. _ a,----------------.-----*-------*---**-.--.****/ static void DoCallOutCommamd (void)

CPB My_cpb; // Channel parameters

CAR My car; // Call Analyβiβ Reβultβ int nDialRetVal; // Dialogic return value char *ptr; int nDialBvent; // Dialogic event number int nDialCallState; // Dialogic call state number

♦if defined(B_W) // windows ♦/ long caϊ__DoβPointer a 0L; βhort call_ProtectedSelector a 0; long call_ProtectedPointer a 0L; ♦endif βwitch (lp->nCmdStage) { caβe 0: // βtart of command REM_VB_ALL_EVENTS; ep->nBvent a EV_CALLOUT; ep->voice_after_anβwer a 0; lp->βilent a TRUE; if ( lp-.bCardlβOffHook ) { ep->reβult - RC_CARDOFFHOOK; lp->bSendBvent a TRUE; goto done;

/*

Convert βtring to dial from Hayeβ format to * Dialogic.

♦ Note: Dialogic doeβn't βupport "wait for dial-tone"

*/ ptr whi ) { )

tone" into "pauβe" /

/ βet channel parameters / Set_Ch_nnel_Mask { ( C_SILOFF + C_SILON + C_LC +

C_LCON ) , 1 ) ; if ( lp-.bSendEvent ) goto done; clrcpb ( tMy_cpb ) ,- / after thiβ many RINGBACKβ, give up /

My_cpb.nbrdna a cp->answer_rings; My " cpb.ansrdgl a 150; ♦if defined(B~W) / Windows / airoc_DOS_mem ( ~ ~ βizeof ( My_cpb ) , t( lp->cpb_DoβPointer ), t( lp-_cpb_ProtectedSelector ), t( lp->cpb~ProtβctedPointer )

) ; if ( lp->cpb_ProtectedSelector aa 0 ) {

Debug (

3, sprintf ( Debugjbuf,

"LD_Dial/DoCallOutCommand:%B(PCB, %d bytes)", cDoβMemErr, βizeof ( My cpb ) ) ) ; ep->reβult a RC_RAMFULL; lp->bSβndEvent a TRUE; goto done; memcpy (

( void ♦ ) lp->cpb_ProtβctedPointer, /♦dest /

( void ) tMy_cpb, / βource / βizeof ( My cpb )

) ; ♦endif nDialRetVal setcpaπn (

CHANNEL ( lp->nLinβNum ),

♦if defined(E_W)

( void * ) lp->cpb_DoβPointer / Windowβ / ♦elβe

♦if defined(B_W) free_DOS_mem ( t(lp->cpb_DoβPointer) ,

~ - t(lp->cpb_ProtectedSelector) , t(lp->cpb ProtectedPointer) ) ,- ♦endif βwitch ( nDialRetVal ) { caβe E_SUCC: /♦ Function waβ successful. / /♦ success / break,- caβe E BADDL: /♦ D4x hardware error. / ep~>reβult a RC_BADLINE; lp->bSendBvent " = TRUE; goto done;

default:/ some unknown error ♦/ ep->reβult - RC_INTBR____LINE; lp--bSendEvent a TRUE; goto done;

}

/* Aβk channel to call out

*/ ♦if defined(B_W) / Windowβ / alloc_DOS_mem ( βtrlen ( cp->βtring ) + 1, tcall_DosPointer, tcall~ProtectedSelector, tcall " ProtectedPointer

) ; if ( call_ProtectedSelector aa 0 ) { ep->reβult a RC_RAMFULL; lp->bSendEvent a TRUE; goto done; memcpy (

( void ♦ ) call_ProtectedPointer, /♦ deβt / ( void * ) cp->βtring, /* source / βtrlen ( cp--βtring ) + 1

) ; ♦endif nDialRetVal a callp (

CHANNEL ( lp->nLineNum ) , ♦if defined(B_W)

~ ( void ♦ ) call DoβPointer cp--βtring / dial βtring ♦/ ♦endif );

♦if defined(B_W) / Windowβ / free_DOS_mβm ( tcall_DoβPointer,

" tcall_ProtectedSelβctor, tcall_ProtectedPointer ) ,-

♦endif βwitch ( nDialRetVal ) {

caβe 1: /* we expect a call-anβwered event / IF_N0 EVENT_THEN_BREAK;

IF_WINDOWS_EVENT_T___N_INTERNALFAIL; nDialBvent a NEXT_EVENT.nDialBvent;

nDialCallState NEXT_EVENT . nDialCallState ; REMOVE_EVENT ; βwitch ( nDialBvent ) { caβe T_SILOFF : /♦ Silence off lp- >βilent FALSE; goto done; // remain in same state case T_SILON : /* Silence on . / lp- >βilent TRUE; goto done ; // remain in βame βtate case T_ CATBRM: / Call analyβiβ termination. /

/♦ restore usual settings / Set Channel_Mask ( CEM_STANDARD, 1 ); if T lp->bSendEvent ) ~ goto done; switch ( nDialCallState ) { caβe CA_BUSY: / Called line iβ buβy. / caβe CA_OPINT: / Called line recvd oper intercept / lp->nTempVRetVal a RCJ3USYDEST; break; case CA_NOAN: / Called line did not answer. */ case CA_N0RNG: /♦ Called line did not ring. */ lp->nTempVRetVal a RC_NOANSWER; break; case CA_CONN: / Called line connected. / lp->bϋserIsOffHook a TRUE; /* βucceβs ♦/ lp->bCardIβOffHook a TRUE; ep->result a RC_SUCCEED; if ( qp->wait_for_silence ) { ep->voice_ _fter_answer a 0;

♦ Get call analysis results */ if defined (E W) /* Windowβ */ alloc_DOS_mem( βizeof ( My car ) , teall_DoβPointβr, tcallJ?rotectedSelector, tcall~ProtectedPointer

); if ( call_Protecte___elector aa o ) { ep-.result a RC_RAMFULL; lp->bSendBvent a TRUE; goto done;

♦endif nDialRetVal a getcar (

CHANNEL ( lp-.nLineNum ) , tMy_car

);

♦if defined (E W) /♦ windowβ / memcpy ( tMy_car, / βource /

( void ♦ ) call_ProtectedPointer, βizeof ( My_car )

) free_DOS_mem ( tcallJ-osPointer,

tcall_ProtectedSelector, tcall_ProtectedPointer ) ;

♦endif ;

My_car.anβrsize 10;

} } lp--bSendEvent a TRUE; goto done; default: / βorne unknown callstate ♦/ ep->result - RC_INTERNALLINB; lp->bSendEvent _ TRUE; goto done; break,- default:/* some unknown event */ /* restore uβual βettingβ /

Set_Channel_Mask ( CEM_STANDARD, 1 ) ,- if ( lp-.bSendEvent ) goto done; ep-.reβult - RC_INTERHALLINE; lp->bSendEvent TRUE; goto done;

/ no anβwer and we are βtill off-hook; hang up */ / Ask channel to go on-hook.

*/ nDialRetVal a sethook (

CHANNEL ( lp-.nLineNum ) , H_ONH

) ; βwitch ( nDialRetVal ) { caβe E_SUCC: / Function waβ βucceββful. ♦/ / βucceββ */ break; caβe E_BADDL: /♦ D4x haurdware error. / ep->reβult a RC_BADLINB; lp-.bSendEvent a TRUE; goto done; default:/ βome unknown error / ep->reβult - RC_INT_RNALLINE; lp--bSendEvent _ TRUE; goto done; lp->nCmdStage a 2;

/♦ now wait until we get an on-hook-complete event / goto done; case 2: IF_NO_EVENT_T-_BN_BR___.;

IF WINDOWS_EVENT_THEN_INTERNALFAIL; nDialBvent~a NEXT_EVENT.nDialBvent; REMOVE_EVENT; switch ( nDialBvent ) { caβe T_SIL0FF: / Silence off. / caβe T_SIL0N: /♦ Silence on. ♦/ goto done; // ignore; remain in βame βtate

caβe T_ONH: / Onhook cooiplete. ♦/ break; default: / βome unknown event / ep-_reβult . RC_INT_RNALLINE; lp->bSendBvent _ TRUE; goto done;

// hung up βucceββfully

// wait a few βecondβ to make βure CO βeeβ we are hung up lp->timeout ■ TIMEOUT_AFTER_ONHCOK;

REMOVE_TIMEOUT_BVENTS7 lp->nCmdStage _ 3;

/♦ now wait until we get a timeout event ♦/ goto done;

IF_NO_EVENT_THBN_BREAK; if (ITIMEOUTJBVENT) { RKMOVE_EVENT; // ignore it and keep waiting goto done;

}

REMOVE -VENT;

/♦ make aββu ption ♦/ lp->bϋβerIβOffHook . FALSE;

/* βucceββ / lp->bCardIβOffHook . FALSE; ep->reβult lp->nTempVRetVal; // βaved result code lp->bSendEvent a TRUE; goto done,- caβe SHUTDOWN_STAGE: // βerver iβ βhutting down ep->reβult a RC_SERVERSHUTDOWN; lp->bSendBvβnt a TRUE; goto done; default: // unknown command βtage βp->rββult a RC_INTERNAL; lp->bSendBvent a TRUE; goto done,-

} done:

// if complete, check to βee if we have to do clean up if (lp->bSendBvent) {

II do βome hing here free_DOS_mem ( tcall_DoβPointer, tcall_ProtectedSelector,

~ tcall_ProtectedPointer ) ,- free_DOS_mem ( t(lp->cpb_DoβPointer) , — — t(lp->cpb_ProtectedSelector) , t(lp->cpb ProtectedPointer) ) ;

} return ; } // end function (DoCallOutCαmmand)

♦ DoConnectCommand -- Grab ownership of the current line.

βtatic void DoConnectCcmmamd (void) {

CPB My_cpb; // Channel parameters

CSB My_cβb; // Chamnel βtatuβ block int nDialRetVal; // Dialogic return value switch (lp->nCmdStage) { caβe 0: // βtart of command

// fill in βtandard parts of event ♦/ REMOVE_ALL_EVENTS; ep->nBvent a BV_CONNECT_D; ep->num lineβ a-nActualLineβ; // valid even if and failβ / // initialize line / lp-.nLineNum a cp-.nLine;

// check if loop current preβent or off hook nDialRetVal a Get Line_Status( tMy_cβb ) ; if ( nDialRetVal T { ep->reβult a nDialRetVal; lp->bSendEvent a TRUE; goto done; if ( 1 ( My_cβb.lineβtat t LS_HOOK ) || // line off

hook 1 ( My_cβb.linββtat t LS_RING ) || // ring incoming ! ( My_cβb.lineβtat t LS_SIL ) | | // silence off

! ( My_cβb.linestat t LS_LOOP ) // loop current - — preβent

) { ep-.reβult a RC_BUSYLINE ; lp- >bSendBvent a TRUE; goto done; }

// βet channel parameterβ ♦/ clrcpb ( tMy_qpb ) ; ♦if defined(B_W) // Windowβ / alloc_DOS_mem { βizeof ( My_cpb ) , t( lp->cpb_DoβPointer ) , t( lp-.cpbJProtectedSelector ) , t( lp->cpb~ * ProtectedPointer ) ) ; if ( lp->cpb_ProtectedSelector aa o ) { Debug (

3, βprintf ( Debug_buf,

"LD_Dial/DoConnectCooB_and:%β (CPB, Vd byteβ) " , cDoβMemErr, βizeof ( My_cpb ) )

) ; ep- >reβult a RC_RAMFULL; lp- >bSendBvent Z TRUE; goto done; } memcpy (

( void ) lp- >cpb_ProtectedPointer, // dest

βource

Windowβ

lector ), nter ) ) ;

/

caβe SHUTDOWN_STAGE: // βerver iβ βhutting down ep->reβult a RC_SERVBRSHUTDOWN; lp->bSendEvent a TRUE; goto done; default: // unknown command βtage ep->rββult a RC_INTERNAL; lp->bSendBvent _ TRUE; goto done;

done:

II it complete, check to βee if we have to do clean up if (lp->bSendBvent) { // do βome hing here ! ! !

} return;

// end function (DoConnectCommand)

/************************* * *

*

DoDiβconnectCommand -- Relinquiβh ownership of the current

line.

*

******************************************************/ βtatic void DoDiβconnectCommand (void) { int ifile_num; / indexed filenum (0 to MAX_IFILE-1) / int nDialRetVal; // Dialogic return value int nDialBvent; // Dialogic event number βwitch (_p-_nCmdStage) { caβe 0: // βtart of command

{

case 1: IF_NO_EVENT_THEN BREAK;

IF_TIMEOUT THEN_ΪNTERNALFAIL; IF_WINDOWS_EVENT_THEN_INTERNALFAIL; nDialBvent~a NEXT_EVENT.nDialBvent; REM VE_EVEN ; βwitch ( nDialBvent ) { caβe T_SILOFF: / Silence off. */ caβe T SILON: /♦ Silence on. /

goto done; // ignore; remain in βame βtate caβe T_ONH: /♦ Onhook complete. ♦/ /♦ success ♦/ brea ; default: / βome unknown event / ep->reβult « RC_INTERNALLINB; lp->bSendEvent a TRUE; goto done; Set_Channel_Maβk ( 0, 0 ) ; if ( lp->bSendEvent ) goto done; // hung up βucceββfully

// wait a few βecondβ to make βure CO βeeβ we are hung up lp->timeout - TIMEOUT AFTER_ONHOOK;

REM0VE_TIMBOUT_EVENTST lp-.nCmdStage Z 2;

/* now wait until we get a timeout event / goto done;

IF_NO BV_NT_THBN BREAK; if (ITIMEOUT_BVENΓ) {

R____VE_BVENT; // ignore it and keep waiting goto done;

REMOVE_EVENT; /* make aββumption */ lp->bϋβerIβOffHook . FALSE;

/* 8UCCβββ */ lp->bCardIβOffHook « FALSE; lp->bConnected a FALSE; ep->reβult a RC_SUCCEED; lp->bSendBvent a TRUE; goto done; caβe SHUTDOWN_STAGE: // βerver iβ βhutting down ep-.result ■ RC_SERVBRSHUTDOWN; lp-_bSβndBvβnt a TRUE; goto done; default: // unknown command βtage ep->reβult a RC_INTBRKAL; lp->bSendBvent a TRUE; goto done;

done: return;

} // end function (DoDiβconnectCommand)

I*

DoWaitForCallCommand -- Wait for an incoming call on the current line and answer it.

7 static void DoWaitForCallCommand (void)

{ int nDialRetVal; // Dialogic return value int nDialBvent; // Dialogic event number βwitch (lp->nCmdStage) { caβe 0: // βtart of command REMOVE_ALL EVENTS; ep->nEvent-- EV_INCOMINGCALL; if ( lp--bCardIβOffHook ) { ep->reβult « RC_CARDOFFHOOK; lp->bSendBvent Z TRUE; goto done; }

// Check line statue nDialRetVal a Get_Line Status( tMy_cβb ) ; if ( nDialRetVal ) { ep-.result a nDialRetVal; lp--bSendEvent a TRUE; goto done; if ( ! ( My_cβb.lineβtat t LS_HOOK ) || // line off hook // Not to detect no βilence βince at thiβ moment, there may // be ring coming in.

// 1 ( My cβb.lineβtat t LS_SIL ) | | // βilence off

! ( My_csb.lineβtat t LS_LOOP ) // loop current preβent

) { ep- . result a RC_BUSYLINE ; lp- - bSendEvent a TRUE; goto done; }

// If ring already present, anβwer it if ( 1 ( My_cβb.lineβtat t LS RING ) ) {

ADD DIALOGIC EVENT( T RING . 0 )

}

// Aβk channel to report inbound callβ. Set_Channel_Maβk ( C_RING + CEM_STANDARD, cp->amβwer_ringβ ) ; if ( lp-.bSendEvent )

forever ♦/ ec ( ) + cp->total_timeout ) ;

/♦ now wait until we get an incoming-call event / lp->nCmdStage a l; goto done; case 1: / we expect an incoming-call event /

IF_NO_EVENT THEN_BREAK; if (TIMBOUT-BVENT) { REMOVE_EVENT; ep--result a RC_TOTALTIMEOOT; lp->bSendEvent a TRUE,- goto done;

}

IF WIND0WS_BVENT_THEN_INTER_1ALFAIL; nDialBvent-a NEXT_EVEN .nDialBvent; REMOVE_EVENT; switch ( nDialBvent ) { caβe T_SILOFP: / Silence off. / caβe TJSILON: / Silence on. ♦/ goto done; // ignore; remain in βame βtate Set_Channel_Maβk ( CEM_STANDARD, 1 ) ; if ( lp-.bSendEvent ) goto done; βwitch ( nDialBvent ) { caβe T_RING: / Ringβ received. / / ♦" βucceββ / brea ,- default:/* βome unknown event */ ep-.reβult - RC_INTERNALLINB; lp->bSendBvent a TRUE; goto done;

/* * Aβk chamnel to go off-hook.

*/ nDialRetVal a βethook (

CHANNEL ( lp->nLineNum ) , H_0FFH

) ; βwitch ( nDialRetVal ) { caβe B_SUCC: / Function waβ βucceββful. ♦/

/ βucceββ / break; case B_BADDL: /♦ D4x hardware error. ♦/ ep->result a RC_BADLINE; lp->bSendBvent a TRUE; goto done; default:/ βome unknown error / ep->reβult a RC_INTBRNALLINE; lp->bSen_Bvent _ TRUE; goto done; lp->timeout a TIMEOϋT_FOR_BROKE ; REMOVE_TIMEOUT_BVENTS;

/ now- ait until we get an off-hook-complete event ♦/ lp->nCmdStage a 2; goto done;

IF NO BVBNT_THBN BREAK; IF~TIMEOUT_THBN_ΪNTBRNALFAIL;

IF-WINDOWS_EVENT_THEN_INT_RNALFAIL; nDialBvent~a NEXT_EVEN .nDialBvent; REMOVE_EVENT; switch ( nDialBvent ) { caβe TJSILOFF: / Silence off. / caβe T~SIL0N: / Silence on. / goto done; // ignore; remain in βame βtate caβe T_0FFH: / Offhook complete. / lp-.bCardlβOffHook a TRUE; lp->bϋβerIs0ffHook a TRUE;

/* success / βp->result a RC_SUCCEED;

lp-.bSendEvent a TRUE; goto done,- default:/* βαme unknown event */ ep->reβult - RC_INTBRNALLINB; lp->bSendBvent a TRUE; goto done;

/* never get here / break; case SHDTDOWN_STAGE: // βerver iβ βhutting down ep-.result a RC_SERVERSHUTDOW ; lp->bSendEvent a TRUE,- goto done; default: // unknown command βtage ep->result a RC CNTERNAL; lp->bSendBvent a TRUE; goto done;

done: return; } // end function (DoWaitForCallCommand)

♦ DoGetDTMFCommand -- Get DTMF digits from user.

_

βtatic void DoGetDTMFCommand (void)

RWB My_xrwb; // read/ rite block βtruct findex far fptr; int nDialRetVal; // Dialogic return value int nDialBvent; // Dialogic event number βwitch (lp->nCmdStage) { caβe 0: // βtart of command

tion() ;

goto done ; if ( cp-.flush digitβ_at_βtart ) { Fluβh_All_D_MF ( ) ; if ( lp--bSendEvent ) goto done;

}

Set_End_Conditionβ ( ) ; if ( lp->bSendEvent ) goto done;

// get digitβ

) ;

My_xrwb.termdtmf . '\0'; /♦ tricky: 0-«unlimited(MAX_DIGITS) ; alβo handle too big ♦/ My_xrwb.maxdtmf a lp->nMaxDTMF My_xrwb.iβxrwb a 1;

My_xrwb.maxβec a ( word ) MSEC_TO_SEC ( cp->total_timeout ) ;

/* maxβil aa add up all βilence between digitβ -- not

what we want

*/ /* My_xrwb.π__csil a MSEC TO_SEC(cp->βtart_timeou ) ; / My_xrwb.intrdig a ( byte-) MSEC_TO_SBC ( cp->interdigit_to ) ;

My_xrwb.rwbflags a 0;

My_xrwb.loopβig a 1;/* terminate on hamgup */

,

t( lp->xrwb_ProtectedSelector ), t( lp->xrwb ProtectedPointer )

) ; if ( lp->xrwb_ProtectedSelector aβ o ) { Debug (

3, βprintf ( Debug_buf,

" D_Dial/DoGetDTMFCommand:%β(XRWB, *d bytes)", cDoβMemErr, βizeof ( My_xrwb ) ) ); ep->reβult a RC_RAMFULL; lp-.bSendEvent Z TRUE; goto done; memcpy ( ( void ) lp->xrwb_ProtectedPointer, ( void ) tMy_xrwb, βizeof ( My_xrwb ) ) ;

♦endif

/ βtart recording data */ nDialRetVal a getdtmfβ (

CHANNEL ( lp-_nLine_um ), ♦if defined(E_W) / Windowβ /

~ ( RWB ) lp->xrwb_DoβPointer tMy_xrwb / non-Windows */

♦endif ); βwitch ( nDialRetVal ) { caβe E_SUCC: / Function waβ βucceββful. */ break; default: / βome unknown error / ep-.reβult a RC.INTER ALLINE; lp->bSendBvent a TRUE; goto done;

/* now wait until we get am event / lp-_nCmdStage a 2; goto done; caβe 2: / we expect am end-of-input event /

IF_NO EVENT THEN BREAK; IF WINDOWS_BVENT-THBN_INTERNALFAIL; nDialBvent a NEXT_BVENT.nDialBvent; REMOVE ZVEN ; βwitch-( nDialBvent ) { caβe T_SILOFF: / Silence off. / caβe T_SIL0N: /* Silence on. / goto done; // ignore; remain in βame βtate

} ♦if defined(E_W) memcpy ( dial_dtmfbuf[lp - line] , ( void * ) lp->dtmf_ProtectedPointer,

MAX_DIGITS + 1 ) ; free_DOS_mem ( t( lp->xrwb_DoβPointer ),

— — t( Tp->xrwb_ProtectedSelector ), t( lp->xrwb_ProtectedPointer ) ) ,- free_DOS_mem ( t( lp->dtmf_DoβPointer ),

- t( lp->dtmf_ProtectedSelector ), t( lp->dtmf_ProtectedPointer ) ) ,- ♦endif βwitch ( nDialBvent ) { caβe T_MAXDT: /* Maximum DTMF digitβ received. / ep->reβult - RC_DATALBNGTH; βtrcpy ( ep->string, dial__dtmfbuf[lp - line] ) ,-

lp-.bSendEvent a TRUE; goto done; caβe T_TERMDT: / Terminating DTMF digit received. / caβe T_MDTMF: /* Terminated by maβked DTMF digit*/ ep->reβult a RC_ENDFLAG; βtrcpy ( ep->βtring, dial_dtmfbuf[lp - line] ); lp->bSendBvent a TRUE; goto done; caβe T_TIME: /* Rec/play/getdtmf timed out. */ caβe T_SIL: / Maucimum βilence received. ♦/ caβe T_IDTIME: / Interdigit delay exceeded. ♦/ βtrcpy ( ep-.string, dial_dtmfbuf[lp - line] ),- if ( ep--string[0] -a '\0' ) ep--result a RC_STARTTIMEOUT; ep->reβult a RC_INTERDIGITTIMEOUT; lp->bSendBvent a TRUE; goto done; caβe T_LCTERM: / Terminate by drop in loop

signal / ep->reβult a RC_USERHUNGUP; βtrcpy ( ep--String, dial_dtmfbuf[lp - line] ) ; lp->bSendBvent a TRUE; goto done; caβe T STOP: / Rec/play/getdtmf stopped. / caβe T~HFAIL: / Hardware failure. ♦/ ep~>reβult a RC_BADLINE; lp->bSendEvent a TRUE; goto done,- default: / some unknown event / ep->result a RC_INTERNALLINE; lp->bSendEvent a TRUE; goto done; }

/ never get here / break; caβe SHUTDOWN STAGE: // βerver iβ βhutting down ep->reβult " - RC_SERVERSH0T_>OWN lp->bSendBvent _ TRUE; goto done; default: // unknown command βtage ep->reβult - RC_INTERNAL; lp->bSβndBvent Z TRUE; goto done;

} done:

// if cασplete, check to βee if we have to do clean up if (lp--bSendEvent) {

// do βomething here ! ! 1 free_DOS_mem ( t( lp->xrwb_DosPointer ), - t( lp->xrwb_ProtectedSelector ), t( lp->xrwb_ProtectedPointer ) ) ; free_DOS_mem ( t( lp->dtmf_DoβPointer ), t( lp->dtmf_ProtectedSelector ) , t( lp->dtmf_ProtectedPointer ) ) ; } retur ,- } // end function (DoGetDTMFCommand)

DoGetWordsCommand -- Get wordβ from uβer.

static void DoGetWordsCommand (void) int nDialBvent; // Dialogic event number int nDialCallState; // Dialogic call βtate number βwitch (lp->nCmdStage) {

// tricky; fall through ... caβe 40: // βtill βtart of command vr_Get_Wordβ (

*qp,

0, // nDialBvent 0, // nDialCallState tip->nCπvdStage, ep, tip--bSendEvent

) goto done; case 41: // βome event caβe 42: // βome event

IF NO_EVBNT_THEN_BREAK; IF-WINDOWS_EVENT_THEN_INT_RNALFAIL; nDialBvent a NEXT EVENT.nDialBvent; nDialCallState a NEXT_EVENT.nDialCallState,-

REMOVE_EVKNT; vr_Get-Wordβ ( ♦cp,

NEXT EVENT.nDialBvent, NEXT ' EVENT.nDialCallState, tip->nCmdStage, ep, tip->bSendEvent ) ; goto done; case SHUTDOWN_STAGE: // server is shutting down

vi _Get_Wordβ ( cp,

0,

0 , tip-.nCmdStage, ep, tip--bSendEvent

) ; goto done; default: // unknown command stage ep->result a RC_INTERNAL; lp--bSendEvent a TRUE; goto done;

done : return ,- } // end function (DoGetWordsCommand)

DoSetlFileCommand - - Open indexed prompt file . * * */ static void DoSetlFileCommand (void) int ifile_num; // indexed filenum (0 to MAX_IFILE-1) / int pcount; // phraβe count / char buffer[40] ; // file input buffer for βetifile / int rval; char ♦ptr, ♦startptr, *βndptr;

// in-file βtruct to acceββ an indexed file / βtruct { long max; // number of if_entry's that follow ifJieader ♦/ long rate; // always 6053 */ long top; // number of phraβes in file (<a max) / long rβv3; // reserved / long βize; // βize of file in byteβ / long rsv5; // reβerved / } ifJieader;

// in-file βtruct to acceββ an indexed file / // one if_entry per phraβe in file / βtruct { long start; // starting byte in file / long length; //length in bytes (0_-no phrase here)*/ long annotate; // unused (a 0) */ } if_entry; switch (lp-.nCmdStage) { case 0: // start of command ep-.nEvent a EV_SETIFILE;

/* error-checking */ if ( lp->num_openifile >« MAX_IFILE ) {

ep->reβult a RC_BADCOMMAND; lp->bSendBvent a TRUE; goto done; ifile_num a lp->num_openifile;

/* ♦ Firβt, do the voice file. */ lp->ifile[ifile_num] .ifhandle a open ( cp->play_itern.fname, 0_BINARY | 0_RDONLY ) if ( lp->ifile[ifile_num] .ifhandle < 0 ) { ~ ep->reβult - RC_NOFILE; ep->C_error a errno; ep->DOS_error a _doβerrno; lp-.bSendEvent a TRUE; goto done;

/* read header from indexed file / rval - read ( lp->ifile[ifile_num] .ifhandle, tif_header, βizeof ( if header )

) ; if ( rval < βizeof ( if eader ) ) { ep->reβult a RC_BADFILE; ep->C_error a errno; ep->DOS_error a _doβerrno; cloβe ( lp->if ile [ifile num] . if handle ) ; lp- >if ile [if ile_num] . if handle > ( -l ) ; lp->bSendBvent a TRUE; goto done,-

/* do βαme error- checking ♦/ if ( ( if_hβader. tαp < 0 )

H ( ifjieader. max < 0 ) ( if header . top > if Jieader. max ) ) { ep- >reβult - RC BADFILE; cloβe ( lp- >ifiϊe [if ile_num] . if handle ) ; lp- >ifile [i ile_num] .if handle « ( -1 ) ; lp->bSendBvent a TRUE; goto done; if ( if_header.max >. MAX PHRASE ) { ep->reβult a RC_BADFILE; cloβe ( lp->ifiϊe[ifile_num] .ifhandle ) ,- lp->ifile[ifile_num] .ifHandle ( -1 ) ; lp->bSendBvent a TRUE; goto done; }

/♦ read phraβe entries from indexed file */ for ( pcount a 0; pcount < ifJieader.max; pcount++ ) { rval a read ( lp->ifile[ifile_num] .ifhandle, tif_entry, βizeof { if_entry )

) ; if ( rval < βizeof ( if_entry ) ) { ep-.result a RC_BADFILE; ep->C_error a errno; ep->DOS_error a _doβerrno; cloβe ( lp->ifile[ifile_num] .ifhandle );

lp->ifile[ifile_num] .ifhandle a ( -l ) ,- lp->bSendBvent Z TRUE; goto done; / do βome error-checking */ if ( ( if_entry.βtart < ( βizeof ( ifjieader ) + ( if_header.max βizeof ( if-entry ) ) ) ) || ( i _entry.length < 0 ) ) { ep->reβult a RC_BADFILE; ~ close ( lp->ifiϊe[ifile_num] .ifhandle ); lp->ifile[ifile_num] .ifhandle a ( -1 ) ; lp->bSendBvent a TRUE; goto done; lp->ifile[ifile_num] .indextab[pcount] .start a

- if_βntry.βtart; lp->ifile[ifile_num] .indextab[pcount] .Length a if_entry.length; lp->ifile [ifile_num] .num phraβeβ a ( int )

~ ifjieader . max ;

/♦ ' ,- {

/

ep->reβult a RC_BADFILE; ep->C_error a errno; ep->DOS_error a _doβerrno; lp->bSendEvent a TRUE; goto done; buffer[rval] a » \0'; /* for debugging purposes / ptr a buffer; startptr a ptr; endptr a ptr + rval; while ( ptr < endptr ) { while ( ( ptr < endptr ) tt ( Λ ptr la '\n' ) ) ptr++; βtrncat ( lp->ifile[ifile_num] .indextab[pcount] .fname, βtartptr, ptr - βtartptr

) ; if ( *ptr a« '\n' ) { pcount++; ptr++; βtartptr a ptr;

} }

/ doeβn't detect map file containβ more lineβ than expected / cloβe ( lp->open_file ) ; lp->open_file a ( -l ) ; if ( Debύg_flag >a 9 ) for ( pcount a 0; pcount < lp->ifile[ifile_num] .num_phraseβ; pcount++ ) lp->num_openifile++; ep->reβϋlt a RC_SUCCEED; lp-.bSendEvent a TRUE; goto done; default: // unknown cααmamd βtage ep->reβult a RC_INTERNAL; lp->bSendBvent a TRUE; goto done;

done : return ; } // end function (DoSetlFileCommand)

♦ DoRecordCommand -- Record from the current line to a file.

*

static void DoRecordCommand (void)

RWB My_xrwb; // read/write block int nDialRetVal; // Dialogic return value int nDialBvent; // Dialogic event number char far fptr;

βwitch (lp-> nCmdStage) { caβe 0: // βtart of commamd ep->nBvent a EV RECORD ; ep->βtring[0] «-'\0'; if ( llp-.bCardlβOffHook ) { βp->rββult - RC_CARDONHOOK; lp->bSendEvent a TRUE; goto done; } if ( llp->bUβerlBθffHook ) { ep->reβult - RC_USERONHOOK; lp--bSendEvent a TRUE; goto done; }

/* nDialRetVal a Check Ueer_Connection() ; if ( nDialRetVal ) J ep->reβult nDialRetVal; lp->bSendEvent a TRUE; goto done;

I, if ( cp->flu8h_digitβ_at_Btart ) {

Fluβh_All_DTMF ( ) ; if ( Tp--bSendEvent ) goto done;

Set_End_Conditionβ ( ) ; if ( lp-.bSendEvent ) goto done; clrxrwb ( tMy_xrwb ) ;

) ;

);

My_xrwb.rwbflags a RW_TONE; / play beep tone / My~xrwb.rwbdatal a 2,-~ /♦ beep duration (200 msec

- incrs) */

My_xrwb.loopsig « 1; /♦ terminate on hangup / My_xrwb.iβxrwb a ι ;

My_xrwb.intrdig « ( byte ) MSEC_TO_SEC ( ( int ) ( cp->βnd_βilence ) ) ,- My_xrwb.maxnsil a ( byte ) MSEC_TO_SEC ( ( " int ) ( cp-_total_recording ) ) ; / start recording data /

♦if defined(E_W) /* Windows */ allocJ)OS_mem ( sizeof ( My_xrwb ) , t( lp->xrwb_DosPointer ), t( lp->xrwb~ProtectedSelector ), t( lp->xrwb~~ProtectedPointer )

) ; if ( lp->xrwb_ProtectedSelector aa o ) { ep->reβult a RC_RAMFULL; lp->bSendEvent a TRUE; goto done;

} memcpy ( ( void * ) lp-> rwb_ProtectedPoιnter,

( void ) tMy_xrwb, βizeof { My_xrwb ) ) ; ♦endif nDialRetVal a recfile (

CHANNEL ( lp->nLineNum ) , ♦if defined(B_W)

( RWB ) lp->xrwb DoβPointer, / Windowβ / tMy_xrwb, / non-Windowβ /

♦endif

RM_NORM

); switch ( nDialRetVal ) { caβe E_SUCC: /♦ Function waβ βuccessful. / break; caβe B_BADDL: /♦ D4x hardware error. / cloβe ( lp->opβn_filβ ) ; lp->open_file a 7 -1 ); ep->rββult a RC_BADLINB; lp->bSendEvent a TRUE; goto done; default: / βαme unknown error / cloβe ( lp->open_file ) ; lp->open file a ( -l ) ; ep-.result - RC_INT_R_U___INE; lp->bSendBvent a TRUE; goto done; }

/♦ now wait until we get an event */ lp--nCmdStage a l; goto done; caβe 1: /* we expect an end-of-recording event */

IF N0_Ev_NT THEN_BREAK; IF-WINDOWS_EVENT_THEN_INT_RNALFAIL; nDialBvent a NEXT_EVENT.nDialBvent; REMOVE_EVENT; βwitch ( nDialBvent ) { caβe T_SILOFF: /* Silence off. / caβe T-SIL0N: /♦ Silence on. /

goto done ; // ignore; remain in βame βtate

♦if defined(B_W) free_DOS_mem ( t( lp-_xrwb_DosPointer ), - - t( lp->xrwb_Protβcte___elector ) , t( lp->xrwb_ProtectedPointer ) ) ; ♦endif close ( lp->open_file ) ,- lp->open_file a ( -l ) ; / extract base filename from full filename / fname_full_to baβe ( cp~->play_item.fname, / input -- full filename / ep--filename / retval •- baβe fname / ); βwitch ( nDialBvent ) { caβe T_MAXDT: /♦ Maximum DTMF digitβ received. / lp-_nTempVRetVal - RC_DATALBNGTH; break; caβe T_TBRMDT: / Terminating DTMF digit received. / caβe T MDTMF: /♦ Terminated by maβked DTMF

- digit */ lp->nTempVRetVal - RC_ENDFLAG; breaϋc; caβe T_MAXBYT: /♦ Max byteβ reached on play or rec / lp->nTempVRetVal - RC_DATAI__NGTH; break; caβe TJTIMB: / Rec/play/getdtmf timed out. / lp->nTempVRetVal a RC_T0TALTIMEOUT; break; ~ caβe T_SIL: /♦ Maximum βilence received. / caβe T_IDTIME: /♦ Interdigit delay exceeded. / lp->nTβmpVRetVal ■ RC_INTERDIGITTIMEOUT; break,- caβe T_LCTBRM: /* Terminate by drop in loop signal ♦/ ep-.result RC USBRHUNGUP; // don't bother getting any digits they may have entered (?) lp-.bSendEvent a TRUE; goto done; caβe TJDOSERR: /♦ Doe error. / caβe T_DFULL: /* Diβk full. / remove ( βp--filename ) ,- ep-.filename[0] - '\0' ; ep->reβult a RC_DISKFULL; lp->bSendEvent a TRUE; goto done; caβe T_STOP: / Rec/play/getdtmf βtopped. / caβe T_HFAIL: /* Hardware failure. / caβe T_EMSERR: / Terminated by EMS error. / ep--filename[0] a '\0'; ep-.reβult a RCJBADLINE; lp->bSendBvent a TRUE; goto done;

♦if 0 caβe 37: / βome undocumented event / /♦ ignore event / goto done; ♦endif default: /♦ βome unknown event / ep-.filename[0] a '\0';

ep->reβult a RC_INTERNALLINE; lp--bSendEvent a TRUE; goto done;

> if ( !cp->get_digitβ_at_end ) { ep->reβult a lp-__ιTempVRetVal; lp->bSendEvent a TRUE; goto done; }

// get digitβ

) ; My_xrwb. ermdtmf - '\0';

/ tricky: Oaaunlimited(MAXJJIGITS) ; alβo handle too big / My_xrwb.maxdtmf a lp->nMaxDTMF My_xrwb.iβxrwb a l; cp->total_timeout ■ SEC TO MSEC ( 1 ); / don't wai My_xrwb.maxβec - ( word " ) MSEC_TO_SEC (

— — cp->total_timeout );

/* maxβil aa add up all βilence between digitβ -- not what we want */

/ My_xrwb.____.il a MSEC TO_SEC(cp->βtart_timeout) ; / My_xrwb.intrdig « ( byte-) MSEC_TO_SEC ( cp->interdigit_to ) ; My_xrwb.rwbflagβ a 0; ~ "

My_xrwb.loopβig a 1;/* terminate on hangup ♦/ ♦if defined(B_W) / Windowβ ♦/ alloc_DOS_mem ( MAX_DIGITS + 1, t( lp->dtmf_DoβPointer ), t( lp->dtmf_ProtectedSelector ), t( lp->dtmf " ProtectedPointer )

); if ( lp->dtmf_ProtectedSelector aa o ) { ep-.reβult " a RC_RAMFULL; lp->bSendBvent a TRUE; goto done; fptr a ( void ) lp->dtmf_DoβPointer; fptr a dial_dtmfbuf[lp->nLine_um] ; ♦endif

My_xrwb.xferβeg a FP_SEG ( fptr ) ; My_xrwb.xferoff a FP_OFF ( fptr ) ;

My xrwb.maxbyteh a 0~ My~xrwb.maxbyte a MAX_DIGITS; ♦if defined(B_W) / Windows / airoc_DOS_mem ( ~ sizeof ( My_xrwb ) , t( lp->xrwb~DoβPointer ), t( lp-_xrwb " Protec_edSelector ) ,

t( lp->xrwb ProtectedPointer )

); if ( lp--xrwb_ProtectedSelector aa o ) { ep->result " a RC_RAMFULL; lp->bSendBvent _ TRUE; goto done; memcpy ( ( void ) lp->xrwb_ProtectedPointer,

( void ) tMy_-_rwb, βizeof ( My_xrwb ) ) ; ♦endif

/♦ βtart recording data / nDialRetVal a getdtmfs (

CHANNEL ( lp-.nLineNum ), ♦if defined(E_W) ( RWB ) lp->xrwb_DosPointer /* Windowβ / tMy_xrwb / non-Windowβ / ♦endif

) ; βwitch ( nDialRetVal ) { caβe E_SUCC: /* Function waβ βucceββful. / break; default: / βαme unknown error / ep-.reβult ■ RC_INTBR_0___INB; lp->bSendEvent * TRUE; goto done;

/* now wait until we get am event / lp->nCmdStage a 2; goto done; caβe 2: / we expect an end-of-input event /

IF_NO_EVENT_THEN_BREAK; IF WINDOWS_EVENT_THEN_INTBRNALFAIL; nDialBvent " a NEXTJZVENT.nDialBvent;

REMOVE_EVBNT; βwitch " ( nDialBvent ) { caβe T_SIL0FP: / Silence off. / caβe T_SILON: /♦ Silence on. / goto done; // ignore; remain in βame βtate

♦if define (E_W) memcpy ( dial_dtmfbuf[lp - line] , ( void ) lp->dtmf_ProtectedPointer, MAX_DIGITS + 1 ) ; free_DOS_mem ( t( lp->xrwb_DoβPointer ), t( lp->xrwb_ProtectedSelector ), t( lp->xrwb_ProtectβdPointer ) ) ,- free_DOS_mem ( t( lp->dtmf_DoβPointer ),

~ t( lp->dtmf_ProtectedSelector ), t( lp-.dtmf ProtectedPointer ) ),-

♦endif βwitch ( nDialBvent ) { caβe T MAXDT: /* Maximum DTMF digitβ received. / ep~>rββult a RC_DATALENGTH; βtrcpy ( ep->βtring, dial_dtmfbuf[lp - line] ); lp->bSendEvent a TRUE; goto done; case TJTERMDT: / Terminating DTMF digit

~ received. / caβe T_MDTMF: /♦ Terminated by maβked DTMF digit ep~>reβult a RC_ENDFLAG; βtrcpy ( ep->βtring, dial_dtmfbuf[lp - line] ),-

lp->bSendEvent a TRUE; goto done; caβe T_TIME: /* Rec/play/getdtmf timed out. */ caβe T_SIL: /* Maximum βilence received. */ caβe T_IDTIME: / Interdigit delay exceeded. / βtrcpy ( ep-.string, dial dtmfbuf[lp - line] ); if ( ep->string[0] — '\0 T ) p->reβult a RC_STARTTIMEOUT; ep->reβult = RC_IN__RDIG_TTIMEOUT; lp->bSendEvent TRUE; goto done; caβe T_LCTERM: / Terminate by drop in loop signal ♦/ ep->result a RC_USERHUNGUP; βtrcpy ( ep->string, dial_dtmfbuf[lp - line] ); lp->bSendEvent a TRUE; goto done; caβe T_STOP: /* Rec/play/getdtmf stopped. / caβe T_HFAIL: / Hardware failure. / ep->result a RC_BADLINE; lp->bSendEvent a TRUE; goto done; default: / βome unknown event / ep-.reβult a RC_INTERNALLINE; lp-.bSendEvent a TRUE; goto done;

/* never get here / break; caβe SHUTDOWN_STAGE: // βerver iβ βhutting down ep->reβult~ a RC_SERVERSHUTDOWN; lp->bSendBvent a TRUE; goto done; default: // unknown command βtage ep->reβult a RC_INTERNAL; lp->bSβndBvent a TRUE; goto done;

} done:

// if complete, check to βee if we have to do clean up if (lp--bSendEvent) {

// do something here ! ! 1 free_DOS_mem ( t( lp->xrwb_DosPointer ), t( lp->xrwb_ProtectedSelector ), t( lp->xrwb__rotectedPointer ) ) ; free_DOS_mem ( t( lp->dtmf_DoβPointer ), t( lp->dtmf_ProtectedSelector ), t( lp->dtmf_ProtectedPointer ) ),-

} return; } // end function (DoRecordCommand)

/*********** * ***** ♦if DO FAKE LINES

// tricky: include C code into thiβ file, becauβe needs to get at // line array ♦include "fakeline.c" ♦endif

Id Do Command

int ld_Do_Command ( const struct command and, // command to execute const boolean start_of_αnd, // βtart new and ? or continue old βtruct event event // reβult of command ) int nVRetVal; // Voyβyβ return value boolean GotBvent; if ( Unitialized ) { nVRetVal a RC_NOTINITIALIZED; event-.reβult a nVRetVal,- goto done;

if ( ( and.nLine < 0 ) ♦if DO_FAKB LINES

" | ( and.nLine >- ( int ) MAX AL ) ) {

I I ( cmd.nLine >B ( int ) nActualLineβ ) ) { ♦endif nVRetVal - RCJTOLINE; event-.result a nVRetVal; goto done; } // βee if driver haβ any eventβ for ANY line - get all of them / GotBvent a TRUE; while ( GotBvent )

Check_For_Event ( tGotEvent ) ; lp a tline[and.nLine] ,- cp a t( lp-.cmd ) ; ep B t( lp-.event ); ep->reβult a RC_CMDN0TDONB; // if we are waiting for an event and we haven't got one yet, // return ♦/ if ( ( 1βtart_of_αnd ) tt ( !IS_AN_EVENT )) { if (( lp-.bCmdlnProgresβ ) tt ( lp-_nCmdStage aa

ABORT_STAGE) ) goto doit; if (( lp-.bCmdlnProgress ) tt ( lp-.nCmdStage aa

SHUTDOWN_STAGE) ) goto doit; if ( ( lp->timeout la o ) tt ( time_in_mβec0 >a lp->timeout ) ) {

// command timed out / ADD_EVENT<WM_TIMER,0,0,0);

} elβe o finished;

if { start_of_cmd ) lp - .nCmdStage a 0 ; doit: lp->bSendBvent a FALSE;

{ /

♦if DO_FAKE_LINES if-( αnd.nLine >a ( int ) nActualLineβ ) { DoFakeLineCαmmamd() ; if ( lp--bSendEvent ) goto βendev; goto finiβhed; ♦endif βwitch ( _p->nCommand ) {

/* */ caβe DVRC_CONNECT: // connect Line taβk to line N

DoConnectCommandO ; if ( lp->bSendEvent ) goto βendev; goto finiβhed;

/* */ caβe DVRC_DISCONNECT: // diβconnect Line taβk from line N DoDiβconnectCαmmamdi) ; if ( lp-.bSendEvent ) goto βendev; goto finiβhed;

/* */ caβe DVRC_GETSTATE: ep-.nEvent a EV_GOTSTATE; ep->uβer_off_hook a lp->bUβerIβOffHook; ep->reβuϊt a RC_SUCCBED; goto βendev;

/* */ caβe DVRC_WAITFORCALL: / wait for incoming call + anβwer it /

DoWaitForCallCommand ; if ( lp-.bSendEvent ) goto βendev,- goto finished;

/* */ case DVRC_RECORD: / record voice or FAX from uβer ♦/ DoRecordCommand() ; if ( lp--bSendEvent ) goto βendev; goto finished;

/* */ case DVRC INITPLAY: / initialize playlist to empty / ep->nBvent ■ EV_INITPLAY; lp->pliβt_count-a 0; ep->reβult a RC_SUCCEED; goto βendev;

goto βendev;

/* */ caβe DVRC_PLAY: / play voice or FAX out to uβer ♦/ DoPlayCommand() ; if ( lp-.bSendEvent ) goto βendev; goto finiβhed; /* */ caβe DVRC CALLOUT: / initiate a call + wait for anβwer

*/ DoCallOutCαmmand ; if ( lp->bSendBvent ) goto βendev,- goto finiβhed;

/* */ caβe DVRC GETDTMF: / get DTMF digit βtring from uβer

*/ DoGetDTMFCommand{) ; if ( lp--bSendEvent ) goto βendev; goto finished;

/* */ case DVRC_SENDDTMF: / send DTMF digit string out on line / DoSendDTMFCommand0 ; if ( lp-.bSendEvent ) goto sendev; goto finished;

/* */ caβe DVRC_ABORT: /♦ abort any operation in progreββ */

/ if we get here, it meanβ there waβ no command in progreββ ♦/ ep->nBvent a EV_ABORT; ep- .reβult a RC_NULLABORT; goto βendev; ~ /* */ caβe DVRC_HANGUP: / hang up (go on-hook) /

DoHamgupCommamd ( ) ; if ( lp - .bSendEvent ) goto βendev; goto finished;

/* */ caβe DVRC_SETIFILE: /♦ open indexed prompt file / DoSetΪFileCommandO ; if ( lp->bSendEvent ) goto βendev; goto finiβhed;

/* */ caβe DVRC_SETVOCAB: /♦ open vocabulary map fileβ / ep--nEvent a EVJSETVOCAB; ep->reβult a vr_Set_Vocabulary ( ^cp ) ; goto βendev; /* */ caβ ice-recognized wordβ from

goto finiβhed;

/* */ caβe DVRC_GBT: /♦ get options and parameters / switch " ( cmd.nSetGetOpCode ) { caβe GET_FILEFORMATS: ep-.nEvent a EV_GET; ep->cGetParm[0] a ( char ) NULL; ep->dwGetPaπn a FM VOX; ep->reβult RC_SUCCEED; break; } /♦ end of βwitch /

goto βendev,-

/ */ default: / unknown/illegal command value ♦/ / βend back event / ep•>]__-vent a _V_B_J_COMMAND,• ep->reβult a RC_BADC_______;

/* βend the event */ goto βendev; }

/* never get here */ βendev:

/♦ βend back event / event a ep; lp->bCmdlnProgreββ a FALSE; / fall through to ... / finiβhed: nVRetVal a ep-.result,- done: if ( nVRetVal RC_CMDN0TD0NE ) return ( nVRetVal )T }

/ * * * ** * ** *** *** * *** ** * *** *** *** ***** * *** * ********* *** **

* ld_Initialize - Verify that Dialogic driver is running, find

out how many lineβ exiβt, initialize them.

int Id Initialize ( conβt int ld_hw_irq, // HW IRQ for LD card const int ld_βw_int, //SW interrupt for LD card driver conβt long winjilnβt, // hlnβt if uβing Windowβ conβt long win_hWnd, // hWnd if uβing Windowβ char ♦promptext, // filename extenβion for prompt fileβ char ♦ifileext // filename extenβion for indexed prompt fileβ

) int aline_num; / active line num (0 to MAX_LINES-1) / int ifile num; / indexed file number (0 to

MAX_IFILE-1) ♦/ int nVRetVal; // Voyβyβ return value int nDialRetVal; // Dialogic return value

♦if DO_SETXPARM

DCB * My_dcb; / dialog control block (global paramβ) / ♦if defined(B_W) / Windowβ / long dcb_DosPointer a 0L; βhort dcb~ProtectedSelector 0; long dcb_ProtectedPointer a 0L; ♦endif ♦endif int GotBvent;

long far ♦addresβ; if ( Initialized ) { nVRetVal a RC_ALREADYINITIALIZED; goto done;

}

// βanity-check a debug array if ((βizeof(DialEventNumToName) /βizeof(char*)) 1- ______RM+1) { Debug { 1, βprintf ( Debug_buf, "LD_TAPI/ld_Initialize:

DialEventNumToName sanity failure" ) ) ; nVRetVal - RC_INTERNAL; goto done; ~

βtrcpy ( promptext, PROMPT_FEXT ) ; βtrcpy ( ifileext, INDEX_FEXT ) ; nActualLineβ a 0; /*

♦ Teβt to βee if the Dialogic driver iβ up and running.

*/ int_level a ld_βw_int; /* βoftware interrupt level - muβt ~ ~ agree with driver */ nDialRetVal « getvctr ( ) ; if ( ( nDialRetVal a. 0 ) | | ( nDialRetVal 1- Id βw_int ) ) { nVRetVal - RC_N0LINE; goto done; addreββ a ( long far ) _doβ_getvect ( ld_βw_int ) ; addreββ a ( long far ) _doβ_getvect ( ld_βw_int - 1 ) ;

♦if 0 nDialRetVal a iβdrvact ( ld_sw_int ) ,- if ( nDialRetVal la l ) { nVRetVal - RC_NOLINE; goto done;

} ♦endif

/*

♦ Shut the βyβtem down. Ignore the return code; we don't

♦ care if the βyβtem iβ already initialized or not.

*/ nDialRetVal a βtopsys ( ) ;

♦if DO_SET__PARM

/* ♦ Initialize the Dialogic βyβtem.

*/ clrdcb ( tMy deb ) ; ♦if defined(E_W) / Windowβ / alloc_DOS mem ( ~ βizeof ( My_dcb ) , tdcb_DoβPointer, tdcb_ProtectedSelector, tdcb_ProtectedPointer

) ; if { dcb_ProtectedSelector aa o ) { nVRetVal a RC_RAMFULL; goto done;

r, / deβt ♦/ /

♦if defined(B_W) / Windowβ ♦/ free_DOS_mem ( tdcb_DoβPointer, tdcb_ProtectedSelector, ,

~ ~ tdcb ProtectedPointer ) ;

♦endif ~ ' βwitch ( nDialRetVal ) { caβe B_SUCC: / Function waβ βucceββful. / break; caβe B_BADDL: / D4x hardware error. / nVRetVal - RC_NOLINE; goto done; default: / some unknown error / nVRetVal - RC_INTERNALLINB; goto done; ♦endif nDialRetVal a βtartβyβ ( ld_hw_irq, /♦ HW interrupt level /

SM~EVENT, /♦ uβe Event mode /

0, /♦ uβe buffere βpecified to driver / 0, / uβe buffere βpecified to driver / tnActualLines // retval -- number of lineβ available

) ; βwitch ( nDialRetVal ) { caβe E_SUCC: /♦ Function waβ βucceββful. / break; caβe B_FAILST: / Board failed βelf teβt. / caβe E_SACT: /♦ Syβtem already active. / caβe E_SNACT: / Syβtem not active. / caβe B~BADDL: / D4x hardware error. / caβe E_BADINT: / Interrupt level not available. / nVRetVal a RC_NOLINE; goto done; default: / βome unknown error / nVRetVal - RC_INTERN___LINE; goto done; }

♦if defined(E_W) / Windowβ / /♦ allocate memory for Check_For_Bvent / alloc_DOS_mem ( ~ ~ βizeof ( BVTBLK ) , tcfe_DoβPointer, tcfeJ?rotecte-Selector, tcf- " ProtectedPointer

); if ( cfe_ProtectedSelector aa o ) {

nVRetVal - RC_RAMFULL; goto done; ~

} ♦endif if ( nActualLineβ > MAX_LINBS ) nActualLineβ MAX_LINES; if ( nActualLineβ > MAX_AL ) nActualLineβ a MAX AL;

/ initialize array of active line βtateβ ♦/ for ( aline num a 0; aline_num < MAX_AL; aline_num++ ) { lp a tlihe [aline_num] ; ~ lp->nLineNum a aline_num; lp->bConnected a FALSE; lp->bCmdInProgreββ a FALSE; lp-> imeout a 0; lp->bUβer!βOffHθθk FALSE; lp->bCardIβOffHook - FALSE; lp->num_events a 0; lp->pliβt_count a 0; lp->open file a ( -l ) ; for ( ifile_num a 0; ifile_num < MAX_IFILB; ifilβ_num++ ) lp->ifiϊe[ifile_num] .ifhamdle a ( -ι ) ; ~ lp->num openifile a 0; } Initialized a TRUE; nVRetVal « RC_SUCCEED;

/* See if we have voice recognition capabilities. They are * optional; not having them iβ not an error.

*/ vr Initialize ( ) ;

Check_For_Ev_nt { tGotEvβnt ) ; /* !!! */ done: return ( nVRetVal ) ; }

int ld_Shutdown ( void ) { int nVRetVal; // Voyβyβ return value int nDialRetVal; // Dialogic return value int aline_num; /* active line num (0 to MAX_AL-1) / int ifile~num; /* indexed file number (0 to

MAX IFILB- 1) / boolean bNeedToWait; int nLineNum; if ( Unitialized ) { nVRetVal » RC_NOTINITIALIZED ; goto done;

}

// abort any commamd in progreββ on any line bNeedToWait a FALSE; for ( nLineNum a 0; nLineNum < MAX_AL; nLineNum++ ) { lp a tline[nLineNum] ; if ( lp->bCmdlnProgresβ ) { if ( lp->cmd.nCαmmand 1- DVRC DISCONNECT ) { lp->nCmdStage - SHUTDOWN STAGE;

} bNeedToWait a TRUE; , ' if (bNeedToWait) { nVRetVal - RC_BUSYLINB; goto done; }

// fail if any line is connected for ( nLineNum a 0; nLineNum < MAX_AL; nLineNum++ ) { lp a tline[nLineNum] ; ~ if ( lp-.bConnected ) { nVRetVal - RC_BUSYLINE; goto done;

Initialized a FALSE; /*

Shut down voice recognition (if any) . */ vr_Shutdown ( ) ;

/*

Shut the βyβtem down. */ nDialRetVal a βtopβyβ ( ) ; βwitch ( nDialRetVal ) { caβe E_SUCC: /* Function waβ βucceββful. ♦/ break; caβe E_S___CT: / Syβtem not active. / brea ; default: / βome unknown error / nVRetVal - RC_I___R___LLINE; goto done; ~

} for ( aline_num 0; aline_num < MAX_AL; aline_num++ ) { /♦ cloβe " any fileβ left " open (βhould never happen) / if ( line[alinβ_num] .σpen_file >a 0 ) cloβe ( line[aline_nu_T] .open_file ); /* cloβe any indexed fileβ that " are open / for ( ifile_num a 0; ifile_num < line[aline num] .num_σpenifile; ifile_num++ ) { if ( line[aline_num .ifile[ifile_num] .ifhandle >a 0 ) cloβe ( line[aline_num] .ifile[ifile_num] .ifhandle) ; line[aline_num] .ifile[ifile_num] .ifhandle a ( -ι ) ; } line [aline_num] .num_openi file a 0 ;

♦if defined(B_W) / Windows /

/ free memory for Check_For_Bvent / free_DOS_mem ( tcfeJDosPointer, tcfe_ProtectedSelector, tcfe ProtectedPointer ) ; ♦endif nVRetVal « RC_SUCCEED; done: return ( nVRetVal ) ; }

/****************************************/

II handle event that came from above (from application) int ld_Procβββ_Event ( conβt " UINT meββage, // Windowβ meββage number conβt WPARAM wParam, // parameter conβt LPARAM lParam // parameter )

{ int nVRetVal; // Voyβyβ return value if ( Unitialized ) { nVRetVal - RC_NOTINITIALIZED; goto done;

Debug ( 1, βprintf ( Debugjbuf, "LD_Dial/ld_Process_Event: discard event 0x%04X", message " ) ) ; ~ nVRetVal - RC_SUCCBBD; done: return ( nVRetVal ) ;

}