2 * Copyright (c) 2012-2014, Intel Corporation
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
8 * * Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * * Neither the name of Intel Corporation nor the names of its contributors
14 * may be used to endorse or promote products derived from this software
15 * without specific prior written permission.
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 #include <sys/types.h>
34 #include <murphy/common/debug.h>
35 #include <murphy/common/log.h>
36 #include <murphy/common/mm.h>
37 #include <murphy/common/mainloop.h>
39 #include <espeak/speak_lib.h>
41 #include "srs/daemon/plugin.h"
42 #include "srs/daemon/voice.h"
43 #include "srs/daemon/pulse.h"
45 #include "espeak-voice.h"
47 #define PLUGIN_NAME "espeak-voice"
48 #define PLUGIN_DESCR "An espeak-based voice synthesizer plugin for SRS."
49 #define PLUGIN_AUTHORS "Krisztian Litkey <kli@iki.fi>"
50 #define PLUGIN_VERSION "0.0.1"
52 #define CONFIG_VOICEDIR "espeak.voicedir"
54 #define ESPEAK_CONTINUE 0
55 #define ESPEAK_ABORT 1
63 static void stream_event_cb(srs_pulse_t *p, srs_voice_event_t *event,
66 espeak_t *e = (espeak_t *)user_data;
70 e->voice.notify(event, e->voice.notify_data);
74 static int espeak_synth_cb(short *samples, int nsample, espeak_EVENT *events)
76 synth_data_t *data = events->user_data;
79 return ESPEAK_CONTINUE;
81 if (mrp_realloc(data->samples, 2 * (data->nsample + nsample)) == NULL)
84 memcpy(data->samples + 2 * data->nsample, samples, 2 * nsample);
85 data->nsample += nsample;
87 return ESPEAK_CONTINUE;
91 static int espeak_setrate(double drate)
93 int min, max, step, rate, orig;
95 if (0.0 < drate && drate <= 2.0) {
97 rate = espeakRATE_NORMAL;
98 else if (drate < 1.0) {
99 min = espeakRATE_MINIMUM;
100 max = espeakRATE_NORMAL;
101 step = (max - min) / 1.0;
102 rate = (int)(min + drate * step);
104 else { /*drate > 1.0*/
105 min = espeakRATE_NORMAL;
106 max = espeakRATE_MAXIMUM;
107 step = (max - min) / 1.0;
108 rate = (int)(min + (drate - 1.0) * step);
111 orig = espeak_GetParameter(espeakRATE, 1);
112 espeak_SetParameter(espeakRATE, rate, 0);
121 static int espeak_setpitch(double dpitch)
123 int min, max, step, pitch, orig;
125 if (0.0 < dpitch && dpitch <= 2.0) {
126 pitch = (int)(50 * dpitch);
127 orig = espeak_GetParameter(espeakPITCH, 1);
128 espeak_SetParameter(espeakPITCH, pitch, 0);
137 static uint32_t espeak_render(const char *msg, char **tags, int actor,
138 double rate, double pitch, int notify_events,
141 espeak_t *e = (espeak_t *)api_data;
142 int size, start, end, type, orate, opitch;
143 unsigned int flags, uid;
151 if (0 <= actor && actor <= e->nactor) {
152 if (espeak_SetVoiceByName(e->actors[actor].name) != EE_OK) {
153 mrp_log_error("espeak: failed to activate espeak voice #%d ('%s').",
154 actor, e->actors[actor].name);
155 return SRS_VOICE_INVALID;
159 mrp_log_error("espeak: invalid espeak voice #%d requested.", actor);
160 return SRS_VOICE_INVALID;
164 type = POS_CHARACTER;
167 flags = espeakCHARS_UTF8;
169 data = (synth_data_t) { NULL, 0 };
171 orate = espeak_setrate(rate);
172 opitch = espeak_setpitch(pitch);
174 r = espeak_Synth(msg, size, start, type, end, flags, &uid, &data);
176 espeak_setrate(orate);
177 espeak_setpitch(opitch);
179 if (r != EE_OK || data.samples == NULL) {
180 mrp_log_error("espeak: failed to synthesize message with espeak.");
181 return SRS_VOICE_INVALID;
184 id = srs_play_stream(e->srs->pulse, data.samples, e->config.rate, 1,
185 data.nsample, tags, notify_events,
188 if (id == SRS_VOICE_INVALID)
189 mrp_free(data.samples);
195 static void espeak_cancel(uint32_t id, void *api_data)
197 espeak_t *e = (espeak_t *)api_data;
199 srs_stop_stream(e->srs->pulse, id, FALSE, FALSE);
203 static int create_espeak(srs_plugin_t *plugin)
207 mrp_debug("creating espeak voice plugin");
209 e = mrp_allocz(sizeof(*e));
213 e->srs = plugin->srs;
215 plugin->plugin_data = e;
224 static int config_espeak(srs_plugin_t *plugin, srs_cfg_t *cfg)
226 espeak_t *e = (espeak_t *)plugin->plugin_data;
230 mrp_debug("configure espeak voice plugin");
232 e->config.voicedir = srs_get_string_config(cfg, CONFIG_VOICEDIR, NULL);
234 out = AUDIO_OUTPUT_SYNCHRONOUS;
235 path = e->config.voicedir;
238 rate = espeak_Initialize(out, blen, path, 0);
241 mrp_log_error("espeak: failed to initialize espeak.");
245 mrp_log_info("espeak: chose %d Hz for sample rate.", rate);
247 e->config.rate = rate;
249 espeak_SetSynthCallback(espeak_synth_cb);
250 /*espeak_SetParameter(espeakRATE, espeakRATE_NORMAL, 0);
251 espeak_SetParameter(espeakPITCH, 50, 0);*/
257 static inline const char *espeak_language(const char *languages)
261 * We don't handle correctly potential multiple languages. The
262 * documentation states that the 'languages' attribute of a
263 * queried voice has a "list of pairs of (byte) priority +
264 * (string) language[+dialect qualifier].
266 * I haven't seen such a voice in practice yet but provided they
267 * do exist we'd need to change our voice->actor mapping logic
268 * to accomodate for this and in such a case extract all the
269 * languages and separately map them to several actors.
271 return languages + 1; /* just strip priority for now */
275 static inline int espeak_gender(int gender)
278 case 1: return SRS_VOICE_GENDER_MALE;
279 case 2: return SRS_VOICE_GENDER_FEMALE;
280 default: return SRS_VOICE_GENDER_MALE;
285 static inline char *espeak_description(espeak_VOICE *v)
287 static char descr[256];
289 snprintf(descr, sizeof(descr), "espeak %s voice (%s).", v->languages,
290 v->identifier ? v->identifier : "-");
296 static int start_espeak(srs_plugin_t *plugin)
298 static srs_voice_api_t api = {
299 .render = espeak_render,
300 .cancel = espeak_cancel
303 espeak_t *e = (espeak_t *)plugin->plugin_data;
304 espeak_VOICE **voices, *v;
308 if (e->srs->pulse == NULL)
311 voices = (espeak_VOICE **)espeak_ListVoices(NULL);
313 if (voices == NULL) {
314 mrp_log_error("espeak: could not find any voices.");
318 for (nvoice = 0; voices[nvoice] != NULL; nvoice++)
321 if ((e->actors = mrp_allocz_array(typeof(*e->actors), nvoice)) == NULL)
324 mrp_log_info("espeak: found available voices:");
326 for (i = 0; i < nvoice; i++) {
329 mrp_log_info(" %s (%smale, age %d, languages: %s (id: %s))", v->name,
330 v->gender == 2 ? "fe" : "", v->age,
331 v->languages, v->identifier);
334 e->actors[i].name = mrp_strdup(v->name);
335 e->actors[i].lang = mrp_strdup(espeak_language(v->languages));
336 e->actors[i].dialect = NULL;
337 e->actors[i].gender = espeak_gender(v->gender);
338 e->actors[i].description = mrp_strdup(espeak_description(v));
340 if (e->actors[i].name == NULL || e->actors[i].lang == NULL)
346 if (srs_register_voice(e->self->srs, "espeak", &api, e,
347 e->actors, e->nactor,
348 &e->voice.notify, &e->voice.notify_data) == 0)
352 for (i = 0; i < e->nactor; i++) {
353 mrp_free(e->actors[i].name);
354 mrp_free(e->actors[i].lang);
355 mrp_free(e->actors[i].description);
362 static void stop_espeak(srs_plugin_t *plugin)
368 static void destroy_espeak(srs_plugin_t *plugin)
370 espeak_t *e = (espeak_t *)plugin->plugin_data;
373 srs_unregister_voice(e->self->srs, "espeak");
376 for (i = 0; i < e->nactor; i++) {
377 mrp_free(e->actors[i].name);
378 mrp_free(e->actors[i].lang);
379 mrp_free(e->actors[i].description);
386 SRS_DECLARE_PLUGIN(PLUGIN_NAME, PLUGIN_DESCR, PLUGIN_AUTHORS, PLUGIN_VERSION,
387 create_espeak, config_espeak, start_espeak, stop_espeak,