MayaFlux 0.4.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)
42 : NodeContext(value)
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 * @struct InputCallback
77 * @brief Callback registration with event type and optional parameters
78 */
82 std::optional<NodeCondition> condition;
83 std::optional<double> threshold;
84 std::optional<std::pair<double, double>> range;
85};
86
87// ─────────────────────────────────────────────────────────────────────────────
88// InputNode - Base class for input-driven nodes
89// ─────────────────────────────────────────────────────────────────────────────
90
91/**
92 * @class InputNode
93 * @brief Abstract base class for nodes that receive external input
94 *
95 * InputNode follows a similar pattern to GpuSync: it bridges async external
96 * data (input events) with the synchronous node processing system.
97 *
98 * Key characteristics:
99 * - `on_input()` receives async data from InputSubsystem (like compute_frame produces data)
100 * - `process_sample()` returns smoothed values for downstream consumption
101 * - `needs_processing()` indicates if new input has arrived (like needs_gpu_update)
102 * - Minimal callback overhead (input IS the event)
103 *
104 * Unlike audio Generators that produce continuous streams, InputNodes:
105 * - Are event-driven (data arrives sporadically)
106 * - Apply smoothing to convert discrete events to continuous signals
107 * - Don't drive timing (they respond to external timing)
108 *
109 * Derived classes implement:
110 * - `extract_value()`: Convert InputValue to scalar
111 * - Optionally override `on_input()` for specialized handling
112 */
113class MAYAFLUX_API InputNode : public Node {
114public:
115 // explicit InputNode(Config config);
116
117 explicit InputNode(InputConfig config = {});
118 ~InputNode() override = default;
119
120 // ─────────────────────────────────────────────────────────────────────
121 // Node Interface
122 // ─────────────────────────────────────────────────────────────────────
123
124 /**
125 * @brief Process a single sample
126 * @param input Unused (InputNodes generate from external input)
127 * @return Current smoothed input value
128 */
129 double process_sample(double input = 0.0) override;
130
131 /**
132 * @brief Process a batch of samples
133 * @param num_samples Number of samples to generate
134 * @return Vector of smoothed values
135 */
136 std::vector<double> process_batch(unsigned int num_samples) override;
137
138 // ─────────────────────────────────────────────────────────────────────
139 // Input Reception (called by InputManager)
140 // ─────────────────────────────────────────────────────────────────────
141
142 /**
143 * @brief Process an input value from a backend
144 * @param value The input value
145 *
146 * Called by InputManager when input arrives. This is the main entry point.
147 * - Extracts scalar value via extract_value()
148 * - Applies smoothing
149 * - Stores result
150 * - Fires notify_tick() which triggers user callbacks
151 *
152 * Thread-safe. Called from InputManager's processing thread.
153 */
154 virtual void process_input(const Core::InputValue& value);
155
156 /**
157 * @brief Check if new input has arrived since last check
158 * @return true if process_input() was called
159 *
160 * Useful for polling-style checks. Calling this clears the flag.
161 */
162 bool has_new_input();
163
164 /**
165 * @brief Clear the new input flag without checking
166 */
167 void clear_input_flag() { m_has_new_input.store(false); }
168
169 // ─────────────────────────────────────────────────────────────────────
170 // State Access
171 // ─────────────────────────────────────────────────────────────────────
172
173 /** @brief Get the target value (before smoothing) */
174 [[nodiscard]] double get_target_value() const { return m_target_value.load(); }
175
176 /** @brief Get the current smoothed value */
177 [[nodiscard]] double get_current_value() const { return m_current_value.load(); }
178
179 /** @brief Get the most recent raw InputValue */
180 [[nodiscard]] std::optional<Core::InputValue> get_last_input() const;
181
182 /** @brief Get input history (thread-safe copy) */
183 [[nodiscard]] std::vector<Core::InputValue> get_input_history() const;
184
185 // ─────────────────────────────────────────────────────────────────────
186 // Configuration
187 // ─────────────────────────────────────────────────────────────────────
188
189 void set_smoothing(SmoothingMode mode, double factor = 0.1)
190 {
191 m_config.smoothing = mode;
192 m_config.smoothing_factor = factor;
193 }
194 void set_slew_rate(double rate) { m_config.slew_rate = rate; }
195 [[nodiscard]] const InputConfig& get_config() const { return m_config; }
196
197 // ─────────────────────────────────────────────────────────────────────
198 // Context Access (for callbacks)
199 // ─────────────────────────────────────────────────────────────────────
200
201 NodeContext& get_last_context() override { return m_context; }
202
203 /**
204 * @brief Register callback for any input received
205 * @param callback Function to call on every tick
206 *
207 * The callback receives an InputContext with the current value and input details.
208 * Fires on every process_input() call, after smoothing is applied.
209 */
210 void on_tick(const TypedHook<InputContext>& callback);
211
212 /**
213 * @brief Register conditional callback for input received
214 * @param condition Predicate that determines when callback is triggered
215 * @param callback Function to call when condition is met
216 *
217 * The callback receives an InputContext with the current value and input details.
218 * Fires on every process_input() call where the condition returns true.
219 */
220 void on_tick_if(const NodeCondition& condition, const TypedHook<InputContext>& callback);
221
222 /**
223 * @brief Register callback for any input received
224 * @param callback Function to call on every input
225 *
226 * Alias for on_tick() - fires on every process_input() call
227 */
228 void on_input(const TypedHook<InputContext>& callback)
229 {
230 on_tick(callback);
231 }
232
233 /**
234 * @brief Register callback for value changes
235 * @param callback Function to call when value differs from previous
236 * @param epsilon Minimum change to trigger (default: 0.0001)
237 */
238 void on_value_change(const TypedHook<InputContext>& callback, double epsilon = 0.0001);
239
240 /**
241 * @brief Register callback for threshold crossing (rising edge)
242 * @param threshold Value threshold
243 * @param callback Function to call when value crosses threshold upward
244 */
245 void on_threshold_rising(double threshold, const TypedHook<InputContext>& callback);
246
247 /**
248 * @brief Register callback for threshold crossing (falling edge)
249 * @param threshold Value threshold
250 * @param callback Function to call when value crosses threshold downward
251 */
252 void on_threshold_falling(double threshold, const TypedHook<InputContext>& callback);
253
254 /**
255 * @brief Register callback for entering a value range
256 * @param min Range minimum
257 * @param max Range maximum
258 * @param callback Function to call when value enters range
259 */
260 void on_range_enter(double min, double max, const TypedHook<InputContext>& callback);
261
262 /**
263 * @brief Register callback for exiting a value range
264 * @param min Range minimum
265 * @param max Range maximum
266 * @param callback Function to call when value exits range
267 */
268 void on_range_exit(double min, double max, const TypedHook<InputContext>& callback);
269
270 /**
271 * @brief Register callback for button press (0.0 → 1.0 transition)
272 * @param callback Function to call on button press
273 *
274 * Specialized for button inputs - fires once on press
275 */
276 void on_button_press(const TypedHook<InputContext>& callback);
277
278 /**
279 * @brief Register callback for button release (1.0 → 0.0 transition)
280 * @param callback Function to call on button release
281 *
282 * Specialized for button inputs - fires once on release
283 */
284 void on_button_release(const TypedHook<InputContext>& callback);
285
286 /**
287 * @brief Register callback while value is in range
288 * @param min Range minimum
289 * @param max Range maximum
290 * @param callback Function to call continuously while in range
291 */
292 void while_in_range(double min, double max, const TypedHook<InputContext>& callback);
293
294protected:
295 /**
296 * @brief Extract a scalar value from an InputValue
297 * @param value The input value to extract from
298 * @return Scalar value for smoothing/output
299 *
300 * Override in derived classes for specific input types.
301 * Default handles SCALAR and MIDI CC values.
302 */
303 virtual double extract_value(const Core::InputValue& value);
304
305 /**
306 * @brief Update context after processing
307 */
308 void update_context(double value) override;
309
310 /**
311 * @brief Notify callbacks (minimal for InputNode)
312 *
313 * InputNodes fire callbacks sparingly - the input event IS the notification.
314 * Override if you need per-sample callbacks (rare for input).
315 */
316 void notify_tick(double value) override;
317
320
321private:
322 std::atomic<double> m_target_value { 0.0 };
323 std::atomic<double> m_current_value { 0.0 };
324 std::atomic<bool> m_has_new_input { false };
325 double m_previous_value {};
326 bool m_was_in_range {};
327
328 std::atomic<uint32_t> m_last_device_id { 0 };
329 Core::InputType m_last_source_type { Core::InputType::HID };
330 std::vector<InputCallback> m_input_callbacks;
331
333
334 [[nodiscard]] double apply_smoothing(double target, double current) const;
335
336 void add_input_callback(
337 const TypedHook<InputContext>& callback,
338 InputEventType event_type,
339 std::optional<double> threshold = {},
340 std::optional<std::pair<double, double>> range = {},
341 std::optional<NodeCondition> condition = {});
342};
343
344} // namespace MayaFlux::Nodes::Input
Core::GlobalInputConfig input
Definition Config.cpp:36
glm::vec2 current
Policy-driven unified circular buffer implementation.
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.
void on_input(const TypedHook< InputContext > &callback)
Register callback for any input received.
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)
std::vector< InputCallback > m_input_callbacks
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)
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.
std::optional< double > threshold
Definition InputNode.hpp:83
TypedHook< InputContext > callback
Definition InputNode.hpp:80
std::optional< std::pair< double, double > > range
Definition InputNode.hpp:84
std::optional< NodeCondition > condition
Definition InputNode.hpp:82
Callback registration with event type and optional parameters.
Definition InputNode.hpp:79
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