#!/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" # 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 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 "" regsub -all {\[\*\]} $text { } text synth " $prefix $text" service return "" } proc l {text} { global tts global langcode global langsynth set la $langcode(current) set prefix "" if {[regexp {[A-Z]} $text]} { # pitch instead of 80%, high which could be 75%. set prefix "$prefix " } set tts(not_stopped) 1 # TBD: say-as, format attribute: instead of characters/glyphs, define "word" synth "$prefix $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 "" 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 service {} { global tts set talking [speakingP] while {$talking == 1} { 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 "" 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] } 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 {eSpeak.} service #Start the main command loop: commandloop # }}} # {{{ Emacs local variables ### Local variables: ### major-mode: tcl-mode ### voice-lock-mode: t ### folded-file: t ### End: # }}}