OSSIA
Open Scenario System for Interactive Application
Loading...
Searching...
No Matches
pipewire_quantum.hpp
1#pragma once
2
3// PipeWire-facing helpers with no PipeWire dependency, so that the process
4// cycle policy is unit-testable without a daemon.
5//
6// Background: the quantum (block size) of a PipeWire graph is a global,
7// per-driver negotiation. Even when a node sets node.force-quantum +
8// node.lock-quantum, the graph can legitimately run at a different quantum:
9// - transiently, between the node joining and the driver picking up
10// clock.target_duration (one cycle minimum, more when drivers move);
11// - when the global `clock.force-quantum` setting is active (it overrides
12// every per-node force);
13// - when another node's force-quantum has a newer stamp (last-write-wins
14// between force-quantum nodes on the same driver);
15// - when the value is clamped by `clock.quantum-floor` / `clock.quantum-limit`
16// (e.g. min-quantum is raised to 1024 inside VMs by the default config).
17// A client that refuses to process such cycles outputs silence forever
18// (its output io stays in NEED_DATA); the only correct behaviour is to adapt.
19
20#include <cstddef>
21#include <cstdint>
22
23namespace ossia::pipewire
24{
25
26// Tracks the block size (or sample rate) seen on each process cycle against
27// the configured value, and turns changes into loggable transitions so the
28// realtime callback logs once per reconfiguration instead of once per cycle.
29struct quantum_tracker
30{
31 std::uint32_t expected{};
32 std::uint32_t last_seen{};
33
34 enum class event : std::uint8_t
35 {
36 steady, // same value as the previous cycle
37 matched, // first cycle, at the expected value
38 mismatch, // value changed to something != expected: warn
39 recovered, // back to the expected value after a mismatch: info
40 };
41
42 constexpr event observe(std::uint32_t value) noexcept
43 {
44 if (value == last_seen)
45 return event::steady;
46 const auto prev = last_seen;
47 last_seen = value;
48 if (value == expected)
49 return prev == 0 ? event::matched : event::recovered;
50 return event::mismatch;
51 }
52};
53
54// Splits a cycle of nframes into consecutive chunks of at most max_frames,
55// invoking f(offset, frames) for each. The engine's buffers are sized for
56// the configured block size, so a larger graph quantum must be processed
57// in slices instead of overrunning them.
58template <typename F>
59constexpr void
60for_each_chunk(std::uint32_t nframes, std::uint32_t max_frames, F&& f)
61{
62 if (max_frames == 0)
63 return;
64 for (std::uint32_t offset = 0; offset < nframes;)
65 {
66 const std::uint32_t n
67 = nframes - offset < max_frames ? nframes - offset : max_frames;
68 f(offset, n);
69 offset += n;
70 }
71}
72
73// Builds the per-chunk channel pointer array: channels whose cycle buffer
74// exists advance by offset, missing ones fall back to scratch. Inputs and
75// outputs must use *distinct* scratch storage: sharing one dummy buffer
76// feeds each tick's discarded output back into the missing inputs.
77inline void assign_chunk_pointers(
78 float* const* cycle, float** chunk, std::size_t channels,
79 std::uint32_t offset, float* scratch) noexcept
80{
81 for (std::size_t i = 0; i < channels; ++i)
82 chunk[i] = cycle[i] ? cycle[i] + offset : scratch;
83}
84
85}