voiceinfo list work
(crashes for some reason)
This commit is contained in:
parent
3b127a0b08
commit
126566c3cf
9 changed files with 124 additions and 15 deletions
4
Justfile
4
Justfile
|
@ -8,9 +8,9 @@ build:
|
||||||
|
|
||||||
build-debug:
|
build-debug:
|
||||||
dotnet build -c Debug
|
dotnet build -c Debug
|
||||||
make -C speech2 CONFIG=Debug -j$(nproc)
|
make -C speech2 CONFIG=Release -j$(nproc)
|
||||||
|
|
||||||
cp speech2/bin/x86/Debug/speech2.dll SAPIServer/bin/Debug/net40/windows-x86
|
cp speech2/bin/x86/Release/speech2.dll SAPIServer/bin/Debug/net40/windows-x86
|
||||||
cp /usr/i686-w64-mingw32/bin/libgcc_s_dw2-1.dll SAPIServer/bin/Debug/net40/windows-x86/
|
cp /usr/i686-w64-mingw32/bin/libgcc_s_dw2-1.dll SAPIServer/bin/Debug/net40/windows-x86/
|
||||||
cp /usr/i686-w64-mingw32/bin/libstdc++-6.dll SAPIServer/bin/Debug/net40/windows-x86/
|
cp /usr/i686-w64-mingw32/bin/libstdc++-6.dll SAPIServer/bin/Debug/net40/windows-x86/
|
||||||
|
|
||||||
|
|
|
@ -15,13 +15,21 @@ namespace SAPIServer {
|
||||||
public SpeechServer() {
|
public SpeechServer() {
|
||||||
// Test out creating a speech2 api object
|
// Test out creating a speech2 api object
|
||||||
apis["sapi4"] = new SpeechAPI(EngineType.ET_SAPI4);
|
apis["sapi4"] = new SpeechAPI(EngineType.ET_SAPI4);
|
||||||
|
#if true
|
||||||
|
foreach(var voice in apis["sapi4"].GetVoices()) {
|
||||||
|
Console.WriteLine($"ggg {voice.name}");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
httpServer.Get("/api/voices", ctx => {
|
httpServer.Get("/api/voices", ctx => {
|
||||||
|
/*
|
||||||
using(var synth = new SpeechSynthesizer()) {
|
using(var synth = new SpeechSynthesizer()) {
|
||||||
ctx.Response.ContentType = "application/json";
|
ctx.Response.ContentType = "application/json";
|
||||||
return Encoding.UTF8.GetBytes(
|
return Encoding.UTF8.GetBytes(
|
||||||
JsonConvert.SerializeObject(new VoicesResponse { voices = synth.GetInstalledVoices().Select(v => v.VoiceInfo.Name).ToArray() }));
|
JsonConvert.SerializeObject(new VoicesResponse { voices = synth.GetInstalledVoices().Select(v => v.VoiceInfo.Name).ToArray() }));
|
||||||
}
|
}*/
|
||||||
|
|
||||||
|
return new byte[]{0};
|
||||||
});
|
});
|
||||||
|
|
||||||
httpServer.Post("/api/synthesize", ctx => {
|
httpServer.Post("/api/synthesize", ctx => {
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFrameworks>net40</TargetFrameworks>
|
<TargetFrameworks>net40</TargetFrameworks>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
|
|
@ -1,13 +1,39 @@
|
||||||
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Security;
|
||||||
|
|
||||||
namespace SAPIServer {
|
namespace SAPIServer {
|
||||||
|
|
||||||
// Sync with C++ code.
|
// Sync with C++ code.
|
||||||
public enum EngineType : int { ET_SAPI4, ET_SAPI5, ET_DECTALK }
|
public enum EngineType : int { ET_SAPI4, ET_SAPI5, ET_DECTALK }
|
||||||
|
|
||||||
|
public class VoiceDef {
|
||||||
|
public Guid guid;
|
||||||
|
public string name;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
internal struct VoiceDefInternal {
|
||||||
|
Guid guid;
|
||||||
|
|
||||||
|
[MarshalAs(UnmanagedType.LPStr)]
|
||||||
|
string name;
|
||||||
|
|
||||||
|
public VoiceDef Voicify() {
|
||||||
|
Console.WriteLine($"FUCK ${name}");
|
||||||
|
//var str = Marshal.PtrToStringAnsi(name);
|
||||||
|
VoiceDef def = new();
|
||||||
|
|
||||||
|
def.guid = guid;
|
||||||
|
def.name = name;
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Speech2 DLL API. Sync with c++ code.
|
// Speech2 DLL API. Sync with c++ code.
|
||||||
internal class SpeechDLL {
|
internal class SpeechDLL {
|
||||||
[DllImport("speech2.dll")]
|
[DllImport("speech2.dll")]
|
||||||
|
@ -15,6 +41,12 @@ namespace SAPIServer {
|
||||||
|
|
||||||
[DllImport("speech2.dll")]
|
[DllImport("speech2.dll")]
|
||||||
public static extern void speech2_destroy_api(IntPtr pAPI);
|
public static extern void speech2_destroy_api(IntPtr pAPI);
|
||||||
|
|
||||||
|
[DllImport("speech2.dll")]
|
||||||
|
public static extern int speech2_api_get_voiceinfo_count(IntPtr pAPI);
|
||||||
|
|
||||||
|
[DllImport("speech2.dll")]
|
||||||
|
public static extern IntPtr speech2_api_get_voiceinfo_index(IntPtr pAPI, int index);
|
||||||
}
|
}
|
||||||
|
|
||||||
// A speech API. This is generic for all speech2 supported speech APIs, so
|
// A speech API. This is generic for all speech2 supported speech APIs, so
|
||||||
|
@ -28,6 +60,18 @@ namespace SAPIServer {
|
||||||
throw new InvalidOperationException("Failed to create speech API");
|
throw new InvalidOperationException("Failed to create speech API");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<VoiceDef> GetVoices() {
|
||||||
|
var count = SpeechDLL.speech2_api_get_voiceinfo_count(handle);
|
||||||
|
Console.WriteLine($"count {count}");
|
||||||
|
var list = new List<VoiceDef>();
|
||||||
|
for(var i = 0; i < count; ++i) {
|
||||||
|
var ptr = SpeechDLL.speech2_api_get_voiceinfo_index(handle, i);
|
||||||
|
var obj = (VoiceDefInternal)Marshal.PtrToStructure(ptr, typeof(VoiceDefInternal));
|
||||||
|
list.Add(obj.Voicify());
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
void IDisposable.Dispose() {
|
void IDisposable.Dispose() {
|
||||||
if(handle != IntPtr.Zero)
|
if(handle != IntPtr.Zero)
|
||||||
SpeechDLL.speech2_destroy_api(handle);
|
SpeechDLL.speech2_destroy_api(handle);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Base compiler flags. Only change if you *explicitly* know what you're doing.
|
# Base compiler flags. Only change if you *explicitly* know what you're doing.
|
||||||
BASE_CCFLAGS = -MMD -fvisibility=hidden -std=gnu17 -fpermissive -fno-ident -msse -Iinclude -Isrc -D_UCRT -D_WIN32_WINNT=0x0501
|
BASE_CCFLAGS = -MMD -fvisibility=hidden -std=gnu17 -fpermissive -fno-pic -fno-pie -fno-ident -msse -Iinclude -Isrc -D_UCRT -D_WIN32_WINNT=0x0501
|
||||||
BASE_CXXFLAGS = -MMD -fvisibility=hidden -std=c++20 -fpermissive -fno-ident -msse -Iinclude -Isrc -Ithird_party -D_UCRT -D_WIN32_WINNT=0x0501
|
BASE_CXXFLAGS = -MMD -fvisibility=hidden -std=c++20 -fpermissive -fno-pic -fno-pie -fno-ident -msse -Iinclude -Isrc -Ithird_party -D_UCRT -D_WIN32_WINNT=0x0501
|
||||||
BASE_LDFLAGS = -Wl,--subsystem=windows -fvisibility=hidden -shared -lkernel32 -lshell32 -luser32 -luuid -lole32
|
BASE_LDFLAGS = -Wl,--subsystem=windows -fvisibility=hidden -shared -lkernel32 -lshell32 -luser32 -luuid -lole32
|
||||||
|
|
||||||
Release_Valid = yes
|
Release_Valid = yes
|
||||||
|
|
|
@ -9,10 +9,20 @@ enum class EngineType : int { ET_SAPI4, ET_SAPI5, ET_DECTALK };
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
SP2_EXPORT void* speech2_create_api(EngineType type) {
|
SP2_EXPORT void* speech2_create_api(EngineType type) {
|
||||||
|
ISpeechAPI* api = nullptr;
|
||||||
switch(type) {
|
switch(type) {
|
||||||
case EngineType::ET_SAPI4: return static_cast<void*>(ISpeechAPI::CreateSapi4());
|
case EngineType::ET_SAPI4:
|
||||||
|
api = ISpeechAPI::CreateSapi4();
|
||||||
|
break;
|
||||||
default: return nullptr;
|
default: return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(auto hr = api->Initialize(); FAILED(hr)) {
|
||||||
|
delete api;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return static_cast<void*>(api);
|
||||||
}
|
}
|
||||||
|
|
||||||
SP2_EXPORT void speech2_destroy_api(void* engine) {
|
SP2_EXPORT void speech2_destroy_api(void* engine) {
|
||||||
|
@ -22,4 +32,20 @@ SP2_EXPORT void speech2_destroy_api(void* engine) {
|
||||||
|
|
||||||
// API bindings TODO
|
// API bindings TODO
|
||||||
|
|
||||||
|
SP2_EXPORT int speech2_api_get_voiceinfo_count(void* engine) {
|
||||||
|
if(engine) {
|
||||||
|
auto* api = static_cast<ISpeechAPI*>(engine);
|
||||||
|
return api->GetVoices().size();
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
SP2_EXPORT const ISpeechAPI::VoiceInfo* speech2_api_get_voiceinfo_index(void* engine, int index) {
|
||||||
|
if(engine) {
|
||||||
|
auto* api = static_cast<ISpeechAPI*>(engine);
|
||||||
|
return &api->GetVoices()[index];
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
#include <winscard.h>
|
||||||
|
|
||||||
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
|
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
|
||||||
// N.B: Should initalize COM if it's not initalized.
|
// N.B: Should initalize COM if it's not initalized.
|
||||||
// Note that with .NET Framework, *all* managed threads (incl. ThreadPool threads)
|
// Note that with .NET Framework, *all* managed threads (incl. ThreadPool threads)
|
||||||
// have COM initalized by default, so we don't need to do so there.
|
// have COM initalized by default, so we don't need to do so there.
|
||||||
switch(fdwReason) {
|
switch(fdwReason) {
|
||||||
case DLL_PROCESS_ATTACH: break;
|
case DLL_PROCESS_ATTACH:
|
||||||
|
CoInitialize(nullptr);
|
||||||
|
break;
|
||||||
|
|
||||||
case DLL_THREAD_ATTACH: break;
|
case DLL_THREAD_ATTACH: break;
|
||||||
|
|
||||||
|
@ -15,6 +18,7 @@ BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
|
||||||
if(lpvReserved != nullptr) {
|
if(lpvReserved != nullptr) {
|
||||||
break; // do not do cleanup if process termination scenario
|
break; // do not do cleanup if process termination scenario
|
||||||
}
|
}
|
||||||
|
CoUninitialize();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return TRUE;
|
return TRUE;
|
||||||
|
|
|
@ -19,23 +19,40 @@ struct SpeechAPI_SAPI4 : public ISpeechAPI {
|
||||||
HRESULT Initialize() override {
|
HRESULT Initialize() override {
|
||||||
HRESULT hRes;
|
HRESULT hRes;
|
||||||
|
|
||||||
|
printf("speech2: SpeechAPI_Sapi4::Initalize() begin\n");
|
||||||
|
|
||||||
hRes = pEnum.CreateInstance(CLSID_TTSEnumerator, CLSCTX_INPROC);
|
hRes = pEnum.CreateInstance(CLSID_TTSEnumerator, CLSCTX_INPROC);
|
||||||
if(FAILED(hRes))
|
if(FAILED(hRes))
|
||||||
return hRes;
|
return hRes;
|
||||||
|
|
||||||
|
|
||||||
|
pEnum->Reset();
|
||||||
|
|
||||||
|
printf("speech2: SpeechAPI_Sapi4::Initalize() created enum\n");
|
||||||
|
|
||||||
|
// Fill out voices
|
||||||
|
EnumVoices();
|
||||||
|
|
||||||
|
|
||||||
|
printf("speech2: SpeechAPI_Sapi4::Initalize() filled out voices! Yay\n");
|
||||||
|
|
||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<VoiceInfo> GetVoices() override {
|
void EnumVoices() {
|
||||||
TTSMODEINFO found {};
|
static TTSMODEINFO found {};
|
||||||
std::vector<VoiceInfo> ret;
|
|
||||||
|
|
||||||
while(!pEnum->Next(1, &found, nullptr)) {
|
while(!pEnum->Next(1, &found, nullptr)) {
|
||||||
ret.push_back({ .guid = found.gModeID, .voiceName = found.szModeName });
|
//auto ptr = strdup(found.szModeName);
|
||||||
|
printf("EnumVoices() voice %p\n", &found.szModeName);
|
||||||
|
//voices.push_back(VoiceInfo { .guid = found.gModeID, .voiceName = ptr });
|
||||||
}
|
}
|
||||||
|
|
||||||
pEnum->Reset();
|
pEnum->Reset();
|
||||||
return ret;
|
}
|
||||||
|
|
||||||
|
const std::vector<VoiceInfo>& GetVoices() override {
|
||||||
|
return voices;
|
||||||
}
|
}
|
||||||
|
|
||||||
HRESULT SelectVoiceImpl(const GUID& guid) {
|
HRESULT SelectVoiceImpl(const GUID& guid) {
|
||||||
|
@ -77,6 +94,8 @@ struct SpeechAPI_SAPI4 : public ISpeechAPI {
|
||||||
|
|
||||||
// The above comment is also why this isn't a ComPtr.
|
// The above comment is also why this isn't a ComPtr.
|
||||||
AudioOutBuffer* pAudioOut { nullptr };
|
AudioOutBuffer* pAudioOut { nullptr };
|
||||||
|
|
||||||
|
std::vector<VoiceInfo> voices{};
|
||||||
};
|
};
|
||||||
|
|
||||||
ISpeechAPI* ISpeechAPI::CreateSapi4() {
|
ISpeechAPI* ISpeechAPI::CreateSapi4() {
|
||||||
|
|
|
@ -7,7 +7,15 @@ struct ISpeechAPI {
|
||||||
|
|
||||||
struct VoiceInfo {
|
struct VoiceInfo {
|
||||||
GUID guid{}; // Optional. May not be filled out if th e
|
GUID guid{}; // Optional. May not be filled out if th e
|
||||||
std::string voiceName;
|
char* voiceName;
|
||||||
|
|
||||||
|
//VoiceInfo(const VoiceInfo&) = delete;
|
||||||
|
//VoiceInfo(VoiceInfo&&) = delete;
|
||||||
|
|
||||||
|
~VoiceInfo() {
|
||||||
|
if(voiceName)
|
||||||
|
free(voiceName);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
virtual ~ISpeechAPI() = default;
|
virtual ~ISpeechAPI() = default;
|
||||||
|
@ -19,7 +27,7 @@ struct ISpeechAPI {
|
||||||
/// Performs the bare level of initalization required to use the speech API
|
/// Performs the bare level of initalization required to use the speech API
|
||||||
virtual HRESULT Initialize() = 0;
|
virtual HRESULT Initialize() = 0;
|
||||||
|
|
||||||
virtual std::vector<VoiceInfo> GetVoices() = 0;
|
virtual const std::vector<VoiceInfo>& GetVoices() = 0;
|
||||||
|
|
||||||
/// Selects a voice.
|
/// Selects a voice.
|
||||||
virtual HRESULT SelectVoice(std::string_view voiceName) = 0;
|
virtual HRESULT SelectVoice(std::string_view voiceName) = 0;
|
||||||
|
|
Loading…
Reference in a new issue