#include #include #include #include #include #include #include #include #include #include static int vox_stop(xas_vox *vox, ...) { if (vox->stdin >= 0) { (void)close(vox->stdin); vox->stdin = -1; } if (vox->stdout >= 0) { (void)close(vox->stdout); vox->stdout = -1; } if (vox->in) { (void)fclose(vox->in); vox->in = NULL; } if (vox->pid > 0) { int status; if (waitpid(vox->pid, &status, 0) < 0) { goto error_waitpid; } vox->pid = -1; } vox->state = XAS_VOX_IDLE; return 0; error_waitpid: return -1; } static char **args_concat(int argn1, char **args1, int argn2, char **args2, int *total) { char **ret; int count = argn1 + argn2, i, o; if ((ret = malloc((count + 1) * sizeof(char *))) == NULL) { goto error_malloc; } for (i=0, o=0; istate == XAS_VOX_ACTIVE) { (void)vox_stop(vox); } if (vox->tmpfh) { if (fflush(vox->tmpfh) < 0) { goto error_fflush; } } if (pipe(pipe_stdin) < 0) { goto error_pipe_stdin; } if (pipe(pipe_stdout) < 0) { goto error_pipe_stdout; } if ((pid = fork()) < 0) { goto error_fork; } if (pid == 0) { char *args[] = { (char *)vox->text2wave_path, "-F", sample_rate, "-", "-o", "-", NULL, NULL, NULL }; int argn = 6; int argc; char **argv; int fd; snprintf(sample_rate, sizeof(sample_rate)-1, "%zu", vox->format.sample_rate); if (vox->tmpfile[0] && vox->tmpfd > 0) { args[argn++] = "-eval"; args[argn++] = vox->tmpfile; } if ((argv = args_concat(argn, args, vox->argn, vox->args, &argc)) == NULL) { goto error_child; } if ((fd = open("/dev/null", O_WRONLY)) < 0) { goto error_child; } close(pipe_stdin[1]); close(pipe_stdout[0]); if (dup2(pipe_stdin[0], 0) < 0) { goto error_child; } if (dup2(pipe_stdout[1], 1) < 0) { goto error_child; } if (dup2(fd, 2) < 0) { goto error_child; } if (execv(vox->text2wave_path, argv) < 0) { goto error_child; } error_child: exit(EX_OSERR); } close(pipe_stdin[0]); close(pipe_stdout[1]); vox->pid = pid; vox->stdin = pipe_stdin[1]; vox->stdout = pipe_stdout[0]; vox->in = fdopen(pipe_stdin[1], "w"); vox->state = XAS_VOX_ACTIVE; return 0; error_fork: (void)close(pipe_stdout[1]); (void)close(pipe_stdout[0]); error_pipe_stdout: (void)close(pipe_stdin[1]); (void)close(pipe_stdin[0]); error_pipe_stdin: error_fflush: return -1; } static void vox_cleanup(xas_vox *vox) { (void)vox_stop(vox); } static ssize_t vox_fill(xas_vox *vox, int16_t *samples, size_t count) { ssize_t readlen, readcount; if (vox->state != XAS_VOX_ACTIVE) { return 0; } if ((readlen = read(vox->stdout, samples, count * vox->format.sample_size)) < 0) { goto error_read; } if (readlen == 0) { vox_stop(vox); } readcount = readlen / vox->format.sample_size; if (readcount > 0 && vox->gain != 1.0f) { xas_audio_apply_gain(vox->format, samples, vox->gain, 0, readcount); } return readcount; error_read: return -1; } static int tmpfile_open(xas_vox *vox) { if (vox->tmpfile[0] && vox->tmpfd > 0) { return 0; } memset(vox->tmpfile, '\0', sizeof(vox->tmpfile)); memcpy(vox->tmpfile, XAS_VOX_SETTINGS_TMP_PATH, strlen(XAS_VOX_SETTINGS_TMP_PATH)); if ((vox->tmpfd = mkstemp(vox->tmpfile)) < 0) { goto error_mkstemp; } if ((vox->tmpfh = fdopen(vox->tmpfd, "w")) == NULL) { goto error_fdopen; } return 0; error_fdopen: close(vox->tmpfd); unlink(vox->tmpfile); error_mkstemp: return -1; } static void tmpfile_close(xas_vox *vox) { fclose(vox->tmpfh); (void)close(vox->tmpfd); unlink(vox->tmpfile); memset(vox->tmpfile, '\0', sizeof(vox->tmpfile)); vox->tmpfd = -1; vox->tmpfh = NULL; } static int set_gain(xas_vox *vox, float gain) { xas_vox_set_gain(vox, gain); return 0; } static int vox_generate(xas_vox *vox, ...) { return xas_vox_generate(vox); } static xas_object_call_table call_table = { .start = (xas_object_start_callback)vox_generate, .stop = (xas_object_stop_callback)vox_stop, .set_gain = (xas_object_set_gain_callback)set_gain, .stream_new = (xas_object_stream_new_callback)xas_vox_stream_new, .destroy = (xas_object_destroy_callback)xas_vox_destroy }; xas_vox *xas_vox_new_args(xas_audio_format format, size_t buffer_size, const char *text2wave_path, int argn, char **args) { xas_vox *vox; if ((vox = malloc(sizeof(*vox))) == NULL) { goto error_malloc_vox; } vox->obj.table = &call_table; vox->text2wave_path = text2wave_path; vox->format.channels = XAS_AUDIO_MONO; vox->format.sample_size = format.sample_size; vox->format.sample_rate = format.sample_rate; vox->buffer_size = buffer_size; vox->gain = XAS_VOX_DEFAULT_GAIN; vox->state = XAS_VOX_IDLE; vox->pid = -1; vox->stdin = -1; vox->stdout = -1; vox->in = NULL; vox->argn = argn; vox->args = args; memset(vox->tmpfile, '\0', sizeof(vox->tmpfile)); vox->tmpfd = -1; vox->tmpfh = NULL; return vox; error_malloc_vox: return NULL; } xas_vox *xas_vox_new(xas_audio_format format, size_t buffer_size, const char *text2wave_path) { return xas_vox_new_args(format, buffer_size, text2wave_path, 0, NULL); } void xas_vox_destroy(xas_vox *vox) { if (vox->tmpfile[0] && vox->tmpfd > 0) { tmpfile_close(vox); } free(vox); } void xas_vox_set_gain(xas_vox *vox, float gain) { vox->gain = gain; } int xas_vox_set_voice(xas_vox *vox, const char *voice) { if (tmpfile_open(vox) < 0) { goto error_tmpfile_open; } if (fprintf(vox->tmpfh, "(%s)\n", voice) < 0) { goto error_fprintf; } return 0; error_fprintf: error_tmpfile_open: return -1; } static int set_parameter_va(xas_vox *vox, const char *name, const char *format, ...) { va_list args; if (tmpfile_open(vox) < 0) { goto error_tmpfile_open; } va_start(args, format); if (fprintf(vox->tmpfh, "(Parameter.set '%s ", name) < 0) { goto error_fprintf; } if (vfprintf(vox->tmpfh, format, args) < 0) { goto error_fprintf; } if (fprintf(vox->tmpfh, ")\n") < 0) { goto error_fprintf; } va_end(args); return 0; error_fprintf: va_end(args); error_tmpfile_open: return -1; } int xas_vox_set_parameter(xas_vox *vox, const char *name, const char *value) { return set_parameter_va(vox, name, "%s", value); } int xas_vox_set_parameter_str(xas_vox *vox, const char *name, const char *value) { return set_parameter_va(vox, name, "\"%s\"", value); } int xas_vox_set_parameter_float(xas_vox *vox, const char *name, float value) { return set_parameter_va(vox, name, "%f", value); } int xas_vox_set_parameter_int(xas_vox *vox, const char *name, int value) { return set_parameter_va(vox, name, "%d", value); } int xas_vox_active(xas_vox *vox) { return vox->state == XAS_VOX_ACTIVE; } int xas_vox_generate(xas_vox *vox) { xas_audio_stream *output; (void)fflush(vox->in); (void)fclose(vox->in); (void)close(vox->stdin); vox->stdin = -1; vox->in = NULL; if ((output = xas_riff_open_fd(vox->stdout)) == NULL) { goto error_riff_open_fd; } if (!xas_audio_format_eq(output->format, vox->format)) { errno = EINVAL; goto error_invalid_stream; } xas_audio_stream_destroy(output); return 0; error_invalid_stream: error_riff_open_fd: return -1; } int xas_vox_stop(xas_vox *vox) { return vox_stop(vox); } int xas_vox_vsayf(xas_vox *vox, const char *format, va_list args) { if (vox->state == XAS_VOX_IDLE && vox_start(vox) < 0) { goto error_vox_start; } return vfprintf(vox->in, format, args); error_vox_start: return -1; } int xas_vox_sayf(xas_vox *vox, const char *format, ...) { int ret; va_list args; va_start(args, format); ret = xas_vox_vsayf(vox, format, args); va_end(args); return ret; } int xas_vox_say(xas_vox *vox, const char *message) { return xas_vox_sayf(vox, "%s\n", message); } xas_audio_stream *xas_vox_stream_new(xas_vox *vox) { return xas_audio_stream_new_source((xas_audio_fill)vox_fill, (xas_audio_cleanup)vox_cleanup, vox->format, vox->buffer_size, vox); }