diff --git a/CMakeLists.txt b/CMakeLists.txt index f96ec440..d3ef4310 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,7 @@ endif() add_executable(piper src/cpp/main.cpp src/cpp/piper.cpp) add_executable(test_piper src/cpp/test.cpp src/cpp/piper.cpp) +add_library(piperlib SHARED src/cpp/piperlib.cpp src/cpp/piper.cpp) # NOTE: external project prefix are shortened because of path length restrictions on Windows # NOTE: onnxruntime is pulled from piper-phonemize @@ -39,6 +40,7 @@ if(NOT DEFINED FMT_DIR) ) add_dependencies(piper fmt_external) add_dependencies(test_piper fmt_external) + add_dependencies(piperlib fmt_external) endif() # ---- spdlog --- @@ -50,10 +52,11 @@ if(NOT DEFINED SPDLOG_DIR) spdlog_external PREFIX "${CMAKE_CURRENT_BINARY_DIR}/s" URL "https://github.com/gabime/spdlog/archive/refs/tags/v${SPDLOG_VERSION}.zip" - CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${SPDLOG_DIR} + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${SPDLOG_DIR} -DCMAKE_BUILD_TYPE=Release ) add_dependencies(piper spdlog_external) add_dependencies(test_piper spdlog_external) + add_dependencies(piperlib spdlog_external) endif() # ---- piper-phonemize --- @@ -68,6 +71,7 @@ if(NOT DEFINED PIPER_PHONEMIZE_DIR) ) add_dependencies(piper piper_phonemize_external) add_dependencies(test_piper piper_phonemize_external) + add_dependencies(piperlib piper_phonemize_external) endif() # ---- Declare executable ---- @@ -104,6 +108,39 @@ target_include_directories(piper PUBLIC target_compile_definitions(piper PUBLIC _PIPER_VERSION=${piper_version}) + +# ---- Declare library ---- + +if((NOT MSVC) AND (NOT APPLE)) + # Linux flags + string(APPEND CMAKE_CXX_FLAGS " -Wall -Wextra -Wl,-rpath,'$ORIGIN'") + string(APPEND CMAKE_C_FLAGS " -Wall -Wextra") + target_link_libraries(piperlib -static-libgcc -static-libstdc++) + + set(PIPER_EXTRA_LIBRARIES "pthread") +endif() + +target_link_libraries(piperlib + fmt + spdlog + espeak-ng + piper_phonemize + onnxruntime + ${PIPER_EXTRA_LIBRARIES} +) + +target_link_directories(piperlib PUBLIC + ${FMT_DIR}/lib + ${SPDLOG_DIR}/lib + ${PIPER_PHONEMIZE_DIR}/lib +) + +target_include_directories(piperlib PUBLIC + ${FMT_DIR}/include + ${SPDLOG_DIR}/include + ${PIPER_PHONEMIZE_DIR}/include +) + # ---- Declare test ---- include(CTest) enable_testing() @@ -170,3 +207,8 @@ install( FILES ${PIPER_PHONEMIZE_DIR}/share/libtashkeel_model.ort DESTINATION ${CMAKE_INSTALL_PREFIX} ) + +install( + TARGETS piperlib + LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX} +) diff --git a/src/cpp/piperlib.cpp b/src/cpp/piperlib.cpp new file mode 100644 index 00000000..734bb34e --- /dev/null +++ b/src/cpp/piperlib.cpp @@ -0,0 +1,122 @@ +#include "piper.hpp" +#include "piperlib.hpp" +#include + +extern "C" +{ + eSpeakConfig* create_eSpeakConfig() { + return new eSpeakConfig(); + } + + void destroy_eSpeakConfig(eSpeakConfig* config) { + delete config; + } + + PiperConfig* create_PiperConfig(char* eSpeakDataPath) { + auto config = new PiperConfig(); + config->eSpeakDataPath = eSpeakDataPath; + return config; + } + + void destroy_PiperConfig(PiperConfig* config) { + delete config; + } + + PhonemizeConfig* create_PhonemizeConfig() { + return new PhonemizeConfig(); + } + + void destroy_PhonemizeConfig(PhonemizeConfig* config) { + delete config; + } + + SynthesisConfig* create_SynthesisConfig() { + return new SynthesisConfig(); + } + + void destroy_SynthesisConfig(SynthesisConfig* config) { + delete config; + } + + ModelConfig* create_ModelConfig() { + return new ModelConfig(); + } + + void destroy_ModelConfig(ModelConfig* config) { + delete config; + } + + ModelSession* create_ModelSession() { + return new ModelSession(); + } + + void destroy_ModelSession(ModelSession* config) { + delete config; + } + + SynthesisResult* create_SynthesisResult() { + return new SynthesisResult(); + } + + void destroy_SynthesisResult(SynthesisResult* config) { + delete config; + } + + Voice* create_Voice() { + return new Voice(); + } + + void destroy_Voice(Voice* voice) { + delete voice; + } + + bool isSingleCodepoint(const char* s) { + std::string str(s); + return piper::isSingleCodepoint(str); + } + + char32_t getCodepoint(const char* s) { + return piper::getCodepoint(s); + } + + char* getVersion() { + auto version = piper::getVersion(); + char* cstr = new char[version.size() + 1]; + std::strcpy(cstr, version.c_str()); + return cstr; + } + + void initializePiper(PiperConfig* config) { + piper::initialize(*config); + } + + void terminatePiper(PiperConfig* config) { + piper::terminate(*config); + } + + SynthesisConfig getSynthesisConfig(Voice* voice) { + return voice->synthesisConfig; + } + + void loadVoice(PiperConfig* config, const char* modelPath, const char* modelConfigPath, Voice* voice, int64_t* speakerId) { + std::optional optSpeakerId; + if (speakerId) { + optSpeakerId = *speakerId; + } + piper::loadVoice(*config, modelPath, modelConfigPath, *voice, optSpeakerId); + } + + void textToAudio(PiperConfig* config, Voice* voice, const char* text, SynthesisResult* result, AudioCallback audioCallback) { + std::vector audioBuf; + piper::textToAudio(*config, *voice, text, audioBuf, *result, [&audioBuf, audioCallback] { audioCallback(audioBuf.data(), audioBuf.size()); }); + } + + void textToWavFile(PiperConfig* config, Voice* voice, const char* text, const char* audioFile, SynthesisResult* result) { + std::string audioFilePath = audioFile; + std::ofstream audioFileStream(audioFilePath, std::ios::binary); + piper::textToWavFile(*config, *voice, text, audioFileStream, *result); + audioFileStream << "opened"; + audioFileStream.flush(); + audioFileStream.close(); + } +} diff --git a/src/cpp/piperlib.hpp b/src/cpp/piperlib.hpp new file mode 100644 index 00000000..d7684d95 --- /dev/null +++ b/src/cpp/piperlib.hpp @@ -0,0 +1,42 @@ +#include +#include +#include "piper.hpp" + +using namespace piper; + +#if defined(_WIN32) && !defined(__MINGW32__) +# define PIPER_API __declspec(dllexport) +#else +# define PIPER_API __attribute__ ((visibility ("default"))) +#endif + +extern "C" { + typedef void (*AudioCallback)(int16_t* audioBuffer, int length); + + PIPER_API eSpeakConfig* create_eSpeakConfig(); + PIPER_API void destroy_eSpeakConfig(eSpeakConfig* config); + PIPER_API PiperConfig* create_PiperConfig(char* eSpeakDataPath); + PIPER_API void destroy_PiperConfig(PiperConfig* config); + PIPER_API PhonemizeConfig* create_PhonemizeConfig(); + PIPER_API void destroy_PhonemizeConfig(PhonemizeConfig* config); + PIPER_API SynthesisConfig* create_SynthesisConfig(); + PIPER_API void destroy_SynthesisConfig(SynthesisConfig* config); + PIPER_API ModelConfig* create_ModelConfig(); + PIPER_API void destroy_ModelConfig(ModelConfig* config); + PIPER_API ModelSession* create_ModelSession(); + PIPER_API void destroy_ModelSession(ModelSession* config); + PIPER_API SynthesisResult* create_SynthesisResult(); + PIPER_API void destroy_SynthesisResult(SynthesisResult* config); + PIPER_API Voice* create_Voice(); + PIPER_API void destroy_Voice(Voice* voice); + + PIPER_API bool isSingleCodepoint(const char* s); + PIPER_API char32_t getCodepoint(const char* s); + PIPER_API char* getVersion(); + PIPER_API void initializePiper(PiperConfig* config); + PIPER_API void terminatePiper(PiperConfig* config); + PIPER_API SynthesisConfig getSynthesisConfig(Voice* voice); + PIPER_API void loadVoice(PiperConfig* config, const char* modelPath, const char* modelConfigPath, Voice* voice, SpeakerId* speakerId); + PIPER_API void textToAudio(PiperConfig* config, Voice* voice, const char* text, SynthesisResult* result, AudioCallback audioCallback); + PIPER_API void textToWavFile(PiperConfig* config, Voice* voice, const char* text, const char* audioFile, SynthesisResult* result); +}