blob: 9ab8c94cd25e250aacf701fae7ada6968783b9d1 [file] [log] [blame]
/*
* generic.c - Speech Dispatcher generic output module
*
* Copyright (C) 2001, 2002, 2003, 2007 Brailcom, o.p.s.
*
* This 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.
*
* This software 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 this program. If not, see <https://www.gnu.org/licenses/>.
*
* $Id: generic.c,v 1.30 2008-07-30 09:15:51 hanke Exp $
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <glib.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
#include <locale.h>
#include <speechd_types.h>
#include "module_utils.h"
#define MODULE_NAME "generic"
#define MODULE_VERSION "0.2"
DECLARE_DEBUG()
/* Thread and process control */
static int generic_speaking = 0;
static pthread_t generic_speak_thread;
static pid_t generic_pid;
static sem_t *generic_semaphore;
static char *generic_message;
static SPDMessageType generic_message_type;
static int generic_position = 0;
static int generic_pause_requested = 0;
static char *execute_synth_str1;
static char *execute_synth_str2;
static gboolean initialized = FALSE;
/* Internal functions prototypes */
static void *get_ht_option(GHashTable * hash_table, const char *key);
static void *_generic_speak(void *);
static void _generic_child(TModuleDoublePipe dpipe, const size_t maxlen);
static void generic_child_close(TModuleDoublePipe dpipe);
void generic_set_rate(signed int rate);
void generic_set_pitch(signed int pitch);
void generic_set_pitch_range(signed int pitch_range);
void generic_set_voice(SPDVoiceType voice);
void generic_set_synthesis_voice(char *name);
void generic_set_language(char *language);
void generic_set_volume(signed int volume);
void generic_set_punct(SPDPunctuation punct);
/* Fill the module_info structure with pointers to this modules functions */
MOD_OPTION_1_STR(GenericExecuteSynth)
MOD_OPTION_1_STR(GenericCmdDependency)
MOD_OPTION_1_INT(GenericPortDependency)
MOD_OPTION_1_STR(GenericSoundIconFolder)
MOD_OPTION_1_INT(GenericMaxChunkLength)
MOD_OPTION_1_STR(GenericDelimiters)
MOD_OPTION_1_STR(GenericPunctNone)
MOD_OPTION_1_STR(GenericPunctSome)
MOD_OPTION_1_STR(GenericPunctMost)
MOD_OPTION_1_STR(GenericPunctAll)
MOD_OPTION_1_STR(GenericStripPunctChars)
MOD_OPTION_1_STR(GenericRecodeFallback)
MOD_OPTION_1_STR(GenericDefaultCharset)
MOD_OPTION_1_INT(GenericRateAdd)
MOD_OPTION_1_FLOAT(GenericRateMultiply)
MOD_OPTION_1_INT(GenericRateForceInteger)
MOD_OPTION_1_INT(GenericPitchAdd)
MOD_OPTION_1_FLOAT(GenericPitchMultiply)
MOD_OPTION_1_INT(GenericPitchForceInteger)
MOD_OPTION_1_INT(GenericPitchRangeAdd)
MOD_OPTION_1_FLOAT(GenericPitchRangeMultiply)
MOD_OPTION_1_INT(GenericPitchRangeForceInteger)
MOD_OPTION_1_INT(GenericVolumeAdd)
MOD_OPTION_1_FLOAT(GenericVolumeMultiply)
MOD_OPTION_1_INT(GenericVolumeForceInteger)
MOD_OPTION_3_HT(GenericLanguage, code, name, charset)
static char generic_msg_pitch_str[16];
static char generic_msg_pitch_range_str[16];
static char generic_msg_rate_str[16];
static char generic_msg_volume_str[16];
static char *generic_msg_voice_str = NULL;
static TGenericLanguage *generic_msg_language = NULL;
static char *generic_msg_punct_str;
/* Public functions */
int module_load(void)
{
INIT_SETTINGS_TABLES();
MOD_OPTION_1_STR_REG(GenericExecuteSynth, "");
MOD_OPTION_1_STR_REG(GenericCmdDependency, "");
MOD_OPTION_1_INT_REG(GenericPortDependency, 0);
MOD_OPTION_1_STR_REG(GenericSoundIconFolder, "/usr/share/sounds/sound-icons/");
REGISTER_DEBUG();
MOD_OPTION_1_INT_REG(GenericMaxChunkLength, 300);
MOD_OPTION_1_STR_REG(GenericDelimiters, ".");
MOD_OPTION_1_STR_REG(GenericStripPunctChars, "");
MOD_OPTION_1_STR_REG(GenericRecodeFallback, "?");
MOD_OPTION_1_STR_REG(GenericDefaultCharset, "iso-8859-1");
MOD_OPTION_1_INT_REG(GenericRateAdd, 0);
MOD_OPTION_1_FLOAT_REG(GenericRateMultiply, 1);
MOD_OPTION_1_INT_REG(GenericRateForceInteger, 0);
MOD_OPTION_1_INT_REG(GenericPitchAdd, 0);
MOD_OPTION_1_FLOAT_REG(GenericPitchMultiply, 1);
MOD_OPTION_1_INT_REG(GenericPitchForceInteger, 0);
MOD_OPTION_1_INT_REG(GenericPitchRangeAdd, 0);
MOD_OPTION_1_FLOAT_REG(GenericPitchRangeMultiply, 1);
MOD_OPTION_1_INT_REG(GenericPitchRangeForceInteger, 0);
MOD_OPTION_1_INT_REG(GenericVolumeAdd, 0);
MOD_OPTION_1_FLOAT_REG(GenericVolumeMultiply, 1);
MOD_OPTION_1_INT_REG(GenericVolumeForceInteger, 0);
MOD_OPTION_HT_REG(GenericLanguage);
MOD_OPTION_1_STR_REG(GenericPunctNone, "");
MOD_OPTION_1_STR_REG(GenericPunctSome, "");
MOD_OPTION_1_STR_REG(GenericPunctMost, "");
MOD_OPTION_1_STR_REG(GenericPunctAll, "");
module_register_available_voices();
module_register_settings_voices();
return 0;
}
int module_init(char **status_info)
{
int ret;
*status_info = NULL;
if (module_list_registered_voices() == NULL)
{
*status_info = g_strdup("The module does not have any voice configured, "
"please add them in the configuration file, "
"or install the required files");
return -1;
}
DBG("GenericMaxChunkLength = %d\n", GenericMaxChunkLength);
DBG("GenericDelimiters = %s\n", GenericDelimiters);
DBG("GenericExecuteSynth = %s\n", GenericExecuteSynth);
DBG("GenericCmdDependency = %s\n", GenericCmdDependency);
DBG("GenericPortDependency = %u\n", GenericPortDependency);
generic_msg_language =
(TGenericLanguage *) g_malloc(sizeof(TGenericLanguage));
generic_msg_language->code = g_strdup("en-US");
generic_msg_language->charset = g_strdup(GenericDefaultCharset);
generic_msg_language->name = g_strdup("english");
/* For mbtowc to work in locale charset */
setlocale(LC_CTYPE, "");
generic_message = NULL;
char name[64];
snprintf(name, sizeof(name), "/speechd-modules-generic-%d", getpid());
generic_semaphore = sem_open(name, O_CREAT | O_EXCL, S_IRUSR | S_IWUSR, 0);
sem_unlink(name);
DBG("Generic: creating new thread for generic_speak\n");
generic_speaking = 0;
ret = spd_pthread_create(&generic_speak_thread, NULL, _generic_speak, NULL);
if (ret != 0) {
DBG("Generic: thread failed\n");
*status_info = g_strdup("The module couldn't initialize threads "
"This can be either an internal problem or an "
"architecture problem. If you are sure your architecture "
"supports threads, please report a bug.");
return -1;
}
initialized = TRUE;
*status_info = g_strdup("Everything ok so far.");
return 0;
}
SPDVoice **module_list_voices(void)
{
return module_list_registered_voices();
}
int module_speak(gchar * data, size_t bytes, SPDMessageType msgtype)
{
char *tmp, *newtmp;
GError *gerror = NULL;
DBG("speak()\n");
if (generic_speaking) {
DBG("Speaking when requested to write");
return 0;
}
UPDATE_STRING_PARAMETER(voice.language, generic_set_language);
UPDATE_PARAMETER(voice_type, generic_set_voice);
UPDATE_STRING_PARAMETER(voice.name, generic_set_synthesis_voice);
UPDATE_PARAMETER(punctuation_mode, generic_set_punct);
UPDATE_PARAMETER(pitch, generic_set_pitch);
UPDATE_PARAMETER(pitch_range, generic_set_pitch_range);
UPDATE_PARAMETER(rate, generic_set_rate);
UPDATE_PARAMETER(volume, generic_set_volume);
DBG("Requested data (%d): |%s|\n", msgtype, data);
/* TODO: use a generic engine for SPELL, CHAR, KEY */
if (msgtype == SPD_MSGTYPE_TEXT)
{
tmp = module_strip_ssml(data);
bytes = strlen(tmp);
}
else
{
tmp = g_strndup(data, bytes);
}
module_strip_punctuation_some(tmp, GenericStripPunctChars);
/* Set the appropriate charset */
assert(generic_msg_language != NULL);
if (generic_msg_language->charset != NULL) {
if (strcasecmp(generic_msg_language->charset, "utf-8") != 0) {
DBG("Recoding from UTF-8 to %s...",
generic_msg_language->charset);
newtmp =
(char *)g_convert_with_fallback(tmp, bytes,
generic_msg_language->charset,
"UTF-8",
GenericRecodeFallback, NULL,
NULL, &gerror);
if (tmp != data)
g_free(tmp);
tmp = newtmp;
}
} else {
DBG("Warning: Preferred charset not specified, recoding to %s", GenericDefaultCharset);
newtmp =
(char *)g_convert_with_fallback(tmp, bytes, GenericDefaultCharset,
"UTF-8",
GenericRecodeFallback, NULL,
NULL, &gerror);
if (tmp != data)
g_free(tmp);
tmp = newtmp;
}
if (tmp == NULL) {
DBG("Warning: Conversion failed: %d: %s\n", gerror->code, gerror->message);
g_error_free(gerror);
return -1;
}
generic_message = tmp;
generic_message_type = msgtype;
DBG("Converted data to (%d): |%s|\n", msgtype, tmp);
/* Send semaphore signal to the speaking thread */
generic_speaking = 1;
sem_post(generic_semaphore);
DBG("Generic: leaving write() normally\n\r");
return 1;
}
int module_stop(void)
{
DBG("generic: stop()\n");
if (generic_speaking && generic_pid != 0) {
DBG("generic: stopping process group pid %d\n", generic_pid);
kill(-generic_pid, SIGKILL);
}
return 0;
}
size_t module_pause(void)
{
DBG("pause requested\n");
if (generic_speaking) {
DBG("Sending request to pause to child\n");
generic_pause_requested = 1;
DBG("paused at byte: %d", generic_position);
return 0;
} else {
return -1;
}
}
char *module_is_speaking(void)
{
return NULL;
}
int module_close(void)
{
DBG("generic: close()\n");
if (generic_speaking) {
module_stop();
}
if (!initialized)
return 0;
if (module_terminate_thread(generic_speak_thread) != 0)
return -1;
sem_close(generic_semaphore);
initialized = FALSE;
return 0;
}
/* Internal functions */
static void *get_ht_option(GHashTable * hash_table, const char *key)
{
void *option;
assert(key != NULL);
option = g_hash_table_lookup(hash_table, key);
if (option == NULL)
DBG("Requested option by key %s not found.\n", key);
return option;
}
/* Replace all occurances of 'token' in 'sting'
with 'data' */
char *string_replace(char *string, const char *token, const char *data)
{
char *p;
char *str1;
char *str2;
char *new;
char *mstring;
mstring = g_strdup(string);
while (1) {
/* Split the string in two parts, omit the token */
p = strstr(mstring, token);
if (p == NULL) {
return mstring;
}
*p = 0;
str1 = mstring;
str2 = p + (strlen(token));
/* Put it together, replacing token with data */
new = g_strdup_printf("%s%s%s", str1, data, str2);
g_free(mstring);
mstring = new;
}
}
void *_generic_speak(void *nothing)
{
TModuleDoublePipe module_pipe;
int ret;
int status;
spd_pthread_setname("_generic_speak");
DBG("generic: speaking thread starting.......\n");
/* Make interruptible */
set_speaking_thread_parameters();
while (1) {
sem_wait(generic_semaphore);
DBG("Semaphore on\n");
const char *play_command = NULL;
play_command = spd_audio_get_playcmd(module_audio_id);
if (play_command == NULL) {
DBG("This audio backend has no default play command; using \"play\"\n");
play_command = "play";
}
if (generic_message_type == SPD_MSGTYPE_SOUND_ICON) {
if (strchr(generic_message, '\\') ||
strchr(generic_message, '\'') ||
strchr(generic_message, '/')) {
DBG("Warning: bad icon name %s\n", generic_message);
}
char *cmd = g_strdup_printf("%s '%s/%s'", play_command, GenericSoundIconFolder, generic_message);
module_report_event_begin();
DBG("icon command = |%s|\n", cmd);
ret = system(cmd);
if (ret)
DBG("failed to run icon command: (error=%d) %s\n", errno, strerror(errno));
module_report_event_end();
free(cmd);
generic_speaking = 0;
continue;
}
ret = pipe(module_pipe.pc);
if (ret != 0) {
DBG("Can't create pipe pc\n");
generic_speaking = 0;
continue;
}
ret = pipe(module_pipe.cp);
if (ret != 0) {
DBG("Can't create pipe cp\n");
close(module_pipe.pc[0]);
close(module_pipe.pc[1]);
generic_speaking = 0;
continue;
}
module_report_event_begin();
/* Create a new process so that we could send it signals */
generic_pid = fork();
switch (generic_pid) {
case -1:
DBG("Can't say the message. fork() failed!\n");
close(module_pipe.pc[0]);
close(module_pipe.pc[1]);
close(module_pipe.cp[0]);
close(module_pipe.cp[1]);
generic_speaking = 0;
continue;
case 0:{
char *e_string;
char *p;
char *tmpdir, *homedir;
const char *helper;
helper = getenv("TMPDIR");
if (helper)
tmpdir = g_strdup(helper);
else
tmpdir = g_strdup("/tmp");
helper = g_get_home_dir();
if (helper)
homedir = g_strdup(helper);
else
homedir =
g_strdup("UNKNOWN_HOME_DIRECTORY");
/* Set this process as a process group leader (so that SIGKILL
is also delivered to the child processes created by system()) */
if (setpgid(0, 0) == -1)
DBG("Can't set myself as project group leader!");
e_string = g_strdup(GenericExecuteSynth);
e_string =
string_replace(e_string, "$PLAY_COMMAND",
play_command);
e_string =
string_replace(e_string, "$TMPDIR", tmpdir);
g_free(tmpdir);
e_string =
string_replace(e_string, "$HOMEDIR",
homedir);
g_free(homedir);
e_string =
string_replace(e_string, "$PITCH",
generic_msg_pitch_str);
e_string =
string_replace(e_string, "$PITCH_RANGE",
generic_msg_pitch_range_str);
e_string =
string_replace(e_string, "$RATE",
generic_msg_rate_str);
e_string =
string_replace(e_string, "$VOLUME",
generic_msg_volume_str);
e_string =
string_replace(e_string, "$LANGUAGE",
generic_msg_language->name);
e_string =
string_replace(e_string, "$PUNCT",
generic_msg_punct_str);
if (generic_msg_voice_str != NULL)
e_string =
string_replace(e_string, "$VOICE",
generic_msg_voice_str);
else {
char *default_voice = module_getdefaultvoice();
if (!default_voice)
default_voice = "no_voice";
e_string =
string_replace(e_string, "$VOICE",
default_voice);
}
/* Cut it into two strings */
p = strstr(e_string, "$DATA");
if (p == NULL)
exit(1);
*p = 0;
execute_synth_str1 = g_strdup(e_string);
execute_synth_str2 =
g_strdup(p + (strlen("$DATA")));
g_free(e_string);
/* execute_synth_str1 has to get here somehow */
DBG("Starting child...\n");
_generic_child(module_pipe,
GenericMaxChunkLength);
}
break;
default:
/* This is the parent. Send data to the child. */
generic_position =
module_parent_wfork(module_pipe, generic_message,
generic_message_type,
GenericMaxChunkLength,
GenericDelimiters,
&generic_pause_requested);
DBG("Waiting for child...");
ret = waitpid(generic_pid, &status, 0);
if (ret < 0) {
// Not supposed to happen
DBG("waitpid failed (ret=%d error=%d) %s", ret, errno, strerror(errno));
exit(EXIT_FAILURE);
}
generic_speaking = 0;
DBG("child terminated -: exit?:%d status:%d signal?:%d signal number:%d.\n", WIFEXITED(status), WEXITSTATUS(status), WIFSIGNALED(status), WTERMSIG(status));
if (WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL)
// That's a stop from us, report that we stopped
module_report_event_stop();
else if (!WIFEXITED(status) || WEXITSTATUS(status)) {
// That's not from us
DBG("We failed to speak, kill ourself to avoid no speech");
exit(EXIT_FAILURE);
} else
// terminated normally
module_report_event_end();
}
}
generic_speaking = 0;
DBG("generic: speaking thread ended.......\n");
pthread_exit(NULL);
}
void _generic_child(TModuleDoublePipe dpipe, const size_t maxlen)
{
char *text;
sigset_t some_signals;
int bytes;
char *command;
GString *message;
int i;
int ret;
sigfillset(&some_signals);
module_sigunblockusr(&some_signals);
module_child_dp_init(dpipe);
DBG("Entering child loop\n");
while (1) {
/* Read the waiting data */
text = g_malloc((maxlen + 1) * sizeof(char));
bytes = module_child_dp_read(dpipe, text, maxlen);
DBG("read %d bytes in child", bytes);
if (bytes == 0) {
g_free(text);
generic_child_close(dpipe);
}
text[bytes] = 0;
DBG("text read is: |%s|\n", text);
/* Escape any quotes */
message = g_string_new("");
for (i = 0; i <= bytes - 1; i++) {
if (text[i] == '\'')
message = g_string_append(message, "'\\''");
else {
g_string_append_printf(message, "%c", text[i]);
}
}
DBG("child: escaped text is |%s|", message->str);
if (strlen(message->str) != 0) {
// We want to catch failure of any part of the pipeline
command = g_strdup_printf("set -o pipefail ; %s%s%s",
execute_synth_str1, message->str, execute_synth_str2);
DBG("child: synth command = |%s|", command);
DBG("Speaking in child...");
module_sigblockusr(&some_signals);
{
pid_t pid = fork();
if (pid == -1) {
DBG("Could not fork\n");
exit(EXIT_FAILURE);
} else if (pid == 0) {
// child, execute command
ret = execl("/bin/sh", "sh", "-c", command, (char *) NULL);
// catch missing sh
DBG("Missing /bin/sh? (ret=%d error=%d) %s", ret, errno, strerror(errno));
exit(EXIT_FAILURE);
} else {
int status;
// parent, wait for child
ret = waitpid(pid, &status, 0);
if (ret < 0) {
// Not supposed to happen
DBG("waitpid failed (ret=%d error=%d) %s", ret, errno, strerror(errno));
exit(EXIT_FAILURE);
}
DBG("subchild terminated -: exit?:%d status:%d signal?:%d signal number:%d.\n", WIFEXITED(status), WEXITSTATUS(status), WIFSIGNALED(status), WTERMSIG(status));
if (!WIFEXITED(status) || WEXITSTATUS(status))
{
DBG("We failed to speak, kill ourself to avoid no speech");
exit(EXIT_FAILURE);
}
}
}
g_free(command);
}
module_sigunblockusr(&some_signals);
g_free(text);
g_string_free(message, 1);
DBG("child->parent: ok, send more data");
module_child_dp_write(dpipe, "C", 1);
}
}
static void generic_child_close(TModuleDoublePipe dpipe)
{
DBG("child: Pipe closed, exiting, closing pipes..\n");
module_child_dp_close(dpipe);
DBG("Child ended...\n");
exit(0);
}
void generic_set_pitch(int pitch)
{
float hpitch;
hpitch = ((float)pitch) * GenericPitchMultiply + GenericPitchAdd;
if (!GenericPitchForceInteger) {
snprintf(generic_msg_pitch_str, 15, "%.2f", hpitch);
} else {
snprintf(generic_msg_pitch_str, 15, "%d", (int)hpitch);
}
}
void generic_set_pitch_range(int pitch_range)
{
float hpitch_range;
hpitch_range =
((float)pitch_range) * GenericPitchRangeMultiply +
GenericPitchRangeAdd;
if (!GenericPitchRangeForceInteger) {
snprintf(generic_msg_pitch_range_str, 15, "%.2f", hpitch_range);
} else {
snprintf(generic_msg_pitch_range_str, 15, "%d",
(int)hpitch_range);
}
}
void generic_set_rate(int rate)
{
float hrate;
hrate = ((float)rate) * GenericRateMultiply + GenericRateAdd;
if (!GenericRateForceInteger) {
snprintf(generic_msg_rate_str, 15, "%.2f", hrate);
} else {
snprintf(generic_msg_rate_str, 15, "%d", (int)hrate);
}
}
void generic_set_volume(int volume)
{
float hvolume;
DBG("Volume: %d", volume);
hvolume = ((float)volume) * GenericVolumeMultiply + GenericVolumeAdd;
DBG("HVolume: %f", hvolume);
if (!GenericVolumeForceInteger) {
snprintf(generic_msg_volume_str, 15, "%.2f", hvolume);
} else {
snprintf(generic_msg_volume_str, 15, "%d", (int)hvolume);
}
}
void generic_set_language(char *lang)
{
DBG("Setting language %s", lang);
char *dash = strchr(lang, '-');
generic_msg_language =
(TGenericLanguage *) get_ht_option(GenericLanguage, lang);
if (generic_msg_language == NULL && dash) {
char *lang_only = g_strdup(lang);
lang_only[dash-lang] = 0;
generic_msg_language =
(TGenericLanguage *) get_ht_option(GenericLanguage, lang_only);
g_free(lang_only);
}
if (generic_msg_language == NULL) {
DBG("Language %s not found in the configuration file, using defaults.", lang);
generic_msg_language =
(TGenericLanguage *) g_malloc(sizeof(TGenericLanguage));
generic_msg_language->code = g_strdup(lang);
generic_msg_language->charset = NULL;
generic_msg_language->name = g_strdup(lang);
}
else if (generic_msg_language->name == NULL) {
DBG("Language name for %s not found in the configuration file.",
lang);
generic_msg_language =
(TGenericLanguage *) g_malloc(sizeof(TGenericLanguage));
generic_msg_language->code = g_strdup("en-US");
generic_msg_language->charset = g_strdup(GenericDefaultCharset);
generic_msg_language->name = g_strdup("english");
}
generic_set_voice(msg_settings.voice_type);
}
void generic_set_voice(SPDVoiceType voice)
{
DBG("Setting voice type %d", voice);
assert(generic_msg_language);
generic_msg_voice_str =
module_getvoice(generic_msg_language->code, voice);
if (generic_msg_voice_str == NULL) {
DBG("Invalid voice type specified or no voice available!");
}
}
void generic_set_synthesis_voice(char *name)
{
DBG("Setting voice name %s (%s)", name, msg_settings.voice.name);
assert(msg_settings.voice.name);
if (module_existsvoice(msg_settings.voice.name))
generic_msg_voice_str = msg_settings.voice.name;
}
void generic_set_punct(SPDPunctuation punct)
{
if (punct == SPD_PUNCT_NONE) {
generic_msg_punct_str = g_strdup((char *)GenericPunctNone);
return;
} else if (punct == SPD_PUNCT_SOME) {
generic_msg_punct_str = g_strdup((char *)GenericPunctSome);
return;
} else if (punct == SPD_PUNCT_MOST) {
if (GenericPunctMost[0] == '\0' && GenericPunctSome[0] != '\0')
/* Compatibility with old configuration files */
generic_msg_punct_str = g_strdup((char *)GenericPunctSome);
else
generic_msg_punct_str = g_strdup((char *)GenericPunctMost);
return;
} else if (punct == SPD_PUNCT_ALL) {
generic_msg_punct_str = g_strdup((char *)GenericPunctAll);
return;
} else {
DBG("ERROR: Unknown punctuation setting, ignored");
}
}