MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
LogicProcessor.hpp
Go to the documentation of this file.
1#pragma once
2
5
6namespace MayaFlux::Buffers {
7
8class AudioBuffer;
9
10/**
11 * @class LogicProcessor
12 * @brief Digital signal processor that applies boolean logic operations to data streams
13 *
14 * LogicProcessor bridges Nodes::Generator::Logic nodes with the buffer processing system,
15 * enabling sophisticated digital signal manipulation through various modulation strategies.
16 *
17 * The processor's job is simple:
18 * 1. Iterate through buffer samples
19 * 2. Generate logic values (0.0 or 1.0) by calling the Logic node
20 * 3. Apply logic to buffer data using a modulation strategy
21 *
22 * All logic computation (threshold detection, edge detection, state machines, etc.)
23 * is handled by the Logic node itself. The processor only manages iteration and
24 * application of results.
25 *
26 * Use cases include:
27 * - Binary pattern detection in data streams
28 * - Digital control signal generation
29 * - Conditional data flow routing
30 * - Event triggering based on signal characteristics
31 * - Digital state machine implementation
32 * - Signal quantization and discretization
33 */
34class MAYAFLUX_API LogicProcessor : public BufferProcessor {
35public:
36 /**
37 * @enum ModulationType
38 * @brief Defines how logic values modulate buffer content
39 *
40 * These are readymade strategies for applying binary logic (0.0/1.0) to
41 * continuous audio data, providing common compositional primitives for
42 * logic-based signal processing.
43 */
44 enum class ModulationType : uint8_t {
45 REPLACE, ///< Replace buffer with logic values: out = logic
46 MULTIPLY, ///< Gate/mask buffer: out = logic * buffer (standard audio gate)
47 ADD, ///< Offset buffer: out = logic + buffer
48
49 INVERT_ON_TRUE, ///< Invert signal when logic is true: out = logic ? -buffer : buffer
50 HOLD_ON_FALSE, ///< Hold last value when logic is false: out = logic ? buffer : last_value
51 ZERO_ON_FALSE, ///< Silence when logic is false: out = logic ? buffer : 0.0
52 CROSSFADE, ///< Smooth interpolation: out = lerp(0.0, buffer, logic)
53 THRESHOLD_REMAP, ///< Binary value selection: out = logic ? high_val : low_val
54 SAMPLE_AND_HOLD, ///< Sample on logic change: out = logic_changed ? buffer : held_value
55
56 CUSTOM ///< User-defined modulation function
57 };
58
59 /**
60 * @typedef ModulationFunction
61 * @brief Function type for custom digital signal transformations
62 *
63 * Defines a transformation that combines a logic value (0.0 or 1.0)
64 * with a buffer sample to produce a modulated output sample.
65 *
66 * Parameters:
67 * - double logic_val: The binary logic value (0.0 or 1.0)
68 * - double buffer_val: The original buffer sample value
69 *
70 * Returns:
71 * - double: The transformed output sample value
72 *
73 * This enables implementation of arbitrary digital transformations
74 * based on binary logic states, supporting complex conditional processing,
75 * digital filtering, and algorithmic decision trees in signal processing.
76 */
77 using ModulationFunction = std::function<double(double, double)>;
78
79 /**
80 * @brief Constructs a LogicProcessor with internal Logic node
81 * @param args Arguments to pass to the Logic constructor
82 *
83 * Creates a LogicProcessor that constructs its own Logic node internally.
84 */
85 template <typename... Args>
86 requires std::constructible_from<Nodes::Generator::Logic, Args...>
87 LogicProcessor(Args&&... args)
88 : m_logic(std::make_shared<Nodes::Generator::Logic>(std::forward<Args>(args)...))
89 , m_reset_between_buffers(false)
90 , m_use_internal(true)
91 , m_modulation_type(ModulationType::REPLACE)
92 , m_has_generated_data(false)
93 , m_high_value(1.0)
94 , m_low_value(0.0)
95 , m_last_held_value(0.0)
96 , m_last_logic_value(0.0)
97 {
98 }
99
100 /**
101 * @brief Constructs a LogicProcessor with external Logic node
102 * @param logic The logic node to use for processing
103 * @param reset_between_buffers Whether to reset logic state between buffer calls
104 *
105 * Creates a LogicProcessor that uses an external Logic node.
106 * NOTE: Using external Logic node implies side effects of any processing chain
107 * the node is connected to.
108 */
110 const std::shared_ptr<Nodes::Generator::Logic>& logic,
111 bool reset_between_buffers = false);
112
113 /**
114 * @brief Processes a buffer through the logic node
115 * @param buffer The audio buffer to process
116 *
117 * Processing steps:
118 * 1. Iterates through each sample in the buffer
119 * 2. Calls Logic node's process_sample() for each sample to get logic value
120 * 3. Applies the selected modulation strategy to combine logic with buffer data
121 *
122 * The Logic node handles all temporal state, callbacks, and logic computation.
123 * The processor only manages iteration and modulation application.
124 */
125 void processing_function(const std::shared_ptr<Buffer>& buffer) override;
126
127 /**
128 * @brief Called when the processor is attached to a buffer
129 * @param buffer The buffer this processor is being attached to
130 */
131 void on_attach(const std::shared_ptr<Buffer>& buffer) override;
132
133 /**
134 * @brief Called when the processor is detached from a buffer
135 * @param buffer The buffer this processor is being detached from
136 */
137 inline void on_detach(const std::shared_ptr<Buffer>&) override { }
138
139 /**
140 * @brief Generates discrete logic data from input without modifying any buffer
141 * @param num_samples Number of samples to generate
142 * @param input_data Input data to process through logic node
143 * @return Whether generation was successful
144 *
145 * This method allows for offline processing of data through the logic system,
146 * useful for analysis, preprocessing, or generating control signals independently
147 * of the main signal path.
148 */
149 bool generate(size_t num_samples, const std::vector<double>& input_data);
150
151 /**
152 * @brief Applies stored logic data to the given buffer
153 * @param buffer Buffer to apply logic data to
154 * @param modulation_func Optional function defining how to apply logic to audio
155 * @return Whether application was successful
156 *
157 * Use cases:
158 * - Conditional data transformation
159 * - Digital gating of signals
160 * - Binary masking operations
161 * - Custom digital signal processing chains
162 */
163 bool apply(const std::shared_ptr<Buffer>& buffer,
164 ModulationFunction modulation_func = nullptr);
165
166 /**
167 * @brief Gets the stored logic data
168 * @return Reference to the internal logic data vector
169 */
170 [[nodiscard]] const std::vector<double>& get_logic_data() const { return m_logic_data; }
171
172 /**
173 * @brief Checks if logic data has been generated
174 * @return True if logic data is available
175 */
176 [[nodiscard]] inline bool has_generated_data() const { return m_has_generated_data; }
177
178 /**
179 * @brief An independent processor over a clone of the node
180 *
181 * Same reset policy, modulation type, custom modulation function and
182 * remap values; the node is cloned, so its history and coefficient array
183 * are not shared. Attach one clone per channel instead of one processor to
184 * several.
185 */
186 [[nodiscard]] std::shared_ptr<LogicProcessor> clone() const;
187
188 /**
189 * @brief Reads another buffer as an extra input slot of a parallel node
190 * @param buffer The key, for example another channel or a sidechain
191 *
192 * With a parallel Logic node, every sample is evaluated with one slot per
193 * key, in the order added, followed by this buffer's own sample, the same
194 * order input nodes use. The key is read as it stands when this buffer is
195 * processed, so process it first; a key shorter than this buffer or no
196 * longer alive reads as silence. Keys are ignored by a node that is not
197 * parallel.
198 */
199 void add_key(const std::shared_ptr<AudioBuffer>& buffer);
200
201 /**
202 * @brief Removes every key
203 */
204 void clear_keys();
205
206 /**
207 * @brief Processes every sample as a word of bit streams, one node per bit
208 * @param bits Word width, 1 to 24; 0 turns it off
209 *
210 * Each sample becomes an offset binary word (Kinesis::Discrete::to_word) and
211 * every bit position runs through its own clone of the node, so each plane
212 * keeps its own state. Planes are presented to the node as 0.0 or 1.0 and
213 * the output bits are recombined into the sample, so the logic data is a
214 * value in [-1, 1] rather than a bit. With a parallel node and keys, plane j
215 * sees plane j of every key and of this buffer: bitwise word operations
216 * across channels. The clones are taken at the next block and again when
217 * the node is replaced or this is called.
218 */
219 void set_bit_planes(uint32_t bits);
220
221 /**
222 * @brief Treats the samples of a block as cells of an automaton
223 * @param radius Cells on each side that feed a cell
224 * @param generations Steps per block; 0 turns it off
225 * @param wrap Whether the block's ends meet, otherwise cells beyond them are 0
226 * @param memory Whether the block perturbs the previous result instead of replacing it
227 *
228 * Needs a parallel node. A cell is 1 when its sample is above zero, and the
229 * node is evaluated for every cell with 2 * radius + 1 slots, slot 0 the
230 * cell furthest to the right and the last slot the furthest to the left,
231 * so Wolfram's rule numbers work with `input_truth_table_state(3, rule)`.
232 * With memory the block's cells are XORed into the previous block's final
233 * generation, so the pattern evolves across blocks; use one processor per
234 * channel (clone) for that. The logic data is the final generation, 0 or 1.
235 */
236 void set_neighbourhood(size_t radius, size_t generations = 1, bool wrap = true, bool memory = false);
237
238 /**
239 * @brief Turns the automaton off
240 */
241 void clear_neighbourhood();
242
243 /**
244 * @brief Set how logic values modulate buffer content
245 * @param type Modulation type to use
246 *
247 * Different modulation types enable various digital transformations:
248 * - REPLACE: Binary substitution of values
249 * - MULTIPLY: Binary masking/gating
250 * - ADD: Digital offset or bias
251 * - INVERT_ON_TRUE: Phase inversion based on logic state
252 * - HOLD_ON_FALSE: Sample-and-hold behavior
253 * - ZERO_ON_FALSE: Hard gating (silence on false)
254 * - CROSSFADE: Smooth amplitude interpolation
255 * - THRESHOLD_REMAP: Binary value mapping
256 * - SAMPLE_AND_HOLD: Update only on logic changes
257 * - CUSTOM: Arbitrary digital transformation
258 */
259 inline void set_modulation_type(ModulationType type) { m_modulation_type = type; }
260
261 /**
262 * @brief Get current modulation type
263 * @return Current modulation type
264 */
265 [[nodiscard]] inline ModulationType get_modulation_type() const { return m_modulation_type; }
266
267 /**
268 * @brief Set custom modulation function
269 *
270 * This sets the ModulationType to CUSTOM and uses the provided
271 * function for modulating the buffer. The function takes two
272 * parameters: the logic value and the buffer value, and returns
273 * the modulated result.
274 *
275 * Use cases:
276 * - Conditional data transformation based on logic state
277 * - Complex digital signal processing operations
278 * - Custom digital filtering based on binary conditions
279 * - Algorithmic decision trees in signal processing
280 *
281 * @param func Custom modulation function
282 */
283 void set_modulation_function(ModulationFunction func);
284
285 /**
286 * @brief Get current modulation function
287 * @return Current modulation function
288 */
289 [[nodiscard]] inline const ModulationFunction& get_modulation_function() const { return m_modulation_function; }
290
291 /**
292 * @brief Sets high and low values for THRESHOLD_REMAP mode
293 * @param high_val Value to use when logic is true (1.0)
294 * @param low_val Value to use when logic is false (0.0)
295 */
296 inline void set_threshold_remap_values(double high_val, double low_val)
297 {
298 m_high_value = high_val;
299 m_low_value = low_val;
300 }
301
302 /**
303 * @brief Gets the high value for THRESHOLD_REMAP mode
304 * @return High value
305 */
306 [[nodiscard]] inline double get_high_value() const { return m_high_value; }
307
308 /**
309 * @brief Gets the low value for THRESHOLD_REMAP mode
310 * @return Low value
311 */
312 [[nodiscard]] inline double get_low_value() const { return m_low_value; }
313
314 /**
315 * @brief Sets whether to reset logic state between buffer calls
316 * @param reset True to reset between buffers, false otherwise
317 *
318 * Controls whether the processor maintains state memory across buffer boundaries,
319 * enabling either stateless processing or continuous state tracking.
320 */
321 inline void set_reset_between_buffers(bool reset) { m_reset_between_buffers = reset; }
322
323 /**
324 * @brief Gets whether logic state is reset between buffer calls
325 * @return True if logic state is reset between buffers
326 */
327 [[nodiscard]] inline bool get_reset_between_buffers() const { return m_reset_between_buffers; }
328
329 /**
330 * @brief Checks if the processor is using the internal logic node
331 * @return True if using internal logic node, false otherwise
332 */
333 [[nodiscard]] inline bool is_using_internal() const { return m_use_internal; }
334
335 /**
336 * @brief Forces the processor to use a new internal logic node
337 *
338 * This replaces the current logic node with a new one constructed from the
339 * provided arguments, ensuring the processor uses its own internal logic
340 * instead of an external one.
341 */
342 template <typename... Args>
343 requires std::constructible_from<Nodes::Generator::Logic, Args...>
344 void force_use_internal(Args&&... args)
345 {
346 m_pending_logic = std::make_shared<Nodes::Generator::Logic>(std::forward<Args>(args)...);
347 }
348
349 /**
350 * @brief Updates the logic node used for processing
351 * @param logic New logic node to use
352 *
353 * NOTE: Using external Logic node implies side effects of any processing chain
354 * the node is connected to. This could mean that the buffer data is not used as
355 * input when the node's cached value is used.
356 */
357 inline void update_logic_node(const std::shared_ptr<Nodes::Generator::Logic>& logic)
358 {
359 m_pending_logic = logic;
360 }
361
362 /**
363 * @brief Gets the logic node used for processing
364 * @return Logic node used for processing
365 */
366 [[nodiscard]] inline std::shared_ptr<Nodes::Generator::Logic> get_logic() const { return m_logic; }
367
368private:
369 std::shared_ptr<Nodes::Generator::Logic> m_logic; ///< Logic node for processing
370 bool m_reset_between_buffers; ///< Whether to reset logic between buffers
371 bool m_use_internal; ///< Whether to use internal logic node
372 ModulationType m_modulation_type; ///< How logic values modulate buffer content
373 std::shared_ptr<Nodes::Generator::Logic> m_pending_logic; ///< Pending logic node update
374
375 ModulationFunction m_modulation_function; ///< Custom transformation function
376 std::vector<double> m_logic_data; ///< Stored logic processing results
377 bool m_has_generated_data; ///< Whether logic data has been generated
378
379 // State for special modulation modes
380 double m_high_value; ///< High value for THRESHOLD_REMAP
381 double m_low_value; ///< Low value for THRESHOLD_REMAP
382 double m_last_held_value; ///< Last held value for HOLD_ON_FALSE and SAMPLE_AND_HOLD
383 double m_last_logic_value; ///< Previous logic value for change detection
384
385 std::vector<std::weak_ptr<AudioBuffer>> m_keys; ///< Buffers read as extra input slots
386 uint32_t m_plane_bits {}; ///< Word width when processing bit planes, 0 when off
387 size_t m_radius {}; ///< Neighbourhood radius of the automaton
388 size_t m_generations {}; ///< Automaton steps per block, 0 when off
389 bool m_wrap { true }; ///< Whether the automaton's block ends meet
390 bool m_memory {}; ///< Whether blocks perturb the previous automaton result
391 std::vector<std::shared_ptr<Nodes::Generator::Logic>> m_plane_nodes; ///< One clone of the node per bit plane
392 std::shared_ptr<Nodes::Generator::Logic> m_plane_template; ///< The node the plane clones were taken from
393 std::vector<std::shared_ptr<AudioBuffer>> m_live_keys; ///< Keys locked for the current block
394 std::vector<uint32_t> m_key_words; ///< Words of the keys at the current sample
395 std::vector<double> m_slots; ///< Input slots handed to the node
396 std::vector<uint8_t> m_cells; ///< Automaton cells
397 std::vector<uint8_t> m_next_cells; ///< Automaton cells of the next generation
398 std::vector<uint8_t> m_previous_cells; ///< Last automaton result, kept when memory is on
399
400 void generate_temporal(size_t num_samples, const std::vector<double>& input_data);
401 void generate_keyed(size_t num_samples, const std::vector<double>& input_data);
402 void generate_planes(size_t num_samples, const std::vector<double>& input_data);
403 void generate_spatial(size_t num_samples, const std::vector<double>& input_data);
404 void lock_keys();
405};
406
407} // namespace MayaFlux::Buffers
float radius
std::shared_ptr< NetworkGeometryBuffer > buffer
vk::PhysicalDeviceType type
Definition VKDevice.cpp:126
Central computational transformation interface for continuous buffer processing.
void force_use_internal(Args &&... args)
Forces the processor to use a new internal logic node.
std::vector< double > m_slots
Input slots handed to the node.
ModulationFunction m_modulation_function
Custom transformation function.
std::vector< std::shared_ptr< Nodes::Generator::Logic > > m_plane_nodes
One clone of the node per bit plane.
LogicProcessor(Args &&... args)
Constructs a LogicProcessor with internal Logic node.
const std::vector< double > & get_logic_data() const
Gets the stored logic data.
ModulationType
Defines how logic values modulate buffer content.
ModulationType get_modulation_type() const
Get current modulation type.
std::vector< double > m_logic_data
Stored logic processing results.
const ModulationFunction & get_modulation_function() const
Get current modulation function.
double get_high_value() const
Gets the high value for THRESHOLD_REMAP mode.
std::vector< uint8_t > m_next_cells
Automaton cells of the next generation.
bool m_has_generated_data
Whether logic data has been generated.
void set_modulation_type(ModulationType type)
Set how logic values modulate buffer content.
std::vector< uint32_t > m_key_words
Words of the keys at the current sample.
double m_last_held_value
Last held value for HOLD_ON_FALSE and SAMPLE_AND_HOLD.
std::shared_ptr< Nodes::Generator::Logic > get_logic() const
Gets the logic node used for processing.
std::vector< uint8_t > m_previous_cells
Last automaton result, kept when memory is on.
double m_high_value
High value for THRESHOLD_REMAP.
bool get_reset_between_buffers() const
Gets whether logic state is reset between buffer calls.
std::function< double(double, double)> ModulationFunction
Function type for custom digital signal transformations.
bool is_using_internal() const
Checks if the processor is using the internal logic node.
std::vector< uint8_t > m_cells
Automaton cells.
double m_low_value
Low value for THRESHOLD_REMAP.
std::shared_ptr< Nodes::Generator::Logic > m_logic
Logic node for processing.
std::shared_ptr< Nodes::Generator::Logic > m_plane_template
The node the plane clones were taken from.
bool has_generated_data() const
Checks if logic data has been generated.
double m_last_logic_value
Previous logic value for change detection.
void on_detach(const std::shared_ptr< Buffer > &) override
Called when the processor is detached from a buffer.
ModulationType m_modulation_type
How logic values modulate buffer content.
void set_threshold_remap_values(double high_val, double low_val)
Sets high and low values for THRESHOLD_REMAP mode.
std::vector< std::weak_ptr< AudioBuffer > > m_keys
Buffers read as extra input slots.
void set_reset_between_buffers(bool reset)
Sets whether to reset logic state between buffer calls.
std::shared_ptr< Nodes::Generator::Logic > m_pending_logic
Pending logic node update.
std::vector< std::shared_ptr< AudioBuffer > > m_live_keys
Keys locked for the current block.
double get_low_value() const
Gets the low value for THRESHOLD_REMAP mode.
bool m_use_internal
Whether to use internal logic node.
bool m_reset_between_buffers
Whether to reset logic between buffers.
void update_logic_node(const std::shared_ptr< Nodes::Generator::Logic > &logic)
Updates the logic node used for processing.
Digital signal processor that applies boolean logic operations to data streams.
Digital signal processor implementing boolean logic operations.
Definition Logic.hpp:180