MayaFlux 0.4.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
InputNode.cpp
Go to the documentation of this file.
1#include "InputNode.hpp"
2
4
5// ─────────────────────────────────────────────────────────────────────────────
6// InputNode Implementation
7// ─────────────────────────────────────────────────────────────────────────────
8
10 : m_config(config)
11 , m_context(m_config.default_value, m_config.default_value,
12 Core::InputType::HID, 0)
13{
16}
17
18double InputNode::process_sample(double /*input*/)
19{
20 double target = m_target_value.load();
21 double current = m_current_value.load();
22
23 double output = apply_smoothing(target, current);
24 m_current_value.store(output);
25 m_last_output = output;
26
27 notify_tick(output);
28 return output;
29}
30
31std::vector<double> InputNode::process_batch(unsigned int num_samples)
32{
33 std::vector<double> output(num_samples);
34 for (unsigned int i = 0; i < num_samples; ++i) {
35 output[i] = process_sample(0.0);
36 }
37 return output;
38}
39
41{
42 double extracted = extract_value(value);
43
44 double current = m_current_value.load();
45 double smoothed = apply_smoothing(extracted, current);
46
47 m_target_value.store(extracted);
48 m_current_value.store(smoothed);
49 m_last_output = smoothed;
50 m_has_new_input.store(true);
51
52 m_last_device_id.store(value.device_id);
54
55 (void)m_input_history.push(value);
56
57 notify_tick(smoothed);
58}
59
61{
62 return m_has_new_input.exchange(false);
63}
64
65std::optional<Core::InputValue> InputNode::get_last_input() const
66{
67 auto history = m_input_history.snapshot();
68 if (history.empty())
69 return std::nullopt;
70 return history.back();
71}
72
73std::vector<Core::InputValue> InputNode::get_input_history() const
74{
75 return m_input_history.snapshot();
76}
77
79{
80 switch (value.type) {
82 return value.as_scalar();
83
85 const auto& vec = value.as_vector();
86 return vec.empty() ? m_config.default_value : vec[0];
87 }
88
90 // Default: treat data2 as 0-127 normalized value
91 const auto& midi = value.as_midi();
92 return static_cast<double>(midi.data2) / 127.0;
93 }
94
95 default:
97 }
98}
99
107
108double InputNode::apply_smoothing(double target, double current) const
109{
110 switch (m_config.smoothing) {
112 return target;
113
115 double diff = target - current;
116 return current + diff * m_config.smoothing_factor;
117 }
118
120 // y[n] = a * x[n] + (1-a) * y[n-1]
121 return m_config.smoothing_factor * target + (1.0 - m_config.smoothing_factor) * current;
122 }
123
124 case SmoothingMode::SLEW: {
125 double diff = target - current;
126 if (std::abs(diff) <= m_config.slew_rate) {
127 return target;
128 }
129 return current + (diff > 0 ? m_config.slew_rate : -m_config.slew_rate);
130 }
131
132 default:
133 return target;
134 }
135}
136
138{
139 m_callbacks.emplace_back([callback](NodeContext& ctx) {
140 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast)
141 callback(static_cast<InputContext&>(ctx));
142 });
143}
144
145void InputNode::on_tick_if(const NodeCondition& condition, const TypedHook<InputContext>& callback)
146{
147 m_conditional_callbacks.emplace_back([callback](NodeContext& ctx) {
148 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast)
149 callback(static_cast<InputContext&>(ctx));
150 },
151 condition);
152}
153
154void InputNode::on_value_change(const TypedHook<InputContext>& callback, double epsilon)
155{
157}
158
159void InputNode::on_threshold_rising(double threshold, const TypedHook<InputContext>& callback)
160{
162}
163
164void InputNode::on_threshold_falling(double threshold, const TypedHook<InputContext>& callback)
165{
167}
168
169void InputNode::on_range_enter(double min, double max, const TypedHook<InputContext>& callback)
170{
171 add_input_callback(callback, InputEventType::RANGE_ENTER, {}, { { min, max } });
172}
173
174void InputNode::on_range_exit(double min, double max, const TypedHook<InputContext>& callback)
175{
176 add_input_callback(callback, InputEventType::RANGE_EXIT, {}, { { min, max } });
177}
178
183
188
189void InputNode::while_in_range(double min, double max, const TypedHook<InputContext>& callback)
190{
192 [min, max](const NodeContext& ctx) {
193 return ctx.value >= min && ctx.value <= max;
194 },
195 callback);
196}
197
199 const TypedHook<InputContext>& callback,
200 InputEventType event_type,
201 std::optional<double> threshold,
202 std::optional<std::pair<double, double>> range,
203 std::optional<NodeCondition> condition)
204{
205 m_input_callbacks.emplace_back(callback, event_type, std::move(condition), threshold, range);
206}
207
208void InputNode::notify_tick(double value)
209{
210 update_context(value);
211 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast)
212 auto& ctx = static_cast<InputContext&>(get_last_context());
213
214 for (auto& callback : m_callbacks) {
215 callback(ctx);
216 }
217 for (auto& [callback, condition] : m_conditional_callbacks) {
218 if (condition(ctx)) {
219 callback(ctx);
220 }
221 }
222
223 for (auto& cb : m_input_callbacks) {
224 bool should_fire = false;
225
226 switch (cb.event_type) {
228 should_fire = true;
229 break;
230
232 double epsilon = cb.threshold.value_or(0.0001);
233 should_fire = std::abs(value - m_previous_value) > epsilon;
234 break;
235 }
236
238 should_fire = (m_previous_value < cb.threshold.value() && value >= cb.threshold.value());
239 break;
240
242 should_fire = (m_previous_value >= cb.threshold.value() && value < cb.threshold.value());
243 break;
244
246 auto [min, max] = cb.range.value();
247 bool in_range_now = (value >= min && value <= max);
248 should_fire = (!m_was_in_range && in_range_now);
249 m_was_in_range = in_range_now;
250 break;
251 }
252
254 auto [min, max] = cb.range.value();
255 bool in_range_now = (value >= min && value <= max);
256 should_fire = (m_was_in_range && !in_range_now);
257 m_was_in_range = in_range_now;
258 break;
259 }
260
262 should_fire = (m_previous_value < 0.5 && value >= 0.5);
263 break;
264
266 should_fire = (m_previous_value >= 0.5 && value < 0.5);
267 break;
268
270 should_fire = cb.condition && cb.condition.value()(ctx);
271 break;
272 }
273
274 if (should_fire) {
275 cb.callback(ctx);
276 }
277 }
278
279 m_previous_value = value;
280}
281
282} // namespace MayaFlux::Nodes::Input
glm::vec2 current
uint32_t device_id
Source device ID.
Definition InputNode.hpp:51
double raw_value
Unsmoothed input value.
Definition InputNode.hpp:49
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
void update_context(double value) override
Update context after processing.
std::optional< Core::InputValue > get_last_input() const
Get the most recent raw InputValue.
Definition InputNode.cpp:65
bool has_new_input()
Check if new input has arrived since last check.
Definition InputNode.cpp:60
std::vector< Core::InputValue > get_input_history() const
Get input history (thread-safe copy)
Definition InputNode.cpp:73
NodeContext & get_last_context() override
Retrieves the last created context object.
void on_button_release(const TypedHook< InputContext > &callback)
Register callback for button release (1.0 → 0.0 transition)
void on_threshold_falling(double threshold, const TypedHook< InputContext > &callback)
Register callback for threshold crossing (falling edge)
std::atomic< double > m_target_value
Memory::LockFreeQueue< Core::InputValue, 64 > m_input_history
InputNode(InputConfig config={})
Definition InputNode.cpp:9
double process_sample(double input=0.0) override
Process a single sample.
Definition InputNode.cpp:18
std::atomic< uint32_t > m_last_device_id
void add_input_callback(const TypedHook< InputContext > &callback, InputEventType event_type, std::optional< double > threshold={}, std::optional< std::pair< double, double > > range={}, std::optional< NodeCondition > condition={})
void on_value_change(const TypedHook< InputContext > &callback, double epsilon=0.0001)
Register callback for value changes.
std::vector< InputCallback > m_input_callbacks
virtual void process_input(const Core::InputValue &value)
Process an input value from a backend.
Definition InputNode.cpp:40
std::vector< double > process_batch(unsigned int num_samples) override
Process a batch of samples.
Definition InputNode.cpp:31
void on_range_enter(double min, double max, const TypedHook< InputContext > &callback)
Register callback for entering a value range.
void notify_tick(double value) override
Notify callbacks (minimal for InputNode)
virtual double extract_value(const Core::InputValue &value)
Extract a scalar value from an InputValue.
Definition InputNode.cpp:78
void on_threshold_rising(double threshold, const TypedHook< InputContext > &callback)
Register callback for threshold crossing (rising edge)
double apply_smoothing(double target, double current) const
std::atomic< bool > m_has_new_input
void while_in_range(double min, double max, const TypedHook< InputContext > &callback)
Register callback while value is in range.
void on_tick_if(const NodeCondition &condition, const TypedHook< InputContext > &callback)
Register conditional callback for input received.
void on_button_press(const TypedHook< InputContext > &callback)
Register callback for button press (0.0 → 1.0 transition)
void on_tick(const TypedHook< InputContext > &callback)
Register callback for any input received.
void on_range_exit(double min, double max, const TypedHook< InputContext > &callback)
Register callback for exiting a value range.
std::atomic< double > m_current_value
double value
Current sample value.
Definition Node.hpp:63
Base context class for node callbacks.
Definition Node.hpp:53
std::vector< NodeHook > m_callbacks
Collection of standard callback functions.
Definition Node.hpp:434
double m_last_output
The most recent sample value generated by this oscillator.
Definition Node.hpp:405
std::vector< std::pair< NodeHook, NodeCondition > > m_conditional_callbacks
Collection of conditional callback functions with their predicates.
Definition Node.hpp:444
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.
@ 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
uint32_t device_id
Source device identifier.
const std::vector< double > & as_vector() const
@ MIDI
Structured MIDI message.
@ VECTOR
Multiple float values (e.g., accelerometer xyz)
@ SCALAR
Single normalized float [-1.0, 1.0] or [0.0, 1.0].
InputType source_type
Backend that generated this value.
const MIDIMessage & as_midi() const
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
double default_value
Initial output value.
Definition InputNode.hpp:71
Configuration for InputNode behavior.
Definition InputNode.hpp:67