### 9.3 Example text mode ¶

Here is a short example of a tts mode for reading email messages. It is by no means complete but is a start at showing how you can customize tts modes without writing new C++ code.

The first task is to define a filter that will take a saved mail message and remove extraneous headers and just leave the from line, subject and body of the message. The filter program is given a file name as its first argument and should output the result on standard out. For our purposes we will do this as a shell script.

    #!/bin/sh
    #  Email filter for Festival tts mode
    #  usage: email_filter mail_message >tidied_mail_message
    grep "^From: " $1
    echo
    grep "^Subject: " $1
    echo
    # delete up to first blank line (i.e. the header)
    sed '1,/^$/ d' $1

Next we define the email init function, which will be called when we start this mode. What we will do is save the current token to words function and slot in our own new one. We can then restore the previous one when we exit.

    (define (email_init_func)
     "Called on starting email text mode."
     (set! email_previous_t2w_func token_to_words)
     (set! english_token_to_words email_token_to_words)
     (set! token_to_words email_token_to_words))

Note that both `english_token_to_words` and `token_to_words` should be set to ensure that our new token to word function is still used when we change voices.

The corresponding end function puts the token to words function back.

    (define (email_exit_func)
     "Called on exit email text mode."
     (set! english_token_to_words email_previous_t2w_func)
     (set! token_to_words email_previous_t2w_func))

Now we can define the email specific token to words function. In this example we deal with two specific cases. First we deal with the common form of email addresses so that the angle brackets are not pronounced. The second points are to recognise quoted text and immediately change the the speaker to the alternative speaker.

    (define (email_token_to_words token name)
      "Email specific token to word rules."
      (cond

This first condition identifies the token as a bracketed email address and removes the brackets and splits the token into name and IP address. Note that we recursively call the function `email_previous_t2w_func` on the email name and IP address so that they will be pronounced properly. Note that because that function returns a list of words we need to append them together.

       ((string-matches name "<.*.*>")
         (append
          (email_previous_t2w_func token
           (string-after (string-before name "@") "<"))
          (cons
           "at"
           (email_previous_t2w_func token
            (string-before (string-after name "@") ">")))))

Our next condition deals with identifying a greater than sign being used as a quote marker. When we detect this we select the alternative speaker, even though it may already be selected. We then return no words so the quote marker is not spoken. The following condition finds greater than signs which are the first token on a line.

       ((and (string-matches name ">")
             (string-matches (item.feat token "whitespace")
                             "[ \t\n]*\n *"))
        (voice_don_diphone)
        nil ;; return nothing to say
       )

If it doesn’t match any of these we can go ahead and use the builtin token to words function Actually, we call the function that was set before we entered this mode to ensure any other specific rules still remain. But before that we need to check if we’ve had a newline with doesn’t start with a greater than sign. In that case we switch back to the primary speaker.

       (t  ;; for all other cases
         (if (string-matches (item.feat token "whitespace")
                             ".*\n[ \t\n]*")
             (voice_rab_diphone))
         (email_previous_t2w_func token name))))

In addition to these we have to actually declare the text mode. This we do by adding to any existing modes as follows.

    (set! tts_text_modes
       (cons
        (list
          'email   ;; mode name
          (list         ;; email mode params
           (list 'init_func email_init_func)
           (list 'exit_func email_exit_func)
           '(filter "email_filter")))
        tts_text_modes))

This will now allow simple email messages to be dealt with in a mode specific way.

An example mail message is included in `examples/ex1.email`. To hear the result of the above text mode start Festival, load in the email mode descriptions, and call TTS on the example file.

    (tts ".../examples/ex1.email" 'email)

The above is very short of a real email mode but does illustrate how one might go about building one. It should be reiterated that text modes are new in Festival and their most effective form has not been discovered yet. This will improve with time and experience.