Programmable Voice

  1. Home
  2. Docs
  3. Programmable Voice
  4. Introduction to Using Voice Elements
  5. Start Coding Voice Elements

Start Coding Voice Elements

Start Coding: Write Your First Voice Application in Minutes

This page provides a starting point from which to launch a basic telephony project using Voice Elements in Visual Studio. It will walk you through some samples provided by Inventive Labs to get you started. It will provide a basic framework for inbound and outbound calls using C#.

This guide will require you to have the Voice Elements toolkit installed. It will also refer to and show samples from the Sample Solutions section of the customer downloads page, which you should locate and have ready to get started.  First, let’s make sure you have these ready.

Voice Elements Toolkit

If you have not already, you must download a demo version of Voice Elements.  This will allow you to create a Voice Elements account and provides 100 minutes of Voice Elements Cloud access for testing.  Access is also provided to the sample solutions described below.

Sample Solutions

These basic solutions provide you with an easy way to get started by giving you one place to build your solution with just the needed components, without the overhead of the samplers.  They consist of solution files, project files, and some basic classes we will walk through below.  They can be downloaded from the Voice Elements customer portal under Settings -> Downloads. You can simply copy them, rename, and start building your application. If you do not have them or are not sure if you have the latest versions, you may download them any time.  The sample solutions include examples for:

  • Getting Started – Basic inbound and outbound examples for voice and SMS.
  • IVR with Transfer – Basic inbound IVR with outbound transfer.
  • Dialer – Call multiple numbers simultaneously can play a message.
  • Speech Recognition – IVR using speech recognition or key presses to navigate.
  • Faxing – Send and receive faxes.
  • Conferencing – Add multiple participants to a conference call.
  • Amazon Lex Bot

The steps below will walk through some of the fundamental concepts of using the Voice Elements API; the Getting Started demo solution is examined.  To follow along, please download the Getting Started solution and open in Visual Studio.  There are also detailed tutorials for each of the solutions above available in the downloads section for your account.  The requirements for building and executing the sample solutions are described in System Requirements for Voice Elements Demo Samples.

Step 1: Connect to a Telephony Server

Whether your application handles inbound calls, places outbound calls or does both, you must first connect to a Voice Elements Server. When you download the Voice Elements (VE) demo and install it, you receive 100 minutes to use on the Inventive Labs Cloud servers. You may also setup your own server and connect to it as described below. For information on setting one up and licensing, contact Inventive Labs Sales.

Below is the Starting Point Code used for telephony server connection and setup in the Getting Started solution.

In the C# Skeleton, go to “IvrApplication.cs”

C#

try
{
    Log.Write("Connecting to: {0}", AppSettings.PhoneServer);

    // CHANGE YOUR PHONE SERVER, USERNAME AND PASSWORD IN THE APP.CONFIG FILE
    s_telephonyServer = new TelephonyServer(AppSettings.PhoneServer,
                                            AppSettings.Username,
                                            AppSettings.Password);

    // CHANGE YOUR CACHE MODE HERE
    //
    // Client Session mode means that the server will stream and cache the
    // files to/from your client machine. Files are flushed after you
    // disconnect.
    //

    // Server mode means that the files reside on the server and will use the
    // full path name to find them there.
    // Server mode can only be used on your own dedicate VE server.

    s_telephonyServer.CacheMode = VoiceElements.Interface.CacheMode.ClientSession;

    // SUBSCRIBE to the new call event.
    s_telephonyServer.NewCall += new VoiceElements.Client.NewCall(TelephonyServer_NewCall);

    s_telephonyServer.RegisterDNIS();

    s_telephonyServer.SmsMyPrivateKeyXml = AppSettings.CustomerKeyPairXml;
    s_telephonyServer.SmsBorderElementsPublicKeyXml = AppSettings.BorderKeyPairXml;
    s_telephonyServer.SmsMessage += TelephonyServer_SmsMessage;
    s_telephonyServer.SmsDeliveryReport += TelephonyServer_SmsDeliveryReport;

    // Subscribe to the connection events to allow you to reconnect if something happens to the internet connection.
    // If you are running your own VE server, this is less likely to happen except when you restart your VE server.

    s_telephonyServer.ConnectionLost += new ConnectionLost(TelephonyServer_ConnectionLost);
    s_telephonyServer.ConnectionRestored += new ConnectionRestored(TelephonyServer_ConnectionRestored);
}

As you can see, the comments in the code explain where to replace critical settings and give some indication of the critical steps. Below is a list of the important elements used in order.  You can use this page to link to the documentation and explore the classes, methods, and properties used to better understand the concepts behind the code.

Now let’s step through the code above in detail. Follow the links to explore the individual elements.

  1. Create a new TelephonyServer object. By creating it, you are “connecting.” This object and its children will provide you with all voice functions in your application.
  2. Set your CacheMode. This determines how your application handles the storage of any voice files you will play.
  3. Subscribe to NewCall events. This will tell the server to raise new events when inbound calls arrive. All inbound processing will occur from this event.
  4. Register inbound DNIS numbers or number ranges. Do this using the RegisterDNIS method and its overloads to tell the server which specific number(s) you would like sent to your application.
  5. Lastly, subscribe to all ConnectionLost and ConnectionRestored events to allow your application to execute support actions if your connection to the server is somehow interrupted.

Step 2: Begin a Loop and Wait for Events or Delegate Outbound Calls

C#

while (true)
{
    s_looping = true;

    // Waits for an asynchronous event.
    ThreadEvent.WaitOne(1000, false);

    // At this point you are in control.  You can farm out calls from a database,
    // or use the frmInteractive Form to start an outbound call

    lock (SyncVar)
    {
        if (State != State.Running)
            break;
    }
}

After successfully connecting to the server and setting up event handlers, the application begins its main loop.  ThreadEvent.WaitOne() is called to allow the thread to be signaled to perform other actions; for example, a second thread could be checking a database for new outbound calls to place.  When one is available, it would set the AutoResetEvent to signal this thread to place the call.

While in this loop, if an inbound call is detected, the new call handler added in step 1 will be called to handle it.

Step 3: Process Inbound Calls

C#

static void TelephonyServer_NewCall(object sender, VoiceElements.Client.NewCallEventArgs e)
{
    try
    {

        Log.Write("NewCall Arrival! DNIS: {0}  ANI: {1}  Caller ID Name: {2}", e.ChannelResource.Dnis,
                    e.ChannelResource.Ani, e.ChannelResource.CallerIdName);

        // Handle The New Call Here
        InboundCall inboundCall = new InboundCall(s_telephonyServer, e.ChannelResource);

        inboundCall.RunScript();
    }

    catch (Exception ex)
    {
        Log.WriteException(ex, "IvrApplication::NewCall");

        e.ChannelResource.Disconnect();
        e.ChannelResource.Dispose();
    }

}

In the new call handler, a new InboundCall object is instantiated to process the call.  Call processing is performed in its RunScript() method.  The InboundCall class is defined in InboundCall.cs.

C#

public void RunScript()
{
    try
    {
        // Answer the call
        Log.WriteWithId(m_channelResource.DeviceName, "Answering...");
        m_channelResource.Answer();

        // Play text
        Log.WriteWithId(m_channelResource.DeviceName, "Playing text to speech...");
        m_channelResource.VoiceResource.PlayTTS("Hello, " + AppSettings.FirstName + ".");

        // Play a recorded message
        Log.WriteWithId(m_channelResource.DeviceName, "Playing welcome message...");
        m_channelResource.VoiceResource.Play(AppSettings.AudioFolder + "WelcomeMessage.wav");

        // Uncomment this to test receiving digits from the caller
        // ReceiveDigits();

        // Uncomment this to see how a voicemail message could be recorded
        // RecordVoicemail();
    }

    catch (ElementsException ee)
    {
        // These are Telephony Specific exceptions, such an the caller hanging up the phone during a play or record
        if (ee is HangupException)
            Log.WriteWithId(m_channelResource.DeviceName, "The caller hung up");
        else
            Log.WriteException(ee, "Script Elements Exception");
    }

    catch (Exception ex)
    {
        // This would be a general logic exception, such as a null reference violation or other .NET exception
        Log.WriteException(ex, "InboundCall Exception");
    }
    finally
    {
        // Always use the finally block to clean up the call

        // Note: Protect each call to the server with a try-catch block

        // Disconnect the call (I.E. Hang up)
        try { m_channelResource.Disconnect(); }
        catch (Exception ex) { Log.WriteException(ex, "VE Command Failure In CleanUpCall"); }

        // Dispose of the channel resource, (this will dispose of its attached voice resource automatically)
        try { m_channelResource.Dispose(); }
        catch (Exception ex) { Log.WriteException(ex, "VE Command Failure In CleanUpCall"); }

        // Set these values to null so they can't be referenced anymore.  Once the call is disposed,
        //       the resources cannot be utilized.

        m_channelResource = null;
        m_voiceResource = null;
        m_telephonyServer = null;

        // Now that the channel is no longer in use you can add code here to update your database or other
        // records with the result of the call.

        Log.Write("Call complete");
    }
}

 

This is a large method as most call processing takes place inside it.

Call processing begins by calling the VoiceElements.Client.ChannelResource Answer() method.  This answers the call so that further call handling can be performed.

Next, media operations can be performed by using methods in the VoiceElements.Client.VoiceResource class.  These include playing and recording voice files, collecting DTMF digit input, playing text-to-speech, etc.  In this example, the PlayTTS() is method is called to speak a message.

After the call is processed, it is torn down by calling the Disconnect() method.  ChannelResource’s resources are released by calling Dispose().  After the object is disposed no further operations can be performed on it.

Step 4: Make Outbound Calls

Outbound calls are made by instantiating an instance of the OutboundCall class, defined in OutboundCall.cs.  This class’ constructor stores a reference to the VoiceElements.Client.TelephonyServer class along with the number to call. Once constructed, its RunScript() method is called.

C#

public void RunScript()
{
    try
    {

        // Use WriteWithId to differentiate between separate instances of the class
        Log.WriteWithId(m_channelResource.DeviceName, "OutboundCall Script Starting");
        Log.WriteWithId(m_channelResource.DeviceName, "Dialing {0}", m_numberToCall);

        // With this, the server will detect if a human or machine answers the phone
        m_channelResource.CallProgress = CallProgress.AnalyzeCall;

        // You can display any outbound Caller ID phone number if needed (this is disabled for testing)
        m_channelResource.OriginatingPhoneNumber = AppSettings.TestPhoneNumber;

        // Instruct the server to wait no more then 30 seconds for a connection
        m_channelResource.MaximumTime = 30;

        // Place the call
        DialResult dr = m_channelResource.Dial(m_numberToCall);

        Log.WriteWithId(m_channelResource.DeviceName, "The dial result for {0} was: {1}", m_numberToCall, dr);

        if (dr == DialResult.Connected)
        {
            Log.WriteWithId(m_channelResource.DeviceName, "Playing File...");
            m_voiceResource.Play(AppSettings.AudioFolder + "WelcomeMessage.wav");
        }
        else
        {
            Log.WriteWithId(m_channelResource.DeviceName, "Unexpected dial result, cancelling Call");

            if (dr == DialResult.OperatorIntercept && m_channelResource.GeneralCause == 402)
                Log.WriteWithId(m_channelResource.DeviceName, "You have ran out of minutes. Contact customer support to have more added");
        }
    }

    catch (ElementsException ee)
    {
        // These are Telephony Specific exceptions, such as the caller hanging up the phone during a play or record
        if (ee is HangupException)
            Log.Write("The caller hung up");
        else
            Log.WriteException(ee, "Script Elements Exception");
    }
    catch (Exception ex)
    {
        // This would be a general logic exception, such as a null reference violation or other .NET exception
        Log.WriteException(ex, "Script General Exception");
    }
    finally
    {
        // Always use the finally block to clean up the call
        // Note: Protect each call to the server with a try-catch block
        // Disconnect the call (I.E. Hang up)

        try { m_channelResource.Disconnect(); }
        catch (Exception ex) { Log.WriteException(ex, "VE Command Failure In CleanUpCall"); }

        // Dispose of the channel resource, (this will dispose of its attached voice resource automatically)
        try { m_channelResource.Dispose(); }
        catch (Exception ex) { Log.WriteException(ex, "VE Command Failure In CleanUpCall"); }

        // Set these values to null so they can't be referenced anymore.  Once the call is disposed,
        //      the resources cannot be utilized

        m_channelResource = null;
        m_voiceResource = null;
        m_telephonyServer = null;

        // Now that the channel is no longer in use you can add code here to update your database or other
        //      records with the result of the call

        Log.Write("Call complete");

    }
}

 

Processing starts by setting the setting value of the VoiceElements.Client.ChannelResource CallProgress property.  The property controls whether and how call analysis is performed on the outbound dial.  If set to AnalyzeCall full analysis is performed from dial through answer of the call; as a result, the DialResult values HumanDetected and MachineDetected will be available.  These values indicate that the call was answered by a human or answering machine (voice-mail), respectively.  CallProgress.WaitForConnect is another common value. This tells VE to return immediately on an Answer, Busy, or when a call has not been answered in MaximumTime seconds. Call analysis in Voice Elements is described in much more detail in Call Analysis Features.

Processing continues by setting the originating number for the outbound call using the VoiceElements.Client.ChannelResource’s OriginatingPhoneNumber property.  This is the number that the recipient will see as caller ID when receiving the call.  The maximum time to wait for an answer, in seconds, is then set using the MaximumTime property.

The call is then placed by calling the Dial(), passing the destination phone number as its only argument.  The result of the call is stored in a DialResult variable.  If the result of the call is Connected, a voice file is played via the VoiceElements.Client.VoiceResource Play() method.  If the call does not connected, it is logged.

When the application has performed all desired call operations, the finally block is executed.  Much like in the InboundCall class, the call is dropped using the Disconnect() method.  Its resources are then released by calling Dispose().

A note about call analysis: While call analysis can be very effective and accurate, another paradigm for discerning human- versus machine-answered calls can be even more so: Beep detection.  Voice Elements’ beep detection works by calling VoiceElements.Client.VoiceResource.BeepDetectorStart() after Dial returns a success.  You can now interact with the call as though a human answered, checking the TerminationCode of each method.  If the TerminationCode is Beep it indicates that a tone, such as that at the end of a voice-mail greeting, was detected during the operation.  This indicates to the application that it should stop beep detection by calling BeepDetectorStop() and play an appropriate message for voice-mail.  If no beep is detected, continue the interaction as though a human answered, checking the TerminationCode of each method upon its return to see if a beep was detected.  Be sure to always call BeepDetectorStop() when after a beep is detected; failure to do so will result in the next media operation returning immediately with a TerminationCode of Beep.  The Getting Started solution doesn’t make use of beep detection; however, the example shown in Use the Beep Detector covers it in detail.  Also note that there are potential drawbacks to using beep detection.  One, “artifacting,” occurs due to the time it takes to detect the beep tone.  If the duration of the tone is very short, the initial greeting may still be playing after it is complete.  The result is that small amount of the initial greeting may be recorded in the recipient’s voice-mail.  Other considerations for using call analysis and beep detection are discussed in Beep Detector Strategies.

General Application Notes

Notice that calls to all Voice Elements methods are protected by try/catch/finally blocks.  Voice Elements methods can throw exceptions; it is important to handle them to prevent unexpected behavior.  Most processing takes place in the try block of class methods.  This includes call control and media operations.  The finally block is used to ensure certain code is always executed, such as disconnecting any active call and disposing of class resources.

The IvrApplication class is run in its own thread to separate the application interface from call processing logic.  Likewise, the OutboundCall object is run in its own thread to prevent the main thread from blocking while making an outbound call.  This is not necessary for the InboundCall object as it is instantiated by the NewCall delegate on its own thread.

The Voice Elements class library and hierarchy is described in detail in the Voice Elements Developer Help documentation.  If you need to know what methods and properties are available in each class, this is the place to look.

Each of the provided sample solutions can also be installed and run as a service.  Each solution implements the .NET System.Configuration.Install Installer class and overrides its Install and Uninstall methods.  This allows the application to be easily installed as a service using any of several methods.  These include using InstallUtil.exe (included with Microsoft Visual Studio) or the New-Service PowerShell applet.  To use InstallUtil.exe, run the following from the Windows command prompt (this assumes your application is in C:\Getting Started\):

C:\Getting Started\InstallUtil.exe VoiceApp.exe

Or, using PowerShell:

PS C:\Getting Started\New-Service -Name "IvrService" -BinaryPathName "C:\Getting Started\VoiceApp.exe"

The application checks at runtime to see if it’s being run interactively; if so, it displays its form and runs as a user application.  However, if it is running under services.exe, it immediately instantiates its main class and begins execution.  This allows a single executable to be used for both environments during testing.

Was this article helpful to you? Yes No