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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ its phases, and the audit findings behind them. Tick its items (with the PR) as
signatures) that `processor.h` builds its descriptions from), `value.h` (the Max-atom value model and its coercion
rules, which reproduce `atom_getlong`/`atom_getfloat`/`atom_getsym`), `processor.h` (load/reload,
class introspection, attribute and message dispatch, `prepare()`, and `process()` — per sample, or
per vector when the input is hinted `np.ndarray` — plus `flush_reports()`). New CPython-facing
per vector when the input is hinted `np.ndarray` — plus `flush_reports()`), `worker.h` (worker
mode, plan 2.5: `process()` on a thread of its own, a fixed number of vectors behind an audio
thread that only copies through a lock-free ring and never takes the GIL). New CPython-facing
behavior goes here, never in the wrapper. CMake target `tap::python` (`core/CMakeLists.txt`).
- **`core/tests/`** — the core's Catch2 battery against CPython 3.13, with Python fixtures in
`core/tests/python/` and the shipped examples copied alongside. Runs on Linux, including under
Expand Down
3 changes: 3 additions & 0 deletions core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ add_library(tap::python ALIAS tap_python_core) # the family convention: tap
add_library(tap::python_core ALIAS tap_python_core) # the original spelling, kept
target_include_directories(tap_python_core INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_compile_features(tap_python_core INTERFACE cxx_std_20)
# worker.h runs process() on a std::thread (plan 2.5)
find_package(Threads REQUIRED)
target_link_libraries(tap_python_core INTERFACE Threads::Threads)

if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
enable_testing()
Expand Down
18 changes: 12 additions & 6 deletions core/include/tap/python/processor.h
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,10 @@ namespace tap::python {
/// thread, after load().
const std::vector<std::string>& input_names() const noexcept { return m_input_names; }

/// How many times load() has succeeded: what a host reporting once per load compares against
/// (worker mode does, plan 2.5). Any thread.
std::uint64_t load_count() const noexcept { return m_loads.load(std::memory_order_acquire); }

/// The most inputs, and the most outputs, a process() may declare.
static constexpr std::size_t k_max_channels = 64;

Expand Down Expand Up @@ -237,6 +241,7 @@ namespace tap::python {
next.process_function = nullptr;
next.prepare_function = nullptr;
reset_warnings(); // each kind of audio-thread problem is reported once per load
m_loads.fetch_add(1, std::memory_order_release);

release(previous); // may run finalizers, which may let other threads in: now safe
if (executed) {
Expand Down Expand Up @@ -528,12 +533,13 @@ namespace tap::python {
std::vector<attribute_info> m_attributes;
std::vector<bound_message> m_messages;
// The rest of the binding and the block buffer: read and written only under the GIL.
PyObject* m_prepare_function{}; // strong, or null
bool m_block_mode{};
std::size_t m_inputs{1}; // the bound process()'s inputs and outputs
std::size_t m_outputs{1};
std::vector<std::string> m_input_names; // main thread only
bool m_announcing{}; // this load() ran the file: see announce()
PyObject* m_prepare_function{}; // strong, or null
bool m_block_mode{};
std::size_t m_inputs{1}; // the bound process()'s inputs and outputs
std::size_t m_outputs{1};
std::vector<std::string> m_input_names; // main thread only
bool m_announcing{}; // this load() ran the file: see announce()
std::atomic<std::uint64_t> m_loads{}; // successful load()s
// The np.ndarray each input arrives in, reused every vector: strong, its buffer held so its
// memory cannot move.
struct block_buffer {
Expand Down
333 changes: 333 additions & 0 deletions core/include/tap/python/worker.h

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions core/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ add_executable(tap_python_core_tests
test_loading.cpp
test_types.cpp
test_channels.cpp
test_worker.cpp
)
target_link_libraries(tap_python_core_tests PRIVATE tap::python Python3::Python Catch2::Catch2WithMain)

Expand Down
13 changes: 13 additions & 0 deletions core/tests/python/stalls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Test fixture: a process() that can be told to stall once, as a worker falling behind (plan 2.5).
# Identity otherwise, so each output sample says which input it came from.
import time


class stalls:
stall: float = 0.0 # seconds to sleep at the next sample, once

def process(self, x: float) -> float:
if self.stall:
seconds, self.stall = self.stall, 0.0
time.sleep(seconds)
return x
Loading
Loading