# $Id: mbrola,v 9.0 1998/10/24 05:17:28 raman Exp $
# Description:  Interfacing to MBROLA  via TCL. 
# Keywords: Emacspeak, MBROLA, TCL
# {{{ LCD Entry: 

# LCD Archive Entry:
# emacspeak| T. V. Raman |raman@adobe.com 
# A speech interface to Emacs |
# $Date: 1998/10/24 05:17:28 $ |
#  $Revision: 9.0 $ | 
# Location undetermined
#

# }}}
# {{{ Copyright:  

# Copyright (c) 1995, 1996, 1997, 1998  T. V. Raman, Adobe Systems
# Incorporated.
#All Rights Reserved
# Copyright (c) 1994, 1995 by Digital Equipment Corporation.
# All Rights Reserved. 
#
# This file is not part of GNU Emacs, but the same permissions apply.
#
# GNU Emacs is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# GNU Emacs is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with GNU Emacs; see the file COPYING.  If not, write to
# the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.

# }}}
# {{{ procedures  

proc tts_set_punctuations {mode} {
    global tts
    set tts(punctuations) $mode
    return ""
}

proc tts_set_speech_rate {rate} {
    global tts
    set factor $tts(char_factor) 
    set tts(say_rate) [round \
                                       [expr $rate  * $factor ]]
    set tts(speech_rate) $rate
    return ""
}

proc tts_set_character_scale {factor} {
    global tts
    set tts(say_rate) [round \
                                       [expr $tts(speech_rate) * $factor ]]
    set tts(char_factor) $factor
    return ""
}

proc tts_say {text} {
    global tts
    set tts(not_stopped) 1
    set pattern {\[[^]*\]}
    regsub -all $pattern $text " " text
    exec echo $text | $tts(speak) 2>&1 > /dev/null        
    return ""
}

#formerly called tts_letter

proc l {text} {
    global tts
    set tts(not_stopped) 1
    set r $tts(speech_rate)
    set f  $tts(say_rate)
    exec echo $text | $tts(speak) 2>&1 > /dev/null
        return ""
}

#formerly called tts_speak
proc d {} {
    speech_task
}

proc tts_resume  {} {
    global tts
    queue_restore
    if {[queue_empty?]} {
        set text "No speech to resume. "
        exec echo $text | $tts(speak) 2>&1 > /dev/null
        set tts(not_stopped) 1
    } else {
        speech_task
    }
    return ""
}

proc tts_pause {} {
    global tts 
    queue_backup
    s
    return ""
}

#formerly called tts_stop 

proc s {}  {
    queue_clear
}

#formerly called tts_tone

proc t  {{pitch 440} {duration 50}} {
    global tts queue
    set tone "\[:to $pitch $duration\]"
    set queue($tts(q_tail)) [list t $tone]
    incr tts(q_tail)
    return ""
}

proc sh  {{duration 50}} {
    global tts queue
    set silence "\[_<$duration>\]"
    set queue($tts(q_tail)) [list t $silence]
    incr tts(q_tail)
    return ""
}


proc tts_split_caps {flag} {
    global tts 
    set tts(split_caps) $flag
    return ""
}

proc tts_capitalize {flag} {
    global tts 
    set tts(capitalize) $flag
    return ""
}

proc tts_allcaps_beep {flag} {
    global tts 
    set tts(allcaps_beep) $flag
    return ""
}

proc  read_pending_p  {file_handle} {
    set status   [lsearch [select [list  $file_handle]  {} {} 0] $file_handle]
    expr $status >= 0
}

#note that we cannot use stdin here due to a tcl bug.
#in tcl 7.4 we could always say file0
#in 7.5 and above  (only tested in 7.5 and 8.0)
 #we need to say sock0 when we are a server

proc tts_get_acknowledgement {} {
    global tts
    set input $tts(input)
    set status [select [list    $input ] {} {} 0]
    if {[lsearch $status $input]   >=0} {
        set tts(talking?) 0
    } else {
    }
    return ""
}

#Gobble up any garbage the Dectalk has returned.

proc tts_gobble_acknowledgements {{delay 0.1}} {
    global tts
    set r $tts(read)
    while {[lsearch [select [list  $r] {} {} 0.001] $r] >= 0  } {
        read $r  1
    }
}

proc tts_reset {} {
    set text "reset is not yet implemented. "
    exec echo $text | $tts(speak) 2>&1 > /dev/null
}

# }}}
# {{{ speech task 

proc speech_task {} {
    global queue tts
    set tts(talking?) 1
    set tts(not_stopped) 1
    set mode  $tts(punctuations) 
    set r $tts(speech_rate)
    set length [queue_length]
    #set up tts state
    set phone freephone
    set lex -h/usr/lib/mbrola/tts-English/lib/lexicon
    set synth mbrola
    set lang /usr/lib/mbrola/en1/en1
    set rate  [expr 300.0 / $r]
    loop index 0 $length {
        set event   [queue_remove]
        set event_type [lindex $event 0]
        switch  -exact -- $event_type {
            s {
                set text [clean [lindex $event 1]]
                catch [exec echo $text \
                           | $phone $lex \
                           | $synth -v $tts(volume) -f $tts(pitch) \
                           -l $tts(freq) -t $rate  $lang - -.au  \
                           | na_play 2>&1 > /dev/null] errCode
                set retval [tts_get_acknowledgement ]
            }
            t {
                set text  [lindex $event 1]
                #send out the tone
            }
            a {
                set sound [lindex $event 1]
                catch "exec $tts(play) $sound >& /dev/null &" errCode
            }
        }
        if {$tts(talking?) == 0} {break;} 
    }
    set tts(talking?) 0
    return ""
}

# }}}
# {{{ queue:

#currently we use an inlined version of this test in speech_task

proc queue_empty? {} {
    global tts
    expr $tts(q_head) == $tts(q_tail)
}

proc queue_nonempty? {} {
    global tts
    expr $tts(q_head) != $tts(q_tail)
}

proc queue_length {} {
    global tts
    expr $tts(q_tail) - $tts(q_head)
}

proc queue_clear {} {
    global tts queue
    if {$tts(debug)} {
        exec echo $text | $tts(speak) 2>&1 > /dev/null
    }
    unset queue
    set queue(-1) "" 
    set tts(q_head) 0
    set tts(q_tail) 0 
    return ""
}

#formerly called queue_speech --queue speech event

proc q {element} {
    global queue tts
    set queue($tts(q_tail)) [list s $element]
    incr tts(q_tail)
    set mod [expr ($tts(q_tail) - $tts(q_head)) % 50]
    set sound "progress.au"
    if {$mod == 0} {
        catch "exec $tts(play) $sound >& /dev/null &" errCode
    }
    return ""
}

#queue a sound event

proc a {sound} {
    global queue tts
    set queue($tts(q_tail)) [list a $sound]
    incr tts(q_tail)
    return ""
}


proc queue_remove {} {
    global tts queue 
    set element  $queue($tts(q_head))
    incr tts(q_head)
    return $element
}

proc queue_backup {} {
    global tts  backup queue
    unset backup
    set backup(-1) ""
    set head [expr  $tts(q_head) - 2]
    set tail $tts(q_tail)
    loop i $head $tail 1 {
        set backup($i) $queue($i)
    }
    set tts(backup_head) $head
    set tts(backup_tail) $tail
}

proc queue_restore {} {
    global tts  backup queue
    unset queue
    set queue(-1) ""
    set head $tts(backup_head)
    set tail $tts(backup_tail)
    loop i $head $tail 1 {
        set queue($i) $backup($i)
    }
    set tts(q_head) $head
    set tts(q_tail) $tail
}

# }}}
# {{{sounds: 

#play a sound over the server
proc p {sound} {
    global tts
    catch "exec $tts(play) $sound >& /dev/null &" errCode
    speech_task
}

    # }}}
# {{{mbrola specific code 

#preprocess element before sending it out:

#set up pronunciation table 

proc clean {element} {
    global tts pronounce
    #first nuke all embedded controls 
    regsub -all $tts(inline_commands) $element { }  element
    #mbrola (specifically freephone) dies on blank lines
    regsub -all "\012|\015" $element { } element
    #split numbers 
    regsub -all {[0-9]} $element { & } element
    if {[string match all $tts(punctuations)]} {
        set p $tts(punct_pattern)
        while {[regexp $p $element match]} {
            regsub -all "\\$match" $element " $pronounce($match) " element
        }
    }
    return $element
}

# }}}
# {{{set punctuation pronunciation table 

set tts(inline_commands)  {\[[^]]*\]}
set pronounce(\!) exclamation 
set pronounce(\@) at
set pronounce(\#) pound
set pronounce(\$) dollar
set pronounce(\%) percent 
set pronounce(\^) caret 
set pronounce(\&) ampersand 
set pronounce(\*) star
set pronounce(\() "left paren"
set pronounce(\)) "right paren"
set pronounce(\-) dash
set pronounce(\_) underscore 
set pronounce(\+) plus
set pronounce(\=) equals
set pronounce(\[) "left bracket"
set pronounce(\]) "right bracket"
set pronounce(\{) "left brace"
set pronounce(\}) "right brace"
set pronounce(\\) backslash
set pronounce(\|) pipe
set pronounce(\,) comma
set pronounce(\.) period 
set pronounce(\;) "semi colon"
set pronounce(\:) colon
set pronounce(\') apostrophe 
set pronounce(\") quotes 
set pronounce(\/) slash
set pronounce(\?) "question mark"
set pronounce(\`) backquote 
set pronounce(\~) tilde 
set pronounce(<) "less than"
set pronounce(>) "greater than"
set tts(punct_pattern) [join [array names pronounce] "|\\"]

# }}}

# {{{ globals

#mbrola
set tts(pitch) 1.0
set tts(freq) 16384
set tts(volume) 2.0
set tts(speak) "/usr/lib/mbrola/tts-English/speakme"
#optional debuggin output
if {[info exists env(DTK_DEBUG)] } {
    set tts(debug) 1
} else {
    set tts(debug) 0
}

#flag to avoid multiple consecutive stops
set tts(not_stopped) 1
#split caps flag: 
set tts(split_caps) 1
# Capitalize flag
set tts(capitalize)  0
#allcaps beep flag
set tts(allcaps_beep)  0
set tts(talking?) 0
set tts(speech_rate) 425 
set tts(char_factor)  1.2
set tts(say_rate) [round \
                       [expr $tts(speech_rate) * $tts(char_factor)]]
set tts(q_head)  0
set tts(q_tail) 0
set tts(backup_head)  0
set tts(backup_tail) 0
set tts(punctuations) some
set queue(-1) ""
set backup(-1) ""
#play program for auditory icons
if {[info exists env(EMACSPEAK_PLAY_PROGRAM)] } {
    set tts(play)  $env(EMACSPEAK_PLAY_PROGRAM)
} else {
    set tts(play) "play"
}

# }}}
# {{{ Initialize and set state.

#working around tcl 7.5
set tts(input) file0
if {[string match [info tclversion] 7.5]
    || [string match 8.0 [info tclversion]] } {
    if {[info exists server_p]} {
        set tts(input) sock0
    } else {
        set tts(input) file0
    }
}
#do not die if you see a control-c
signal ignore {sigint}

#Start the main command loop:
commandloop

# }}}
# {{{ Emacs local variables  

### Local variables:
### major-mode: tcl-mode 
### voice-lock-mode: t
### folded-file: t
### End:

# }}}
