| /* |
| * 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]), "<", 4)) { |
| i += 3; |
| out[n++] = '<'; |
| } else if (!strncmp(&(message[i]), ">", 4)) { |
| i += 3; |
| out[n++] = '>'; |
| } else if (!strncmp(&(message[i]), "&", 5)) { |
| i += 4; |
| out[n++] = '&'; |
| } else if (!strncmp(&(message[i]), """, 6)) { |
| i += 5; |
| out[n++] = '"'; |
| } else if (!strncmp(&(message[i]), "'", 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; |
| } |