Skip to content

Commit

Permalink
UI v1. Text edits are too bad, will have to switch framework.
Browse files Browse the repository at this point in the history
  • Loading branch information
Philippe Groarke committed Mar 26, 2020
1 parent 05ff571 commit dfa708a
Show file tree
Hide file tree
Showing 11 changed files with 1,062 additions and 89 deletions.
4 changes: 2 additions & 2 deletions CMakeLists.conan.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
cmake_minimum_required (VERSION 3.10)
project(conan-setup NONE)

execute_process(COMMAND conan install ${CMAKE_CURRENT_SOURCE_DIR} --build missing -s build_type=Debug)
execute_process(COMMAND conan install ${CMAKE_CURRENT_SOURCE_DIR} --build missing -s build_type=Release)
execute_process(COMMAND conan install ${CMAKE_CURRENT_SOURCE_DIR} --build missing -s build_type=Debug --settings compiler.runtime=MT)
execute_process(COMMAND conan install ${CMAKE_CURRENT_SOURCE_DIR} --build missing -s build_type=Release --settings compiler.runtime=MT)
23 changes: 7 additions & 16 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
cmake_policy(SET CMP0091 NEW)
include(${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.conan.txt)
cmake_minimum_required (VERSION 3.10)
cmake_minimum_required (VERSION 3.15)
project(wsay VERSION 1.2.0 LANGUAGES CXX)

include(GNUInstallDirs)
Expand Down Expand Up @@ -36,18 +36,6 @@ set(DEPENDENCY_FOLDER "Dependencies")
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
set_property(GLOBAL PROPERTY PREDEFINED_TARGETS_FOLDER ${DEPENDENCY_FOLDER})

# Static MSVC runtimes
set(CompilerFlags
CMAKE_CXX_FLAGS
CMAKE_CXX_FLAGS_DEBUG
CMAKE_CXX_FLAGS_RELEASE
CMAKE_C_FLAGS
CMAKE_C_FLAGS_DEBUG
CMAKE_C_FLAGS_RELEASE
)
foreach(CompilerFlag ${CompilerFlags})
string(REPLACE "/MD" "/MT" ${CompilerFlag} "${${CompilerFlag}}")
endforeach()

# Compile Options
function(set_compile_options REQUIRED_ARG)
Expand All @@ -60,12 +48,13 @@ function(set_compile_options REQUIRED_ARG)
elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
target_compile_definitions(${REQUIRED_ARG} PUBLIC NOMINMAX)
target_compile_options(${REQUIRED_ARG} PRIVATE /Zc:__cplusplus /Zc:alignedNew /permissive- /W4 /WX)
set_target_properties(${REQUIRED_ARG} PROPERTIES MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
endif()
endfunction()


##
# libwsay
# libwsay MT
##
set(LIB_NAME lib${PROJECT_NAME})
file(GLOB_RECURSE LIB_HEADERS "libinclude/*.hpp" "libinclude/*.h" "libinclude/*.tpp")
Expand Down Expand Up @@ -106,15 +95,17 @@ target_link_libraries(${PROJECT_NAME} PRIVATE ${LIB_NAME})
# wsay_gui
##
find_package(nuklear CONFIG REQUIRED)
find_package(glfw CONFIG REQUIRED)
find_package(glad CONFIG REQUIRED)
set(GUITOOL_NAME ${PROJECT_NAME}_gui)
file(GLOB_RECURSE GUITOOL_SOURCES "src_gui/*.cpp" "src_gui/*.c" "src_gui/*.hpp" "src_gui/*.h" "src_gui/*.tpp")
add_executable(${GUITOOL_NAME} ${GUITOOL_SOURCES})
add_executable(${GUITOOL_NAME} WIN32 ${GUITOOL_SOURCES})
target_include_directories(${GUITOOL_NAME} PRIVATE src_gui) # For based paths.
target_include_directories(${GUITOOL_NAME} PRIVATE ${LIB_INCLUDE_DIR})
set_compile_options(${GUITOOL_NAME})
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ${GUITOOL_NAME})
add_definitions(-DWSAY_VERSION="${PROJECT_VERSION}")
target_link_libraries(${GUITOOL_NAME} PRIVATE ${LIB_NAME} nuklear::nuklear)
target_link_libraries(${GUITOOL_NAME} PRIVATE ${LIB_NAME} nuklear::nuklear glfw::glfw glad::glad)


##
Expand Down
6 changes: 6 additions & 0 deletions conanfile.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
[requires]
gtest/1.10.0@_/_
nuklear/4.01.7@_/_
glfw/3.3@bincrafters/stable
glad/0.1.33@_/_

[generators]
cmake_find_package_multi

[options]
glad:gl_profile=core
glad:gl_version=4.5
glad:spec=gl
glad:no_loader=False
gtest:build_gmock=False

[imports]
Expand Down
8 changes: 5 additions & 3 deletions libinclude/wsay/wsay.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ struct voice {
size_t available_devices_size() const;
std::vector<std::wstring> available_devices() const;

void add_file_output(const std::filesystem::path& path);
void add_device_playback(size_t device_idx);
void start_file_output(const std::filesystem::path& path);
void stop_file_output();

void enable_device_playback(size_t device_idx);
void disable_device_playback(size_t device_idx);

// Call this once all outputs have been added.
void select_voice(size_t voice_idx);

// Speaks the sentence using selected voice to playback outputs and file.
Expand Down
195 changes: 131 additions & 64 deletions libsrc/wsay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ extern CComModule _Module;

#include <algorithm>
#include <fea_utils/fea_utils.hpp>
#include <functional>
#include <wil/resource.h>
#include <wil/result.h>

Expand Down Expand Up @@ -93,37 +94,6 @@ get_playback_devices() {
return ret;
}

CComPtr<ISpVoice> create_voice() {
CComPtr<ISpVoice> ret;
if (!SUCCEEDED(ret.CoCreateInstance(CLSID_SpVoice))) {
fprintf(stderr, "Couldn't initialize voice.\n");
std::exit(-1);
}
return ret;
}

void setup_fileout(CComPtr<ISpVoice>& voice,
const std::filesystem::path& output_filepath) {
CComPtr<ISpStream> cpStream;
CSpStreamFormat cAudioFmt;
if (!SUCCEEDED(cAudioFmt.AssignFormat(SPSF_44kHz16BitMono))) {
fprintf(stderr, "Couldn't set audio format (44kHz, 16bit, mono).\n");
std::exit(-1);
}

std::wstring fileout = output_filepath.wstring();

if (!SUCCEEDED(SPBindToFile(fileout.c_str(), SPFM_CREATE_ALWAYS, &cpStream,
&cAudioFmt.FormatId(), cAudioFmt.WaveFormatExPtr()))) {
fprintf(stderr, "Couldn't bind stream to file.\n");
std::exit(-1);
}

if (!SUCCEEDED(voice->SetOutput(cpStream, TRUE))) {
fprintf(stderr, "Couldn't set voice output.\n");
std::exit(-1);
}
}

} // namespace

Expand Down Expand Up @@ -168,6 +138,60 @@ bool parse_text_file(
return true;
}

struct voice_data {
// Creates default voice.
voice_data(ISpObjectToken* selected_voice) {
if (!SUCCEEDED(voice_ptr.CoCreateInstance(CLSID_SpVoice))) {
throw std::runtime_error{ "Couldn't initialize voice." };
}

voice_ptr->SetVoice(selected_voice);
}

// Creates file out voice.
voice_data(ISpObjectToken* selected_voice,
const std::filesystem::path& filepath)
: voice_data(selected_voice) {
path = filepath;

CComPtr<ISpStream> cpStream;
CSpStreamFormat cAudioFmt;
if (!SUCCEEDED(cAudioFmt.AssignFormat(SPSF_44kHz16BitMono))) {
throw std::runtime_error{
"Couldn't set audio format (44kHz, 16bit, mono)."
};
}

std::wstring fileout = path.wstring();

if (!SUCCEEDED(SPBindToFile(fileout.c_str(), SPFM_CREATE_ALWAYS,
&cpStream, &cAudioFmt.FormatId(),
cAudioFmt.WaveFormatExPtr()))) {
throw std::runtime_error{ "Couldn't bind stream to file." };
}

if (!SUCCEEDED(voice_ptr->SetOutput(cpStream, TRUE))) {
throw std::runtime_error{ "Couldn't set voice output." };
}
}

voice_data(ISpObjectToken* selected_voice, size_t device_idx,
ISpObjectToken* device)
: voice_data(selected_voice) {

device_playback_idx = device_idx;
voice_ptr->SetOutput(device, TRUE);
}

bool is_default() const {
return device_playback_idx == std::numeric_limits<size_t>::max()
&& path.empty();
}

CComPtr<ISpVoice> voice_ptr;
size_t device_playback_idx = std::numeric_limits<size_t>::max();
std::filesystem::path path;
};

struct voice_impl {
voice_impl() {
Expand All @@ -184,16 +208,31 @@ struct voice_impl {

available_devices = get_playback_devices();

voices.push_back(create_voice());
voices.push_back({ nullptr });
}

template <class Func>
void execute(Func func) {
if (voices.size() == 1) {
std::invoke(func, voices.back().voice_ptr);
return;
}

for (voice_data& v : voices) {
if (v.is_default()) {
continue;
}
std::invoke(func, v.voice_ptr);
}
}

std::vector<std::pair<std::wstring, CComPtr<ISpObjectToken>>>
available_voices;
std::vector<std::pair<std::wstring, CComPtr<ISpObjectToken>>>
available_devices;

std::vector<CComPtr<ISpVoice>> voices;
bool contains_default_voice = true;
std::vector<voice_data> voices;
size_t selected_voice = 0;
};


Expand Down Expand Up @@ -256,70 +295,98 @@ std::vector<std::wstring> voice::available_devices() const {
return ret;
}

void voice::add_file_output(const std::filesystem::path& path) {
if (_impl->contains_default_voice) {
_impl->voices.clear();
_impl->contains_default_voice = false;
void voice::start_file_output(const std::filesystem::path& path) {
if (path.empty()) {
throw std::invalid_argument{ "voice : Ouput filepath is empty." };
}

_impl->voices.push_back(
{ _impl->available_voices[_impl->selected_voice].second, path });
}

void voice::stop_file_output() {
auto it = std::find_if(_impl->voices.begin(), _impl->voices.end(),
[&](const voice_data& v) { return !v.path.empty(); });

if (it == _impl->voices.end()) {
return;
}

if (path.empty()) {
throw std::runtime_error{ "voice : Ouput filepath is empty." };
_impl->voices.erase(it);
}

void voice::enable_device_playback(size_t device_idx) {
if (device_idx >= _impl->available_devices.size()) {
throw std::out_of_range{
"voice : Selected device index out-of-range."
};
}

for (voice_data& v : _impl->voices) {
v.voice_ptr->Speak(
L"", SPF_DEFAULT | SPF_PURGEBEFORESPEAK | SPF_ASYNC, nullptr);
}

_impl->voices.push_back(create_voice());
setup_fileout(_impl->voices.back(), path);
_impl->voices.push_back(
{ _impl->available_voices[_impl->selected_voice].second, device_idx,
_impl->available_devices[device_idx].second });
}

void voice::add_device_playback(size_t device_idx) {
void voice::disable_device_playback(size_t device_idx) {
if (device_idx >= _impl->available_devices.size()) {
throw std::runtime_error{
throw std::out_of_range{
"voice : Selected device index out-of-range."
};
}

if (_impl->contains_default_voice) {
_impl->voices.clear();
_impl->contains_default_voice = false;
auto it = std::find_if(_impl->voices.begin(), _impl->voices.end(),
[&](const voice_data& v) {
return v.device_playback_idx == device_idx;
});

if (it == _impl->voices.end()) {
return;
}

_impl->voices.push_back(create_voice());
_impl->voices.back()->SetOutput(
_impl->available_devices[device_idx].second, TRUE);
it->voice_ptr->Speak(
L"", SPF_DEFAULT | SPF_PURGEBEFORESPEAK | SPF_ASYNC, nullptr);
_impl->voices.erase(it);
}

void voice::select_voice(size_t voice_idx) {
if (voice_idx >= _impl->available_voices.size()) {
throw std::runtime_error{
"voice : Selected voice index out-of-range."
};
throw std::out_of_range{ "voice : Selected voice index out-of-range." };
}

for (CComPtr<ISpVoice>& voice : _impl->voices) {
voice->SetVoice(_impl->available_voices[voice_idx].second);
_impl->selected_voice = voice_idx;
for (voice_data& v : _impl->voices) {
v.voice_ptr->Speak(
L"", SPF_DEFAULT | SPF_PURGEBEFORESPEAK | SPF_ASYNC, nullptr);
v.voice_ptr->SetVoice(
_impl->available_voices[_impl->selected_voice].second);
}
}

void voice::speak(const std::wstring& sentence) {
for (CComPtr<ISpVoice>& voice : _impl->voices) {
_impl->execute([&](CComPtr<ISpVoice>& voice) {
voice->Speak(sentence.c_str(), SPF_DEFAULT | SPF_ASYNC, nullptr);
}
});

for (CComPtr<ISpVoice>& voice : _impl->voices) {
voice->WaitUntilDone(INFINITE);
}
_impl->execute(
[&](CComPtr<ISpVoice>& voice) { voice->WaitUntilDone(INFINITE); });
}

void voice::speak_async(const std::wstring& sentence) {
for (CComPtr<ISpVoice>& voice : _impl->voices) {
_impl->execute([&](CComPtr<ISpVoice>& voice) {
voice->Speak(sentence.c_str(), SPF_DEFAULT | SPF_ASYNC, nullptr);
}
});
}

void voice::stop_speaking() {
for (CComPtr<ISpVoice>& voice : _impl->voices) {
_impl->execute([](CComPtr<ISpVoice>& voice) {
voice->Speak(
L"", SPF_DEFAULT | SPF_PURGEBEFORESPEAK | SPF_ASYNC, nullptr);
}
});
}

} // namespace wsy
4 changes: 2 additions & 2 deletions src_cmd/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ int main(int argc, char** argv) {
if (!f.empty()) {
filepath = { f };
}
voice.add_file_output(filepath);
voice.start_file_output(filepath);
return true;
},
"Outputs to wav file. Uses 'out.wav' if no filename is "
Expand Down Expand Up @@ -102,7 +102,7 @@ int main(int argc, char** argv) {
return false;
}

voice.add_device_playback(chosen_device - 1);
voice.enable_device_playback(chosen_device - 1);
}

return true;
Expand Down
Loading

0 comments on commit dfa708a

Please sign in to comment.