Beautiful Code [328]
proc tts_say {text} {puts -nonewline $tts(write) "$text"}
The speech server for the software DECTalk implemented an equivalent, simplified tts_say version that looks like:
proc say {text} {_say "$text"}
where _say calls the underlying C implementation provided by the DECTalk software.
The net result of this design was to create separate speech servers for each available engine, where each speech server was a simple script that invoked TCL's default readeval-print loop after loading in the relevant definitions. The client/server API therefore came down to the client (Emacspeak) launching the appropriate speech server, caching this connection, and invoking server commands by issuing appropriate procedure calls over this connection.
Notice that so far I have said nothing explicit about how this client/server connection was opened; this late-binding proved beneficial later when it came to making Emacspeak network-aware. Thus, the initial implementation worked by the Emacspeak client communicating to the speech server using stdio. Later, making this client/server communication go over the network required the addition of a few lines of code that opened a server socket and connected stdin/stdout to the resulting connection.
Thus, designing a clean client/server abstraction, and relying on the power of Unix I/O, has made it trivial to later run Emacspeak on a remote machine and have it connect back to a speech server running on a local client. This enables me to run Emacspeak inside screen on my work machine, and access this running session from anywhere in the world. Upon connecting, I have the remote Emacspeak session connect to a speech server on my laptop, the audio equivalent of setting up X to use a remote display.
Emacspeak: The Complete Audio Desktop > Speech-Enabling Emacs
31.2. Speech-Enabling Emacs
The simplicity of the speech server abstraction described above meant that version 0 of the speech server was running within an hour after I started implementing the system. This meant that I could then move on to the more interesting part of the project: producing good quality spoken output. Version 0 of the speech server was by no means perfect; it was improved as I built the Emacspeak speech client.
31.2.1. A Simple First-Cut Implementation
A friend of mine had pointed me at the marvels of Emacs Lisp advice a few weeks earlier. Som when I sat down to speech-enable Emacs, advice was the natural choice. The first task was to have Emacs automatically speak the line under the cursor whenever the user pressed the up/down arrow keys.
In Emacs, all user actions invoke appropriate Emacs Lisp functions. In standard editing modes, pressing the down arrow invokes function next-line, while pressing the up arrow invokes previous-line. To speech-enable these commands, version 0 of Emacspeak implemented the following rather simple advice fragment:
(defadvice next-line (after emacspeak)
"Speak line after moving."
(when (interactive-p) (emacspeak-speak-line)))
The emacspeak-speak-line function implemented the necessary logic to grab the text of the line under the cursor and send it to the speech server. With the previous definition in place, Emacspeak 0.0 was up and running; it provided the scaffolding for building the actual system.
31.2.2. Iterating on the First-Cut Implementation
The next iteration returned to the speech server to enhance it with a well-defined eventing loop. Rather than simply executing each speech command as it was received, the speech server queued client requests and provided a launch command that caused the server to execute queued requests.
The server used the select system call to check for newly arrived commands after sending each clause to the speech engine. This enabled immediate silencing of speech; with the somewhat naïve implementation described in version 0 of the speech server, the command to stop speech would not take immediate effect since the speech server would first process previously