blob: 9967d027d31de465611bb96db30d39b40334c69a [file] [log] [blame] [edit]
/*
* module_utils.c - Module utilities
* Functions to help writing output modules for Speech Dispatcher
* Copyright (C) 2003,2006, 2007 Brailcom, o.p.s.
*
* This is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1, 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 Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* $Id: module_utils.c,v 1.55 2008-07-10 15:37:18 hanke Exp $
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <fdsetconv.h>
#include <wchar.h>
#include "module_utils.h"
#include "spd_module_main.h"
static char *module_audio_pars[10];
int log_level;
AudioID *module_audio_id;
SPDMsgSettings msg_settings;
SPDMsgSettings msg_settings_old;
int Debug;
FILE *CustomDebugFile;
configfile_t *configfile;
configoption_t *module_dc_options;
int module_num_dc_options;
const char *module_name;
int current_index_mark;
char *module_index_mark;
typedef struct {
gchar *pattern;
gchar *replace;
} MulticasesString;
void MSG(int level, const char *format, ...) {
if (level < 4 || Debug) {
va_list ap;
time_t t;
struct timeval tv;
char tstr[26];
t = time(NULL);
ctime_r(&t, tstr);
tstr[strlen(tstr)-1] = 0;
gettimeofday(&tv,NULL);
fprintf(stderr," %s [%d]",tstr, (int) tv.tv_usec);
fprintf(stderr, ": ");
va_start(ap, format);
vfprintf(stderr, format, ap);
va_end(ap);
fprintf(stderr, "\n");
fflush(stderr);
if ((Debug==2) || (Debug==3)) {
fprintf(CustomDebugFile," %s [%d]",tstr, (int) tv.tv_usec);
fprintf(CustomDebugFile, ": ");
va_start(ap, format);
vfprintf(CustomDebugFile, format, ap);
va_end(ap);
fprintf(CustomDebugFile, "\n");
fflush(CustomDebugFile);
}
}
}
#define SET_PARAM_NUM(name, cond) \
if(!strcmp(cur_item, #name)){ \
char *tptr; \
int number; \
number = strtol(cur_value, &tptr, 10); \
if(!(cond)){ return -1; } \
if (tptr == cur_value){ return -1; } \
msg_settings.name = number; \
}
#define SET_PARAM_STR(name) \
if(!strcmp(cur_item, #name)){ \
g_free(msg_settings.name); \
if(!strcmp(cur_value, "NULL")) msg_settings.name = NULL; \
else msg_settings.name = g_strdup(cur_value); \
}
#define SET_PARAM_STR_C(name, fconv) \
if(!strcmp(cur_item, #name)){ \
int ret = fconv(cur_value); \
if (ret != -1) msg_settings.name = ret; \
else return -1; \
}
int module_set(const char *cur_item, const char *cur_value)
{
SET_PARAM_NUM(rate,
((number >= -100) && (number <= 100)))
else
SET_PARAM_NUM(pitch,
((number >= -100)
&& (number <= 100)))
else
SET_PARAM_NUM(pitch_range,
((number >= -100)
&& (number <= 100)))
else
SET_PARAM_NUM(volume,
((number >= -100)
&& (number <= 100)))
else
SET_PARAM_STR_C(punctuation_mode,
str2EPunctMode)
else
SET_PARAM_STR_C(spelling_mode, str2ESpellMode)
else
SET_PARAM_STR_C(cap_let_recogn,
str2ECapLetRecogn)
else
if (!strcmp(cur_item, "voice")) {
int ret = str2EVoice(cur_value);
if (ret != -1)
msg_settings.voice_type = ret;
else
return -1;
} else if (!strcmp(cur_item, "synthesis_voice")) {
g_free(msg_settings.voice.name);
if (!strcmp(cur_value, "NULL")) {
if (msg_settings.voice.name) {
/* Force to set voice again. */
msg_settings.voice_type = -1;
}
msg_settings.voice.name = NULL;
} else
msg_settings.voice.name =
g_strdup(cur_value);
} else if (!strcmp(cur_item, "language")) {
g_free(msg_settings.voice.language);
if (!strcmp(cur_value, "NULL"))
msg_settings.voice.language = NULL;
else
msg_settings.voice.language =
g_strdup(cur_value);
} else
return -1; /* Unknown parameter */
return 0;
}
#define SET_AUDIO_STR(name,idx) \
if(!strcmp(cur_item, #name)){ \
g_free(module_audio_pars[idx]); \
if(!strcmp(cur_value, "NULL")) module_audio_pars[idx] = NULL; \
else module_audio_pars[idx] = g_strdup(cur_value); \
}
int module_audio_set(const char *cur_item, const char *cur_value) {
SET_AUDIO_STR(audio_output_method, 0)
else
SET_AUDIO_STR(audio_oss_device, 1)
else
SET_AUDIO_STR(audio_alsa_device, 2)
else
SET_AUDIO_STR(audio_nas_server, 3)
else
/* TODO: restore AudioPulseServer option
SET_AUDIO_STR(audio_pulse_server, 4)
else
*/
SET_AUDIO_STR(audio_pulse_device, 4)
else
SET_AUDIO_STR(audio_pulse_min_length, 5)
else
/* 6 reserved for speech-dispatcher module name */
return -1; /* Unknown parameter */
return 0;
}
#define SET_LOGLEVEL_NUM(name, cond) \
if(!strcmp(cur_item, #name)){ \
char *tptr; \
int number; \
number = strtol(cur_value, &tptr, 10); \
if(!(cond)){ return -1; } \
if (tptr == cur_value){ return -1; } \
log_level = number; \
spd_audio_set_loglevel(module_audio_id, number); \
}
int module_loglevel_set(const char *cur_item, const char *cur_value)
{
SET_LOGLEVEL_NUM(log_level, 1)
else
return -1; /* Unknown parameter */
return 0;
}
int module_debug(int enable, const char *filename)
{
if (enable) {
DBG("Additional logging into specific path %s requested",
filename);
FILE *new_CustomDebugFile = fopen(filename, "w+");
if (new_CustomDebugFile == NULL) {
DBG("ERROR: Can't open custom debug file for logging: %d (%s)", errno, strerror(errno));
return -1;
}
if (CustomDebugFile != NULL)
fclose(CustomDebugFile);
CustomDebugFile = new_CustomDebugFile;
if (Debug == 1)
Debug = 3;
else
Debug = 2;
DBG("Additional logging initialized");
} else {
if (Debug == 3)
Debug = 1;
else
Debug = 0;
if (CustomDebugFile != NULL)
fclose(CustomDebugFile);
CustomDebugFile = NULL;
DBG("Additional logging into specific path terminated");
}
return 0;
}
#undef SET_PARAM_NUM
#undef SET_PARAM_STR
int module_loop(void)
{
int ret = module_process(STDIN_FILENO, 1);
if (ret != 0)
DBG("Broken pipe, exiting...\n");
return ret;
}
int
module_get_message_part(const char *message, char *part, unsigned int *pos,
size_t maxlen, const char *dividers)
{
int i, n;
int num_dividers;
int len;
assert(part != NULL);
assert(message != NULL);
len = strlen(message);
if (message[*pos] == 0)
return -1;
if (dividers != NULL) {
num_dividers = strlen(dividers);
} else {
num_dividers = 0;
}
for (i = 0; i <= maxlen - 1; i++) {
unsigned char c = message[*pos];
// Stop at UTF-8 boundaries
if (((c & 0xe0) == 0xc0 && i >= maxlen - 1) // Will need one more byte, can't put it.
|| ((c & 0xf0) == 0xe0 && i >= maxlen - 2) // Will need two more bytes, can't put it.
|| ((c & 0xf8) == 0xf0 && i >= maxlen - 3) // Will need three more bytes, can't put it.
|| ((c & 0xfc) == 0xf8 && i >= maxlen - 4) // Will need four more bytes, can't put it.
|| ((c & 0xfe) == 0xfc && i >= maxlen - 5)) // Will need five more bytes, can't put it.
c = 0;
part[i] = c;
if (part[i] == 0) {
return i;
}
// DBG("pos: %d", *pos);
if ((len - 1 - i) > 2) {
if ((message[*pos + 1] == ' ')
|| (message[*pos + 1] == '\n')
|| (message[*pos + 1] == '\r')) {
for (n = 0; n <= num_dividers - 1; n++) {
if (part[i] == dividers[n]) {
part[i + 1] = 0;
(*pos)++;
return i + 1;
}
}
}
if ((message[*pos] == '\n')
&& (message[*pos + 1] == '\n')) {
part[i + 1] = 0;
(*pos)++;
return i + 1;
}
}
if ((len - 1 - i) > 4) {
if ( (message[*pos] == '\r')
&& (message[*pos + 1] == '\n')
&& (message[*pos + 2] == '\r')
&& (message[*pos + 3] == '\n')) {
part[i + 1] = 0;
(*pos)++;
return i + 1;
}
}
(*pos)++;
}
part[i] = 0;
return i;
}
void module_strip_punctuation_some(char *message, char *punct_chars)
{
int len, inc;
int lenp;
char *p = message;
int i;
assert(message != NULL);
mbstate_t state;
if (punct_chars == NULL)
return;
memset(&state, 0, sizeof(state));
len = strlen(message);
lenp = strlen(punct_chars);
for (i = 0; i < len; i += inc) {
wchar_t wc;
inc = mbrtowc(&wc, p, len - i, &state);
if (inc < 0) {
DBG("Oops, at %d invalid char -%c- (%d)?\n", i, *p, inc);
return;
}
if (memmem(punct_chars, lenp, p, inc)) {
DBG("Substitution %d: char -%.*s- (%d) for whitespace\n", i, inc, p, inc);
memset(p, ' ', inc);
}
p += inc;
}
}
char *module_strip_ssml(const char *message)
{
int len;
char *out;
int i, n;
int omit = 0;
assert(message != NULL);
len = strlen(message);
out = (char *)g_malloc(sizeof(char) * (len + 1));
for (i = 0, n = 0; i <= len; i++) {
if (message[i] == '<') {
omit = 1;
continue;
}
if (message[i] == '>') {
omit = 0;
continue;
}
if (!strncmp(&(message[i]), "&lt;", 4)) {
i += 3;
out[n++] = '<';
} else if (!strncmp(&(message[i]), "&gt;", 4)) {
i += 3;
out[n++] = '>';
} else if (!strncmp(&(message[i]), "&amp;", 5)) {
i += 4;
out[n++] = '&';
} else if (!strncmp(&(message[i]), "&quot;", 6)) {
i += 5;
out[n++] = '"';
} else if (!strncmp(&(message[i]), "&apos;", 6)) {
i += 5;
out[n++] = '\'';
} else if (!omit || i == len)
out[n++] = message[i];
}
DBG("In stripping ssml: |%s|", out);
return out;
}
void module_strip_punctuation_default(char *buf)
{
assert(buf != NULL);
module_strip_punctuation_some(buf, "~#$%^&*+=|<>[]_");
}
static gchar *module_multicases_string_replace(
gchar *text,
const MulticasesString *mcstr)
{
GRegex *regex;
GError *error = NULL;
gchar *result;
regex = g_regex_new(mcstr->pattern, G_REGEX_OPTIMIZE, 0, &error);
if (!regex) {
DBG("ERROR compiling regular expression: %s.", error->message);
g_error_free(error);
return text;
}
result = g_regex_replace(regex, text, -1, 0, mcstr->replace, G_REGEX_MATCH_DEFAULT, NULL);
g_error_free(error);
g_regex_unref(regex);
g_free(text);
return result;
}
char *module_multicases_string(char *message)
{
static MulticasesString mcstr[] = {
{"([a-z]+)([A-Z][a-z]+)", "\\1 \\2"},
{"([a-z]+)([A-Z]+)", "\\1 \\2"},
{"([A-Z]+)([A-Z][a-z]+)", "\\1 \\2"},
{"([A-Z])([A-Z][a-z]+)", "\\1 \\2"}
};
guint i;
assert(message != NULL);
for (i = 0; i < 4; i++) {
message = (char*)module_multicases_string_replace((gchar*)message, &mcstr[i]);
}
DBG("Multicases string '%s'\n", message);
return message;
}
size_t
module_parent_wfork(TModuleDoublePipe dpipe, const char *message,
SPDMessageType msgtype, const size_t maxlen,
const char *dividers, int *pause_requested)
{
unsigned int pos = 0;
char msg[16];
char *buf;
int bytes;
size_t read_bytes = 0;
DBG("Entering parent process, closing pipes");
buf = (char *)g_malloc((maxlen + 1) * sizeof(char));
module_parent_dp_init(dpipe);
pos = 0;
while (1) {
DBG(" Looping...\n");
if (*pause_requested) {
DBG("Pause requested in parent");
module_parent_dp_close(dpipe);
g_free(buf);
*pause_requested = 0;
return 0;
}
bytes =
module_get_message_part(message, buf, &pos, maxlen,
dividers);
DBG("Returned %d bytes from get_part\n", bytes);
if (bytes > 0) {
DBG("Sending buf to child:|%s| %d\n", buf, bytes);
module_parent_dp_write(dpipe, buf, bytes);
DBG("Waiting for response from child...\n");
while (1) {
read_bytes =
module_parent_dp_read(dpipe, msg, 8);
if (read_bytes == 0) {
DBG("parent: Read bytes 0, child stopped\n");
break;
}
if (msg[0] == 'C') {
DBG("Ok, received report to continue...\n");
break;
}
}
}
if ((bytes == -1) || (read_bytes == 0)) {
DBG("End of data in parent, closing pipes");
module_parent_dp_close(dpipe);
break;
}
}
g_free(buf);
return 0;
}
int module_parent_wait_continue(TModuleDoublePipe dpipe)
{
char msg[16];
int bytes;
DBG("parent: Waiting for response from child...\n");
while (1) {
bytes = module_parent_dp_read(dpipe, msg, 8);
if (bytes == 0) {
DBG("parent: Read bytes 0, child stopped\n");
return 1;
}
if (msg[0] == 'C') {
DBG("parent: Ok, received report to continue...\n");
return 0;
}
}
}
void module_parent_dp_init(TModuleDoublePipe dpipe)
{
close(dpipe.pc[0]);
close(dpipe.cp[1]);
}
void module_parent_dp_close(TModuleDoublePipe dpipe)
{
close(dpipe.pc[1]);
close(dpipe.cp[0]);
}
void module_child_dp_init(TModuleDoublePipe dpipe)
{
close(dpipe.pc[1]);
close(dpipe.cp[0]);
}
void module_child_dp_close(TModuleDoublePipe dpipe)
{
close(dpipe.pc[0]);
close(dpipe.cp[1]);
}
void
module_child_dp_write(TModuleDoublePipe dpipe, const char *msg, size_t bytes)
{
int ret;
assert(msg != NULL);
ret = write(dpipe.cp[1], msg, bytes);
assert(ret);
}
int
module_parent_dp_write(TModuleDoublePipe dpipe, const char *msg, size_t bytes)
{
ssize_t ret;
assert(msg != NULL);
DBG("going to write %lu bytes", (long unsigned)bytes);
ret = write(dpipe.pc[1], msg, bytes);
DBG("written %ld bytes", (long)ret);
return ret;
}
int module_child_dp_read(TModuleDoublePipe dpipe, char *msg, size_t maxlen)
{
int bytes;
while ((bytes = read(dpipe.pc[0], msg, maxlen)) < 0) {
if (errno != EINTR) {
FATAL("Unable to read data");
}
}
return bytes;
}
int module_parent_dp_read(TModuleDoublePipe dpipe, char *msg, size_t maxlen)
{
int bytes;
while ((bytes = read(dpipe.cp[0], msg, maxlen)) < 0) {
if (errno != EINTR) {
FATAL("Unable to read data");
}
}
return bytes;
}
void module_sigblockall(void)
{
int ret;
sigset_t all_signals;
DBG("Blocking all signals");
sigfillset(&all_signals);
ret = sigprocmask(SIG_BLOCK, &all_signals, NULL);
if (ret != 0)
DBG("Can't block signals, expect problems with terminating!\n");
}
void module_sigunblockusr(sigset_t * some_signals)
{
int ret;
DBG("UnBlocking user signal");
sigdelset(some_signals, SIGUSR1);
ret = sigprocmask(SIG_SETMASK, some_signals, NULL);
if (ret != 0)
DBG("Can't block signal set, expect problems with terminating!\n");
}
void module_sigblockusr(sigset_t * some_signals)
{
int ret;
DBG("Blocking user signal");
sigaddset(some_signals, SIGUSR1);
ret = sigprocmask(SIG_SETMASK, some_signals, NULL);
if (ret != 0)
DBG("Can't block signal set, expect problems when terminating!\n");
}
int module_terminate_thread(pthread_t thread)
{
int ret;
ret = pthread_cancel(thread);
if (ret != 0) {
DBG("Cancellation of speak thread failed");
return 1;
}
ret = pthread_join(thread, NULL);
if (ret != 0) {
DBG("join failed!\n");
return 1;
}
return 0;
}
char *module_recode_to_iso(const char *data, int bytes, const char *language,
const char *fallback)
{
char *recoded;
if (language == NULL)
recoded = g_strdup(data);
else if (!strcmp(language, "cs") || !strcmp(language, "cs-CZ"))
recoded =
(char *)g_convert_with_fallback(data, bytes, "ISO8859-2",
"UTF-8", fallback, NULL,
NULL, NULL);
else
recoded =
(char *)g_convert_with_fallback(data, bytes, "ISO8859-1",
"UTF-8", fallback, NULL,
NULL, NULL);
if (recoded == NULL)
DBG("festival: Conversion to ISO coding failed\n");
return recoded;
}
/* --- CONFIGURATION --- */
configoption_t *module_add_config_option(configoption_t * options,
int *num_options, const char *name, int type,
dotconf_callback_t callback,
info_t * info, unsigned long context)
{
configoption_t *opts;
int num_config_options = *num_options;
assert(name != NULL);
num_config_options++;
opts =
(configoption_t *) g_realloc(options,
(num_config_options +
1) * sizeof(configoption_t));
opts[num_config_options - 1].name = (char *)g_strdup(name);
opts[num_config_options - 1].type = type;
opts[num_config_options - 1].callback = callback;
opts[num_config_options - 1].info = info;
opts[num_config_options - 1].context = context;
*num_options = num_config_options;
return opts;
}
int module_audio_init(char **status_info)
{
char *error = 0, *first_error = 0;
gchar **outputs;
int i = 0;
DBG("Opening audio output system");
if (NULL == module_audio_pars[0]) {
*status_info =
g_strdup
("Sound output method specified in configuration not supported. "
"Please choose 'oss', 'alsa', 'nas', 'libao' or 'pulse'.");
return -1;
}
g_free(module_audio_pars[6]);
module_audio_pars[6] = strdup(module_name);
outputs = g_strsplit(module_audio_pars[0], ",", 0);
while (NULL != outputs[i]) {
if (!strcmp(outputs[i], "server")) {
if (!first_error)
first_error = g_strdup("server audio is not supported");
i++;
continue;
}
module_audio_id =
spd_audio_open(outputs[i], (void **)&module_audio_pars[1],
&error);
if (module_audio_id) {
DBG("Using %s audio output method", outputs[i]);
g_strfreev(outputs);
/* Volume is controlled by the synthesizer. Always play at normal on audio device. */
if (spd_audio_set_volume(module_audio_id, 85) < 0) {
DBG("Can't set volume. audio not initialized?");
}
*status_info =
g_strdup("audio initialized successfully.");
g_free(first_error);
return 0;
}
DBG("Can't use %s: %s", outputs[i], error);
if (!first_error)
first_error = error;
else
g_free(error);
i++;
}
*status_info =
g_strdup_printf("Opening sound device failed. Reason: %s. ", first_error);
g_free(first_error); /* g_malloc'ed, in spd_audio_open. */
g_strfreev(outputs);
return -1;
}
int module_tts_output(AudioTrack track, AudioFormat format)
{
if (spd_audio_play(module_audio_id, track, format) < 0) {
DBG("Can't play track for unknown reason.");
return -1;
}
return 0;
}
int module_marks_init(SPDMarks *marks)
{
marks->num = 0;
marks->allocated = 0;
marks->samples = NULL;
marks->names = NULL;
marks->stop = 0;
return 0;
}
int module_marks_add(SPDMarks *marks, unsigned sample, const char *name)
{
marks->num++;
if (marks->num >= marks->allocated) {
/* Amortized reallocation */
marks->allocated = marks->num * 2;
marks->samples = g_realloc(marks->samples, marks->allocated * sizeof(marks->samples[0]));
marks->names = g_realloc(marks->names, marks->allocated * sizeof(marks->names[0]));
}
marks->samples[marks->num - 1] = sample;
marks->names[marks->num - 1] = g_strdup(name);
return 0;
}
int module_marks_clear(SPDMarks *marks)
{
unsigned i;
for (i = 0; i < marks->num; i++)
g_free(marks->names[i]);
marks->num = 0;
marks->allocated = 0;
g_free(marks->samples);
marks->samples = NULL;
g_free(marks->names);
marks->names = NULL;
marks->stop = 0;
return 0;
}
/* Strip silence at head of audio track */
void module_strip_head_silence(AudioTrack * track)
{
assert(track->bits == 16);
unsigned i;
float silence_limit = 0.01;
while (track->num_samples >= track->num_channels) {
for (i = 0; i < track->num_channels; i++)
if (abs(track->samples[i])
>= silence_limit * (1L<<(track->bits-1)))
return;
track->samples += track->num_channels;
track->num_samples -= track->num_channels;
}
}
/* Strip silence at tail of audio track */
void module_strip_tail_silence(AudioTrack * track)
{
assert(track->bits == 16);
unsigned i;
float silence_limit = 0.01;
while (track->num_samples >= track->num_channels) {
for (i = 0; i < track->num_channels; i++)
if (abs(track->samples[track->num_samples - i - 1])
>= silence_limit * (1L<<(track->bits-1)))
return;
track->num_samples -= track->num_channels;
}
}
void module_strip_silence(AudioTrack * track)
{
module_strip_head_silence(track);
module_strip_tail_silence(track);
}
int module_tts_output_marks(AudioTrack track, AudioFormat format, SPDMarks *marks)
{
AudioTrack cur = track;
int current_sample = 0;
/* Loop control */
int start = 0;
int end = marks->num;
int delta = 0; /* loop below figures out the delta */
int i;
for (i = 1; i < marks->num; i++) {
if (marks->samples[i - 1] > marks->samples[i]) {
/* Decreasing mark order */
if (delta > 0) {
DBG("WARNING: Mixed samples order");
} else {
start = marks->num - 1;
end = -1;
delta = -1;
}
} else if (marks->samples[i - 1] < marks->samples[i]) {
/* Increasing mark order */
if (delta < 0) {
DBG("WARNING: Mixed samples order");
} else {
delta = 1;
}
}
}
if (delta == 0) {
/* All marks are at the same sample */
delta = 1;
}
/* Alternate speaking and reporting mark */
for (i = start; i != end; i += delta) {
unsigned end_sample = marks->samples[i];
cur.samples = &track.samples[current_sample];
cur.num_samples = end_sample - current_sample;
current_sample = end_sample;
if (cur.num_samples && module_tts_output(cur, format))
return -1;
if (marks->stop)
return 1;
module_report_index_mark(marks->names[i]);
}
/* Finish with remaining bits if any */
if (track.num_samples > current_sample) {
cur.samples = &track.samples[current_sample];
cur.num_samples = track.num_samples - current_sample;
if (module_tts_output(cur, format))
return -1;
}
return 0;
}
int module_marks_stop(SPDMarks *marks)
{
marks->stop = 1;
return 0;
}