#!/usr/bin/tclsh
# Keywords: Emacspeak, eSpeak , TCL
# {{{ LCD Entry: 

# LCD Archive Entry:
# emacspeak| T. V. Raman |raman@cs.cornell.edu
# A speech interface to Emacs |
# $Date: 2006-08-11 21:11:17 +0200 (ven, 11 ao 2006) $ |
#  $Revision: 4047 $ | 
# Location undetermined
#

# }}}
# {{{ Copyright:  
#Copyright (C) 1995 -- 2001, T. V. Raman 
#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.

# }}}
# {{{source common code 

package require Tclx
set wd [file dirname $argv0]
source $wd/tts-lib.tcl

# }}}

# {{{ procedures  

# Language switching
#
# langsynth: available languages of the voice synthesis
# This variable is set by atcleci
# For example: langsynth(0)=3
# 3 is the atcleci code for the finnish language 

# langsynth(current): current synthesis language, 
# Gives the code of the current synth language.
# This variable is set by the application
# For example: langsynth(current)=3
# means finnish is the current language

# langsynth(top): max available index.
# For example, if there are three available languages: 
# langsynth(top)=2

# langlabel: what will be announced
# e.g. langlabel(0)="finnish"
# This variable is set by tclespeak

# langcode: language identifier
# e.g. langcode(0)="fi"
# This variable is set by tclespeak

# langalias converts a code language ("en", "en_GB",...) to its index in the langsynth array.
# e.g. langalias(fi)=3 could mean "fi_FI" will be used if "fi" is required. 

set langsynth(0) 0
set langsynth(current) 0
set langsynth(top) 0
set langlabel(0) "english"
set langcode(0) "en-uk"
set langcode(current) "en-uk"
set mswindows [expr { $tcl_platform(platform) == "windows" } ]

# select the next synth language
proc set_next_lang {say_it} {
    global langsynth
    global langalias
    global langlabel
    global langcode

    set langsynthkey 0
    set index 0
    while { $index <= $langsynth(top) } {
	if { $langsynth($index) == $langsynth(current) } {
	    set langsynthkey $index
	    break
	}
	incr index
    }

    if { $langsynthkey >= $langsynth(top) } {
	set langsynthkey 0
    } else {
	incr langsynthkey
    }

    set langsynth(current) $langsynth($langsynthkey)
    set langcode(current) $langcode($langsynthkey)

    setLanguage $langsynth(current)
    if { [info exists say_it]} {
	tts_say "$langlabel($langsynthkey) "
    }
}

# select the previous synth language
proc set_previous_lang {say_it} {
    global langsynth
    global langalias
    global langlabel
    global langcode

    set langsynthkey 0
    set index 0
    while { $index <= $langsynth(top) } {
	if { $langsynth($index) == $langsynth(current) } {
	    set langsynthkey $index
	    break
	}
	incr index
    }

    if { $langsynthkey <= 0 } {
	set langsynthkey $langsynth(top)
    } else {
	incr langsynthkey -1
    }

    set langsynth(current) $langsynth($langsynthkey)
    set langcode(current) $langcode($langsynthkey)
    setLanguage $langsynth(current)
    if { [info exists say_it]} {
	tts_say "$langlabel($langsynthkey) "
    }
}

# select a new synth language
# set_lang "en"
proc set_lang {{name "en"} {say_it "nil"}} {
    global langsynth
    global langalias
    global langcode
global langlabel 
     if { ![info exists langalias($name)]} {
	return
     }

     if { $langalias($name) == $langsynth(current) } {
	return
     }
    
    set langsynth(current) $langalias($name)
    set langcode(current) $langcode($langsynthkey)
    setLanguage $langsynth(current)

    set langsynthkey 0
    set index 0
    while { $index <= $langsynth(top) } {
	if { $langsynth($index) == $langsynth(current) } {
	    set langsynthkey $index
	    break
	}
	incr index
    }

    if { $say_it == "t"} {
	tts_say "$langlabel($langsynthkey) "
    }
}

# set_preferred_lang "en" "en_GB"
proc set_preferred_lang {alias lang} {
    global langsynth
    global langalias

    if { ![info exists langalias($lang)]} {
	return
    }
    set langalias($alias) $langalias($lang)
}

#debug
proc list_lang {} {
    global langsynth
    echo [ array get langsynth ]
}

proc list_langalias {} {
    global langalias
    echo [ array get langalias ]
}


proc version {} {

    SHOW "proc version"

    q " eSpeak [ttsVersion]"
    d
}

proc tts_set_punctuations {mode} {
    global tts

    set tts(punctuations) $mode
    punct $mode
    service
    return ""
}

proc tts_set_speech_rate {rate} {
    global tts

    set factor $tts(char_factor) 
    set tts(speech_rate) $rate
    setRate 0 $rate
    service
    return ""
}

proc tts_set_character_scale {factor} {
    global tts

    set tts(say_rate) [round \
                           [expr $tts(speech_rate) * $factor ]]
    set tts(char_factor) $factor
    service
    return ""
}

proc tts_say {text} {
    global    tts
    global langcode
    global langsynth

    service 
    set la $langcode(current)

    set prefix "<voice xml:lang=\"$la\" gender=\"male\" variant=\"1\">"
    regsub -all {\[\*\]} $text { } text 
    synth " $prefix $text"
    service
    return ""
}

proc l {text} {
    global tts
    global langcode
    global langsynth

    set la $langcode(current)
    set prefix "<voice xml:lang=\"$la\" gender=\"male\" variant=\"1\">"
    if {[regexp  {[A-Z]} $text]} {
	# pitch instead of 80%, high which could be 75%.
        set prefix "$prefix <prosody pitch=\"high\">"
    }
    set tts(not_stopped) 1
    # TBD: say-as, format attribute: instead of characters/glyphs, define "word"
    synth "$prefix <say-as interpret-as=\"characters\" format=\"characters\">$text"
    service
    return ""
}

proc d {} {
    service
    speech_task
}

proc tts_resume  {} {
    resume
    return ""
}
proc tts_pause {} {
    pause
    return ""
}

proc s {} {
    global tts


    if {$tts(not_stopped) == 1} {


        set tts(not_stopped) 0
        stop
        queue_clear
    } else {
        puts stderr StopNoOp
    }
}



proc t  {{pitch 440} {duration 50}} {
    global tts queue
    if {$tts(beep)} {
        b $pitch $duration
        return ""
    }
    service
}

proc sh  {{duration 50}} {
    global tts queue 


    set silence "<break time=\"$duration ms\"/>"
    set queue($tts(q_tail)) [list t $silence]
    incr tts(q_tail)
    service
    return ""
}

# Caps: this driver currently offers either
# - announcing each capitals (tts_split_caps)
# - or highering pitch (tts_capitalize)
# - or beeping (tts_allcaps_beep)
#
proc tts_split_caps {flag} {
    global tts 

    set tts(split_caps) $flag
    if { $flag == 1 } {
	set tts(allcaps_beep) 0
	set tts(capitalize) 0
	caps "spelling"
    } else {
	if { ( $tts(capitalize) == 0 ) && ( $tts(allcaps_beep) == 0 ) } {
	    caps "none"
	}
    }
    service
    return ""
}

proc tts_capitalize {flag} {
    global tts 

    set tts(capitalize) $flag

    if { $flag == 1 } {
	set tts(split_caps) 0
	set tts(allcaps_beep) 0
	caps "pitch"
    } else {
	if { ( $tts(split_caps) == 0 ) && ( $tts(allcaps_beep) == 0 ) } {
	    caps "none"
	}
    }

    service
    return ""
}

proc tts_allcaps_beep {flag} {
    global tts 

    set tts(allcaps_beep) $flag

    if { $flag == 1 } {
	set tts(split_caps) 0
	set tts(capitalize) 0
	caps "tone"
    } else {
	if { ( $tts(split_caps) == 0 ) && ( $tts(capitalize) == 0 ) } {
	    caps "none"
	}
    }
    service
    return ""
}

proc tts_reset {} {
    global tts
    #synth  -reset

    queue_clear
    synth "Resetting engine to factory defaults."
}

proc r {rate} {
    global queue  tts

    set queue($tts(q_tail)) [list r  $rate]
    incr tts(q_tail)
    return ""
}

proc useStereoOutput {} {
    global tts


    setOutput buffer
}

# }}}
# {{{ speech task 

proc trackIndex {index} {
    global tts

    set tts(last_index) $index
}

proc stdin_readable_handler {} {
  global stdin_is_readable
  global timer_or_stdin_breaks
  set stdin_is_readable 1
  set timer_or_stdin_breaks 1
}
if $mswindows { fileevent stdin readable stdin_readable_handler }

proc timer_handler {} {
  global timer_ticks
  global timer_or_stdin_breaks
  after 200 timer_handler
  set timer_ticks 1
  incr timer_or_stdin_breaks
}
if $mswindows { after 0 timer_handler }

proc service {} {
    global tts
    global stdin_is_readable # used only by mswindows

    set talking [speakingP]
    set stdin_is_readable 0
    while {$talking == 1} {
        if $::mswindows {
            # need to workaround lack of "select stdin" on windows
            # set status to 1 if input is available at stdin
            # if input is not available, timer will cause an exit from vwait
            set status -1
            vwait timer_or_stdin_breaks
            if { $stdin_is_readable > 0 } {
                set status 1
            }
        } else {
            set status   [lsearch [select [list  stdin]  {} {} 0.02] stdin]
        }

        if { $status >= 0} {
            set tts(talking?) 0
            set talking 0
            break
        } else {
            set talking [speakingP]
        }
    }
    return $talking
}

proc speech_task {} {
    global queue tts
    global langcode
    global langsynth

    set tts(talking?) 1
    set tts(not_stopped) 1
    set length [queue_length]
    set la $langcode(current)

    #set prefix "<voice xml:lang=\"$la\" gender=\"male\"
    #variant=\"1\">"
    set prefix ""
    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]]
                synth " $prefix $text"
                set retval [service]
            }
            c {
                set text  [lindex $event 1]
                synth "$text"
                set retval ""
            }
            a {

                set sound [lindex $event 1]
                exec $tts(play) $sound >/dev/null   &
            }
            b {

                if {$tts(beep)} {
                    lvarpop event 
                    eval beep $event
                }
            }
            r {

# The first argument to setRate is ignored.
                setRate 0 [lindex  $event 1]
            }
        }
        if {$tts(talking?) == 0} {break;} 
    }


    set tts(talking?) 0
    service
    return ""
}

# }}}
# {{{clean 

#preprocess element before sending it out:
proc clean {element} {
    global queue tts 


# The text conversion is expected to be done by eSpeak.
# For example, the * symbol will be said according to the selected language.
#
# If relying on eSpeak is too optimitisc for text conversion, you may 
# perhaps propose to the eSpeak author a new feature.
#
    return $element
}

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

#do not die if you see a control-c
signal ignore {sigint}

# Set input encoding to utf-8
fconfigure stdin -encoding utf-8

#initialize eSpeak
tts_initialize
set tts(speech_rate)  225
beep_initialize
set tts(input) stdin
if {[info exists server_p]} {
    set tts(input) sock0
}
set servers [file dirname $argv0]
set tclTTS $servers/linux-espeak
load $tclTTS/tclespeak.so
if {[file exists /proc/asound]} {
    set tts(play) /usr/bin/aplay
}
synth {<voice xml:lang="en" variant="1">eSpeak.}

service

#Start the main command loop:

if $mswindows {
  # there is a bug on windows, interactive -on does not work
  # https://sourceforge.net/p/tclx/bugs/81/
  commandloop -interactive off
} else {
  commandloop
}

# }}}
# {{{ Emacs local variables  

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

# }}}
