Editor: Daniel C. Burnett, Voxeo
Authors: Many people at Voxeo
Creation Date: 3 March 2011
This document contains a summarized version of the ASR and TTS capabilities available in Tropo by Voxeo. It is presented not as a complete proposal but rather in order to provide additional viewpoints for the W3C HTML Speech Incubator Group discussion and development of an API. For more information on the Tropo API, see http://www.tropo.com/docs/scripting/.
Parameter | Data Type | Default | Required/ Optional |
Description |
---|---|---|---|---|
text | String | (undefined) | Required | In the case of a voice session, this can either be the text to be rendered by the Text to Speech Engine (may also be SSML), or a URL to an audio file to be played. In the case of a text messaging session, this will be the text to be sent to the user. |
Parameter | Data Type | Default | Required/ Optional |
Description |
---|---|---|---|---|
allowSignals | String or Array | (any signal) | Optional | This parameter allows you to assign a signal to this function. Events with a matching signal name will "interrupt" the function (i.e., stop it from running). If it already ran and completed, your interrupt request will be ignored. If the function has not run yet, the interrupt will be queued until it does run. By default, allowSignals will accept any signal as valid; if you define allowSignals as "", it defines the function as "uninterruptible". You can also use an array - the function will stop if it receives an interrupt signal matching any of the names in the array. |
onSignal | Function | (none) | Optional | This specifies a callback function to run if the function is interrupted by a signal. |
voice | String | wilma | Optional | Specifies the voice to be used when speaking text back to a user. Examples are:
|
say("Guess what? http://www.phono.com/audio/troporocks.mp3", {voice:"fred"});
say "Guess what? http://www.phono.com/audio/troporocks.mp3", {:voice => "fred"}
<?php say("Guess what? http://www.phono.com/audio/troporocks.mp3", array("voice" => "fred")); ?>
say("Guess what? http://www.phono.com/audio/troporocks.mp3", {"voice":"fred"})
say("Guess what? http://www.phono.com/audio/troporocks.mp3", [voice: "fred"])
Parameter | Data Type | Default | Required/ Optional |
Description |
---|---|---|---|---|
text | String | (undefined) | Optional | In the case of a voice session, this can either be the text to be rendered by the Text to Speech Engine (may also be SSML), or a URL to an audio file to be played. In the case of a text messaging session, this will be the text to be sent to the user. |
Parameter | Data Type | Default | Required/ Optional |
Description |
---|---|---|---|---|
allowSignals | String or Array | (any signal) | Optional | This parameter allows you to assign a signal to this function. Events with a matching signal name will "interrupt" the function (i.e., stop it from running). If it already ran and completed, your interrupt request will be ignored. If the function has not run yet, the interrupt will be queued until it does run. By default, allowSignals will accept any signal as valid; if you define allowSignals as "", it defines the function as "uninterruptible". You can also use an array - the function will stop if it receives an interrupt signal matching any of the names in the array. |
attempts | Integer | 1 | Optional | This defines the total amount of times the user will hear the prompt before the ask ends in either a nomatch or noinput. |
bargein | Boolean | true | Optional | The bargein attribute specifies whether or not the caller will be able to interrupt the TTS/audio output with a touch tone phone keypress or voice utterance. A value of 'true' indicates that the user is allowed to interrupt, while a value of 'false' forces the caller to listen to the entire prompt before being allowed to give input to the application. If using Python, make sure to use True and False instead of true and false. |
choices | String | none | Optional | The choices field defines a simple grammar that will be active for the prompting of the user for input. For more information, review the Asking a Question and Working with Simple Grammar sections. |
minConfidence | Float | 0.3 | Optional | This is the minimum amount of confidence that the "recognizer" must have before matching a response to a choice. As an example, if your grammar defines the choices as red, blue and green, and someone says "rud", a particular confidence will be set identifying how likely "rud" was meant to be "red". This is expressed in a Float as a rate between 0 and 1. |
mode | String | any | Optional | The type of caller input allowed for voice calls. This can be 'dtmf' (touch-tone input), 'speech' or 'any'. |
onBadChoice | Function | (undefined) | Optional | This registers an event handler that fires when the number of attempts have been exhausted without a valid response from the user. |
onChoice | Function | (undefined) | Optional | This registers an event handler that fires when a valid response is provided by a user. |
onError | Function | (undefined) | Optional | This registers an event handler that fires when a system error (a non-user error) occurs during input. See onBadChoice and onTimeout for information on how to handle user errors. |
onEvent | Function | (undefined) | Optional | This registers an event handler that fires as a catch all for all events. |
onHangup | Function | (undefined) | Optional | This registers an event handler that fires when the user disconnects or hangs up. |
onSignal | Function | (none) | Optional | This specifies a callback function to run if the function is interrupted by a signal. |
onTimeout | Function | (undefined) | Optional | This event fires when the user doesn't respond to the prompt within a specified period of time. |
recognizer | String | en-us | Optional | The language to listen for; example options are:
|
terminator | String | (none) | Optional | This is the touch-tone key (also known as "DTMF digit") that indicates the end of input. A common use of the terminator is the # key, eg: "Please enter your five digit zip code, then press the pound key." |
timeout | Float | 30.0 | Optional | The amount of time Tropo will wait--in seconds and after sending or playing the prompt--for the user to begin a response. |
voice | String | wilma | Optional | Specifies the voice to be used when speaking text back to a user. Example voices are:
|
ask("What's your four or five digit pin? Press pound when finished", { choices:"[4-5 DIGITS]", terminator:"#", timeout:15.0, onChoice: function(event) { say("Thank you"); } }); // script continues
ask "What's your four or five digit pin? Press pound when finished", { :choices => "[4-5 DIGITS]", :terminator => '#', :timeout => 15.0, :onChoice => lambda { |event| say "Thank you" } } # script continues
<?php ask("What's your four or five digit pin? Press pound when finished", array( "choices"=>"[4-5 DIGITS]", "terminator" => "#", "timeout" => 15.0, "onChoice" => "choiceFCN" ) ); function choiceFCN($event) { say("Thank you"); } // script continues ?>
ask("What's your four or five digit pin? Press pound when finished.", { "choices":"[4-5 DIGITS]", "terminator":"#", "timeout":15.0, "onChoice": lambda event : say("Thank you") }) #script continues
ask("What's your four or five digit pin? Press pound when finished.", [ choices: "[4-5 DIGITS]", terminator: '#', timeout: 15.0, onChoice: { event-> say("Thank you.") } ]) // script continues
Playing audio is just as easy as using Text To Speech (TTS) - just provide the say with a link to an accessible audio file and Tropo will play it back:
say("http://www.phono.com/audio/troporocks.mp3");
say("http://www.phono.com/audio/troporocks.mp3")
say "http://www.phono.com/audio/troporocks.mp3"
"Accessible audio file" means any web-accessible file: a file hosted on your server or from a hosting service.
You can also play multiple audio files in the same say (or ask):
say("http://www.phono.com/audio/troporocks.mp3 http://www.phono.com/audio/holdmusic.mp3");
say("http://www.phono.com/audio/troporocks.mp3 http://www.phono.com/audio/holdmusic.mp3")
say "http://www.phono.com/audio/troporocks.mp3 http://www.phono.com/audio/holdmusic.mp3"
You can also mix and match audio with text-to-speech:
say("http://www.phono.com/audio/troporocks.mp3 Here's some hold music! http://www.phono.com/audio/holdmusic.mp3");
say("http://www.phono.com/audio/troporocks.mp3 Here's some hold music! http://www.phono.com/audio/holdmusic.mp3")
say "http://www.phono.com/audio/troporocks.mp3 Here's some hold music! http://www.phono.com/audio/holdmusic.mp3"
The supported sound formats (and their proper file extensions) are as follows:
You can use other formats like MP3, but they will be automatically downsampled and converted due to limitations in telephony standards, so it's always best to have your files in 8bit, 8Khz u-law format from the start.
During playback, audio is streamed directly from the source - the file isn't downloaded first and then played.
There are many cases when you need or just want to control the pitch, volume and intonation of your prompts and responses. To make this easy, Tropo natively supports a standard called the Synthesized Speech Markup Language (SSML).
SSML is an international standard from the W3C for controlling the pace, tone, pitch and all around sound of computer-generated voices. Here's a command that says something and then repeats it at a slower speed:
say("One potato, two potato, three potato, four. ");One potato, two potato, three potato, four.
say("One potato, two potato, three potato, four. ")One potato, two potato, three potato, four.
say "One potato, two potato, three potato, four. "One potato, two potato, three potato, four.
The previous examples made use of the rate property of the SSML prosody element to control the playback speed. Other attributes of the prosody element are pitch, contour and volume.
function say_as(value,type){ ssml_start="<?xml version='1.0'?><speak>"; ssml_end="</say-as></speak>"; ssml ="<say-as interpret-as='vxml:"+ type + "'>" + value+""; complete_string = ssml_start + ssml + ssml_end; log('@@ Say as: ' + complete_string); say(complete_string); } wait(3000); say_as('USD51.33','currency'); say_as('20314253','digits'); say_as('2031.435','number'); say_as('4075551212','phone'); say_as('20090226','date'); say_as('0515a','time');
function say_as($value, $type) { $ssml_start = "<?xml version='1.0'?><speak>"; $ssml_end="</say-as></speak>"; $ssml ="<say-as interpret-as=\"vxml:$type\">$value"; $complete_string = $ssml_start . $ssml . $ssml_end; _log('@@ Say as: ' . $complete_string); say($complete_string); } wait(3000); say_as("USD51.33","currency"); say_as("20314253","digits"); say_as("2031.435","number"); say_as("4075551212","phone"); say_as("20090226","date"); say_as("0515a","time");
def say_as(value,type): ssml_start="<?xml version='1.0'?><speak>" ssml_end="</say-as></speak>" ssml ="<say-as interpret-as='vxml:"+ type + "'>" + value+"" complete_string = ssml_start + ssml + ssml_end log('@@ Say as: ' + complete_string) say(complete_string) wait(3000) say_as('USD51.33','currency') say_as('20314253','digits') say_as('2031.435','number') say_as('4075551212','phone') say_as('20090226','date') say_as('0515a','time')
def say_as(value, type){ ssml_start = "<?xml version='1.0'?><speak>" ssml_end = "</say-as></speak>" ssml = "<say-as interpret-as='vxml:$type'>$value" complete_string = ssml_start + ssml + ssml_end log('@@ Say as: ' + complete_string) say complete_string } await(3000) say_as('USD51.33','currency') say_as('20314253','digits') say_as('2031.435','number') say_as('4075551212','phone') say_as('20090226','date') say_as('0515a','time')
def say_as(value,type) ssml_start="<?xml version='1.0'?><speak>" ssml_end="</say-as></speak>" ssml ="<say-as interpret-as='vxml:#{type}'>#{value}" complete_string = ssml_start + ssml + ssml_end log '@@ Say as: ' + complete_string say complete_string end wait(3000) say_as('USD51.33','currency') say_as('20314253','digits') say_as('2031.435','number') say_as('4075551212','phone') say_as('20090226','date') say_as('0515a','time')
Tropo provides a REST API to allow for events, called signals, to be sent to functions. The first subsection explains how this works, and the following subsections give examples of allowing a single signal, multiple signals, and unnamed signals, concluding with subsections on how to specify a callback that will be executed when a signal is received and how event queuing works.
Signals are generated by making an HTTP/HTTPS GET or POST request.
A GET request is of the following form:
https://api.tropo.com/1.0/sessions/<session-id>/signals?action=signal&&value=<myname>
where <session-id> is the 16-byte GUID session ID that Tropo gives you in currentCall.sessionId and <myname> is the signal name you want sent to that session.
A POST request is of the following form:
https://api.tropo.com/1.0/sessions/<session-id>/signals
where <session-id> is the 16-byte GUID session ID that Tropo gives you in currentCall.sessionId and the header and body are one of the following forms:
accept:application/json content-type:application/jsonJSON body:
{ "value":"myname" }
accept:text/xml content-type:text/xmlXML body:
<signal> <value>myname</value> </signal>
where myname should be replaced with the signal name you want to send to the session.
For both POST and GET, the response body is as follows:
{"status": "QUEUED"}
<signal> <status>QUEUED</status> </signal>
Say you want to play some hold music, then interrupt it later. In order to interrupt, you would give the say that's playing the hold music a signal using the allowSignals parameter. You can then make a web service call using that name and Tropo will stop running that function. If the function has already run and completed, your interrupt request will be ignored. If it has not run yet, it will be queued until the function runs.
This example uses "exit" for allowSignals:
var sessionid = currentCall.sessionId; log("The Session ID is " + sessionid); say("http://www.phono.com/audio/holdmusic.mp3", { allowSignals: "exit" }); say("You are now off hold.");
$sessionid = $currentCall->sessionId; log("The Session ID is " . $sessionid); say("http://www.phono.com/audio/holdmusic.mp3", array( "allowSignals" => "exit" )); say("You are now off hold.");
sessionid = currentCall.sessionId log("The Session ID is " + sessionid) say("http://www.phono.com/audio/holdmusic.mp3", { "allowSignals": "exit" }) say("You are now off hold.")
sessionid = currentCall.sessionId log("The Session ID is " + sessionid) say("http://www.phono.com/audio/holdmusic.mp3", [ allowSignals: "exit" ]) say("You are now off hold.")
sessionid = $currentCall.sessionId log "The Session ID is " + sessionid say "http://www.phono.com/audio/holdmusic.mp3", { :allowSignals => "exit"} say "You are now off hold."
You can also use an array of signals - the function will stop if it receives an interrupt signal matching any of the names in the array.
var sessionid = currentCall.sessionId; log("The Session ID is " + sessionid); say("http://www.phono.com/audio/holdmusic.mp3", { allowSignals: ["exit", "stopHold", "dequeue"] }); say("You are now off hold.");
$sessionid = $currentCall->sessionId; log("The Session ID is " . $sessionid); say("http://www.phono.com/audio/holdmusic.mp3", array( "allowSignals" => array("exit", "stopHold", "dequeue") )); say("You are now off hold.");
sessionid = currentCall.sessionId log("The Session ID is " + sessionid) say("http://www.phono.com/audio/holdmusic.mp3", { "allowSignals": ["exit", "stopHold", "dequeue"] }) say("You are now off hold.")
sessionid = currentCall.sessionId log("The Session ID is " + sessionid) say("http://www.phono.com/audio/holdmusic.mp3", [ allowSignals: ["exit", "stopHold", "dequeue"] ]) say("You are now off hold.")
sessionid = $currentCall.sessionId log "The Session ID is " + sessionid say "http://www.phono.com/audio/holdmusic.mp3", { :allowSignals => ["exit", "stopHold", "dequeue"] } say "You are now off hold."
If you don't provide a function with a signal (or a list of signals), it will be interrupted by any signal sent to the API; the default value of allowSignals is essentially a wildcard. This allows you to send a signal to interrupt a Tropo function without telling Tropo ahead of time that you intend to interrupt the function. The say in this app can be interrupted by any signal:
say("http://www.phono.com/audio/holdmusic.mp3");
say("http://www.phono.com/audio/holdmusic.mp3")
say "http://www.phono.com/audio/holdmusic.mp3"
However, if you specifically define allowSignals using "", this will be interpreted as "never interrupt" instead:
say("http://phono.com/audio/holdmusic.mp3", { allowSignals: "" });
say("http://www.phono.com/audio/holdmusic.mp3", array( "allowSignals" => "" ));
say("http://www.phono.com/audio/holdmusic.mp3", { "allowSignals": "" })
say("http://www.phono.com/audio/holdmusic.mp3", [ allowSignals: "" ])
say "http://www.phono.com/audio/holdmusic.mp3", { :allowSignals => "" }
You can include an onSignal parameter that specifies a callback function to run if the function is interrupted. If included, this will run, the method will end and your script will continue. If it's not present, the method simply ends and returns control back to your script. Here's an example:
say("http://phono.com/audio/holdmusic.mp3", { allowSignals: "exit", onSignal: function (event) { say("Hold music over."); } }); say("You are now off hold.");
function signalFCN($event) { say("Hold music over."); } say("http://phono.com/audio/holdmusic.mp3", array( "allowSignals" => "exit", "onSignal" => "signalFCN" )); say("You are now off hold.");
def signalFCN(event): say("Hold music over.") say("http://phono.com/audio/holdmusic.mp3", { "allowSignals": "exit", "onSignal": signalFCN }) say("You are now off hold.")
say("http://phono.com/audio/holdmusic.mp3", [ allowSignals: "exit", onSignal: { event-> say "Hold music over."} ]) say("You are now off hold.")
say "http://phono.com/audio/holdmusic.mp3", { :allowSignals => "exit", :onSignal => lambda { |event| say "Hold music over."} } say "You are now off hold."
The event queue is a first-in-first-out queue. When an interruptible Tropo method runs, it starts processing the queue, discarding events that don't match until it reaches one that does. It then stops, leaving the rest of the items on the queue. Events that arrive during the execution of a Tropo method are processed in the same way. This means if you have a number of interruptible events in an application, you should take care to send interrupts in the order that they appear in your application.
Consider the following application. This application uses the "conference" method defined in Tropo but not included in this document.
say("http://phono.com/audio/holdmusic.mp3", { allowSignals: "exithold" }); conference("1234", { allowSignals: "endconf" });
say("http://phono.com/audio/holdmusic.mp3", array( "allowSignals" => "exithold" )); conference("1234", array( "allowSignals"=>"endconf" ));
say("http://phono.com/audio/holdmusic.mp3", { "allowSignals": "exithold" }) conference("1234", { "allowSignals": "endconf" })
say("http://phono.com/audio/holdmusic.mp3", [ allowSignals: "exithold" ]) conference("1234", [ allowSignals: "endconf" ])
say "http://phono.com/audio/holdmusic.mp3", { :allowSignals => "exithold"} conference "1234", { :allowSignals => "endconf"}
You believe the hold music to be already over, so you send only an "endconf" event. But the hold music is still playing, so the say function will receive the "endconf" event, see it doesn't match, and discard it. The conference will never be interrupted.
To be safe, you can send both the "exithold" event and the "endconf" event, in order. If the hold music is already over, the conference will reject and discard the "exithold" event and move onto the next event, "endconf". If it isn't over, the hold music will be interrupted, followed immediately by the interruption of the conference.This section explains and provides examples of how to use ask.
A typical ask has three steps:
Here's a basic example that asks the user their favorite color, repeats it back to him/her, and records the result in the log. Best part? It'll work on any channel - phone, text or IM:
var result = ask("What's your favorite color? Choose from red, blue or green.", { choices:"red, blue, green" }); say("You said " + result.value); log("They said " + result.value);
$result = ask("What's your favorite color? Choose from red, blue or green.", array( "choices" => "red, blue, green" )); say("You said" . $result->value); _log("They said " . $result->value);
result = ask("What's your favorite color? Choose from red, blue or green.", { "choices":"red, blue, green"}) say("You said " + result.value) log("They said " + result.value)
result = ask("What's your favorite color? Choose from red, blue or green.", [ choices: "red, blue, green"]) say("You said $result.value") log("They said $result.value")
result = ask "What's your favorite color? Choose from red, blue or green.", { :choices => "red, blue, green"} say "You said " + result.value log "They said " + result.value
Tropo supports a number of simple ways to specify typical choices. For example, if you want to collect a single digit input from a user, you could do this:
result=ask("Pick a number from 0 to 9", { choices:"0,1,2,3,4,5,6,7,8,9" }); say("You said " + result.value); log("They said " + result.value);
$result = ask("Pick a number from 0 to 9", array( "choices" => "0,1,2,3,4,5,6,7,8,9" )); say("You said" . $result->value); _log("They said " . $result->value);
result = ask("Pick a number from 0 to 9", { "choices":"0,1,2,3,4,5,6,7,8,9"}) say("You said " + result.value) log("They said " + result.value)
result = ask("Pick a number from 0 to 9", [ choices: "0,1,2,3,4,5,6,7,8,9"]) say("You said $result.value") log("They said $result.value")
result = ask "Pick a number from 0 to 9", { :choices => "0,1,2,3,4,5,6,7,8,9"} say "You said " + result.value log "They said " + result.value
Or, more simply, you can replace the numbered list with the [DIGITS] grammar:
result=ask("Pick a number from 0 to 9", { choices:"[1 DIGIT]" }); say("You said " + result.value); log("They said " + result.value);
$result = ask("Pick a number from 0 to 9", array( "choices" => "[1 DIGIT]" )); say("You said" . $result->value); _log("They said " . $result->value);
result = ask("Pick a number from 0 to 9", { "choices":"[1 DIGIT]"}) say("You said " + result.value) log("They said " + result.value)
result = ask("Pick a number from 0 to 9", [ choices: "[1 DIGIT]"]) say("You said $result.value") log("They said $result.value")
result = ask "Pick a number from 0 to 9", { :choices => "[1 DIGIT]"} say "You said " + result.value log "They said " + result.value
Or if you want the user to enter their four digit pin code, just replace the ask prompt with
"Please enter your four digit pin"
and change the [DIGITS] grammar to:
[4 DIGITS]
The simple syntax Tropo uses to allow you to specify possible user inputs is called, simply put, "simple grammar". Simple grammar doesn't necessarily mean basic, however, as it can be fairly complex and comprehensive.
A slightly more advanced version of the Tropo ask method has five steps:
Tropo has several optional parameters you can set that control the behavior of an ask. For example, let's say you want to repeat your question when the user doesn't respond to it before the default 30 second timeout occurs. You can set the optional attempts parameter to ask the question up to three times before giving up:
var result = ask("What's your favorite color? Choose from red, blue or green.", { choices: "red, blue, green", attempts: 3 }); say("You chose " + result.value);
$result = ask("What's your favorite color? Choose from red, blue or green.", array( "choices" => "red, blue, green", "attempts" => 3 )); say("You chose" . $result->value);
result = ask("What's your favorite color? Choose from red, blue or green.", { "choices" : "red, blue, green" "attempts" : 3}) say("You chose " + result.value)
result = ask("What's your favorite color? Choose from red, blue or green.", [ choices: "red, blue, green", attempts: 3]) say("You chose $result.value")
result = ask "What's your favorite color? Choose from red, blue or green.", { :choices => "red, blue, green", :attempts => 3} say "You chose " + result.value
Tropo will by default wait up to thirty seconds for the user to respond. What if you want to wait 10 seconds between attempts instead of thirty? Just use the timeout parameter:
var result = ask("What's your favorite color? Choose from red, blue or green.", { choices: "red, blue, green", timeout: 10.0, attempts: 3 }); say("You chose " + result.value);
$result = ask("What's your favorite color? Choose from red, blue or green.", array( "choices" => "red, blue, green", "timeout" => 10.0, "attempts" => 3, )); say("You chose" . $result->value);
result = ask("What's your favorite color? Choose from red, blue or green.", { "choices":"red, blue, green", "timeout":10.0, "attempts":3}) say("You chose " + result.value)
result = ask("What's your favorite color? Choose from red, blue or green.", [ choices: "red, blue, green", timeout: 10.0, attempts: 3]) say("You chose $result.value")
result = ask "What's your favorite color? Choose from red, blue or green.", { :choices => "red, blue, green", :timeout => 10.0, :attempts => 3} say "You chose " + result.value
With the above, Tropo will still ask up to three times, but only wait 10 seconds between attempts.
What if the user never responds to your ask, responds with something other than the possible choices you've specified or responds with something Tropo doesn't understand? You'd probably like to tell the user they made a bad choice, and possibly provide more information so they can respond properly. To do this, just use the optional onBadChoice and onChoice event handlers:
ask("What's your favorite color?", { choices: "red, blue, green", timeout: 10.0, attempts: 3, onBadChoice: function(event) { say("I'm sorry, I didn't understand that. You can select red, blue, or green"); }, onChoice: function(event) { say("You chose " + event.value); } });
ask("What's your favorite color?", array( "choices" => "red, blue, green", "timeout" => 10.0, "attempts" => 3, "onChoice" => "choiceFCN", "onBadChoice" => "badChoiceFCN" ) ); function choiceFCN($event) { say("You chose" . $event->value); } function badChoiceFCN($event) { say("I'm sorry, I didn't understand that. You can select red, blue, or green"); }
ask("What's your favorite color?", { "choices":"red, blue, green", "timeout":10.0, "attempts":3, "onChoice": lambda event : say("You chose " + event.value), "onBadChoice": lambda event : say("I'm sorry, I didn't understand that. You can select red, blue, or green") })
ask("What's your favorite color?", [ choices: "red, blue, green", timeout: 10.0, attempts: 3, onChoice: {event-> say("You chose $event.value") }, onBadChoice: {event-> say("I'm sorry, I didn't understand that. You can select red, blue, or green") } ])
ask "What's your favorite color?", { :choices => "red, blue, green", :timeout => 10.0, :attempts => 3, :onBadChoice => lambda { |event| say "I'm sorry, I didn't understand that. You can select red, blue, or green" }, :onChoice => lambda { |event| say "You chose " + event.value } }
Grammar is just a fancy word for telling Tropo what to expect from the user; Simple Grammar is the term we use for the built-in default way of expressing input requirements in Tropo.
In the Asking for Digits section, we introduced the [DIGITS] grammar and used it to tell Tropo to expect 1 digit. You can also express a range of digits by using [4-5 DIGITS] instead (the number values are defined by you; it could be [10-20 DIGITS], [1-2 DIGITS], and so on). If your caller enters 4 digits instead of 5, Tropo will wait for a period of time (defined by the timeout value, which automatically defaults to 30.0 seconds, but can be set longer or shorter) before considering the input complete. If you want to allow your callers to press a key to tell Tropo they're done, just add the terminator parameter to your ask statement and set it to the key they should press.
Because there are two primary ways of interacting with users over the phone (keypad and voice) and by default Tropo will listen for input in both modes, we'll want to specify "keypad" input for our pin number request. This behavior can be controlled using the mode parameter. Possible values are "keypad", "speech" or "any":
ask("What's your four or five digit pin? Press pound when finished", { choices:"[4-5 DIGITS]", terminator:"#", timeout:15.0, mode:"keypad", onChoice: function(event) { say("Thank you"); } });
ask("What's your four or five digit pin? Press pound when finished", array( "choices"=>"[4-5 DIGITS]", "terminator" => "#", "timeout" => 15.0, "mode" => "keypad", "onChoice" => "choiceFCN" ) ); function choiceFCN($event) { say("Thank you"); }
ask("What's your four or five digit pin? Press pound when finished.", { "choices":"[4-5 DIGITS]", "terminator":"#", "timeout":15.0, "mode":"keypad", "onChoice": lambda event : say("Thank you") })
ask("What's your four or five digit pin? Press pound when finished.", [ choices: "[4-5 DIGITS]", terminator: '#', timeout: 15.0, mode: "keypad" onChoice: { event-> say("Thank you.") } ])
ask "What's your four or five digit pin? Press pound when finished", { :choices => "[4-5 DIGITS]", :terminator => '#', :timeout => 15.0, :mode => "keypad", :onChoice => lambda { |event| say "Thank you" } }
So far we've covered how to collect numeric data, but Tropo is capable of so much more. You can use Tropo's simple grammar notation to recognize words and even entire phrases.
The following script works via the phone, text or IM. It's a company directory that starts off by welcoming the user, then asks the user who they're trying to reach; this can be the name of a person or department. Depending on the type selected (person or department), one of two web services is called to play back the contact information.
ask("Welcome to the Tropo company directory. Who are you trying to reach?", { choices:"department(support, engineering, sales), person(jose, jason, adam)", onChoice: function(event) { say("You said " + event.choice.interpretation + ", which is a " + event.value); } });
ask("Welcome to the Tropo company directory. Who are you trying to reach?", array( "choices"=>"department(support, engineering, sales), person(jose, jason, adam)", "onChoice" => "choiceFCN" ) ); function choiceFCN($event) { say("You said " . $event->choice->interpretation . ", which is a " . $event->value); }
def choiceFCN(event): if (event.name == "choice"): say("You said " + event.choice.interpretation + ", which is a " + event.value) event = ask("Welcome to the Tropo company directory. Who are you trying to reach?", { "choices":"department(support, engineering, sales), person(jose, jason, adam)", "onChoice":choiceFCN})
ask("Welcome to the Tropo company directory. Who are you trying to reach?", [ choices: "department(support, engineering, sales), person(jose, jason, adam)", onChoice: { event-> say "You said " + event.choice.interpretation + ", which is a " + event.value} ])
ask "Welcome to the Tropo company directory. Who are you trying to reach?", { :choices => "department(support, engineering, sales), person(jose, jason, adam)", :onChoice => lambda { |event| say("You said " + event.choice.interpretation + ", which is a " + event.value) } }
There's a lot going on in this short example so let's break it down.
First, we tell Tropo to speak the introductory prompt which asks the user who they'd like to reach. We pass in two parameters to the ask method: choices and onChoice. The choices parameter instructs Tropo to listen for a set of words; in this case, department and people names. The key thing here is the notation used to define the grammar:
department(support, engineering, sales), person(jose, jason, adam)
The words outside the parentheses are called concepts. Concepts provide a context when handling the user's response. The company directory grammar defines two concepts: department and person. When the caller says one of the items inside the parentheses (such as support, jose, etc.), an event is triggered that gives us access to both the spoken word and the concept to which it belongs.
While Tropo's Simple Grammar is pretty awesome, it's not really suited for extremely large data sets.
For example, let's say you were writing a travel app and wanted to allow your users to speak their destination city or airport. There are hundreds, if not thousands of airports, and many more ways to actually say them. They could say "JFK", "John F. Kennedy", "NYC", "New York International", etc. These types of complex grammars are best suited for the Speech Recognition Grammar Specification (SRGS). The SRGS is a W3C standard way of controlling speech recognition engines. SRGS can take a variety of forms, with the most popular being Grammar XML (or GRXML for short).
To use GRXML from your Tropo Scripting application, simply provide the URL to an external file.
var result = ask("What's your destination?", { choices:"http://example.com/tropo/destinations.grxml" });
$result = ask("What's your destination?", array( "choices" => "http://example.com/tropo/destinations.grxml" ) );
result = ask("What's your destination?", { "choices":"http://example.com/tropo/destinations.grxml" })
result = ask("What's your destination?", [ choices: "http://example.com/tropo/destinations.grxml"])
result = ask "What's your destination?", { :choices => "http://example.com/tropo/destinations.grxml"}
For more information on SRGS grammars, see http://www.w3.org/TR/speech-grammar/
This event is returned whenever a valid response is provided by a user, such as returning "john" when the choices provided are "john, jane".
choice(, {anonymous function: STRING})
None
event=ask("Who would you like to call? Just say John or Jane", { attempts:3, timeout:7, choices:"john, jane", onChoice:function(event) { say("You said" + event.choice); }, onBadChoice: function(event) { say("I'm sorry, I didn't understand what you said."); }, onError:function(event) { log("There was an error"); }, onHangup:function(event) { log("The user hungup"); }, onTimeout:function(event) { say("I'm sorry, I didn't hear anything."); } });
ask("Who would you like to call? Just say John or Jane", array( "attempts" => 3, "timeout" => 7, "choices"=>"john, jane", "onChoice" => "choiceFCN" , "onBadChoice" => "badChoiceFCN", "onError" => "errorFCN", "onHangup" => "hangupFCN", "onTimeout" => "timeoutFCN" ) ); function choiceFCN($event) { say("You said" . $event->value); } function badChoiceFCN($event) { say("I'm sorry, I didn't understand what you said."); } function errorFCN($event) { log("There was an error"); } function hangupFCN($event) { log("The user hungup"); } function timeoutFCN($event) { say("I'm sorry, I didn't hear anything"); }
ask("Who would you like to call? Just say John or Jane", { "attempt":3, "timeout":7, "choices":"john, jane", "onChoice": lambda event : say("You said" + event.value), "onBadChoice": lambda event : say("I'm sorry, I didn't understand what you said."), "onError": lambda event : log("There was an error"), "onHangup": lambda event : log("The user hungup"), "onTimeout": lambda event : say("I'm sorry, I didn't hear anything") })
ask("Who would you like to call? Just say John or Jane", [ attempts: 3, timeout: 7, choices: "john, jane", onChoice: { event-> say("You said" + event.value)}, onBadChoice: { event-> say("I'm sorry, I didn't understand what you said")}, onError: { event-> log("There was an error")}, onHangup: { event-> log("The user hungup")}, onTimeout: { event-> say("I'm sorry, I didn't understand what you said")} ])
event=ask "Who would you like to call? Just say John or Jane", { :attempts => 3, :timeout => 7, :choices => "john, jane", :onChoice => lambda { |event| say "You said" + event.value}, :onBadChoice => lambda { |event| say "I'm sorry, I didn't understand what you said."}, :onError => lambda { |event| log "There was an error"}, :onHangup => lambda { |event| log "The user hungup"}, :onTimeout => lambda { |event| say "I'm sorry, I didn't hear anything"} }
This event is returned whenever an unexpected, significant system error occurs, such as the ASR engine failing. This should be a very rare event; it's unlikely to be encountered with any regularity, if at all.
error(, {anonymous function: STRING})
None
event=ask("Who would you like to call? Just say John or Jane", { choices:"john, jane", onError:function(event) { log("There was an error"); }} );
ask("Who would you like to call? Just say John or Jane", array( "choices"=>"john, jane", "onError" => "errorFCN" ) ); function errorFCN($event) { log("There was an error"); }
ask("Who would you like to call? Just say John or Jane", { "choices":"john, jane", "onError": lambda event : log("There was an error") })
ask("Who would you like to call? Just say John or Jane", [ choices: "john, jane", onError: { event-> log("There was an error")} ])
event=ask "Who would you like to call? Just say John or Jane", { :choices => "john, jane", :onError => lambda { |event| log "There was an error"} }
This event is returned when the user disconnects or "hangs up" the call.
hangup(, {anonymous function: STRING})
None
event=ask("Who would you like to call? Just say John or Jane", { choices:"john, jane", onChoice:function(event) { say("You said" + event.choice); }, onBadChoice: function(event) { say("I'm sorry, I didn't understand what you said."); }, onTimeout:function(event) { say("I'm sorry, I didn't hear anything."); }, onHangup: function(event) { log("Caller disconnected"); } });
ask("Who would you like to call? Just say John or Jane", array( "choices"=>"john, jane", "onChoice" => "choiceFCN" , "onBadChoice" => "badChoiceFCN", "onTimeout" => "timeoutFCN", "onHangup" => "hangupFCN" ) ); function choiceFCN($event) { say("You said" . $event->value); } function badChoiceFCN($event) { say("I'm sorry, I didn't understand what you said."); } function timeoutFCN($event) { say("I'm sorry, I didn't hear anything"); } function hangupFCN ($event) { log("Caller disconnected."); }
ask("Who would you like to call? Just say John or Jane", { "choices":"john, jane", "onChoice": lambda event : say("You said" + event.value), "onBadChoice": lambda event : say("I'm sorry, I didn't understand what you said."), "onTimeout": lambda event : say("I'm sorry, I didn't hear anything"), "onHangup": lambda event : log("Caller disconnected") })
ask("Who would you like to call? Just say John or Jane", [ choices: "john, jane", onChoice: { event-> say("You said" + event.value)}, onBadChoice: { event-> say("I'm sorry, I didn't understand what you said")}, onTimeout: { event-> say("I'm sorry, I didn't understand what you said")}, onHangup: { event -> log("Caller disconnected") ])
event=ask "Who would you like to call? Just say John or Jane", { :choices => "john, jane", :onChoice => lambda { |event| say "You said" + event.value}, :onBadChoice => lambda { |event| say "I'm sorry, I didn't understand what you said."}, :onTimeout => lambda { |event| say "I'm sorry, I didn't hear anything"}, :onHangup => lambda { |event| log "Caller disconnected"} }
This event is returned when the user did not respond within a specified period of time, defined by the 'timeout' parameter.
timeout(, {anonymous function: STRING})
None
event=ask("Who would you like to call? Just say John or Jane", { attempts:3, timeout:7, choices:"john, jane", onChoice:function(event) { say("You said" + event.choice); }, onBadChoice: function(event) { say("I'm sorry, I didn't understand what you said."); }, onError:function(event) { log("There was an error"); }, onHangup:function(event) { log("The user hungup"); }, onTimeout:function(event) { say("I'm sorry, I didn't hear anything."); } });
ask("Who would you like to call? Just say John or Jane", array( "attempts" => 3, "timeout" => 7, "choices"=>"john, jane", "onChoice" => "choiceFCN" , "onBadChoice" => "badChoiceFCN", "onError" => "errorFCN", "onHangup" => "hangupFCN", "onTimeout" => "timeoutFCN" ) ); function choiceFCN($event) { say("You said" . $event->value); } function badChoiceFCN($event) { say("I'm sorry, I didn't understand what you said."); } function errorFCN($event) { log("There was an error"); } function hangupFCN($event) { log("The user hungup"); } function timeoutFCN($event) { say("I'm sorry, I didn't hear anything"); }
ask("Who would you like to call? Just say John or Jane", { "attempt":3, "timeout":7, "choices":"john, jane", "onChoice": lambda event : say("You said" + event.value), "onBadChoice": lambda event : say("I'm sorry, I didn't understand what you said."), "onError": lambda event : log("There was an error"), "onHangup": lambda event : log("The user hungup"), "onTimeout": lambda event : say("I'm sorry, I didn't hear anything") })
ask("Who would you like to call? Just say John or Jane", [ attempts: 3, timeout: 7, choices: "john, jane", onChoice: { event-> say("You said" + event.value)}, onBadChoice: { event-> say("I'm sorry, I didn't understand what you said")}, onError: { event-> log("There was an error")}, onHangup: { event-> log("The user hungup")}, onTimeout: { event-> say("I'm sorry, I didn't understand what you said")} ])
event=ask "Who would you like to call? Just say John or Jane", { :attempts => 3, :timeout => 7, :choices => "john, jane", :onChoice => lambda { |event| say "You said" + event.value}, :onBadChoice => lambda { |event| say "I'm sorry, I didn't understand what you said."}, :onError => lambda { |event| log "There was an error"}, :onHangup => lambda { |event| log "The user hungup"}, :onTimeout => lambda { |event| say "I'm sorry, I didn't hear anything"} }
Represents the result of a system operation.
Parameter | Data Type | Default | Required/ Optional |
Description |
---|---|---|---|---|
attempt | String | (none) | Optional | This allows you to set behavior for an individual attempt (the number of possible attempts is defined by the attempts method). In the following JavaScript example, attempts is defined as 3, so it will repeat the prompt three times. For event.attempt 1 and event.attempt 2, different behavior as been defined (two different says); on the third attempt, if the caller still does not return valid input, the call will just disconnect.ask("What's your favorite color?", { attempts:3, choices:"red, blue, green", onBadChoice:function(event) { switch(event.attempt) { case 1: say("We don't support that color. You can say red, blue or green.") case 2: say("It's really simple, man. Just say red, blue or green." } } |
choice | String | none | Optional | Please note that the event structure has additional information available in a "choice" object. Specifically, there is an "event.choice" object that itself has the following fields:
|
name | String | none | Optional | Depending on the event that ends the method, the event.name attribute will be set to: choice, record, timeout, badChoice, hangup, silenceTimeout, or error. |
recordURI | String | (none) | Optional | This returns either the location of a recording when working with text (e.g. when used with log) or audibly returns what was actually recorded (e.g. when used with a say on a voice call), such as in this JavaScript example:answer() record("Please leave your message at the beep.", { beep:true, timeout:10, silenceTimeout:7, maxTime:60, onRecord: function(event) { log("Recording result = " + event.recordURI) say("You said " + event.recordURI) } }) |
value | String | none | Optional | The event.value attribute will be set for choice and record events, as will event.recordURI as appropriate. The rules are:
|
ask("Welcome to the Tropo company directory. Who are you trying to reach?", { choices:"department(support, engineering, sales), person(jose, jason, adam)", onChoice: function(event) { say("You said " + event.choice.interpretation + ", which is a " + event.value); } });
ask("Welcome to the Tropo company directory. Who are you trying to reach?", array( "choices"=>"department(support, engineering, sales), person(jose, jason, adam)", "onChoice" => "choiceFCN" ) ); function choiceFCN($event) { say("You said " . $event->choice->interpretation . ", which is a " . $event->value); }
def choiceFCN(event): if (event.name == "choice"): say("You said " + event.choice.interpretation + ", which is a " + event.value) event = ask("Welcome to the Tropo company directory. Who are you trying to reach?", { "choices":"department(support, engineering, sales), person(jose, jason, adam)", "onChoice":choiceFCN})
ask("Welcome to the Tropo company directory. Who are you trying to reach?", [ choices: "department(support, engineering, sales), person(jose, jason, adam)", onChoice: { event-> say "You said " + event.choice.interpretation + ", which is a " + event.value} ])
ask "Welcome to the Tropo company directory. Who are you trying to reach?", { :choices => "department(support, engineering, sales), person(jose, jason, adam)", :onChoice => lambda { |event| say("You said " + event.choice.interpretation + ", which is a " + event.value) } }