Also introduce the piper TTS library.
From: Shaun Ren sren@codeweavers.com
Also introduce a unixlib stub. --- dlls/msttsengine/Makefile.in | 4 ++- dlls/msttsengine/main.c | 6 ++++ dlls/msttsengine/tts.c | 19 ++++++++++ dlls/msttsengine/ttseng_private.h | 10 ++++++ dlls/msttsengine/unixlib.c | 59 +++++++++++++++++++++++++++++++ 5 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 dlls/msttsengine/unixlib.c
diff --git a/dlls/msttsengine/Makefile.in b/dlls/msttsengine/Makefile.in index 9a1fc965f2c..b6c9b9d27c2 100644 --- a/dlls/msttsengine/Makefile.in +++ b/dlls/msttsengine/Makefile.in @@ -1,7 +1,9 @@ MODULE = msttsengine.dll +UNIXLIB = msttsengine.so IMPORTS = ole32
SOURCES = \ main.c \ tts.c \ - ttseng_classes.idl + ttseng_classes.idl \ + unixlib.c diff --git a/dlls/msttsengine/main.c b/dlls/msttsengine/main.c index bb8ddae3e0f..735a3c53305 100644 --- a/dlls/msttsengine/main.c +++ b/dlls/msttsengine/main.c @@ -39,7 +39,13 @@ BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, void *reserved) if (reason == DLL_PROCESS_ATTACH) { DisableThreadLibraryCalls(instance); + __wine_init_unix_call(); } + else if (reason == DLL_PROCESS_DETACH) + { + free_tts(); + } + return TRUE; }
diff --git a/dlls/msttsengine/tts.c b/dlls/msttsengine/tts.c index f895b35e216..fac8fb05743 100644 --- a/dlls/msttsengine/tts.c +++ b/dlls/msttsengine/tts.c @@ -31,6 +31,8 @@
#include "wine/debug.h"
+#include "ttseng_private.h" + WINE_DEFAULT_DEBUG_CHANNEL(msttsengine);
struct ttsengine @@ -42,6 +44,9 @@ struct ttsengine ISpObjectToken *token; };
+static INIT_ONCE init_once = INIT_ONCE_STATIC_INIT; +static tts_t tts = 0; + static inline struct ttsengine *impl_from_ISpTTSEngine(ISpTTSEngine *iface) { return CONTAINING_RECORD(iface, struct ttsengine, ISpTTSEngine_iface); @@ -52,6 +57,17 @@ static inline struct ttsengine *impl_from_ISpObjectWithToken(ISpObjectWithToken return CONTAINING_RECORD(iface, struct ttsengine, ISpObjectWithToken_iface); }
+static BOOL WINAPI init_tts(INIT_ONCE *once, void *param, void **ctx) +{ + WINE_UNIX_CALL(unix_tts_create, &tts); + return tts != 0; +} + +void free_tts(void) +{ + if (tts) WINE_UNIX_CALL(unix_tts_destroy, &tts); +} + static HRESULT WINAPI ttsengine_QueryInterface(ISpTTSEngine *iface, REFIID iid, void **obj) { struct ttsengine *This = impl_from_ISpTTSEngine(iface); @@ -204,6 +220,9 @@ HRESULT ttsengine_create(REFIID iid, void **obj) struct ttsengine *This; HRESULT hr;
+ if (!InitOnceExecuteOnce(&init_once, init_tts, NULL, NULL) || !tts) + return E_FAIL; + if (!(This = malloc(sizeof(*This)))) return E_OUTOFMEMORY;
diff --git a/dlls/msttsengine/ttseng_private.h b/dlls/msttsengine/ttseng_private.h index 0c55b660bf1..04c0807dc25 100644 --- a/dlls/msttsengine/ttseng_private.h +++ b/dlls/msttsengine/ttseng_private.h @@ -26,6 +26,16 @@ #include "windef.h" #include "winternl.h"
+#include "wine/unixlib.h" + +void free_tts(void); HRESULT ttsengine_create(REFIID iid, void **obj);
+typedef UINT64 tts_t; +enum unix_funcs +{ + unix_tts_create, + unix_tts_destroy, +}; + #endif /* __WINE_TTSENG_PRIVATE_H */ diff --git a/dlls/msttsengine/unixlib.c b/dlls/msttsengine/unixlib.c new file mode 100644 index 00000000000..fc2cc23f836 --- /dev/null +++ b/dlls/msttsengine/unixlib.c @@ -0,0 +1,59 @@ +/* + * MSTTSEngine unixlib. + * + * Copyright 2023 Shaun Ren for CodeWeavers + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#if 0 +#pragma makedep unix +#endif + +#include <stdarg.h> +#include <stdlib.h> +#include <unistd.h> + +#include "ntstatus.h" +#define WIN32_NO_STATUS +#include "winternl.h" + +#include "ttseng_private.h" + +static NTSTATUS tts_create(void *args) +{ + return STATUS_NOT_IMPLEMENTED; +} + +static NTSTATUS tts_destroy(void *args) +{ + return STATUS_NOT_IMPLEMENTED; +} + +const unixlib_entry_t __wine_unix_call_funcs[] = +{ + tts_create, + tts_destroy, +}; + +#ifdef _WIN64 + +const unixlib_entry_t __wine_unix_call_wow64_funcs[] = +{ + tts_create, + tts_destroy, +}; + +#endif /* _WIN64 */
From: Shaun Ren sren@codeweavers.com
Also introduce the piper TTS library. --- configure.ac | 13 +++++++++++++ dlls/msttsengine/Makefile.in | 2 ++ dlls/msttsengine/unixlib.c | 17 +++++++++++++++-- 3 files changed, 30 insertions(+), 2 deletions(-)
diff --git a/configure.ac b/configure.ac index 5a6594adbed..1e6a8d2cb8d 100644 --- a/configure.ac +++ b/configure.ac @@ -51,6 +51,7 @@ AC_ARG_WITH(oss, AS_HELP_STRING([--without-oss],[do not use the OSS sound AC_ARG_WITH(pcap, AS_HELP_STRING([--without-pcap],[do not use the Packet Capture library]), [if test "x$withval" = "xno"; then ac_cv_header_pcap_pcap_h=no; fi]) AC_ARG_WITH(pcsclite, AS_HELP_STRING([--without-pcsclite],[do not use PCSC lite])) +AC_ARG_WITH(piper, AS_HELP_STRING([--without-piper],[do not use the Piper TTS library])) AC_ARG_WITH(pthread, AS_HELP_STRING([--without-pthread],[do not use the pthread library])) AC_ARG_WITH(pulse, AS_HELP_STRING([--without-pulse],[do not use PulseAudio sound support])) AC_ARG_WITH(sane, AS_HELP_STRING([--without-sane],[do not use SANE (scanner support)])) @@ -1819,6 +1820,18 @@ fi WINE_NOTICE_WITH(vulkan,[test "x$ac_cv_lib_soname_vulkan" = "x" -a "x$ac_cv_lib_soname_MoltenVK" = "x"], [libvulkan and libMoltenVK ${notice_platform}development files not found, Vulkan won't be supported.])
+dnl **** Check for libpiper **** +if test "x$with_piper" != "xno" +then + WINE_PACKAGE_FLAGS(PIPER,[piper],[-lpiper],,, + [AC_CHECK_HEADER(piper/piper_c.h, + [AC_CHECK_LIB(piper,piperInitialize,[:],[PIPER_LIBS=""],[$PIPER_LIBS])], + [PIPER_LIBS=""])]) +fi +WINE_NOTICE_WITH(piper,[test "$ac_cv_lib_piper_piperInitialize" != "yes"], + [libpiper ${notice_platform}development files not found, TTS won't be supported.], + [enable_msttsengine]) + dnl **** Check for gcc specific options ****
if test "x${GCC}" = "xyes" diff --git a/dlls/msttsengine/Makefile.in b/dlls/msttsengine/Makefile.in index b6c9b9d27c2..9b64e684875 100644 --- a/dlls/msttsengine/Makefile.in +++ b/dlls/msttsengine/Makefile.in @@ -1,6 +1,8 @@ MODULE = msttsengine.dll UNIXLIB = msttsengine.so IMPORTS = ole32 +UNIX_CFLAGS = $(PIPER_CFLAGS) +UNIX_LIBS = $(PIPER_LIBS)
SOURCES = \ main.c \ diff --git a/dlls/msttsengine/unixlib.c b/dlls/msttsengine/unixlib.c index fc2cc23f836..864f97fa9a2 100644 --- a/dlls/msttsengine/unixlib.c +++ b/dlls/msttsengine/unixlib.c @@ -26,20 +26,33 @@ #include <stdlib.h> #include <unistd.h>
+#include <piper/piper_c.h> + #include "ntstatus.h" #define WIN32_NO_STATUS #include "winternl.h"
#include "ttseng_private.h"
+static inline Piper *get_piper(tts_t tts) +{ + return (Piper *)(ULONG_PTR)tts; +} + static NTSTATUS tts_create(void *args) { - return STATUS_NOT_IMPLEMENTED; + Piper *piper = piperInitialize(NULL); + + *(tts_t *)args = (tts_t)(ULONG_PTR)piper; + return piper ? STATUS_SUCCESS : STATUS_UNSUCCESSFUL; }
static NTSTATUS tts_destroy(void *args) { - return STATUS_NOT_IMPLEMENTED; + Piper *piper = get_piper(*(tts_t *)args); + + piperTerminate(piper); + return STATUS_SUCCESS; }
const unixlib_entry_t __wine_unix_call_funcs[] =
From: Shaun Ren sren@codeweavers.com
--- dlls/msttsengine/tts.c | 49 ++++++++++++++++++++- dlls/msttsengine/ttseng_private.h | 13 ++++++ dlls/msttsengine/unixlib.c | 72 +++++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+), 1 deletion(-)
diff --git a/dlls/msttsengine/tts.c b/dlls/msttsengine/tts.c index fac8fb05743..9b65416a981 100644 --- a/dlls/msttsengine/tts.c +++ b/dlls/msttsengine/tts.c @@ -42,6 +42,8 @@ struct ttsengine LONG ref;
ISpObjectToken *token; + INT64 speaker_id; + tts_voice_t voice; };
static INIT_ONCE init_once = INIT_ONCE_STATIC_INIT; @@ -68,6 +70,20 @@ void free_tts(void) if (tts) WINE_UNIX_CALL(unix_tts_destroy, &tts); }
+static tts_voice_t tts_voice_load(tts_t tts, const char *model_path, INT64 speaker_id) +{ + struct tts_voice_load_params params = + { + .tts = tts, + .model_path = model_path, + .speaker_id = speaker_id, + .voice = 0, + }; + + WINE_UNIX_CALL(unix_tts_voice_load, ¶ms); + return params.voice; +} + static HRESULT WINAPI ttsengine_QueryInterface(ISpTTSEngine *iface, REFIID iid, void **obj) { struct ttsengine *This = impl_from_ISpTTSEngine(iface); @@ -111,6 +127,8 @@ static ULONG WINAPI ttsengine_Release(ISpTTSEngine *iface) if (!ref) { if (This->token) ISpObjectToken_Release(This->token); + if (This->voice) WINE_UNIX_CALL(unix_tts_voice_destroy, &This->voice); + free(This); }
@@ -174,14 +192,41 @@ static ULONG WINAPI objwithtoken_Release(ISpObjectWithToken *iface) static HRESULT WINAPI objwithtoken_SetObjectToken(ISpObjectWithToken *iface, ISpObjectToken *token) { struct ttsengine *This = impl_from_ISpObjectWithToken(iface); + WCHAR *value; + char *model_path; + int ret; + HRESULT hr;
- FIXME("(%p, %p): semi-stub.\n", iface, token); + TRACE("(%p, %p).\n", iface, token);
if (!token) return E_INVALIDARG; if (This->token) return SPERR_ALREADY_INITIALIZED;
+ if (FAILED(hr = ISpObjectToken_GetStringValue(token, L"ModelPath", &value))) + return hr; + model_path = wine_get_unix_file_name(value); + CoTaskMemFree(value); + if (!model_path) + return E_INVALIDARG; + + hr = ISpObjectToken_GetStringValue(token, L"SpeakerID", &value); + if (FAILED(hr) && hr != SPERR_NOT_FOUND) + return hr; + else if (SUCCEEDED(hr)) + { + ret = swscanf(value, L"%I64d", &This->speaker_id); + CoTaskMemFree(value); + if (ret != 1) + return E_INVALIDARG; + } + + This->voice = tts_voice_load(tts, model_path, This->speaker_id); + HeapFree(GetProcessHeap(), 0, model_path); + if (!This->voice) + return E_FAIL; + ISpObjectToken_AddRef(token); This->token = token; return S_OK; @@ -231,6 +276,8 @@ HRESULT ttsengine_create(REFIID iid, void **obj) This->ref = 1;
This->token = NULL; + This->speaker_id = 0; + This->voice = 0;
hr = ISpTTSEngine_QueryInterface(&This->ISpTTSEngine_iface, iid, obj); ISpTTSEngine_Release(&This->ISpTTSEngine_iface); diff --git a/dlls/msttsengine/ttseng_private.h b/dlls/msttsengine/ttseng_private.h index 04c0807dc25..62bb89f431a 100644 --- a/dlls/msttsengine/ttseng_private.h +++ b/dlls/msttsengine/ttseng_private.h @@ -32,10 +32,23 @@ void free_tts(void); HRESULT ttsengine_create(REFIID iid, void **obj);
typedef UINT64 tts_t; +typedef UINT64 tts_voice_t; + +struct tts_voice_load_params +{ + tts_t tts; + const char *model_path; + INT64 speaker_id; + tts_voice_t voice; +}; + enum unix_funcs { unix_tts_create, unix_tts_destroy, + + unix_tts_voice_load, + unix_tts_voice_destroy, };
#endif /* __WINE_TTSENG_PRIVATE_H */ diff --git a/dlls/msttsengine/unixlib.c b/dlls/msttsengine/unixlib.c index 864f97fa9a2..e45b870d066 100644 --- a/dlls/msttsengine/unixlib.c +++ b/dlls/msttsengine/unixlib.c @@ -34,11 +34,22 @@
#include "ttseng_private.h"
+struct tts_voice +{ + Piper *piper; + PiperVoice *voice; +}; + static inline Piper *get_piper(tts_t tts) { return (Piper *)(ULONG_PTR)tts; }
+static inline struct tts_voice *get_voice(tts_voice_t voice) +{ + return (struct tts_voice *)(ULONG_PTR)voice; +} + static NTSTATUS tts_create(void *args) { Piper *piper = piperInitialize(NULL); @@ -55,18 +66,79 @@ static NTSTATUS tts_destroy(void *args) return STATUS_SUCCESS; }
+static NTSTATUS tts_voice_load(void *args) +{ + struct tts_voice_load_params *params = args; + struct tts_voice *voice; + + if (!(voice = calloc(1, sizeof(*voice)))) + return STATUS_NO_MEMORY; + + voice->piper = get_piper(params->tts); + if (!(voice->voice = piperLoadVoice(voice->piper, params->model_path, NULL, params->speaker_id))) + { + free(voice); + return STATUS_UNSUCCESSFUL; + } + + params->voice = (tts_voice_t)(ULONG_PTR)voice; + + return STATUS_SUCCESS; +} + +static NTSTATUS tts_voice_destroy(void *args) +{ + struct tts_voice *voice = get_voice(*(tts_voice_t *)args); + + piperFreeVoice(voice->voice); + + free(voice); + + return STATUS_SUCCESS; +} + const unixlib_entry_t __wine_unix_call_funcs[] = { tts_create, tts_destroy, + + tts_voice_load, + tts_voice_destroy, };
#ifdef _WIN64
+typedef ULONG PTR32; + +static NTSTATUS wow64_tts_voice_load(void *args) +{ + struct + { + tts_t tts; + PTR32 model_path; + INT64 speaker_id; + tts_voice_t voice; + } *params32 = args; + struct tts_voice_load_params params = + { + .tts = params32->tts, + .model_path = ULongToPtr(params32->model_path), + .speaker_id = params32->speaker_id, + }; + NTSTATUS ret; + + ret = tts_voice_load(¶ms); + params32->voice = params.voice; + return ret; +} + const unixlib_entry_t __wine_unix_call_wow64_funcs[] = { tts_create, tts_destroy, + + wow64_tts_voice_load, + tts_voice_destroy, };
#endif /* _WIN64 */
From: Shaun Ren sren@codeweavers.com
--- dlls/msttsengine/tts.c | 27 +++++++++++++++++++++++++-- dlls/msttsengine/ttseng_private.h | 10 ++++++++++ dlls/msttsengine/unixlib.c | 18 ++++++++++++++++++ 3 files changed, 53 insertions(+), 2 deletions(-)
diff --git a/dlls/msttsengine/tts.c b/dlls/msttsengine/tts.c index 9b65416a981..8e60119ce68 100644 --- a/dlls/msttsengine/tts.c +++ b/dlls/msttsengine/tts.c @@ -148,9 +148,32 @@ static HRESULT WINAPI ttsengine_GetOutputFormat(ISpTTSEngine *iface, const GUID const WAVEFORMATEX *wfx, GUID *out_fmtid, WAVEFORMATEX **out_wfx) { - FIXME("(%p, %s, %p, %p, %p): stub.\n", iface, debugstr_guid(fmtid), wfx, out_fmtid, out_wfx); + struct ttsengine *This = impl_from_ISpTTSEngine(iface); + struct tts_voice_get_config_params params = + { + .voice = This->voice, + };
- return E_NOTIMPL; + TRACE("(%p, %s, %p, %p, %p).\n", iface, debugstr_guid(fmtid), wfx, out_fmtid, out_wfx); + + if (!This->voice) + return SPERR_UNINITIALIZED; + + *out_fmtid = SPDFID_WaveFormatEx; + if (!(*out_wfx = CoTaskMemAlloc(sizeof(WAVEFORMATEX)))) + return E_OUTOFMEMORY; + + WINE_UNIX_CALL(unix_tts_voice_get_config, ¶ms); + + (*out_wfx)->wFormatTag = WAVE_FORMAT_PCM; + (*out_wfx)->nChannels = params.channels; + (*out_wfx)->nSamplesPerSec = params.sample_rate; + (*out_wfx)->wBitsPerSample = params.sample_width * 8; + (*out_wfx)->nBlockAlign = params.sample_width * params.channels; + (*out_wfx)->nAvgBytesPerSec = params.sample_rate * params.sample_width * params.channels; + (*out_wfx)->cbSize = 0; + + return S_OK; }
static ISpTTSEngineVtbl ttsengine_vtbl = diff --git a/dlls/msttsengine/ttseng_private.h b/dlls/msttsengine/ttseng_private.h index 62bb89f431a..1bbd1500e33 100644 --- a/dlls/msttsengine/ttseng_private.h +++ b/dlls/msttsengine/ttseng_private.h @@ -42,6 +42,15 @@ struct tts_voice_load_params tts_voice_t voice; };
+struct tts_voice_get_config_params +{ + tts_voice_t voice; + float length_scale; + INT32 sample_rate; + INT32 sample_width; + INT32 channels; +}; + enum unix_funcs { unix_tts_create, @@ -49,6 +58,7 @@ enum unix_funcs
unix_tts_voice_load, unix_tts_voice_destroy, + unix_tts_voice_get_config, };
#endif /* __WINE_TTSENG_PRIVATE_H */ diff --git a/dlls/msttsengine/unixlib.c b/dlls/msttsengine/unixlib.c index e45b870d066..b544872b5c2 100644 --- a/dlls/msttsengine/unixlib.c +++ b/dlls/msttsengine/unixlib.c @@ -97,6 +97,22 @@ static NTSTATUS tts_voice_destroy(void *args) return STATUS_SUCCESS; }
+static NTSTATUS tts_voice_get_config(void *args) +{ + struct tts_voice_get_config_params *params = args; + struct tts_voice *voice = get_voice(params->voice); + PiperSynthesisConfig config; + + piperGetVoiceSynthesisConfig(voice->voice, &config); + + params->length_scale = config.lengthScale; + params->sample_rate = config.sampleRate; + params->sample_width = config.sampleWidth; + params->channels = config.channels; + + return STATUS_SUCCESS; +} + const unixlib_entry_t __wine_unix_call_funcs[] = { tts_create, @@ -104,6 +120,7 @@ const unixlib_entry_t __wine_unix_call_funcs[] =
tts_voice_load, tts_voice_destroy, + tts_voice_get_config, };
#ifdef _WIN64 @@ -139,6 +156,7 @@ const unixlib_entry_t __wine_unix_call_wow64_funcs[] =
wow64_tts_voice_load, tts_voice_destroy, + tts_voice_get_config, };
#endif /* _WIN64 */
Hi,
It looks like your patch introduced the new failures shown below. Please investigate and fix them before resubmitting your patch. If they are not new, fixing them anyway would help a lot. Otherwise please ask for the known failures list to be updated.
The full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=139758
Your paranoid android.
=== debian11 (build log) ===
error: patch failed: configure.ac:51 Task: Patch failed to apply
=== debian11b (build log) ===
error: patch failed: configure.ac:51 Task: Patch failed to apply
Ideally we'd install piper in the CI image: see `tools/gitlab/image.docker`.
On Wed Nov 15 05:42:40 2023 +0000, Huw Davies wrote:
Ideally we'd install piper in the CI image: see `tools/gitlab/image.docker`.
Piper right now is not packaged anywhere, and the code in this MR needs some additions in piper not currently upstreamed. Would it be a good idea to clone and build piper in `build.yml`?
On Wed Nov 15 05:42:40 2023 +0000, Shaun Ren wrote:
Piper right now is not packaged anywhere, and the code in this MR needs some additions in piper not currently upstreamed. Would it be a good idea to clone and build piper in `build.yml`?
That's unfortunate. No, I don't think we want to build piper in the CI.
Could you at least document the steps to build it here, so that folks can do so locally in order to test this? That should include having the additions that you mentioned that aren't yet upstreamed.
On Wed Nov 15 06:20:41 2023 +0000, Huw Davies wrote:
That's unfortunate. No, I don't think we want to build piper in the CI. Could you at least document the steps to build it here, so that folks can do so locally in order to test this? That should include having the additions that you mentioned that aren't yet upstreamed.
If it's not packaged anywhere, is it really the right tool for the job? Isn't there some more commonly available library we could use?
On Wed Nov 15 14:35:02 2023 +0000, Alexandre Julliard wrote:
If it's not packaged anywhere, is it really the right tool for the job? Isn't there some more commonly available library we could use?
There are some other pre-packaged TTS libraries that we could use, but they use outdated synthesis methods and the synthesis quality is poor compared to modern neuarl net TTS libraries like piper. It would be best if wine has a TTS engine using the latest libraries.
Closed in favor of !4886.
This merge request was closed by Shaun Ren.