From d90d11837a654e31b1f39024e9c47748b73691b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Letz?= Date: Thu, 12 Feb 2026 22:55:44 +0100 Subject: [PATCH 1/5] Add static/shared NAM libraries and C API for external integration --- CMakeLists.txt | 81 ++++++++++++++++++++++- NAM/nam_c_api.cpp | 140 ++++++++++++++++++++++++++++++++++++++++ NAM/nam_c_api.h | 58 +++++++++++++++++ README.md | 34 ++++++++++ tools/CMakeLists.txt | 86 ++++++++++++------------ tools/c_api_example.cpp | 60 +++++++++++++++++ 6 files changed, 414 insertions(+), 45 deletions(-) create mode 100644 NAM/nam_c_api.cpp create mode 100644 NAM/nam_c_api.h create mode 100644 tools/c_api_example.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 5381d99..7e381bd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,7 +25,86 @@ else() endif() set(NAM_DEPS_PATH "${CMAKE_CURRENT_SOURCE_DIR}/Dependencies") -include_directories(SYSTEM "${NAM_DEPS_PATH}/eigen") + +set(NAM_CORE_SOURCES + NAM/activations.cpp + NAM/conv1d.cpp + NAM/convnet.cpp + NAM/dsp.cpp + NAM/get_dsp.cpp + NAM/lstm.cpp + NAM/ring_buffer.cpp + NAM/util.cpp + NAM/wavenet.cpp + NAM/nam_c_api.cpp +) + +add_library(nam_static STATIC ${NAM_CORE_SOURCES}) +add_library(nam_shared SHARED ${NAM_CORE_SOURCES}) +add_library(nam::static ALIAS nam_static) +add_library(nam::shared ALIAS nam_shared) + +set(NAM_LIBRARY_TARGETS nam_static nam_shared) +foreach(target ${NAM_LIBRARY_TARGETS}) + target_compile_features(${target} PUBLIC cxx_std_20) + target_include_directories(${target} + PUBLIC + $ + $ + PRIVATE + ${NAM_DEPS_PATH}/eigen + ${NAM_DEPS_PATH}/nlohmann + ) + set_target_properties(${target} + PROPERTIES + CXX_VISIBILITY_PRESET hidden + VISIBILITY_INLINES_HIDDEN YES + POSITION_INDEPENDENT_CODE ON + ) + if (MSVC) + target_compile_options(${target} PRIVATE + "$<$:/W4>" + "$<$:/O2>" + ) + else() + target_compile_options(${target} PRIVATE + -Wall -Wextra -Wpedantic -Wstrict-aliasing -Wunreachable-code -Weffc++ + -Wno-unused-parameter + "$<$:-Og;-ggdb;-Werror>" + "$<$:-Ofast>" + ) + endif() +endforeach() + +target_compile_definitions(nam_shared PRIVATE NAM_BUILD_SHARED) +set_target_properties(nam_static PROPERTIES OUTPUT_NAME nam) +set_target_properties(nam_shared PROPERTIES + OUTPUT_NAME nam + VERSION ${PROJECT_VERSION} + SOVERSION ${PROJECT_VERSION_MAJOR} +) + +if (CMAKE_SYSTEM_NAME STREQUAL "Windows") + set_target_properties(nam_shared PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON) + target_compile_definitions(nam_static PRIVATE NOMINMAX WIN32_LEAN_AND_MEAN) + target_compile_definitions(nam_shared PRIVATE NOMINMAX WIN32_LEAN_AND_MEAN) +endif() + +# There's an error in eigen's GeneralBlockPanelKernel.h in some debug builds. +set_source_files_properties(NAM/dsp.cpp PROPERTIES COMPILE_FLAGS "-Wno-error") +set_source_files_properties(NAM/conv1d.cpp PROPERTIES COMPILE_FLAGS "-Wno-error") + +install(TARGETS nam_static nam_shared EXPORT NAMTargets + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin +) +install(FILES NAM/nam_c_api.h DESTINATION include/NAM) +install(EXPORT NAMTargets + FILE NAMTargets.cmake + NAMESPACE nam:: + DESTINATION lib/cmake/NAM +) add_subdirectory(tools) diff --git a/NAM/nam_c_api.cpp b/NAM/nam_c_api.cpp new file mode 100644 index 0000000..35622c7 --- /dev/null +++ b/NAM/nam_c_api.cpp @@ -0,0 +1,140 @@ +#include "nam_c_api.h" + +#include +#include +#include +#include +#include + +#include "get_dsp.h" + +struct nam_model +{ + std::unique_ptr dsp; +}; + +namespace +{ +thread_local std::string g_last_error; + +void set_error(std::string message) +{ + g_last_error = std::move(message); +} + +nam_status_t handle_exception() +{ + try + { + throw; + } + catch (const std::exception& e) + { + set_error(e.what()); + } + catch (...) + { + set_error("Unknown exception"); + } + return NAM_STATUS_EXCEPTION; +} +} // namespace + +extern "C" +{ + +nam_status_t nam_create_model_from_file(const char* model_path, nam_model_t** out_model) +{ + if (model_path == nullptr || out_model == nullptr) + { + set_error("Invalid argument: model_path and out_model must be non-null."); + return NAM_STATUS_INVALID_ARGUMENT; + } + + *out_model = nullptr; + try + { + auto model = std::make_unique(); + model->dsp = nam::get_dsp(std::filesystem::path(model_path)); + *out_model = model.release(); + g_last_error.clear(); + return NAM_STATUS_OK; + } + catch (...) + { + return handle_exception(); + } +} + +void nam_destroy_model(nam_model_t* model) +{ + delete model; +} + +nam_status_t nam_reset(nam_model_t* model, double sample_rate, int max_buffer_size) +{ + if (model == nullptr || model->dsp == nullptr || max_buffer_size < 0) + { + set_error("Invalid argument: model must be valid and max_buffer_size must be >= 0."); + return NAM_STATUS_INVALID_ARGUMENT; + } + + try + { + model->dsp->Reset(sample_rate, max_buffer_size); + g_last_error.clear(); + return NAM_STATUS_OK; + } + catch (...) + { + return handle_exception(); + } +} + +nam_status_t nam_process(nam_model_t* model, nam_sample_t** input, nam_sample_t** output, int num_frames) +{ + if (model == nullptr || model->dsp == nullptr || input == nullptr || output == nullptr || num_frames < 0) + { + set_error("Invalid argument: model/input/output must be valid and num_frames must be >= 0."); + return NAM_STATUS_INVALID_ARGUMENT; + } + + try + { + model->dsp->process(input, output, num_frames); + g_last_error.clear(); + return NAM_STATUS_OK; + } + catch (...) + { + return handle_exception(); + } +} + +int nam_num_input_channels(const nam_model_t* model) +{ + if (model == nullptr || model->dsp == nullptr) + return 0; + return model->dsp->NumInputChannels(); +} + +int nam_num_output_channels(const nam_model_t* model) +{ + if (model == nullptr || model->dsp == nullptr) + return 0; + return model->dsp->NumOutputChannels(); +} + +double nam_expected_sample_rate(const nam_model_t* model) +{ + if (model == nullptr || model->dsp == nullptr) + return -1.0; + return model->dsp->GetExpectedSampleRate(); +} + +const char* nam_get_last_error(void) +{ + return g_last_error.c_str(); +} + +} // extern "C" diff --git a/NAM/nam_c_api.h b/NAM/nam_c_api.h new file mode 100644 index 0000000..8afb239 --- /dev/null +++ b/NAM/nam_c_api.h @@ -0,0 +1,58 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(_WIN32) + #if defined(NAM_BUILD_SHARED) + #define NAM_API __declspec(dllexport) + #elif defined(NAM_USE_SHARED) + #define NAM_API __declspec(dllimport) + #else + #define NAM_API + #endif +#elif defined(__GNUC__) || defined(__clang__) + #define NAM_API __attribute__((visibility("default"))) +#else + #define NAM_API +#endif + +#ifdef NAM_SAMPLE_FLOAT +typedef float nam_sample_t; +#else +typedef double nam_sample_t; +#endif + +typedef struct nam_model nam_model_t; + +typedef enum nam_status +{ + NAM_STATUS_OK = 0, + NAM_STATUS_INVALID_ARGUMENT = 1, + NAM_STATUS_EXCEPTION = 2 +} nam_status_t; + +// Returns a model handle loaded from a .nam file path. +NAM_API nam_status_t nam_create_model_from_file(const char* model_path, nam_model_t** out_model); + +// Releases a model handle created by nam_create_model_from_file. +NAM_API void nam_destroy_model(nam_model_t* model); + +// Resets DSP state for the given runtime sample rate and max block size. +NAM_API nam_status_t nam_reset(nam_model_t* model, double sample_rate, int max_buffer_size); + +// Processes deinterleaved channels: input[channel][frame], output[channel][frame]. +NAM_API nam_status_t nam_process(nam_model_t* model, nam_sample_t** input, nam_sample_t** output, + int num_frames); + +NAM_API int nam_num_input_channels(const nam_model_t* model); +NAM_API int nam_num_output_channels(const nam_model_t* model); +NAM_API double nam_expected_sample_rate(const nam_model_t* model); + +// Thread-local error message set when a NAM_STATUS_EXCEPTION is returned. +NAM_API const char* nam_get_last_error(void); + +#ifdef __cplusplus +} +#endif diff --git a/README.md b/README.md index f28bc97..a161d3f 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,40 @@ Core C++ DSP library for NAM plugins. For an example how to use, see [NeuralAmpModelerPlugin](https://github.com/sdatkinson/NeuralAmpModelerPlugin). +## Compiling + +Configure and build: + +- `cmake -S . -B build` +- `cmake --build build` + +Release builds: + +- Single-config generators (Unix Makefiles, Ninja): + - `cmake -S . -B build-release -DCMAKE_BUILD_TYPE=Release` + - `cmake --build build-release --target nam_static nam_shared tools` +- Multi-config generators (Visual Studio, Xcode): + - `cmake -S . -B build` + - `cmake --build build --config Release --target nam_static nam_shared tools` + +Build only the NAM libraries: + +- `cmake --build build --target nam_static` +- `cmake --build build --target nam_shared` + +The public C API for external projects is exposed by: + +- `NAM/nam_c_api.h` + +Simple example using the C API (linked against the static library): + +- build target: `cmake --build build --target c_api_example` +- run: `./build/tools/c_api_example example_models/wavenet.nam` + +You can also install libraries + C API header with: + +- `cmake --install build` + ## Testing A workflow for testing the library is provided in `.github/workflows/build.yml`. You should be able to run it locally to test if you'd like. diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 8118e08..4384089 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -1,18 +1,25 @@ -file(GLOB_RECURSE NAM_SOURCES ../NAM/*.cpp ../NAM/*.c ../NAM*.h) - -# TODO: add loadmodel and run_tests to TOOLS? -set(TOOLS benchmodel) +set(TOOLS + loadmodel + benchmodel + c_api_example + run_tests +) -add_custom_target(tools ALL - DEPENDS ${TOOLS}) +add_custom_target(tools ALL DEPENDS ${TOOLS}) -include_directories(tools ..) -include_directories(tools ${NAM_DEPS_PATH}/eigen) -include_directories(tools ${NAM_DEPS_PATH}/nlohmann) +add_executable(loadmodel loadmodel.cpp) +add_executable(benchmodel benchmodel.cpp) +add_executable(c_api_example c_api_example.cpp) +add_executable(run_tests run_tests.cpp test/allocation_tracking.cpp) -add_executable(loadmodel loadmodel.cpp ${NAM_SOURCES}) -add_executable(benchmodel benchmodel.cpp ${NAM_SOURCES}) -add_executable(run_tests run_tests.cpp test/allocation_tracking.cpp ${NAM_SOURCES}) +foreach(target ${TOOLS}) + target_link_libraries(${target} PRIVATE nam_static) + target_include_directories(${target} PRIVATE + .. + ${NAM_DEPS_PATH}/eigen + ${NAM_DEPS_PATH}/nlohmann + ) +endforeach() # Compile run_tests without optimizations to ensure allocation tracking works correctly # Also ensure assertions are enabled (NDEBUG is not defined) so tests actually run set_target_properties(run_tests PROPERTIES COMPILE_OPTIONS "-O0") @@ -29,36 +36,27 @@ if(ENABLE_ASSERT_TEST) target_compile_definitions(run_tests PRIVATE ADDASSERT) endif() -source_group(NAM ${CMAKE_CURRENT_SOURCE_DIR} FILES ${NAM_SOURCES}) - -target_compile_features(${TOOLS} PUBLIC cxx_std_20) - -set_target_properties(${TOOLS} - PROPERTIES - CXX_VISIBILITY_PRESET hidden - INTERPROCEDURAL_OPTIMIZATION TRUE - PREFIX "" -) - -if (CMAKE_SYSTEM_NAME STREQUAL "Windows") - target_compile_definitions(${TOOLS} PRIVATE NOMINMAX WIN32_LEAN_AND_MEAN) -endif() - -if (MSVC) - target_compile_options(${TOOLS} PRIVATE - "$<$:/W4>" - "$<$:/O2>" +foreach(target ${TOOLS}) + target_compile_features(${target} PUBLIC cxx_std_20) + set_target_properties(${target} + PROPERTIES + CXX_VISIBILITY_PRESET hidden + INTERPROCEDURAL_OPTIMIZATION TRUE + PREFIX "" ) -else() - target_compile_options(${TOOLS} PRIVATE - -Wall -Wextra -Wpedantic -Wstrict-aliasing -Wunreachable-code -Weffc++ -Wno-unused-parameter - "$<$:-Og;-ggdb;-Werror>" - "$<$:-Ofast>" - ) -endif() - -# There's an error in eigen's -# /Users/steve/src/NeuralAmpModelerCore/Dependencies/eigen/Eigen/src/Core/products/GeneralBlockPanelKernel.h -# Don't let this break my build on debug: -set_source_files_properties(../NAM/dsp.cpp PROPERTIES COMPILE_FLAGS "-Wno-error") -set_source_files_properties(../NAM/conv1d.cpp PROPERTIES COMPILE_FLAGS "-Wno-error") \ No newline at end of file + if (CMAKE_SYSTEM_NAME STREQUAL "Windows") + target_compile_definitions(${target} PRIVATE NOMINMAX WIN32_LEAN_AND_MEAN) + endif() + if (MSVC) + target_compile_options(${target} PRIVATE + "$<$:/W4>" + "$<$:/O2>" + ) + else() + target_compile_options(${target} PRIVATE + -Wall -Wextra -Wpedantic -Wstrict-aliasing -Wunreachable-code -Weffc++ -Wno-unused-parameter + "$<$:-Og;-ggdb;-Werror>" + "$<$:-Ofast>" + ) + endif() +endforeach() diff --git a/tools/c_api_example.cpp b/tools/c_api_example.cpp new file mode 100644 index 0000000..5e2cc30 --- /dev/null +++ b/tools/c_api_example.cpp @@ -0,0 +1,60 @@ +#include +#include +#include + +#include "NAM/nam_c_api.h" + +int main(int argc, char* argv[]) +{ + if (argc < 2) + { + std::fprintf(stderr, "Usage: c_api_example \n"); + return 1; + } + + nam_model_t* model = nullptr; + nam_status_t status = nam_create_model_from_file(argv[1], &model); + if (status != NAM_STATUS_OK || model == nullptr) + { + std::fprintf(stderr, "Failed to load model: %s\n", nam_get_last_error()); + return 1; + } + + const double sample_rate = 48000.0; + const int block_size = 64; + status = nam_reset(model, sample_rate, block_size); + if (status != NAM_STATUS_OK) + { + std::fprintf(stderr, "nam_reset failed: %s\n", nam_get_last_error()); + nam_destroy_model(model); + return 1; + } + + const int in_channels = std::max(1, nam_num_input_channels(model)); + const int out_channels = std::max(1, nam_num_output_channels(model)); + + std::vector> input_storage(in_channels, std::vector(block_size, 0.0)); + std::vector> output_storage(out_channels, std::vector(block_size, 0.0)); + + std::vector input(in_channels); + std::vector output(out_channels); + for (int ch = 0; ch < in_channels; ++ch) + input[ch] = input_storage[ch].data(); + for (int ch = 0; ch < out_channels; ++ch) + output[ch] = output_storage[ch].data(); + + status = nam_process(model, input.data(), output.data(), block_size); + if (status != NAM_STATUS_OK) + { + std::fprintf(stderr, "nam_process failed: %s\n", nam_get_last_error()); + nam_destroy_model(model); + return 1; + } + + std::printf("Model loaded with %d input(s), %d output(s), expected SR: %.1f\n", in_channels, out_channels, + nam_expected_sample_rate(model)); + std::printf("Processed %d frames. First output sample: %f\n", block_size, static_cast(output[0][0])); + + nam_destroy_model(model); + return 0; +} From b087bf0344d1f9ecf50a33648120dfd86499d3dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Letz?= Date: Thu, 12 Feb 2026 23:07:49 +0100 Subject: [PATCH 2/5] Register DSP factories explicitly for static linking --- NAM/convnet.cpp | 8 ++++++-- NAM/convnet.h | 3 +++ NAM/get_dsp.cpp | 5 +++++ NAM/lstm.cpp | 9 ++++++--- NAM/lstm.h | 3 +++ NAM/wavenet.cpp | 9 ++++++--- NAM/wavenet.h | 3 +++ 7 files changed, 32 insertions(+), 8 deletions(-) diff --git a/NAM/convnet.cpp b/NAM/convnet.cpp index fc7c151..62111b6 100644 --- a/NAM/convnet.cpp +++ b/NAM/convnet.cpp @@ -1,6 +1,7 @@ #include // std::max_element #include #include // pow, tanh, expf +#include #include #include #include @@ -340,7 +341,10 @@ std::unique_ptr nam::convnet::Factory(const nlohmann::json& config, st in_channels, out_channels, channels, dilations, batchnorm, activation_config, weights, expectedSampleRate, groups); } -namespace +void nam::convnet::RegisterFactory() { -static nam::factory::Helper _register_ConvNet("ConvNet", nam::convnet::Factory); + static std::once_flag once; + std::call_once(once, []() { + nam::factory::FactoryRegistry::instance().registerFactory("ConvNet", nam::convnet::Factory); + }); } diff --git a/NAM/convnet.h b/NAM/convnet.h index 0d963df..d2c67a8 100644 --- a/NAM/convnet.h +++ b/NAM/convnet.h @@ -173,5 +173,8 @@ class ConvNet : public Buffer std::unique_ptr Factory(const nlohmann::json& config, std::vector& weights, const double expectedSampleRate); +/// \brief Register ConvNet factory in the global registry +void RegisterFactory(); + }; // namespace convnet }; // namespace nam diff --git a/NAM/get_dsp.cpp b/NAM/get_dsp.cpp index 57d0fbd..38f7d31 100644 --- a/NAM/get_dsp.cpp +++ b/NAM/get_dsp.cpp @@ -156,6 +156,11 @@ std::unique_ptr get_dsp(dspData& conf) { verify_config_version(conf.version); + // Explicit registration avoids missing factories when NAM is linked as a static library. + nam::lstm::RegisterFactory(); + nam::convnet::RegisterFactory(); + nam::wavenet::RegisterFactory(); + auto& architecture = conf.architecture; nlohmann::json& config = conf.config; std::vector& weights = conf.weights; diff --git a/NAM/lstm.cpp b/NAM/lstm.cpp index d162d55..e447407 100644 --- a/NAM/lstm.cpp +++ b/NAM/lstm.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -177,8 +178,10 @@ std::unique_ptr nam::lstm::Factory(const nlohmann::json& config, std:: in_channels, out_channels, num_layers, input_size, hidden_size, weights, expectedSampleRate); } -// Register the factory -namespace +void nam::lstm::RegisterFactory() { -static nam::factory::Helper _register_LSTM("LSTM", nam::lstm::Factory); + static std::once_flag once; + std::call_once(once, []() { + nam::factory::FactoryRegistry::instance().registerFactory("LSTM", nam::lstm::Factory); + }); } diff --git a/NAM/lstm.h b/NAM/lstm.h index d97de20..dffd255 100644 --- a/NAM/lstm.h +++ b/NAM/lstm.h @@ -103,5 +103,8 @@ class LSTM : public DSP std::unique_ptr Factory(const nlohmann::json& config, std::vector& weights, const double expectedSampleRate); +/// \brief Register LSTM factory in the global registry +void RegisterFactory(); + }; // namespace lstm }; // namespace nam diff --git a/NAM/wavenet.cpp b/NAM/wavenet.cpp index 6eb74a3..55fd5dd 100644 --- a/NAM/wavenet.cpp +++ b/NAM/wavenet.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -834,8 +835,10 @@ std::unique_ptr nam::wavenet::Factory(const nlohmann::json& config, st in_channels, layer_array_params, head_scale, with_head, weights, std::move(condition_dsp), expectedSampleRate); } -// Register the factory -namespace +void nam::wavenet::RegisterFactory() { -static nam::factory::Helper _register_WaveNet("WaveNet", nam::wavenet::Factory); + static std::once_flag once; + std::call_once(once, []() { + nam::factory::FactoryRegistry::instance().registerFactory("WaveNet", nam::wavenet::Factory); + }); } diff --git a/NAM/wavenet.h b/NAM/wavenet.h index 63e1378..c9c5923 100644 --- a/NAM/wavenet.h +++ b/NAM/wavenet.h @@ -720,5 +720,8 @@ class WaveNet : public DSP /// \return Unique pointer to a DSP object (WaveNet instance) std::unique_ptr Factory(const nlohmann::json& config, std::vector& weights, const double expectedSampleRate); + +/// \brief Register WaveNet factory in the global registry +void RegisterFactory(); }; // namespace wavenet }; // namespace nam From 2a41dc86dba076013a062c155f794ecae4e03d3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Letz?= Date: Thu, 12 Feb 2026 23:12:16 +0100 Subject: [PATCH 3/5] Add install targets for NAM components and tool binaries --- CMakeLists.txt | 14 +++++++++++++- README.md | 13 +++++++++++++ tools/CMakeLists.txt | 5 +++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7e381bd..8fae3e7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -98,16 +98,28 @@ install(TARGETS nam_static nam_shared EXPORT NAMTargets ARCHIVE DESTINATION lib LIBRARY DESTINATION lib RUNTIME DESTINATION bin + COMPONENT nam ) -install(FILES NAM/nam_c_api.h DESTINATION include/NAM) +install(FILES NAM/nam_c_api.h DESTINATION include/NAM COMPONENT nam) install(EXPORT NAMTargets FILE NAMTargets.cmake NAMESPACE nam:: DESTINATION lib/cmake/NAM + COMPONENT nam ) add_subdirectory(tools) +add_custom_target(install_nam + COMMAND ${CMAKE_COMMAND} --install ${CMAKE_BINARY_DIR} --config $ --component nam + COMMENT "Install NAM libraries and C API" +) + +add_custom_target(install_tools + COMMAND ${CMAKE_COMMAND} --install ${CMAKE_BINARY_DIR} --config $ --component tools + COMMENT "Install NAM tool binaries" +) + #file(MAKE_DIRECTORY build/tools) #add_custom_target(copy_tools ALL diff --git a/README.md b/README.md index a161d3f..d5528e8 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,19 @@ You can also install libraries + C API header with: - `cmake --install build` +Installation targets: + +- `cmake --build build --target install`: install everything (libs, C API, tools) +- `cmake --build build --target install_nam`: install NAM libraries + C API only +- `cmake --build build --target install_tools`: install tool binaries only + +Component-based install commands: + +- `cmake --install build --component nam` +- `cmake --install build --component tools` + +For multi-config generators, add `--config Release` to build/install commands. + ## Testing A workflow for testing the library is provided in `.github/workflows/build.yml`. You should be able to run it locally to test if you'd like. diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 4384089..4f84993 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -60,3 +60,8 @@ foreach(target ${TOOLS}) ) endif() endforeach() + +install(TARGETS ${TOOLS} + RUNTIME DESTINATION bin + COMPONENT tools +) From dc153ae5998df92b6df72f2f48da9c34c41eb92d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Letz?= Date: Fri, 13 Feb 2026 12:38:43 +0100 Subject: [PATCH 4/5] Force NAM libraries to build in double precision --- CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8fae3e7..63a97d5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,6 +47,11 @@ add_library(nam::shared ALIAS nam_shared) set(NAM_LIBRARY_TARGETS nam_static nam_shared) foreach(target ${NAM_LIBRARY_TARGETS}) target_compile_features(${target} PUBLIC cxx_std_20) + if (MSVC) + target_compile_options(${target} PRIVATE /UNAM_SAMPLE_FLOAT) + else() + target_compile_options(${target} PRIVATE -UNAM_SAMPLE_FLOAT) + endif() target_include_directories(${target} PUBLIC $ From 993e0b19c14161e353c99a4b107ee8a1a7a467dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Letz?= Date: Fri, 13 Feb 2026 15:57:07 +0100 Subject: [PATCH 5/5] Add fast_tanh and LUT controls to C API --- NAM/nam_c_api.cpp | 74 +++++++++++++++++++++++++++++++++++++++++ NAM/nam_c_api.h | 7 ++++ README.md | 20 +++++++++++ tools/c_api_example.cpp | 28 +++++++++++++++- 4 files changed, 128 insertions(+), 1 deletion(-) diff --git a/NAM/nam_c_api.cpp b/NAM/nam_c_api.cpp index 35622c7..1b3052b 100644 --- a/NAM/nam_c_api.cpp +++ b/NAM/nam_c_api.cpp @@ -6,6 +6,7 @@ #include #include +#include "activations.h" #include "get_dsp.h" struct nam_model @@ -132,6 +133,79 @@ double nam_expected_sample_rate(const nam_model_t* model) return model->dsp->GetExpectedSampleRate(); } +nam_status_t nam_enable_fast_tanh(void) +{ + try + { + nam::activations::Activation::enable_fast_tanh(); + g_last_error.clear(); + return NAM_STATUS_OK; + } + catch (...) + { + return handle_exception(); + } +} + +nam_status_t nam_disable_fast_tanh(void) +{ + try + { + nam::activations::Activation::disable_fast_tanh(); + g_last_error.clear(); + return NAM_STATUS_OK; + } + catch (...) + { + return handle_exception(); + } +} + +int nam_is_fast_tanh_enabled(void) +{ + return nam::activations::Activation::using_fast_tanh ? 1 : 0; +} + +nam_status_t nam_enable_lut(const char* function_name, float min, float max, int n_points) +{ + if (function_name == nullptr || n_points <= 1 || !(min < max)) + { + set_error("Invalid argument: function_name must be non-null, min < max, and n_points > 1."); + return NAM_STATUS_INVALID_ARGUMENT; + } + + try + { + nam::activations::Activation::enable_lut(function_name, min, max, static_cast(n_points)); + g_last_error.clear(); + return NAM_STATUS_OK; + } + catch (...) + { + return handle_exception(); + } +} + +nam_status_t nam_disable_lut(const char* function_name) +{ + if (function_name == nullptr) + { + set_error("Invalid argument: function_name must be non-null."); + return NAM_STATUS_INVALID_ARGUMENT; + } + + try + { + nam::activations::Activation::disable_lut(function_name); + g_last_error.clear(); + return NAM_STATUS_OK; + } + catch (...) + { + return handle_exception(); + } +} + const char* nam_get_last_error(void) { return g_last_error.c_str(); diff --git a/NAM/nam_c_api.h b/NAM/nam_c_api.h index 8afb239..4318863 100644 --- a/NAM/nam_c_api.h +++ b/NAM/nam_c_api.h @@ -50,6 +50,13 @@ NAM_API int nam_num_input_channels(const nam_model_t* model); NAM_API int nam_num_output_channels(const nam_model_t* model); NAM_API double nam_expected_sample_rate(const nam_model_t* model); +// Enables/disables fast tanh approximation globally for NAM activations. +NAM_API nam_status_t nam_enable_fast_tanh(void); +NAM_API nam_status_t nam_disable_fast_tanh(void); +NAM_API int nam_is_fast_tanh_enabled(void); +NAM_API nam_status_t nam_enable_lut(const char* function_name, float min, float max, int n_points); +NAM_API nam_status_t nam_disable_lut(const char* function_name); + // Thread-local error message set when a NAM_STATUS_EXCEPTION is returned. NAM_API const char* nam_get_last_error(void); diff --git a/README.md b/README.md index d5528e8..8d571b8 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,26 @@ Simple example using the C API (linked against the static library): - build target: `cmake --build build --target c_api_example` - run: `./build/tools/c_api_example example_models/wavenet.nam` +C API runtime options (global activation behavior): + +```c +// Enable fast tanh approximation +nam_enable_fast_tanh(); + +// Enable LUT approximations (valid names: "Tanh", "Sigmoid") +nam_enable_lut("Tanh", -4.0f, 4.0f, 4096); +nam_enable_lut("Sigmoid", -8.0f, 8.0f, 4096); + +// ... process ... + +// Optional cleanup / restore defaults +nam_disable_lut("Sigmoid"); +nam_disable_lut("Tanh"); +nam_disable_fast_tanh(); +``` + +These settings are process-global and affect subsequently created/used NAM models. + You can also install libraries + C API header with: - `cmake --install build` diff --git a/tools/c_api_example.cpp b/tools/c_api_example.cpp index 5e2cc30..4178ab9 100644 --- a/tools/c_api_example.cpp +++ b/tools/c_api_example.cpp @@ -12,8 +12,30 @@ int main(int argc, char* argv[]) return 1; } + // Optional global runtime options. + nam_status_t status = nam_enable_fast_tanh(); + if (status != NAM_STATUS_OK) + { + std::fprintf(stderr, "nam_enable_fast_tanh failed: %s\n", nam_get_last_error()); + return 1; + } + + status = nam_enable_lut("Tanh", -4.0f, 4.0f, 4096); + if (status != NAM_STATUS_OK) + { + std::fprintf(stderr, "nam_enable_lut(Tanh) failed: %s\n", nam_get_last_error()); + return 1; + } + + status = nam_enable_lut("Sigmoid", -8.0f, 8.0f, 4096); + if (status != NAM_STATUS_OK) + { + std::fprintf(stderr, "nam_enable_lut(Sigmoid) failed: %s\n", nam_get_last_error()); + return 1; + } + nam_model_t* model = nullptr; - nam_status_t status = nam_create_model_from_file(argv[1], &model); + status = nam_create_model_from_file(argv[1], &model); if (status != NAM_STATUS_OK || model == nullptr) { std::fprintf(stderr, "Failed to load model: %s\n", nam_get_last_error()); @@ -56,5 +78,9 @@ int main(int argc, char* argv[]) std::printf("Processed %d frames. First output sample: %f\n", block_size, static_cast(output[0][0])); nam_destroy_model(model); + // Restore defaults for caller processes that keep running. + (void)nam_disable_lut("Sigmoid"); + (void)nam_disable_lut("Tanh"); + (void)nam_disable_fast_tanh(); return 0; }