MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
InputNode.hpp
Go to the documentation of this file.
1#pragma once
2
5
7
9
10/**
11 * @enum InputEventType
12 * @brief Types of input events that can trigger callbacks
13 */
14enum class InputEventType : uint8_t {
15 TICK, ///< Every input received
16 VALUE_CHANGE, ///< Value changed from previous
17 THRESHOLD_RISING, ///< Value crossed threshold upward
18 THRESHOLD_FALLING, ///< Value crossed threshold downward
19 RANGE_ENTER, ///< Value entered specified range
20 RANGE_EXIT, ///< Value exited specified range
21 BUTTON_PRESS, ///< Button went from 0.0 to 1.0
22 BUTTON_RELEASE, ///< Button went from 1.0 to 0.0
23 CONDITIONAL ///< User-provided condition
24};
25
26// ─────────────────────────────────────────────────────────────────────────────
27// InputContext - Specialized context for input node callbacks
28// ─────────────────────────────────────────────────────────────────────────────
29
30/**
31 * @class InputContext
32 * @brief Context for InputNode callbacks - provides input event access
33 *
34 * Similar to GeneratorContext or TextureContext, provides node-specific
35 * information to callbacks. Contains both the smoothed output value and
36 * access to the raw input data.
37 */
38class MAYAFLUX_API InputContext : public NodeContext {
39public:
40 InputContext(double value, double raw_value,
41 Core::InputType source, uint32_t device_id)
43 , raw_value(raw_value)
44 , source_type(source)
45 , device_id(device_id)
46 {
47 }
48
49 double raw_value; ///< Unsmoothed input value
50 Core::InputType source_type; ///< Backend that produced this input
51 uint32_t device_id; ///< Source device ID
52};
53
54/**
55 * @brief Smoothing mode for input values
56 */
57enum class SmoothingMode : uint8_t {
58 NONE, ///< No smoothing - immediate value changes (buttons)
59 LINEAR, ///< Linear interpolation between values
60 EXPONENTIAL, ///< Exponential smoothing / 1-pole lowpass (default)
61 SLEW ///< Slew rate limiting
62};
63
64/**
65 * @brief Configuration for InputNode behavior
66 */
69 double smoothing_factor { 0.1 }; ///< 0-1, higher = faster response
70 double slew_rate { 1.0 }; ///< Max change per sample (SLEW mode)
71 double default_value { 0.0 }; ///< Initial output value
72 size_t history_size { 8 }; ///< Input history buffer size
73};
74
75// ─────────────────────────────────────────────────────────────────────────────
76// InputNode - Base class for input-driven nodes
77// ─────────────────────────────────────────────────────────────────────────────
78
79/**
80 * @class InputNode
81 * @brief Abstract base class for nodes that receive external input
82 *
83 * InputNode follows a similar pattern to GpuSync: it bridges async external
84 * data (input events) with the synchronous node processing system.
85 *
86 * Key characteristics:
87 * - `on_input()` receives async data from InputSubsystem (like compute_frame produces data)
88 * - `process_sample()` returns smoothed values for downstream consumption
89 * - `needs_processing()` indicates if new input has arrived (like needs_gpu_update)
90 * - Minimal callback overhead (input IS the event)
91 *
92 * Unlike audio Generators that produce continuous streams, InputNodes:
93 * - Are event-driven (data arrives sporadically)
94 * - Apply smoothing to convert discrete events to continuous signals
95 * - Don't drive timing (they respond to external timing)
96 *
97 * Derived classes implement:
98 * - `extract_value()`: Convert InputValue to scalar
99 * - Optionally override `on_input()` for specialized handling
100 */
101class MAYAFLUX_API InputNode : public Node {
102public:
103 // explicit InputNode(Config config);
104
105 explicit InputNode(InputConfig config = {});
106 ~InputNode() override = default;
107
108 // ─────────────────────────────────────────────────────────────────────
109 // Node Interface
110 // ─────────────────────────────────────────────────────────────────────
111
112 /**
113 * @brief Process a single sample
114 * @param input Unused (InputNodes generate from external input)
115 * @return Current smoothed input value
116 */
117 double process_sample(double input = 0.0) override;
118
119 /**
120 * @brief Process a batch of samples
121 * @param num_samples Number of samples to generate
122 * @return Vector of smoothed values
123 */
124 std::vector<double> process_batch(unsigned int num_samples) override;
125
126 // ─────────────────────────────────────────────────────────────────────
127 // Input Reception (called by InputManager)
128 // ─────────────────────────────────────────────────────────────────────
129
130 /**
131 * @brief Process an input value from a backend
132 * @param value The input value
133 *
134 * Called by InputManager when input arrives. This is the main entry point.
135 * - Extracts scalar value via extract_value()
136 * - Applies smoothing
137 * - Stores result
138 * - Fires notify_tick() which triggers user callbacks
139 *
140 * Thread-safe. Called from InputManager's processing thread.
141 */
142 virtual void process_input(const Core::InputValue& value);
143
144 /**
145 * @brief Check if new input has arrived since last check
146 * @return true if process_input() was called
147 *
148 * Useful for polling-style checks. Calling this clears the flag.
149 */
150 bool has_new_input();
151
152 /**
153 * @brief Clear the new input flag without checking
154 */
155 void clear_input_flag() { m_has_new_input.store(false); }
156
157 // ─────────────────────────────────────────────────────────────────────
158 // State Access
159 // ─────────────────────────────────────────────────────────────────────
160
161 /** @brief Get the target value (before smoothing) */
162 [[nodiscard]] double get_target_value() const { return m_target_value.load(); }
163
164 /** @brief Get the current smoothed value */
165 [[nodiscard]] double get_current_value() const { return m_current_value.load(); }
166
167 /** @brief Get the most recent raw InputValue */
168 [[nodiscard]] std::optional<Core::InputValue> get_last_input() const;
169
170 /** @brief Get input history (thread-safe copy) */
171 [[nodiscard]] std::vector<Core::InputValue> get_input_history() const;
172
173 // ─────────────────────────────────────────────────────────────────────
174 // Configuration
175 // ─────────────────────────────────────────────────────────────────────
176
177 void set_smoothing(SmoothingMode mode, double factor = 0.1)
178 {
179 m_config.smoothing = mode;
180 m_config.smoothing_factor = factor;
181 }
182 void set_slew_rate(double rate) { m_config.slew_rate = rate; }
183 [[nodiscard]] const InputConfig& get_config() const { return m_config; }
184
185 // ─────────────────────────────────────────────────────────────────────
186 // Context Access (for callbacks)
187 // ─────────────────────────────────────────────────────────────────────
188
189 NodeContext& get_last_context() override { return m_context; }
190
191 /**
192 * @brief Register callback for any input received
193 * @param callback Function to call on every tick
194 *
195 * The callback receives an InputContext with the current value and input details.
196 * Fires on every process_input() call, after smoothing is applied.
197 */
199
200 /**
201 * @brief Register conditional callback for input received
202 * @param condition Predicate that determines when callback is triggered
203 * @param callback Function to call when condition is met
204 *
205 * The callback receives an InputContext with the current value and input details.
206 * Fires on every process_input() call where the condition returns true.
207 */
208 HookId on_tick_if(const NodeCondition& condition, const TypedHook<InputContext>& callback);
209
210 /**
211 * @brief Register callback for any input received
212 * @param callback Function to call on every input
213 *
214 * Alias for on_tick() - fires on every process_input() call
215 */
217 {
218 return on_tick(callback);
219 }
220
221 /**
222 * @brief Register callback for value changes
223 * @param callback Function to call when value differs from previous
224 * @param epsilon Minimum change to trigger (default: 0.0001)
225 */
226 HookId on_value_change(const TypedHook<InputContext>& callback, double epsilon = 0.0001);
227
228 /**
229 * @brief Register callback for threshold crossing (rising edge)
230 * @param threshold Value threshold
231 * @param callback Function to call when value crosses threshold upward
232 */
233 HookId on_threshold_rising(double threshold, const TypedHook<InputContext>& callback);
234
235 /**
236 * @brief Register callback for threshold crossing (falling edge)
237 * @param threshold Value threshold
238 * @param callback Function to call when value crosses threshold downward
239 */
240 HookId on_threshold_falling(double threshold, const TypedHook<InputContext>& callback);
241
242 /**
243 * @brief Register callback for entering a value range
244 * @param min Range minimum
245 * @param max Range maximum
246 * @param callback Function to call when value enters range
247 */
248 HookId on_range_enter(double min, double max, const TypedHook<InputContext>& callback);
249
250 /**
251 * @brief Register callback for exiting a value range
252 * @param min Range minimum
253 * @param max Range maximum
254 * @param callback Function to call when value exits range
255 */
256 HookId on_range_exit(double min, double max, const TypedHook<InputContext>& callback);
257
258 /**
259 * @brief Register callback for button press (0.0 → 1.0 transition)
260 * @param callback Function to call on button press
261 *
262 * Specialized for button inputs - fires once on press
263 */
264 HookId on_button_press(const TypedHook<InputContext>& callback);
265
266 /**
267 * @brief Register callback for button release (1.0 → 0.0 transition)
268 * @param callback Function to call on button release
269 *
270 * Specialized for button inputs - fires once on release
271 */
272 HookId on_button_release(const TypedHook<InputContext>& callback);
273
274 /**
275 * @brief Register callback while value is in range
276 * @param min Range minimum
277 * @param max Range maximum
278 * @param callback Function to call continuously while in range
279 */
280 HookId while_in_range(double min, double max, const TypedHook<InputContext>& callback);
281
282protected:
283 /**
284 * @brief Extract a scalar value from an InputValue
285 * @param value The input value to extract from
286 * @return Scalar value for smoothing/output
287 *
288 * Override in derived classes for specific input types.
289 * Default handles SCALAR and MIDI CC values.
290 */
291 virtual double extract_value(const Core::InputValue& value);
292
293 /**
294 * @brief Update context after processing
295 */
296 void update_context(double value) override;
297
298 /**
299 * @brief Fires the hooks for one delivered input
300 *
301 * Called only from process_input(), so every hook runs on the thread that delivers
302 * input and never on the graph thread. process_sample() only advances smoothing and
303 * does not notify. The context, previous-value and range state are therefore owned by
304 * the input thread, and Node::m_last_output by the graph thread.
305 */
306 void notify_tick(double value) override;
307
308 /**
309 * @brief The smoothed value the node currently holds
310 *
311 * What extract_value() returns when an input carries nothing for this node.
312 */
313 [[nodiscard]] double held_value() const { return m_current_value.load(); }
314
317
318private:
319 std::atomic<double> m_target_value { 0.0 };
320 std::atomic<double> m_current_value { 0.0 };
321 std::atomic<bool> m_has_new_input { false };
322 double m_previous_value {};
323 bool m_was_in_range {};
324
325 std::atomic<uint32_t> m_last_device_id { 0 };
326 Core::InputType m_last_source_type { Core::InputType::HID };
327 HookList m_input_callbacks { *this };
328
330
331 [[nodiscard]] double apply_smoothing(double target, double current) const;
332
333 HookId add_input_callback(
335 InputEventType event_type,
336 std::optional<double> threshold = {},
337 std::optional<std::pair<double, double>> range = {},
338 std::optional<NodeCondition> condition = {});
339};
340
341} // namespace MayaFlux::Nodes::Input
Core::GlobalInputConfig input
Definition Config.cpp:42
glm::vec2 current
float rate
PickCallback callback
Definition Picker.cpp:22
uint32_t mode
glm::vec3 value
float threshold
Policy-driven unified circular buffer implementation.
Stores, fires and clears the hooks of one node channel.
Definition NodeUtils.hpp:93
uint32_t device_id
Source device ID.
Definition InputNode.hpp:51
double raw_value
Unsmoothed input value.
Definition InputNode.hpp:49
InputContext(double value, double raw_value, Core::InputType source, uint32_t device_id)
Definition InputNode.hpp:40
Core::InputType source_type
Backend that produced this input.
Definition InputNode.hpp:50
Context for InputNode callbacks - provides input event access.
Definition InputNode.hpp:38
double get_current_value() const
Get the current smoothed value.
NodeContext & get_last_context() override
Retrieves the last created context object.
const InputConfig & get_config() const
void clear_input_flag()
Clear the new input flag without checking.
void set_smoothing(SmoothingMode mode, double factor=0.1)
Memory::LockFreeQueue< Core::InputValue, 64 > m_input_history
double get_target_value() const
Get the target value (before smoothing)
HookId on_input(const TypedHook< InputContext > &callback)
Register callback for any input received.
double held_value() const
The smoothed value the node currently holds.
Abstract base class for nodes that receive external input.
Base context class for node callbacks.
Definition Node.hpp:53
Base interface for all computational processing nodes.
Definition Node.hpp:126
InputType
Input backend type enumeration.
InputEventType
Types of input events that can trigger callbacks.
Definition InputNode.hpp:14
@ BUTTON_PRESS
Button went from 0.0 to 1.0.
@ THRESHOLD_FALLING
Value crossed threshold downward.
@ VALUE_CHANGE
Value changed from previous.
@ THRESHOLD_RISING
Value crossed threshold upward.
@ CONDITIONAL
User-provided condition.
@ RANGE_ENTER
Value entered specified range.
@ RANGE_EXIT
Value exited specified range.
@ BUTTON_RELEASE
Button went from 1.0 to 0.0.
SmoothingMode
Smoothing mode for input values.
Definition InputNode.hpp:57
@ LINEAR
Linear interpolation between values.
@ NONE
No smoothing - immediate value changes (buttons)
@ EXPONENTIAL
Exponential smoothing / 1-pole lowpass (default)
uint64_t HookId
Identifies one registration inside a HookList.
Definition NodeUtils.hpp:60
std::function< void(ContextT &)> TypedHook
Callback function type for node processing events, parameterised on context type.
Definition NodeUtils.hpp:28
std::function< bool(NodeContext &)> NodeCondition
Predicate function type for conditional callbacks.
Definition NodeUtils.hpp:54
Generic input value container.
double slew_rate
Max change per sample (SLEW mode)
Definition InputNode.hpp:70
double smoothing_factor
0-1, higher = faster response
Definition InputNode.hpp:69
size_t history_size
Input history buffer size.
Definition InputNode.hpp:72
double default_value
Initial output value.
Definition InputNode.hpp:71
Configuration for InputNode behavior.
Definition InputNode.hpp:67