Compare commits

..

16 commits

Author SHA1 Message Date
Lily Tsuru 3c585b3a04 experiment with http stuff
seems to work!
2024-07-21 04:27:29 -04:00
Lily Tsuru 0379b729eb update readme 2024-07-20 07:14:26 -04:00
Lily Tsuru 183e11dc0d don't stub threadsafe statics
we are using winpthreads now anyways, so we can unstub the existing code.
2024-07-20 06:47:28 -04:00
Lily Tsuru ddd3483620 add boost asio to speech2
currently the main program is just some stackful coro example stuff that i used just to test

i'll add beast too in a sec but just. hang on
2024-07-20 06:37:09 -04:00
Lily Tsuru b4a6860e75 remove dll files
(unused)
2024-07-19 22:06:13 -04:00
Lily Tsuru 326d3e5161 replace build system with cmake
no more makefile :)
2024-07-19 22:01:05 -04:00
Lily Tsuru 9a9d0043f2 setup scaffold for cmake build
don't care for make and it makes importing other third party code harder
2024-07-19 19:34:25 -04:00
Lily Tsuru 510d3547a2 port dll to clang-cl
really need to figure out why the stupid thing isn't working properly
2024-07-19 07:45:18 -04:00
Lily Tsuru cc59c0c6db Switch to clang-cl
It's less painful than mingw.
I'll next get the DLL going like this, though it'll be pretty easy.
2024-07-19 05:10:10 -04:00
Lily Tsuru d95d305734 more binding fuckery
i'm going to switch the build to clang-cl. To test, I've added EXE build support back to the speech2 C++ buildsystem and I'm going to replace the current mingw command lines for clang-cl in the next commit.

Because we only need XP SP3 compatibility, we won't need any compat library stuff, but I may still alter his libc++ fork if MSVC CRT ends up blowing too many chunks.
2024-07-19 03:59:19 -04:00
Lily Tsuru 126566c3cf voiceinfo list work
(crashes for some reason)
2024-07-18 06:30:57 -04:00
Lily Tsuru 3b127a0b08 remove vs(non dotnet sdk) csproj
not needed, since dotnet sdk tooling can target .net 4.0 (incl windows x86 to boot! pretty cool.).

afaik the project can still be opened by newer vs versions anyways so /shrug
2024-07-18 05:06:37 -04:00
Lily Tsuru 13ce046d3f actually use bindings
about time
2024-07-18 05:05:35 -04:00
Lily Tsuru 1ebd3285f9 remove obsolete source files from speech2 2024-07-18 03:56:02 -04:00
Lily Tsuru bd048875c8 make speech2 a dll + start binding it 2024-07-18 03:54:12 -04:00
Lily Tsuru 20f59c5663 add speech2 stuff 2024-07-17 21:40:14 -04:00
136 changed files with 56856 additions and 612 deletions

44
.clang-format Executable file
View file

@ -0,0 +1,44 @@
BasedOnStyle: Google
# force T* or T&
DerivePointerAlignment: false
PointerAlignment: Left
TabWidth: 4
IndentWidth: 4
UseTab: Always
IndentPPDirectives: BeforeHash
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: false
AllowShortFunctionsOnASingleLine: InlineOnly
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: true
BinPackArguments: true
BinPackParameters: true
BreakConstructorInitializers: BeforeColon
BreakStringLiterals: false
ColumnLimit: 150
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ContinuationIndentWidth: 0
# turning this on causes major issues with initializer lists
Cpp11BracedListStyle: false
SpaceBeforeCpp11BracedList: true
FixNamespaceComments: true
NamespaceIndentation: All
ReflowComments: true
SortIncludes: CaseInsensitive
SortUsingDeclarations: true
SpacesInSquareBrackets: false
SpaceBeforeParens: Never
SpacesBeforeTrailingComments: 1

11
.editorconfig Normal file
View file

@ -0,0 +1,11 @@
root = true
[*]
end_of_line = lf
insert_final_newline = true
indent_style = tab
indent_size = 4
# specifically for YAML
[{yml, yaml}]
indent_style = space

370
.gitignore vendored
View file

@ -1,363 +1,11 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# ccls/clangd
.cache/
**/bin/
**/obj/
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# on your own machine, please.
/speech2/build
/speech2/build-debug
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Oo]ut/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
# cmake tools is viciously unaware of subdirectories
/build

201
.gitmodules vendored Normal file
View file

@ -0,0 +1,201 @@
[submodule "speech2/third_party/boost/algorithm"]
path = speech2/third_party/boost/algorithm
url = https://github.com/boostorg/algorithm.git
[submodule "speech2/third_party/boost/align"]
path = speech2/third_party/boost/align
url = https://github.com/boostorg/align.git
[submodule "speech2/third_party/boost/array"]
path = speech2/third_party/boost/array
url = https://github.com/boostorg/array.git
[submodule "speech2/third_party/boost/assert"]
path = speech2/third_party/boost/assert
url = https://github.com/boostorg/assert.git
[submodule "speech2/third_party/boost/atomic"]
path = speech2/third_party/boost/atomic
url = https://github.com/boostorg/atomic.git
[submodule "speech2/third_party/boost/bind"]
path = speech2/third_party/boost/bind
url = https://github.com/boostorg/bind.git
[submodule "speech2/third_party/boost/charconv"]
path = speech2/third_party/boost/charconv
url = https://github.com/boostorg/charconv.git
[submodule "speech2/third_party/boost/chrono"]
path = speech2/third_party/boost/chrono
url = https://github.com/boostorg/chrono.git
[submodule "speech2/third_party/boost/circular_buffer"]
path = speech2/third_party/boost/circular_buffer
url = https://github.com/boostorg/circular_buffer.git
[submodule "speech2/third_party/boost/concept_check"]
path = speech2/third_party/boost/concept_check
url = https://github.com/boostorg/concept_check.git
[submodule "speech2/third_party/boost/config"]
path = speech2/third_party/boost/config
url = https://github.com/boostorg/config.git
[submodule "speech2/third_party/boost/container"]
path = speech2/third_party/boost/container
url = https://github.com/boostorg/container.git
[submodule "speech2/third_party/boost/container_hash"]
path = speech2/third_party/boost/container_hash
url = https://github.com/boostorg/container_hash.git
[submodule "speech2/third_party/boost/context"]
path = speech2/third_party/boost/context
url = https://github.com/boostorg/context.git
[submodule "speech2/third_party/boost/conversion"]
path = speech2/third_party/boost/conversion
url = https://github.com/boostorg/conversion.git
[submodule "speech2/third_party/boost/core"]
path = speech2/third_party/boost/core
url = https://github.com/boostorg/core.git
[submodule "speech2/third_party/boost/coroutine"]
path = speech2/third_party/boost/coroutine
url = https://github.com/boostorg/coroutine.git
[submodule "speech2/third_party/boost/date_time"]
path = speech2/third_party/boost/date_time
url = https://github.com/boostorg/date_time.git
[submodule "speech2/third_party/boost/describe"]
path = speech2/third_party/boost/describe
url = https://github.com/boostorg/describe.git
[submodule "speech2/third_party/boost/detail"]
path = speech2/third_party/boost/detail
url = https://github.com/boostorg/detail.git
[submodule "speech2/third_party/boost/endian"]
path = speech2/third_party/boost/endian
url = https://github.com/boostorg/endian.git
[submodule "speech2/third_party/boost/exception"]
path = speech2/third_party/boost/exception
url = https://github.com/boostorg/exception.git
[submodule "speech2/third_party/boost/filesystem"]
path = speech2/third_party/boost/filesystem
url = https://github.com/boostorg/filesystem.git
[submodule "speech2/third_party/boost/function"]
path = speech2/third_party/boost/function
url = https://github.com/boostorg/function.git
[submodule "speech2/third_party/boost/functional"]
path = speech2/third_party/boost/functional
url = https://github.com/boostorg/functional.git
[submodule "speech2/third_party/boost/function_types"]
path = speech2/third_party/boost/function_types
url = https://github.com/boostorg/function_types.git
[submodule "speech2/third_party/boost/fusion"]
path = speech2/third_party/boost/fusion
url = https://github.com/boostorg/fusion.git
[submodule "speech2/third_party/boost/integer"]
path = speech2/third_party/boost/integer
url = https://github.com/boostorg/integer.git
[submodule "speech2/third_party/boost/intrusive"]
path = speech2/third_party/boost/intrusive
url = https://github.com/boostorg/intrusive.git
[submodule "speech2/third_party/boost/io"]
path = speech2/third_party/boost/io
url = https://github.com/boostorg/io.git
[submodule "speech2/third_party/boost/iterator"]
path = speech2/third_party/boost/iterator
url = https://github.com/boostorg/iterator.git
[submodule "speech2/third_party/boost/json"]
path = speech2/third_party/boost/json
url = https://github.com/boostorg/json.git
[submodule "speech2/third_party/boost/leaf"]
path = speech2/third_party/boost/leaf
url = https://github.com/boostorg/leaf.git
[submodule "speech2/third_party/boost/lexical_cast"]
path = speech2/third_party/boost/lexical_cast
url = https://github.com/boostorg/lexical_cast.git
[submodule "speech2/third_party/boost/lockfree"]
path = speech2/third_party/boost/lockfree
url = https://github.com/boostorg/lockfree.git
[submodule "speech2/third_party/boost/logic"]
path = speech2/third_party/boost/logic
url = https://github.com/boostorg/logic.git
[submodule "speech2/third_party/boost/move"]
path = speech2/third_party/boost/move
url = https://github.com/boostorg/move.git
[submodule "speech2/third_party/boost/mp11"]
path = speech2/third_party/boost/mp11
url = https://github.com/boostorg/mp11.git
[submodule "speech2/third_party/boost/mpl"]
path = speech2/third_party/boost/mpl
url = https://github.com/boostorg/mpl.git
[submodule "speech2/third_party/boost/numeric_conversion"]
path = speech2/third_party/boost/numeric_conversion
url = https://github.com/boostorg/numeric_conversion.git
[submodule "speech2/third_party/boost/optional"]
path = speech2/third_party/boost/optional
url = https://github.com/boostorg/optional.git
[submodule "speech2/third_party/boost/parameter"]
path = speech2/third_party/boost/parameter
url = https://github.com/boostorg/parameter.git
[submodule "speech2/third_party/boost/pool"]
path = speech2/third_party/boost/pool
url = https://github.com/boostorg/pool.git
[submodule "speech2/third_party/boost/predef"]
path = speech2/third_party/boost/predef
url = https://github.com/boostorg/predef.git
[submodule "speech2/third_party/boost/preprocessor"]
path = speech2/third_party/boost/preprocessor
url = https://github.com/boostorg/preprocessor.git
[submodule "speech2/third_party/boost/range"]
path = speech2/third_party/boost/range
url = https://github.com/boostorg/range.git
[submodule "speech2/third_party/boost/ratio"]
path = speech2/third_party/boost/ratio
url = https://github.com/boostorg/ratio.git
[submodule "speech2/third_party/boost/rational"]
path = speech2/third_party/boost/rational
url = https://github.com/boostorg/rational.git
[submodule "speech2/third_party/boost/regex"]
path = speech2/third_party/boost/regex
url = https://github.com/boostorg/regex.git
[submodule "speech2/third_party/boost/scope"]
path = speech2/third_party/boost/scope
url = https://github.com/boostorg/scope.git
[submodule "speech2/third_party/boost/smart_ptr"]
path = speech2/third_party/boost/smart_ptr
url = https://github.com/boostorg/smart_ptr.git
[submodule "speech2/third_party/boost/static_assert"]
path = speech2/third_party/boost/static_assert
url = https://github.com/boostorg/static_assert.git
[submodule "speech2/third_party/boost/static_string"]
path = speech2/third_party/boost/static_string
url = https://github.com/boostorg/static_string.git
[submodule "speech2/third_party/boost/system"]
path = speech2/third_party/boost/system
url = https://github.com/boostorg/system.git
[submodule "speech2/third_party/boost/throw_exception"]
path = speech2/third_party/boost/throw_exception
url = https://github.com/boostorg/throw_exception.git
[submodule "speech2/third_party/boost/tokenizer"]
path = speech2/third_party/boost/tokenizer
url = https://github.com/boostorg/tokenizer.git
[submodule "speech2/third_party/boost/tuple"]
path = speech2/third_party/boost/tuple
url = https://github.com/boostorg/tuple.git
[submodule "speech2/third_party/boost/type_index"]
path = speech2/third_party/boost/type_index
url = https://github.com/boostorg/type_index.git
[submodule "speech2/third_party/boost/typeof"]
path = speech2/third_party/boost/typeof
url = https://github.com/boostorg/typeof.git
[submodule "speech2/third_party/boost/type_traits"]
path = speech2/third_party/boost/type_traits
url = https://github.com/boostorg/type_traits.git
[submodule "speech2/third_party/boost/unordered"]
path = speech2/third_party/boost/unordered
url = https://github.com/boostorg/unordered.git
[submodule "speech2/third_party/boost/url"]
path = speech2/third_party/boost/url
url = https://github.com/boostorg/url.git
[submodule "speech2/third_party/boost/utility"]
path = speech2/third_party/boost/utility
url = https://github.com/boostorg/utility.git
[submodule "speech2/third_party/boost/variant2"]
path = speech2/third_party/boost/variant2
url = https://github.com/boostorg/variant2.git
[submodule "speech2/third_party/boost/winapi"]
path = speech2/third_party/boost/winapi
url = https://github.com/boostorg/winapi.git
[submodule "speech2/third_party/boost/asio"]
path = speech2/third_party/boost/asio
url = https://github.com/boostorg/asio.git
[submodule "speech2/third_party/boost/beast"]
path = speech2/third_party/boost/beast
url = https://github.com/boostorg/beast.git

10
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,10 @@
{
"cmake.sourceDirectory": "${workspaceFolder}/speech2",
"cmake.configureArgs": [
"--toolchain cmake/clangcl-winxp.cmake"
],
"cmake.configureEnvironment": {
"VCDIR": "${env:HOME}/vs2022"
},
"cmake.ignoreCMakeListsMissing": true,
}

11
Justfile Normal file
View file

@ -0,0 +1,11 @@
build:
cd speech2; cmake --toolchain cmake/clangcl-winxp.cmake -GNinja -Bbuild; cd ..
cd speech2; cd build; ninja; cd ..; cd ..;
clean:
rm -rf SAPIServer/bin SAPIServer/obj
make -C speech2 clean
format:
cd SAPIServer; for f in *.cs; do clang-format -i $f; done; cd ..;

View file

@ -2,6 +2,17 @@
Simple HTTP frontend API for Microsoft Speech API
## Building
Requirements
- .NET SDK
- VS2022 lib pack (TODO: link)
- LLVM toolchain
You'll also need to chattr +F (or mount the whole thing with `ciopfs` and rename the headers to lowercase, if not on ext4 or you don't want to tune2fs) the windows sdk header directories so the build works.
`just` should do the trick.
## Running
```
@ -27,4 +38,4 @@ Content-Type: application/json
{"text":"Lorem ipsum doler sit amet...","voice":"Microsoft Sam"}
```
Returns synthesized TTS audio as a wave-form file.
Returns synthesized TTS audio as a wave-form file.

View file

@ -3,112 +3,103 @@ using System.Collections.Generic;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace SAPIServer
{
/// <summary>
/// Simple routing HTTP server for .NET Framework
/// </summary>
class HTTPServer
{
private bool listening = false;
private HttpListener listener;
private Dictionary<string, Func<HttpListenerContext, byte[]>> GETListeners;
private Dictionary<string, Func<HttpListenerContext, byte[]>> POSTListeners;
namespace SAPIServer {
/// <summary>
/// Simple routing HTTP server for .NET Framework
/// </summary>
class HTTPServer {
private bool listening = false;
private HttpListener listener;
private Dictionary<string, Func<HttpListenerContext, byte[]>> GETListeners;
private Dictionary<string, Func<HttpListenerContext, byte[]>> POSTListeners;
public HTTPServer()
{
listener = new HttpListener();
this.GETListeners = new Dictionary<string, Func<HttpListenerContext, byte[]>>();
this.POSTListeners = new Dictionary<string, Func<HttpListenerContext, byte[]>>();
}
public HTTPServer() {
listener = new HttpListener();
this.GETListeners = new Dictionary<string, Func<HttpListenerContext, byte[]>>();
this.POSTListeners = new Dictionary<string, Func<HttpListenerContext, byte[]>>();
}
public void Get(string uri, Func<HttpListenerContext, byte[]> handler)
{
if (GETListeners.ContainsKey(uri)) throw new InvalidOperationException("A handler by that URI already exists.");
if (!uri.StartsWith("/")) throw new ArgumentException("URIs must start with /");
this.GETListeners.Add(uri, handler);
}
public void Get(string uri, Func<HttpListenerContext, byte[]> handler) {
if(GETListeners.ContainsKey(uri))
throw new InvalidOperationException("A handler by that URI already exists.");
if(!uri.StartsWith("/"))
throw new ArgumentException("URIs must start with /");
this.GETListeners.Add(uri, handler);
}
public void Post(string uri, Func<HttpListenerContext, byte[]> handler)
{
if (POSTListeners.ContainsKey(uri)) throw new InvalidOperationException("A handler by that URI already exists.");
if (!uri.StartsWith("/")) throw new ArgumentException("URIs must start with /");
this.POSTListeners.Add(uri, handler);
}
public void Post(string uri, Func<HttpListenerContext, byte[]> handler) {
if(POSTListeners.ContainsKey(uri))
throw new InvalidOperationException("A handler by that URI already exists.");
if(!uri.StartsWith("/"))
throw new ArgumentException("URIs must start with /");
this.POSTListeners.Add(uri, handler);
}
public void Listen(ushort port)
{
if (listening) throw new InvalidOperationException("This HTTPServer is already listening.");
listening = true;
listener.Prefixes.Add(string.Format("http://*:{0}/", port));
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}");
}
}
public void Listen(ushort port) {
if(listening)
throw new InvalidOperationException("This HTTPServer is already listening.");
listening = true;
listener.Prefixes.Add(string.Format("http://*:{0}/", port));
listener.Start();
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}");
}
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;
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}");
}
});
}
}
}
}

View file

@ -6,69 +6,80 @@ using System.Speech.Synthesis;
using System.Text;
using Newtonsoft.Json;
namespace SAPIServer
{
class Program
{
static void Main(string[] args)
{
if (args.Length < 1 || !ushort.TryParse(args[0], out var port))
{
Console.Error.WriteLine("Usage: SAPIServer.exe <port>");
Environment.Exit(1);
return;
}
var http = new HTTPServer();
http.Get("/api/voices", ctx =>
{
using (var synth = new SpeechSynthesizer())
{
ctx.Response.ContentType = "application/json";
return Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(new VoicesResponse
{
voices = synth.GetInstalledVoices().Select(v => v.VoiceInfo.Name).ToArray()
}));
}
});
http.Post("/api/synthesize", ctx =>
{
SynthesizePayload body;
try
{
string bodyraw;
using (var reader = new StreamReader(ctx.Request.InputStream))
{
bodyraw = reader.ReadToEnd();
}
body = JsonConvert.DeserializeObject<SynthesizePayload>(bodyraw);
}
catch (Exception e)
{
ctx.Response.StatusCode = 400;
return Encoding.UTF8.GetBytes("Bad payload");
}
if (body == null || body.text == null || body.voice == null)
{
ctx.Response.StatusCode = 400;
return Encoding.UTF8.GetBytes("Bad payload");
}
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);
}
}
namespace SAPIServer {
class SpeechServer {
private Dictionary<string, SpeechAPI> apis = new();
private HTTPServer httpServer = new();
public SpeechServer() {
// Test out creating a speech2 api object
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 => {
/*
using(var synth = new SpeechSynthesizer()) {
ctx.Response.ContentType = "application/json";
return Encoding.UTF8.GetBytes(
JsonConvert.SerializeObject(new VoicesResponse { voices = synth.GetInstalledVoices().Select(v => v.VoiceInfo.Name).ToArray() }));
}*/
return new byte[]{0};
});
httpServer.Post("/api/synthesize", ctx => {
SynthesizePayload body;
try {
string bodyraw;
using(var reader = new StreamReader(ctx.Request.InputStream)) {
bodyraw = reader.ReadToEnd();
}
body = JsonConvert.DeserializeObject<SynthesizePayload>(bodyraw);
} catch(Exception) {
ctx.Response.StatusCode = 400;
return Encoding.UTF8.GetBytes("Bad payload");
}
if(body == null || body.text == null || body.voice == null) {
ctx.Response.StatusCode = 400;
return Encoding.UTF8.GetBytes("Bad payload");
}
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();
}
}
});
}
public void Start(ushort port) {
Console.WriteLine($"[{DateTime.Now:u}] Starting HTTP server on port {port}");
httpServer.Listen(port);
}
}
class Program {
static void Main(string[] args) {
if(args.Length < 1 || !ushort.TryParse(args[0], out var port)) {
Console.Error.WriteLine("Usage: SAPIServer.exe <port>");
Environment.Exit(1);
return;
}
var server = new SpeechServer();
server.Start(port);
}
}
}

View file

@ -1,58 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{BF824074-4C4E-4DE1-8DCA-F022682B00E1}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>SAPIServer</RootNamespace>
<AssemblyName>SAPIServer</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
<PublishUrl>publish\</PublishUrl>
<Install>true</Install>
<InstallFrom>Disk</InstallFrom>
<UpdateEnabled>false</UpdateEnabled>
<UpdateMode>Foreground</UpdateMode>
<UpdateInterval>7</UpdateInterval>
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
<UpdatePeriodically>false</UpdatePeriodically>
<UpdateRequired>false</UpdateRequired>
<MapFileExtensions>true</MapFileExtensions>
<ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
<IsWebBootstrapper>false</IsWebBootstrapper>
<UseApplicationTrust>false</UseApplicationTrust>
<BootstrapperEnabled>true</BootstrapperEnabled>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<!-- Older VS generated one for us; we're just using that one.
Unless we wanna use the generated one?? -->
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<OutputType>Exe</OutputType>
<TargetFrameworks>net40</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup>
<ApplicationManifest>app.manifest</ApplicationManifest>
<RuntimeIdentifier>windows-x86</RuntimeIdentifier>
<!-- N.B: This is only to gain support for some compiler-supported nicities, like new(). -->
<LangVersion>9.0</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.13.0.3\lib\net40\Newtonsoft.Json.dll</HintPath>
</Reference>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<!--<PackageReference Include="Microsoft.Bcl.Async" Version="1.0.168" />-->
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Speech" />
@ -62,23 +27,5 @@
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="HTTPServer.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SynthesizePayload.cs" />
<Compile Include="VoicesResponse.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.manifest" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
<Visible>False</Visible>
<ProductName>.NET Framework 3.5 SP1</ProductName>
<Install>false</Install>
</BootstrapperPackage>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
</Project>

85
SAPIServer/SpeechDLL.cs Normal file
View file

@ -0,0 +1,85 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Security;
namespace SAPIServer {
// Sync with C++ code.
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.
internal class SpeechDLL {
private const string Speech2DLL = "speech2.dll";
[DllImport(Speech2DLL, CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr speech2_create_api(EngineType type);
[DllImport(Speech2DLL, CallingConvention = CallingConvention.StdCall)]
public static extern void speech2_destroy_api(IntPtr pAPI);
[DllImport(Speech2DLL, CallingConvention = CallingConvention.StdCall)]
public static extern int speech2_api_get_voiceinfo_count(IntPtr pAPI);
[DllImport(Speech2DLL, CallingConvention = CallingConvention.StdCall)]
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
// we can use the same code for everything. Cool, huh?
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");
}
public List<VoiceDef> GetVoices() {
Console.WriteLine("SpeechAPI.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() {
if(handle != IntPtr.Zero)
SpeechDLL.speech2_destroy_api(handle);
}
}
}

View file

@ -1,8 +1,6 @@
namespace SAPIServer
{
class SynthesizePayload
{
public string voice { get; set; }
public string text { get; set; }
}
namespace SAPIServer {
class SynthesizePayload {
public string voice { get; set; }
public string text { get; set; }
}
}

View file

@ -1,7 +1,5 @@
namespace SAPIServer
{
class VoicesResponse
{
public string[] voices { get; set; }
}
namespace SAPIServer {
class VoicesResponse {
public string[] voices { get; set; }
}
}

20
speech2/CMakeLists.txt Normal file
View file

@ -0,0 +1,20 @@
cmake_minimum_required(VERSION 3.15)
project(speech2)
xp_init()
enable_language(ASM)
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")
include(Policies)
include(ProjectFuncs)
add_subdirectory(third_party/boost)
add_subdirectory(src)

3
speech2/README.md Normal file
View file

@ -0,0 +1,3 @@
# speech2
speech dll thing

View file

@ -0,0 +1,22 @@
# CMake policy configuration
# Macro to enable new CMake policy.
# Makes this file a *LOT* shorter.
macro (_new_cmake_policy policy)
if(POLICY ${policy})
#message(STATUS "Enabling new policy ${policy}")
cmake_policy(SET ${policy} NEW)
endif()
endmacro()
_new_cmake_policy(CMP0026) # CMake 3.0: Disallow use of the LOCATION property for build targets.
_new_cmake_policy(CMP0042) # CMake 3.0+ (2.8.12): MacOS "@rpath" in target's install name
_new_cmake_policy(CMP0046) # warn about non-existent dependencies
_new_cmake_policy(CMP0048) # CMake 3.0+: project() command now maintains VERSION
_new_cmake_policy(CMP0054) # CMake 3.1: Only interpret if() arguments as variables or keywords when unquoted.
_new_cmake_policy(CMP0056) # try_compile() linker flags
_new_cmake_policy(CMP0066) # CMake 3.7: try_compile(): use per-config flags, like CMAKE_CXX_FLAGS_RELEASE
_new_cmake_policy(CMP0067) # CMake 3.8: try_compile(): honor language standard variables (like C++11)
_new_cmake_policy(CMP0068) # CMake 3.9+: `RPATH` settings on macOS do not affect `install_name`.
_new_cmake_policy(CMP0075) # CMake 3.12+: Include file check macros honor `CMAKE_REQUIRED_LIBRARIES`
_new_cmake_policy(CMP0077) # CMake 3.13+: option() honors normal variables.

View file

@ -0,0 +1,14 @@
function(speech2_target target)
target_compile_definitions(${target} PRIVATE "$<$<CONFIG:DEBUG>:SPEECH2_DEBUG>")
target_compile_features(${target} PRIVATE cxx_std_20)
target_include_directories(${target} PRIVATE ${PROJECT_SOURCE_DIR}/src ${PROJECT_SOURCE_DIR}/third_party ${CMAKE_CURRENT_BINARY_DIR})
# use the static multithreaded C library
set_property(TARGET ${target} PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
# TODO option for this.
target_link_options(${target} PRIVATE
-Wl,/safeseh:no
-Xlinker /subsystem:console,${CMAKE_SYSTEM_VERSION}
)
endfunction()

View file

@ -0,0 +1,78 @@
# Windows XP
set(CMAKE_SYSTEM_NAME Windows)
set(CMAKE_SYSTEM_VERSION 5.1)
set(CMAKE_SYSTEM_PROCESSOR x86)
set(TARGET_VERSION_MAJOR 5)
set(TARGET_VERSION_MINOR)
set(_MSVC_TRIPLET "i686-pc-windows-msvc")
# CXXRTDIR = $(VCDIR)/crt
# UCRTDIR = $(VCDIR)/ucrt
# PSDKDIR = $(VCDIR)/winsdk
if("$ENV{VCDIR}" STREQUAL "")
message(FATAL_ERROR "Please set VCDIR in your environment to an appropiate path.")
endif()
set(_CRTDIR "$ENV{VCDIR}/crt")
#-isystem ${_CRTDIR}/include
set(_UCRTDIR "$ENV{VCDIR}/ucrt")
set(_PSDKDIR "$ENV{VCDIR}/winsdk")
set(_CLANG_BASEFLAGS "-target ${_MSVC_TRIPLET} -fms-extensions -fms-compatibility -fms-compatibility-version=19 -isystem $ENV{VCDIR}/libcxx/include/c++ -isystem ${_UCRTDIR}/include -isystem $ENV{VCDIR}/libcxx/include -isystem ${_PSDKDIR}/include/shared -isystem ${_PSDKDIR}/include/um ")
set(_CLANG_ARCHFLAGS "-march=pentium4 -D_WIN32_WINNT=0x0501")
set(CMAKE_C_COMPILER "clang" CACHE FILEPATH "")
set(CMAKE_CXX_COMPILER "clang++" CACHE FILEPATH "")
set(CMAKE_LINKER "lld-link" CACHE FILEPATH "")
set(CMAKE_ASM_FLAGS_INIT "${_CLANG_BASEFLAGS} ${_CLANG_ARCHFLAGS}")
set(CMAKE_C_FLAGS_INIT "${_CLANG_BASEFLAGS} ${_CLANG_ARCHFLAGS}")
set(CMAKE_C_FLAGS_RELEASE_INIT "${CMAKE_C_FLAGS_INIT} -fomit-frame-pointer")
set(CMAKE_CXX_FLAGS_INIT "${CMAKE_C_FLAGS_INIT}")
set(CMAKE_CXX_FLAGS_RELEASE_INIT "${CMAKE_C_FLAGS_RELEASE_INIT} ${CMAKE_CXX_FLAGS_INIT}")
# Set base linker library paths
foreach(type EXE MODULE SHARED)
# -Xlinker /nodefaultlib:libcpmt -Xlinker /nodefaultlib:libcpmtd
set(CMAKE_${type}_LINKER_FLAGS "-Xlinker /libpath:${_CRTDIR}/lib/x86 -Xlinker /libpath:${_UCRTDIR}/lib -Xlinker /libpath:${_PSDKDIR}/lib")
endforeach()
# Remove fluff libraries; projects should specify them
set(CMAKE_C_STANDARD_LIBRARIES "" CACHE STRING "" FORCE)
if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
set(CMAKE_CXX_STANDARD_LIBRARIES "-Xlinker /libpath:$ENV{VCDIR}/libcxx/lib -Xlinker libcpmtd.lib -Xlinker libc++d.lib -Xlinker winpthreadsd.lib" CACHE STRING "" FORCE)
else()
set(CMAKE_CXX_STANDARD_LIBRARIES "-Xlinker /libpath:$ENV{VCDIR}/libcxx/lib -Xlinker libcpmt.lib -Xlinker libc++.lib -Xlinker winpthreads.lib" CACHE STRING "" FORCE)
endif()
# Run this once after you call project() to replace broken
macro(xp_init)
foreach(lang C CXX)
# have to patch the link commands to replace /MANIFEST:EMBED with /MANIFEST:NO
# because ....
set(CMAKE_${lang}_CREATE_SHARED_LIBRARY
"<CMAKE_${lang}_COMPILER> -nostartfiles -nostdlib <CMAKE_SHARED_LIBRARY_${lang}_FLAGS> <LANGUAGE_COMPILE_FLAGS> <LINK_FLAGS> <CMAKE_SHARED_LIBRARY_CREATE_${lang}_FLAGS> -o <TARGET> ${CMAKE_GNULD_IMAGE_VERSION} -Xlinker /MANIFEST:EMBED -Xlinker /implib:<TARGET_IMPLIB> -Xlinker /pdb:<TARGET_PDB> -Xlinker /version:${CMAKE_SYSTEM_VERSION} <OBJECTS> <LINK_LIBRARIES> <MANIFESTS>" CACHE STRING "" FORCE)
set(CMAKE_${lang}_CREATE_SHARED_MODULE ${CMAKE_${lang}_CREATE_SHARED_LIBRARY} CACHE STRING "" FORCE)
set(CMAKE_${lang}_LINK_EXECUTABLE
"<CMAKE_${lang}_COMPILER> -nostartfiles -nostdlib <FLAGS> <CMAKE_${lang}_LINK_FLAGS> <LINK_FLAGS> <OBJECTS> -o <TARGET> -Xlinker /MANIFEST:NO -Xlinker /implib:<TARGET_IMPLIB> -Xlinker /pdb:<TARGET_PDB> -Xlinker /version:${CMAKE_SYSTEM_VERSION} ${CMAKE_GNULD_IMAGE_VERSION} <LINK_LIBRARIES> <MANIFESTS>" CACHE STRING "" FORCE)
# also patch these so we can set the subsytem version
set(CMAKE_${lang}_CREATE_WIN32_EXE "-Xlinker /subsystem:windows,${CMAKE_SYSTEM_VERSION}" CACHE STRING "" FORCE)
set(CMAKE_${lang}_CREATE_CONSOLE_EXE "-Xlinker /subsystem:console,${CMAKE_SYSTEM_VERSION}" CACHE STRING "" FORCE)
endforeach()
endmacro()
# Use the static multithreaded C library
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
# dummy
set(CMAKE_ASM_MASM_COMPILE_OBJECT "${CMAKE_ASM_COMPILE_OBJECT}")
xp_init()

View file

@ -0,0 +1,39 @@
add_subdirectory(base)
add_subdirectory(impl)
add_subdirectory(sapi4)
add_executable(sapiserver
main.cpp
winxp_compat_fwd.S
winxp_compat.cpp
winxp_compat_threadsafe_static.c
)
speech2_target(sapiserver)
target_compile_definitions(sapiserver PRIVATE
# Need to force this on, since I think clang's msvc compatibility
# is deciding to set a wrong __cplusplus (like MSVC, so it's not *exactly* clang's fault).
# The best way to fix it would probably involve using clang-cl frontend and passing the option (I think.)
# (nevermind, it's just broken.)
-DBOOST_ASIO_HAS_STD_INVOKE_RESULT=1
# Disable the "helpful" auto-link Boost.Config tries to do. CMake already has a functional
# dependency graph based on our input, so we don't need it.
-DBOOST_ALL_NO_LIB=1
)
target_link_libraries(sapiserver PRIVATE
#libc++
# subprojects
speech2::base
speech2::impl
speech2::api_sapi4
# SDK libraries
uuid.lib
ole32.lib
)

View file

@ -0,0 +1,14 @@
add_library(speech2_base STATIC
Thread.cpp
# Logging system
Logger.cpp
Logger_priv.cpp
StdoutSink.cpp
)
speech2_target(speech2_base)
add_library(speech2::base ALIAS speech2_base)

View file

@ -0,0 +1,41 @@
#include "Logger.hpp"
#include <base/Logger_priv.hpp>
#include <base/Thread.hpp>
namespace base {
inline auto& GlobalState() {
return logger_impl::LoggerGlobalState::The();
}
void LoggerAttachSink(LoggerSink& sink) {
GlobalState().AttachSink(sink);
}
MessageSeverity GetLogLevel() {
return GlobalState().GetLogLevel();
}
void SetLogLevel(MessageSeverity newLevel) {
GlobalState().SetLogLevel(newLevel);
}
Logger& Logger::Get(std::string_view key) {
return logger_impl::GetOrRegister(key);
}
Logger::Logger(ChannelId id)
: channelId(id) {
}
void Logger::VOut(MessageSeverity severity, std::string_view format, std::format_args args) {
logger_impl::MessageData data {
.time = std::chrono::system_clock::now(), .severity = severity, .channelId = channelId, .message = std::vformat(format, args)
};
// Push data into logger thread.
logger_impl::PushMessage(std::move(data));
}
} // namespace common

View file

@ -0,0 +1,75 @@
#pragma once
#include <base/Types.hpp>
#include <format>
#include <string_view>
namespace base {
/// A logger sink. Outputs messages to some device (a TTY), a file,
/// anything. A interface for the logger to spit stuff out.
///
/// # Notes
/// Sinks *do not* run on the main application thread. Instead, they run on a
/// single internal thread shared with the logging system.
/// Sinks should *not* block for large periods of time.
struct LoggerSink {
virtual void OutputMessage(std::string_view message) = 0;
};
enum class MessageSeverity { Debug, Info, Warning, Error, Fatal };
/// A channel ID. `enum class`es are used to avoid confusion with a normal u32,
/// and to also add addional interface type safety.
/// These are opaque, and only exposed here because it would be annoying to move elsewhere.
enum class ChannelId : u32 {};
/// Attach a sink to all Support loggers; allowing it to output logger messages.
void LoggerAttachSink(LoggerSink& sink);
MessageSeverity GetLogLevel();
void SetLogLevel(MessageSeverity newLevel);
/// An (asynchronous) logger.
struct Logger {
/// Gets or creates a logger for the specified channel.
static Logger& Get(std::string_view channel);
explicit Logger(ChannelId channel);
Logger(const Logger&) = delete;
Logger(Logger&&) = delete;
template <class... Args>
inline void Debug(std::string_view fmt, Args&&... args) {
VOut(MessageSeverity::Debug, fmt, std::make_format_args(args...));
}
template <class... Args>
inline void Info(std::string_view fmt, Args&&... args) {
VOut(MessageSeverity::Info, fmt, std::make_format_args(args...));
}
template <class... Args>
inline void Warning(std::string_view fmt, Args&&... args) {
VOut(MessageSeverity::Warning, fmt, std::make_format_args(args...));
}
template <class... Args>
inline void Error(std::string_view fmt, Args&&... args) {
VOut(MessageSeverity::Error, fmt, std::make_format_args(args...));
}
template <class... Args>
inline void Fatal(std::string_view fmt, Args&&... args) {
VOut(MessageSeverity::Fatal, fmt, std::make_format_args(args...));
}
private:
void VOut(MessageSeverity severity, std::string_view format, std::format_args args);
ChannelId channelId;
};
} // namespace collabvm

View file

@ -0,0 +1,170 @@
#include <base/Logger_priv.hpp>
#include <base/Thread.hpp>
#include <chrono>
#include <condition_variable>
#include <cstddef>
#include <deque>
#include <mutex>
#include <queue>
#include <string_view>
#include <thread>
namespace base::logger_impl {
static constexpr std::string_view SeverityToString(MessageSeverity sev) {
// This must match order of MessageSeverity.
const char* MessageSeverityStringTable[] = { "Debug", "Info", "Warn", "Error", "Fatal" };
return MessageSeverityStringTable[static_cast<std::size_t>(sev)];
}
/// Hash algorithm for channel IDs. In this case it's PJW-ELF.
/// I might switch to murmur or something if collisions are a problem,
/// but I don't think it's a problem.
ChannelId ChannelIDHash(const char* in) {
u32 hash = 0;
u32 high = 0;
while(*in) {
hash = (hash << 4) + *in++;
if((high = hash & 0xf0000000))
hash ^= high >> 23;
hash &= ~high;
}
return static_cast<ChannelId>(hash);
}
std::string_view ChannelToString(ChannelId id) {
auto& gs = LoggerGlobalState::The();
std::unique_lock lk(gs.loggerMapLock);
return gs.loggerMap[id].channelName;
}
/// comparator for [std::priority_queue]
struct LogMessageComparator {
constexpr bool operator()(const MessageData& mdLeft, const MessageData& mdRight) { return mdLeft.time > mdRight.time; }
};
struct LoggerThreadData {
// Logger thread stuff
std::thread loggerThread;
std::mutex logQueueMutex;
std::condition_variable logQueueCv;
std::priority_queue<MessageData, std::deque<MessageData>, LogMessageComparator> logQueue;
// could be an atomic_bool
bool logThreadShutdown = false;
bool ShouldUnblock() {
// N.B: ALL calls of this hold the lock.
// Always unblock if the logger thread needs to be shut down.
if(logThreadShutdown)
return true;
return !logQueue.empty();
}
void PushMessage(MessageData&& md) {
{
std::unique_lock lk(logQueueMutex);
logQueue.push(std::move(md));
}
logQueueCv.notify_one();
}
/// This thread drives the logging system.
static void LoggerThread(LoggerThreadData* self) {
// Fancy thread names.
SetThreadName("AsyncLogger");
auto& state = LoggerGlobalState::The();
while(true) {
// Shutdown if requested.
if(self->logThreadShutdown)
break;
{
std::unique_lock lk(self->logQueueMutex);
if(self->logQueue.empty()) {
// Await for messages.
self->logQueueCv.wait(lk, [self]() { return self->ShouldUnblock(); });
}
}
{
std::unique_lock lk(self->logQueueMutex);
// Flush the logger queue until there are no more messages.
while(!self->logQueue.empty()) {
const auto& msg = self->logQueue.top();
state.OutputMessage(msg);
self->logQueue.pop();
}
}
}
}
};
Unique<LoggerThreadData> threadData;
LoggerGlobalState& LoggerGlobalState::The() {
static LoggerGlobalState storage;
return storage;
}
LoggerGlobalState::LoggerGlobalState() {
// Spawn the logger thread
threadData = std::make_unique<LoggerThreadData>();
threadData->loggerThread = std::thread(&LoggerThreadData::LoggerThread, threadData.get());
}
LoggerGlobalState::~LoggerGlobalState() {
// Shut down the logger thread
threadData->logThreadShutdown = true;
threadData->logQueueCv.notify_all();
threadData->loggerThread.join();
}
void LoggerGlobalState::AttachSink(LoggerSink& sink) {
sinks.push_back(&sink);
}
void LoggerGlobalState::OutputMessage(const MessageData& data) {
// give up early if no sinks are attached
if(sinks.empty())
return;
if(data.severity < logLevel)
return;
auto formattedLoggerMessage = std::format("[{:%F %H:%M:%S}|{}|{}] {}", std::chrono::floor<std::chrono::milliseconds>(data.time),
SeverityToString(data.severity), ChannelToString(data.channelId), data.message);
for(auto sink : sinks)
sink->OutputMessage(formattedLoggerMessage);
}
Logger& GetOrRegister(std::string_view component) {
auto& gs = LoggerGlobalState::The();
std::unique_lock lk(gs.loggerMapLock);
auto hash = ChannelIDHash(component.data());
if(!gs.loggerMap.contains(hash)) {
// Insert a new entry into the logger map.
gs.loggerMap.insert_or_assign(hash, BoltedLoggerData { component, std::make_unique<Logger>(hash) });
}
return *gs.loggerMap[hash].logger.get();
}
void PushMessage(MessageData&& md) {
if(threadData)
threadData->PushMessage(std::move(md));
}
} // namespace base::logger_impl

View file

@ -0,0 +1,59 @@
#pragma once
#include <base/Logger.hpp>
#include <chrono>
#include <mutex>
#include <unordered_map>
#include <vector>
namespace base::logger_impl {
/// Message data. This is only used by logger sinks.
struct MessageData {
std::chrono::system_clock::time_point time;
MessageSeverity severity;
ChannelId channelId; // the channel ID.
std::string message; // DO NOT SET THIS, IT WILL BE OVERWRITTEN AND I WILL BE VERY SAD -lily
};
struct BoltedLoggerData {
std::string_view channelName;
Unique<Logger> logger;
};
/// Shared global state all loggers use.
struct LoggerGlobalState {
static LoggerGlobalState& The();
void AttachSink(LoggerSink& sink);
void OutputMessage(const MessageData& data);
/// Get the current log level.
MessageSeverity GetLogLevel() const { return logLevel; }
/// Set the current log level.
void SetLogLevel(MessageSeverity newLogLevel) { logLevel = newLogLevel; }
private:
LoggerGlobalState();
~LoggerGlobalState();
public:
std::vector<LoggerSink*> sinks;
MessageSeverity logLevel { MessageSeverity::Info };
std::unordered_map<ChannelId, BoltedLoggerData> loggerMap;
std::mutex loggerMapLock;
};
/// Gets or registers a new logger. This routine is threadsafe, and can be called
/// on any thread, like (most) parts of the logging system.
Logger& GetOrRegister(std::string_view component);
/// Push a logger message into the queue.
void PushMessage(MessageData&& md);
} // namespace base::logger_impl

View file

@ -0,0 +1,6 @@
// Sane windows.h
#pragma once
#define _WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <commctrl.h>
#undef _WIN32_LEAN_AND_MEAN

View file

@ -0,0 +1,20 @@
#include <base/StdoutSink.hpp>
#include "Logger.hpp"
namespace base {
StdoutLoggerSink& StdoutLoggerSink::The() {
static StdoutLoggerSink sink;
return sink;
}
void StdoutLoggerSink::OutputMessage(std::string_view message) {
fputs(message.data(), stdout);
fputc('\n', stdout);
fflush(stdout);
}
void LoggerAttachStdout() {
LoggerAttachSink(StdoutLoggerSink::The());
}
} // namespace base

View file

@ -0,0 +1,17 @@
#pragma once
#include <base/Logger.hpp>
namespace base {
/// A logger sink implementation that prints to standard output.
struct StdoutLoggerSink : public LoggerSink {
static StdoutLoggerSink& The();
void OutputMessage(std::string_view message) override;
};
/// Attach the stdout logger sink to the global Lucore logger.
void LoggerAttachStdout();
} // namespace base

View file

@ -0,0 +1,11 @@
#include <pthread.h>
#include <base/Thread.hpp>
namespace base {
void SetThreadNameImpl(const char* name, usize len) {
//COMMON_ASSERT(len <= 15, "name will overflow pthread_setname_np() buffer");
pthread_setname_np(pthread_self(), name);
}
}

View file

@ -0,0 +1,15 @@
#pragma once
#include <base/Types.hpp>
#include <string>
namespace base {
void SetThreadNameImpl(const char* name, usize len);
/// Sets the name of the current thread. Mainly for portability.
inline void SetThreadName(const std::string& name) {
SetThreadNameImpl(name.data(), name.length());
}
} // namespace collabvm

View file

@ -0,0 +1,52 @@
#pragma once
#include <cstdint>
#include <memory>
using u8 = std::uint8_t;
using i8 = std::int8_t;
using u16 = std::uint16_t;
using i16 = std::int16_t;
using u32 = std::uint32_t;
using i32 = std::int32_t;
using u64 = std::uint64_t;
using i64 = std::int64_t;
using usize = std::size_t;
using isize = std::intptr_t;
namespace base {
/// A little ergonomic wrapper over
/// std::unique_ptr<T[]>, for a "kinda-vector"
/// that lives on the heap and is statically sized
template <class T>
struct UniqueArray final {
explicit UniqueArray(usize size)
: array(std::make_unique<T[]>(size)),
size(size) {
}
UniqueArray(UniqueArray&& move) {
array = std::move(move.array);
size = move.size;
// invalidate
move.array = nullptr;
move.size = 0;
}
T& operator[](usize index) { return Get()[index]; }
const T& operator[](usize index) const { return Get()[index]; }
T* Get() { return array.get(); }
const T* Get() const { return array.get(); }
usize Size() const { return size; }
private:
std::unique_ptr<T[]> array {};
usize size {};
};
template<class T>
using Unique = std::unique_ptr<T>;
} // namespace common

View file

@ -0,0 +1,71 @@
#pragma once
#include <base/SaneWin.hpp>
#include <stdio.h>
namespace base {
// A relatively sane (non-intrinsic) COM smart pointer
// TODO: Allow downcasting to ComPtr<IUnknown, IID_IUnknown> (all COM objects implement this anyways)
//
template <class T = IUnknown, const IID* iid = &IID_IUnknown>
struct ComPtr {
//constexpr ComPtr() = default;
//explicit ComPtr(T* t) : interface_ptr(t) {}
constexpr ComPtr() : interface_ptr(nullptr) {}
// Assignment won't require AddRef() because most COM interfaces
// will automatically AddRef() upon querying them.
ComPtr& operator=(T* interface_) {
// Release an existing interface.
if(interface_ptr) {
printf("ComPtr<T>::operator= releasing %p (guid %08x)\n", interface_ptr, *iid);
interface_ptr->Release();
}
interface_ptr = interface_;
return *this;
}
ComPtr(const ComPtr& copy) {
if(interface_ptr) {
interface_ptr->AddRef();
interface_ptr = copy.interface_ptr;
}
}
~ComPtr() {
if(interface_ptr) {
printf("ComPtr<T>::~ComPtr releasing %p (guid %08x)\n", interface_ptr, *iid);
interface_ptr->Release();
}
}
// Helper to CoCreateInstance() on this pointer
HRESULT CreateInstance(REFCLSID clsid, DWORD ctx) {
return CoCreateInstance(clsid, nullptr, CLSCTX_ALL, *iid, reinterpret_cast<void**>(&interface_ptr));
}
// smart pointer overrides
constexpr T** operator&() { return &interface_ptr; }
constexpr T* operator->() { return interface_ptr; }
constexpr operator T*() const { return interface_ptr; }
T* Get() { return interface_ptr; }
const T* Get() const { return interface_ptr; }
// release pointer - you need to manage it yourself or put it
// into another ComSmartPtr then
T* ReleasePtr() {
auto old = interface_ptr;
interface_ptr = nullptr;
return old;
}
private:
T* interface_ptr { nullptr };
};
} // namespace base

View file

@ -0,0 +1,35 @@
add_library(speech2_impl
asio_src.cpp
beast_src.cpp
)
speech2_target(speech2_impl)
target_compile_definitions(speech2_impl PUBLIC
# Need to force this on, since I think clang's msvc compatibility
# is deciding to set a wrong __cplusplus (like MSVC, so it's not *exactly* clang's fault).
# The best way to fix it would probably involve using clang-cl frontend and passing the option (I think.)
# (nevermind, it's just broken.)
-DBOOST_ASIO_HAS_STD_INVOKE_RESULT=1
# We compile all of these header-only libraries in separate .cpp source files
# to decrease build churn
-DBOOST_ASIO_SEPARATE_COMPILATION=1
-DBOOST_BEAST_SEPARATE_COMPILATION=1
# Disable deprecated functionality and some things which add additional dependencies or are
# simply baggage we aren't ever going to use
-DBOOST_ASIO_NO_DEPRECATED=1
-DBOOST_ASIO_DISABLE_BOOST_ARRAY=1
-DBOOST_ASIO_DISABLE_BOOST_BIND=1
)
target_link_libraries(speech2_impl PUBLIC
Boost::asio
Boost::beast
Boost::coroutine
Boost::context
)
add_library(speech2::impl ALIAS speech2_impl)

View file

@ -0,0 +1,5 @@
// Since we're using (BOOST_)ASIO_SEPARATE_COMPILATION, we need
// to include the <(boost/)asio/impl/src.hpp> header in some TU.
// We use this one to explicitly do so.
#include <boost/asio/impl/src.hpp>

View file

@ -0,0 +1 @@
#include <boost/beast/src.hpp>

163
speech2/src/main.cpp Normal file
View file

@ -0,0 +1,163 @@
#include <stdio.h>
#include <boost/asio/detached.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/write.hpp>
#include <boost/beast/core/basic_stream.hpp>
#include <boost/beast/core/flat_buffer.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/http/message_generator.hpp>
#include <boost/beast/http/read.hpp>
#include <boost/beast/http/string_body.hpp>
#include <boost/beast/http/write.hpp>
#include <format>
#include <iostream>
#include <memory>
#include "base/Logger.hpp"
#include "base/StdoutSink.hpp"
#include "speechapi.hpp"
namespace net = boost::asio;
namespace beast = boost::beast;
namespace bhttp = beast::http;
using net::ip::tcp;
auto coresv_to_cxx(boost::core::string_view sv) {
return std::string_view { sv.data(), sv.length() };
}
// A test coro
void test_out_of_line_coro(net::any_io_executor ioc,net::yield_context yc) {
net::steady_timer t{ioc};
t.expires_after(std::chrono::seconds(5));
t.async_wait(yc);
}
class session : public std::enable_shared_from_this<session> {
public:
explicit session(net::io_context& io_context, beast::basic_stream<net::ip::tcp> socket)
: socket_(std::move(socket)), strand_(io_context.get_executor()) {}
void go() {
auto self(shared_from_this());
net::spawn(
strand_,
[this, self](net::yield_context yield) {
try {
auto& logger = base::Logger::Get("HTTPSession");
bhttp::request<bhttp::string_body> req {};
beast::flat_buffer buffer {};
logger.Info("Wait test");
// mostly just to test if I can yield stuff from another member function.
// This seems to work, so /shrug
test_out_of_line_coro(socket_.get_executor(), yield);
logger.Info("Wait completed");
for(;;) {
socket_.expires_after(std::chrono::seconds(10));
bhttp::async_read(socket_, buffer, req, yield);
auto const routeTest = [&]() -> bhttp::message_generator {
bhttp::response<bhttp::string_body> resp { bhttp::status::bad_request, req.version() };
resp.set(bhttp::field::server, "Fucker Google/1.0");
resp.set(bhttp::field::content_type, "text/plain");
resp.keep_alive(false);
resp.body() = std::format("You requested \"{} {}\"", coresv_to_cxx(req.method_string()), coresv_to_cxx(req.target()));
resp.prepare_payload();
return resp;
};
logger.Info("HTTP request to {}", coresv_to_cxx(req.target()));
auto res = routeTest();
socket_.expires_after(std::chrono::seconds(10));
beast::async_write(socket_, std::move(res), yield);
if(!req.keep_alive()) {
socket_.close();
return;
}
}
} catch(std::exception& e) {
socket_.close();
}
},
net::detached);
}
private:
beast::basic_stream<net::ip::tcp> socket_;
net::strand<net::io_context::executor_type> strand_;
};
int main(int argc, char** argv) {
// CoInitialize(nullptr);
base::LoggerAttachStdout();
try {
if(argc != 2) {
std::cerr << "Usage: echo_server <port>\n";
return 1;
}
net::io_context io_context;
net::spawn(
io_context,
[&](net::yield_context yield) {
tcp::acceptor acceptor(io_context, tcp::endpoint(tcp::v4(), std::atoi(argv[1])));
for(;;) {
boost::system::error_code ec;
tcp::socket socket(io_context);
acceptor.async_accept(socket, yield[ec]);
if(!ec) {
auto stream = beast::basic_stream<net::ip::tcp>(std::move(socket));
std::make_shared<session>(io_context, std::move(stream))->go();
}
}
},
[](std::exception_ptr e) {
if(e)
std::rethrow_exception(e);
});
io_context.run();
} catch(std::exception& e) {
std::cerr << "Exception: " << e.what() << "\n";
}
#if 0 // test spech shit
auto api = std::unique_ptr<ISpeechAPI>(ISpeechAPI::CreateSapi4());
if(auto hr = api->Initialize(); FAILED(hr)) {
printf("Failed to initialize speech API\n");
return 1;
}
if(auto hr = api->SelectVoice("Sam"); FAILED(hr)) {
printf("Failed to select voice\n");
return 1;
}
printf("Selected voice\n");
#endif
// CoUninitialize();
return 0;
}

View file

@ -0,0 +1,11 @@
# SAPI4 layer for speech2
add_library(speech2_sapi4
api_sapi4.cpp
guid_sapi4.cpp
audio_buffer.cpp
)
speech2_target(speech2_sapi4)
add_library(speech2::api_sapi4 ALIAS speech2_sapi4)

View file

@ -0,0 +1,97 @@
#include <stdio.h>
#include <base/comptr.hpp>
#include "sapi4/audio_buffer.hpp"
#include "sapi4/include/speech.h"
#include "speechapi.hpp"
struct SpeechAPI_SAPI4 : public ISpeechAPI {
virtual ~SpeechAPI_SAPI4() {
printf("~SpeechAPI_SAPI4\n");
// if(pAudioOut)
// pAudioOut->AddRef();
if(pCentral)
pCentral->Release();
};
HRESULT Initialize() override {
HRESULT hRes{};
printf("speech2: SpeechAPI_Sapi4::Initalize() begin: %p\n", this);
hRes = pEnum.CreateInstance(CLSID_TTSEnumerator, CLSCTX_INPROC);
if(FAILED(hRes))
return hRes;
pEnum->Reset();
// Fill out voices
EnumVoices();
return S_OK;
}
void EnumVoices() {
TTSMODEINFO found{};
while(!pEnum->Next(1, &found, nullptr)) {
voices.push_back({found.gModeID, found.szModeName});
}
pEnum->Reset();
}
const std::vector<VoiceInfo>& GetVoices() override {
return voices;
}
HRESULT SelectVoiceImpl(const GUID& guid) {
pAudioOut = new AudioOutBuffer();
pAudioOut->AddRef();
if(pCentral)
pCentral->Release();
ITTSCentral* central;
if(auto hr = pEnum->Select(guid, &central, static_cast<IUnknown*>(pAudioOut)); FAILED(hr))
return hr;
pCentral = central;
// From Microsoft Speech SDK 4.0 documentation:
// The engine will AddRef the interface, and release the interface when the engine is destroyed.
// Because of this the application will need to call Release on the audio object after the select call or audio objects will be leaked.
pAudioOut->Release();
return S_OK;
}
HRESULT SelectVoice(std::string_view voiceName) override {
TTSMODEINFO found {};
while(!pEnum->Next(1, &found, nullptr)) {
if(voiceName == found.szModeName) {
return SelectVoiceImpl(found.gModeID);
}
}
pEnum->Reset();
return S_OK;
}
private:
base::ComPtr<ITTSEnum, &IID_ITTSEnum> pEnum;
ITTSCentral* pCentral { nullptr };
// The above comment is also why this isn't a ComPtr.
AudioOutBuffer* pAudioOut { nullptr };
std::vector<VoiceInfo> voices{};
};
ISpeechAPI* ISpeechAPI::CreateSapi4() {
return new SpeechAPI_SAPI4();
}

View file

@ -0,0 +1,185 @@
#include "audio_buffer.hpp"
#include <stdio.h>
#include <winerror.h>
// Implements IAudioDest.
struct AudioOutBuffer::Dest : public IAudioDest {
Dest(AudioOutBuffer* pOut) : pOut(pOut) {}
~Dest() = default;
// IUnknown
STDMETHODIMP QueryInterface(const IID& riid, LPVOID* ppvObj) noexcept override { return pOut->QueryInterface(riid, ppvObj); }
STDMETHODIMP_(ULONG) AddRef() noexcept override {
m_iRefCount++;
return pOut->AddRef();
}
STDMETHODIMP_(ULONG) Release() noexcept override {
m_iRefCount--;
return pOut->Release();
}
// IAudioDest
STDMETHODIMP FreeSpace(DWORD* pFree, BOOL* pEOF) noexcept override { return E_NOTIMPL; }
STDMETHODIMP DataSet(PVOID pBuffer, DWORD dwSize) noexcept override {
// TODO
printf("AudioOutBuffer::Dest::DataSet()\n");
return E_NOTIMPL;
}
STDMETHODIMP BookMark(DWORD pos) noexcept override {
// Ignored
return S_OK;
}
private:
AudioOutBuffer* pOut;
ULONG m_iRefCount{};
};
// Implements IAudio.
struct AudioOutBuffer::Audio : public IAudio {
Audio(AudioOutBuffer* pOut) : pOut(pOut) {}
~Audio() {
};
// IUnknown
STDMETHODIMP QueryInterface(const IID& riid, LPVOID* ppvObj) noexcept override { return pOut->QueryInterface(riid, ppvObj); }
STDMETHODIMP_(ULONG) AddRef() noexcept override {
m_iRefCount++;
return pOut->AddRef();
}
STDMETHODIMP_(ULONG) Release() noexcept override {
m_iRefCount--;
return pOut->Release();
}
// IAudio
STDMETHODIMP Flush() noexcept override { return S_OK; }
STDMETHODIMP LevelGet(DWORD* pLevel) noexcept override { return E_NOTIMPL; }
STDMETHODIMP LevelSet(DWORD level) noexcept override { return E_NOTIMPL; }
STDMETHODIMP PassNotify(PVOID pSink, IID iid) noexcept override {
printf("AudioOutBuffer::Audio::PassNotify() %p\n", pSink);
if(pSink == nullptr) {
return E_INVALIDARG;
}
if(iid != IID_IAudioDestNotifySink)
return AUDERR_INVALIDNOTIFYSINK;
pOut->m_pDestNotifySink = static_cast<IAudioDestNotifySink*>(pSink);
return S_OK;
}
STDMETHODIMP PosnGet(PQWORD pos) noexcept override {
if(!pos)
return E_POINTER;
*pos = pOut->m_BufferWritten;
return S_OK;
}
STDMETHODIMP Claim() noexcept override { return S_OK; }
STDMETHODIMP UnClaim() noexcept override { return S_OK; }
STDMETHODIMP Start() noexcept override {
// TODO
return S_OK;
}
STDMETHODIMP Stop() noexcept override {
// TODO
return S_OK;
}
STDMETHODIMP TotalGet(PQWORD total) noexcept override { return PosnGet(total); }
STDMETHODIMP ToFileTime(PQWORD time, FILETIME* out) noexcept override { return E_NOTIMPL; }
STDMETHODIMP WaveFormatGet(SDATA* pOut) noexcept override {
// TODO
//IMalloc* pMalloc { nullptr };
//if(auto hr = CoGetMalloc(1, &pMalloc); FAILED(hr))
// return hr;
return S_OK;
}
STDMETHODIMP WaveFormatSet(SDATA data) noexcept override {
if(!data.pData)
return E_INVALIDARG;
return S_OK;
}
private:
AudioOutBuffer* pOut;
ULONG m_iRefCount{};
};
AudioOutBuffer::AudioOutBuffer() {
printf("AudioOutBuffer() %p\n", this);
m_Audio = new AudioOutBuffer::Audio(this);
m_AudioDestBuffer = new AudioOutBuffer::Dest(this);
}
AudioOutBuffer::~AudioOutBuffer() {
printf("~AudioOutBuffer() %p\n", this);
if(m_AudioDestBuffer)
delete m_AudioDestBuffer;
if(m_Audio)
delete m_Audio;
}
// IUnknown
STDMETHODIMP AudioOutBuffer::QueryInterface(const IID& riid, LPVOID* ppvObj) noexcept {
if(!ppvObj)
return E_NOINTERFACE;
*ppvObj = nullptr;
// This object only directly implements IUnknown, and proxies
// to other implementation objects for other interfaces.
if(riid == IID_IUnknown) {
*ppvObj = static_cast<void*>(this);
AddRef();
return S_OK;
}
// Return implementations
if(riid == IID_IAudio) {
*ppvObj = static_cast<void*>(m_Audio);
//m_Audio->AddRef();
return S_OK;
} else if(riid == IID_IAudioDest) {
*ppvObj = static_cast<void*>(m_AudioDestBuffer);
//m_AudioDestBuffer->AddRef();
return S_OK;
}
return E_NOINTERFACE;
}
STDMETHODIMP_(ULONG) AudioOutBuffer::AddRef() {
return InterlockedIncrement(&m_iRefCount);
}
STDMETHODIMP_(ULONG) AudioOutBuffer::Release() {
auto ulRefCount = InterlockedDecrement(&m_iRefCount);
if(ulRefCount == 0) {
printf("Deleting release AudioOutBuffer\n");
delete this;
}
return ulRefCount;
}

View file

@ -0,0 +1,43 @@
#pragma once
#include <windows.h>
#include "base/comptr.hpp"
#include "sapi4/include/speech.h"
// Implementation of SAPI 4 IAudio(Dest) to spit out wave format data to a buffer
// TODO: out-line definition so other components can call methods they need
struct AudioOutBuffer : public IUnknown {
// Implements IAudioDest.
struct Dest;
struct Audio;
AudioOutBuffer();
virtual ~AudioOutBuffer();
AudioOutBuffer(const AudioOutBuffer&) = delete;
AudioOutBuffer(AudioOutBuffer&&) = delete;
// IUnknown
STDMETHODIMP QueryInterface(const IID& riid, LPVOID* ppvObj) noexcept override;
STDMETHODIMP_(ULONG) AddRef() override;
STDMETHODIMP_(ULONG) Release() override;
private:
ULONG m_iRefCount{};
Dest* m_AudioDestBuffer{};
Audio* m_Audio{};
enum class State {
Clear,
WroteHeader,
WritingWaveData,
Done
};
//base::ComPtr<IAudioDestNotifySink, &IID_IAudioDestNotifySink> m_pDestNotifySink{};
IAudioDestNotifySink* m_pDestNotifySink{nullptr};
QWORD m_BufferWritten{};
};

View file

@ -0,0 +1,6 @@
// This file declares all the SAPI 4.0 GUID's.
#include <windows.h>
#include <initguid.h>
#include "sapi4/include/speech.h"

30
speech2/src/speechapi.hpp Normal file
View file

@ -0,0 +1,30 @@
#include <windows.h>
#include <string>
#include <string_view>
#include <vector>
/// base class for access to text-to-speech APIs.
struct ISpeechAPI {
struct VoiceInfo {
GUID guid{}; // Optional. May not be filled out if the API doesn't do guids
std::string voiceName;
};
virtual ~ISpeechAPI() = default;
/// Creates a implementation of SpeechAPI for SAPI 4.
/// COM must be initalized before calling this function.
static ISpeechAPI* CreateSapi4();
/// Performs the bare level of initalization required to use the speech API
virtual HRESULT Initialize() = 0;
virtual const std::vector<VoiceInfo>& GetVoices() = 0;
/// Selects a voice.
virtual HRESULT SelectVoice(std::string_view voiceName) = 0;
//virtual HRESULT Speak(LPCSTR text, char** pOutputBuffer, size_t* pOutSize) = 0;
};

View file

@ -0,0 +1,34 @@
#include <windows.h>
typedef BOOL(WINAPI* PFN_INITIALIZECRITICALSECTIONEX)(LPCRITICAL_SECTION lpCriticalSection, DWORD dwSpinCount, DWORD dwFlags);
extern "C" {
PFN_INITIALIZECRITICALSECTIONEX pfnInitalizeCriticalSectionEx = nullptr;
BOOL WINAPI _InitalizeCriticalSectionEx_xp(LPCRITICAL_SECTION lpCriticalSection, DWORD dwSpinCount, DWORD dwFlags) {
// We ignore dwFlags, but pass dwSpinCount to InitializeCriticalSectionAndSpinCount,
// which DOES exist on XP.
static_cast<void>(dwFlags);
return InitializeCriticalSectionAndSpinCount(lpCriticalSection, dwSpinCount);
}
BOOL WINAPI LibInitalizeCriticalSectionEx(LPCRITICAL_SECTION lpCriticalSection, DWORD dwSpinCount, DWORD dwFlags) {
if(!pfnInitalizeCriticalSectionEx) {
pfnInitalizeCriticalSectionEx =
reinterpret_cast<PFN_INITIALIZECRITICALSECTIONEX>(GetProcAddress(GetModuleHandle("kernel32.dll"), "InitializeCriticalSectionEx"));
// Compatibilty.
if(!pfnInitalizeCriticalSectionEx)
pfnInitalizeCriticalSectionEx = _InitalizeCriticalSectionEx_xp;
}
return pfnInitalizeCriticalSectionEx(lpCriticalSection, dwSpinCount, dwFlags);
}
void WINAPI LibGetSystemTimePreciseAsFileTime(LPFILETIME lpSystemTimeAsFileTime) {
// TODO: Above
return GetSystemTimeAsFileTime(lpSystemTimeAsFileTime);
}
}

View file

@ -0,0 +1,9 @@
// This file declares forwarders for Windows imports which the newer VS 2022 CRT requires.
.globl "__imp__InitializeCriticalSectionEx@12"
"__imp__InitializeCriticalSectionEx@12":
.long "_LibInitalizeCriticalSectionEx@12"
.globl "__imp__GetSystemTimePreciseAsFileTime@4"
"__imp__GetSystemTimePreciseAsFileTime@4":
.long "_LibGetSystemTimePreciseAsFileTime@4"

View file

@ -0,0 +1,104 @@
/* This file provides routines used for thread-safe initialisation of static
* variables when /Zc:threadSafeInit is used (enabled by default).
*
* This is intended to override the default implementations from the Microsoft
* C++ Runtime which are compiled to target Windows Vista or later.
*
* Modeled on the reference implementation in thread_safe_statics.cpp in the
* Microsoft C++ Runtime.
*/
#include <assert.h>
#include <limits.h>
#include <pthread.h>
static const int UNINITIALIZED = 0;
static const int INITIALIZING = -1;
static const int EPOCH_BASE = INT_MIN;
/* Exposed as a public symbol in the reference implementation, so exposed it
* has to stay here too...
*/
int _Init_global_epoch = EPOCH_BASE;
__declspec(thread) int _Init_thread_epoch = EPOCH_BASE;
static pthread_mutex_t _Init_thread_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t _Init_thread_cond = PTHREAD_COND_INITIALIZER;
void __cdecl _Init_thread_lock()
{
pthread_mutex_lock(&_Init_thread_mutex);
}
void __cdecl _Init_thread_unlock()
{
pthread_mutex_unlock(&_Init_thread_mutex);
}
void __cdecl _Init_thread_wait_v2()
{
pthread_cond_wait(&_Init_thread_cond, &_Init_thread_mutex);
}
void __cdecl _Init_thread_notify()
{
pthread_cond_broadcast(&_Init_thread_cond);
}
void __cdecl _Init_thread_header(int* const pOnce)
{
_Init_thread_lock();
if (*pOnce == UNINITIALIZED)
{
*pOnce = INITIALIZING;
}
else
{
while (*pOnce == INITIALIZING)
{
_Init_thread_wait_v2();
if (*pOnce == UNINITIALIZED)
{
*pOnce = INITIALIZING;
_Init_thread_unlock();
return;
}
}
_Init_thread_epoch = _Init_global_epoch;
}
_Init_thread_unlock();
}
void __cdecl _Init_thread_abort(int* const pOnce)
{
_Init_thread_lock();
*pOnce = UNINITIALIZED;
_Init_thread_unlock();
_Init_thread_notify();
}
void __cdecl _Init_thread_footer(int* const pOnce)
{
_Init_thread_lock();
++_Init_global_epoch;
/* Probably unlikely condition... you would need to construct ~2 billion
* static objects before the "epoch" would roll up to the "INITIALIZING"
* constant and cause weird behaviour... the official implementation
* technically has this bug too.
*/
assert(_Init_global_epoch < INITIALIZING);
*pOnce = _Init_global_epoch;
_Init_thread_epoch = _Init_global_epoch;
_Init_thread_unlock();
_Init_thread_notify();
}

4
speech2/third_party/README.md vendored Normal file
View file

@ -0,0 +1,4 @@
Files in this directory are the intellectual property of:
- sapi4: Microsoft Speech SDK 4.0.4.2512
- sapi5: Microsoft Speech SDK 5.1

View file

@ -0,0 +1,13 @@
# Hack but it works :)
set(BOOST_SUPERPROJECT_VERSION 1.85.0)
# Boost Context Have Sucks
set(BOOST_CONTEXT_ASSEMBLER clang_gas CACHE STRING "")
set(BOOST_CONTEXT_ASM_SUFFIX .S CACHE STRING "")
# Populate library list
file(STRINGS ${CMAKE_CURRENT_SOURCE_DIR}/list _COLLABVM3_BOOST_LIBRARY_LIST)
foreach(lib ${_COLLABVM3_BOOST_LIBRARY_LIST})
message(STATUS "Adding boost module ${lib}")
add_subdirectory(${lib})
endforeach()

11
speech2/third_party/boost/README.md vendored Normal file
View file

@ -0,0 +1,11 @@
# Welcome to hell
This is where all the boost libraries used by the server live
see ./list for them
# Reinitalizing
The following bash one liner was used to initalize this repo
`for f in $(cat list); do git submodule add https://github.com/boostorg/$f.git $f; cd $f; git checkout boost-1.82.0; cd ..; done`

10
speech2/third_party/boost/add.sh vendored Executable file
View file

@ -0,0 +1,10 @@
#!/bin/bash
# note that this doesn't add the library to ./list
# you will need to do that yourself
# (it could be added but bleh)
lib=$1
git submodule add https://github.com/boostorg/$lib.git $lib
# remember to bump the version afterwards

@ -0,0 +1 @@
Subproject commit 32c5a6327cfdca5d41ce0f1d8849b811886daa2f

1
speech2/third_party/boost/align vendored Submodule

@ -0,0 +1 @@
Subproject commit 5ad7df63cd792fbdb801d600b93cad1a432f0151

1
speech2/third_party/boost/array vendored Submodule

@ -0,0 +1 @@
Subproject commit 23f6b27c0d9916b9932baac898ae3009817a9153

1
speech2/third_party/boost/asio vendored Submodule

@ -0,0 +1 @@
Subproject commit e65367991cb5fbdb8a7cf218ae38f69ca9a0a9f5

1
speech2/third_party/boost/assert vendored Submodule

@ -0,0 +1 @@
Subproject commit 447e0b3a331930f8708ade0e42683d12de9dfbc3

1
speech2/third_party/boost/atomic vendored Submodule

@ -0,0 +1 @@
Subproject commit 5bbcce0f6e855dc4009e2e6977c62e0520c39573

1
speech2/third_party/boost/beast vendored Submodule

@ -0,0 +1 @@
Subproject commit 98b8be489fa7a74753a724d8d0772d90bcbed0fc

1
speech2/third_party/boost/bind vendored Submodule

@ -0,0 +1 @@
Subproject commit 9fbfdcb3577e9427815d4f8cc25b3a25d5b9696b

10
speech2/third_party/boost/bump_version.sh vendored Executable file
View file

@ -0,0 +1,10 @@
#!/bin/bash
# bump all the repositories
for library in $(cat $PWD/list); do
pushd $library >/dev/null 2>&1
git checkout develop
git pull
git checkout $1
popd >/dev/null 2>&1
done

1
speech2/third_party/boost/charconv vendored Submodule

@ -0,0 +1 @@
Subproject commit ecdca0865d4d8fcfbf8e5b48914f2b5711c418ad

1
speech2/third_party/boost/chrono vendored Submodule

@ -0,0 +1 @@
Subproject commit ee0d6d543a37d9b7243682549e9ae359eb89daa9

@ -0,0 +1 @@
Subproject commit a08a5b55ee82e0c2487523471379ac53a23935dc

@ -0,0 +1 @@
Subproject commit 37c9bddf0bdefaaae0ca5852c1a153d9fc43f278

1
speech2/third_party/boost/config vendored Submodule

@ -0,0 +1 @@
Subproject commit 11385ec21012926e15a612e3bf9f9a71403c1e5b

@ -0,0 +1 @@
Subproject commit 6e697d796897b32b471b4f0740dcaa03d8ee57cc

@ -0,0 +1 @@
Subproject commit 6d214eb776456bf17fbee20780a034a23438084f

1
speech2/third_party/boost/context vendored Submodule

@ -0,0 +1 @@
Subproject commit 1bde50e400547e29336afe7ea0cd693d8c884fb6

@ -0,0 +1 @@
Subproject commit 9f285ef0c43c101e49b37bf5e6085e8d635887dc

1
speech2/third_party/boost/core vendored Submodule

@ -0,0 +1 @@
Subproject commit 083b41c17e34f1fc9b43ab796b40d0d8bece685c

@ -0,0 +1 @@
Subproject commit 1e1347c0b1910b9310ec1719edad8b0bf2fd03c8

@ -0,0 +1 @@
Subproject commit 85e637cb325208c2af9af791c3a1948b4888c6cd

1
speech2/third_party/boost/describe vendored Submodule

@ -0,0 +1 @@
Subproject commit 50719b212349f3d1268285c586331584d3dbfeb5

1
speech2/third_party/boost/detail vendored Submodule

@ -0,0 +1 @@
Subproject commit 9c3a0022b25d3e483f9100cc363bc93a72fd900a

1
speech2/third_party/boost/endian vendored Submodule

@ -0,0 +1 @@
Subproject commit c9b436e5dfce85e8ae365e5aabbb872dd35c29eb

@ -0,0 +1 @@
Subproject commit b9170a02f102250b308c9f94ed6593c5f30eab39

@ -0,0 +1 @@
Subproject commit a0c8edba38a4d31b449fcf7b7ada455977342596

1
speech2/third_party/boost/function vendored Submodule

@ -0,0 +1 @@
Subproject commit 28b88d07bb4807445462c3f5dab0efde6f532d32

@ -0,0 +1 @@
Subproject commit 895335874d67987ada0d8bf6ca1725e70642ed49

@ -0,0 +1 @@
Subproject commit 6a573e4b8333ee63ee62ce95558c3667348db233

1
speech2/third_party/boost/fusion vendored Submodule

@ -0,0 +1 @@
Subproject commit 7d4c03fa032299f2d46149b7b3136c9fd43e4f81

1
speech2/third_party/boost/integer vendored Submodule

@ -0,0 +1 @@
Subproject commit dea8e3445dc3ca29201498260307138b9460a70c

@ -0,0 +1 @@
Subproject commit 07ba0e376177409c396c109807c13b7181a98ebe

1
speech2/third_party/boost/io vendored Submodule

@ -0,0 +1 @@
Subproject commit 342e4c6d10d586058818daa84201a2d301357a53

1
speech2/third_party/boost/iterator vendored Submodule

@ -0,0 +1 @@
Subproject commit 4f7219965a399051bb0d8088ea4ab3929b1ac3f2

1
speech2/third_party/boost/json vendored Submodule

@ -0,0 +1 @@
Subproject commit 9f85ed6d62ff91c6dc4fc30e3a20e9049ec67585

1
speech2/third_party/boost/leaf vendored Submodule

@ -0,0 +1 @@
Subproject commit ed8f9cd32f4fde695d497502f696f6f861b68559

@ -0,0 +1 @@
Subproject commit 02e5821ab32c45fad719829e9644e5d681c9ba0b

67
speech2/third_party/boost/list vendored Normal file
View file

@ -0,0 +1,67 @@
algorithm
align
array
assert
atomic
bind
charconv
chrono
circular_buffer
concept_check
config
container
container_hash
context
conversion
core
coroutine
date_time
describe
detail
endian
exception
filesystem
function
functional
function_types
fusion
integer
intrusive
io
iterator
json
leaf
lexical_cast
lockfree
logic
move
mp11
mpl
numeric_conversion
optional
parameter
pool
predef
preprocessor
range
ratio
rational
regex
scope
smart_ptr
static_assert
static_string
system
throw_exception
tokenizer
tuple
type_index
typeof
type_traits
unordered
url
utility
variant2
winapi
asio
beast

1
speech2/third_party/boost/lockfree vendored Submodule

@ -0,0 +1 @@
Subproject commit fdd4d0632dd0904f6e9c656c45397fe8ef985bc9

1
speech2/third_party/boost/logic vendored Submodule

@ -0,0 +1 @@
Subproject commit 145778490c2d332c1411df6a5274a4b53ec3e091

1
speech2/third_party/boost/move vendored Submodule

@ -0,0 +1 @@
Subproject commit 7c01072629d83a7b54c99de70ef535d699ebd200

1
speech2/third_party/boost/mp11 vendored Submodule

@ -0,0 +1 @@
Subproject commit 863d8b8d2b20f2acd0b5870f23e553df9ce90e6c

1
speech2/third_party/boost/mpl vendored Submodule

@ -0,0 +1 @@
Subproject commit b440c45c2810acbddc917db057f2e5194da1a199

@ -0,0 +1 @@
Subproject commit 50a1eae942effb0a9b90724323ef8f2a67e7984a

1
speech2/third_party/boost/optional vendored Submodule

@ -0,0 +1 @@
Subproject commit c60db27762ff9cc16529e069c3c15f2fa898f994

@ -0,0 +1 @@
Subproject commit c07f2b8d37ded87f6f9d5bac867550f6e61282c1

1
speech2/third_party/boost/pool vendored Submodule

@ -0,0 +1 @@
Subproject commit ec7da07ed13e0c61e50d945b574a12ae7ec83cf4

1
speech2/third_party/boost/predef vendored Submodule

@ -0,0 +1 @@
Subproject commit 0fdfb49c3a6789e50169a44e88a07cc889001106

@ -0,0 +1 @@
Subproject commit c4ea7e40d365ba28faecef8917d5c3f1e0121bf9

1
speech2/third_party/boost/range vendored Submodule

@ -0,0 +1 @@
Subproject commit 2bb6b636796f7b008196888613f51f5bb347c77d

1
speech2/third_party/boost/ratio vendored Submodule

@ -0,0 +1 @@
Subproject commit d5b33caa7d564be9be6d962b18659b7741d764ac

1
speech2/third_party/boost/rational vendored Submodule

@ -0,0 +1 @@
Subproject commit 564623136417068916495e2b24737054d607347c

Some files were not shown because too many files have changed in this diff Show more