diff --git a/.gitignore b/.gitignore index 0ce16da..1acd102 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ **/obj/ # on your own machine, please. -/speech2/compile_commands.json +/speech2/build +/speech2/build-debug diff --git a/Justfile b/Justfile index c4dfdcd..c9450bc 100644 --- a/Justfile +++ b/Justfile @@ -1,14 +1,6 @@ build: - dotnet build -c Release - make -C speech2 CONFIG=Release -j$(nproc) - cp speech2/bin/x86/Release/speech2.dll SAPIServer/bin/Release/net40/windows-x86 - cp speech2/bin/x86/Release/speech2.pdb SAPIServer/bin/Release/net40/windows-x86 - -build-debug: - dotnet build -c Debug - make -C speech2 CONFIG=Debug -j$(nproc) - cp speech2/bin/x86/Debug/speech2.dll SAPIServer/bin/Debug/net40/windows-x86 - cp speech2/bin/x86/Debug/speech2.pdb SAPIServer/bin/Debug/net40/windows-x86 + 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 diff --git a/speech2/CMakeLists.txt b/speech2/CMakeLists.txt index ff2da8a..84fca64 100644 --- a/speech2/CMakeLists.txt +++ b/speech2/CMakeLists.txt @@ -1,14 +1,16 @@ cmake_minimum_required(VERSION 3.15) -project(testprog) +project(speech2) -xp_init() +enable_language(ASM) + +list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") + +include(Policies) +include(ProjectFuncs) + + +add_subdirectory(src) -add_executable(testprog - testprog.cpp -) -# use the static multithreaded C library -set_property(TARGET testprog PROPERTY - MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") diff --git a/speech2/Makefile b/speech2/Makefile deleted file mode 100644 index d4384a1..0000000 --- a/speech2/Makefile +++ /dev/null @@ -1,30 +0,0 @@ -include build/arch.mk -include build/configs.mk - -NAME = speech2 -TYPE = dll - -# Any C++ file in src/ is automatically picked up. -CXXSRCS = $(wildcard src/*.cpp) $(wildcard src/*/*.cpp) - -# Required libraries. -LIBS = kernel32.lib shell32.lib user32.lib uuid.lib ole32.lib - -.PHONY: all clean matrix - -include build/rules.mk - -all: $(BUILD_PRODUCT) - -clean: - echo -e "\e[91mCleaning... \e[0m" - rm -rf $(BINDIR)/ $(OBJS) - -# A fun make trick. This allows for verbose compilation if desired. -# Set V=1 or anything, and it'll be verbose. -$V.SILENT: - -# Include dependency files generated by compilation. -# `make clean` keeps them so we can reuse, however they can -# still optionally be blown away. --include $(OBJS:.o=.d) diff --git a/speech2/Makefile.testprog b/speech2/Makefile.testprog deleted file mode 100644 index 745e06e..0000000 --- a/speech2/Makefile.testprog +++ /dev/null @@ -1,30 +0,0 @@ -include build/arch.mk -include build/configs.mk - -NAME = testprog -TYPE = exe - -# Any C++ file in src/ is automatically picked up. -CXXSRCS = testprog.cpp - -# Required libraries. -LIBS = kernel32.lib shell32.lib user32.lib uuid.lib ole32.lib - -.PHONY: all clean matrix - -include build/rules.mk - -all: $(BUILD_PRODUCT) - -clean: - echo -e "\e[91mCleaning... \e[0m" - rm -rf $(BINDIR)/ $(OBJS) - -# A fun make trick. This allows for verbose compilation if desired. -# Set V=1 or anything, and it'll be verbose. -$V.SILENT: - -# Include dependency files generated by compilation. -# `make clean` keeps them so we can reuse, however they can -# still optionally be blown away. --include $(OBJS:.o=.d) diff --git a/speech2/build/arch.mk b/speech2/build/arch.mk deleted file mode 100644 index bfdfa1b..0000000 --- a/speech2/build/arch.mk +++ /dev/null @@ -1,26 +0,0 @@ -x86_Valid=yes -x86_TRIPLET=i686-pc-windows-msvc - -# Only supported arch. -ifeq ($(ARCH),) -ARCH = x86 -endif - -ifeq ($(VCDIR),) -$(error Please set VCDIR in your environment to an appropiate path) -endif - -ifneq ($($(ARCH)_Valid),yes) -$(error Please select a valid target) -endif - -CC = clang -target $($(ARCH)_TRIPLET) -CXX = clang -target $($(ARCH)_TRIPLET) - -LD = lld-link - -# This is $(WINDRES) thanks to the fact I -# depended on the (relatively low quality) -# MinGW toolchain. Thank God For LLVM. -WINDRES = llvm-rc - diff --git a/speech2/build/configs.mk b/speech2/build/configs.mk deleted file mode 100644 index 57fd68c..0000000 --- a/speech2/build/configs.mk +++ /dev/null @@ -1,43 +0,0 @@ -# Base compiler flags. Only change if you *explicitly* know what you're doing. - -CXXRTDIR = $(VCDIR)/crt -UCRTDIR = $(VCDIR)/ucrt -PSDKDIR = $(VCDIR)/winsdk - -Release_RTLIBS = libcmt.lib libucrt.lib libvcruntime.lib libcpmt.lib -Debug_RTLIBS = libcmtd.lib libucrtd.lib libvcruntimed.lib libcpmtd.lib - -# I really should rename this x_x -CLANG_FLAGS = -fms-extensions -fms-compatibility-version=19 -isystem $(CXXRTDIR)/include -isystem $(UCRTDIR)/include -isystem $(PSDKDIR)/include/shared -isystem $(PSDKDIR)/include/um - -BASE_FLAGS = -MMD -gcodeview -fvisibility=hidden $(CLANG_FLAGS) -march=pentium-mmx -Iinclude -Isrc -Ithird_party -mstack-alignment=4 -D_WIN32_WINNT=0x0501 - -BASE_CCFLAGS = $(BASE_FLAGS) -std=gnu17 -BASE_CXXFLAGS = $(BASE_FLAGS) -std=c++20 -BASE_LDFLAGS_SHARED = /dll - -BASE_LDFLAGS = /nodefaultlib /version:5.1 /machine:i386 /subsystem:windows,5.1 /libpath:$(CXXRTDIR)/lib/x86 /libpath:$(UCRTDIR)/lib /libpath:$(PSDKDIR)/lib - -# TODO: Figure out what optimizations are safe and don't break the stack -Release_Valid = yes -Release_CCFLAGS = -O0 -DNDEBUG -Release_CXXFLAGS = -O0 -DNDEBUG -Release_LDFLAGS = /debug /pdb:$(BINDIR)/$(NAME).pdb - -Debug_Valid = yes -Debug_CCFLAGS = -O0 -DDEBUG # -D_DEBUG -Debug_CXXFLAGS = -O0 -DDEBUG # -D_DEBUG -D_ITERATOR_DEBUG_LEVEL=0 -Debug_LDFLAGS = /debug /pdb:$(BINDIR)/$(NAME).pdb - -# select a default configuration or validate configuration -ifeq ($(CONFIG),) -CONFIG = Release -endif - -ifneq ($($(CONFIG)_Valid),yes) -$(error Please select a valid configuration) -endif - -# define the directories used for output products here. -BINDIR = bin/$(ARCH)/$(CONFIG) -OBJDIR = obj/$(ARCH)/$(CONFIG) diff --git a/speech2/build/rules.mk b/speech2/build/rules.mk deleted file mode 100644 index 688452b..0000000 --- a/speech2/build/rules.mk +++ /dev/null @@ -1,46 +0,0 @@ - -# TODO: Handle C sources and deduplicate. -VPATH = $(dir $(CXXSRCS)) -OBJS = $(addprefix $(OBJDIR)/,$(notdir $(CXXSRCS:.cpp=.o))) - -# Build types -ifeq ($(TYPE),dll) -BUILD_PRODUCT = $(BINDIR)/$(NAME).dll - -$(BINDIR)/$(NAME).dll: $(BINDIR)/ $(OBJDIR)/ $(OBJS) - echo -e "\e[92mLinking DLL $@\e[0m" - $(LD) $(BASE_LDFLAGS_SHARED) $(BASE_LDFLAGS) $($(CONFIG)_LDFLAGS) $($(CONFIG)_RTLIBS) $(LIBS) $(OBJS) /out:$@ -else -ifeq ($(TYPE),exe) -BUILD_PRODUCT = $(BINDIR)/$(NAME).exe - -$(BINDIR)/$(NAME).exe: $(BINDIR)/ $(OBJDIR)/ $(OBJS) - echo -e "\e[92mLinking EXE $@\e[0m" - $(LD) $(BASE_LDFLAGS) $($(CONFIG)_LDFLAGS) $($(CONFIG)_RTLIBS) $(LIBS) $(OBJS) /out:$@ -endif -endif - -$(OBJDIR)/%.o: %.c - echo -e "\e[94mCompiling C source file $< ($@)\e[0m" - $(CC) -c $(BASE_CCFLAGS) $($(CONFIG)_CCFLAGS) $< -o $@ - -$(OBJDIR)/%.o: %.cpp - echo -e "\e[94mCompiling C++ source file $< ($@)\e[0m" - $(CC) -c $(BASE_CXXFLAGS) $($(CONFIG)_CXXFLAGS) $< -o $@ - -$(OBJDIR)/%.o: %.S - echo -e "\e[94mAssembling $< ($@)\e[0m" - $(CC) -xassembler-with-cpp -c $(BASE_CCFLAGS) $($(CONFIG)_CCFLAGS) $< -o $@ - -$(OBJDIR)/%.o: %.rc - echo -e "\e[94mCompiling Windows resource script $<\e[0m" - $(WINDRES) -Iinclude $< -o $@ - -# dir rules -$(BINDIR)/: - echo -e "\e[95mMKDIR $@\e[0m" - mkdir -p $(BINDIR) - -$(OBJDIR)/: - echo -e "\e[95mMKDIR $@\e[0m" - mkdir -p $(OBJDIR) diff --git a/speech2/cmake/Policies.cmake b/speech2/cmake/Policies.cmake new file mode 100644 index 0000000..d7a084b --- /dev/null +++ b/speech2/cmake/Policies.cmake @@ -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. diff --git a/speech2/cmake/ProjectFuncs.cmake b/speech2/cmake/ProjectFuncs.cmake new file mode 100644 index 0000000..8ec3b05 --- /dev/null +++ b/speech2/cmake/ProjectFuncs.cmake @@ -0,0 +1,14 @@ +function(speech2_target target) + target_compile_definitions(${target} PRIVATE "$<$: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$<$:Debug>") + + # TODO option for this. + target_link_options(${target} PRIVATE + -Wl,/safeseh:no + -Xlinker /subsystem:console,${CMAKE_SYSTEM_VERSION} + ) +endfunction() diff --git a/speech2/cmake/clangcl-winxp.cmake b/speech2/cmake/clangcl-winxp.cmake index cd890b1..f010a0e 100644 --- a/speech2/cmake/clangcl-winxp.cmake +++ b/speech2/cmake/clangcl-winxp.cmake @@ -28,7 +28,7 @@ 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}") diff --git a/speech2/compdb.sh b/speech2/compdb.sh deleted file mode 100755 index 0643774..0000000 --- a/speech2/compdb.sh +++ /dev/null @@ -1,2 +0,0 @@ -make clean -bear -- make diff --git a/speech2/src/CMakeLists.txt b/speech2/src/CMakeLists.txt new file mode 100644 index 0000000..0ebf80d --- /dev/null +++ b/speech2/src/CMakeLists.txt @@ -0,0 +1,20 @@ +add_subdirectory(sapi4) + + +xp_init() + +add_executable(sapiserver + main.cpp + + winxp_compat_fwd.asm + winxp_compat.cpp +) + +speech2_target(sapiserver) + +target_link_libraries(sapiserver PRIVATE + speech2_sapi4 + + uuid.lib + ole32.lib +) diff --git a/speech2/src/main.cpp b/speech2/src/main.cpp new file mode 100644 index 0000000..7533e6f --- /dev/null +++ b/speech2/src/main.cpp @@ -0,0 +1,28 @@ +#include +#include + +#include + +#include "speechapi.hpp" + +int main() { + CoInitialize(nullptr); + + auto api = std::unique_ptr(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"); + + CoUninitialize(); + return 0; +} diff --git a/speech2/src/sapi4/CMakeLists.txt b/speech2/src/sapi4/CMakeLists.txt new file mode 100644 index 0000000..02d7f41 --- /dev/null +++ b/speech2/src/sapi4/CMakeLists.txt @@ -0,0 +1,8 @@ + +add_library(speech2_sapi4 + api_sapi4.cpp + guid_sapi4.cpp + audio_buffer.cpp +) + +speech2_target(speech2_sapi4) diff --git a/speech2/src/sapi4/api_sapi4.cpp b/speech2/src/sapi4/api_sapi4.cpp index 1e48bf5..0e5b10a 100644 --- a/speech2/src/sapi4/api_sapi4.cpp +++ b/speech2/src/sapi4/api_sapi4.cpp @@ -6,9 +6,6 @@ #include "sapi4/include/speech.h" #include "speechapi.hpp" -// stupid hacky but Whatevers - - struct SpeechAPI_SAPI4 : public ISpeechAPI { virtual ~SpeechAPI_SAPI4() { printf("~SpeechAPI_SAPI4\n"); @@ -31,32 +28,19 @@ struct SpeechAPI_SAPI4 : public ISpeechAPI { pEnum->Reset(); - printf("speech2: SpeechAPI_Sapi4::Initalize() created enum\n"); - - - printf("speech2: SpeechAPI_Sapi4::Initalize() starting enumvoices\n"); - // Fill out voices EnumVoices(); - printf("speech2: SpeechAPI_Sapi4::Initalize() end enumvoices\n"); - - printf("speech2: SpeechAPI_Sapi4::Initalize() end\n"); - return S_OK; } void EnumVoices() { - static TTSMODEINFO found{}; + TTSMODEINFO found{}; while(!pEnum->Next(1, &found, nullptr)) { - auto ptr = _strdup(found.szModeName); - printf("EnumVoices() voice %s\n", found.szModeName); - voices.push_back({found.gModeID, ptr}); + voices.push_back({found.gModeID, found.szModeName}); } - printf("EnumVoices() end\n"); - pEnum->Reset(); } @@ -66,6 +50,7 @@ struct SpeechAPI_SAPI4 : public ISpeechAPI { HRESULT SelectVoiceImpl(const GUID& guid) { pAudioOut = new AudioOutBuffer(); + pAudioOut->AddRef(); if(pCentral) pCentral->Release(); diff --git a/speech2/src/speechapi.hpp b/speech2/src/speechapi.hpp index ab9b811..462cc33 100644 --- a/speech2/src/speechapi.hpp +++ b/speech2/src/speechapi.hpp @@ -1,22 +1,14 @@ #include #include +#include #include /// base class for access to text-to-speech APIs. struct ISpeechAPI { struct VoiceInfo { - GUID guid{}; // Optional. May not be filled out if th e - char* voiceName; - - //VoiceInfo(const VoiceInfo&) = delete; - //VoiceInfo(VoiceInfo&&) = delete; - - ~VoiceInfo() { - // Make this a lot less stupid at some point. - if(voiceName) - free(voiceName); - } + GUID guid{}; // Optional. May not be filled out if the API doesn't do guids + std::string voiceName; }; virtual ~ISpeechAPI() = default; diff --git a/speech2/src/winxp_compat.cpp b/speech2/src/winxp_compat.cpp new file mode 100644 index 0000000..a55f1cd --- /dev/null +++ b/speech2/src/winxp_compat.cpp @@ -0,0 +1,28 @@ +#include + +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(dwFlags); + return InitializeCriticalSectionAndSpinCount(lpCriticalSection, dwSpinCount); +} + +BOOL WINAPI LibInitalizeCriticalSectionEx(LPCRITICAL_SECTION lpCriticalSection, DWORD dwSpinCount, DWORD dwFlags) { + if(!pfnInitalizeCriticalSectionEx) { + pfnInitalizeCriticalSectionEx = + reinterpret_cast(GetProcAddress(GetModuleHandle("kernel32.dll"), "InitializeCriticalSectionEx")); + + // Compatibilty. + if(!pfnInitalizeCriticalSectionEx) + pfnInitalizeCriticalSectionEx = _InitalizeCriticalSectionEx_xp; + } + + return pfnInitalizeCriticalSectionEx(lpCriticalSection, dwSpinCount, dwFlags); +} +} diff --git a/speech2/src/winxp_compat_fwd.asm b/speech2/src/winxp_compat_fwd.asm new file mode 100644 index 0000000..56711b8 --- /dev/null +++ b/speech2/src/winxp_compat_fwd.asm @@ -0,0 +1,5 @@ +// This file declares forwarders for Windows imports which the newer VS 2022 CRT requires. + +.globl "__imp__InitializeCriticalSectionEx@12" +"__imp__InitializeCriticalSectionEx@12": + .long "_LibInitalizeCriticalSectionEx@12" diff --git a/speech2/testprog.cpp b/speech2/testprog.cpp deleted file mode 100644 index dbbcd9f..0000000 --- a/speech2/testprog.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include - -int main() { - printf("Hello, C++20 on Windows XP~\n"); - return 0; -}