make speech2 a dll + start binding it
This commit is contained in:
parent
20f59c5663
commit
bd048875c8
11 changed files with 230 additions and 299 deletions
4
Justfile
4
Justfile
|
@ -5,3 +5,7 @@ build:
|
||||||
clean:
|
clean:
|
||||||
rm -rf SAPIServer/bin SAPIServer/obj
|
rm -rf SAPIServer/bin SAPIServer/obj
|
||||||
make -C speech2 clean
|
make -C speech2 clean
|
||||||
|
|
||||||
|
|
||||||
|
format:
|
||||||
|
cd SAPIServer; for f in *.cs; do clang-format -i $f; done; cd ..;
|
||||||
|
|
|
@ -3,112 +3,103 @@ using System.Collections.Generic;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace SAPIServer
|
namespace SAPIServer {
|
||||||
{
|
/// <summary>
|
||||||
/// <summary>
|
/// Simple routing HTTP server for .NET Framework
|
||||||
/// Simple routing HTTP server for .NET Framework
|
/// </summary>
|
||||||
/// </summary>
|
class HTTPServer {
|
||||||
class HTTPServer
|
private bool listening = false;
|
||||||
{
|
private HttpListener listener;
|
||||||
private bool listening = false;
|
private Dictionary<string, Func<HttpListenerContext, byte[]>> GETListeners;
|
||||||
private HttpListener listener;
|
private Dictionary<string, Func<HttpListenerContext, byte[]>> POSTListeners;
|
||||||
private Dictionary<string, Func<HttpListenerContext, byte[]>> GETListeners;
|
|
||||||
private Dictionary<string, Func<HttpListenerContext, byte[]>> POSTListeners;
|
|
||||||
|
|
||||||
public HTTPServer()
|
public HTTPServer() {
|
||||||
{
|
listener = new HttpListener();
|
||||||
listener = new HttpListener();
|
this.GETListeners = new Dictionary<string, Func<HttpListenerContext, byte[]>>();
|
||||||
this.GETListeners = new Dictionary<string, Func<HttpListenerContext, byte[]>>();
|
this.POSTListeners = new Dictionary<string, Func<HttpListenerContext, byte[]>>();
|
||||||
this.POSTListeners = new Dictionary<string, Func<HttpListenerContext, byte[]>>();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public void Get(string uri, Func<HttpListenerContext, byte[]> handler)
|
public void Get(string uri, Func<HttpListenerContext, byte[]> handler) {
|
||||||
{
|
if(GETListeners.ContainsKey(uri))
|
||||||
if (GETListeners.ContainsKey(uri)) throw new InvalidOperationException("A handler by that URI already exists.");
|
throw new InvalidOperationException("A handler by that URI already exists.");
|
||||||
if (!uri.StartsWith("/")) throw new ArgumentException("URIs must start with /");
|
if(!uri.StartsWith("/"))
|
||||||
this.GETListeners.Add(uri, handler);
|
throw new ArgumentException("URIs must start with /");
|
||||||
}
|
this.GETListeners.Add(uri, handler);
|
||||||
|
}
|
||||||
|
|
||||||
public void Post(string uri, Func<HttpListenerContext, byte[]> handler)
|
public void Post(string uri, Func<HttpListenerContext, byte[]> handler) {
|
||||||
{
|
if(POSTListeners.ContainsKey(uri))
|
||||||
if (POSTListeners.ContainsKey(uri)) throw new InvalidOperationException("A handler by that URI already exists.");
|
throw new InvalidOperationException("A handler by that URI already exists.");
|
||||||
if (!uri.StartsWith("/")) throw new ArgumentException("URIs must start with /");
|
if(!uri.StartsWith("/"))
|
||||||
this.POSTListeners.Add(uri, handler);
|
throw new ArgumentException("URIs must start with /");
|
||||||
}
|
this.POSTListeners.Add(uri, handler);
|
||||||
|
}
|
||||||
|
|
||||||
public void Listen(ushort port)
|
public void Listen(ushort port) {
|
||||||
{
|
if(listening)
|
||||||
if (listening) throw new InvalidOperationException("This HTTPServer is already listening.");
|
throw new InvalidOperationException("This HTTPServer is already listening.");
|
||||||
listening = true;
|
listening = true;
|
||||||
listener.Prefixes.Add(string.Format("http://*:{0}/", port));
|
listener.Prefixes.Add(string.Format("http://*:{0}/", port));
|
||||||
listener.Start();
|
listener.Start();
|
||||||
while (listener.IsListening)
|
|
||||||
{
|
|
||||||
var context = listener.GetContext();
|
|
||||||
ThreadPool.QueueUserWorkItem(_ =>
|
|
||||||
{
|
|
||||||
var uri = context.Request.RawUrl.Split('?')[0];
|
|
||||||
var ip = context.Request.RemoteEndPoint.Address;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Dictionary<string, Func<HttpListenerContext, byte[]>> handlerDict;
|
|
||||||
// TODO: Make query params parsable
|
|
||||||
byte[] response;
|
|
||||||
switch (context.Request.HttpMethod)
|
|
||||||
{
|
|
||||||
case "GET":
|
|
||||||
{
|
|
||||||
handlerDict = GETListeners;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "POST":
|
|
||||||
{
|
|
||||||
handlerDict = POSTListeners;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
response = Encoding.UTF8.GetBytes($"Method not allowed: {context.Request.HttpMethod}");
|
|
||||||
context.Response.StatusCode = 405;
|
|
||||||
context.Response.ContentType = "text/plain";
|
|
||||||
context.Response.ContentLength64 = response.Length;
|
|
||||||
context.Response.OutputStream.Write(response, 0, response.Length);
|
|
||||||
context.Response.OutputStream.Close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!handlerDict.TryGetValue(uri, out var handler))
|
|
||||||
{
|
|
||||||
response = Encoding.UTF8.GetBytes($"No route defined for {uri}");
|
|
||||||
context.Response.StatusCode = 404;
|
|
||||||
context.Response.ContentType = "text/plain";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
response = handler(context);
|
|
||||||
} catch (Exception e)
|
|
||||||
{
|
|
||||||
response = Encoding.UTF8.GetBytes("Internal Server Error");
|
|
||||||
context.Response.StatusCode = 500;
|
|
||||||
context.Response.ContentType = "text/plain";
|
|
||||||
Console.Error.WriteLine($"[{DateTime.Now:u}] {ip} - {context.Request.HttpMethod} {context.Request.RawUrl} - Handler Failed: {e.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
context.Response.ContentLength64 = response.Length;
|
while(listener.IsListening) {
|
||||||
context.Response.OutputStream.Write(response, 0, response.Length);
|
var context = listener.GetContext();
|
||||||
context.Response.OutputStream.Close();
|
ThreadPool.QueueUserWorkItem(
|
||||||
Console.WriteLine($"[{DateTime.Now:u}] {ip} - {context.Request.HttpMethod} {context.Request.RawUrl} - {context.Response.StatusCode}");
|
_ => {
|
||||||
} catch (Exception e)
|
var uri = context.Request.RawUrl.Split('?')[0];
|
||||||
{
|
var ip = context.Request.RemoteEndPoint.Address;
|
||||||
Console.Error.WriteLine($"[{DateTime.Now:u}] {ip} - {context.Request.HttpMethod} {context.Request.RawUrl} - Exception: {e.Message}");
|
try {
|
||||||
}
|
Dictionary<string, Func<HttpListenerContext, byte[]>> handlerDict;
|
||||||
|
// TODO: Make query params parsable
|
||||||
|
byte[] response;
|
||||||
|
switch(context.Request.HttpMethod) {
|
||||||
|
case "GET": {
|
||||||
|
handlerDict = GETListeners;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "POST": {
|
||||||
|
handlerDict = POSTListeners;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
response = Encoding.UTF8.GetBytes($"Method not allowed: {context.Request.HttpMethod}");
|
||||||
|
context.Response.StatusCode = 405;
|
||||||
|
context.Response.ContentType = "text/plain";
|
||||||
|
context.Response.ContentLength64 = response.Length;
|
||||||
|
context.Response.OutputStream.Write(response, 0, response.Length);
|
||||||
|
context.Response.OutputStream.Close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!handlerDict.TryGetValue(uri, out var handler)) {
|
||||||
|
response = Encoding.UTF8.GetBytes($"No route defined for {uri}");
|
||||||
|
context.Response.StatusCode = 404;
|
||||||
|
context.Response.ContentType = "text/plain";
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
response = handler(context);
|
||||||
|
} catch(Exception e) {
|
||||||
|
response = Encoding.UTF8.GetBytes("Internal Server Error");
|
||||||
|
context.Response.StatusCode = 500;
|
||||||
|
context.Response.ContentType = "text/plain";
|
||||||
|
Console.Error.WriteLine(
|
||||||
|
$"[{DateTime.Now:u}] {ip} - {context.Request.HttpMethod} {context.Request.RawUrl} - Handler Failed: {e.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
});
|
context.Response.ContentLength64 = response.Length;
|
||||||
}
|
context.Response.OutputStream.Write(response, 0, response.Length);
|
||||||
}
|
context.Response.OutputStream.Close();
|
||||||
}
|
Console.WriteLine(
|
||||||
|
$"[{DateTime.Now:u}] {ip} - {context.Request.HttpMethod} {context.Request.RawUrl} - {context.Response.StatusCode}");
|
||||||
|
} catch(Exception e) {
|
||||||
|
Console.Error.WriteLine(
|
||||||
|
$"[{DateTime.Now:u}] {ip} - {context.Request.HttpMethod} {context.Request.RawUrl} - Exception: {e.Message}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,69 +6,52 @@ using System.Speech.Synthesis;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace SAPIServer
|
namespace SAPIServer {
|
||||||
{
|
class Program {
|
||||||
class Program
|
static void Main(string[] args) {
|
||||||
{
|
if(args.Length < 1 || !ushort.TryParse(args[0], out var port)) {
|
||||||
static void Main(string[] args)
|
Console.Error.WriteLine("Usage: SAPIServer.exe <port>");
|
||||||
{
|
Environment.Exit(1);
|
||||||
if (args.Length < 1 || !ushort.TryParse(args[0], out var port))
|
return;
|
||||||
{
|
}
|
||||||
Console.Error.WriteLine("Usage: SAPIServer.exe <port>");
|
var http = new HTTPServer();
|
||||||
Environment.Exit(1);
|
http.Get("/api/voices", ctx => {
|
||||||
return;
|
using(var synth = new SpeechSynthesizer()) {
|
||||||
}
|
ctx.Response.ContentType = "application/json";
|
||||||
var http = new HTTPServer();
|
return Encoding.UTF8.GetBytes(
|
||||||
http.Get("/api/voices", ctx =>
|
JsonConvert.SerializeObject(new VoicesResponse { voices = synth.GetInstalledVoices().Select(v => v.VoiceInfo.Name).ToArray() }));
|
||||||
{
|
}
|
||||||
using (var synth = new SpeechSynthesizer())
|
});
|
||||||
{
|
http.Post("/api/synthesize", ctx => {
|
||||||
ctx.Response.ContentType = "application/json";
|
SynthesizePayload body;
|
||||||
return Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(new VoicesResponse
|
try {
|
||||||
{
|
string bodyraw;
|
||||||
voices = synth.GetInstalledVoices().Select(v => v.VoiceInfo.Name).ToArray()
|
using(var reader = new StreamReader(ctx.Request.InputStream)) {
|
||||||
}));
|
bodyraw = reader.ReadToEnd();
|
||||||
}
|
}
|
||||||
});
|
body = JsonConvert.DeserializeObject<SynthesizePayload>(bodyraw);
|
||||||
http.Post("/api/synthesize", ctx =>
|
} catch(Exception) {
|
||||||
{
|
ctx.Response.StatusCode = 400;
|
||||||
SynthesizePayload body;
|
return Encoding.UTF8.GetBytes("Bad payload");
|
||||||
try
|
}
|
||||||
{
|
if(body == null || body.text == null || body.voice == null) {
|
||||||
string bodyraw;
|
ctx.Response.StatusCode = 400;
|
||||||
using (var reader = new StreamReader(ctx.Request.InputStream))
|
return Encoding.UTF8.GetBytes("Bad payload");
|
||||||
{
|
}
|
||||||
bodyraw = reader.ReadToEnd();
|
using(var ms = new MemoryStream()) using(var synth = new SpeechSynthesizer()) {
|
||||||
}
|
if(!synth.GetInstalledVoices().Any(v => v.VoiceInfo.Name == body.voice)) {
|
||||||
body = JsonConvert.DeserializeObject<SynthesizePayload>(bodyraw);
|
ctx.Response.StatusCode = 400;
|
||||||
}
|
return Encoding.UTF8.GetBytes("Voice not found");
|
||||||
catch (Exception e)
|
}
|
||||||
{
|
synth.SelectVoice(body.voice);
|
||||||
ctx.Response.StatusCode = 400;
|
synth.SetOutputToWaveStream(ms);
|
||||||
return Encoding.UTF8.GetBytes("Bad payload");
|
synth.Speak(body.text);
|
||||||
}
|
ctx.Response.ContentType = "audio/wav";
|
||||||
if (body == null || body.text == null || body.voice == null)
|
return ms.ToArray();
|
||||||
{
|
}
|
||||||
ctx.Response.StatusCode = 400;
|
});
|
||||||
return Encoding.UTF8.GetBytes("Bad payload");
|
Console.WriteLine($"[{DateTime.Now:u}] Starting HTTP server on port {port}");
|
||||||
}
|
http.Listen(port);
|
||||||
using (var ms = new MemoryStream())
|
}
|
||||||
using (var synth = new SpeechSynthesizer())
|
}
|
||||||
{
|
|
||||||
if (!synth.GetInstalledVoices().Any(v => v.VoiceInfo.Name == body.voice))
|
|
||||||
{
|
|
||||||
ctx.Response.StatusCode = 400;
|
|
||||||
return Encoding.UTF8.GetBytes("Voice not found");
|
|
||||||
}
|
|
||||||
synth.SelectVoice(body.voice);
|
|
||||||
synth.SetOutputToWaveStream(ms);
|
|
||||||
synth.Speak(body.text);
|
|
||||||
ctx.Response.ContentType = "audio/wav";
|
|
||||||
return ms.ToArray();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
Console.WriteLine($"[{ DateTime.Now:u}] Starting HTTP server on port {port}");
|
|
||||||
http.Listen(port);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
|
<PackageReference Include="Microsoft.Bcl.Async" Version="1.0.168" />
|
||||||
<Reference Include="System" />
|
<Reference Include="System" />
|
||||||
<Reference Include="System.Core" />
|
<Reference Include="System.Core" />
|
||||||
<Reference Include="System.Speech" />
|
<Reference Include="System.Speech" />
|
||||||
|
|
37
SAPIServer/SpeechDLL.cs
Normal file
37
SAPIServer/SpeechDLL.cs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace SAPIServer {
|
||||||
|
|
||||||
|
// Sync with C++ code.
|
||||||
|
public enum EngineType : int { ET_SAPI4, ET_SAPI5, ET_DECTALK }
|
||||||
|
|
||||||
|
// Speech2 DLL API.
|
||||||
|
internal class SpeechDLL {
|
||||||
|
[DllImport("speech2")]
|
||||||
|
public static extern IntPtr speech2_create_api(EngineType type);
|
||||||
|
|
||||||
|
|
||||||
|
[DllImport("speech2")]
|
||||||
|
public static extern void speech2_destroy_api(IntPtr pAPI);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Native binding of speech2 library from C++ to C#.
|
||||||
|
public class SpeechAPI : IDisposable {
|
||||||
|
private IntPtr handle = IntPtr.Zero;
|
||||||
|
|
||||||
|
public SpeechAPI(EngineType type) {
|
||||||
|
handle = SpeechDLL.speech2_create_api(type);
|
||||||
|
if(handle == IntPtr.Zero)
|
||||||
|
throw new InvalidOperationException("Failed to create speech API");
|
||||||
|
}
|
||||||
|
|
||||||
|
void IDisposable.Dispose() {
|
||||||
|
if(handle != IntPtr.Zero)
|
||||||
|
SpeechDLL.speech2_destroy_api(handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,8 +1,6 @@
|
||||||
namespace SAPIServer
|
namespace SAPIServer {
|
||||||
{
|
class SynthesizePayload {
|
||||||
class SynthesizePayload
|
public string voice { get; set; }
|
||||||
{
|
public string text { get; set; }
|
||||||
public string voice { get; set; }
|
}
|
||||||
public string text { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
namespace SAPIServer
|
namespace SAPIServer {
|
||||||
{
|
class VoicesResponse {
|
||||||
class VoicesResponse
|
public string[] voices { get; set; }
|
||||||
{
|
}
|
||||||
public string[] voices { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ OBJS = $(addprefix $(OBJDIR)/,$(notdir $(CXXSRCS:.cpp=.o)))
|
||||||
|
|
||||||
.PHONY: all dumpinfo clean matrix
|
.PHONY: all dumpinfo clean matrix
|
||||||
|
|
||||||
all: $(BINDIR)/$(NAME).exe
|
all: $(BINDIR)/$(NAME).dll
|
||||||
|
|
||||||
# dir rules
|
# dir rules
|
||||||
$(BINDIR)/:
|
$(BINDIR)/:
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# 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 -std=gnu17 -fpermissive -fno-pic -fno-pie -msse -Iinclude -Isrc -D_UCRT -D_WIN32_WINNT=0x0501
|
BASE_CCFLAGS = -MMD -fvisibility=hidden -std=gnu17 -fpermissive -fno-ident -msse -Iinclude -Isrc -D_UCRT -D_WIN32_WINNT=0x0501
|
||||||
BASE_CXXFLAGS = -MMD -std=c++20 -fpermissive -fno-pic -fno-pie -fno-rtti -msse -Iinclude -Isrc -Ithird_party -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_LDFLAGS = -mwindows -static -static-libgcc -lkernel32 -lshell32 -luser32 -luuid -lole32
|
BASE_LDFLAGS = -Wl,--subsystem=windows -fvisibility=hidden -shared -lkernel32 -lshell32 -luser32 -luuid -lole32
|
||||||
|
|
||||||
Release_Valid = yes
|
Release_Valid = yes
|
||||||
Release_CCFLAGS = -O3 -ffast-math -fomit-frame-pointer -DNDEBUG
|
Release_CCFLAGS = -O3 -ffast-math -fomit-frame-pointer -DNDEBUG
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
# TODO: Link DLL
|
$(BINDIR)/$(NAME).dll: $(BINDIR)/ $(OBJDIR)/ $(OBJS)
|
||||||
$(BINDIR)/$(NAME).exe: $(BINDIR)/ $(OBJDIR)/ $(OBJS)
|
|
||||||
echo -e "\e[92mLinking binary $@\e[0m"
|
echo -e "\e[92mLinking binary $@\e[0m"
|
||||||
$(CXX) $(OBJS) $(BASE_LDFLAGS) $($(CONFIG)_LDFLAGS) -o $@
|
$(CXX) $(OBJS) $(BASE_LDFLAGS) $($(CONFIG)_LDFLAGS) -o $@
|
||||||
|
|
||||||
|
|
|
@ -1,120 +1,40 @@
|
||||||
#include <stdio.h>
|
#include <windows.h>
|
||||||
#include <winscard.h>
|
|
||||||
|
|
||||||
#include <base/BThread.hpp>
|
|
||||||
#include <base/Mutex.hpp>
|
|
||||||
#include <base/Thread.hpp>
|
|
||||||
|
|
||||||
#include "speechapi.hpp"
|
#include "speechapi.hpp"
|
||||||
|
|
||||||
// args
|
#define SP2_EXPORT __declspec(dllexport)
|
||||||
// -v <voice> (voice to use)
|
|
||||||
// -s 4|5 (what SAPI version to use)
|
|
||||||
// -p 0..100 (pitch)
|
|
||||||
// -s 0..100
|
|
||||||
// [message]
|
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
enum class EngineType : int { ET_SAPI4, ET_SAPI5, ET_DECTALK };
|
||||||
#if 1
|
|
||||||
if(FAILED(CoInitialize(nullptr))) {
|
extern "C" {
|
||||||
printf("Couldn't initalize COM\n");
|
|
||||||
return 1;
|
SP2_EXPORT void* speech2_create_api(EngineType type) {
|
||||||
|
switch(type) {
|
||||||
|
case EngineType::ET_SAPI4: return static_cast<void*>(ISpeechAPI::CreateSapi4());
|
||||||
|
default: return nullptr;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
auto api = ISpeechAPI::CreateSapi4();
|
|
||||||
if(!api) {
|
SP2_EXPORT void speech2_destroy_api(void* engine) {
|
||||||
printf("Couldn't allocate memory for speech API\n");
|
if(engine)
|
||||||
return 1;
|
delete static_cast<ISpeechAPI*>(engine);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(auto hRes = api->Initialize(); FAILED(hRes)) {
|
}
|
||||||
printf("Couldn't initalize SAPI 4 (hr: %08x)\n", hRes);
|
|
||||||
return 1;
|
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
|
||||||
}
|
switch(fdwReason) {
|
||||||
|
case DLL_PROCESS_ATTACH: break;
|
||||||
#if 1
|
|
||||||
auto voices = api->GetVoices();
|
case DLL_THREAD_ATTACH: break;
|
||||||
printf("Available voices:\n");
|
|
||||||
for(auto& voice : voices) {
|
case DLL_THREAD_DETACH: break;
|
||||||
auto& guid = voice.guid;
|
|
||||||
|
case DLL_PROCESS_DETACH:
|
||||||
printf("%s (GUID {%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X})\n", voice.voiceName.c_str(), guid.Data1, guid.Data2, guid.Data3,
|
if(lpvReserved != nullptr) {
|
||||||
guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]);
|
break; // do not do cleanup if process termination scenario
|
||||||
}
|
}
|
||||||
#endif
|
break;
|
||||||
|
}
|
||||||
if(auto hRes = api->SelectVoice("Sam"); FAILED(hRes)) {
|
return TRUE;
|
||||||
printf("Test: Couldn't select Microsoft Sam\n");
|
|
||||||
return 1;
|
|
||||||
} else {
|
|
||||||
printf("Test: Selected Microsoft Sam\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
#if 1
|
|
||||||
if(auto hRes = api->SelectVoice("Mike"); FAILED(hRes)) {
|
|
||||||
printf("Test: Couldn't select Microsoft Mike\n");
|
|
||||||
return 1;
|
|
||||||
} else {
|
|
||||||
printf("Test: Seleced Microsoft Mike\n");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
printf("Test: Selected voices successfully\n");
|
|
||||||
|
|
||||||
printf("Test: Destroying voice\n");
|
|
||||||
delete api;
|
|
||||||
|
|
||||||
CoUninitialize();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// condvar/threading tests
|
|
||||||
#if 0
|
|
||||||
static auto cv = base::osdep::BCondVar_Create();
|
|
||||||
static auto PrintMutex = base::Mutex{};
|
|
||||||
static auto n = 0;
|
|
||||||
//auto n = 0;
|
|
||||||
|
|
||||||
Sleep(100);
|
|
||||||
|
|
||||||
base::Thread t([]() {
|
|
||||||
base::osdep::BCondVar_Wait(cv, [](void* ctx) {
|
|
||||||
base::LockGuard lk(PrintMutex);
|
|
||||||
printf("t: wait predicate called %d\n", *static_cast<int*>(ctx));
|
|
||||||
return *static_cast<int*>(ctx) == 4;
|
|
||||||
}, static_cast<void*>(&n));
|
|
||||||
|
|
||||||
{
|
|
||||||
base::LockGuard lk(PrintMutex);
|
|
||||||
printf("t: condvar exited wait!\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
base::Thread t2([]() {
|
|
||||||
base::osdep::BCondVar_Wait(cv, [](void* ctx) {
|
|
||||||
base::LockGuard lk(PrintMutex);
|
|
||||||
printf("t2: wait predicate called %d\n", *static_cast<int*>(ctx));
|
|
||||||
return *static_cast<int*>(ctx) == 4;
|
|
||||||
}, static_cast<void*>(&n));
|
|
||||||
|
|
||||||
{
|
|
||||||
base::LockGuard lk(PrintMutex);
|
|
||||||
printf("t2: condvar exited wait!\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
for(auto i = 0; i < 4; ++i) {
|
|
||||||
base::osdep::BCondVar_SignalOne(cv);
|
|
||||||
n++;
|
|
||||||
Sleep(100);
|
|
||||||
}
|
|
||||||
|
|
||||||
t2.Join();
|
|
||||||
t.Join();
|
|
||||||
base::osdep::BCondVar_Destroy(cv);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue