Companion 8.2.0 introduced the ability to meticulously alter and control the unit's spoken (text) output using the vox pipeline. At the heart of the vox pipeline are speech filters: small LSL programs that receive text through linked messages and then pass them on the same way. This allows for multiple alterations to be applied to a message in succession, with minimal overhead.
How speech filters work
When a filter is initialized, it sends a REPORT_FILTER message to the operating system. This conveys its name and a randomly-generated UUID that the filter will use to identify messages it is meant to parse. It may also be sent a NEXT_FILTER message, which tells it what to do with its output; typically, this is the key of the next filter in the chain, or NULL_KEY if the text should be sent back to the OS for final output.
Typical filter message processing works as follows:
- The system sends the message 810 FILTERED to the first filter in the chain, with the UUID parameter equal to that filter's unique key.
- The filter processes the message, and produces another 810 FILTERED message:
- If NEXT_FILTER has been provided and specifies a non-null key for the successor filter, the filter should send 810 FILTERED to LINK_THIS with that key.
- If NEXT_FILTER was not provided, or if it specified NULL_KEY or no key, the filter should send 810 FILTERED to LINK_ROOT.
- Step 2 is repeated until the second branch is reached.
The complete set of linked messages involved in speech filter processing are in the 800-819 block, specifically:
|message number||message name||purpose|
|801||QUERY_FILTER||sent by operating system to search for available filters|
|802||REPORT_FILTER||sent by filters to the operating system to announce that a filter is available (in response to 801)|
|803||NEXT_FILTER||sent by operating system to tell a filter what to do with its output|
|804||FILTER_CONFIG||sent by operating system to tell a filter how to adjust its settings (or display help information)|
|810||FILTERED||passing of speech along vox chain; process text if key parameter matches filter (see below)|
|812||REPORT_FILTER_EX||sent by filters to the operating system to announce that a filter is available (more efficient than 802, but must be sent with 2048 TASK_START)|
Messages specific to presenting the menu and command-line interfaces for manipulating the filter chain have been omitted for clarity.
Creating a speech filter
Speech filters can come in two formats, either unbundled individually or packed together into one script. For the sake of convenience in reducing script count, we encourage developers to release speech filters with full permissions. When designing a speech filter, you should use the following name format for both script filenames and internal identification:
Where <name> is a one-word unique identifier (e.g. "serpentine" for a filter that imposes snake-like speech) and <type> is one of:
|s||semantic: filters that modify the words spoken, grammar used, and concepts conveyed (modifying the speaker's intent)|
|l||linguistic: filters that completely change the language used, e.g. replacing words with animal noises or translating into a new language|
|p||phonetic: filters that alter the simulated tongue and mouth, e.g. adding hisses or a lisp|
|t||typographic: filters that alter the final output format, e.g. unicode substitution or noise effects|
|m||multiple: several filters bundled together, to save on script count; individual filters within the package should still identify as one of the above types|
For example, by default the controller ships with two scripts, fl_polyglot and fm_standard (which contains fs_vocabulary, fl_barnyard, fl_aphasia, fp_serpentine, fp_lisp, ft_dropout, ft_microvox, and ft_glitch.)
Typographic filters cannot be used when an external output pipe is enabled, as it is assumed they make modifications incompatible with most devices, such as gags.
To identify the filters that are present, the controller will send the message 801 QUERY_FILTER. Filters should respond with 802 REPORT_FILTER or 2860 TASK_START | REPORT_FILTER_EX. The format of the 812 | 2048 message is:
key serpentine_filter_id = llGenerateKey(); // put this elsewhere and generate it on restart llMessageLinked(LINK_ROOT, 812 | 2048, "fp_serpentine", serpentine_filter_id);
The 812 | 2048 message is a new replacement for the traditional 802 message. It was introduced in 8.5 release candidate 1. For broader support you may wish to continue using 802 instead of 812 | 2048.
Previously, we recommended using llGenerateKey() to obtain a unique key for single-filter scripts. However, this will fail if the filter does not have full permissions. See external documentation for more details.
For complete working examples of filters, see fm_standard, available in full-permissions forms from the Companion 8 SDK.
Optimizing multi-filter scripts
As mentioned above, when the unit has a message to send, the script will receive an 810 FILTERED message. It should be passed on with another FILTERED message directed at user memory, i.e.
llMessageLinked(LINK_THIS, 810, message, next_key);
If next_key is NULL_KEY or empty, send it to system memory instead, and the operating system will finish processing the message:
llMessageLinked(LINK_ROOT, 810, message, "");
In order to keep the execution chain efficient, scripts are informed in advance of the next filter's unique identifier. This design allows passing messages from one filter to another to require only one linked message. In the case of multi-filter scripts, this can be expedited further by omitting message-passing entirely. An example of this can be seen in fm_standard.