voiceinfo list work

(crashes for some reason)
This commit is contained in:
Lily Tsuru 2024-07-18 06:30:57 -04:00
parent 3b127a0b08
commit 126566c3cf
9 changed files with 124 additions and 15 deletions

View file

@ -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/

View file

@ -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 => {

View file

@ -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>

View file

@ -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);

View file

@ -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

View file

@ -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;
}
} }

View file

@ -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;

View file

@ -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() {

View file

@ -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;