Migrated from Make to CMake.

This commit is contained in:
Johannes Findeisen 2026-11-06 19:05:14 +01:00
commit b6ba093000
52 changed files with 1276 additions and 1210 deletions

2
.gitignore vendored
View file

@ -1,5 +1,7 @@
# Build products # Build products
/build/ /build/
/cmake-build-*/
/CMakeUserPresets.json
/*.o /*.o
*.a *.a
*.so *.so

View file

@ -28,7 +28,7 @@ This file is the working contract for AI-assisted development in this repository
## First task ## First task
Run `make clean all test`. Inspect compiler and `pkg-config` availability. Do not install packages or alter system configuration without permission. Report current state and propose the next smallest step. Run `cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug`, `cmake --build build --parallel`, and `ctest --test-dir build --output-on-failure`. Inspect compiler and `pkg-config` availability. Do not install packages or alter system configuration without permission. Report current state and propose the next smallest step.
## Roadmap ## Roadmap

200
CMakeLists.txt Normal file
View file

@ -0,0 +1,200 @@
cmake_minimum_required(VERSION 3.25)
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS include/xtc/xtc_version.h)
file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/include/xtc/xtc_version.h" version_line REGEX "^#define XTC_VERSION_STRING")
string(REGEX MATCH "[0-9]+[.][0-9]+[.][0-9]+" XTC_VERSION "${version_line}")
project(xtc VERSION ${XTC_VERSION} LANGUAGES C)
if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR)
message(FATAL_ERROR "Use an out-of-source build: cmake -S . -B build")
endif()
include(GNUInstallDirs)
include(CTest)
find_package(Threads REQUIRED)
find_package(Python3 3.12 REQUIRED COMPONENTS Interpreter)
find_package(PkgConfig QUIET)
option(XTC_WITH_JACK "Discover the optional JACK backend" ON)
option(XTC_WITH_LADSPA "Enable LADSPA when its system header is available" ON)
option(XTC_BUILD_GUI "Build the FLTK application when FLTK is available" ON)
option(XTC_ENABLE_LIVE_TESTS "Register tests requiring an existing JACK server" OFF)
option(XTC_BUILD_JUCE "Build optional externally supplied JUCE examples" OFF)
option(XTC_BUILD_STEINBERG "Build optional externally supplied Steinberg examples" OFF)
option(XTC_SANITIZE "Enable address and undefined-behavior sanitizers" OFF)
option(XTC_REPRODUCIBLE "Remove source/build paths from optimized release artifacts" OFF)
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# Keep project warnings/assertions local; external SDKs control their own flags.
function(xtc_warnings target)
target_compile_options(${target} PRIVATE -Wall -Wextra -Wpedantic -UNDEBUG
"$<$<COMPILE_LANGUAGE:C>:-Wconversion;-Wshadow>")
endfunction()
if(XTC_SANITIZE)
add_compile_options(-fsanitize=address,undefined -fno-omit-frame-pointer)
add_link_options(-fsanitize=address,undefined)
endif()
if(XTC_REPRODUCIBLE)
add_compile_options("-ffile-prefix-map=${CMAKE_CURRENT_SOURCE_DIR}=."
"-ffile-prefix-map=${CMAKE_CURRENT_BINARY_DIR}=build" -g0)
endif()
if(XTC_WITH_JACK AND PkgConfig_FOUND)
pkg_check_modules(JACK QUIET IMPORTED_TARGET jack)
endif()
add_library(xtc-core STATIC
src/plugin/module_path.c
src/midi/file.c
src/midi/output.c
src/project/crossfade.c
src/control/tempo.c
src/project/chain_tools.c
src/project/chain_preset.c
src/mixer/track_fx.c
src/audio/sample.c
src/persistence/collect.c
src/audio/normalize.c
src/project/sections.c
src/project/editing.c
src/audio/export.c
src/plugin/builtin.c
src/settings/settings.c
src/control/model.c
src/audio/waveform.c
src/plugin/ladspa.c
src/plugin/host.c
src/plugin/modern.c
src/native/processor.c
src/audio/recovery.c
src/dsp/ramp.c
src/native/native.c
src/mixer/model.c
src/mixer/render.c
src/project/history.c
src/midi/model.c
src/midi/instrument.c
src/audio/wave.c
src/audio/arrangement.c
src/audio/recorder.c
src/core/xtc.c
src/project/project.c
src/persistence/project_file.c
src/graph/graph.c
src/dsp/gain.c
src/engine/offline_engine.c
src/engine/realtime.c
src/backend/jack_backend.c
)
set_target_properties(xtc-core PROPERTIES OUTPUT_NAME xtc)
xtc_warnings(xtc-core)
add_library(xtc::core ALIAS xtc-core)
target_include_directories(xtc-core PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>" "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>")
target_include_directories(xtc-core SYSTEM PRIVATE third_party/clap/include third_party/vst3)
target_link_libraries(xtc-core PUBLIC m ${CMAKE_DL_LIBS} Threads::Threads)
if(JACK_FOUND AND XTC_WITH_JACK)
target_compile_definitions(xtc-core PRIVATE XTC_HAVE_JACK)
target_link_libraries(xtc-core PUBLIC PkgConfig::JACK)
endif()
include(CheckIncludeFile)
check_include_file(ladspa.h XTC_HAVE_LADSPA_HEADER)
if(XTC_HAVE_LADSPA_HEADER AND XTC_WITH_LADSPA)
set(XTC_LADSPA_AVAILABLE TRUE)
endif()
if(NOT XTC_WITH_LADSPA)
target_compile_definitions(xtc-core PRIVATE XTC_DISABLE_LADSPA)
endif()
function(xtc_executable name source)
add_executable(${name} ${source} ${ARGN})
target_link_libraries(${name} PRIVATE xtc-core)
xtc_warnings(${name})
endfunction()
xtc_executable(xtc-check src/main.c)
xtc_executable(native-gain examples/native_gain.c)
xtc_executable(quality-preview-render examples/quality_preview.c)
set_target_properties(quality-preview-render PROPERTIES OUTPUT_NAME xtc-quality-preview)
xtc_executable(builtin-preview-render examples/builtin_preview.c)
set_target_properties(builtin-preview-render PROPERTIES OUTPUT_NAME xtc-builtin-preview)
foreach(preview quality builtin)
add_custom_target(${preview}-preview
COMMAND ${preview}-preview-render "${CMAKE_CURRENT_BINARY_DIR}/${preview}"
DEPENDS ${preview}-preview-render VERBATIM)
endforeach()
include(cmake/Tests.cmake)
include(cmake/Gui.cmake)
include(cmake/Developer.cmake)
include(cmake/Install.cmake)
if(XTC_BUILD_STEINBERG)
enable_language(CXX)
add_subdirectory(examples/steinberg steinberg-sdk)
xtc_executable(steinberg-render examples/steinberg/render.c)
add_custom_target(steinberg-sdk DEPENDS xtc-sdk-examples validator)
if(BUILD_TESTING)
xtc_executable(test_steinberg_sdk tests/integration/test_steinberg_sdk.c)
set(steinberg_bundle "$<PATH:GET_PARENT_PATH,$<PATH:GET_PARENT_PATH,$<TARGET_FILE_DIR:xtc-sdk-examples>>>")
xtc_test(steinberg-sdk test_steinberg_sdk "${steinberg_bundle}")
add_test(NAME steinberg-validator COMMAND $<TARGET_FILE:validator> "${steinberg_bundle}")
set_tests_properties(steinberg-sdk steinberg-validator PROPERTIES LABELS sdk)
endif()
endif()
if(XTC_BUILD_JUCE)
if(BUILD_TESTING)
find_program(XTC_XVFB_RUN xvfb-run REQUIRED)
endif()
enable_language(CXX)
add_subdirectory(examples/juce juce)
add_custom_target(juce-examples DEPENDS xtc-juce-render XtcJuceSynth_VST3 XtcJuceSynth_Standalone)
if(BUILD_TESTING)
xtc_executable(test_juce_plugin tests/integration/test_juce_plugin.c)
add_test(NAME juce-c-host COMMAND ${XTC_XVFB_RUN} -a $<TARGET_FILE:test_juce_plugin>
"$<PATH:GET_PARENT_PATH,$<PATH:GET_PARENT_PATH,$<TARGET_FILE_DIR:XtcJuceSynth_VST3>>>")
add_test(NAME juce-files-editor COMMAND ${XTC_XVFB_RUN} -a ${Python3_EXECUTABLE}
"${CMAKE_CURRENT_SOURCE_DIR}/tests/integration/test_juce_examples.py" $<TARGET_FILE:xtc-juce-render>)
set_tests_properties(juce-c-host juce-files-editor PROPERTIES LABELS sdk)
endif()
endif()
if(BUILD_TESTING)
add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" USES_TERMINAL)
get_property(xtc_build_targets DIRECTORY PROPERTY BUILDSYSTEM_TARGETS)
foreach(binary IN LISTS xtc_build_targets)
get_target_property(kind ${binary} TYPE)
if(kind MATCHES "^(EXECUTABLE|MODULE_LIBRARY|STATIC_LIBRARY)$" AND NOT binary STREQUAL capture_screenshots)
add_dependencies(check ${binary})
endif()
endforeach()
if(XTC_BUILD_JUCE)
add_dependencies(check xtc-juce-render XtcJuceSynth_VST3 XtcJuceSynth_Standalone)
endif()
if(XTC_BUILD_STEINBERG)
add_dependencies(check xtc-sdk-examples validator)
endif()
get_property(xtc_tests DIRECTORY PROPERTY TESTS)
foreach(name IN LISTS xtc_tests)
if(NOT TARGET test-${name} AND NOT name STREQUAL steinberg-sdk)
add_custom_target(test-${name} COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure -R "^${name}$"
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" USES_TERMINAL)
foreach(binary IN LISTS xtc_build_targets)
get_target_property(kind ${binary} TYPE)
if(kind MATCHES "^(EXECUTABLE|MODULE_LIBRARY|STATIC_LIBRARY)$" AND NOT binary STREQUAL capture_screenshots)
add_dependencies(test-${name} ${binary})
endif()
endforeach()
endif()
endforeach()
endif()
if(BUILD_TESTING)
include(cmake/Variants.cmake)
# Historical workflow names retained as CMake target aliases.
add_custom_target(test-builtins DEPENDS test-builtin)
if(XTC_BUILD_JUCE)
add_custom_target(test-juce COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure -R "juce"
DEPENDS xtc-juce-render XtcJuceSynth_VST3 XtcJuceSynth_Standalone test_juce_plugin USES_TERMINAL)
endif()
if(XTC_BUILD_STEINBERG)
add_custom_target(test-steinberg-sdk COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure -R steinberg
DEPENDS xtc-sdk-examples validator test_steinberg_sdk steinberg-render USES_TERMINAL)
endif()
endif()

22
CMakePresets.json Normal file
View file

@ -0,0 +1,22 @@
{
"version": 6,
"cmakeMinimumRequired": { "major": 3, "minor": 25, "patch": 0 },
"configurePresets": [
{ "name": "debug", "binaryDir": "${sourceDir}/build/debug", "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug" } },
{ "name": "release", "binaryDir": "${sourceDir}/build/release", "cacheVariables": { "CMAKE_BUILD_TYPE": "Release", "XTC_REPRODUCIBLE": "ON" } },
{ "name": "minimal", "binaryDir": "${sourceDir}/build/minimal", "cacheVariables": { "CMAKE_BUILD_TYPE": "Release", "XTC_BUILD_GUI": "OFF", "XTC_WITH_JACK": "OFF", "XTC_WITH_LADSPA": "OFF" } },
{ "name": "sanitize", "binaryDir": "${sourceDir}/build/sanitize", "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug", "CMAKE_C_COMPILER": "clang", "XTC_BUILD_GUI": "OFF", "XTC_SANITIZE": "ON" } }
],
"buildPresets": [
{ "name": "debug", "configurePreset": "debug" },
{ "name": "release", "configurePreset": "release" },
{ "name": "minimal", "configurePreset": "minimal" },
{ "name": "sanitize", "configurePreset": "sanitize" }
],
"testPresets": [
{ "name": "debug", "configurePreset": "debug", "output": { "outputOnFailure": true } },
{ "name": "release", "configurePreset": "release", "output": { "outputOnFailure": true } },
{ "name": "minimal", "configurePreset": "minimal", "output": { "outputOnFailure": true } },
{ "name": "sanitize", "configurePreset": "sanitize", "output": { "outputOnFailure": true } }
]
}

689
Makefile
View file

@ -1,667 +1,46 @@
# Convenience shortcuts only; CMakeLists.txt owns the build.
.DEFAULT_GOAL := all .DEFAULT_GOAL := all
# CMake handles parallel compilation; serialize separate wrapper goals.
.NOTPARALLEL:
CC ?= cc CMAKE ?= cmake
CXX ?= c++ CTEST ?= ctest
PKG_CONFIG ?= pkg-config
FLTK_CONFIG ?= fltk-config
BUILD_DIR ?= build BUILD_DIR ?= build
PREFIX ?= /usr/local BUILD_TYPE ?= Debug
exec_prefix ?= $(PREFIX) JOBS ?= 4
bindir ?= $(exec_prefix)/bin CMAKE_ARGS ?=
libdir ?= $(exec_prefix)/lib CTEST_ARGS ?=
includedir ?= $(PREFIX)/include
datarootdir ?= $(PREFIX)/share
datadir ?= $(datarootdir)
docdir ?= $(datarootdir)/doc/xtc
licensedir ?= $(datarootdir)/licenses/xtc
examplesdir ?= $(datadir)/xtc/examples
desktopdir ?= $(datadir)/applications
pkgconfigdir ?= $(libdir)/pkgconfig
INSTALL_LAYOUT = --prefix "$(PREFIX)" --destdir "$(DESTDIR)" --bindir "$(bindir)" --libdir "$(libdir)" --includedir "$(includedir)" --docdir "$(docdir)" --licensedir "$(licensedir)" --examplesdir "$(examplesdir)" --desktopdir "$(desktopdir)" --pkgconfigdir "$(pkgconfigdir)"
PYTHON ?= python3
VERSION := $(shell sed -n 's/^\#define XTC_VERSION_STRING "\([^"]*\)"/\1/p' include/xtc/xtc_version.h)
CFLAGS ?= -O2 -g .PHONY: all build configure test check gui run clean help
CFLAGS += -std=c99 -Wall -Wextra -Wpedantic -Wconversion -Wshadow -Iinclude all: build
CXXFLAGS ?= -O2 -g
CXXFLAGS += -std=c++17 -Wall -Wextra -Wpedantic -Iinclude
LDFLAGS ?=
CORE_SOURCES := src/plugin/module_path.c src/midi/file.c src/midi/output.c src/project/crossfade.c src/control/tempo.c src/project/chain_tools.c src/project/chain_preset.c src/mixer/track_fx.c src/audio/sample.c src/persistence/collect.c src/audio/normalize.c src/project/sections.c src/project/editing.c src/audio/export.c src/plugin/builtin.c src/settings/settings.c src/control/model.c src/audio/waveform.c src/plugin/ladspa.c src/plugin/host.c src/plugin/modern.c src/native/processor.c src/audio/recovery.c src/dsp/ramp.c src/native/native.c src/mixer/model.c src/mixer/render.c src/project/history.c src/midi/model.c src/midi/instrument.c src/audio/wave.c src/audio/arrangement.c src/audio/recorder.c src/core/xtc.c src/project/project.c src/persistence/project_file.c src/graph/graph.c src/dsp/gain.c src/engine/offline_engine.c src/engine/realtime.c src/backend/jack_backend.c configure:
JACK_AVAILABLE := $(shell $(PKG_CONFIG) --exists jack 2>/dev/null && echo yes) $(CMAKE) -S . -B "$(BUILD_DIR)" -DCMAKE_BUILD_TYPE="$(BUILD_TYPE)" $(CMAKE_ARGS)
ifeq ($(JACK_AVAILABLE),yes)
JACK_CFLAGS := $(shell $(PKG_CONFIG) --cflags jack)
JACK_LIBS := $(shell $(PKG_CONFIG) --libs jack)
BACKEND_TEST := $(BUILD_DIR)/test_backend
$(BUILD_DIR)/tests/unit/test_backend.o: CPPFLAGS += $(JACK_CFLAGS)
$(BUILD_DIR)/src/core/xtc.o: CPPFLAGS += -DXTC_HAVE_JACK
$(BUILD_DIR)/src/backend/jack_backend.o: CPPFLAGS += -DXTC_HAVE_JACK $(JACK_CFLAGS)
endif
CORE_OBJECTS := $(CORE_SOURCES:%.c=$(BUILD_DIR)/%.o)
TEST_OBJECT := $(BUILD_DIR)/tests/unit/test_core.o
ifeq ($(shell $(PKG_CONFIG) --exists fltk 2>/dev/null && echo yes),yes)
FLTK_CXXFLAGS := $(shell $(PKG_CONFIG) --cflags fltk)
FLTK_LIBS := $(shell $(PKG_CONFIG) --libs fltk)
GUI_AVAILABLE := yes
else ifeq ($(shell $(FLTK_CONFIG) --version >/dev/null 2>&1 && echo yes),yes)
FLTK_CXXFLAGS := $(shell $(FLTK_CONFIG) --cxxflags)
FLTK_LIBS := $(shell $(FLTK_CONFIG) --ldflags)
GUI_AVAILABLE := yes
endif
.PHONY: all clean test test-jack-midi test-jack-audio test-jack test-gui test-gui-jack debug gui install build: configure
all: $(BUILD_DIR)/libxtc.a $(BUILD_DIR)/xtc-check $(CMAKE) --build "$(BUILD_DIR)" --config "$(BUILD_TYPE)" --parallel "$(JOBS)"
$(BUILD_DIR)/xtc-check: $(BUILD_DIR)/src/main.o $(BUILD_DIR)/libxtc.a test: build
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl $(CTEST) --test-dir "$(BUILD_DIR)" --build-config "$(BUILD_TYPE)" --output-on-failure $(CTEST_ARGS)
$(BUILD_DIR)/libxtc.a: $(CORE_OBJECTS) check: test
@mkdir -p $(@D)
$(AR) rcsD $@ $^
$(BUILD_DIR)/%.o: %.c gui: configure
@mkdir -p $(@D) $(CMAKE) --build "$(BUILD_DIR)" --config "$(BUILD_TYPE)" --target xtc --parallel "$(JOBS)"
$(CC) $(CPPFLAGS) $(CFLAGS) -MMD -MP -c $< -o $@
$(BUILD_DIR)/%.o: %.cpp run: gui
@mkdir -p $(@D) "$(abspath $(BUILD_DIR))/xtc"
$(CXX) $(CPPFLAGS) $(CXXFLAGS) $(FLTK_CXXFLAGS) -MMD -MP -c $< -o $@
$(TEST_OBJECT): tests/unit/test_core.c
@mkdir -p $(@D)
$(CC) $(CPPFLAGS) $(CFLAGS) -MMD -MP -c $< -o $@
$(BUILD_DIR)/test_core: $(TEST_OBJECT) $(BUILD_DIR)/libxtc.a
$(CC) $(LDFLAGS) -o $@ $(TEST_OBJECT) $(BUILD_DIR)/libxtc.a -lm -ldl
$(BUILD_DIR)/test_offline: $(BUILD_DIR)/tests/unit/test_offline.o $(BUILD_DIR)/libxtc.a
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl
$(BUILD_DIR)/test_realtime: $(BUILD_DIR)/tests/unit/test_realtime.o $(BUILD_DIR)/libxtc.a
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl -pthread
$(BUILD_DIR)/test_backend: $(BUILD_DIR)/tests/unit/test_backend.o $(BUILD_DIR)/libxtc.a
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl
$(BUILD_DIR)/test_graph_edits: $(BUILD_DIR)/tests/unit/test_graph_edits.o $(BUILD_DIR)/libxtc.a
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl
$(BUILD_DIR)/test_project: $(BUILD_DIR)/tests/unit/test_project.o $(BUILD_DIR)/libxtc.a
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl
$(BUILD_DIR)/test_audio: $(BUILD_DIR)/tests/unit/test_audio.o $(BUILD_DIR)/libxtc.a
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl -pthread
$(BUILD_DIR)/test_midi: $(BUILD_DIR)/tests/unit/test_midi.o $(BUILD_DIR)/libxtc.a
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl
$(BUILD_DIR)/test_mixer: $(BUILD_DIR)/tests/unit/test_mixer.o $(BUILD_DIR)/libxtc.a
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl
$(BUILD_DIR)/test_native: $(BUILD_DIR)/tests/unit/test_native.o $(BUILD_DIR)/libxtc.a
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl
$(BUILD_DIR)/native-gain: $(BUILD_DIR)/examples/native_gain.o $(BUILD_DIR)/libxtc.a
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl
test: test-modern-editing $(BUILD_DIR)/test_native $(BUILD_DIR)/native-gain $(BUILD_DIR)/test_mixer $(BUILD_DIR)/test_midi $(BUILD_DIR)/test_audio $(BUILD_DIR)/test_project $(BUILD_DIR)/test_graph_edits $(BACKEND_TEST) $(BUILD_DIR)/test_realtime $(BUILD_DIR)/test_core $(BUILD_DIR)/test_offline $(BUILD_DIR)/xtc-check
$(if $(BACKEND_TEST),./$(BACKEND_TEST),@echo "JACK fault tests skipped: JACK development files unavailable")
./$(BUILD_DIR)/test_native
./$(BUILD_DIR)/native-gain
./$(BUILD_DIR)/test_mixer
./$(BUILD_DIR)/test_midi
./$(BUILD_DIR)/test_audio
./$(BUILD_DIR)/test_project
./$(BUILD_DIR)/test_graph_edits
./$(BUILD_DIR)/test_realtime
./$(BUILD_DIR)/test_core
./$(BUILD_DIR)/test_offline
./$(BUILD_DIR)/xtc-check
debug:
$(MAKE) BUILD_DIR="$(BUILD_DIR)/debug" CFLAGS="$(CFLAGS) -O0 -g3" CXXFLAGS="$(CXXFLAGS) -O0 -g3" all test
ifeq ($(JACK_AVAILABLE),yes)
$(BUILD_DIR)/tests/integration/test_jack_midi.o: CPPFLAGS += $(JACK_CFLAGS)
$(BUILD_DIR)/test_jack_midi: $(BUILD_DIR)/tests/integration/test_jack_midi.o $(BUILD_DIR)/libxtc.a
$(CC) $(LDFLAGS) -o $@ $^ $(JACK_LIBS) -lm -ldl
test-jack-midi: $(BUILD_DIR)/test_jack_midi
./$(BUILD_DIR)/test_jack_midi
$(BUILD_DIR)/tests/integration/test_jack_audio.o: CPPFLAGS += $(JACK_CFLAGS)
$(BUILD_DIR)/test_jack_audio: $(BUILD_DIR)/tests/integration/test_jack_audio.o $(BUILD_DIR)/libxtc.a
$(CC) $(LDFLAGS) -o $@ $^ $(JACK_LIBS) -lm -ldl
test-jack-audio: $(BUILD_DIR)/test_jack_audio
./$(BUILD_DIR)/test_jack_audio
$(BUILD_DIR)/tests/integration/test_jack.o: CPPFLAGS += $(JACK_CFLAGS)
$(BUILD_DIR)/test_jack: $(BUILD_DIR)/tests/integration/test_jack.o $(BUILD_DIR)/libxtc.a
$(CC) $(LDFLAGS) -o $@ $^ $(JACK_LIBS) -lm -ldl
test-jack: $(BUILD_DIR)/test_jack
./$(BUILD_DIR)/test_jack
else
test-jack-midi test-jack-audio test-jack:
@echo "JACK development files are required for the live integration test."
@exit 1
endif
ifeq ($(GUI_AVAILABLE),yes)
gui: $(BUILD_DIR)/xtc
$(BUILD_DIR)/test_gui: $(BUILD_DIR)/tests/integration/test_gui.o $(BUILD_DIR)/src/gui/main_window.o $(BUILD_DIR)/src/gui/graph_widget.o $(BUILD_DIR)/src/gui/sequencer_widget.o $(BUILD_DIR)/src/gui/piano_roll.o $(BUILD_DIR)/src/gui/mixer_widget.o $(BUILD_DIR)/src/gui/plugin_dialog.o $(BUILD_DIR)/src/gui/sample_editor.o $(BUILD_DIR)/libxtc.a
$(CXX) $(LDFLAGS) -o $@ $^ $(FLTK_LIBS) -lm -ldl -Wl,--wrap=xtc_history_reserve
test-gui: $(BUILD_DIR)/test_gui
xvfb-run -a -s "-screen 0 1280x800x24" ./$(BUILD_DIR)/test_gui
ifeq ($(JACK_AVAILABLE),yes)
$(BUILD_DIR)/test_gui_jack: $(BUILD_DIR)/tests/integration/test_gui_jack.o $(BUILD_DIR)/src/gui/main_window.o $(BUILD_DIR)/src/gui/graph_widget.o $(BUILD_DIR)/src/gui/sequencer_widget.o $(BUILD_DIR)/src/gui/piano_roll.o $(BUILD_DIR)/src/gui/mixer_widget.o $(BUILD_DIR)/src/gui/plugin_dialog.o $(BUILD_DIR)/src/gui/sample_editor.o $(BUILD_DIR)/libxtc.a
$(CXX) $(LDFLAGS) -o $@ $^ $(FLTK_LIBS) $(JACK_LIBS) -lm -ldl
test-gui-jack: $(BUILD_DIR)/test_gui_jack
xvfb-run -a -s "-screen 0 1280x800x24" ./$(BUILD_DIR)/test_gui_jack
else
test-gui-jack:
@echo "JACK development files are required for the live GUI test."
@exit 1
endif
$(BUILD_DIR)/xtc: $(BUILD_DIR)/src/gui/main.o $(BUILD_DIR)/src/gui/main_window.o $(BUILD_DIR)/src/gui/graph_widget.o $(BUILD_DIR)/src/gui/sequencer_widget.o $(BUILD_DIR)/src/gui/piano_roll.o $(BUILD_DIR)/src/gui/mixer_widget.o $(BUILD_DIR)/src/gui/plugin_dialog.o $(BUILD_DIR)/src/gui/sample_editor.o $(BUILD_DIR)/libxtc.a
$(CXX) $(LDFLAGS) -o $@ $^ $(FLTK_LIBS) $(JACK_LIBS) -lm -ldl
else
test-gui-jack test-gui:
@echo "FLTK development files are required for GUI tests."
@exit 1
gui:
@echo "FLTK was not found by pkg-config or fltk-config. GUI requires FLTK development files."
@exit 1
endif
# install retains the core-only behavior; install-gui adds the application.
.PHONY: install uninstall install-gui dist release test-install test-release test-performance
$(BUILD_DIR)/xtc.pc: packaging/xtc.pc.in tools/pkgconfig.py tools/dist.py include/xtc/xtc_version.h FORCE
$(PYTHON) tools/pkgconfig.py --prefix "$(PREFIX)" --exec-prefix "$(exec_prefix)" --libdir "$(libdir)" --includedir "$(includedir)" --jack "$(if $(JACK_AVAILABLE),jack)" --output "$@"
.PHONY: FORCE
FORCE:
install: all $(BUILD_DIR)/xtc.pc
$(PYTHON) tools/install.py $(INSTALL_LAYOUT) --build-dir "$(BUILD_DIR)"
install-gui: install gui
$(PYTHON) tools/install.py $(INSTALL_LAYOUT) --build-dir "$(BUILD_DIR)" --gui
uninstall:
$(PYTHON) tools/uninstall.py $(INSTALL_LAYOUT)
dist:
$(PYTHON) tools/dist.py --output "$(BUILD_DIR)/xtc-$(VERSION).tar.gz"
release:
$(MAKE) BUILD_DIR="$(BUILD_DIR)/release" CFLAGS="-O2 -g0 -std=c99 -Wall -Wextra -Wpedantic -Wconversion -Wshadow -Iinclude -ffile-prefix-map=$(CURDIR)=." CXXFLAGS="-O2 -g0 -std=c++17 -Wall -Wextra -Wpedantic -Iinclude -ffile-prefix-map=$(CURDIR)=." FLTK_CXXFLAGS="$(filter -I% -D% -pthread,$(FLTK_CXXFLAGS))" all test $(if $(GUI_AVAILABLE),gui)
test-install: all $(if $(GUI_AVAILABLE),gui)
$(PYTHON) tests/integration/test_install.py --build-dir "$(abspath $(BUILD_DIR))" $(if $(GUI_AVAILABLE),--gui)
test-release: dist
$(PYTHON) tests/integration/test_release.py "$(BUILD_DIR)/xtc-$(VERSION).tar.gz" $(if $(GUI_AVAILABLE),--gui)
# Keep the CMake cache; never recursively delete a caller-supplied path.
clean: clean:
rm -rf "$(BUILD_DIR)" @if [ -f "$(BUILD_DIR)/CMakeCache.txt" ]; then \
$(CMAKE) --build "$(BUILD_DIR)" --config "$(BUILD_TYPE)" --target clean; \
-include $(CORE_OBJECTS:.o=.d) $(TEST_OBJECT:.o=.d) $(BUILD_DIR)/src/main.d $(BUILD_DIR)/tests/unit/test_offline.d $(BUILD_DIR)/tests/unit/test_realtime.d $(BUILD_DIR)/tests/unit/test_backend.d $(BUILD_DIR)/tests/unit/test_graph_edits.d \ fi
$(BUILD_DIR)/src/gui/main.d $(BUILD_DIR)/src/gui/main_window.d $(BUILD_DIR)/src/gui/graph_widget.d $(BUILD_DIR)/src/gui/sequencer_widget.d $(BUILD_DIR)/src/gui/piano_roll.d $(BUILD_DIR)/tests/integration/test_jack.d $(BUILD_DIR)/tests/integration/test_gui.d $(BUILD_DIR)/tests/integration/test_gui_jack.d
help:
-include $(BUILD_DIR)/tests/unit/test_project.d @echo 'make [build] Configure and compile (default)'
@echo 'make configure Configure only'
-include $(BUILD_DIR)/tests/unit/test_audio.d @echo 'make test Build, then run CTest (check is an alias)'
@echo 'make gui/run Build the FLTK app / build and launch it'
-include $(BUILD_DIR)/tests/integration/test_jack_audio.d @echo 'make clean Clean products, retaining the CMake cache'
@echo 'Overrides: BUILD_DIR=build BUILD_TYPE=Debug JOBS=4 CMAKE_ARGS=... CTEST_ARGS=...'
-include $(BUILD_DIR)/tests/unit/test_midi.d
-include $(BUILD_DIR)/tests/integration/test_jack_midi.d
-include $(BUILD_DIR)/src/gui/mixer_widget.d
-include $(BUILD_DIR)/tests/unit/test_mixer.d
-include $(BUILD_DIR)/tests/unit/test_native.d $(BUILD_DIR)/examples/native_gain.d
$(BUILD_DIR)/test_performance: $(BUILD_DIR)/tests/integration/test_performance.o $(BUILD_DIR)/libxtc.a
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl
$(BUILD_DIR)/test_recovery: $(BUILD_DIR)/tests/integration/test_recovery.o $(BUILD_DIR)/libxtc.a
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl
.PHONY: test-recovery
test-performance: $(BUILD_DIR)/test_performance
./$(BUILD_DIR)/test_performance
test-recovery: $(BUILD_DIR)/test_recovery
./$(BUILD_DIR)/test_recovery
-include $(BUILD_DIR)/tests/integration/test_performance.d $(BUILD_DIR)/tests/integration/test_recovery.d
# Linux/GNU-compatible linker test; no instrumentation in production objects.
$(BUILD_DIR)/test_reliability: $(BUILD_DIR)/tests/integration/test_reliability.o $(BUILD_DIR)/libxtc.a
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl -Wl,--wrap=malloc,--wrap=calloc,--wrap=realloc,--wrap=free,--wrap=fopen,--wrap=fread,--wrap=fwrite,--wrap=read,--wrap=write,--wrap=pthread_mutex_lock,--wrap=pthread_cond_wait -pthread
.PHONY: test-reliability test-sanitize analyze
test-reliability: test $(BUILD_DIR)/test_reliability
./$(BUILD_DIR)/test_reliability
test-sanitize:
ASAN_OPTIONS=detect_leaks=1:halt_on_error=1 UBSAN_OPTIONS=halt_on_error=1 $(MAKE) BUILD_DIR="$(BUILD_DIR)/sanitize" CC=clang CFLAGS="-O1 -g -std=c99 -Wall -Wextra -Wpedantic -Wconversion -Wshadow -Iinclude -fsanitize=address,undefined -fno-omit-frame-pointer" LDFLAGS="-fsanitize=address,undefined" test-reliability test-recovery test-scale test-soak
analyze:
$(PYTHON) tools/analyze.py $(CORE_SOURCES)
-include $(BUILD_DIR)/tests/integration/test_reliability.d
.PHONY: test-workflow
ifeq ($(GUI_AVAILABLE)$(JACK_AVAILABLE),yesyes)
$(BUILD_DIR)/test_workflow: $(BUILD_DIR)/tests/integration/test_workflow.o $(BUILD_DIR)/src/gui/main_window.o $(BUILD_DIR)/src/gui/graph_widget.o $(BUILD_DIR)/src/gui/sequencer_widget.o $(BUILD_DIR)/src/gui/piano_roll.o $(BUILD_DIR)/src/gui/mixer_widget.o $(BUILD_DIR)/src/gui/plugin_dialog.o $(BUILD_DIR)/src/gui/sample_editor.o $(BUILD_DIR)/libxtc.a
$(CXX) $(LDFLAGS) -o $@ $^ $(FLTK_LIBS) $(JACK_LIBS) -lm -ldl
test-workflow: $(BUILD_DIR)/test_workflow
xvfb-run -a -s "-screen 0 1280x800x24" ./$(BUILD_DIR)/test_workflow
else
test-workflow:
@echo "FLTK and JACK development files are required for the workflow test."
@exit 1
endif
-include $(BUILD_DIR)/tests/integration/test_workflow.d
$(BUILD_DIR)/test_quality: $(BUILD_DIR)/tests/unit/test_quality.o $(BUILD_DIR)/libxtc.a
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl
.PHONY: test-quality
test-quality: $(BUILD_DIR)/test_quality
./$(BUILD_DIR)/test_quality
test: test-quality
-include $(BUILD_DIR)/tests/unit/test_quality.d
$(BUILD_DIR)/quality-preview: $(BUILD_DIR)/examples/quality_preview.o $(BUILD_DIR)/libxtc.a
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl
.PHONY: quality-preview
quality-preview: $(BUILD_DIR)/quality-preview
./$(BUILD_DIR)/quality-preview "$(abspath $(BUILD_DIR))/quality"
-include $(BUILD_DIR)/examples/quality_preview.d
$(BUILD_DIR)/test_recording: $(BUILD_DIR)/tests/integration/test_recording.o $(BUILD_DIR)/libxtc.a
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl
.PHONY: test-recording
test-recording: $(BUILD_DIR)/test_recording
./$(BUILD_DIR)/test_recording
-include $(BUILD_DIR)/tests/integration/test_recording.d
$(BUILD_DIR)/test_processor: $(BUILD_DIR)/tests/unit/test_processor.o $(BUILD_DIR)/examples/processors.o $(BUILD_DIR)/libxtc.a
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl
.PHONY: test-processor
test-processor: $(BUILD_DIR)/test_processor
./$(BUILD_DIR)/test_processor
test: test-processor
-include $(BUILD_DIR)/tests/unit/test_processor.d $(BUILD_DIR)/examples/processors.d
$(BUILD_DIR)/plugin-fixture.so: tests/plugins/fixture.c
@mkdir -p $(@D)
$(CC) $(CPPFLAGS) $(CFLAGS) -fPIC -shared -o $@ $<
$(BUILD_DIR)/test_plugins: $(BUILD_DIR)/tests/integration/test_plugins.o $(BUILD_DIR)/libxtc.a
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl -Wl,--wrap=malloc,--wrap=calloc,--wrap=realloc,--wrap=free,--wrap=dlopen,--wrap=dlclose
.PHONY: test-plugins test-plugins-real
test-plugins: $(BUILD_DIR)/test_plugins $(BUILD_DIR)/plugin-fixture.so
./$(BUILD_DIR)/test_plugins "$(abspath $(BUILD_DIR))/plugin-fixture.so" fixture
test-plugins-real: $(BUILD_DIR)/test_plugins
./$(BUILD_DIR)/test_plugins /usr/lib/ladspa/amp.so
./$(BUILD_DIR)/test_plugins /usr/lib/ladspa/inv_1429.so
./$(BUILD_DIR)/test_plugins /usr/lib/ladspa/sine.so 3
-include $(BUILD_DIR)/tests/integration/test_plugins.d
.PHONY: test-gui-plugins
test-gui-plugins: $(BUILD_DIR)/test_gui $(BUILD_DIR)/plugin-fixture.so
XTC_TEST_PLUGIN="$(abspath $(BUILD_DIR))/plugin-fixture.so" xvfb-run -a -s "-screen 0 1280x800x24" ./$(BUILD_DIR)/test_gui
$(BUILD_DIR)/test_control: $(BUILD_DIR)/tests/unit/test_control.o $(BUILD_DIR)/libxtc.a
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl
.PHONY: test-control
test-control: $(BUILD_DIR)/test_control
./$(BUILD_DIR)/test_control
test: test-control
-include $(BUILD_DIR)/tests/unit/test_control.d
$(BUILD_DIR)/test_editing: $(BUILD_DIR)/tests/unit/test_editing.o $(BUILD_DIR)/libxtc.a
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl
.PHONY: test-editing
test-editing: $(BUILD_DIR)/test_editing
./$(BUILD_DIR)/test_editing
test: test-editing
-include $(BUILD_DIR)/tests/unit/test_editing.d
$(BUILD_DIR)/test_portability: $(BUILD_DIR)/tests/integration/test_portability.o $(BUILD_DIR)/libxtc.a
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl
.PHONY: test-portability
test-portability: $(BUILD_DIR)/test_portability
./$(BUILD_DIR)/test_portability
test: test-portability
-include $(BUILD_DIR)/tests/integration/test_portability.d
$(BUILD_DIR)/benchmark_graph: $(BUILD_DIR)/tests/integration/benchmark_graph.o $(BUILD_DIR)/libxtc.a
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl
.PHONY: benchmark
benchmark: $(BUILD_DIR)/benchmark_graph
$(PYTHON) tools/benchmark.py --binary "$(BUILD_DIR)/benchmark_graph" --output "$(BUILD_DIR)/performance.json" --compiler "$(CC)" --flags "$(CFLAGS)" --blocks "$(BENCHMARK_BLOCKS)"
-include $(BUILD_DIR)/tests/integration/benchmark_graph.d
$(BUILD_DIR)/test_scale: $(BUILD_DIR)/tests/integration/test_scale.o $(BUILD_DIR)/libxtc.a
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl
.PHONY: test-scale test-stress
test-scale: $(BUILD_DIR)/test_scale
./$(BUILD_DIR)/test_scale
test-stress: $(BUILD_DIR)/test_realtime $(BUILD_DIR)/test_reliability $(BUILD_DIR)/test_scale $(if $(GUI_AVAILABLE),$(BUILD_DIR)/test_gui)
$(PYTHON) tools/stress.py --build-dir "$(BUILD_DIR)" --rounds "$(STRESS_ROUNDS)" $(if $(GUI_AVAILABLE),--gui)
-include $(BUILD_DIR)/tests/integration/test_scale.d
BENCHMARK_BLOCKS ?= 300
BENCHMARK_BASELINE ?= docs/performance-baseline.json
BENCHMARK_MAX_REGRESSION ?= 1.5
.PHONY: benchmark-compare
benchmark-compare: $(BUILD_DIR)/benchmark_graph
$(PYTHON) tools/benchmark.py --binary "$(BUILD_DIR)/benchmark_graph" --output "$(BUILD_DIR)/performance.json" --compiler "$(CC)" --flags "$(CFLAGS)" --blocks "$(BENCHMARK_BLOCKS)" --baseline "$(BENCHMARK_BASELINE)" --max-regression "$(BENCHMARK_MAX_REGRESSION)"
PACKAGE_ARCH := $(shell uname -m)
PACKAGE_FILE := $(BUILD_DIR)/xtc-$(VERSION)-linux-$(PACKAGE_ARCH).tar.gz
.PHONY: package test-package
package: release
$(PYTHON) tools/package.py --build-dir "$(BUILD_DIR)/release" --output "$(PACKAGE_FILE)" $(if $(GUI_AVAILABLE),--gui)
test-package: package
$(PYTHON) tests/integration/test_package.py "$(PACKAGE_FILE)" --build-dir "$(BUILD_DIR)/release"
$(BUILD_DIR)/test_composition: $(BUILD_DIR)/tests/unit/test_composition.o $(BUILD_DIR)/libxtc.a
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl
.PHONY: test-composition
test-composition: $(BUILD_DIR)/test_composition
./$(BUILD_DIR)/test_composition
test: test-composition
-include $(BUILD_DIR)/tests/unit/test_composition.d
$(BUILD_DIR)/test_audio_editing: $(BUILD_DIR)/tests/unit/test_audio_editing.o $(BUILD_DIR)/libxtc.a
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl
.PHONY: test-audio-editing
test-audio-editing: $(BUILD_DIR)/test_audio_editing
./$(BUILD_DIR)/test_audio_editing
test: test-audio-editing
-include $(BUILD_DIR)/tests/unit/test_audio_editing.d
$(BUILD_DIR)/test_routing: $(BUILD_DIR)/tests/unit/test_routing.o $(BUILD_DIR)/libxtc.a
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl
.PHONY: test-routing
test-routing: $(BUILD_DIR)/test_routing
./$(BUILD_DIR)/test_routing
test: test-routing
-include $(BUILD_DIR)/tests/unit/test_routing.d
$(BUILD_DIR)/test_buses: $(BUILD_DIR)/tests/unit/test_buses.o $(BUILD_DIR)/libxtc.a
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl
.PHONY: test-buses
test-buses: $(BUILD_DIR)/test_buses
./$(BUILD_DIR)/test_buses
test: test-buses
-include $(BUILD_DIR)/tests/unit/test_buses.d
-include $(BUILD_DIR)/src/gui/plugin_dialog.d
$(BUILD_DIR)/test_parameters: $(BUILD_DIR)/tests/unit/test_parameters.o $(BUILD_DIR)/libxtc.a
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl
.PHONY: test-parameters
test-parameters: $(BUILD_DIR)/test_parameters
./$(BUILD_DIR)/test_parameters
test: test-parameters
-include $(BUILD_DIR)/tests/unit/test_parameters.d
$(BUILD_DIR)/test_project_diagnostics: $(BUILD_DIR)/tests/unit/test_project_diagnostics.o $(BUILD_DIR)/libxtc.a
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl -Wl,--wrap=fflush,--wrap=fclose,--wrap=fsync,--wrap=rename
.PHONY: test-project-diagnostics
test-project-diagnostics: $(BUILD_DIR)/test_project_diagnostics
./$(BUILD_DIR)/test_project_diagnostics
test: test-project-diagnostics
-include $(BUILD_DIR)/tests/unit/test_project_diagnostics.d
.PHONY: test-project-inspection
test-project-inspection: $(BUILD_DIR)/xtc-check
$(PYTHON) tests/integration/test_project_inspection.py $(BUILD_DIR)/xtc-check
test: test-project-inspection
$(BUILD_DIR)/test_soak: $(BUILD_DIR)/tests/integration/test_soak.o $(BUILD_DIR)/libxtc.a
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl
.PHONY: test-soak benchmark-project benchmark-project-compare check-development
test-soak: $(BUILD_DIR)/test_soak
./$(BUILD_DIR)/test_soak
PROJECT_BENCHMARK_BASELINE ?= docs/project-performance-baseline.json
STRESS_ROUNDS ?= 3
test-stress: $(BUILD_DIR)/test_portability $(BUILD_DIR)/test_project_diagnostics $(BUILD_DIR)/test_soak $(BACKEND_TEST)
benchmark-project: $(BUILD_DIR)/test_performance
$(PYTHON) tools/benchmark.py --workload project --binary "$(BUILD_DIR)/test_performance" --output "$(BUILD_DIR)/project-performance.json" --compiler "$(CC)" --flags "$(CFLAGS)" --blocks "$(BENCHMARK_BLOCKS)"
benchmark-project-compare: $(BUILD_DIR)/test_performance
$(PYTHON) tools/benchmark.py --workload project --binary "$(BUILD_DIR)/test_performance" --output "$(BUILD_DIR)/project-performance.json" --compiler "$(CC)" --flags "$(CFLAGS)" --blocks "$(BENCHMARK_BLOCKS)" --baseline "$(PROJECT_BENCHMARK_BASELINE)" --max-regression "$(BENCHMARK_MAX_REGRESSION)"
check-development: all test-reliability test-scale test-soak analyze test-sanitize
-include $(BUILD_DIR)/tests/integration/test_soak.d
.PHONY: test-benchmark-tools
test-benchmark-tools:
$(PYTHON) tests/tools/test_benchmark.py
check-development: test-benchmark-tools
.PHONY: test-stress-plugins
test-stress-plugins: $(BUILD_DIR)/test_realtime $(BUILD_DIR)/test_reliability $(BUILD_DIR)/test_scale $(BUILD_DIR)/test_portability $(BUILD_DIR)/test_project_diagnostics $(BUILD_DIR)/test_soak $(BACKEND_TEST) $(BUILD_DIR)/test_plugins $(BUILD_DIR)/plugin-fixture.so
$(PYTHON) tools/stress.py --build-dir "$(BUILD_DIR)" --rounds "$(STRESS_ROUNDS)" --plugin "$(BUILD_DIR)/plugin-fixture.so"
.PHONY: test-install-layout
test-install-layout: all $(if $(GUI_AVAILABLE),gui)
$(PYTHON) tests/integration/test_install.py --build-dir "$(abspath $(BUILD_DIR))" --custom-layout $(if $(GUI_AVAILABLE),--gui)
$(BUILD_DIR)/src/plugin/modern.o: override CPPFLAGS += -isystem third_party/clap/include -isystem third_party/vst3
$(BUILD_DIR)/clap-fixture.clap: tests/plugins/clap_fixture.c
@mkdir -p $(@D)
$(CC) $(CPPFLAGS) $(CFLAGS) -isystem third_party/clap/include $(LDFLAGS) -fPIC -shared -o $@ $<
$(BUILD_DIR)/vst2-fixture.so: tests/plugins/vst2_fixture.c src/plugin/vst2_abi.h
@mkdir -p $(@D)
$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -fPIC -shared -o $@ $<
$(BUILD_DIR)/vst3-fixture.so: tests/plugins/vst3_fixture.c
@mkdir -p $(@D)
$(CC) $(CPPFLAGS) $(CFLAGS) -isystem third_party/vst3 $(LDFLAGS) -fPIC -shared -o $@ $<
$(BUILD_DIR)/test_modern_plugins: $(BUILD_DIR)/tests/integration/test_modern_plugins.o $(BUILD_DIR)/libxtc.a
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl -Wl,--wrap=malloc,--wrap=calloc,--wrap=realloc,--wrap=free,--wrap=dlopen,--wrap=dlclose
.PHONY: test-modern-plugins
test-modern-plugins: $(BUILD_DIR)/test_modern_plugins $(BUILD_DIR)/clap-fixture.clap $(BUILD_DIR)/vst2-fixture.so $(BUILD_DIR)/vst3-fixture.so
./$(BUILD_DIR)/test_modern_plugins "$(abspath $(BUILD_DIR))/clap-fixture.clap" "$(abspath $(BUILD_DIR))/vst2-fixture.so" "$(abspath $(BUILD_DIR))/vst3-fixture.so"
test: test-modern-plugins
-include $(BUILD_DIR)/tests/integration/test_modern_plugins.d
.PHONY: test-gui-modern-plugins
ifeq ($(GUI_AVAILABLE),yes)
test-gui-modern-plugins: $(BUILD_DIR)/test_gui $(BUILD_DIR)/clap-fixture.clap $(BUILD_DIR)/vst2-fixture.so $(BUILD_DIR)/vst3-fixture.so
XTC_TEST_CLAP="$(abspath $(BUILD_DIR))/clap-fixture.clap" XTC_TEST_VST2="$(abspath $(BUILD_DIR))/vst2-fixture.so" XTC_TEST_VST3="$(abspath $(BUILD_DIR))/vst3-fixture.so" xvfb-run -a -s "-screen 0 1280x800x24" ./$(BUILD_DIR)/test_gui
else
test-gui-modern-plugins:
@echo "FLTK development files are required for modern plugin GUI tests."
@exit 1
endif
$(BUILD_DIR)/clap-synth-fixture.clap: tests/plugins/clap_synth_fixture.c
@mkdir -p $(@D)
$(CC) $(CPPFLAGS) $(CFLAGS) -isystem third_party/clap/include $(LDFLAGS) -fPIC -shared -o $@ $<
$(BUILD_DIR)/vst2-synth-fixture.so: tests/plugins/vst2_synth_fixture.c src/plugin/vst2_abi.h
@mkdir -p $(@D)
$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -fPIC -shared -o $@ $<
$(BUILD_DIR)/vst3-synth-fixture.so: tests/plugins/vst3_synth_fixture.c
@mkdir -p $(@D)
$(CC) $(CPPFLAGS) $(CFLAGS) -isystem third_party/vst3 $(LDFLAGS) -fPIC -shared -o $@ $<
$(BUILD_DIR)/test_instruments: $(BUILD_DIR)/tests/integration/test_instruments.o $(BUILD_DIR)/libxtc.a
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl -Wl,--wrap=malloc,--wrap=calloc,--wrap=realloc,--wrap=free,--wrap=dlopen,--wrap=dlclose
.PHONY: test-instruments
test-instruments: $(BUILD_DIR)/test_instruments $(BUILD_DIR)/clap-synth-fixture.clap $(BUILD_DIR)/vst2-synth-fixture.so $(BUILD_DIR)/vst3-synth-fixture.so
./$(BUILD_DIR)/test_instruments "$(abspath $(BUILD_DIR))/clap-synth-fixture.clap" "$(abspath $(BUILD_DIR))/vst2-synth-fixture.so" "$(abspath $(BUILD_DIR))/vst3-synth-fixture.so"
test: test-instruments
-include $(BUILD_DIR)/tests/integration/test_instruments.d
.PHONY: test-gui-instruments
test-gui-instruments: $(BUILD_DIR)/test_gui $(BUILD_DIR)/clap-synth-fixture.clap $(BUILD_DIR)/vst2-synth-fixture.so $(BUILD_DIR)/vst3-synth-fixture.so
XTC_TEST_SYNTH_CLAP="$(abspath $(BUILD_DIR))/clap-synth-fixture.clap" XTC_TEST_SYNTH_VST2="$(abspath $(BUILD_DIR))/vst2-synth-fixture.so" XTC_TEST_SYNTH_VST3="$(abspath $(BUILD_DIR))/vst3-synth-fixture.so" xvfb-run -a -s "-screen 0 1280x800x24" ./$(BUILD_DIR)/test_gui
$(BUILD_DIR)/test_settings: $(BUILD_DIR)/tests/unit/test_settings.o $(BUILD_DIR)/libxtc.a
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl
.PHONY: test-settings
test-settings: $(BUILD_DIR)/test_settings
./$(BUILD_DIR)/test_settings
test: test-settings
-include $(BUILD_DIR)/tests/unit/test_settings.d
$(BUILD_DIR)/test_builtin: tests/unit/test_builtin.c $(BUILD_DIR)/libxtc.a
$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -o $@ $< $(BUILD_DIR)/libxtc.a -lm -ldl
.PHONY: test-builtins
test: test-builtins
test-builtins: $(BUILD_DIR)/test_builtin
./$(BUILD_DIR)/test_builtin
$(BUILD_DIR)/builtin-preview: $(BUILD_DIR)/examples/builtin_preview.o $(BUILD_DIR)/libxtc.a
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl
.PHONY: builtin-preview
builtin-preview: $(BUILD_DIR)/builtin-preview
./$(BUILD_DIR)/builtin-preview "$(abspath $(BUILD_DIR))/builtin"
-include $(BUILD_DIR)/examples/builtin_preview.d
$(BUILD_DIR)/test_modern_editing: tests/unit/test_modern_editing.c $(BUILD_DIR)/libxtc.a
$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -o $@ $< $(BUILD_DIR)/libxtc.a -lm -ldl
.PHONY: test-modern-editing
test-modern-editing: $(BUILD_DIR)/test_modern_editing
./$(BUILD_DIR)/test_modern_editing
.PHONY: test-gui-modern-editing
test-gui-modern-editing: $(BUILD_DIR)/test_gui
XTC_TEST_MODERN_EDITING_ONLY=1 xvfb-run -a -s "-screen 0 1280x800x24" ./$(BUILD_DIR)/test_gui
$(BUILD_DIR)/test_next_daw: tests/unit/test_next_daw.c $(BUILD_DIR)/libxtc.a
$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -o $@ $< $(BUILD_DIR)/libxtc.a -lm -ldl
.PHONY: test-next-daw test-gui-next-daw
test-next-daw: $(BUILD_DIR)/test_next_daw
./$(BUILD_DIR)/test_next_daw
test: test-next-daw
test-gui-next-daw: $(BUILD_DIR)/test_gui
XTC_TEST_MODERN_EDITING_ONLY=1 XTC_TEST_NEXT_DAW_ONLY=1 xvfb-run -a -s "-screen 0 1280x800x24" ./$(BUILD_DIR)/test_gui
# Optional documentation capture; requires Xvfb, ImageMagick import and xdotool.
$(BUILD_DIR)/capture_screenshots: $(BUILD_DIR)/tools/capture_screenshots.o $(BUILD_DIR)/src/gui/main_window.o $(BUILD_DIR)/src/gui/graph_widget.o $(BUILD_DIR)/src/gui/sequencer_widget.o $(BUILD_DIR)/src/gui/piano_roll.o $(BUILD_DIR)/src/gui/mixer_widget.o $(BUILD_DIR)/src/gui/plugin_dialog.o $(BUILD_DIR)/src/gui/sample_editor.o $(BUILD_DIR)/libxtc.a
$(CXX) $(LDFLAGS) -o $@ $^ $(FLTK_LIBS) $(JACK_LIBS) -lm -ldl
.PHONY: screenshots
screenshots: $(BUILD_DIR)/capture_screenshots $(BUILD_DIR)/plugin-fixture.so $(BUILD_DIR)/clap-synth-fixture.clap $(BUILD_DIR)/vst2-synth-fixture.so $(BUILD_DIR)/vst3-synth-fixture.so
xvfb-run -a -s "-screen 0 1440x960x24" ./$(BUILD_DIR)/capture_screenshots "$(CURDIR)/screenshots"
-include $(BUILD_DIR)/tools/capture_screenshots.d
$(BUILD_DIR)/test_sample: $(BUILD_DIR)/tests/unit/test_sample.o $(BUILD_DIR)/libxtc.a
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl
.PHONY: test-sample test-gui-sample
test-sample: $(BUILD_DIR)/test_sample
./$(BUILD_DIR)/test_sample
test: test-sample
test-gui-sample: $(BUILD_DIR)/test_gui
XTC_TEST_SAMPLE_EDITOR_ONLY=1 xvfb-run -a -s "-screen 0 1280x900x24" ./$(BUILD_DIR)/test_gui
-include $(BUILD_DIR)/src/gui/sample_editor.d $(BUILD_DIR)/tests/unit/test_sample.d
$(BUILD_DIR)/test_track_fx: $(BUILD_DIR)/tests/unit/test_track_fx.o $(BUILD_DIR)/libxtc.a
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl
.PHONY: test-track-fx
test-track-fx: $(BUILD_DIR)/test_track_fx
./$(BUILD_DIR)/test_track_fx
test: test-track-fx
-include $(BUILD_DIR)/tests/unit/test_track_fx.d
.PHONY: test-gui-track-fx
test-gui-track-fx: $(BUILD_DIR)/test_gui $(BUILD_DIR)/plugin-fixture.so $(BUILD_DIR)/clap-fixture.clap $(BUILD_DIR)/vst2-fixture.so $(BUILD_DIR)/vst3-fixture.so
XTC_TEST_TRACK_FX_ONLY=1 XTC_TEST_PLUGIN="$(abspath $(BUILD_DIR))/plugin-fixture.so" XTC_TEST_CLAP="$(abspath $(BUILD_DIR))/clap-fixture.clap" XTC_TEST_VST2="$(abspath $(BUILD_DIR))/vst2-fixture.so" XTC_TEST_VST3="$(abspath $(BUILD_DIR))/vst3-fixture.so" xvfb-run -a -s "-screen 0 1280x900x24" ./$(BUILD_DIR)/test_gui
$(BUILD_DIR)/test_creative: $(BUILD_DIR)/tests/unit/test_creative.o $(BUILD_DIR)/libxtc.a
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl
.PHONY: test-creative test-gui-creative
test-creative: $(BUILD_DIR)/test_creative
./$(BUILD_DIR)/test_creative
test: test-creative
test-gui-creative: $(BUILD_DIR)/test_gui
XTC_TEST_CREATIVE_ONLY=1 xvfb-run -a -s "-screen 0 1280x900x24" ./$(BUILD_DIR)/test_gui
-include $(BUILD_DIR)/tests/unit/test_creative.d
.PHONY: test-gui-midi-workflows
test-gui-midi-workflows: $(BUILD_DIR)/test_gui
XTC_TEST_MIDI_WORKFLOWS_ONLY=1 xvfb-run -a -s "-screen 0 1440x960x24" ./$(BUILD_DIR)/test_gui
$(BUILD_DIR)/test_midi_file: $(BUILD_DIR)/tests/unit/test_midi_file.o $(BUILD_DIR)/libxtc.a
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl
.PHONY: test-midi-file
test-midi-file: $(BUILD_DIR)/test_midi_file
./$(BUILD_DIR)/test_midi_file
test: test-midi-file
-include $(BUILD_DIR)/tests/unit/test_midi_file.d
.PHONY: test-configuration-startup
test-configuration-startup: gui
xvfb-run -a -s "-screen 0 1440x960x24" $(PYTHON) tests/integration/test_configuration_startup.py ./$(BUILD_DIR)/xtc
.PHONY: test-gui-midi-labels
test-gui-midi-labels: $(BUILD_DIR)/test_gui
XTC_TEST_MIDI_LABELS_ONLY=1 xvfb-run -a -s "-screen 0 1440x960x24" ./$(BUILD_DIR)/test_gui
# Optional external Steinberg SDK qualification. Normal builds never fetch SDKs.
VST3_SDK_ROOT ?=
STEINBERG_BUILD_DIR ?= $(BUILD_DIR)/steinberg-sdk
.PHONY: steinberg-sdk test-steinberg-sdk test-plugin-module-path
steinberg-sdk:
@test -n "$(VST3_SDK_ROOT)" || { echo "Set VST3_SDK_ROOT to an official recursive SDK checkout."; exit 1; }
cmake -S examples/steinberg -B "$(STEINBERG_BUILD_DIR)" -DXTC_VST3_SDK_ROOT="$(abspath $(VST3_SDK_ROOT))" -DCMAKE_BUILD_TYPE=Release
cmake --build "$(STEINBERG_BUILD_DIR)" --target xtc-sdk-examples validator --parallel 4
$(BUILD_DIR)/test_plugin_module_path: $(BUILD_DIR)/tests/unit/test_plugin_module_path.o $(BUILD_DIR)/libxtc.a
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl
test-plugin-module-path: $(BUILD_DIR)/test_plugin_module_path
./$(BUILD_DIR)/test_plugin_module_path
test: test-plugin-module-path
$(BUILD_DIR)/test_steinberg_sdk: $(BUILD_DIR)/tests/integration/test_steinberg_sdk.o $(BUILD_DIR)/libxtc.a
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl
$(BUILD_DIR)/steinberg-render: $(BUILD_DIR)/examples/steinberg/render.o $(BUILD_DIR)/libxtc.a
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl
# Linux bundle convention; native Windows SDK examples use CMake directly.
test-steinberg-sdk: steinberg-sdk $(BUILD_DIR)/test_steinberg_sdk $(BUILD_DIR)/steinberg-render
"$(STEINBERG_BUILD_DIR)/bin/Release/validator" "$(abspath $(STEINBERG_BUILD_DIR))/VST3/Release/xtc-sdk-examples.vst3"
./$(BUILD_DIR)/test_steinberg_sdk "$(abspath $(STEINBERG_BUILD_DIR))/VST3/Release/xtc-sdk-examples.vst3"
-include $(BUILD_DIR)/tests/unit/test_plugin_module_path.d $(BUILD_DIR)/tests/integration/test_steinberg_sdk.d $(BUILD_DIR)/examples/steinberg/render.d
# Optional JUCE bridge/examples; externally supplied SDK, no downloads or installs.
JUCE_ROOT ?=
JUCE_BUILD_DIR ?= $(BUILD_DIR)/juce
.PHONY: juce-examples test-juce
juce-examples:
@test -n "$(JUCE_ROOT)" || { echo "Set JUCE_ROOT to an external JUCE 9 checkout (AGPLv3/commercial)."; exit 1; }
cmake -S examples/juce -B "$(JUCE_BUILD_DIR)" -DXTC_JUCE_ROOT="$(abspath $(JUCE_ROOT))" -DCMAKE_BUILD_TYPE=Release
cmake --build "$(JUCE_BUILD_DIR)" --target xtc-juce-render XtcJuceSynth_VST3 XtcJuceSynth_Standalone --parallel 3
$(BUILD_DIR)/test_juce_plugin: $(BUILD_DIR)/tests/integration/test_juce_plugin.o $(BUILD_DIR)/libxtc.a
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl
test-juce: juce-examples $(BUILD_DIR)/test_juce_plugin
xvfb-run -a $(PYTHON) tests/integration/test_juce_examples.py "$(JUCE_BUILD_DIR)/xtc-juce-render_artefacts/Release/xtc-juce-render"
xvfb-run -a ctest --test-dir "$(JUCE_BUILD_DIR)" --output-on-failure
xvfb-run -a ./$(BUILD_DIR)/test_juce_plugin "$(abspath $(JUCE_BUILD_DIR))/XtcJuceSynth_artefacts/Release/VST3/xtc JUCE Synth.vst3"
-include $(BUILD_DIR)/tests/integration/test_juce_plugin.d

View file

@ -40,23 +40,37 @@ hosting supports the compatible mono processors documented in
## Build ## Build
Requirements: a C compiler, a C++ compiler, GNU Make, and `pkg-config`. FLTK is optional for the core and tests. Requirements: CMake 3.25+, a C99 compiler, Python 3.12+ and a build backend
such as Ninja or Unix Makefiles. The optional GUI needs C++17 and FLTK; JACK and
LADSPA development files enable their optional integrations.
```sh ```sh
make cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug
make test cmake --build build --parallel
ctest --test-dir build --output-on-failure
``` ```
`make` also builds `build/xtc-check`, a headless core startup/shutdown For quick development, the optional Make wrapper provides the same workflow:
check. Use `make debug` for a separate debug build and test run. See
[docs/BUILD.md](docs/BUILD.md) for the inspected toolchain and baseline limits.
Build the optional FLTK GUI if FLTK is installed:
```sh ```sh
make gui make # configure and build
make test # build and run CTest
make gui # build just the FLTK application
make run # build and launch it
make clean # clean products; keep the CMake cache
``` ```
Override `BUILD_DIR`, `BUILD_TYPE` or `JOBS` as needed, for example
`make BUILD_DIR=build/release BUILD_TYPE=Release JOBS=8`. Pass additional options
with `CMAKE_ARGS` and `CTEST_ARGS`. CMake remains the authoritative build system.
This builds `libxtc.a`, the headless `xtc-check`, tests, examples and the FLTK
application when available. Live JACK tests are opt-in. Use `-DXTC_BUILD_GUI=OFF`
for the C-only build, or the `debug`, `release`, `minimal` and `sanitize` presets
for separate configurations. See [docs/BUILD.md](docs/BUILD.md) for options,
focused tests, installation and IDE setup. [Phase 108](docs/ROADMAP.md#phase-108--complete-cmake-build-migration) records
the CMake migration.
Run the GUI: Run the GUI:
```sh ```sh
@ -93,24 +107,24 @@ active graph intact; queued changes are labeled until the engine adopts them.
See [GUI documentation](docs/GUI.md). See [GUI documentation](docs/GUI.md).
For Linux installation, routing and CLI options see [LINUX.md](docs/LINUX.md); For Linux installation, routing and CLI options see [LINUX.md](docs/LINUX.md);
for native effects/instruments see [NATIVE_API.md](docs/NATIVE_API.md). for native effects/instruments see [NATIVE_API.md](docs/NATIVE_API.md).
`make release`, `make dist`, `make test-install` and `make test-release` provide The release preset and CMake’s `dist`, `test-install`, `test-release` and
release preparation; see [RELEASING.md](docs/RELEASING.md) for exact guarantees. `test-package` targets provide release preparation; see
With JACK development files and an existing JACK-compatible server, run the [RELEASING.md](docs/RELEASING.md) for exact guarantees.
isolated software-loopback test with `make test-jack` and the record/reopen/play With JACK development files and an existing JACK-compatible server, configure
acceptance test with `make test-jack-audio` and MIDI acceptance with with `-DXTC_ENABLE_LIVE_TESTS=ON`, build, and run
`make test-jack-midi`. It connects only test `ctest --test-dir build --output-on-failure -L live`. These software-loopback
clients. See [engine documentation](docs/AUDIO_ENGINE.md) for configuration, checks connect only test clients. See [engine documentation](docs/AUDIO_ENGINE.md)
routing, ownership and recovery. for configuration, routing, ownership and recovery.
Clean generated files: Clean generated files:
```sh ```sh
make clean cmake --build build --target clean
``` ```
## Suggested first agent task ## Suggested first agent task
Read `AGENT.md` and `docs/ARCHITECTURE.md`. Inspect the existing implementation, run `make test`, and report gaps before adding the next feature. Do not jump directly to plugin hosting or a full sequencer. Read `AGENT.md` and `docs/ARCHITECTURE.md`. Inspect the existing implementation, run `cmake --build build --target check`, and report gaps before adding the next feature. Do not jump directly to plugin hosting or a full sequencer.
## License ## License
@ -128,19 +142,19 @@ See [RECOVERY.md](docs/RECOVERY.md), [NATIVE_API.md](docs/NATIVE_API.md) and
For everyday use see [USER_MANUAL.md](docs/USER_MANUAL.md), For everyday use see [USER_MANUAL.md](docs/USER_MANUAL.md),
[KNOWN_LIMITATIONS.md](docs/KNOWN_LIMITATIONS.md) and [KNOWN_LIMITATIONS.md](docs/KNOWN_LIMITATIONS.md) and
[MAINTENANCE.md](docs/MAINTENANCE.md). `xtc-check --diagnose` reports build features; [MAINTENANCE.md](docs/MAINTENANCE.md). `xtc-check --diagnose` reports build features;
`make test-package` checks a local system-linked Linux binary archive. `cmake --build build --target test-package` checks a local system-linked Linux binary archive.
Effect automation/controllers, editing commands, v7 project-folder media paths, Effect automation/controllers, editing commands, v7 project-folder media paths,
performance baselines and release/installation checks are also supported. performance baselines and release/installation checks are also supported.
Non-destructive audio splitting, clip gain/fades and cached timeline waveforms Non-destructive audio splitting, clip gain/fades and cached timeline waveforms
are supported, with undo and v8 persistence (reading v1–v7). See are supported, with undo and v8 persistence (reading v1–v7). See
[AUDIO_FILES.md](docs/AUDIO_FILES.md#non-destructive-clip-editing). [AUDIO_FILES.md](docs/AUDIO_FILES.md#non-destructive-clip-editing).
`make test-audio-editing` checks source preservation and exact boundary output. `cmake --build build --target test-audio-editing` checks source preservation and exact boundary output.
Time stretching and independent pitch shifting remain deferred. Time stretching and independent pitch shifting remain deferred.
Signal-flow graph layout, routing diagnostics, direction/role indicators and Signal-flow graph layout, routing diagnostics, direction/role indicators and
atomic unity-gain insertion into a wire are supported. See atomic unity-gain insertion into a wire are supported. See
[ROUTING.md](docs/ROUTING.md); `make test-routing` verifies the C routing contract. [ROUTING.md](docs/ROUTING.md); `cmake --build build --target test-routing` verifies the C routing contract.
One-level mix buses, routing, coherent track/bus peaks and master clipping One-level mix buses, routing, coherent track/bus peaks and master clipping
indicators are supported, persisted in v9 (reading v1–v8). See [MIXER.md](docs/MIXER.md). indicators are supported, persisted in v9 (reading v1–v8). See [MIXER.md](docs/MIXER.md).
@ -198,12 +212,12 @@ for file support, routing and persistence details.
[Steinberg SDK integration (Phase 106)](docs/STEINBERG.md) adds conventional Linux [Steinberg SDK integration (Phase 106)](docs/STEINBERG.md) adds conventional Linux
VST3 bundle loading, transport context, official-SDK gain/synth examples and VST3 bundle loading, transport context, official-SDK gain/synth examples and
validator checks. Run `make VST3_SDK_ROOT=/path/to/vst3sdk test-steinberg-sdk` validator checks. Run `cmake -S . -B build -DXTC_VST3_SDK_ROOT=/path/to/vst3sdk -DXTC_BUILD_STEINBERG=ON && cmake --build build --target test-steinberg-sdk`
for optional SDK qualification. A Windows ASIO device diagnostic is provided; for optional SDK qualification. A Windows ASIO device diagnostic is provided;
a native ASIO playback/capture backend remains planned. a native ASIO playback/capture backend remains planned.
[JUCE integration (Phase 107)](docs/JUCE.md) provides an optional VST3/standalone [JUCE integration (Phase 107)](docs/JUCE.md) provides an optional VST3/standalone
Soft Keys instrument with a native editor, DSP effects, presets, MIDI-to-WAV Soft Keys instrument with a native editor, DSP effects, presets, MIDI-to-WAV
rendering and VST3 hosting examples. Build and qualify it with rendering and VST3 hosting examples. Build and qualify it with
`make JUCE_ROOT=/path/to/JUCE test-juce`; review JUCE’s AGPLv3/commercial licence `cmake -S . -B build -DXTC_JUCE_ROOT=/path/to/JUCE -DXTC_BUILD_JUCE=ON && cmake --build build --target test-juce`; review JUCE’s AGPLv3/commercial licence
before distributing linked binaries. See [the examples](examples/juce/README.md). before distributing linked binaries. See [the examples](examples/juce/README.md).

57
cmake/Developer.cmake Normal file
View file

@ -0,0 +1,57 @@
get_target_property(xtc_sources xtc-core SOURCES)
add_custom_target(analyze COMMAND ${Python3_EXECUTABLE} tools/analyze.py ${xtc_sources}
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" VERBATIM)
if(BUILD_TESTING)
xtc_executable(benchmark_graph tests/integration/benchmark_graph.c)
set(XTC_BENCHMARK_BLOCKS 300 CACHE STRING "Benchmark iterations")
set(XTC_BENCHMARK_MAX_REGRESSION 1.5 CACHE STRING "Benchmark regression limit")
set(XTC_STRESS_ROUNDS 3 CACHE STRING "Stress iterations")
set(XTC_BENCHMARK_BASELINE "${PROJECT_SOURCE_DIR}/docs/performance-baseline.json" CACHE FILEPATH "Graph benchmark baseline")
set(XTC_PROJECT_BENCHMARK_BASELINE "${PROJECT_SOURCE_DIR}/docs/project-performance-baseline.json" CACHE FILEPATH "Project benchmark baseline")
foreach(workload graph project)
if(workload STREQUAL graph)
set(binary benchmark_graph)
set(stem benchmark)
set(baseline "${XTC_BENCHMARK_BASELINE}")
set(report performance.json)
else()
set(binary test_performance)
set(stem benchmark-project)
set(baseline "${XTC_PROJECT_BENCHMARK_BASELINE}")
set(report project-performance.json)
endif()
foreach(compare "" "-compare")
set(extra)
if(compare)
set(extra --baseline "${baseline}" --max-regression ${XTC_BENCHMARK_MAX_REGRESSION})
endif()
add_custom_target(${stem}${compare}
COMMAND ${Python3_EXECUTABLE} tools/benchmark.py --workload ${workload}
--binary $<TARGET_FILE:${binary}> --output "${CMAKE_CURRENT_BINARY_DIR}/${report}"
--compiler "${CMAKE_C_COMPILER}" --flags "${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_RELEASE}"
--blocks ${XTC_BENCHMARK_BLOCKS} ${extra}
DEPENDS ${binary} WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" VERBATIM)
endforeach()
endforeach()
set(stress_deps test_realtime test_reliability test_scale test_portability test_project_diagnostics test_soak)
if(TARGET test_backend)
list(APPEND stress_deps test_backend)
endif()
set(gui_arg)
if(TARGET test_gui)
set(gui_arg --gui)
list(APPEND stress_deps test_gui)
endif()
add_custom_target(test-stress COMMAND ${Python3_EXECUTABLE} tools/stress.py
--build-dir "${CMAKE_CURRENT_BINARY_DIR}" --rounds ${XTC_STRESS_ROUNDS} ${gui_arg}
DEPENDS ${stress_deps} WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" VERBATIM)
if(XTC_LADSPA_AVAILABLE)
add_custom_target(test-stress-plugins COMMAND ${Python3_EXECUTABLE} tools/stress.py
--build-dir "${CMAKE_CURRENT_BINARY_DIR}" --rounds ${XTC_STRESS_ROUNDS} --plugin $<TARGET_FILE:plugin-fixture>
DEPENDS ${stress_deps} test_plugins plugin-fixture WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" VERBATIM)
endif()
add_custom_target(test-plugins-real
COMMAND test_plugins /usr/lib/ladspa/amp.so
COMMAND test_plugins /usr/lib/ladspa/inv_1429.so
COMMAND test_plugins /usr/lib/ladspa/sine.so 3 DEPENDS test_plugins VERBATIM)
endif()

87
cmake/Gui.cmake Normal file
View file

@ -0,0 +1,87 @@
if(NOT XTC_BUILD_GUI)
return()
endif()
if(PkgConfig_FOUND)
pkg_check_modules(FLTK QUIET IMPORTED_TARGET fltk)
endif()
if(FLTK_FOUND)
set(xtc_fltk PkgConfig::FLTK)
else()
find_program(XTC_FLTK_CONFIG fltk-config)
if(NOT XTC_FLTK_CONFIG)
message(STATUS "FLTK unavailable: building core and offline tests only")
return()
endif()
execute_process(COMMAND ${XTC_FLTK_CONFIG} --cxxflags OUTPUT_VARIABLE fltk_flags OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND_ERROR_IS_FATAL ANY)
execute_process(COMMAND ${XTC_FLTK_CONFIG} --ldflags OUTPUT_VARIABLE fltk_libs OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND_ERROR_IS_FATAL ANY)
separate_arguments(fltk_flags UNIX_COMMAND "${fltk_flags}")
separate_arguments(fltk_libs UNIX_COMMAND "${fltk_libs}")
# Distribution flags must not override this project's debug/release settings.
list(FILTER fltk_flags INCLUDE REGEX "^(-I|-D|-pthread)")
add_library(xtc-fltk INTERFACE)
target_compile_options(xtc-fltk INTERFACE ${fltk_flags})
target_link_libraries(xtc-fltk INTERFACE ${fltk_libs})
set(xtc_fltk xtc-fltk)
endif()
enable_language(CXX)
set(XTC_GUI_AVAILABLE TRUE)
add_library(xtc-gui STATIC src/gui/main_window.cpp src/gui/graph_widget.cpp
src/gui/sequencer_widget.cpp src/gui/piano_roll.cpp src/gui/mixer_widget.cpp
src/gui/plugin_dialog.cpp src/gui/sample_editor.cpp)
xtc_warnings(xtc-gui)
target_link_libraries(xtc-gui PUBLIC xtc-core ${xtc_fltk})
add_executable(xtc src/gui/main.cpp)
xtc_warnings(xtc)
target_link_libraries(xtc PRIVATE xtc-gui)
add_custom_target(gui DEPENDS xtc)
find_program(XTC_XVFB_RUN xvfb-run)
add_executable(capture_screenshots EXCLUDE_FROM_ALL tools/capture_screenshots.cpp)
xtc_warnings(capture_screenshots)
target_link_libraries(capture_screenshots PRIVATE xtc-gui)
if(XTC_XVFB_RUN AND BUILD_TESTING)
if(XTC_LADSPA_AVAILABLE)
add_custom_target(screenshots COMMAND ${XTC_XVFB_RUN} -a -s "-screen 0 1440x960x24"
$<TARGET_FILE:capture_screenshots> "${PROJECT_SOURCE_DIR}/screenshots"
DEPENDS capture_screenshots plugin-fixture clap-synth-fixture vst2-synth-fixture vst3-synth-fixture
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" VERBATIM)
endif()
add_executable(test_gui tests/integration/test_gui.cpp)
xtc_warnings(test_gui)
target_link_libraries(test_gui PRIVATE xtc-gui)
xtc_wrap(test_gui xtc_history_reserve)
function(xtc_gui_test name)
add_test(NAME ${name} COMMAND ${XTC_XVFB_RUN} -a -s "-screen 0 1440x960x24" $<TARGET_FILE:test_gui>)
set_tests_properties(${name} PROPERTIES WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" LABELS gui ENVIRONMENT "${ARGN}")
endfunction()
xtc_gui_test(gui)
if(XTC_LADSPA_AVAILABLE)
xtc_gui_test(gui-plugins "XTC_TEST_PLUGIN=$<TARGET_FILE:plugin-fixture>")
endif()
xtc_gui_test(gui-modern-plugins "XTC_TEST_CLAP=$<TARGET_FILE:clap-fixture>" "XTC_TEST_VST2=$<TARGET_FILE:vst2-fixture>" "XTC_TEST_VST3=$<TARGET_FILE:vst3-fixture>")
xtc_gui_test(gui-instruments "XTC_TEST_SYNTH_CLAP=$<TARGET_FILE:clap-synth-fixture>" "XTC_TEST_SYNTH_VST2=$<TARGET_FILE:vst2-synth-fixture>" "XTC_TEST_SYNTH_VST3=$<TARGET_FILE:vst3-synth-fixture>")
foreach(pair "modern-editing|MODERN_EDITING" "next-daw|NEXT_DAW" "sample|SAMPLE_EDITOR" "creative|CREATIVE" "midi-workflows|MIDI_WORKFLOWS" "midi-labels|MIDI_LABELS")
string(REPLACE "|" ";" pair "${pair}")
list(GET pair 0 name)
list(GET pair 1 env)
xtc_gui_test(gui-${name} "XTC_TEST_${env}_ONLY=1")
endforeach()
set_property(TEST gui-next-daw APPEND PROPERTY ENVIRONMENT XTC_TEST_MODERN_EDITING_ONLY=1)
if(XTC_LADSPA_AVAILABLE)
xtc_gui_test(gui-track-fx "XTC_TEST_TRACK_FX_ONLY=1" "XTC_TEST_PLUGIN=$<TARGET_FILE:plugin-fixture>" "XTC_TEST_CLAP=$<TARGET_FILE:clap-fixture>" "XTC_TEST_VST2=$<TARGET_FILE:vst2-fixture>" "XTC_TEST_VST3=$<TARGET_FILE:vst3-fixture>")
endif()
add_test(NAME configuration-startup COMMAND ${XTC_XVFB_RUN} -a -s "-screen 0 1440x960x24" ${Python3_EXECUTABLE}
"${PROJECT_SOURCE_DIR}/tests/integration/test_configuration_startup.py" $<TARGET_FILE:xtc>)
set_tests_properties(configuration-startup PROPERTIES LABELS gui)
if(JACK_FOUND AND XTC_WITH_JACK)
foreach(kind gui_jack workflow)
add_executable(test_${kind} tests/integration/test_${kind}.cpp)
xtc_warnings(test_${kind})
target_link_libraries(test_${kind} PRIVATE xtc-gui)
if(XTC_ENABLE_LIVE_TESTS)
string(REPLACE "_" "-" name "${kind}")
add_test(NAME ${name} COMMAND ${XTC_XVFB_RUN} -a $<TARGET_FILE:test_${kind}>)
set_tests_properties(${name} PROPERTIES WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" LABELS live RUN_SERIAL TRUE)
endif()
endforeach()
endif()
endif()

46
cmake/Install.cmake Normal file
View file

@ -0,0 +1,46 @@
# Retain the audited owned-file inventory and symlink-safe staging implementation.
set(XTC_INSTALL_BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}" CACHE PATH "Directory containing built install inputs")
option(XTC_INSTALL_GUI "Include the GUI in installation" ${XTC_GUI_AVAILABLE})
set(XTC_INSTALL_LICENSEDIR "${CMAKE_INSTALL_DATAROOTDIR}/licenses/xtc" CACHE STRING "License install directory")
set(XTC_INSTALL_EXAMPLESDIR "${CMAKE_INSTALL_DATAROOTDIR}/xtc/examples" CACHE STRING "Example install directory")
set(XTC_INSTALL_DESKTOPDIR "${CMAKE_INSTALL_DATAROOTDIR}/applications" CACHE STRING "Desktop entry directory")
set(XTC_INSTALL_PKGCONFIGDIR "${CMAKE_INSTALL_LIBDIR}/pkgconfig" CACHE STRING "pkg-config directory")
configure_file(cmake/InstallDriver.cmake.in xtc-install.cmake @ONLY)
install(SCRIPT "${CMAKE_CURRENT_BINARY_DIR}/xtc-install.cmake")
add_custom_target(uninstall COMMAND ${CMAKE_COMMAND} -DXTC_UNINSTALL=ON -P "${CMAKE_CURRENT_BINARY_DIR}/xtc-install.cmake" VERBATIM)
add_custom_target(dist COMMAND ${Python3_EXECUTABLE} tools/dist.py --output "${CMAKE_CURRENT_BINARY_DIR}/xtc-${PROJECT_VERSION}.tar.gz"
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" VERBATIM)
set(gui_arg)
if(XTC_GUI_AVAILABLE)
set(gui_arg --gui)
endif()
if(BUILD_TESTING)
foreach(layout "" "-layout")
set(extra)
if(layout)
set(extra --custom-layout)
endif()
add_custom_target(test-install${layout} COMMAND ${Python3_EXECUTABLE} tests/integration/test_install.py
--build-dir "${CMAKE_CURRENT_BINARY_DIR}" ${gui_arg} ${extra}
DEPENDS xtc-core xtc-check
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" VERBATIM)
if(XTC_GUI_AVAILABLE)
add_dependencies(test-install${layout} xtc)
endif()
endforeach()
add_custom_target(test-release COMMAND ${Python3_EXECUTABLE} tests/integration/test_release.py
"${CMAKE_CURRENT_BINARY_DIR}/xtc-${PROJECT_VERSION}.tar.gz" --generator "${CMAKE_GENERATOR}" ${gui_arg}
DEPENDS dist WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" VERBATIM)
endif()
set(package_file "${CMAKE_CURRENT_BINARY_DIR}/xtc-${PROJECT_VERSION}-linux-${CMAKE_SYSTEM_PROCESSOR}.tar.gz")
add_custom_target(package-binary COMMAND ${Python3_EXECUTABLE} tools/package.py
--build-dir "${CMAKE_CURRENT_BINARY_DIR}" --output "${package_file}" ${gui_arg}
DEPENDS xtc-core xtc-check WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" VERBATIM)
if(XTC_GUI_AVAILABLE)
add_dependencies(package-binary xtc)
endif()
add_custom_target(test-package COMMAND ${Python3_EXECUTABLE} tests/integration/test_package.py
"${package_file}" --build-dir "${CMAKE_CURRENT_BINARY_DIR}"
DEPENDS package-binary WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" VERBATIM)
add_custom_target(package DEPENDS package-binary)

View file

@ -0,0 +1,41 @@
if(NOT DEFINED CMAKE_INSTALL_PREFIX)
set(CMAKE_INSTALL_PREFIX "@CMAKE_INSTALL_PREFIX@")
endif()
set(layout --prefix "${CMAKE_INSTALL_PREFIX}" --destdir "$ENV{DESTDIR}")
set(keys bindir libdir includedir docdir licensedir examplesdir desktopdir pkgconfigdir)
set(dirs "@CMAKE_INSTALL_BINDIR@" "@CMAKE_INSTALL_LIBDIR@" "@CMAKE_INSTALL_INCLUDEDIR@" "@CMAKE_INSTALL_DOCDIR@"
"@XTC_INSTALL_LICENSEDIR@" "@XTC_INSTALL_EXAMPLESDIR@" "@XTC_INSTALL_DESKTOPDIR@" "@XTC_INSTALL_PKGCONFIGDIR@")
foreach(index RANGE 0 7)
list(GET keys ${index} key)
list(GET dirs ${index} dir)
if(NOT IS_ABSOLUTE "${dir}")
set(dir "${CMAKE_INSTALL_PREFIX}/${dir}")
endif()
list(APPEND layout --${key} "${dir}")
endforeach()
if(XTC_UNINSTALL)
set(operation uninstall)
set(extra)
else()
set(operation install)
set(extra --build-dir "@XTC_INSTALL_BUILD_DIR@")
if("@XTC_INSTALL_GUI@")
list(APPEND extra --gui)
endif()
set(jack)
if("@JACK_FOUND@" AND "@XTC_WITH_JACK@")
set(jack jack)
endif()
list(GET dirs 1 libdir)
list(GET dirs 2 includedir)
foreach(key libdir includedir)
if(NOT IS_ABSOLUTE "${${key}}")
set(${key} "${CMAKE_INSTALL_PREFIX}/${${key}}")
endif()
endforeach()
execute_process(COMMAND "@Python3_EXECUTABLE@" tools/pkgconfig.py --prefix "${CMAKE_INSTALL_PREFIX}"
--libdir "${libdir}" --includedir "${includedir}" --jack "${jack}" --output "@XTC_INSTALL_BUILD_DIR@/xtc.pc"
WORKING_DIRECTORY "@PROJECT_SOURCE_DIR@" COMMAND_ERROR_IS_FATAL ANY)
endif()
execute_process(COMMAND "@Python3_EXECUTABLE@" "tools/${operation}.py" ${layout} ${extra}
WORKING_DIRECTORY "@PROJECT_SOURCE_DIR@" COMMAND_ERROR_IS_FATAL ANY)

144
cmake/Tests.cmake Normal file
View file

@ -0,0 +1,144 @@
# Every test runs from the source root: fixtures/project paths are source-relative.
function(xtc_test name binary)
add_test(NAME ${name} COMMAND ${binary} ${ARGN})
set_tests_properties(${name} PROPERTIES WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}")
endfunction()
function(xtc_wrap target)
foreach(symbol IN LISTS ARGN)
target_link_options(${target} PRIVATE "LINKER:--wrap=${symbol}")
endforeach()
endfunction()
if(NOT BUILD_TESTING)
return()
endif()
xtc_executable(test_offline tests/unit/test_offline.c)
xtc_test(offline test_offline)
xtc_executable(test_realtime tests/unit/test_realtime.c)
xtc_test(realtime test_realtime)
xtc_executable(test_graph_edits tests/unit/test_graph_edits.c)
xtc_test(graph-edits test_graph_edits)
xtc_executable(test_project tests/unit/test_project.c)
xtc_test(project test_project)
xtc_executable(test_audio tests/unit/test_audio.c)
xtc_test(audio test_audio)
xtc_executable(test_midi tests/unit/test_midi.c)
xtc_test(midi test_midi)
xtc_executable(test_mixer tests/unit/test_mixer.c)
xtc_test(mixer test_mixer)
xtc_executable(test_native tests/unit/test_native.c)
xtc_test(native test_native)
xtc_executable(test_performance tests/integration/test_performance.c)
xtc_test(performance test_performance)
xtc_executable(test_recovery tests/integration/test_recovery.c)
xtc_test(recovery test_recovery)
xtc_executable(test_reliability tests/integration/test_reliability.c)
xtc_wrap(test_reliability malloc calloc realloc free fopen fread fwrite read write pthread_mutex_lock pthread_cond_wait)
xtc_test(reliability test_reliability)
xtc_executable(test_quality tests/unit/test_quality.c)
xtc_test(quality test_quality)
xtc_executable(test_recording tests/integration/test_recording.c)
xtc_test(recording test_recording)
xtc_executable(test_processor tests/unit/test_processor.c examples/processors.c)
xtc_test(processor test_processor)
xtc_executable(test_plugins tests/integration/test_plugins.c)
xtc_wrap(test_plugins malloc calloc realloc free dlopen dlclose)
xtc_executable(test_control tests/unit/test_control.c)
xtc_test(control test_control)
xtc_executable(test_editing tests/unit/test_editing.c)
xtc_test(editing test_editing)
xtc_executable(test_portability tests/integration/test_portability.c)
xtc_test(portability test_portability)
xtc_executable(test_scale tests/integration/test_scale.c)
xtc_test(scale test_scale)
xtc_executable(test_composition tests/unit/test_composition.c)
xtc_test(composition test_composition)
xtc_executable(test_audio_editing tests/unit/test_audio_editing.c)
xtc_test(audio-editing test_audio_editing)
xtc_executable(test_routing tests/unit/test_routing.c)
xtc_test(routing test_routing)
xtc_executable(test_buses tests/unit/test_buses.c)
xtc_test(buses test_buses)
xtc_executable(test_parameters tests/unit/test_parameters.c)
xtc_test(parameters test_parameters)
xtc_executable(test_project_diagnostics tests/unit/test_project_diagnostics.c)
xtc_wrap(test_project_diagnostics fflush fclose fsync rename)
xtc_test(project-diagnostics test_project_diagnostics)
xtc_executable(test_soak tests/integration/test_soak.c)
xtc_test(soak test_soak)
xtc_executable(test_modern_plugins tests/integration/test_modern_plugins.c)
xtc_wrap(test_modern_plugins malloc calloc realloc free dlopen dlclose)
xtc_executable(test_instruments tests/integration/test_instruments.c)
xtc_wrap(test_instruments malloc calloc realloc free dlopen dlclose)
xtc_executable(test_settings tests/unit/test_settings.c)
xtc_test(settings test_settings)
xtc_executable(test_builtin tests/unit/test_builtin.c)
xtc_test(builtin test_builtin)
xtc_executable(test_modern_editing tests/unit/test_modern_editing.c)
xtc_test(modern-editing test_modern_editing)
xtc_executable(test_next_daw tests/unit/test_next_daw.c)
xtc_test(next-daw test_next_daw)
xtc_executable(test_sample tests/unit/test_sample.c)
xtc_test(sample test_sample)
xtc_executable(test_track_fx tests/unit/test_track_fx.c)
xtc_test(track-fx test_track_fx)
xtc_executable(test_creative tests/unit/test_creative.c)
xtc_test(creative test_creative)
xtc_executable(test_midi_file tests/unit/test_midi_file.c)
xtc_test(midi-file test_midi_file)
xtc_executable(test_plugin_module_path tests/unit/test_plugin_module_path.c)
xtc_test(plugin-module-path test_plugin_module_path)
xtc_executable(test_core tests/unit/test_core.c)
xtc_test(core test_core)
if(XTC_LADSPA_AVAILABLE)
add_library(plugin-fixture MODULE tests/plugins/fixture.c)
xtc_warnings(plugin-fixture)
set_target_properties(plugin-fixture PROPERTIES PREFIX "" SUFFIX ".so")
target_include_directories(plugin-fixture SYSTEM PRIVATE "${PROJECT_SOURCE_DIR}/third_party/clap/include" "${PROJECT_SOURCE_DIR}/third_party/vst3")
endif()
add_library(clap-fixture MODULE tests/plugins/clap_fixture.c)
xtc_warnings(clap-fixture)
set_target_properties(clap-fixture PROPERTIES PREFIX "" SUFFIX ".clap")
target_include_directories(clap-fixture SYSTEM PRIVATE "${PROJECT_SOURCE_DIR}/third_party/clap/include" "${PROJECT_SOURCE_DIR}/third_party/vst3")
add_library(vst2-fixture MODULE tests/plugins/vst2_fixture.c)
xtc_warnings(vst2-fixture)
set_target_properties(vst2-fixture PROPERTIES PREFIX "" SUFFIX ".so")
target_include_directories(vst2-fixture SYSTEM PRIVATE "${PROJECT_SOURCE_DIR}/third_party/clap/include" "${PROJECT_SOURCE_DIR}/third_party/vst3")
add_library(vst3-fixture MODULE tests/plugins/vst3_fixture.c)
xtc_warnings(vst3-fixture)
set_target_properties(vst3-fixture PROPERTIES PREFIX "" SUFFIX ".so")
target_include_directories(vst3-fixture SYSTEM PRIVATE "${PROJECT_SOURCE_DIR}/third_party/clap/include" "${PROJECT_SOURCE_DIR}/third_party/vst3")
add_library(clap-synth-fixture MODULE tests/plugins/clap_synth_fixture.c)
xtc_warnings(clap-synth-fixture)
set_target_properties(clap-synth-fixture PROPERTIES PREFIX "" SUFFIX ".clap")
target_include_directories(clap-synth-fixture SYSTEM PRIVATE "${PROJECT_SOURCE_DIR}/third_party/clap/include" "${PROJECT_SOURCE_DIR}/third_party/vst3")
add_library(vst2-synth-fixture MODULE tests/plugins/vst2_synth_fixture.c)
xtc_warnings(vst2-synth-fixture)
set_target_properties(vst2-synth-fixture PROPERTIES PREFIX "" SUFFIX ".so")
target_include_directories(vst2-synth-fixture SYSTEM PRIVATE "${PROJECT_SOURCE_DIR}/third_party/clap/include" "${PROJECT_SOURCE_DIR}/third_party/vst3")
add_library(vst3-synth-fixture MODULE tests/plugins/vst3_synth_fixture.c)
xtc_warnings(vst3-synth-fixture)
set_target_properties(vst3-synth-fixture PROPERTIES PREFIX "" SUFFIX ".so")
target_include_directories(vst3-synth-fixture SYSTEM PRIVATE "${PROJECT_SOURCE_DIR}/third_party/clap/include" "${PROJECT_SOURCE_DIR}/third_party/vst3")
if(XTC_LADSPA_AVAILABLE)
xtc_test(plugins test_plugins $<TARGET_FILE:plugin-fixture> fixture)
endif()
xtc_test(modern-plugins test_modern_plugins $<TARGET_FILE:clap-fixture> $<TARGET_FILE:vst2-fixture> $<TARGET_FILE:vst3-fixture>)
xtc_test(instruments test_instruments $<TARGET_FILE:clap-synth-fixture> $<TARGET_FILE:vst2-synth-fixture> $<TARGET_FILE:vst3-synth-fixture>)
xtc_test(startup xtc-check)
xtc_test(native-example native-gain)
add_test(NAME project-inspection COMMAND ${Python3_EXECUTABLE} "${PROJECT_SOURCE_DIR}/tests/integration/test_project_inspection.py" $<TARGET_FILE:xtc-check>)
set_tests_properties(project-inspection PROPERTIES WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}")
add_test(NAME benchmark-tools COMMAND ${Python3_EXECUTABLE} "${PROJECT_SOURCE_DIR}/tests/tools/test_benchmark.py")
set_tests_properties(benchmark-tools PROPERTIES WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}")
if(JACK_FOUND AND XTC_WITH_JACK)
xtc_executable(test_backend tests/unit/test_backend.c)
xtc_test(backend test_backend)
foreach(kind jack jack_audio jack_midi)
xtc_executable(test_${kind} tests/integration/test_${kind}.c)
if(XTC_ENABLE_LIVE_TESTS)
string(REPLACE "_" "-" name "${kind}")
xtc_test(${name} test_${kind})
set_tests_properties(${name} PROPERTIES LABELS live RUN_SERIAL TRUE)
endif()
endforeach()
endif()

29
cmake/Variants.cmake Normal file
View file

@ -0,0 +1,29 @@
# Each compiler/flag variant has its own cache and products.
foreach(variant debug release sanitize)
set(extra)
if(CMAKE_CXX_COMPILER)
list(APPEND extra "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}")
endif()
if(variant STREQUAL debug)
set(kind Debug)
else()
set(kind Release)
list(APPEND extra -DXTC_REPRODUCIBLE=ON)
endif()
if(variant STREQUAL sanitize)
set(kind Debug)
list(APPEND extra -DXTC_SANITIZE=ON -DXTC_BUILD_GUI=OFF -DCMAKE_C_COMPILER=clang)
set(target test-sanitize)
else()
set(target ${variant})
endif()
add_custom_target(${target}
COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" -S "${PROJECT_SOURCE_DIR}" -B "${CMAKE_CURRENT_BINARY_DIR}/${variant}"
-DCMAKE_BUILD_TYPE=${kind} -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}
-DXTC_WITH_JACK=${XTC_WITH_JACK} -DXTC_WITH_LADSPA=${XTC_WITH_LADSPA}
-DXTC_BUILD_GUI=${XTC_BUILD_GUI} ${extra}
COMMAND ${CMAKE_COMMAND} --build "${CMAKE_CURRENT_BINARY_DIR}/${variant}" --parallel 3
COMMAND ${CMAKE_CTEST_COMMAND} --test-dir "${CMAKE_CURRENT_BINARY_DIR}/${variant}" --output-on-failure
USES_TERMINAL VERBATIM)
endforeach()
add_custom_target(check-development DEPENDS check analyze test-sanitize)

View file

@ -109,12 +109,9 @@ implemented.
## Verification ## Verification
make test includes deterministic WAV/graph samples, resampling/overlap/trim, CTest includes deterministic WAV/graph samples, resampling/overlap/trim,
malformed inputs, missing/relinked assets, SPSC concurrency/overflow/cleanup, malformed inputs, missing/relinked assets, SPSC concurrency/overflow/cleanup,
and record -> save/reopen -> exact playback through the backend callback. make and record -> save/reopen -> exact playback through the backend callback. The CTest gui test covers the workflow and rollback with a fake backend. The CTest gui-jack test records/reopens a silent take using real JACK callbacks. The CTest jack-audio test routes only two software clients: known source -> recorded WAV
test-gui covers the workflow and rollback with a fake backend. make
test-gui-jack records/reopens a silent take using real JACK callbacks. make
test-jack-audio routes only two software clients: known source -> recorded WAV
-> saved/reopened project -> graph playback -> capture assertion. Physical -> saved/reopened project -> graph playback -> capture assertion. Physical
input/output, device latency, slow-disk endurance and server loss during a real input/output, device latency, slow-disk endurance and server loss during a real
recording remain unverified. recording remain unverified.
@ -139,7 +136,7 @@ never modified. A trailing partial frame is ignored. This handles xtc's
canonical 44-byte mono float32 header only, not arbitrary damaged WAVs. Recovery canonical 44-byte mono float32 header only, not arbitrary damaged WAVs. Recovery
is explicit and control-thread only; no orphan scan runs during playback. is explicit and control-thread only; no orphan scan runs during playback.
`make test-recording` simulates 30 minutes at 8 kHz with repeated ring wrapping `cmake --build build --target test-recording` simulates 30 minutes at 8 kHz with repeated ring wrapping
and draining, destination races/symlinks, a child-local disk-size limit, prefix and draining, destination races/symlinks, a child-local disk-size limit, prefix
recovery and incomplete/missing asset reopening. `test-recovery` retains process recovery and incomplete/missing asset reopening. `test-recovery` retains process
kill coverage; `test_audio` covers saturation and concurrent drain. This is kill coverage; `test_audio` covers saturation and concurrent drain. This is
@ -224,7 +221,7 @@ claimed. Source files should remain unchanged during a session; reopen after
external replacement. Cache allocation failure omits previews without changing external replacement. Cache allocation failure omits previews without changing
music metadata. music metadata.
`make test-audio-editing` verifies exact split/gain/fade playback, odd blocks, `cmake --build build --target test-audio-editing` verifies exact split/gain/fade playback, odd blocks,
seeks, shortened media tails, one-frame files, source/project limits, capacity seeks, shortened media tails, one-frame files, source/project limits, capacity
rollback, duplication/fade inheritance, persistence/invalid metadata, undo, peak rollback, duplication/fade inheritance, persistence/invalid metadata, undo, peak
bins after source removal and byte-identical source preservation. GUI tests bins after source removal and byte-identical source preservation. GUI tests

View file

@ -150,13 +150,13 @@ maps and physical recording-offset calibration remain unsupported.
## Verification and Listening ## Verification and Listening
``` ```
make all test gui test-reliability test-workflow test-gui test-gui-jack cmake -S . -B build -DXTC_ENABLE_LIVE_TESTS=ON && cmake --build build --parallel && cmake --build build --target check gui test-reliability test-workflow test-gui test-gui-jack
make test-jack test-jack-audio test-jack-midi test-performance cmake -S . -B build -DXTC_ENABLE_LIVE_TESTS=ON && cmake --build build --target test-jack test-jack-audio test-jack-midi test-performance
make test-sanitize analyze cmake --build build --target test-sanitize analyze
make quality-preview cmake --build build --target quality-preview
``` ```
`test_quality` is part of `make test`. It covers a sample-rate/tempo matrix up `test_quality` is part of `cmake --build build --target check`. It covers a sample-rate/tempo matrix up
to 768 kHz and the maximum project tick, conversion errors, old/new tempo to 768 kHz and the maximum project tick, conversion errors, old/new tempo
snapshots for both audio and MIDI, exact offset/on/off order, sample boundary snapshots for both audio and MIDI, exact offset/on/off order, sample boundary
behavior, raw versus faded endpoints, media ends shorter than rounded clip ends, behavior, raw versus faded endpoints, media ends shorter than rounded clip ends,
@ -174,8 +174,8 @@ the existing fontconfig-only suppression; core leak checks are unsuppressed.
Physical-device listening and human listening acceptance are **not yet Physical-device listening and human listening acceptance are **not yet
verified**. verified**.
`make quality-preview` writes ignored build artifacts `quality-raw.wav` and `cmake --build build --target quality-preview` writes ignored build artifacts `quality-raw.wav` and
`quality-smooth.wav` in BUILD_DIR using `examples/quality_preview.c`. Compare at `quality-smooth.wav` in the selected binary directory using `examples/quality_preview.c`. Compare at
moderate playback level: first listen to the three-note phrase endings, then the moderate playback level: first listen to the three-note phrase endings, then the
constant clip at two seconds, then a mid-period tone stop near three seconds. constant clip at two seconds, then a mid-period tone stop near three seconds.
Check that fades reduce edge clicks, do not add noise and do not unexpectedly Check that fades reduce edge clicks, do not add noise and do not unexpectedly

View file

@ -1,325 +1,188 @@
# Build Baseline # Building xtc with CMake
The existing layout is retained: public C headers in `include/xtc`, C The root `CMakeLists.txt` is the authoritative build. C99 engine/model/DSP and
implementation modules under `src`, optional C++ GUI under `src/gui`, and tests public C APIs remain in `include/xtc` and `src`; C++17 is used for the optional
under `tests`. `src/main.c` is a minimal headless startup/shutdown check; it FLTK interface and SDK examples. The small root Makefile only forwards common
does not initialize audio devices or open a GUI. commands to CMake/CTest; it contains no source lists or compilation rules.
Direct CMake builds need no Make wrapper. CMake does not download SDKs or
install system dependencies.
--- ## Configure, build and test
## Commands Requirements: CMake 3.25+, a C99 compiler, Python 3.12+ for build/test/release
tools, a POSIX toolchain with threads, math and dynamic loading, and a CMake
backend such as Ninja or Unix Makefiles. C++17 is required when GUI/SDK targets
are enabled. `pkg-config` discovers optional JACK; FLTK discovery tries
`pkg-config` then `fltk-config`. LADSPA uses its installed development header.
Missing optional development files do not prevent the core from building.
From the source root:
```sh ```sh
make clean all test cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug
cmake --build build --parallel
ctest --test-dir build --output-on-failure
./build/xtc-check ./build/xtc-check
make debug ./build/xtc # when FLTK was found
make gui
``` ```
`all` builds the static C library and startup check. `test` runs the existing All binaries and the static `libxtc.a` are in the chosen binary directory.
core/offline tests, concurrent plan-handoff stress, JACK fault injection (when The default build includes offline test binaries and plugin fixtures. CTest
JACK headers are available), and the startup check. `debug` builds and tests runs offline, fault-injection, plugin, recovery, capacity/endurance, and available
separately in `build/debug`. `gui` builds the FLTK shell with backend controls GUI tests; live JACK tests require explicit opt-in. GUI tests use Xvfb and
and status. The shell starts with a silent graph and never connects physical startup checks also require xdotool. No physical ports are connected by tests.
ports. Generated files remain under `build` and header changes trigger rebuilds. CTest runs source-relative fixtures with an explicit working directory.
Compiler, flags, `BUILD_DIR`, `PKG_CONFIG`, `FLTK_CONFIG`, and installation Assertions stay enabled in optimized test builds.
`PREFIX` can be overridden through Make variables. C uses C99; GUI uses C++17.
--- Use a new build directory when changing compiler, generator or instrumentation.
For Ninja, add `-G Ninja` to the configure command. CMake also writes
`compile_commands.json` for editors and analysis tools. In CLion, open the root
project and select a CMake profile/preset; do not import a Makefile project.
## Inspected Linux Toolchain (2026-10-26) Optional presets separate configurations:
| Component | Available version | Role | ```sh
cmake --preset debug
cmake --build --preset debug --parallel
ctest --preset debug
```
`release`, `minimal` and `sanitize` presets follow the same three commands.
Release enables optimized builds and source/binary prefix maps. Minimal disables
FLTK, JACK and LADSPA. Sanitize selects Clang ASan/UBSan for offline C tests;
it does not enable GUI or physical-device tests. Existing GUI sanitizer caveats
and fontconfig leak suppressions are described in
[the integration notes](../tests/integration/README.md).
## Quick Make shortcuts
With GNU Make installed, `make` (or `make build`) configures and builds;
`make test`/`make check` also runs CTest. `make gui` builds the FLTK app,
`make run` builds and launches it, and `make clean` cleans products while keeping
the cache. `make configure` only configures, and `make help` lists shortcuts.
Defaults are `BUILD_DIR=build`, `BUILD_TYPE=Debug` and `JOBS=4`. For example:
```sh
make BUILD_DIR=build/release BUILD_TYPE=Release JOBS=8
make BUILD_DIR=build/minimal CMAKE_ARGS='-G Ninja -DXTC_BUILD_GUI=OFF -DXTC_WITH_JACK=OFF'
make test CTEST_ARGS='-R ^core$'
```
Use a fresh binary directory when changing generator/compiler. Extra configure
flags go in `CMAKE_ARGS`; extra CTest flags go in `CTEST_ARGS`. These shortcuts
use the same CMake targets and cache as the direct commands above. Keep source
lists, dependencies, SDK and packaging rules in CMake.
## Configuration
| Cache option | Default | Meaning |
| --- | --- | --- | | --- | --- | --- |
| GCC / G++ | 16.2.1 | C core and optional C++ GUI | | `BUILD_TESTING` | `ON` | Build/register the test suite |
| GNU Make | 4.4.1 | Build and test driver | | `XTC_BUILD_GUI` | `ON` | Discover FLTK and build the application when available |
| pkg-config | 3.0.10 | Development package discovery | | `XTC_WITH_JACK` | `ON` | Discover/build the JACK/PipeWire backend |
| FLTK | 1.4.5 | Optional GUI; discovered through `fltk-config` | | `XTC_WITH_LADSPA` | `ON` | Enable LADSPA when the system header exists |
| JACK | 3.1609.0 (pkg-config) | Optional JACK backend (PipeWire compatibility library) | | `XTC_ENABLE_LIVE_TESTS` | `OFF` | Register software JACK loopback and GUI/JACK workflows |
| ALSA | 1.2.16.1 (pkg-config) | Future MIDI candidate; not linked yet | | `XTC_SANITIZE` | `OFF` | Address/undefined sanitizers, frame pointers |
| Clang | 23.1.3 | Alternative compiler / diagnostics | | `XTC_REPRODUCIBLE` | `OFF` | Remove source/binary directory paths and debug information |
| `XTC_BUILD_JUCE` | `OFF` | Build externally supplied JUCE integration |
| `XTC_BUILD_STEINBERG` | `OFF` | Build externally supplied official VST3 SDK examples |
Valgrind is also available. The core tests use a small in-tree C test harness Use `-DNAME=VALUE` during configuration, not Make variables. Standard CMake
and require no external test framework. FLTK has no `fltk.pc` on this host; the settings include `CMAKE_C_COMPILER`, `CMAKE_CXX_COMPILER`, `CMAKE_BUILD_TYPE`,
Makefile tries pkg-config first, then `fltk-config`. `CMAKE_C_FLAGS`, `CMAKE_CXX_FLAGS` and `CMAKE_EXE_LINKER_FLAGS`. CMake propagates
includes, definitions and link dependencies through targets. Core users can link
`xtc::core` inside this build; installed consumers use `pkg-config xtc`.
--- A fully offline minimal configuration:
## Dependencies and Licensing
The offline core needs the C runtime and math library. Optional JACK support is
selected with pkg-config; missing JACK does not prevent core/tests from
building. Realtime publication uses GCC/Clang lock-free atomic builtins in C99.
Tests also use POSIX threads. Backend consumers must link JACK explicitly;
backend objects are pulled from the static library only when used. The
repository is MIT licensed. The optional GUI uses maintained FLTK 1.4 (LGPL with
FLTK's static-linking exception); its platform link flags come from the
installed package. Distribution still requires review of all linked dependencies
and notices. The installed PipeWire JACK library is MIT; traditional JACK client
libraries use LGPL licensing. See decision 0004 for the dependency choice and
caveats. ALSA remains a future MIDI candidate. No packages or system settings
are changed by these build commands.
---
## Backend Verification
```sh ```sh
make all test gui debug cmake -S . -B build/minimal -G Ninja -DCMAKE_BUILD_TYPE=Release \
make test-jack -DXTC_BUILD_GUI=OFF -DXTC_WITH_JACK=OFF -DXTC_WITH_LADSPA=OFF
# Extended live run after building the integration binary: cmake --build build/minimal --parallel
./build/test_jack 30 ctest --test-dir build/minimal --output-on-failure
# Verify optional build without JACK metadata, in a separate directory:
make BUILD_DIR=build/nojack PKG_CONFIG=false all test
# Address/undefined sanitizers (non-realtime tests only):
make BUILD_DIR=build/sanitize CC=clang CFLAGS='-O1 -g -std=c99 -Wall -Wextra -Wpedantic -Wconversion -Wshadow -Iinclude -fsanitize=address,undefined -fno-omit-frame-pointer' LDFLAGS='-fsanitize=address,undefined' all test
# Thread sanitizer for publication/reclamation:
make BUILD_DIR=build/tsan CC=cc CFLAGS='-O1 -g -std=c99 -Wall -Wextra -Wpedantic -Wconversion -Wshadow -Iinclude -fsanitize=thread' LDFLAGS='-fsanitize=thread' build/tsan/test_realtime
./build/tsan/test_realtime
``` ```
Normal, sanitizer and no-JACK tests pass. ThreadSanitizer reports no race in This path needs no C++ compiler, FLTK, JACK or LADSPA development files. CLAP and
2,000 concurrent submissions. The 30-second live loopback test passed at 48 kHz VST3 C headers stay pinned in `third_party`; VST2 uses original declarations.
with zero xruns and ten restart cycles; see tests/integration/README.md. Live No external plugin or SDK is included in normal packages. See [LICENSES.md](LICENSES.md).
server termination/format changes were not performed on the shared server;
fault-injection tests cover those notification paths. No packages installed or
system/device configuration changed. Sanitizers are not enabled in the live
callback qualification run.
--- ## Focused tests and tools
## GUI Shell Verification Build before invoking CTest, or use `check`, which builds the test dependencies:
```sh ```sh
make clean all test gui debug test-gui test-gui-jack cmake --build build --target check --parallel
ctest --test-dir build --output-on-failure -R '^(core|realtime|modern-plugins)$'
ctest --test-dir build --output-on-failure -L gui
cmake --build build --target test-reliability test-recording test-scale test-soak
cmake --build build --target analyze
cmake --build build --target quality-preview builtin-preview
``` ```
`test-gui` needs Xvfb and `xvfb-run`, uses a fake C backend and requires no JACK Each registered root test also has a `test-NAME` build target (for example
server. `test-gui-jack` needs a running server; it creates no physical routes. `test-core`, `test-gui`, `test-track-fx`). `test-builtins` aliases `test-builtin`.
Both passed, including repeated active-client close and reconnect. The GUI also `gui` builds the optional main application. Preview targets write WAVs below the
builds without JACK metadata and reports unsupported backend support at startup. binary directory without playback. Screenshot commands are in
A live keyboard/visual smoke run passed under Xvfb. Clang ASan/UBSan GUI tests [the gallery](../screenshots/README.md). These commands assume configuration
passed with a suppression limited to the installed fontconfig library's has already completed; feature-specific docs use the same convention.
exit-time font allocations; see tests/integration/README.md for the distinction
and command. No compiler warnings remain with GCC or Clang. Core/backend tests
pass with unsuppressed ASan/UBSan. No packages or system configuration changed.
--- To qualify live software routing against an **existing** JACK-compatible server:
## Graph Editor Verification
```sh ```sh
make clean all test gui debug test-gui test-gui-jack cmake -S . -B build -DXTC_ENABLE_LIVE_TESTS=ON
make BUILD_DIR=build/nojack PKG_CONFIG=false all test gui test-gui cmake --build build --parallel
ctest --test-dir build --output-on-failure -L live
./build/test_jack 30 # optional longer software loopback run
``` ```
The normal suite adds test_graph_edits for stable IDs, dense snapshots, Live tests are serialized. They neither start/reconfigure the server nor
transactional rejection/deletion/disconnect and known rendered samples before establish physical-device acceptance. See [AUDIO_ENGINE.md](AUDIO_ENGINE.md).
and after edits. Core/backend/realtime tests pass under unsuppressed Clang
ASan/UBSan; ThreadSanitizer passes the concurrent handoff test with revision-to-
gain checks. GUI ASan/UBSan tests pass with the existing fontconfig-only leak
suppression. Live GUI tests build a connected graph, adopt accepted edits during
playback, reject cycles, reconnect and close active clients. Mouse/visual smoke
checks cover node creation, wiring, cycle rejection, dragging and deletion. No
physical routes, new dependencies or system changes are introduced.
--- Developer targets include `benchmark`, `benchmark-compare`, `benchmark-project`,
`benchmark-project-compare`, `test-stress`, `test-stress-plugins`,
`test-plugins-real` and `check-development`. The latter combines the normal
suite, Clang static analysis and a separate sanitizer build. `debug`, `release`
and `test-sanitize` convenience targets configure/build/test child directories;
prefer presets when selecting the directory explicitly. Benchmark/stress cache
variables are `XTC_BENCHMARK_BLOCKS`, `XTC_BENCHMARK_MAX_REGRESSION`,
`XTC_BENCHMARK_BASELINE`, `XTC_PROJECT_BENCHMARK_BASELINE` and `XTC_STRESS_ROUNDS`.
Run benchmarks without concurrent testing/instrumentation for meaningful timing.
## Project Verification For ThreadSanitizer, use a separate configuration with
`-DCMAKE_C_FLAGS='-g -O1 -fsanitize=thread'` and
`-DCMAKE_EXE_LINKER_FLAGS=-fsanitize=thread`, build `test_realtime`/`test_audio`,
and run those binaries. Tool/runtime support depends on the host.
## SDKs, installation and release
See [JUCE.md](JUCE.md) and [STEINBERG.md](STEINBERG.md) for optional SDK
configuration, licenses and qualification limits. Both now build within the root
CMake project; the JUCE bridge links the same PIC C core target.
```sh ```sh
make clean all test gui debug test-gui test-gui-jack cmake -S . -B build/release -DCMAKE_BUILD_TYPE=Release -DXTC_REPRODUCIBLE=ON \
make BUILD_DIR=build/nojack PKG_CONFIG=false all test gui test-gui -DCMAKE_INSTALL_PREFIX=/usr
cmake --build build/release --parallel
ctest --test-dir build/release --output-on-failure
DESTDIR="$PWD/build/stage" cmake --install build/release --prefix /usr
DESTDIR="$PWD/build/stage" cmake --build build/release --target uninstall
``` ```
The suite adds test_project: track/clip limits, stable IDs, trims, integer For uninstall, configure with `-DCMAKE_INSTALL_PREFIX=/usr` first when that is
transport conversion, exact file roundtrip with graph rendering, exhaustive the desired permanent prefix, or invoke the generated `xtc-install.cmake` with
fixture truncations, byte mutations, failed saves and temporary cleanup. GUI `-DCMAKE_INSTALL_PREFIX=/usr -DXTC_UNINSTALL=ON -P` explicitly. The `--prefix`
tests cover region gestures, zoom, save/load and backend rollback. Live JACK install override does not persist in CMake's cache. Prefer setting the prefix at
tests verify playhead advance, pause and resume without creating routes. The configure time for a matching install/uninstall pair. Standard `DESTDIR` staging
updated core and GUI tests pass Clang ASan/UBSan (the GUI uses the existing never enters installed metadata. The audited Python installer retains exact
fontconfig-only LSan suppression). An Xvfb/xdotool visual check created a track owned-file inventory, atomic file replacement and symlink protection.
and region, moved it one bar and zoomed the canvas. No dependencies were added.
Audio assets and recording are supported. Silent arrangement regions, fixed `test-install` and `test-install-layout` stage into temporary directories and
tempo/4/4, no undo, no persisted GUI layout, and unverified physical-device compile installed C/C++ consumers. `dist` exports indexed files; `test-release`
playback/endurance remain explicit limits. rebuilds two exports; `package-binary` (also aliased as `package`) and `test-package` stage and verify a local
Linux archive. See [RELEASING.md](RELEASING.md) for layout/manifest details.
--- Clean products with `cmake --build build --target clean`; to discard the cache,
remove only the selected build directory. Never configure in the source tree.
## Audio Verification Historical validation entries in ROADMAP describe the build used at that time;
Phase 108 records the CMake migration and its verified results.
```sh
make all test gui debug test-gui test-gui-jack test-jack-audio
make BUILD_DIR=build/nojack PKG_CONFIG=false all test gui test-gui
make BUILD_DIR=build/tsan CC=cc CFLAGS='-O1 -g -std=c99 -Wall -Wextra -Wpedantic -Wconversion -Wshadow -Iinclude -fsanitize=thread' LDFLAGS='-fsanitize=thread' build/tsan/test_audio build/tsan/test_realtime
./build/tsan/test_audio
./build/tsan/test_realtime
```
No new dependency is required for WAV import, recording or playback. New audio
sources remain C99; only GUI controls use C++/FLTK. test_audio covers decoder
samples, truncations/nonfinite/unsupported input, resampling, trim/overlap,
missing assets and concurrent recording. test_backend records known callback
input, saves/reopens and checks exact graph output. Project tests cover v1
migration and 1024 maximum-length asset paths in a valid v2 file.
The live test-jack-audio target connects only its software clients and checks
record -> WAV -> saved/reopened project -> graph playback. GUI/JACK tests also
record, finalize, save and reopen a take without hardware routing. Clang
ASan/UBSan passes the core and GUI tests (GUI retains the existing
fontconfig-only LSan suppression). ThreadSanitizer passes the concurrent
recorder and existing realtime handoff tests. Hardware capture/playback, disk
endurance and real server loss during recording remain unverified. See
AUDIO_FILES.md.
---
## MIDI Verification
```sh
make all test gui debug test-gui test-gui-jack test-jack-audio test-jack-midi
make BUILD_DIR=build/nojack PKG_CONFIG=false all test gui test-gui
```
`test_midi` adds exact sample scheduling, trim/seek/panic, block independence,
voice capacities, stable notes and v3 malformed-file/roundtrip/v2 migration
coverage. Backend fault injection checks event offsets, dropped/malformed input,
disconnection, xruns, missing output buffers and stop/restart cleanup. GUI tests
draw/move/resize notes, save/reopen and render them. The live MIDI target
connects only software clients and checks on/off, panic, disconnect, and
saved-note playback/seek. It needs an existing JACK-compatible server. No
hardware routes or system changes are made. MIDI recording/SMF and physical
devices are deferred.
A fresh `build/verify` build passed all targets above with no compiler warnings.
The no-JACK core/GUI suite and Clang ASan/UBSan core/GUI suite also passed; GUI
leak checking uses only the existing fontconfig suppression documented in the
integration README. Physical MIDI and audio devices remain unverified.
---
## Mixer Verification
```sh
make all test gui debug test-gui test-gui-jack test-jack-audio test-jack-midi
make BUILD_DIR=build/nojack PKG_CONFIG=false all test gui test-gui
```
`test_mixer` covers stereo gain/pan, mute/solo, sample automation, block
independence, WAV and graph routing, v4 persistence failures and bounded
history. GUI tests add automation mouse edits, mixer keys, history
rollback/branching and save/reopen. The existing live MIDI test now uses two
software capture ports and checks hard-right saved-note playback and output
meter snapshots; mono audio record/play acceptance remains covered. A native
Xvfb mouse smoke check confirms sliders and automation drawing. See MIXER.md for
capacities and deferred features.
The fresh verification build, no-JACK core/GUI suite and Clang ASan/UBSan
core/GUI suite passed without compiler warnings. GUI leak checking retains only
the existing fontconfig suppression. ThreadSanitizer also passed the recorder
concurrency and realtime publication tests. Physical devices and long-running
stereo performance remain unverified.
---
## Release Preparation
```sh
make all test gui debug test-gui test-gui-jack test-jack test-jack-audio test-jack-midi
make test-install test-recovery test-performance
make test-release
```
Core test now includes native ABI/lifecycle/retirement and the native-gain
example. Optional release tests use Python 3.12+ standard library, Git for
indexed source exports, GNU/LLVM deterministic ar and temporary directories. No
live prefix is installed. test-install checks core/GUI files, a prefix
containing spaces, headless CLI, pkg-config C/C++ consumers, exact/idempotent
uninstall and preservation of unrelated files. test-release compares two clean
exported optimized builds and repacked archives byte-for-byte. test-recovery
kills private child writers and forces a child-local write limit, checking
complete finalized destinations and explicit orphan temporaries. Performance
timings are reported, not used as a universal realtime guarantee. See
RELEASING.md, RECOVERY.md and PERFORMANCE.md.
Verification passed a fresh warning-enabled core/debug/GUI build, all software
JACK targets, staged install/uninstall, recovery and performance tests. No-JACK
core/GUI/install checks and Clang ASan/UBSan core/GUI checks also passed (the
GUI retains only its existing fontconfig leak suppression). TSan recorder and
realtime-handoff checks passed. Two independent optimized source exports and
their repacked source archives were byte-identical on this toolchain.
---
## Reliability Audit
Run `make test-reliability` for the core suites, allocation-failure sweep,
callback allocation/free guard and native reclamation checks. Run
`make test-sanitize` for Clang ASan/UBSan/leak checks and `make analyze` for
Clang static analysis of all core sources. These need installed development
tools; the allocation guard uses a GNU-compatible linker. See
[RELIABILITY.md](RELIABILITY.md) for ownership, audit findings, full
verification commands and remaining limits.
---
## Workflow Acceptance
`make test-workflow` requires FLTK/JACK development files, Xvfb and an existing
JACK-compatible server. It does not start/configure the server or connect
physical devices. See [WORKFLOW.md](WORKFLOW.md) for the full GUI composition
acceptance sequence and transport semantics.
---
## Audio Quality
`make test-quality` runs exact timing, source-boundary, gain-ramp, event-order
and numeric/layout tests (also included in `make test`). `make quality-preview`
builds the C example and writes raw/smoothed listening WAVs into BUILD_DIR
without playing them or configuring devices. See
[AUDIO_QUALITY.md](AUDIO_QUALITY.md).
Parameter-processor and plugin checks:
`make test-processor test-plugins test-gui-plugins`. The latter two need the
system LADSPA development header; real-plugin acceptance is separate
(`make test-plugins-real`, paths listed in PLUGIN_HOSTING.md). With no
`ladspa.h`, the ordinary core/GUI still builds and plugin operations return
UNSUPPORTED; existing plugin metadata can still be loaded/saved. To exercise
that configuration explicitly, use a fresh BUILD_DIR with
`CPPFLAGS=-DXTC_DISABLE_LADSPA JACK_AVAILABLE=`. No dependency is downloaded.
Link static consumers with `-lm -ldl` plus the backend dependencies when used.
See LICENSES.md for header/plugin licensing and decision 0009 for rationale.
Recording stress/recovery is available with `make test-recording`.
Developer qualification: `make check-development` runs the core build,
regressions, scale/virtual endurance, benchmark-gate checks, Clang analysis and
ASan/UBSan. Run `make benchmark benchmark-project` separately without contention
or sanitizers for timing reports. Optional `make test-stress-plugins` requires
installed LADSPA headers; `make test-stress` supports minimal builds too.
---
## Installation Layouts
`make test-install test-install-layout` verifies default and custom layouts in
throwaway staging directories, C/C++ installed consumers, examples and exact
removal. Override `PREFIX`, `exec_prefix`, `bindir`, `libdir`, `includedir`,
`datarootdir`, `datadir`, `docdir`, `pkgconfigdir`, `licensedir`, `examplesdir`
or `desktopdir` as needed. `DESTDIR` adds staging separately; pass the same
layout to uninstall. See RELEASING.md for commands and RELEASE_NOTES.md for
upgrades.
Modern plugin adapters build in C99 using the pinned header-only CLAP and VST3 C
APIs under third_party; there is no network build or SDK runtime. VST2
compatibility uses original ABI declarations. `make test-modern-plugins` runs
original shared-library fixtures without a server;
`make test-gui-modern-plugins` adds GUI/browser/undo/reopen tests under Xvfb.
The fixtures also run in `make test` and therefore in sanitizer/release builds.
Notices are installed with LICENSE.
The GUI uses a header-only FLTK theme and existing drawing primitives; no new
GUI dependency or generated asset is required. `make test-gui-instruments` also
exercises sidebar track creation, rollback, undo/reopen and minimum/large
layouts. See WORKSPACE.md for the default size and manual visual verification.

View file

@ -38,7 +38,7 @@ fixed presets. Readers still accept formats 1–12. Older xtc builds reject form
allocation, file access, library loading, locking or GUI calls. allocation, file access, library loading, locking or GUI calls.
Validation: Validation:
`make BUILD_DIR=build/builtins all gui test test-gui-instruments test-gui-modern-plugins test-gui-plugins analyze` `cmake --build build/builtins --parallel && cmake --build build/builtins --target gui check test-gui-instruments test-gui-modern-plugins test-gui-plugins analyze`
(join onto one line). `test-builtins` checks impulse tails, dry mix, feedback (join onto one line). `test-builtins` checks impulse tails, dry mix, feedback
bounds, block partition consistency, note release/panic and instrument/effect bounds, block partition consistency, note release/panic and instrument/effect
project round trips. project round trips.
@ -47,7 +47,7 @@ project round trips.
## Acceptance Checks ## Acceptance Checks
`make BUILD_DIR=build/builtin-acceptance all gui test test-reliability test-gui builtin-preview` `cmake --build build/builtin-acceptance --parallel && cmake --build build/builtin-acceptance --target gui check test-reliability test-gui builtin-preview`
checks all four device identities and GUI paths without external plugin checks all four device identities and GUI paths without external plugin
binaries. The reliability runner fails each internal preparation allocation in binaries. The reliability runner fails each internal preparation allocation in
turn and forbids allocation/free, direct file I/O and blocking locks during turn and forbids allocation/free, direct file I/O and blocking locks during
@ -57,7 +57,7 @@ cover voice exhaustion, sustain, repeated notes, partitioning and transport
reset/seek/panic. The existing portability suite verifies older project reset/seek/panic. The existing portability suite verifies older project
migrations. migrations.
For repeatable manual listening, run `make builtin-preview` and audition For repeatable manual listening, run `cmake --build build --target builtin-preview` and audition
`build/builtin-{0,1,2,3}.wav` at a conservative level: 0 is Soft Keys, 1 Sub `build/builtin-{0,1,2,3}.wav` at a conservative level: 0 is Soft Keys, 1 Sub
Bass, 2 Delay and 3 Reverb. Each six-second file includes held tones or two Bass, 2 Delay and 3 Reverb. Each six-second file includes held tones or two
impulses followed by silence for tails. Check onset/release, unwanted impulses followed by silence for tails. Check onset/release, unwanted
@ -164,7 +164,7 @@ Tests compare whole/split blocks, tempo-synced impulse placement, left-to-right
energy, bounded extreme feedback/diffusion and graph/direct stereo output energy, bounded extreme feedback/diffusion and graph/direct stereo output
(including the engine's subnormal-to-zero policy). GUI tests save/reopen both (including the engine's subnormal-to-zero policy). GUI tests save/reopen both
identities; callback guards and ASan/UBSan cover processing. identities; callback guards and ASan/UBSan cover processing.
`make builtin-preview` also writes files 5/6 for repeatable listening; human `cmake --build build --target builtin-preview` also writes files 5/6 for repeatable listening; human
listening remains pending. listening remains pending.
--- ---

View file

@ -46,7 +46,7 @@ and the project tempo. Repeated render/save/reopen, odd block partition, exact
endpoints, seek/plan replacement, sustain/retrigger and controller ramp tests: endpoints, seek/plan replacement, sustain/retrigger and controller ramp tests:
```sh ```sh
make test test-control test-plugins test-gui-plugins test-plugins-real cmake --build build --target check test-control test-plugins test-gui-plugins test-plugins-real
``` ```
Physical controller devices and listening remain unqualified; no tempo map, Physical controller devices and listening remain unqualified; no tempo map,

View file

@ -106,7 +106,7 @@ edits do not request extra approval. No operation here deletes source media.
## Evidence and Manual Procedure ## Evidence and Manual Procedure
`make all test gui test-gui-plugins test-reliability analyze test-workflow test-gui-jack` `cmake -S . -B build -DXTC_ENABLE_LIVE_TESTS=ON && cmake --build build --parallel && cmake --build build --target check gui test-gui-plugins test-reliability analyze test-workflow test-gui-jack`
covers core commands, grouped gestures/batches, backend rollback, selection covers core commands, grouped gestures/batches, backend rollback, selection
retention, take undo/redo and real software JACK state restoration. The GUI retention, take undo/redo and real software JACK state restoration. The GUI
fixture injects history-reservation failure before graph/map/import publication fixture injects history-reservation failure before graph/map/import publication

View file

@ -61,10 +61,10 @@ process exit. Normal close/reconnect paths permit retry and release ownership
only after successful close. The entry point catches C++ exceptions before only after successful close. The entry point catches C++ exceptions before
process exit. Backend operations return C error codes throughout. process exit. Backend operations return C error codes throughout.
Tests: `make test-gui` runs real widgets/callbacks/timers against a fake C Tests: `cmake --build build --target test-gui` runs real widgets/callbacks/timers against a fake C
backend under Xvfb, including control errors, startup without JACK, partially backend under Xvfb, including control errors, startup without JACK, partially
initialized handles, server/format notifications, view switching, retrying initialized handles, server/format notifications, view switching, retrying
close, and timer removal. `make test-gui-jack` additionally requires a running close, and timer removal. `cmake -S . -B build -DXTC_ENABLE_LIVE_TESTS=ON && cmake --build build --target test-gui-jack` additionally requires a running
JACK-compatible server, tests repeated start/stop, reconnect and active-client JACK-compatible server, tests repeated start/stop, reconnect and active-client
close, and creates no routes. Physical playback is outside this shell. See close, and creates no routes. Physical playback is outside this shell. See
BUILD.md and the integration test README for sanitizer limitations and keyboard BUILD.md and the integration test README for sanitizer limitations and keyboard
@ -214,7 +214,7 @@ finalizing a take (and is refused during recording). Home stops and returns to
zero. Failed candidate installation now refreshes the actual backend state even zero. Failed candidate installation now refreshes the actual backend state even
when the previous document is retained. when the previous document is retained.
`make test-workflow` uses a live JACK-compatible backend and Xvfb, with exact `cmake -S . -B build -DXTC_ENABLE_LIVE_TESTS=ON && cmake --build build --target test-workflow` uses a live JACK-compatible backend and Xvfb, with exact
stereo render comparisons across destruction/recreation of the GUI and live stereo render comparisons across destruction/recreation of the GUI and live
playhead/meter/transport checks. `test-gui` covers stop/seek failure and playhead/meter/transport checks. `test-gui` covers stop/seek failure and
recording guards. No project files need manual editing for this workflow. recording guards. No project files need manual editing for this workflow.

View file

@ -172,7 +172,7 @@ Adapt this to an existing repository instead of replacing it blindly.
xtc/ xtc/
├── README.md ├── README.md
├── LICENSE ├── LICENSE
├── Makefile ├── CMakeLists.txt
├── docs/ ├── docs/
│ ├── ARCHITECTURE.md │ ├── ARCHITECTURE.md
│ ├── AUDIO_ENGINE.md │ ├── AUDIO_ENGINE.md
@ -624,8 +624,8 @@ make an edit partially visible if it fails validation.
### Build system ### Build system
Use a straightforward Makefile unless the existing repository has a sound reason Use the root CMake project and CTest. Keep SDK integrations optional and
to use another build system. all generated output in separate build directories.
The build must: The build must:
@ -736,7 +736,7 @@ feature is exciting.
### Repository and design baseline ### Repository and design baseline
- Inspect the repository and available toolchain. - Inspect the repository and available toolchain.
- Create or update architecture documentation and the Makefile. - Create or update architecture documentation and the CMake project.
- Verify C compiler, C++ compiler, FLTK, audio development headers, and test - Verify C compiler, C++ compiler, FLTK, audio development headers, and test
tools. tools.
- Record dependency and licensing decisions. - Record dependency and licensing decisions.
@ -851,7 +851,7 @@ Start with the **repository and design baseline** only.
development packages, `pkg-config`, and test tools. development packages, `pkg-config`, and test tools.
3. Do not install packages or modify system configuration without permission. 3. Do not install packages or modify system configuration without permission.
4. Propose the smallest initial source tree compatible with this brief. 4. Propose the smallest initial source tree compatible with this brief.
5. Add or update the Makefile and documentation only as needed. 5. Add or update the CMake project and documentation only as needed.
6. Create a minimal program that builds and exits cleanly; do not yet implement 6. Create a minimal program that builds and exits cleanly; do not yet implement
a full audio engine or GUI. a full audio engine or GUI.
7. Build it and run its tests. 7. Build it and run its tests.

View file

@ -17,8 +17,9 @@ normal builds or automatically installed. The integration uses:
Synthesis is the real xtc **Soft Keys** implementation, called through Synthesis is the real xtc **Soft Keys** implementation, called through
`xtc_plugin_*`. C++ remains confined to the optional integration. The SDK build `xtc_plugin_*`. C++ remains confined to the optional integration. The SDK build
uses a separate PIC build of the C archive with JACK/FLTK disabled; the installed links the root CMake PIC C archive through target dependencies; standalone SDK
application and existing build directories are not modified. configuration adds the same CMake core with JACK/FLTK disabled. SDK targets do
not install plugins or modify other binary directories.
## Screenshots ## Screenshots
@ -43,10 +44,11 @@ before distributing those binaries. No SDK source or JUCE-linked binary is added
to the normal xtc install/package targets. to the normal xtc install/package targets.
JUCE requires C++17 and its Linux development dependencies, including ALSA, JUCE requires C++17 and its Linux development dependencies, including ALSA,
Fontconfig/FreeType and X11 libraries. CMake >= 3.25 and GNU Make build these Fontconfig/FreeType and X11 libraries. CMake >= 3.25 and a backend such as Ninja build these
examples. Xvfb runs automated checks without a desktop. The SDK's `juceaide` examples. Xvfb runs automated checks without a desktop. The SDK's `juceaide`
tool is compiled at configure time. Builds are substantially heavier than the tool is compiled at configure time. Builds are substantially heavier than the
C-only core. Browser/network features are disabled; normal builds never fetch C-only core. JUCE’s bundled codec C sources use GNU C11; the xtc core stays
strict C99. Browser/network features are disabled; normal builds never fetch
SDKs or install system packages. Only the inspected SDK revision has been SDKs or install system packages. Only the inspected SDK revision has been
qualified; use the documented revision for reproducibility. qualified; use the documented revision for reproducibility.

View file

@ -26,9 +26,11 @@ redistributing dependencies. The default install uses system shared libraries.
| HarfBuzz, Wayland, libdecor, xkbcommon | MIT-family licenses; check each shipped package. | | HarfBuzz, Wayland, libdecor, xkbcommon | MIT-family licenses; check each shipped package. |
| X11 libraries | MIT/X11-family notices. | | X11 libraries | MIT/X11-family notices. |
| D-Bus | AFL-2.1 OR GPL-2.0-or-later. | | D-Bus | AFL-2.1 OR GPL-2.0-or-later. |
| Python 3 | PSF-2.0; optional build/test/package scripts use only the standard library. Not an application runtime dependency. | | Python 3 | PSF-2.0; build/test/package scripts use only the standard library. Not an application runtime dependency. |
GNU Make, GCC/Clang, binutils, pkg-config and optional Xvfb/xdotool/sanitizers CMake (BSD-3-Clause), a backend such as Ninja (Apache-2.0) or GNU Make
(GPL-3.0-or-later), GCC/Clang, binutils, pkg-config and optional
Xvfb/xdotool/sanitizers
are build/test tools, not shipped dependencies. New Python tooling needs Python are build/test tools, not shipped dependencies. New Python tooling needs Python
3.12+ for safe tar extraction in release tests; this host has Python 3.14.8. No 3.12+ for safe tar extraction in release tests; this host has Python 3.14.8. No
packages were installed and no application dependency was added. packages were installed and no application dependency was added.

View file

@ -1,15 +1,15 @@
# Linux Setup and Installation # Linux Setup and Installation
Use a C99 compiler, C++17 compiler for the GUI, GNU Make, binutils and Use a C99 compiler, C++17 compiler for the GUI, CMake 3.25+, a CMake backend, binutils and
pkg-config. The core needs only the C/math runtime. FLTK 1.4 development files pkg-config. The core needs only the C/math runtime. FLTK 1.4 development files
enable the GUI; JACK-compatible development files enable the optional audio/MIDI enable the GUI; JACK-compatible development files enable the optional audio/MIDI
backend. Packaging/install tests additionally use Python 3.12+ (standard library backend. Build/test/install tooling uses Python 3.12+ (standard library only). No command starts a server, installs dependencies or changes system
only). No command starts a server, installs dependencies or changes system
settings. settings.
```sh ```sh
make all test cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug
make gui cmake --build build --parallel
ctest --test-dir build --output-on-failure
./build/xtc ./build/xtc
``` ```
@ -31,17 +31,20 @@ files.
## Staged Installation ## Staged Installation
Core-only installation retains the original `make install` behavior and now adds CMake installation includes the core, xtc-check, pkg-config metadata, licenses
xtc-check, pkg-config metadata, license and documentation. install-gui adds the and documentation. `XTC_INSTALL_GUI` defaults on when FLTK was found, adding the
GUI executable and desktop launcher. Defaults to PREFIX=/usr/local: application and desktop launcher. Use `-DXTC_INSTALL_GUI=OFF` for core-only
installation. The prefix defaults to `/usr/local`:
```sh ```sh
make install-gui DESTDIR="$PWD/build/stage" PREFIX=/usr cmake -S . -B build -DCMAKE_INSTALL_PREFIX=/usr
make uninstall DESTDIR="$PWD/build/stage" PREFIX=/usr cmake --build build --parallel
DESTDIR="$PWD/build/stage" cmake --install build
DESTDIR="$PWD/build/stage" cmake --build build --target uninstall
``` ```
Review staged contents before using a package manager or choosing a writable Review staged contents before using a package manager or choosing a writable
live prefix. For a user installation, use an absolute PREFIX such as live prefix. For a user installation, use an absolute `CMAKE_INSTALL_PREFIX` such as
`$HOME/.local` and ensure its bin is in PATH. Prefixes containing spaces are `$HOME/.local` and ensure its bin is in PATH. Prefixes containing spaces are
tested; shell metacharacters/newlines, quotes, dollar signs and backslashes are tested; shell metacharacters/newlines, quotes, dollar signs and backslashes are
not supported. No root installation is performed by tests. Uninstall from the not supported. No root installation is performed by tests. Uninstall from the
@ -59,15 +62,16 @@ C/C++ consumers use `pkg-config --cflags --libs --static xtc` plus the chosen
compiler; expose PREFIX/lib/pkgconfig through PKG_CONFIG_PATH when needed. compiler; expose PREFIX/lib/pkgconfig through PKG_CONFIG_PATH when needed.
Static backend consumers need JACK if that optional backend was built; the pc Static backend consumers need JACK if that optional backend was built; the pc
file records its private dependency. Public headers contain no JACK/FLTK types. file records its private dependency. Public headers contain no JACK/FLTK types.
To build without optional libraries, set PKG_CONFIG=false FLTK_CONFIG=false. To build without optional libraries, configure with `-DXTC_BUILD_GUI=OFF
-DXTC_WITH_JACK=OFF -DXTC_WITH_LADSPA=OFF`.
Python is not needed to run the installed executables or library. Python is not needed to run the installed executables or library.
--- ---
## Binary/Development Archive ## Binary/Development Archive
`make package` creates a system-linked Linux archive plus SHA-256 sidecar below `cmake --build build --target package-binary` creates a system-linked Linux archive plus SHA-256 sidecar below
BUILD_DIR; `make test-package` verifies its contents. It includes usr/bin, the selected binary directory; `cmake --build build --target test-package` verifies its contents. It includes usr/bin,
headers/static library/pkg-config metadata, desktop file (when FLTK is present), headers/static library/pkg-config metadata, desktop file (when FLTK is present),
manuals, baseline and MIT license. No shared library, third-party plugin, media, manuals, baseline and MIT license. No shared library, third-party plugin, media,
service or dependency installer is bundled. `BINARY_MANIFEST.json` lists file service or dependency installer is bundled. `BINARY_MANIFEST.json` lists file
@ -80,8 +84,8 @@ extract the archive with two leading path components stripped
(`xtc-VERSION-linux- ARCH/usr`), placing bin/include/lib/share directly under (`xtc-VERSION-linux- ARCH/usr`), placing bin/include/lib/share directly under
that prefix. Put its bin on PATH and lib/pkgconfig on PKG_CONFIG_PATH. The that prefix. Put its bin on PATH and lib/pkgconfig on PKG_CONFIG_PATH. The
archive's pkg-config prefix is relative to its installed location; normal archive's pkg-config prefix is relative to its installed location; normal
`make install` retains explicit PREFIX. Use the matching source's uninstall with `cmake --build build --target install` retains its configured prefix. Use the matching source's uninstall with
that PREFIX, or review the manifest before removing its files. Unknown/unrelated that prefix, or review the manifest before removing its files. Unknown/unrelated
files are retained by source uninstall. This local tar package is not a files are retained by source uninstall. This local tar package is not a
pacman/RPM/deb registration or public distribution. pacman/RPM/deb registration or public distribution.
@ -90,5 +94,5 @@ without opening a server or executing a plugin. `xtc --diagnose` works
headlessly as well. Missing runtime GUI libraries are loader errors: install the headlessly as well. Missing runtime GUI libraries are loader errors: install the
distribution's compatible dependencies or rebuild the core-only configuration. distribution's compatible dependencies or rebuild the core-only configuration.
Build without optional libraries using a fresh directory, Build without optional libraries using a fresh directory,
`PKG_CONFIG=false FLTK_CONFIG=false CPPFLAGS=-DXTC_DISABLE_LADSPA`. No system `cmake -S . -B build/minimal -DXTC_BUILD_GUI=OFF -DXTC_WITH_JACK=OFF -DXTC_WITH_LADSPA=OFF`. No system
setting or package is changed by tests. setting or package is changed by tests.

View file

@ -2,7 +2,7 @@
## Supported Development Target ## Supported Development Target
Initial target: x86_64 Artix/Arch-family Linux with glibc, GNU Make, GCC/Clang, Initial target: x86_64 Artix/Arch-family Linux with glibc, CMake 3.25+ and a backend such as Ninja, GCC/Clang,
FLTK 1.4 and a JACK-compatible client/server. The inspected host uses GCC FLTK 1.4 and a JACK-compatible client/server. The inspected host uses GCC
16.2.1, FLTK 1.4.5 and the dependencies in LICENSES.md. 16.2.1, FLTK 1.4.5 and the dependencies in LICENSES.md.
Core-only/no-JACK/no-LADSPA builds are checked too. Build on the destination Core-only/no-JACK/no-LADSPA builds are checked too. Build on the destination
@ -19,16 +19,16 @@ only. No dependency is fetched automatically.
bump, Git tag, public upload or external release is performed by local bump, Git tag, public upload or external release is performed by local
targets. targets.
2. Run warning-enabled 2. Run warning-enabled
`make all test gui test-plugins test-plugins-real test-gui-plugins test-workflow test-gui-jack test-jack-midi test-recording test-recovery test-reliability test-scale analyze` `cmake -S . -B build -DXTC_ENABLE_LIVE_TESTS=ON && cmake --build build --parallel && cmake --build build --target check gui test-plugins test-plugins-real test-gui-plugins test-workflow test-gui-jack test-jack-midi test-recording test-recovery test-reliability test-scale analyze`
where dependencies exist. where dependencies exist.
3. Use a separate BUILD_DIR for ASan/UBSan/leak runs and another for optional- 3. Use a separate CMake binary directory for ASan/UBSan/leak runs and another for optional-
dependency builds. GUI leak checks use only the documented fontconfig dependency builds. GUI leak checks use only the documented fontconfig
suppression; core tests use none. Run publication/recording race tests as suppression; core tests use none. Run publication/recording race tests as
described in RELIABILITY.md. Sanitizers are not latency qualification. described in RELIABILITY.md. Sanitizers are not latency qualification.
4. Run `make benchmark test-performance test-stress`; compare on matching 4. Run `cmake --build build --target benchmark test-performance test-stress`; compare on matching
host/compiler/flags against a reviewed baseline. Investigate regressions; do host/compiler/flags against a reviewed baseline. Investigate regressions; do
not weaken thresholds or replace evidence without explanation. not weaken thresholds or replace evidence without explanation.
5. Run `make test-install test-release test-package`. Inspect archive manifests, 5. Run `cmake --build build --target test-install test-release test-package`. Inspect archive manifests,
hashes, dynamic dependency names and licenses. Source exports rebuild twice hashes, dynamic dependency names and licenses. Source exports rebuild twice
with identical artifacts on this host. Repeat binary packaging compares with identical artifacts on this host. Repeat binary packaging compares
bytes. bytes.

View file

@ -74,11 +74,11 @@ zoom yet. Note edits participate in project snapshot undo/redo.
## Verification ## Verification
`make test` checks exact onset/off samples, block partition independence, trim, `cmake --build build --target check` checks exact onset/off samples, block partition independence, trim,
seek, panic, channel handling, voice/event limits, persistence migration and seek, panic, channel handling, voice/event limits, persistence migration and
malformed files, plus callback fault recovery. `make test-gui` draws, moves and malformed files, plus callback fault recovery. `cmake --build build --target test-gui` draws, moves and
resizes notes with FLTK events, then saves/reopens and renders them. resizes notes with FLTK events, then saves/reopens and renders them.
`make test-jack-midi` uses only software MIDI/audio ports to verify live on/off, `cmake -S . -B build -DXTC_ENABLE_LIVE_TESTS=ON && cmake --build build --target test-jack-midi` uses only software MIDI/audio ports to verify live on/off,
panic, disconnection and saved/reopened scheduled playback. JACK port discovery panic, disconnection and saved/reopened scheduled playback. JACK port discovery
may lag activation under PipeWire; the test waits before connecting. Physical may lag activation under PipeWire; the test waits before connecting. Physical
MIDI devices, bridge/device loss behind an unchanged port, latency and MIDI devices, bridge/device loss behind an unchanged port, latency and
@ -138,10 +138,10 @@ on/off/control messages execute in caller/JACK order, with no implicit priority
reordering. The renderer rejects malformed/unsorted/out-of-range events before reordering. The renderer rejects malformed/unsorted/out-of-range events before
touching output or runtime; raw velocity-zero note-ons decode to note-off. touching output or runtime; raw velocity-zero note-ons decode to note-off.
`make test-composition` covers filters, property preservation, triplet rounding, `cmake --build build --target test-composition` covers filters, property preservation, triplet rounding,
repeats/fresh IDs, rejected batch rollback, capacity, undo snapshots, repeats/fresh IDs, rejected batch rollback, capacity, undo snapshots,
equal-offset live order, adjacent/overlapping notes, exact block partitions and equal-offset live order, adjacent/overlapping notes, exact block partitions and
seek chase. `make test-gui` covers mouse edits and the new commands with seek chase. `cmake --build build --target test-gui` covers mouse edits and the new commands with
undo/redo and engine reinstallation. Listening and physical MIDI-device undo/redo and engine reinstallation. Listening and physical MIDI-device
validation remain pending. validation remain pending.

View file

@ -124,8 +124,8 @@ The code is built with warnings enabled and tested. Reproduce the core,
transaction and allocation-failure checks with: transaction and allocation-failure checks with:
```sh ```sh
make BUILD_DIR=build/roadmap-audit all gui test-reliability cmake --build build/roadmap-audit --parallel && cmake --build build/roadmap-audit --target gui test-reliability
make BUILD_DIR=build/roadmap-audit test-gui-modern-editing cmake --build build/roadmap-audit --target test-gui-modern-editing
``` ```
The full GUI, instrument/effect plugin fixtures, software JACK lifecycle, Clang The full GUI, instrument/effect plugin fixtures, software JACK lifecycle, Clang

View file

@ -1,7 +1,7 @@
# Native C Processor API, Version 1 # Native C Processor API, Version 1
`xtc_native.h` defines a small compile-time extension boundary for trusted C99 `xtc_native.h` defines a small compile-time extension boundary for trusted C99
sources, effects and instruments. Build `make build/native-gain` and run it for sources, effects and instruments. Build `cmake --build build --target native-gain` and run it for
a complete graph-effect example in `examples/native_gain.c`. This is a source a complete graph-effect example in `examples/native_gain.c`. This is a source
API for this release, not a promise of binary compatibility across API for this release, not a promise of binary compatibility across
builds/platforms. There is no module scanner, dynamic loader, SDK dependency or builds/platforms. There is no module scanner, dynamic loader, SDK dependency or
@ -90,7 +90,7 @@ policy must precede serialized native extensions or external plugin hosting.
Tests cover ABI/size/field rejection, descriptor copying, aliased buffers, Tests cover ABI/size/field rejection, descriptor copying, aliased buffers,
prepare/process failures, stereo compile cleanup, retirement/destruction outside prepare/process failures, stereo compile cleanup, retirement/destruction outside
processing, event offsets, seek note reset, source ports, instrument-buffer processing, event offsets, seek note reset, source ports, instrument-buffer
graph routing and unsupported-save preservation. Run `make test`. graph routing and unsupported-save preservation. Run `cmake --build build --target check`.
--- ---
@ -99,7 +99,7 @@ graph routing and unsupported-save preservation. Run `make test`.
`xtc_processor.h` is the public C99 parameter-only extension interface (ABI 2). `xtc_processor.h` is the public C99 parameter-only extension interface (ABI 2).
It complements the ABI 1 graph adapter without changing its layout or behavior. It complements the ABI 1 graph adapter without changing its layout or behavior.
`examples/processors.c` implements a mono gain effect and a monophonic MIDI sine `examples/processors.c` implements a mono gain effect and a monophonic MIDI sine
instrument using only public headers; `make test-processor` builds and exercises instrument using only public headers; `cmake --build build --target test-processor` builds and exercises
both. The instrument has immediate note-off and no voice stealing/release tail. both. The instrument has immediate note-off and no voice stealing/release tail.
Descriptors have an exact ABI version and structure size, a persistent Descriptors have an exact ABI version and structure size, a persistent

View file

@ -41,7 +41,7 @@ termination remain unqualified.
## Matrix, Capacity and Contention Checks ## Matrix, Capacity and Contention Checks
`make benchmark` records 24 warmed graph cases: 2/16/64/256 nodes, mono/stereo, `cmake --build build --target benchmark` records 24 warmed graph cases: 2/16/64/256 nodes, mono/stereo,
44.1k/31, 48k/128 and 96k/1024. Each reports control-thread compile time 44.1k/31, 48k/128 and 96k/1024. Each reports control-thread compile time
separately from 300 process calls, mean/p99/max, block deadline, mean load, and separately from 300 process calls, mean/p99/max, block deadline, mean load, and
paired graph buffer bytes. Timing, sorting, allocation and logging happen paired graph buffer bytes. Timing, sorting, allocation and logging happen
@ -51,10 +51,10 @@ CPU/compiler/flags/kernel and all measurements. Copy a new run only after
reviewing changes and conditions. reviewing changes and conditions.
```sh ```sh
make benchmark test-performance test-scale test-stress cmake --build build --target benchmark test-performance test-scale test-stress
make benchmark-compare BENCHMARK_MAX_REGRESSION=1.5 cmake -S . -B build -DXTC_BENCHMARK_MAX_REGRESSION=1.5 && cmake --build build --target benchmark-compare
# Select a baseline from this same host/compiler/flags: # Select a baseline from this same host/compiler/flags:
make benchmark-compare BENCHMARK_BASELINE=/path/to/performance.json cmake -S . -B build -DXTC_BENCHMARK_BASELINE=/path/to/performance.json && cmake --build build --target benchmark-compare
``` ```
Comparison rejects mismatched CPU/architecture/compiler/flags or missing cases. Comparison rejects mismatched CPU/architecture/compiler/flags or missing cases.
@ -64,7 +64,7 @@ Repeat surprising results under comparable load; do not silently replace the
baseline to make a regression disappear. Scheduling, clocks, thermal state and baseline to make a regression disappear. Scheduling, clocks, thermal state and
shared-machine contention still affect results. No real-time scheduling/pinning shared-machine contention still affect results. No real-time scheduling/pinning
or external profiler is required. CPU-pressure runs are correctness checks, not or external profiler is required. CPU-pressure runs are correctness checks, not
comparable timing baselines. Tests/benchmark outputs live below BUILD_DIR. comparable timing baselines. Tests/benchmark outputs live below the selected binary directory.
Initial matrix (Threadripper 1920X, GCC 16.2.1, -O2, 300 blocks): 256-node Initial matrix (Threadripper 1920X, GCC 16.2.1, -O2, 300 blocks): 256-node
stereo 48k/128 mean 353.214 us, p99 396.495 us, load .132; 96k/1024 mean stereo 48k/128 mean 353.214 us, p99 396.495 us, load .132; 96k/1024 mean
@ -118,13 +118,13 @@ graph matrix separately scales nodes and chain connections from 2 to 256. These
synthetic cases do not qualify every possible dense graph or plugin chain. synthetic cases do not qualify every possible dense graph or plugin chain.
```sh ```sh
make benchmark benchmark-project cmake --build build --target benchmark benchmark-project
make benchmark-compare cmake --build build --target benchmark-compare
make benchmark-project-compare cmake --build build --target benchmark-project-compare
# Baselines must match the current CPU/compiler/flags and workload. # Baselines must match the current CPU/compiler/flags and workload.
make benchmark-project-compare PROJECT_BENCHMARK_BASELINE=/path/to/report.json cmake -S . -B build -DXTC_PROJECT_BENCHMARK_BASELINE=/path/to/report.json && cmake --build build --target benchmark-project-compare
./build/test_performance 10000 64 128 ./build/test_performance 10000 64 128
make test-benchmark-tools cmake --build build --target test-benchmark-tools
``` ```
Both matrices report wall-clock mean/p99/max processing duration and the count Both matrices report wall-clock mean/p99/max processing duration and the count
@ -154,10 +154,10 @@ on machine timing. Do not replace a failing baseline without investigating.
## Endurance, Faults and Callback Audit ## Endurance, Faults and Callback Audit
```sh ```sh
make test-soak test-stress test-stress-plugins cmake --build build --target test-soak test-stress test-stress-plugins
make test-stress STRESS_ROUNDS=10 cmake -S . -B build -DXTC_STRESS_ROUNDS=10 && cmake --build build --target test-stress
./build/test_soak 10000000 ./build/test_soak 10000000
make check-development cmake --build build --target check-development
``` ```
`test-soak` renders exactly 1,000,000 alternating 31/128/1,024-frame stereo `test-soak` renders exactly 1,000,000 alternating 31/128/1,024-frame stereo
@ -210,7 +210,7 @@ introduced without a separate correctness/performance justification.
Reproduce in an isolated build (GNU gprof must already be installed): Reproduce in an isolated build (GNU gprof must already be installed):
```sh ```sh
make BUILD_DIR=build/profile CFLAGS='-O2 -g -pg -std=c99 -Iinclude' LDFLAGS=-pg build/profile/test_performance cmake -S . -B build/profile -DCMAKE_C_FLAGS='-O2 -g -pg -std=c99 -Iinclude' -DCMAKE_EXE_LINKER_FLAGS=-pg && cmake --build build/profile --target test_performance
GMON_OUT_PREFIX="$PWD/build/profile/gmon" ./build/profile/test_performance 10000 64 128 GMON_OUT_PREFIX="$PWD/build/profile/gmon" ./build/profile/test_performance 10000 64 128
gprof build/profile/test_performance build/profile/gmon.* > build/profile/profile.txt gprof build/profile/test_performance build/profile/gmon.* > build/profile/profile.txt
``` ```

View file

@ -92,8 +92,8 @@ binary/version identities.
## Repeatable Acceptance ## Repeatable Acceptance
```sh ```sh
make all test gui test-plugins test-gui-plugins analyze cmake --build build --parallel && cmake --build build --target check gui test-plugins test-gui-plugins analyze
make test-plugins-real cmake --build build --target test-plugins-real
``` ```
The first plugin target builds an original MIT shared-library fixture with gain, The first plugin target builds an original MIT shared-library fixture with gain,
@ -269,8 +269,8 @@ inside the callback. CLAP process errors and VST3 failure results silence a
block. VST2 has no process error return. block. VST2 has no process error return.
```sh ```sh
make all test gui test-modern-plugins test-gui-modern-plugins cmake --build build --parallel && cmake --build build --target check gui test-modern-plugins test-gui-modern-plugins
make test-sanitize analyze cmake --build build --target test-sanitize analyze
``` ```
Original CLAP/VST2/VST3 shared-library fixtures exercise exact in-place and Original CLAP/VST2/VST3 shared-library fixtures exercise exact in-place and

View file

@ -230,7 +230,7 @@ project Save intentionally replaces the selected document. In-memory models and
the existing destination are preserved on load failure. No automatic the existing destination are preserved on load failure. No automatic
unknown-field stripping or media deletion occurs. unknown-field stripping or media deletion occurs.
`make test-portability test test-plugins test-recovery test-recording` covers `cmake --build build --target test-portability check test-plugins test-recovery test-recording` covers
checked-in v1–v10 fixtures (including missing v5/v6 plugins), current exact checked-in v1–v10 fixtures (including missing v5/v6 plugins), current exact
roundtrips, moved-folder playback from a different cwd, missing media retention, roundtrips, moved-folder playback from a different cwd, missing media retention,
unknown records, every truncation and deterministic byte mutations, malformed unknown records, every truncation and deterministic byte mutations, malformed
@ -265,7 +265,7 @@ source references; splitting keeps the original ID on the left, gives the right
a fresh ID/source offset, retains outer explicit fades and clears inner ones. No a fresh ID/source offset, retains outer explicit fades and clears inner ones. No
waveform cache or source audio is serialized. Checked-in v1–v7 migrations, v8 waveform cache or source audio is serialized. Checked-in v1–v7 migrations, v8
exact roundtrips, malformed new metadata and byte-mutation tests cover the exact roundtrips, malformed new metadata and byte-mutation tests cover the
format change (`make test test-portability test-audio-editing`). format change (`cmake --build build --target check test-portability test-audio-editing`).
--- ---
@ -369,13 +369,13 @@ destination. Plugin binaries and state are not bundled. JACK availability,
sample rate, block limits and device routing are runtime dependencies; loading sample rate, block limits and device routing are runtime dependencies; loading
metadata does not restore device connections. metadata does not restore device connections.
`make test` includes v1–v10 migration, malformed/truncated/mutated/oversized `cmake --build build --target check` includes v1–v10 migration, malformed/truncated/mutated/oversized
input, retained-state diagnostics, CLI dependency inspection, and deterministic input, retained-state diagnostics, CLI dependency inspection, and deterministic
save faults at flush, file sync, close, rename and directory sync. Pre-rename save faults at flush, file sync, close, rename and directory sync. Pre-rename
faults preserve the old bytes and remove the temporary. After rename, a sync faults preserve the old bytes and remove the temporary. After rename, a sync
failure reports I/O while leaving a complete new document; inspect it before failure reports I/O while leaving a complete new document; inspect it before
retrying. Fault wrappers are linked into tests only. These tests complement retrying. Fault wrappers are linked into tests only. These tests complement
killed-writer coverage in `make test-recovery`; they do not qualify power-cut killed-writer coverage in `cmake --build build --target test-recovery`; they do not qualify power-cut
behavior. behavior.
--- ---

View file

@ -51,7 +51,7 @@ There is no autosave, crash journal, automatic orphan recovery or persisted Undo
history. Existing finalized files are protected against the tested process-level history. Existing finalized files are protected against the tested process-level
failures; unsaved edits/in-flight takes can still be lost. failures; unsaved edits/in-flight takes can still be lost.
`make test-recovery` uses temporary directories and child processes only. It `cmake --build build --target test-recovery` uses temporary directories and child processes only. It
forces an EFBIG-style write failure with a child-local file-size limit, kills forces an EFBIG-style write failure with a child-local file-size limit, kills
eight project writers while temporaries are visible, and kills a recorder after eight project writers while temporaries are visible, and kills a recorder after
draining but before finalization. Complete old/new destinations and explicit draining but before finalization. Complete old/new destinations and explicit
@ -69,7 +69,7 @@ recorder recovery path before destruction, or use the GUI's reported path.
float32 WAV from complete on-disk frames, ignoring a trailing partial frame. It float32 WAV from complete on-disk frames, ignoring a trailing partial frame. It
never changes the source or overwrites an existing destination. This supersedes never changes the source or overwrites an existing destination. This supersedes
the earlier lack of a WAV recovery tool described above. See AUDIO_FILES.md for the earlier lack of a WAV recovery tool described above. See AUDIO_FILES.md for
format/ownership/size limits and `make test-recording`. format/ownership/size limits and `cmake --build build --target test-recording`.
--- ---
@ -95,7 +95,7 @@ native plugin code; LADSPA has no runtime error-return channel.
## Save Diagnostics ## Save Diagnostics
`make test-project-diagnostics` deterministically injects flush, file-sync, `cmake --build build --target test-project-diagnostics` deterministically injects flush, file-sync,
close, rename and directory-sync errors. Pre-rename failures retain the previous close, rename and directory-sync errors. Pre-rename failures retain the previous
bytes and clean temporary files. Post-rename sync errors leave a complete new bytes and clean temporary files. Post-rename sync errors leave a complete new
file and return failure; there is no rollback after replacement. Load file and return failure; there is no rollback after replacement. Load

View file

@ -3,29 +3,32 @@
Source packaging, staged install/uninstall and repeatable builds are testable. Source packaging, staged install/uninstall and repeatable builds are testable.
It does not turn the prototype into a production-qualified DAW. The version It does not turn the prototype into a production-qualified DAW. The version
remains 0.1.0; no tag, external release or binary publication is created by remains 0.1.0; no tag, external release or binary publication is created by
`make dist` or `make release`. `cmake --build build --target dist` or `cmake --build build --target release`.
--- ---
## Build and Package ## Build and Package
```sh ```sh
make release cmake --preset release
make test-install cmake --build --preset release --parallel
make test-recovery test-performance ctest --preset release
make dist cmake --build build/release --target test-install test-install-layout
make test-release cmake --build build/release --target dist test-release
cmake --build build/release --target test-package
``` ```
release builds optimized, warning-enabled C/C++ code, tests and the optional GUI The release preset builds optimized, warning-enabled C/C++ code, test binaries
in BUILD_DIR/release with no debug paths, a source-prefix map, deterministic ar and the available GUI in `build/release`, with source/binary prefix maps,
metadata and FLTK include/define flags rather than distribution-specific LTO no debug information, deterministic archive metadata and assertions retained.
flags. It retains assertions so the test harness actually runs. dist creates FLTK discovery takes include/define flags without overriding the project's
BUILD_DIR/xtc-0.1.0.tar.gz using Python's standard library. It includes only Git optimization settings. `dist` creates `xtc-0.1.0.tar.gz` below the binary
indexed regular files (working-tree contents, including staged new files), plus directory using Python's standard library. It includes only Git-indexed regular
a source manifest; stage intended files first and check `git diff --cached`. files (working-tree contents, including staged new files), plus a source
Untracked files, ignored build output and .git are excluded. Exported archives manifest; stage intended files first and check `git diff --cached`. Untracked
can reproduce themselves without Git using SOURCE_MANIFEST.json. files, ignored build output and .git are excluded. Exported archives reproduce
themselves without Git using SOURCE_MANIFEST.json. The `test-release` tool uses
CMake and CTest in two independent exported trees, without invoking Make.
Archive order, permissions, ownership, gzip filename and timestamps are Archive order, permissions, ownership, gzip filename and timestamps are
normalized. SOURCE_DATE_EPOCH defaults to 0; set an unsigned 32-bit epoch normalized. SOURCE_DATE_EPOCH defaults to 0; set an unsigned 32-bit epoch
@ -34,8 +37,8 @@ from a clean checkout and record its commit/hash externally. Do not treat a
dirty-tree archive as an attested release. No artifact is uploaded by these dirty-tree archive as an attested release. No artifact is uploaded by these
targets. targets.
test-release unpacks the source into two different temporary paths, runs release test-release unpacks the source into two different temporary paths, configures
in each, compares libxtc.a/xtc-check/optional xtc SHA-256 values, and repacks a reproducible Release build and runs CTest in each, compares libxtc.a/xtc-check/optional xtc SHA-256 values, and repacks
each export to compare archive bytes. This verifies reproducibility for the same each export to compare archive bytes. This verifies reproducibility for the same
host, compiler, flags and dependency versions. It does not promise bit-identical host, compiler, flags and dependency versions. It does not promise bit-identical
output across distributions, compilers or library updates. output across distributions, compilers or library updates.
@ -66,11 +69,11 @@ shared-library extension ABI in this release.
## Local Binary Packaging and Installed Workflow ## Local Binary Packaging and Installed Workflow
`make test-package` builds the release, stages installation only in a temporary `cmake --build build/release --target test-package` uses the selected build and stages installation only in a temporary
DESTDIR, writes a normalized Linux binary/development archive, checks its DESTDIR, writes a normalized Linux binary/development archive, checks its
SHA-256 sidecar and per-file manifest, relocatable pkg-config paths and headless SHA-256 sidecar and per-file manifest, relocatable pkg-config paths and headless
commands. Same-host repeated packaging is byte-identical; dependency versions commands. Same-host repeated packaging is byte-identical; dependency versions
and toolchain still determine binary compatibility. `make test-install` now and toolchain still determine binary compatibility. `cmake --build build --target test-install` now
repeats installation to verify repair/upgrade of owned files while preserving an repeats installation to verify repair/upgrade of owned files while preserving an
unrelated sentinel, then compiles both C99 and C++17 installed consumers that unrelated sentinel, then compiles both C99 and C++17 installed consumers that
create MIDI, save/reopen and compare nonzero exact rendering. Removal remains create MIDI, save/reopen and compare nonzero exact rendering. Removal remains
@ -88,21 +91,28 @@ pending.
## Release Workflow ## Release Workflow
Installation now shares one explicit file inventory with uninstallation. Installation shares the existing explicit owned-file inventory with removal.
GNU-style `exec_prefix`, `bindir`, `libdir`, `includedir`, `datarootdir`, Use CMake's `CMAKE_INSTALL_PREFIX`, `CMAKE_INSTALL_BINDIR`,
`datadir`, `docdir`, `pkgconfigdir` and the xtc-specific `licensedir`, `CMAKE_INSTALL_LIBDIR`, `CMAKE_INSTALL_INCLUDEDIR`, `CMAKE_INSTALL_DATAROOTDIR`
`examplesdir`, `desktopdir` can be overridden alongside `PREFIX`/`DESTDIR`. and `CMAKE_INSTALL_DOCDIR`. xtc-specific cache entries are
Paths must be absolute POSIX paths; spaces are supported. Do not include DESTDIR `XTC_INSTALL_LICENSEDIR`, `XTC_INSTALL_EXAMPLESDIR`, `XTC_INSTALL_DESKTOPDIR`
in a directory variable: staging is applied separately and never embedded in and `XTC_INSTALL_PKGCONFIGDIR`. Relative directories resolve beneath the prefix;
pkg-config metadata. Staging refuses parent symlinks that escape DESTDIR. Use absolute directories are supported. Spaces work; control characters, quotes,
exactly the same variables for removal. dollar signs, backslashes and `..` are rejected by the audited layout helper.
`DESTDIR` is a separate staging environment variable. The same cache and prefix
must be used for uninstall; a `cmake --install --prefix` override is temporary.
The internal `XTC_INSTALL_BUILD_DIR` setting lets packaging/test drivers stage
already-built inputs using a temporary configuration without modifying the
caller's cache. SDK products are never in this inventory.
```sh ```sh
make BUILD_DIR=build/release-check all test gui cmake -S . -B build/release-check -DCMAKE_BUILD_TYPE=Release \
make BUILD_DIR=build/release-check test-install test-install-layout -DXTC_REPRODUCIBLE=ON -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=lib64
make PREFIX=/usr DESTDIR=/tmp/xtc-stage libdir=/usr/lib64 install-gui cmake --build build/release-check --parallel
make PREFIX=/usr DESTDIR=/tmp/xtc-stage libdir=/usr/lib64 uninstall ctest --test-dir build/release-check --output-on-failure
make test-release test-package cmake --build build/release-check --target test-install test-install-layout
DESTDIR=/tmp/xtc-stage cmake --install build/release-check
DESTDIR=/tmp/xtc-stage cmake --build build/release-check --target uninstall
``` ```
Core install includes the static library, public headers, `xtc-check`, Core install includes the static library, public headers, `xtc-check`,
@ -114,7 +124,7 @@ metadata; custom live layouts are tested separately. Existing owned file
symlinks are replaced, not followed; installation is atomic per file, not a symlinks are replaced, not followed; installation is atomic per file, not a
transaction over the entire prefix. transaction over the entire prefix.
`make dist` now writes a `.sha256` sidecar as well as the normalized source `cmake --build build --target dist` now writes a `.sha256` sidecar as well as the normalized source
archive. Record that hash, the reviewed commit, version and build/dependency archive. Record that hash, the reviewed commit, version and build/dependency
conditions with release logs. `test-release` verifies the hash, builds/tests two conditions with release logs. `test-release` verifies the hash, builds/tests two
fresh source exports and compares artifacts and repacks. It validates exported fresh source exports and compares artifacts and repacks. It validates exported

View file

@ -94,11 +94,11 @@ formal proof.
From the repository root: From the repository root:
```sh ```sh
make BUILD_DIR=build/audit all gui test-reliability test-recovery cmake --build build/audit --parallel && cmake --build build/audit --target gui test-reliability test-recovery
make BUILD_DIR=build/audit test-sanitize cmake --build build/audit --target test-sanitize
make analyze cmake --build build --target analyze
make BUILD_DIR=build/audit test-gui test-gui-jack test-jack test-jack-audio test-jack-midi cmake -S . -B build/audit -DXTC_ENABLE_LIVE_TESTS=ON && cmake --build build/audit --target test-gui test-gui-jack test-jack test-jack-audio test-jack-midi
make BUILD_DIR=build/nojack PKG_CONFIG=false all test-reliability gui cmake -S . -B build/nojack -DXTC_WITH_JACK=OFF && cmake --build build/nojack --parallel && cmake --build build/nojack --target test-reliability gui
``` ```
`test-sanitize` needs installed Clang and runs core, allocation-failure and `test-sanitize` needs installed Clang and runs core, allocation-failure and
@ -141,10 +141,10 @@ stop/close handles faults or missing callbacks. See AUDIO_QUALITY.md.
## Repeatable Development Checks ## Repeatable Development Checks
`make check-development` combines normal tests, capacity/endurance, `cmake --build build --target check-development` combines normal tests, capacity/endurance,
deterministic benchmark-gate tests, static analysis and ASan/UBSan (including deterministic benchmark-gate tests, static analysis and ASan/UBSan (including
scale and soak). `make test-stress` repeats project/backend/publication faults scale and soak). `cmake --build build --target test-stress` repeats project/backend/publication faults
under CPU contention; `make test-stress-plugins` adds the original LADSPA under CPU contention; `cmake --build build --target test-stress-plugins` adds the original LADSPA
failure fixture. The reliability callback guard now also rejects common host failure fixture. The reliability callback guard now also rejects common host
file I/O and blocking mutex/condition calls. It audits directly linked host file I/O and blocking mutex/condition calls. It audits directly linked host
code, not arbitrary dynamic-plugin internals. See PERFORMANCE.md for exact code, not arbitrary dynamic-plugin internals. See PERFORMANCE.md for exact

View file

@ -1,5 +1,9 @@
# Roadmap # Roadmap
Build commands in Phases 0–107 are historical qualification records from the
Makefile era. The current supported commands are in [BUILD.md](BUILD.md);
Phase 108 replaces that build with CMake and CTest.
## Phase 0 — Baseline ## Phase 0 — Baseline
### Goal ### Goal
@ -3500,6 +3504,77 @@ building JUCE's standalone wrapper does not establish hardware acceptance.
--- ---
## Phase 108 — Complete CMake Build Migration
### Goal
Replace the hand-written root Makefile with a full CMake project and CTest,
preserving C99/C++17 boundaries, optional dependencies, SDK examples, fixtures,
developer tools and the audited installation/release workflows. This explicitly
requested build migration can proceed alongside remaining hardware/platform
qualification in earlier phases.
### Tasks and acceptance criteria
- [x] Define target-based core, CLI, GUI, example and plugin-fixture builds with
warnings, generated dependencies and optional JACK/FLTK/LADSPA discovery.
- [x] Register offline/unit/integration and GUI checks with CTest; keep live
JACK workflows opt-in and retain linker-based fault injection.
- [x] Provide separate debug/release/minimal/sanitizer presets, screenshot,
analysis, benchmark, stress and focused test targets.
- [x] Build optional Steinberg/JUCE SDK examples through root CMake; eliminate
JUCE's recursive Make dependency while retaining standalone SDK entry points.
- [x] Preserve staged install/uninstall, configurable layouts, pkg-config,
license inventory, reproducible source exports and binary-package checks.
- [x] Remove the root Makefile and update current documentation, examples,
third-party build guidance and agent instructions to use CMake/CTest.
- [x] Validate GNU Makefiles and Ninja, a minimal C-only build, GUI checks,
sanitizer tests, SDK interoperability and release/install/package workflows.
### Implementation status
Completed on 2026-11-06 with CMake 4.4.5, GCC 16.2.1, Clang 23.1.4 and
both Unix Makefiles and Ninja. The original build-rule Makefile was removed; see
[BUILD.md](BUILD.md), [RELEASING.md](RELEASING.md) and
[third-party guidance](../third_party/README.md).
Validation:
- Debug CMake build with JACK/FLTK: all 55 registered offline/GUI tests passed.
- Ninja Release build with GUI/JACK/LADSPA disabled: all 41 tests passed; the
configuration enabled only C, and `BUILD_TESTING=OFF` also built successfully.
- Clang ASan/UBSan: all 42 offline tests passed, including one million virtual
playback blocks. Clang static analysis passed for all C core sources.
- Default/custom staged install and removal, C/C++ installed pkg-config consumers,
symlink protection, prefix spaces and minimal/no-JACK installation passed.
Binary archive contents, hashes, relocation and repeat packaging passed.
- Two independent source-export Release builds (including the GUI and CTest)
produced byte-identical `libxtc.a`, `xtc-check`, `xtc` and repacked archives.
The check used an isolated temporary Git index to include the new CMake files
without staging the user's working index; normal `dist` still exports indexed
files as documented.
- Root Steinberg SDK build: host interoperability and official validator CTests
passed. Root JUCE build: all four C-host, file/editor, processor and JUCE-host
CTests passed. The standalone JUCE entry point also configured and built its
CMake core target successfully. The bridge does not invoke Make.
- CMake screenshot capture produced four actual creative-workflow images in the
ignored binary tree. Preview targets wrote the expected WAV files; graph and
project benchmark drivers ran successfully (timings were not used as a gate
while compilation was running). Three minimal-build CPU stress rounds passed.
- Five opt-in live JACK/workflow tests were registered and inspected; they were
not rerun as hardware acceptance. External SDK sources can emit upstream
warnings; the normal project builds were warning-free.
A subsequent user-requested convenience Makefile forwards configure/build,
CTest, GUI/run and clean commands to CMake. It has no compilation rules or source
inventory; CMake remains authoritative. See [quick shortcuts](BUILD.md#quick-make-shortcuts).
Historical validation above remains as recorded. This migration adds no
Windows/macOS, physical-device, native ASIO, or additional plugin-format
qualification.
---
## Recommended Execution Order ## Recommended Execution Order
Reorder the phases when the audit reveals a blocking dependency. Do not mark a Reorder the phases when the audit reveals a blocking dependency. Do not mark a

View file

@ -102,12 +102,12 @@ public C snapshots/commands and never engine internals or callback storage.
## Verification and Limits ## Verification and Limits
`make test-routing` checks branch reachability, external input, levels, capacity `cmake --build build --target test-routing` checks branch reachability, external input, levels, capacity
rollback, exact unity-insertion rendering and complete plan handoff. `make test` rollback, exact unity-insertion rendering and complete plan handoff. `cmake --build build --target check`
also retains concurrent publication/lifecycle tests. `make test-reliability` also retains concurrent publication/lifecycle tests. `cmake --build build --target test-reliability`
retains the callback-allocation guards. `make test-gui-plugins` checks selection retains the callback-allocation guards. `cmake --build build --target test-gui-plugins` checks selection
diagnostics, unique placement/layout, failed backend publication, atomic diagnostics, unique placement/layout, failed backend publication, atomic
insertion undo/redo and keyboard focus. `make test-gui-jack` insertion undo/redo and keyboard focus. `cmake -S . -B build -DXTC_ENABLE_LIVE_TESTS=ON && cmake --build build --target test-gui-jack`
inserts/disconnects/restores real routes during live software playback and inserts/disconnects/restores real routes during live software playback and
checks engine revision acknowledgements and cycle rejection. checks engine revision acknowledgements and cycle rejection.

View file

@ -25,6 +25,12 @@ check without adding a C++ runtime or network dependency to libxtc.
- The C render example demonstrates the actual xtc plugin facade and WAV writer, - The C render example demonstrates the actual xtc plugin facade and WAV writer,
including no-clobber output, without requiring JACK or a GUI. including no-clobber output, without requiring JACK or a GUI.
Enable the root CMake integration with `-DXTC_BUILD_STEINBERG=ON` and
`-DXTC_VST3_SDK_ROOT=/path/to/vst3sdk`, build, then run
`ctest --test-dir build --output-on-failure -R steinberg`. Normal builds leave
the SDK option off. The example subdirectory also remains a standalone CMake
entry point for native SDK/ASIO diagnostics.
See [the example instructions](../examples/steinberg/README.md) for SDK versions, See [the example instructions](../examples/steinberg/README.md) for SDK versions,
build commands, validation and ASIO diagnostics. build commands, validation and ASIO diagnostics.

View file

@ -86,7 +86,7 @@ host ownership and cleanup.
Reproduce focused verification with: Reproduce focused verification with:
```sh ```sh
make BUILD_DIR=build/audio-editor test-track-fx test-gui-track-fx cmake --build build/audio-editor --target test-track-fx test-gui-track-fx
XTC_TEST_HOST_VST3=1 ./build/audio-editor/test_track_fx XTC_TEST_HOST_VST3=1 ./build/audio-editor/test_track_fx
``` ```

View file

@ -9,20 +9,21 @@ implied.
## Build or Install ## Build or Install
Follow BUILD.md and LINUX.md. Core: `make all test`; GUI: `make gui`, then Configure first with `cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug`; see
BUILD.md and LINUX.md. Core: `cmake --build build --parallel && cmake --build build --target check`; GUI: `cmake --build build --target gui`, then
`./build/xtc`. Use an existing JACK-compatible server and reconnect with F6. `./build/xtc`. Use an existing JACK-compatible server and reconnect with F6.
Connect `midi_in` to a controller, `input` to mono capture and `output` / Connect `midi_in` to a controller, `input` to mono capture and `output` /
`output_right` to left/right playback using your JACK routing application. No `output_right` to left/right playback using your JACK routing application. No
server, physical routing or system configuration is changed automatically. server, physical routing or system configuration is changed automatically.
`xtc --version`, `xtc --help` and `xtc --diagnose` need no display/server. `xtc --version`, `xtc --help` and `xtc --diagnose` need no display/server.
For a source install stage with For a source installation, configure `-DCMAKE_INSTALL_PREFIX=/usr`, build, then
`make install-gui DESTDIR="$PWD/build/stage" PREFIX=/usr`, inspect the stage, stage with `DESTDIR="$PWD/build/stage" cmake --install build`. Inspect the stage
and use your normal deployment process. A user prefix is supported (for example and use your normal deployment process. A user prefix such as an absolute path
an absolute path to your `.local` folder). `make uninstall` with the same to `.local` is also supported. Use `cmake --build build --target uninstall`
DESTDIR/PREFIX removes only that version's known files. See LINUX.md for package with the same DESTDIR and configured prefix for exact removal. See LINUX.md for
extraction, dependencies and removal. Never use a release built for a different package extraction, dependencies and removal. Rebuild and test for the intended
system ABI without rebuilding/testing it. system ABI before deployment.
--- ---

View file

@ -86,7 +86,7 @@ With installed FLTK/JACK development files, Xvfb and an already running
JACK-compatible server: JACK-compatible server:
```sh ```sh
make all test gui test-gui test-workflow test-gui-jack test-jack-midi cmake -S . -B build -DXTC_ENABLE_LIVE_TESTS=ON && cmake --build build --parallel && cmake --build build --target check gui test-gui test-workflow test-gui-jack test-jack-midi
``` ```
`test-workflow` drives real GUI commands and timeline gestures under Xvfb. It `test-workflow` drives real GUI commands and timeline gestures under Xvfb. It
@ -148,7 +148,7 @@ A successful recorded take adds a reference as one undoable edit. Undo/removing
its region never deletes the WAV; recording failures retain their recovery path. its region never deletes the WAV; recording failures retain their recovery path.
There is no destructive media-delete operation. New/Open reset history after There is no destructive media-delete operation. New/Open reset history after
unsaved-change handling. The capacity limit and allocation-error reporting in unsaved-change handling. The capacity limit and allocation-error reporting in
MIXER.md still apply. Tests: `make test-editing test-gui-plugins test-workflow`. MIXER.md still apply. Tests: `cmake -S . -B build -DXTC_ENABLE_LIVE_TESTS=ON && cmake --build build --target test-editing test-gui-plugins test-workflow`.
--- ---

View file

@ -191,7 +191,7 @@ described below. The floating mixer resizes down to 600 × 420 and retains
horizontal track scrolling. horizontal track scrolling.
Verified with Verified with
`make BUILD_DIR=build/workspace-detach gui test-gui test-gui-instruments test-gui-jack` `cmake -S . -B build/workspace-detach -DXTC_ENABLE_LIVE_TESTS=ON && cmake --build build/workspace-detach --target gui test-gui test-gui-instruments test-gui-jack`
(one line), plus the GUI suite under AddressSanitizer/UBSan. Tests cover (one line), plus the GUI suite under AddressSanitizer/UBSan. Tests cover
repeated reparenting, close-to-dock, resize, shared BPM/loop state, playback repeated reparenting, close-to-dock, resize, shared BPM/loop state, playback
shortcuts, failed shutdown and cleanup. shortcuts, failed shutdown and cleanup.

View file

@ -1,11 +1,11 @@
# Examples # Examples
`native_gain.c` is a complete trusted native C effect compiled into an offline `native_gain.c` is a complete trusted native C effect compiled into an offline
graph. Build/run with `make build/native-gain && ./build/native-gain`; it needs graph. Build/run with `cmake --build build --target native-gain && ./build/native-gain`; it needs
no GUI/server/plugin SDK. See ../docs/NATIVE_API.md for lifecycle/event contracts. no GUI/server/plugin SDK. See ../docs/NATIVE_API.md for lifecycle/event contracts.
`quality_preview.c` uses public C MIDI, recorder and gain-ramp APIs to generate `quality_preview.c` uses public C MIDI, recorder and gain-ramp APIs to generate
matched raw/smoothed listening fixtures. Run `make quality-preview`; generated matched raw/smoothed listening fixtures. Run `cmake --build build --target quality-preview`; generated
WAVs remain in the ignored build directory. See docs/AUDIO_QUALITY.md for the WAVs remain in the ignored build directory. See docs/AUDIO_QUALITY.md for the
listening sequence and the distinction between automated checks and human listening sequence and the distinction between automated checks and human
listening acceptance. It never starts a backend or plays audio. listening acceptance. It never starts a backend or plays audio.

View file

@ -1,5 +1,6 @@
cmake_minimum_required(VERSION 3.25) cmake_minimum_required(VERSION 3.25)
project(xtc_juce_examples VERSION 0.1.0 LANGUAGES C CXX) project(xtc_juce_examples VERSION 0.1.0 LANGUAGES C CXX)
include(CTest)
set(XTC_JUCE_ROOT "" CACHE PATH "External JUCE source checkout") set(XTC_JUCE_ROOT "" CACHE PATH "External JUCE source checkout")
if(NOT EXISTS "${XTC_JUCE_ROOT}/modules/juce_audio_utils/juce_audio_utils.h") if(NOT EXISTS "${XTC_JUCE_ROOT}/modules/juce_audio_utils/juce_audio_utils.h")
message(FATAL_ERROR "Set XTC_JUCE_ROOT to an external JUCE checkout; normal xtc builds do not download JUCE") message(FATAL_ERROR "Set XTC_JUCE_ROOT to an external JUCE checkout; normal xtc builds do not download JUCE")
@ -11,21 +12,17 @@ message(STATUS "JUCE is AGPLv3/commercial: review the external SDK LICENSE.md be
set(JUCE_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) set(JUCE_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
set(JUCE_BUILD_EXTRAS OFF CACHE BOOL "" FORCE) set(JUCE_BUILD_EXTRAS OFF CACHE BOOL "" FORCE)
add_subdirectory("${XTC_JUCE_ROOT}" juce-sdk EXCLUDE_FROM_ALL) add_subdirectory("${XTC_JUCE_ROOT}" juce-sdk EXCLUDE_FROM_ALL)
find_program(XTC_MAKE_EXECUTABLE NAMES gmake make REQUIRED)
get_filename_component(XTC_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../.." ABSOLUTE) get_filename_component(XTC_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../.." ABSOLUTE)
set(XTC_CORE_BUILD "${CMAKE_CURRENT_BINARY_DIR}/c-core") if(NOT TARGET xtc-core)
add_custom_target(xtc-juce-core set(XTC_BUILD_GUI OFF CACHE BOOL "" FORCE)
COMMAND "${XTC_MAKE_EXECUTABLE}" -C "${XTC_ROOT}" set(XTC_WITH_JACK OFF CACHE BOOL "" FORCE)
"BUILD_DIR=${XTC_CORE_BUILD}" "CC=${CMAKE_C_COMPILER}" set(XTC_BUILD_JUCE OFF CACHE BOOL "" FORCE)
"CFLAGS=-O2 -g -fPIC -std=c99 -Wall -Wextra -Wpedantic -Wconversion -Wshadow -Iinclude" set(XTC_BUILD_STEINBERG OFF CACHE BOOL "" FORCE)
PKG_CONFIG=false FLTK_CONFIG=false "${XTC_CORE_BUILD}/libxtc.a" set(xtc_saved_testing ${BUILD_TESTING})
BYPRODUCTS "${XTC_CORE_BUILD}/libxtc.a" set(BUILD_TESTING OFF)
VERBATIM USES_TERMINAL) add_subdirectory("${XTC_ROOT}" c-core EXCLUDE_FROM_ALL)
add_library(xtc-juce-c-core STATIC IMPORTED) set(BUILD_TESTING ${xtc_saved_testing})
set_target_properties(xtc-juce-c-core PROPERTIES endif()
IMPORTED_LOCATION "${XTC_CORE_BUILD}/libxtc.a"
INTERFACE_INCLUDE_DIRECTORIES "${XTC_ROOT}/include")
add_dependencies(xtc-juce-c-core xtc-juce-core)
juce_add_plugin(XtcJuceSynth juce_add_plugin(XtcJuceSynth
COMPANY_NAME xtc COMPANY_NAME xtc
@ -42,12 +39,15 @@ juce_add_plugin(XtcJuceSynth
NEEDS_WEB_BROWSER FALSE NEEDS_WEB_BROWSER FALSE
NEEDS_CURL FALSE) NEEDS_CURL FALSE)
target_sources(XtcJuceSynth PRIVATE Processor.cpp Editor.cpp PluginEntry.cpp) target_sources(XtcJuceSynth PRIVATE Processor.cpp Editor.cpp PluginEntry.cpp)
# JUCE’s bundled FLAC uses POSIX fileno; its C module sources need GNU extensions.
# The xtc-core target remains strict C99.
set_target_properties(XtcJuceSynth PROPERTIES C_STANDARD 11 C_EXTENSIONS ON)
target_compile_features(XtcJuceSynth PUBLIC cxx_std_17) target_compile_features(XtcJuceSynth PUBLIC cxx_std_17)
target_compile_definitions(XtcJuceSynth PUBLIC target_compile_definitions(XtcJuceSynth PUBLIC
JUCE_WEB_BROWSER=0 JUCE_USE_CURL=0 JUCE_VST3_CAN_REPLACE_VST2=0 JUCE_PLUGINHOST_VST3=1 JUCE_WEB_BROWSER=0 JUCE_USE_CURL=0 JUCE_VST3_CAN_REPLACE_VST2=0 JUCE_PLUGINHOST_VST3=1
) )
target_include_directories(XtcJuceSynth PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}" "${XTC_ROOT}/include") target_include_directories(XtcJuceSynth PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}" "${XTC_ROOT}/include")
target_link_libraries(XtcJuceSynth PRIVATE xtc-juce-c-core juce::juce_audio_utils juce::juce_dsp target_link_libraries(XtcJuceSynth PRIVATE xtc-core juce::juce_audio_utils juce::juce_dsp
PUBLIC juce::juce_recommended_config_flags juce::juce_recommended_warning_flags) PUBLIC juce::juce_recommended_config_flags juce::juce_recommended_warning_flags)
add_executable(xtc-juce-render Render.cpp) add_executable(xtc-juce-render Render.cpp)
@ -57,8 +57,11 @@ target_include_directories(xtc-juce-render PRIVATE "${XTC_JUCE_ROOT}/modules")
# Exact float comparisons intentionally check bit-identical C bridge output. # Exact float comparisons intentionally check bit-identical C bridge output.
set_source_files_properties(Render.cpp PROPERTIES COMPILE_OPTIONS "-Wno-float-equal") set_source_files_properties(Render.cpp PROPERTIES COMPILE_OPTIONS "-Wno-float-equal")
target_compile_definitions(xtc-juce-render PRIVATE JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1) target_compile_definitions(xtc-juce-render PRIVATE JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1)
target_link_libraries(xtc-juce-render PRIVATE XtcJuceSynth xtc-juce-c-core) target_link_libraries(xtc-juce-render PRIVATE XtcJuceSynth xtc-core)
enable_testing() if(BUILD_TESTING)
add_test(NAME xtc-juce-processor COMMAND xtc-juce-render --self-test) find_program(XTC_XVFB_RUN xvfb-run REQUIRED)
add_test(NAME xtc-juce-processor COMMAND ${XTC_XVFB_RUN} -a $<TARGET_FILE:xtc-juce-render> --self-test)
add_test(NAME xtc-juce-vst3 COMMAND xtc-juce-render --host-check "$<TARGET_FILE_DIR:XtcJuceSynth_VST3>/../..") add_test(NAME xtc-juce-vst3 COMMAND ${XTC_XVFB_RUN} -a $<TARGET_FILE:xtc-juce-render>
--host-check "$<PATH:GET_PARENT_PATH,$<PATH:GET_PARENT_PATH,$<TARGET_FILE_DIR:XtcJuceSynth_VST3>>>")
set_tests_properties(xtc-juce-processor xtc-juce-vst3 PROPERTIES LABELS sdk)
endif()

View file

@ -10,27 +10,34 @@ No SDK or linked binary is copied into normal xtc packages.
## Build and test (Linux) ## Build and test (Linux)
Supply JUCE explicitly, for example in `/your/path/JUCE`. With CMake >= 3.25, Supply JUCE explicitly, for example in `/your/path/JUCE`. With CMake >= 3.25,
GNU Make, a C/C++17 toolchain and JUCE's Linux development prerequisites: Ninja or another CMake backend, a C/C++17 toolchain and JUCE's Linux development
prerequisites, configure from the repository root:
```sh ```sh
make JUCE_ROOT=/your/path/JUCE juce-examples cmake -S . -B build -DCMAKE_BUILD_TYPE=Release \
make JUCE_ROOT=/your/path/JUCE test-juce -DXTC_BUILD_JUCE=ON -DXTC_JUCE_ROOT=/your/path/JUCE
cmake --build build --target juce-examples --parallel 3
cmake --build build --target test-juce
``` ```
The build never downloads JUCE, installs dependencies or copies plugins into The build never downloads JUCE, installs dependencies or copies plugins into
user plugin directories. It builds its own C99 PIC archive with JACK/FLTK disabled user plugin directories. The bridge links the root `xtc-core` PIC C99 target;
under `build/juce/c-core`. `test-juce` also requires Xvfb and runs the processor there is no recursive Make build or duplicate core archive. `test-juce` requires
and JUCE-host checks, then loads the built instrument through xtc's C VST3 host. Xvfb and runs the processor, JUCE-host, file/editor and xtc C-host checks.
Normal `make`, `make gui` and `make test` remain JUCE-independent. Normal builds leave `XTC_BUILD_JUCE=OFF` and remain JUCE-independent.
Build products: Build products (Release):
- `build/juce/XtcJuceSynth_artefacts/Release/VST3/xtc JUCE Synth.vst3` - `build/juce/XtcJuceSynth_artefacts/Release/VST3/xtc JUCE Synth.vst3`
- `build/juce/XtcJuceSynth_artefacts/Release/Standalone/xtc JUCE Synth` - `build/juce/XtcJuceSynth_artefacts/Release/Standalone/xtc JUCE Synth`
- `build/juce/xtc-juce-render_artefacts/Release/xtc-juce-render` - `build/juce/xtc-juce-render_artefacts/Release/xtc-juce-render`
Set `BUILD_DIR=...` to move the main build root, or `JUCE_BUILD_DIR=...` to select Use CMake's `-B` argument to select another binary directory. Debug builds use
only the SDK example build directory. `Debug` in place of `Release`. The examples can also be configured directly with
`cmake -S examples/juce -B build/juce-only -DXTC_JUCE_ROOT=/your/path/JUCE
-DCMAKE_BUILD_TYPE=Release`; that standalone path adds the same root CMake core
with GUI/JACK/tests disabled rather than invoking Make. Its artefacts are directly
under `build/juce-only`, without the additional `juce` directory.
## Play and shape Soft Keys ## Play and shape Soft Keys
@ -84,7 +91,7 @@ VST3 host. These operations do not start an audio device.
## Regression checks and an actual editor image ## Regression checks and an actual editor image
```sh ```sh
xvfb-run -a ctest --test-dir build/juce --output-on-failure ctest --test-dir build --output-on-failure -R juce
xvfb-run -a ./build/juce/xtc-juce-render_artefacts/Release/xtc-juce-render --snapshot /absolute/new-editor.png xvfb-run -a ./build/juce/xtc-juce-render_artefacts/Release/xtc-juce-render --snapshot /absolute/new-editor.png
``` ```

View file

@ -16,6 +16,7 @@ if(XTC_VST3_SDK_ROOT)
set(SMTG_ENABLE_VST3_HOSTING_EXAMPLES ON CACHE BOOL "" FORCE) set(SMTG_ENABLE_VST3_HOSTING_EXAMPLES ON CACHE BOOL "" FORCE)
add_subdirectory("${XTC_VST3_SDK_ROOT}" sdk) add_subdirectory("${XTC_VST3_SDK_ROOT}" sdk)
set(public_sdk_SOURCE_DIR "${XTC_VST3_SDK_ROOT}/public.sdk") set(public_sdk_SOURCE_DIR "${XTC_VST3_SDK_ROOT}/public.sdk")
set(SMTG_CUSTOM_BINARY_LOCATION "${CMAKE_CURRENT_BINARY_DIR}")
smtg_add_vst3plugin(xtc-sdk-examples sdk_plugins.cpp) smtg_add_vst3plugin(xtc-sdk-examples sdk_plugins.cpp)
target_link_libraries(xtc-sdk-examples PRIVATE sdk) target_link_libraries(xtc-sdk-examples PRIVATE sdk)
target_compile_features(xtc-sdk-examples PRIVATE cxx_std_17) target_compile_features(xtc-sdk-examples PRIVATE cxx_std_17)

View file

@ -1,6 +1,6 @@
# Official Steinberg SDK examples # Official Steinberg SDK examples
These are optional integration examples. Normal `make` / `make test` uses the These are optional integration examples. Normal CMake builds and CTest use the
existing pinned C ABI headers and never downloads SDKs. Original example source existing pinned C ABI headers and never downloads SDKs. Original example source
is MIT-licensed like xtc. SDK code is compiled from an external checkout, not is MIT-licensed like xtc. SDK code is compiled from an external checkout, not
vendored or installed by these targets. C++ is confined to the SDK examples; vendored or installed by these targets. C++ is confined to the SDK examples;
@ -26,13 +26,17 @@ trademark distribution rights.
From the xtc repository root: From the xtc repository root:
```sh ```sh
make VST3_SDK_ROOT=/your/path/vst3sdk test-steinberg-sdk cmake -S . -B build -DCMAKE_BUILD_TYPE=Release \
-DXTC_BUILD_STEINBERG=ON -DXTC_VST3_SDK_ROOT=/your/path/vst3sdk
cmake --build build --parallel 3
ctest --test-dir build --output-on-failure -R steinberg
``` ```
This builds the original `xtc-sdk-examples.vst3` bundle using Steinberg's factory, This builds the original `xtc-sdk-examples.vst3` bundle using Steinberg's factory,
AudioEffect, controller and platform-entry implementations; runs the official AudioEffect, controller and platform-entry implementations; runs the official
validator; and runs xtc's gain/MIDI interoperability checks. Build output stays validator; and runs xtc's gain/MIDI interoperability checks. The plugin bundle stays
under `build/steinberg-sdk`; automatic plugin-directory links and installation under `build/steinberg-sdk`; SDK helper executables use `build/bin`. Automatic
plugin-directory links and installation
are disabled. SDK source may emit its own compiler warnings. are disabled. SDK source may emit its own compiler warnings.
The bundle contains: The bundle contains:
@ -56,9 +60,9 @@ xtc synth -> effect host path:
/absolute/path/to/new-sdk-demo.wav /absolute/path/to/new-sdk-demo.wav
``` ```
The destination must not exist. This executable is built by `test-steinberg-sdk`. The destination must not exist. This executable is built when the SDK option is enabled.
To use a different xtc build directory, pass `BUILD_DIR=...`; the SDK build then Use CMake’s `-B` argument to select another binary directory; the SDK build then
lives under that directory unless `STEINBERG_BUILD_DIR=...` is explicitly set. lives under its `steinberg-sdk` subdirectory.
The main app's existing plugin loader can use the bundle path through its C API; The main app's existing plugin loader can use the bundle path through its C API;
its file chooser may require selecting the inner `.so`. The effect loads as a its file chooser may require selecting the inner `.so`. The effect loads as a

View file

@ -56,10 +56,11 @@ The host is stopped and meters are idle. No ASIO interface is shown or implied.
See [Steinberg integration](../docs/STEINBERG.md) and See [Steinberg integration](../docs/STEINBERG.md) and
[JUCE integration](../docs/JUCE.md). [JUCE integration](../docs/JUCE.md).
After building both SDK examples, reproduce only these host captures with: After configuring the root CMake project and building both SDK examples,
reproduce only these host captures with:
```sh ```sh
make build/capture_screenshots cmake --build build --target capture_screenshots
XTC_CAPTURE_STEINBERG_BUNDLE="$PWD/build/steinberg-sdk/VST3/Release/xtc-sdk-examples.vst3" \ XTC_CAPTURE_STEINBERG_BUNDLE="$PWD/build/steinberg-sdk/VST3/Release/xtc-sdk-examples.vst3" \
XTC_CAPTURE_JUCE_BUNDLE="$PWD/build/juce/XtcJuceSynth_artefacts/Release/VST3/xtc JUCE Synth.vst3" \ XTC_CAPTURE_JUCE_BUNDLE="$PWD/build/juce/XtcJuceSynth_artefacts/Release/VST3/xtc JUCE Synth.vst3" \
xvfb-run -a -s '-screen 0 1440x960x24' \ xvfb-run -a -s '-screen 0 1440x960x24' \
@ -91,14 +92,15 @@ mean no automatic connection. Actual device choices depend on host JACK/PipeWire
exposure. Configuration paths are disabled in captures to preserve user settings. exposure. Configuration paths are disabled in captures to preserve user settings.
See [MIDI workflows and limits](../docs/MIDI_WORKFLOWS.md). See [MIDI workflows and limits](../docs/MIDI_WORKFLOWS.md).
Reproduce these five captures with After configuring `build/audio-editor` with the root CMake project,
`XTC_CAPTURE_MIDI_ONLY=1 make BUILD_DIR=build/audio-editor screenshots`. reproduce these five captures with
`XTC_CAPTURE_MIDI_ONLY=1 cmake --build build/audio-editor --target screenshots`.
The open input dropdown was captured after the device-label lifetime fix. It The open input dropdown was captured after the device-label lifetime fix. It
shows actual host sources, including UMC204HD, USB MIDI, Oxygen Pro 25 and BLE shows actual host sources, including UMC204HD, USB MIDI, Oxygen Pro 25 and BLE
MIDI ports. No route is selected and user preferences are preserved. Reproduce MIDI ports. No route is selected and user preferences are preserved. Reproduce
just this image with real MIDI sources exposed by JACK/PipeWire: just this image with real MIDI sources exposed by JACK/PipeWire:
`XTC_CAPTURE_MIDI_INPUT_ONLY=1 make BUILD_DIR=build/audio-editor screenshots`. `XTC_CAPTURE_MIDI_INPUT_ONLY=1 cmake --build build/audio-editor --target screenshots`.
The capture fails explicitly if the host exposes no input sources. The capture fails explicitly if the host exposes no input sources.
## Creative workflows (Phases 90–99) ## Creative workflows (Phases 90–99)
@ -112,7 +114,7 @@ creative controls; the rest of the gallery also reflects the current UI.
- [Strummed chord notes in the piano roll](creative-strum-result.png) - [Strummed chord notes in the piano roll](creative-strum-result.png)
Reproduce only these captures with Reproduce only these captures with
`XTC_CAPTURE_CREATIVE_ONLY=1 make BUILD_DIR=build/audio-editor screenshots`. `XTC_CAPTURE_CREATIVE_ONLY=1 cmake --build build/audio-editor --target screenshots`.
The rack shows the actual template and idle meter; no live peak readings are The rack shows the actual template and idle meter; no live peak readings are
fabricated. See [creative workflow limits](../docs/CREATIVE_WORKFLOWS.md). fabricated. See [creative workflow limits](../docs/CREATIVE_WORKFLOWS.md).
@ -219,7 +221,8 @@ to return it to the main window.
From the repository root, run: From the repository root, run:
```sh ```sh
make BUILD_DIR=build/next-daw screenshots cmake -S . -B build/next-daw -DCMAKE_BUILD_TYPE=Debug
cmake --build build/next-daw --target screenshots
``` ```
This optional documentation target requires the existing FLTK/JACK build This optional documentation target requires the existing FLTK/JACK build
@ -230,7 +233,7 @@ PNGs. All demonstration edits and collection outputs use an automatically remove
temporary directory; no preferences are saved. It neither installs dependencies temporary directory; no preferences are saved. It neither installs dependencies
nor starts/reconfigures the audio server. nor starts/reconfigures the audio server.
Set `XTC_CAPTURE_SAMPLE_ONLY=1` for the capture tool (or the make target) to write Set `XTC_CAPTURE_SAMPLE_ONLY=1` for the capture tool (or the CMake target) to write
only the two sample-editor images while retaining the existing gallery. only the two sample-editor images while retaining the existing gallery.
@ -241,6 +244,6 @@ XTC_CAPTURE_HOST_VST3=1 xvfb-run -a -s '-screen 0 1440x960x24' \
./build/audio-editor/capture_screenshots "$PWD/screenshots" ./build/audio-editor/capture_screenshots "$PWD/screenshots"
``` ```
Run `make BUILD_DIR=build/audio-editor screenshots` first to build the tool and Run `cmake --build build/audio-editor --target screenshots` first to build the tool and
refresh the full fixture/demo gallery. All captures use temporary projects and refresh the full fixture/demo gallery. All captures use temporary projects and
preserve user settings and original media. preserve user settings and original media.

View file

@ -1,12 +1,20 @@
# Integration tests # Integration tests
`make test` is hardware/server independent: offline render tests, concurrent Configure the root project first with `cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug`,
then `cmake --build build --parallel`. Run `ctest --test-dir build
--output-on-failure` for registered tests. Live tests require a separate configure
with `-DXTC_ENABLE_LIVE_TESTS=ON` and an existing JACK server; CTest serializes them.
See [BUILD.md](../../docs/BUILD.md) for presets and feature options. Older validation
records below describe tests performed at that time; commands use the CMake
workflow and assume the relevant configuration has already completed.
`cmake --build build --target check` is hardware/server independent: offline render tests, concurrent
plan-replacement stress, and (when JACK headers are present) JACK ABI fault plan-replacement stress, and (when JACK headers are present) JACK ABI fault
injection. The fake links against installed declarations without linking injection. The fake exports JACK entry points against the installed declarations;
libjack. It does not claim to reproduce JACK scheduling; the separate the CMake core target carries the optional JACK link dependency. It does not claim to reproduce JACK scheduling; the separate
threaded stress test exercises publication/reclamation. threaded stress test exercises publication/reclamation.
`make test-jack` explicitly runs a five-second live JACK loopback test. Requires `cmake -S . -B build -DXTC_ENABLE_LIVE_TESTS=ON && cmake --build build --target test-jack` explicitly runs a five-second live JACK loopback test. Requires
an existing JACK or PipeWire JACK-compatible server; does not start a server, an existing JACK or PipeWire JACK-compatible server; does not start a server,
change its sample rate/block size, or connect physical outputs. Source output change its sample rate/block size, or connect physical outputs. Source output
is connected only to the test's capture client, then disconnected and removed. is connected only to the test's capture client, then disconnected and removed.
@ -14,7 +22,7 @@ It checks finite/bounded captured samples at two gains, active plan replacement,
status and ten restart cycles. For a longer run: status and ten restart cycles. For a longer run:
```sh ```sh
make build/test_jack cmake --build build --target test_jack
./build/test_jack 30 ./build/test_jack 30
``` ```
@ -30,9 +38,9 @@ JACK callbacks/results in `tests/unit/test_backend.c`.
## FLTK shell tests ## FLTK shell tests
```sh ```sh
make gui test-gui cmake --build build --target gui test-gui
# Optional, requires a running JACK-compatible server: # Optional, requires a running JACK-compatible server:
make test-gui-jack cmake -S . -B build -DXTC_ENABLE_LIVE_TESTS=ON && cmake --build build --target test-gui-jack
``` ```
These targets require FLTK development files, `xvfb-run` and Xvfb. No additional These targets require FLTK development files, `xvfb-run` and Xvfb. No additional
@ -58,15 +66,15 @@ the third-party font stack is leak-free. Apply only to GUI sanitizer tests:
LSAN_OPTIONS=suppressions=tests/integration/lsan.supp xvfb-run -a -s '-screen 0 1280x800x24' ./build/gui-sanitize/test_gui LSAN_OPTIONS=suppressions=tests/integration/lsan.supp xvfb-run -a -s '-screen 0 1280x800x24' ./build/gui-sanitize/test_gui
``` ```
Build that binary with Clang/Clang++, `-fsanitize=address,undefined` in both Build that binary in a separate CMake directory with
CFLAGS/CXXFLAGS and LDFLAGS, using the baseline warning/include/standard flags `-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DXTC_SANITIZE=ON`.
from BUILD.md. Keep FLTK's `-I` and `-D` flags from `fltk-config --cxxflags`, CMake propagates sanitizer compile/link options and filters FLTK discovery flags
but exclude the distribution's GCC-specific LTO/object-code flags for Clang. so they do not override the chosen compiler or optimization settings.
No suppressions are used for core/backend/realtime sanitizer tests. No suppressions are used for core/backend/realtime sanitizer tests.
## Phase 4 graph editing coverage ## Phase 4 graph editing coverage
The same `make test-gui` target now adds nodes through toolbar callbacks, The same `cmake --build build --target test-gui` target now adds nodes through toolbar callbacks,
connects the exact rendered graph, validates expected oscillator/gain samples connects the exact rendered graph, validates expected oscillator/gain samples
in the submitted backend plan, checks cycle/direction/duplicate rejection, in the submitted backend plan, checks cycle/direction/duplicate rejection,
checks backend I/O/pending-capacity rollback, deletes wires and incident edges, checks backend I/O/pending-capacity rollback, deletes wires and incident edges,
@ -74,7 +82,7 @@ and verifies stable IDs, preserved positions, prepared/queued/active feedback
and empty-graph silence. The fake backend compiles submitted C snapshots rather and empty-graph silence. The fake backend compiles submitted C snapshots rather
than accepting an unrelated GUI model. Move-only actions do not submit plans. than accepting an unrelated GUI model. Move-only actions do not submit plans.
`make test-gui-jack` exercises the same widget against real JACK: it builds a `cmake -S . -B build -DXTC_ENABLE_LIVE_TESTS=ON && cmake --build build --target test-gui-jack` exercises the same widget against real JACK: it builds a
graph while stopped, starts it, adds/deletes nodes during active processing, graph while stopped, starts it, adds/deletes nodes during active processing,
waits for revision acknowledgement, and rejects cycles without changing the waits for revision acknowledgement, and rejects cycles without changing the
model. Start/stop/reconnect and callback-safe active close remain covered. No model. Start/stop/reconnect and callback-safe active close remain covered. No
@ -99,14 +107,14 @@ A sample-status update drives the cursor to an exact tick; a graph edit retains
that position. test_gui_jack checks actual playhead advancement and a stable that position. test_gui_jack checks actual playhead advancement and a stable
paused cursor across repeated starts/stops. No routes are created. paused cursor across repeated starts/stops. No routes are created.
test_project additionally covers serialization, exact graph samples/IDs, test_project additionally covers serialization, exact graph samples/IDs,
truncation/mutation input, capacity, save failures and cleanup (part of make test). truncation/mutation input, capacity, save failures and cleanup (part of CTest).
The core/backend tests remain unsuppressed under ASan/UBSan; GUI checks retain The core/backend tests remain unsuppressed under ASan/UBSan; GUI checks retain
the narrowly scoped fontconfig-only suppression described above. the narrowly scoped fontconfig-only suppression described above.
## Phase 6 audio acceptance ## Phase 6 audio acceptance
make test-jack-audio requires a running JACK-compatible server. It creates only cmake -S . -B build -DXTC_ENABLE_LIVE_TESTS=ON && cmake --build build --target test-jack-audio requires a running JACK-compatible server. It creates only
software routes between its probe and xtc backend: known 0.25 source, input software routes between its probe and xtc backend: known 0.25 source, input
capture through the recorder ring, WAV finalization, project save/reopen, capture through the recorder ring, WAV finalization, project save/reopen,
then clip source graph playback observed at the probe input. All test ports, then clip source graph playback observed at the probe input. All test ports,
@ -117,14 +125,14 @@ test_gui covers record/Stop, exact take playback, saving/reopening, failed impor
missing assets, and blocked edits/file actions/close while recording. The live missing assets, and blocked edits/file actions/close while recording. The live
test_gui_jack records a silent take, finalizes and saves/reopens it, then starts test_gui_jack records a silent take, finalizes and saves/reopens it, then starts
playback before callback-safe close. Decoder and producer/consumer concurrency playback before callback-safe close. Decoder and producer/consumer concurrency
checks are part of make test; run test_audio and test_realtime under TSan as in checks are part of CTest; run test_audio and test_realtime under TSan as in
BUILD.md. Physical input/output and latency compensation are not tested. BUILD.md. Physical input/output and latency compensation are not tested.
## Phase 7 verification ## Phase 7 verification
```sh ```sh
make all test gui debug test-gui test-gui-jack test-jack-audio test-jack-midi cmake -S . -B build -DXTC_ENABLE_LIVE_TESTS=ON && cmake --build build --parallel && cmake --build build --target check gui debug test-gui test-gui-jack test-jack-audio test-jack-midi
make BUILD_DIR=build/nojack PKG_CONFIG=false all test gui test-gui cmake -S . -B build/nojack -DXTC_WITH_JACK=OFF && cmake --build build/nojack --parallel && cmake --build build/nojack --target check gui test-gui
``` ```
`test_midi` adds exact sample scheduling, trim/seek/panic, block independence, `test_midi` adds exact sample scheduling, trim/seek/panic, block independence,
@ -139,8 +147,8 @@ or system changes are made. MIDI recording/SMF and physical devices are deferred
## Phase 8 verification ## Phase 8 verification
```sh ```sh
make all test gui debug test-gui test-gui-jack test-jack-audio test-jack-midi cmake -S . -B build -DXTC_ENABLE_LIVE_TESTS=ON && cmake --build build --parallel && cmake --build build --target check gui debug test-gui test-gui-jack test-jack-audio test-jack-midi
make BUILD_DIR=build/nojack PKG_CONFIG=false all test gui test-gui cmake -S . -B build/nojack -DXTC_WITH_JACK=OFF && cmake --build build/nojack --parallel && cmake --build build/nojack --target check gui test-gui
``` ```
`test_mixer` covers stereo gain/pan, mute/solo, sample automation, block `test_mixer` covers stereo gain/pan, mute/solo, sample automation, block
@ -160,9 +168,9 @@ long-running stereo performance remain unverified.
## Phase 9 release preparation ## Phase 9 release preparation
```sh ```sh
make all test gui debug test-gui test-gui-jack test-jack test-jack-audio test-jack-midi cmake -S . -B build -DXTC_ENABLE_LIVE_TESTS=ON && cmake --build build --parallel && cmake --build build --target check gui debug test-gui test-gui-jack test-jack test-jack-audio test-jack-midi
make test-install test-recovery test-performance cmake --build build --target test-install test-recovery test-performance
make test-release cmake --build build --target test-release
``` ```
Core test now includes native ABI/lifecycle/retirement and the native-gain example. Core test now includes native ABI/lifecycle/retirement and the native-gain example.
@ -185,9 +193,9 @@ and their repacked source archives were byte-identical on this toolchain.
## Phase 10 reliability audit ## Phase 10 reliability audit
Run `make test-reliability` for the core suites, allocation-failure sweep, Run `cmake --build build --target test-reliability` for the core suites, allocation-failure sweep,
callback allocation/free guard and native reclamation checks. Run callback allocation/free guard and native reclamation checks. Run
`make test-sanitize` for Clang ASan/UBSan/leak checks and `make analyze` for `cmake --build build --target test-sanitize` for Clang ASan/UBSan/leak checks and `cmake --build build --target analyze` for
Clang static analysis of all core sources. These need installed development Clang static analysis of all core sources. These need installed development
tools; the allocation guard uses a GNU-compatible linker. See tools; the allocation guard uses a GNU-compatible linker. See
[RELIABILITY.md](../../docs/RELIABILITY.md) for ownership, audit findings, [RELIABILITY.md](../../docs/RELIABILITY.md) for ownership, audit findings,
@ -195,7 +203,7 @@ full verification commands and remaining limits.
## Phase 11 composition workflow ## Phase 11 composition workflow
`make test-workflow` builds and runs `test_workflow.cpp` under Xvfb with the real `cmake -S . -B build -DXTC_ENABLE_LIVE_TESTS=ON && cmake --build build --target test-workflow` builds and runs `test_workflow.cpp` under Xvfb with the real
JACK-compatible backend. It requires an already running server. It creates and JACK-compatible backend. It requires an already running server. It creates and
arranges MIDI and imported audio through GUI commands, saves/closes/destroys the arranges MIDI and imported audio through GUI commands, saves/closes/destroys the
window, reopens in another window, compares stereo rendering exactly, verifies window, reopens in another window, compares stereo rendering exactly, verifies

View file

@ -27,20 +27,39 @@ def main():
sentinel = root / 'lib/keep-unrelated.txt' sentinel = root / 'lib/keep-unrelated.txt'
sentinel.parent.mkdir(parents=True) sentinel.parent.mkdir(parents=True)
sentinel.write_text('keep\n') sentinel.write_text('keep\n')
make = shlex.split(os.environ.get('MAKE', 'make')) # Generate the real CMake install driver against the already-built inputs.
# MAKEFLAGS jobserver descriptors do not cross Python subprocesses. # The temporary cache keeps the caller's build configuration untouched.
env = dict(os.environ, MAKEFLAGS='', MFLAGS='') installer = stage / 'cmake-driver'
variables = [f'BUILD_DIR={args.build_dir}', f'PREFIX={prefix}', f'DESTDIR={stage}'] cache = (args.build_dir / 'CMakeCache.txt').read_text()
generator = next(line.partition('=')[2] for line in cache.splitlines()
if line.startswith('CMAKE_GENERATOR:INTERNAL='))
jack = 'OFF' if 'XTC_WITH_JACK:BOOL=OFF' in cache else 'ON'
env = dict(os.environ)
variables = ['-DBUILD_TESTING=OFF', f'-DXTC_BUILD_GUI={"ON" if args.gui else "OFF"}',
f'-DCMAKE_INSTALL_PREFIX={prefix}',
f'-DXTC_INSTALL_BUILD_DIR={args.build_dir.resolve()}',
f'-DXTC_WITH_JACK={jack}', '-DXTC_INSTALL_GUI=OFF', '-DCMAKE_INSTALL_DOCDIR=share/doc/xtc']
if args.custom_layout: if args.custom_layout:
variables += [f'exec_prefix={prefix}/runtime', f'libdir={prefix}/lib64', variables += ['-DCMAKE_INSTALL_BINDIR=runtime/bin', '-DCMAKE_INSTALL_LIBDIR=lib64',
f'includedir={prefix}/headers', f'datarootdir={prefix}/resources', '-DCMAKE_INSTALL_INCLUDEDIR=headers', '-DCMAKE_INSTALL_DATAROOTDIR=resources',
f'docdir={prefix}/manual', f'pkgconfigdir={prefix}/metadata'] '-DCMAKE_INSTALL_DOCDIR=manual', '-DXTC_INSTALL_PKGCONFIGDIR=metadata']
def install_command(operation, options, **kwargs):
config = subprocess.run(['cmake', '-G', generator, '-S', str(source), '-B', str(installer),
*options], env=env, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, text=True)
if config.returncode:
raise RuntimeError(config.stdout)
command = (['cmake', '--build', str(installer), '--target', 'uninstall']
if operation == 'uninstall' else ['cmake', '--install', str(installer)])
return subprocess.run(command, env=dict(env, DESTDIR=str(stage)), **kwargs)
binaries = root / ('runtime/bin' if args.custom_layout else 'bin') binaries = root / ('runtime/bin' if args.custom_layout else 'bin')
headers = root / ('headers' if args.custom_layout else 'include') headers = root / ('headers' if args.custom_layout else 'include')
resources = root / ('resources' if args.custom_layout else 'share') resources = root / ('resources' if args.custom_layout else 'share')
docs = root / ('manual' if args.custom_layout else 'share/doc/xtc') docs = root / ('manual' if args.custom_layout else 'share/doc/xtc')
metadata = root / ('metadata' if args.custom_layout else 'lib/pkgconfig') metadata = root / ('metadata' if args.custom_layout else 'lib/pkgconfig')
run(make + ['install'] + variables, env=env) install_command('install', variables, check=True)
assert not (binaries / 'xtc').exists() assert not (binaries / 'xtc').exists()
assert (resources / 'licenses/xtc/LICENSE').read_bytes() == (source / 'LICENSE').read_bytes() assert (resources / 'licenses/xtc/LICENSE').read_bytes() == (source / 'LICENSE').read_bytes()
assert (docs / 'PROJECT_FORMAT.md').is_file() assert (docs / 'PROJECT_FORMAT.md').is_file()
@ -64,7 +83,7 @@ def main():
(binaries / 'xtc-check').symlink_to(protected) (binaries / 'xtc-check').symlink_to(protected)
# Upgrade/repair repeats the same owned-file install and keeps unrelated data. # Upgrade/repair repeats the same owned-file install and keeps unrelated data.
(headers / 'xtc/xtc.h').write_text('old installation marker\n') (headers / 'xtc/xtc.h').write_text('old installation marker\n')
run(make + ['install'] + variables, env=env) install_command('install', variables, check=True)
assert (headers / 'xtc/xtc.h').read_bytes() == (source / 'include/xtc/xtc.h').read_bytes() assert (headers / 'xtc/xtc.h').read_bytes() == (source / 'include/xtc/xtc.h').read_bytes()
assert sentinel.read_text() == 'keep\n' assert sentinel.read_text() == 'keep\n'
assert protected.read_text() == 'keep unrelated target\n' assert protected.read_text() == 'keep unrelated target\n'
@ -79,8 +98,7 @@ def main():
pkg_env = dict(env, PKG_CONFIG_PATH=str(metadata) + ':' + env.get('PKG_CONFIG_PATH', ''), pkg_env = dict(env, PKG_CONFIG_PATH=str(metadata) + ':' + env.get('PKG_CONFIG_PATH', ''),
PKG_CONFIG_SYSROOT_DIR=str(stage)) PKG_CONFIG_SYSROOT_DIR=str(stage))
pkg = shlex.split(os.environ.get('PKG_CONFIG', 'pkg-config')) pkg = shlex.split(os.environ.get('PKG_CONFIG', 'pkg-config'))
# A no-JACK test can be invoked with PKG_CONFIG=false; package consumer # The build may disable JACK; consumer discovery still uses pkg-config.
# discovery itself still uses the installed pkg-config tool.
if pkg == ['false']: if pkg == ['false']:
pkg = ['pkg-config'] pkg = ['pkg-config']
flags = shlex.split(subprocess.check_output(pkg + ['--cflags', '--libs', '--static', 'xtc'], env=pkg_env, text=True)) flags = shlex.split(subprocess.check_output(pkg + ['--cflags', '--libs', '--static', 'xtc'], env=pkg_env, text=True))
@ -110,7 +128,7 @@ int main(int argc, char **argv) {
run(command + [f'-std={standard}', '-Wall', '-Wextra', '-Werror', str(consumer)] + flags + ['-o', str(output)]) run(command + [f'-std={standard}', '-Wall', '-Wextra', '-Werror', str(consumer)] + flags + ['-o', str(output)])
run([str(output), str(stage / ("installed-" + compiler + ".xtc"))]) run([str(output), str(stage / ("installed-" + compiler + ".xtc"))])
if args.gui: if args.gui:
run(make + ['install-gui'] + variables, env=env) install_command('install', variables + ['-DXTC_INSTALL_GUI=ON'], check=True)
gui_env = dict(env, DISPLAY='', WAYLAND_DISPLAY='') gui_env = dict(env, DISPLAY='', WAYLAND_DISPLAY='')
assert subprocess.check_output([str(binaries / 'xtc'), '--version'], env=gui_env, text=True) == version assert subprocess.check_output([str(binaries / 'xtc'), '--version'], env=gui_env, text=True) == version
run([str(binaries / 'xtc'), '--help'], env=gui_env) run([str(binaries / 'xtc'), '--help'], env=gui_env)
@ -120,9 +138,9 @@ int main(int argc, char **argv) {
with tempfile.TemporaryDirectory(prefix='xtc-install-outside-') as outside: with tempfile.TemporaryDirectory(prefix='xtc-install-outside-') as outside:
escape = root / 'escape' escape = root / 'escape'
escape.symlink_to(outside, target_is_directory=True) escape.symlink_to(outside, target_is_directory=True)
redirected = variables + [f'docdir={prefix}/escape/docs'] redirected = variables + [f'-DCMAKE_INSTALL_DOCDIR={prefix}/escape/docs']
for operation in ('install', 'uninstall'): for operation in ('install', 'uninstall'):
refusal = subprocess.run(make + [operation] + redirected, env=env, refusal = install_command(operation, redirected,
stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout=subprocess.PIPE, stderr=subprocess.PIPE)
assert refusal.returncode != 0 and not list(Path(outside).iterdir()) assert refusal.returncode != 0 and not list(Path(outside).iterdir())
assert (headers / 'xtc/xtc.h').is_file() assert (headers / 'xtc/xtc.h').is_file()
@ -131,13 +149,13 @@ int main(int argc, char **argv) {
saved_binary = stage / 'saved-check' saved_binary = stage / 'saved-check'
(binaries / 'xtc-check').rename(saved_binary) (binaries / 'xtc-check').rename(saved_binary)
(binaries / 'xtc-check').mkdir() (binaries / 'xtc-check').mkdir()
refusal = subprocess.run(make + ['uninstall'] + variables, env=env, refusal = install_command('uninstall', variables + ['-DCMAKE_INSTALL_DOCDIR=manual' if args.custom_layout else '-DCMAKE_INSTALL_DOCDIR=share/doc/xtc'],
stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout=subprocess.PIPE, stderr=subprocess.PIPE)
assert refusal.returncode != 0 and (headers / 'xtc/xtc.h').is_file() assert refusal.returncode != 0 and (headers / 'xtc/xtc.h').is_file()
(binaries / 'xtc-check').rmdir() (binaries / 'xtc-check').rmdir()
saved_binary.rename(binaries / 'xtc-check') saved_binary.rename(binaries / 'xtc-check')
run(make + ['uninstall'] + variables, env=env) install_command('uninstall', variables, check=True)
run(make + ['uninstall'] + variables, env=env) install_command('uninstall', variables, check=True)
remaining = sorted(p.relative_to(root).as_posix() for p in root.rglob('*') if p.is_file() or p.is_symlink()) remaining = sorted(p.relative_to(root).as_posix() for p in root.rglob('*') if p.is_file() or p.is_symlink())
assert remaining == ['lib/keep-unrelated.txt'], remaining assert remaining == ['lib/keep-unrelated.txt'], remaining
assert sentinel.read_text() == 'keep\n' assert sentinel.read_text() == 'keep\n'

View file

@ -4,7 +4,6 @@ import argparse
import hashlib import hashlib
import os import os
from pathlib import Path from pathlib import Path
import shlex
import subprocess import subprocess
import tarfile import tarfile
import tempfile import tempfile
@ -18,6 +17,7 @@ def main():
parser = argparse.ArgumentParser(description=__doc__) parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('archive', type=Path) parser.add_argument('archive', type=Path)
parser.add_argument('--gui', action='store_true') parser.add_argument('--gui', action='store_true')
parser.add_argument('--generator', default=os.environ.get('CMAKE_GENERATOR'))
args = parser.parse_args() args = parser.parse_args()
archive = args.archive.resolve() archive = args.archive.resolve()
assert archive.with_name(archive.name + '.sha256').read_text().split()[0] == digest(archive) assert archive.with_name(archive.name + '.sha256').read_text().split()[0] == digest(archive)
@ -33,16 +33,23 @@ def main():
assert all('__pycache__' not in Path(n).parts and '.git' not in Path(n).parts for n in names) assert all('__pycache__' not in Path(n).parts and '.git' not in Path(n).parts for n in names)
for name in ('first', 'different-path'): for name in ('first', 'different-path'):
source.extractall(root / name, filter='data') source.extractall(root / name, filter='data')
env = dict(os.environ, SOURCE_DATE_EPOCH=str(epoch), MAKEFLAGS='', MFLAGS='') env = dict(os.environ, SOURCE_DATE_EPOCH=str(epoch))
if args.generator:
env['CMAKE_GENERATOR'] = args.generator
builds = [] builds = []
for name in ('first', 'different-path'): for name in ('first', 'different-path'):
project = root / name / top project = root / name / top
log = root / (name + '.log') log = root / (name + '.log')
make = shlex.split(env.get('MAKE', 'make'))
with log.open('w') as output: with log.open('w') as output:
try: try:
subprocess.run(make + ['BUILD_DIR=build', 'release'], cwd=project, env=env, stdout=output, subprocess.run(['cmake', '-S', '.', '-B', 'build/release',
stderr=subprocess.STDOUT, check=True) '-DCMAKE_BUILD_TYPE=Release', '-DXTC_REPRODUCIBLE=ON',
f'-DXTC_BUILD_GUI={"ON" if args.gui else "OFF"}'],
cwd=project, env=env, stdout=output, stderr=subprocess.STDOUT, check=True)
subprocess.run(['cmake', '--build', 'build/release', '--parallel', '3'],
cwd=project, env=env, stdout=output, stderr=subprocess.STDOUT, check=True)
subprocess.run(['ctest', '--test-dir', 'build/release', '--output-on-failure'],
cwd=project, env=env, stdout=output, stderr=subprocess.STDOUT, check=True)
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
print(log.read_text()[-12000:]) print(log.read_text()[-12000:])
raise raise

21
third_party/README.md vendored
View file

@ -29,3 +29,24 @@ vendored headers. The inspected JUCE 9.0.4 revision is AGPLv3/commercial; a
commercial licence may be required. Original example source does not relicense commercial licence may be required. Original example source does not relicense
JUCE or its dependencies. JUCE-linked outputs stay outside normal install and JUCE or its dependencies. JUCE-linked outputs stay outside normal install and
package targets. See [JUCE dependency/license details](../docs/JUCE.md). package targets. See [JUCE dependency/license details](../docs/JUCE.md).
## CMake integration
The root CMake target `xtc-core` uses the pinned CLAP/VST3 directories as private
system include paths. No download step or SDK runtime is required. Configure
optional full SDK builds explicitly:
```sh
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release \
-DXTC_BUILD_STEINBERG=ON -DXTC_VST3_SDK_ROOT=/path/to/vst3sdk \
-DXTC_BUILD_JUCE=ON -DXTC_JUCE_ROOT=/path/to/JUCE
cmake --build build --parallel 3
ctest --test-dir build --output-on-failure -R 'steinberg|juce'
```
Leave both options off for normal builds. ASIO's native Windows diagnostic
retains its separate CMake configuration described in the Steinberg examples;
the Linux core is not a Windows/ASIO backend. See [BUILD.md](../docs/BUILD.md)
for target options and [RELEASING.md](../docs/RELEASING.md) for staging. The
owned install inventory retains the CLAP and VST3 C-header license notices;
JUCE/official SDK plugin binaries are excluded from normal install/package targets.

View file

@ -6,7 +6,7 @@ import subprocess
import sys import sys
if not shutil.which("clang"): if not shutil.which("clang"):
sys.exit("clang is required for make analyze") sys.exit("clang is required for the CMake analyze target")
jack = subprocess.run(["pkg-config", "--cflags", "jack"], capture_output=True, text=True) if shutil.which("pkg-config") else None jack = subprocess.run(["pkg-config", "--cflags", "jack"], capture_output=True, text=True) if shutil.which("pkg-config") else None
for source in sys.argv[1:]: for source in sys.argv[1:]:
flags = [] flags = []

View file

@ -9,7 +9,6 @@ import os
from pathlib import Path from pathlib import Path
import platform import platform
import re import re
import shlex
import subprocess import subprocess
import tarfile import tarfile
import tempfile import tempfile
@ -29,9 +28,23 @@ def main():
a.output.parent.mkdir(parents=True, exist_ok=True) a.output.parent.mkdir(parents=True, exist_ok=True)
with tempfile.TemporaryDirectory(prefix='xtc-binary-stage-') as temporary: with tempfile.TemporaryDirectory(prefix='xtc-binary-stage-') as temporary:
stage = Path(temporary) stage = Path(temporary)
env = dict(os.environ, MAKEFLAGS='', MFLAGS='') env = dict(os.environ)
subprocess.run(shlex.split(env.get('MAKE', 'make')) + ['install-gui' if a.gui else 'install', driver = stage / 'cmake-driver'
f'BUILD_DIR={a.build_dir.resolve()}', f'DESTDIR={stage}', 'PREFIX=/usr'], env=env, check=True) cache = (a.build_dir / 'CMakeCache.txt').read_text()
generator = next(line.partition('=')[2] for line in cache.splitlines()
if line.startswith('CMAKE_GENERATOR:INTERNAL='))
jack = 'OFF' if 'XTC_WITH_JACK:BOOL=OFF' in cache else 'ON'
subprocess.run(['cmake', '-G', generator, '-S', str(Path.cwd()), '-B', str(driver),
'-DBUILD_TESTING=OFF', '-DCMAKE_INSTALL_PREFIX=/usr', '-DCMAKE_INSTALL_LIBDIR=lib',
f'-DXTC_BUILD_GUI={"ON" if a.gui else "OFF"}',
'-DXTC_INSTALL_PKGCONFIGDIR=lib/pkgconfig',
f'-DXTC_INSTALL_BUILD_DIR={a.build_dir.resolve()}',
f'-DXTC_WITH_JACK={jack}', f'-DXTC_INSTALL_GUI={"ON" if a.gui else "OFF"}'],
env=env, check=True)
subprocess.run(['cmake', '--install', str(driver)], env=dict(env, DESTDIR=str(stage)), check=True)
# Only the installed /usr tree belongs to the archive.
import shutil
shutil.rmtree(driver)
pc = stage / 'usr/lib/pkgconfig/xtc.pc' pc = stage / 'usr/lib/pkgconfig/xtc.pc'
pc.write_text(pc.read_text().replace('prefix=/usr\n', 'prefix=${pcfiledir}/../..\n')) pc.write_text(pc.read_text().replace('prefix=/usr\n', 'prefix=${pcfiledir}/../..\n'))
entries = [] entries = []