/* * Copyright (C) 2016 Reece H. Dunn * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program 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 General Public License * along with this program; if not, see: . */ #include "config.h" #include #include #include #include #include #include #include extern "C" ULONG ObjectCount; static HRESULT espeak_status_to_hresult(espeak_ng_STATUS status) { switch (status) { case ENS_OK: return S_OK; case EACCES: return E_ACCESSDENIED; case EINVAL: return E_INVALIDARG; case ENOENT: return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); case ENOMEM: return E_OUTOFMEMORY; default: return E_FAIL; } } struct TtsEngine : public ISpObjectWithToken , public ISpTTSEngine { TtsEngine(); ~TtsEngine(); // IUnknown ULONG __stdcall AddRef(); ULONG __stdcall Release(); HRESULT __stdcall QueryInterface(REFIID iid, void **object); // ISpObjectWithToken HRESULT __stdcall GetObjectToken(ISpObjectToken **token); HRESULT __stdcall SetObjectToken(ISpObjectToken *token); // ISpTTSEngine HRESULT __stdcall Speak(DWORD flags, REFGUID formatId, const WAVEFORMATEX *format, const SPVTEXTFRAG *textFragList, ISpTTSEngineSite *site); HRESULT __stdcall GetOutputFormat(const GUID *targetFormatId, const WAVEFORMATEX *targetFormat, GUID *formatId, WAVEFORMATEX **format); int OnEvent(short *data, int samples, espeak_EVENT *events); private: HRESULT GetStringValue(LPCWSTR key, char *&value); ULONG refCount; ISpObjectToken *objectToken; ISpTTSEngineSite *site; }; static int espeak_callback(short *data, int samples, espeak_EVENT *events) { TtsEngine *engine = (TtsEngine *)events->user_data; return engine->OnEvent(data, samples, events); } TtsEngine::TtsEngine() : refCount(1) , objectToken(NULL) , site(NULL) { InterlockedIncrement(&ObjectCount); } TtsEngine::~TtsEngine() { InterlockedDecrement(&ObjectCount); if (objectToken) objectToken->Release(); } ULONG __stdcall TtsEngine::AddRef() { return InterlockedIncrement(&refCount); } ULONG __stdcall TtsEngine::Release() { ULONG ret = InterlockedDecrement(&refCount); if (ret == 0) delete this; return ret; } HRESULT __stdcall TtsEngine::QueryInterface(REFIID iid, void **object) { *object = NULL; if (IsEqualIID(iid, IID_IUnknown) || IsEqualIID(iid, IID_ISpTTSEngine)) *object = (ISpTTSEngine *)this; else if (IsEqualIID(iid, IID_ISpObjectWithToken)) *object = (ISpObjectWithToken *)this; else return E_NOINTERFACE; this->AddRef(); return S_OK; } HRESULT __stdcall TtsEngine::GetObjectToken(ISpObjectToken **token) { if (!token) return E_POINTER; *token = objectToken; if (objectToken) { objectToken->AddRef(); return S_OK; } return S_FALSE; } HRESULT __stdcall TtsEngine::SetObjectToken(ISpObjectToken *token) { if (!token) return E_INVALIDARG; if (objectToken) return SPERR_ALREADY_INITIALIZED; objectToken = token; objectToken->AddRef(); char *path = NULL; GetStringValue(L"Path", path); espeak_ng_InitializePath(path); if (path) free(path); espeak_ng_STATUS status; status = espeak_ng_Initialize(NULL); if (status == ENS_OK) status = espeak_ng_InitializeOutput(ENOUTPUT_MODE_SYNCHRONOUS, 100, NULL); espeak_SetSynthCallback(espeak_callback); char *voiceName = NULL; if (SUCCEEDED(GetStringValue(L"VoiceName", voiceName))) { if (status == ENS_OK) status = espeak_ng_SetVoiceByName(voiceName); free(voiceName); } return espeak_status_to_hresult(status); } HRESULT __stdcall TtsEngine::Speak(DWORD flags, REFGUID formatId, const WAVEFORMATEX *format, const SPVTEXTFRAG *textFragList, ISpTTSEngineSite *site) { if (!site || !textFragList) return E_INVALIDARG; this->site = site; while (textFragList != NULL) { DWORD actions = site->GetActions(); if (actions & SPVES_ABORT) return S_OK; switch (textFragList->State.eAction) { case SPVA_Speak: espeak_ng_Synthesize(textFragList->pTextStart, 0, 0, POS_CHARACTER, 0, espeakCHARS_WCHAR, NULL, this); break; } textFragList = textFragList->pNext; } return E_NOTIMPL; } HRESULT __stdcall TtsEngine::GetOutputFormat(const GUID *targetFormatId, const WAVEFORMATEX *targetFormat, GUID *formatId, WAVEFORMATEX **format) { *format = (WAVEFORMATEX *)CoTaskMemAlloc(sizeof(WAVEFORMATEX)); if (!*format) return E_OUTOFMEMORY; (*format)->wFormatTag = WAVE_FORMAT_PCM; (*format)->nChannels = 1; (*format)->nBlockAlign = 2; (*format)->nSamplesPerSec = 22050; (*format)->wBitsPerSample = 16; (*format)->nAvgBytesPerSec = (*format)->nSamplesPerSec * (*format)->nBlockAlign; (*format)->cbSize = 0; *formatId = SPDFID_WaveFormatEx; return S_OK; } int TtsEngine::OnEvent(short *data, int samples, espeak_EVENT *events) { DWORD actions = site->GetActions(); if (actions & SPVES_ABORT) return 1; if (data) site->Write(data, samples * 2, NULL); return 0; } HRESULT TtsEngine::GetStringValue(LPCWSTR key, char *&value) { if (!objectToken) return E_FAIL; LPWSTR wvalue = NULL; HRESULT hr = objectToken->GetStringValue(key, &wvalue); if (FAILED(hr)) return hr; size_t len = wcslen(wvalue); value = (char *)malloc(len + 1); if (!value) { CoTaskMemFree(wvalue); return E_OUTOFMEMORY; } wcstombs(value, wvalue, len + 1); CoTaskMemFree(wvalue); return S_OK; } extern "C" HRESULT __stdcall TtsEngine_CreateInstance(IClassFactory *iface, IUnknown *outer, REFIID iid, void **object) { if (outer != NULL) return CLASS_E_NOAGGREGATION; TtsEngine *engine = new (std::nothrow) TtsEngine(); if (!engine) return E_OUTOFMEMORY; HRESULT ret = engine->QueryInterface(iid, object); engine->Release(); return ret; }