Beautiful Code [329]
Implementing an event queue inside the speech server also gave the client application finer control over how text was split into chunks before synthesis. This turns out to be crucial for producing good intonation structure. The rules by which text should be split up into clauses varies depending on the nature of the text being spoken. As an example, newline characters in programming languages such as Python are statement delimiters and determine clause boundaries, but newlines do not constitute clause delimiters in English text.
As an example, a clause boundary is inserted after each line when speaking the following Python code:
i=1
j=2
See the section "Augmenting Emacs to create aural display lists," later in this chapter, for details on how Python code is distinguished and its semantics are transferred to the speech layer.
With the speech server now capable of smart text handling, the Emacspeak client could become more sophisticated with respect to its handling of text. The emacspeak-speak-line function turned into a library of speech-generation functions that implemented the following steps:
Parse text to split it into a sequence of clauses.
Preprocess text—e.g., handle repeated strings of punctuation marks.
Carry out a number of other functions that got added over time.
Queue each clause to the speech server, and issue the launch command.
From here on, the rest of Emacspeak was implemented using Emacspeak as the development environment. This has been significant in how the code base has evolved. New features are tested immediately because badly implemented features can render the entire system unusable. Lisp's incremental code development fits naturally with the former; to cover the latter, the Emacspeak code base has evolved to be "bushy"—i.e., most parts of the higher-level system are mutually independent and depend on a small core that is carefully maintained.
31.2.3. A Brief advice Tutorial
Lisp advice is key to the Emacspeak implementation, and this chapter would not be complete without a brief overview. The advice facility allows one to modify existing functions without changing the original implementation. What's more, once a function f has been modified by advice m, all calls to function f are affected by advice.
advice comes in three flavors:
before
The advice body is run before the original function is invoked.
after
The advice body is run after the original function has completed.
around
The advice body is run instead of the original function. The around advice can call the original function if desired.
All advice forms get access to the arguments of the adviced function; in addition, around and after get access to the return value computed by the original function. The Lisp implementation achieves this magic by:
Caching the original implementation of the function
Evaluating the advice form to generate a new function definition
Storing this definition as the adviced function
Thus, when the advice fragment shown in the earlier section "A Simple First-Cut Implementation" is evaluated, Emacs' original next-line function is replaced by a modified version that speaks the current line after the original next-line function has completed its work.
31.2.4. Generating Rich Auditory Output
At this point in its evolution, here is what the overall design looked like:
Emacs' interactive commands are speech-enabled or adviced to produce auditory output.
advice definitions are collected into modules, one each for every Emacs application being speech-enabled.
The advice forms forward text to core speech functions.
These functions extract the text to be spoken and forward it to the tts-speak function.
The tts-speak function produces auditory output by preprocessing its text argument and sending it to the speech