Skip to main content

Say Modules — Number, Date, and Money Pronunciation

The say subsystem converts structured data — integers, monetary amounts, timestamps, IP addresses, and spelled names — into spoken audio by assembling sequences of pre-recorded sound files. Each mod_say_* module implements a language-specific pronunciation algorithm that determines which sound files to play and in what order. The say dialplan application and the say_string API command both invoke these modules at runtime.

Sound files are located under the path set by sound_prefix for the active language (configured in the <languages> section of freeswitch.xml). The modules use sub-directories such as digits/, currency/, and time/ relative to that prefix.

Supported Languages

The following modules ship in src/mod/say. Each module registers a say interface under its interface name; that name is what you pass as <module_name> in the say application.

ModuleInterface nameLanguage
mod_say_dedeGerman
mod_say_enenEnglish
mod_say_esesSpanish (Castilian)
mod_say_es_ares_arSpanish (Argentina)
mod_say_fafaPersian (Farsi)
mod_say_frfrFrench
mod_say_heheHebrew
mod_say_hrhrCroatian
mod_say_huhuHungarian
mod_say_ititItalian
mod_say_jajaJapanese
mod_say_nlnlDutch
mod_say_plplPolish
mod_say_ptptPortuguese
mod_say_ruruRussian
mod_say_svsvSwedish
mod_say_ththThai
mod_say_zhzhChinese
mod_say_zhzh_CNChinese (mainland; distinct currency pronunciation)

Default load: Only mod_say_en is enabled by default in autoload_configs/modules.conf.xml. All other say modules are commented out and must be explicitly added:

<load module="mod_say_en"/>
<!-- <load module="mod_say_ru"/> -->
<!-- <load module="mod_say_zh"/> -->
<!-- <load module="mod_say_sv"/> -->

To activate an additional language, uncomment its <load> line or add the equivalent to your local modules configuration.

The say Application and say_string

say dialplan application

Registered by mod_dptools. Syntax (from SAY_SYNTAX in mod_dptools.c):

say <module_name>[:<lang>] <say_type> <say_method> [<say_gender>] <text>
  • <module_name> — the say interface name (e.g. en, de, fr). Optionally append :<lang> to override the language tag used for sound-path lookup while keeping the pronunciation module separate (e.g. en:fr).
  • <say_type> — what kind of value is being spoken (see table below).
  • <say_method> — how the value should be rendered (see table below).
  • <say_gender> — optional grammatical gender (see table below); relevant only for languages with gendered inflection.
  • <text> — the value to speak (a number, epoch timestamp, dotted-quad IP, currency string, etc.).

The full data string must contain exactly 4 or 5 space-separated tokens: <module_name> <say_type> <say_method> <text> (4 tokens) or <module_name> <say_type> <say_method> <say_gender> <text> (5 tokens). It sets PLAYBACK_TERMINATOR_USED to empty before playing.

Example:

<action application="say" data="en NUMBER PRONOUNCED 12345"/>
<action application="say" data="en CURRENCY PRONOUNCED 19.99"/>
<action application="say" data="en CURRENT_DATE_TIME PRONOUNCED 0"/>
<action application="say" data="de NUMBER COUNTED MASCULINE 1"/>

say_string API command

Registered by mod_commands. Returns the colon-separated file path string instead of playing audio. Syntax (from SAY_STRING_SYNTAX in mod_commands.c):

say_string <module_name>[.<ext>] <lang>[.<ext>] <say_type> <say_method> [<say_gender>] <text>

The optional .ext suffix on either the first or second token sets the audio file extension (default wav). The <lang> token sets the language for sound-path lookup. Prefix <text> with ~ to strip the file_string:// protocol prefix from the returned path.

Example:

say_string en en NUMBER PRONOUNCED 42
say_string en.wav en NUMBER ITERATED 555

Say types

The <say_type> token maps to switch_say_type_t. The string names are case-insensitive.

TokenDescription
NUMBERCardinal integer
ITEMSItem count
PERSONSPerson count
MESSAGESMessage count
CURRENCYMonetary amount (dollars and cents for English)
TIME_MEASUREMENTDuration in hours, minutes, and seconds
CURRENT_DATEDate portion of a timestamp
CURRENT_TIMETime portion of a timestamp
CURRENT_DATE_TIMEFull date and time of a timestamp
SHORT_DATE_TIMEAbbreviated date/time (today/yesterday/day-of-week heuristic)
TELEPHONE_NUMBERPhone number digit-by-digit
TELEPHONE_EXTENSIONExtension digits
IP_ADDRESSIPv4 dotted-quad address
URLURL string
EMAIL_ADDRESSE-mail address
POSTAL_ADDRESSPostal address
ACCOUNT_NUMBERAccount number
NAME_SPELLEDName spelled out using ascii/ sound files
NAME_PHONETICName spelled using phonetic-ascii/ sound files

Not all modules handle every type. For example, mod_say_en handles TELEPHONE_NUMBER; mod_say_fr additionally handles TELEPHONE_EXTENSION, URL, EMAIL_ADDRESS, POSTAL_ADDRESS, and ACCOUNT_NUMBER; several modules (e.g. mod_say_es, mod_say_ja, mod_say_nl, mod_say_th) do not implement SHORT_DATE_TIME. Unrecognised types log an error and return false.

Say methods

The <say_method> token maps to switch_say_method_t.

TokenDescription
PRONOUNCEDSpeak the value as a natural number (e.g. "twelve thousand three hundred forty-five")
ITERATEDSpeak each digit or character individually (e.g. "one two three")
COUNTEDSpeak the value as an ordinal (e.g. "twelfth")
PRONOUNCED_YEARSpeak a year in the conventional two-group form (e.g. "nineteen ninety-nine")

When say_method is ITERATED and say_type is CURRENT_TIME, mod_say_en uses 24-hour (military) time format.

Say genders

The <say_gender> token maps to switch_say_gender_t. This argument is optional and is silently ignored by modules that do not use it.

TokenDescription
MASCULINEMasculine grammatical gender
FEMININEFeminine grammatical gender
NEUTERNeuter grammatical gender

mod_say_de uses gender to select inflected sound files for counted ordinals: masculine uses digits/h-1_m.wav, neuter uses digits/h-1_n.wav, and feminine (and unspecified gender) falls back to the unsuffixed form digits/h-1.wav. mod_say_he, mod_say_ru, and other modules with grammatical gender do the same.

Selecting the Language

When switch_ivr_say is called (from the say application), it determines the active language in this order:

  1. The :lang suffix on the <module_name> argument, if present.
  2. The channel variable language.
  3. The channel variable default_language.
  4. The module name itself (bare, without any suffix).

The resolved language tag is then used to look up a <language> element in the languages section of freeswitch.xml. The lookup is performed via switch_xml_locate_language.

The <languages> section

freeswitch.xml contains a <section name="languages"> block that includes per-language XML files:

<section name="languages" description="Language Management">
<X-PRE-PROCESS cmd="include" data="lang/de/*.xml"/>
<X-PRE-PROCESS cmd="include" data="lang/en/*.xml"/>
<X-PRE-PROCESS cmd="include" data="lang/fr/*.xml"/>
<X-PRE-PROCESS cmd="include" data="lang/ru/*.xml"/>
<X-PRE-PROCESS cmd="include" data="lang/he/*.xml"/>
<X-PRE-PROCESS cmd="include" data="lang/es/es_ES.xml"/>
<X-PRE-PROCESS cmd="include" data="lang/pt/pt_BR.xml"/>
</section>

Each included file defines a <language> element. The say-module attribute on <language> tells the core which say interface module to use; sound-prefix sets the base path for audio files.

English (lang/en/en.xml):

<language name="en" say-module="en" sound-prefix="$${sound_prefix}"
tts-engine="cepstral" tts-voice="callie">
<phrases>
<macros>
<X-PRE-PROCESS cmd="include" data="demo/*.xml"/>
<X-PRE-PROCESS cmd="include" data="vm/sounds.xml"/>
<X-PRE-PROCESS cmd="include" data="dir/sounds.xml"/>
<X-PRE-PROCESS cmd="include" data="ivr/*.xml"/>
</macros>
</phrases>
</language>

French (lang/fr/fr.xml):

<language name="fr" say-module="fr"
sound-prefix="$${sounds_dir}/fr/ca/june"
tts-engine="cepstral" tts-voice="david">
AttributePurpose
nameLanguage tag matched against the language / default_language channel variables
say-moduleSay interface name to load (must match the interface_name registered by the module)
sound-prefixBase directory for all audio files used by this language
tts-engineTTS engine name (used by phrase macros that call TTS; not used by the say subsystem directly)
tts-voiceTTS voice name (same scope as tts-engine)

If say-module is absent, the core falls back to the deprecated module attribute (with a warning), and then to the language tag itself.

The sound_prefix channel variable can be overridden at call time. The core saves and restores the previous value around each say invocation unless sound_prefix_enforced is set to a true value on the channel.

Source: src/mod/say, src/mod/applications/mod_dptools, src/mod/applications/mod_commands, src/switch_ivr.c, src/switch_ivr_say.c