Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 21 additions & 4 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ option(VOICEDETECT_GGML_METAL "Forward GGML_METAL" OFF)
option(VOICEDETECT_GGML_VULKAN "Forward GGML_VULKAN" OFF)
option(VOICEDETECT_GGML_HIP "Forward GGML_HIP (ROCm)" OFF)

set(VOICEDETECT_TOP_LEVEL OFF)
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
set(VOICEDETECT_TOP_LEVEL ON)
endif()
option(VOICEDETECT_EXTERNAL_DR_WAV "Use the embedding project's dr_wav implementation" OFF)

# An embedding host owns its ggml configuration; everything up to the ggml
# add_subdirectory below only applies to a top-level build.
if(VOICEDETECT_TOP_LEVEL)

# Multi-ISA CPU backend with RUNTIME dispatch. ggml can build one CPU backend
# per x86 micro-arch (sse42/sandybridge/haswell/skylakex/icelake/zen4/...) as
# dlopen'd modules and pick the best one the host actually supports AT LOAD TIME.
Expand Down Expand Up @@ -130,7 +140,11 @@ if(BASH_EXECUTABLE AND EXISTS "${CMAKE_SOURCE_DIR}/scripts/apply_ggml_patches.sh
endif()
endif()

add_subdirectory(third_party/ggml)
endif() # VOICEDETECT_TOP_LEVEL

if(NOT TARGET ggml)
add_subdirectory(third_party/ggml)
endif()

set(VOICEDETECT_SRC
src/voicedetect.cpp
Expand Down Expand Up @@ -159,9 +173,12 @@ if(VOICEDETECT_SHARED)
else()
add_library(voicedetect STATIC ${VOICEDETECT_SRC})
endif()
target_include_directories(voicedetect PUBLIC include PRIVATE src ${CMAKE_SOURCE_DIR}/third_party)
target_include_directories(voicedetect PUBLIC include PRIVATE src ${CMAKE_CURRENT_SOURCE_DIR}/third_party)
target_compile_definitions(voicedetect PUBLIC $<$<CXX_COMPILER_ID:MSVC>:_USE_MATH_DEFINES>)
target_link_libraries(voicedetect PUBLIC ggml)
if(VOICEDETECT_EXTERNAL_DR_WAV)
target_compile_definitions(voicedetect PRIVATE VOICEDETECT_EXTERNAL_DR_WAV)
endif()
if(VOICEDETECT_GGML_CUDNN)
target_compile_definitions(voicedetect PRIVATE VOICEDETECT_GGML_CUDNN)
endif()
Expand Down Expand Up @@ -218,10 +235,10 @@ if(VOICEDETECT_HAS_AVX2)
endif()
endif()

if(VOICEDETECT_BUILD_CLI)
if(VOICEDETECT_BUILD_CLI AND VOICEDETECT_TOP_LEVEL)
add_subdirectory(examples/cli)
endif()
if(VOICEDETECT_BUILD_TESTS)
if(VOICEDETECT_BUILD_TESTS AND VOICEDETECT_TOP_LEVEL)
enable_testing()
add_subdirectory(tests)
endif()
2 changes: 1 addition & 1 deletion examples/cli/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
add_executable(voicedetect-cli main.cpp)
target_link_libraries(voicedetect-cli PRIVATE voicedetect)
target_include_directories(voicedetect-cli PRIVATE ${CMAKE_SOURCE_DIR}/src)
target_include_directories(voicedetect-cli PRIVATE ${PROJECT_SOURCE_DIR}/src)
5 changes: 5 additions & 0 deletions include/voicedetect_capi.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ int voicedetect_capi_verify_paths(voicedetect_ctx* ctx, const char* a,
char* voicedetect_capi_analyze_path_json(voicedetect_ctx* ctx,
const char* wav_path);

// Size of the L2-normalized embedding this model produces (192 to 512), 0 for a
// model with no speaker embedding (the age/gender/emotion analyze heads), -1 for
// a NULL ctx. Additive: no ABI version bump.
int voicedetect_capi_embedding_dim(voicedetect_ctx* ctx);

#ifdef __cplusplus
} // extern "C"
#endif
Expand Down
4 changes: 3 additions & 1 deletion src/audio_io.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
#define DR_WAV_IMPLEMENTATION
#ifndef VOICEDETECT_EXTERNAL_DR_WAV
#define DR_WAV_IMPLEMENTATION // an embedding project may provide it instead
#endif
#include "dr_wav.h"
#include "audio_io.hpp"
#include "common.hpp"
Expand Down
5 changes: 5 additions & 0 deletions src/voicedetect_capi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ extern "C" int voicedetect_capi_abi_version(void) {
return VOICEDETECT_CAPI_ABI_VERSION;
}

extern "C" int voicedetect_capi_embedding_dim(voicedetect_ctx* ctx) {
if (!ctx || !ctx->model) return -1;
return ctx->model->embedding_dim();
}

extern "C" voicedetect_ctx* voicedetect_capi_load(const char* gguf_path) {
if (!gguf_path) return nullptr;
try {
Expand Down
25 changes: 13 additions & 12 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
function(vd_add_test name)
add_executable(${name} ${name}.cpp)
target_link_libraries(${name} PRIVATE voicedetect)
target_include_directories(${name} PRIVATE ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/third_party)
target_include_directories(${name} PRIVATE ${PROJECT_SOURCE_DIR}/src ${PROJECT_SOURCE_DIR}/third_party)
add_test(NAME ${name} COMMAND ${name})
set_tests_properties(${name} PROPERTIES SKIP_RETURN_CODE 77)
endfunction()

vd_add_test(test_smoke)
vd_add_test(test_capi_dim)
vd_add_test(test_fbank)
vd_add_test(test_encoder)
vd_add_test(test_embed)
Expand All @@ -19,37 +20,37 @@ vd_add_test(test_cache_safety)
# Manual benchmark target (not registered as a ctest; run directly).
add_executable(bench_wespeaker bench_wespeaker.cpp)
target_link_libraries(bench_wespeaker PRIVATE voicedetect)
target_include_directories(bench_wespeaker PRIVATE ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/third_party)
target_include_directories(bench_wespeaker PRIVATE ${PROJECT_SOURCE_DIR}/src ${PROJECT_SOURCE_DIR}/third_party)

add_executable(bench_eres2net bench_eres2net.cpp)
target_link_libraries(bench_eres2net PRIVATE voicedetect)
target_include_directories(bench_eres2net PRIVATE ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/third_party)
target_include_directories(bench_eres2net PRIVATE ${PROJECT_SOURCE_DIR}/src ${PROJECT_SOURCE_DIR}/third_party)

# Model/baseline-dependent tests read fixtures/baselines via paths relative to
# the project root, and skip (exit 77) when the baseline env var is unset.
set_tests_properties(test_fbank test_encoder test_embed test_verify test_analyze test_age_gender test_cache_safety PROPERTIES
LABELS "model"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})

# Python converter check (skips with exit 77 when the venv/model are absent).
find_program(VOICEDETECT_PY NAMES python3 HINTS ${CMAKE_SOURCE_DIR}/.venv/bin)
find_program(VOICEDETECT_PY NAMES python3 HINTS ${PROJECT_SOURCE_DIR}/.venv/bin)
if(VOICEDETECT_PY)
add_test(NAME check_convert COMMAND ${VOICEDETECT_PY} ${CMAKE_SOURCE_DIR}/tests/python/check_convert.py)
add_test(NAME check_convert COMMAND ${VOICEDETECT_PY} ${PROJECT_SOURCE_DIR}/tests/python/check_convert.py)
set_tests_properties(check_convert PROPERTIES SKIP_RETURN_CODE 77 LABELS "model")
# Analyze (wav2vec2 emotion) converter check: a SEPARATE analyze GGUF, skips
# independently of the speaker-encoder check when its HF model is absent.
add_test(NAME check_convert_analyze COMMAND ${VOICEDETECT_PY} ${CMAKE_SOURCE_DIR}/tests/python/check_convert.py --analyze)
add_test(NAME check_convert_analyze COMMAND ${VOICEDETECT_PY} ${PROJECT_SOURCE_DIR}/tests/python/check_convert.py --analyze)
set_tests_properties(check_convert_analyze PROPERTIES SKIP_RETURN_CODE 77 LABELS "model")
add_test(NAME check_baseline_analyze COMMAND ${VOICEDETECT_PY} ${CMAKE_SOURCE_DIR}/tests/python/check_baseline.py --analyze)
add_test(NAME check_baseline_analyze COMMAND ${VOICEDETECT_PY} ${PROJECT_SOURCE_DIR}/tests/python/check_baseline.py --analyze)
set_tests_properties(check_baseline_analyze PROPERTIES SKIP_RETURN_CODE 77 LABELS "model")
# Age/gender (audeering wav2vec2-large-robust) converter + baseline checks: a
# SEPARATE analyze GGUF, skips independently when its HF model is absent.
add_test(NAME check_convert_age_gender COMMAND ${VOICEDETECT_PY} ${CMAKE_SOURCE_DIR}/tests/python/check_convert.py --age-gender)
add_test(NAME check_convert_age_gender COMMAND ${VOICEDETECT_PY} ${PROJECT_SOURCE_DIR}/tests/python/check_convert.py --age-gender)
set_tests_properties(check_convert_age_gender PROPERTIES SKIP_RETURN_CODE 77 LABELS "model")
add_test(NAME check_baseline_age_gender COMMAND ${VOICEDETECT_PY} ${CMAKE_SOURCE_DIR}/tests/python/check_baseline.py --age-gender)
add_test(NAME check_baseline_age_gender COMMAND ${VOICEDETECT_PY} ${PROJECT_SOURCE_DIR}/tests/python/check_baseline.py --age-gender)
set_tests_properties(check_baseline_age_gender PROPERTIES SKIP_RETURN_CODE 77 LABELS "model")
add_test(NAME check_manifest COMMAND ${VOICEDETECT_PY} ${CMAKE_SOURCE_DIR}/tests/python/check_manifest.py)
add_test(NAME check_manifest COMMAND ${VOICEDETECT_PY} ${PROJECT_SOURCE_DIR}/tests/python/check_manifest.py)
set_tests_properties(check_manifest PROPERTIES SKIP_RETURN_CODE 77 LABELS "model")
add_test(NAME check_baseline COMMAND ${VOICEDETECT_PY} ${CMAKE_SOURCE_DIR}/tests/python/check_baseline.py)
add_test(NAME check_baseline COMMAND ${VOICEDETECT_PY} ${PROJECT_SOURCE_DIR}/tests/python/check_baseline.py)
set_tests_properties(check_baseline PROPERTIES SKIP_RETURN_CODE 77 LABELS "model")
endif()
32 changes: 32 additions & 0 deletions tests/test_capi_dim.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// voicedetect_capi_embedding_dim: NULL ctx is -1; with VOICEDETECT_TEST_GGUF set,
// it matches the length embed_pcm returns. Skips (77) without a model.
#include "voicedetect_capi.h"

#include <cstdio>
#include <cstdlib>
#include <vector>

int main() {
if (voicedetect_capi_embedding_dim(nullptr) != -1) {
std::fprintf(stderr, "FAIL: NULL ctx should give -1\n");
return 1;
}
const char* path = std::getenv("VOICEDETECT_TEST_GGUF");
if (!path) return 77;
voicedetect_ctx* ctx = voicedetect_capi_load(path);
if (!ctx) { std::fprintf(stderr, "FAIL: load %s\n", path); return 1; }
const int dim = voicedetect_capi_embedding_dim(ctx);
std::vector<float> pcm(32000);
for (size_t i = 0; i < pcm.size(); ++i) pcm[i] = 0.1f * (float)((i * 37) % 200 - 100) / 100.0f;
float* v = nullptr;
int n = 0;
const int rc = voicedetect_capi_embed_pcm(ctx, pcm.data(), (int)pcm.size(), 16000, &v, &n);
voicedetect_capi_free_vec(v);
voicedetect_capi_free(ctx);
if (rc != 0 || dim <= 0 || dim != n) {
std::fprintf(stderr, "FAIL: rc=%d dim=%d embed len=%d\n", rc, dim, n);
return 1;
}
std::printf("embedding_dim OK (%d)\n", dim);
return 0;
}
Loading