MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
LogicProcessor.cpp
Go to the documentation of this file.
1#include "LogicProcessor.hpp"
2
5
6namespace MayaFlux::Buffers {
7
9 const std::shared_ptr<Nodes::Generator::Logic>& logic,
10 bool reset_between_buffers)
11 : m_logic(logic)
12 , m_reset_between_buffers(reset_between_buffers)
13 , m_use_internal(false)
14 , m_modulation_type(ModulationType::REPLACE)
15 , m_has_generated_data(false)
16 , m_high_value(1.0)
17 , m_low_value(0.0)
18 , m_last_held_value(0.0)
19 , m_last_logic_value(0.0)
20{
21}
22
23bool LogicProcessor::generate(size_t num_samples, const std::vector<double>& input_data)
24{
25 if (!m_logic || input_data.empty()) {
26 return false;
27 }
28
29 if (m_pending_logic) {
31 m_pending_logic.reset();
32 m_use_internal = true;
33 }
34
35 m_logic_data.resize(num_samples, 0);
36
38 m_logic->reset();
39 for (const auto& node : m_plane_nodes) {
40 node->reset();
41 }
42 }
43
44 const bool guarded = m_logic->m_state.load() != Nodes::NodeState::INACTIVE;
45 if (guarded) {
46 m_logic->save_state();
47 }
48
49 const bool parallel = m_logic->get_mode() == Nodes::Generator::LogicMode::MULTI_INPUT;
50
51 if (m_generations > 0 && parallel) {
52 generate_spatial(num_samples, input_data);
53 } else if (m_plane_bits > 0) {
54 generate_planes(num_samples, input_data);
55 } else if (!m_keys.empty() && parallel) {
56 generate_keyed(num_samples, input_data);
57 } else {
58 generate_temporal(num_samples, input_data);
59 }
60
61 if (guarded) {
62 m_logic->restore_state();
63 }
64
66 return true;
67}
68
69void LogicProcessor::generate_temporal(size_t num_samples, const std::vector<double>& input_data)
70{
71 const size_t available = input_data.size();
72 for (size_t i = 0; i < num_samples; ++i) {
73 m_logic_data[i] = m_logic->process_sample(i < available ? input_data[i] : 0.0);
74 }
75}
76
78{
79 m_live_keys.clear();
80 for (const auto& key : m_keys) {
81 m_live_keys.push_back(key.lock());
82 }
83}
84
85void LogicProcessor::generate_keyed(size_t num_samples, const std::vector<double>& input_data)
86{
87 lock_keys();
88
89 const size_t available = input_data.size();
90 m_slots.assign(m_live_keys.size() + 1, 0.0);
91
92 for (size_t i = 0; i < num_samples; ++i) {
93 for (size_t k = 0; k < m_live_keys.size(); ++k) {
94 const auto& key = m_live_keys[k];
95 m_slots[k] = (key && i < key->get_data().size()) ? key->get_data()[i] : 0.0;
96 }
97 m_slots.back() = i < available ? input_data[i] : 0.0;
98 m_logic_data[i] = m_logic->process_multi_input(m_slots);
99 }
100}
101
102void LogicProcessor::generate_planes(size_t num_samples, const std::vector<double>& input_data)
103{
104 const uint32_t bits = std::clamp(m_plane_bits, uint32_t { 1 }, uint32_t { 24 });
105
106 if (m_plane_nodes.size() != bits || m_plane_template != m_logic) {
107 m_plane_nodes.clear();
108 for (uint32_t plane = 0; plane < bits; ++plane) {
109 m_plane_nodes.push_back(m_logic->clone());
110 }
112 }
113
114 const bool parallel = m_logic->get_mode() == Nodes::Generator::LogicMode::MULTI_INPUT;
115
116 lock_keys();
117 const size_t key_count = parallel ? m_live_keys.size() : 0;
118 m_key_words.assign(key_count, 0);
119 m_slots.assign(key_count + 1, 0.0);
120
121 const size_t available = input_data.size();
122 const uint32_t silence = Kinesis::Discrete::to_word(0.0, bits);
123
124 for (size_t i = 0; i < num_samples; ++i) {
125 const uint32_t own = Kinesis::Discrete::to_word(i < available ? input_data[i] : 0.0, bits);
126 for (size_t k = 0; k < key_count; ++k) {
127 const auto& key = m_live_keys[k];
128 m_key_words[k] = (key && i < key->get_data().size())
129 ? Kinesis::Discrete::to_word(key->get_data()[i], bits)
130 : silence;
131 }
132
133 uint32_t word = 0;
134 for (uint32_t plane = 0; plane < bits; ++plane) {
135 const auto bit_of = [plane](uint32_t w) { return static_cast<double>((w >> plane) & 1U); };
136
137 double out = 0.0;
138 if (parallel) {
139 for (size_t k = 0; k < key_count; ++k) {
140 m_slots[k] = bit_of(m_key_words[k]);
141 }
142 m_slots.back() = bit_of(own);
143 out = m_plane_nodes[plane]->process_multi_input(m_slots);
144 } else {
145 out = m_plane_nodes[plane]->process_sample(bit_of(own));
146 }
147 word |= (out > 0.5 ? 1U : 0U) << plane;
148 }
149
151 }
152}
153
154void LogicProcessor::generate_spatial(size_t num_samples, const std::vector<double>& input_data)
155{
156 if (num_samples == 0) {
157 return;
158 }
159
160 const size_t available = input_data.size();
161 const size_t width = 2 * m_radius + 1;
162 const auto count = static_cast<std::ptrdiff_t>(num_samples);
163
164 m_cells.assign(num_samples, 0);
165 m_next_cells.assign(num_samples, 0);
166 m_slots.assign(width, 0.0);
167
168 for (size_t i = 0; i < num_samples; ++i) {
169 uint8_t cell = (i < available && input_data[i] > 0.0) ? 1 : 0;
170 if (m_memory && i < m_previous_cells.size()) {
171 cell ^= m_previous_cells[i];
172 }
173 m_cells[i] = cell;
174 }
175
176 for (size_t generation = 0; generation < m_generations; ++generation) {
177 for (std::ptrdiff_t i = 0; i < count; ++i) {
178 for (size_t k = 0; k < width; ++k) {
179 std::ptrdiff_t at = i + static_cast<std::ptrdiff_t>(m_radius) - static_cast<std::ptrdiff_t>(k);
180 double cell = 0.0;
181 if (at >= 0 && at < count) {
182 cell = m_cells[static_cast<size_t>(at)];
183 } else if (m_wrap) {
184 at = ((at % count) + count) % count;
185 cell = m_cells[static_cast<size_t>(at)];
186 }
187 m_slots[k] = cell;
188 }
189 m_next_cells[static_cast<size_t>(i)] = m_logic->process_multi_input(m_slots) > 0.5 ? 1 : 0;
190 }
191 m_cells.swap(m_next_cells);
192 }
193
194 for (size_t i = 0; i < num_samples; ++i) {
195 m_logic_data[i] = m_cells[i];
196 }
197 if (m_memory) {
199 }
200}
201
202void LogicProcessor::add_key(const std::shared_ptr<AudioBuffer>& buffer)
203{
204 if (buffer) {
205 m_keys.push_back(buffer);
206 }
207}
208
210{
211 m_keys.clear();
212}
213
215{
216 m_plane_bits = bits == 0 ? 0 : std::clamp(bits, uint32_t { 1 }, uint32_t { 24 });
217 m_plane_nodes.clear();
218 m_plane_template.reset();
219}
220
221void LogicProcessor::set_neighbourhood(size_t radius, size_t generations, bool wrap, bool memory)
222{
224 m_generations = generations;
225 m_wrap = wrap;
226 m_memory = memory;
227 m_previous_cells.clear();
228}
229
235
236bool LogicProcessor::apply(const std::shared_ptr<Buffer>& buffer, ModulationFunction modulation_func)
237{
238 if (!buffer || !m_has_generated_data) {
239 return false;
240 }
241
242 auto& buffer_data = std::dynamic_pointer_cast<AudioBuffer>(buffer)->get_data();
243 size_t min_size = std::min(m_logic_data.size(), buffer_data.size());
244
245 if (!modulation_func) {
246 switch (m_modulation_type) {
248 modulation_func = [](double logic_val, double /*buffer_val*/) {
249 return logic_val;
250 };
251 break;
252
254 modulation_func = [](double logic_val, double buffer_val) {
255 return logic_val * buffer_val;
256 };
257 break;
258
260 modulation_func = [](double logic_val, double buffer_val) {
261 return logic_val + buffer_val;
262 };
263 break;
264
266 modulation_func = [](double logic_val, double buffer_val) {
267 return logic_val > 0.5 ? -buffer_val : buffer_val;
268 };
269 break;
270
272 if (min_size > 0) {
273 m_last_held_value = buffer_data[0];
274 }
275
276 modulation_func = [this](double logic_val, double buffer_val) mutable {
277 if (logic_val > 0.5) {
278 m_last_held_value = buffer_val;
279 return buffer_val;
280 }
281 return m_last_held_value;
282 };
283 break;
284
286 modulation_func = [](double logic_val, double buffer_val) {
287 return logic_val > 0.5 ? buffer_val : 0.0;
288 };
289 break;
290
292 modulation_func = [](double logic_val, double buffer_val) {
293 return buffer_val * logic_val;
294 };
295 break;
296
298 modulation_func = [this](double logic_val, double /*buffer_val*/) {
299 return logic_val > 0.5 ? m_high_value : m_low_value;
300 };
301 break;
302
304 if (min_size > 0) {
305 m_last_held_value = buffer_data[0];
307 }
308
309 modulation_func = [this, first_sample = true](double logic_val, double buffer_val) mutable {
310 if (first_sample) {
311 first_sample = false;
312 return buffer_val;
313 }
314
315 bool logic_changed = std::abs(logic_val - m_last_logic_value) > 0.01;
316 m_last_logic_value = logic_val;
317
318 if (logic_changed) {
319 m_last_held_value = buffer_val;
320 }
321 return m_last_held_value;
322 };
323 break;
324
326 modulation_func = m_modulation_function;
327 break;
328
329 default:
330 modulation_func = [](double logic_val, double /*buffer_val*/) {
331 return logic_val;
332 };
333 }
334 }
335
336 for (size_t i = 0; i < min_size; ++i) {
337 buffer_data[i] = modulation_func(m_logic_data[i], buffer_data[i]);
338 }
339
340 return true;
341}
342
343void LogicProcessor::processing_function(const std::shared_ptr<Buffer>& buffer)
344{
345 if (!m_logic || !buffer) {
346 return;
347 }
348
349 auto audio_buffer = std::dynamic_pointer_cast<AudioBuffer>(buffer);
350 if (!audio_buffer || audio_buffer->get_data().empty()) {
351 return;
352 }
353
354 generate(audio_buffer->get_num_samples(), audio_buffer->get_data());
355 apply(buffer);
356}
357
358void LogicProcessor::on_attach(const std::shared_ptr<Buffer>& /*buffer*/)
359{
360 if (m_logic) {
361 m_logic->reset();
362 }
363
364 for (const auto& node : m_plane_nodes) {
365 node->reset();
366 }
367 m_previous_cells.clear();
368
369 m_last_held_value = 0.0;
370 m_last_logic_value = 0.0;
371}
372
373std::shared_ptr<LogicProcessor> LogicProcessor::clone() const
374{
375 if (!m_logic) {
376 return std::make_shared<LogicProcessor>(std::shared_ptr<Nodes::Generator::Logic> {}, m_reset_between_buffers);
377 }
378
379 auto copy = std::make_shared<LogicProcessor>(m_logic->clone(), m_reset_between_buffers);
380 copy->m_use_internal = true;
381 copy->m_modulation_type = m_modulation_type;
382 copy->m_modulation_function = m_modulation_function;
383 copy->m_high_value = m_high_value;
384 copy->m_low_value = m_low_value;
385 copy->m_keys = m_keys;
386 copy->m_plane_bits = m_plane_bits;
387 copy->m_radius = m_radius;
388 copy->m_generations = m_generations;
389 copy->m_wrap = m_wrap;
390 copy->m_memory = m_memory;
391 return copy;
392}
393
399
400} // namespace MayaFlux::Buffers
float radius
std::shared_ptr< NetworkGeometryBuffer > buffer
std::vector< float > * out
bool available
Definition VKDevice.cpp:41
size_t count
uint32_t width
Samples read as fixed width binary words.
std::vector< double > m_slots
Input slots handed to the node.
void on_attach(const std::shared_ptr< Buffer > &buffer) override
Called when the processor is attached to a buffer.
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.
size_t m_generations
Automaton steps per block, 0 when off.
ModulationType
Defines how logic values modulate buffer content.
@ MULTIPLY
Gate/mask buffer: out = logic * buffer (standard audio gate)
@ SAMPLE_AND_HOLD
Sample on logic change: out = logic_changed ? buffer : held_value.
@ REPLACE
Replace buffer with logic values: out = logic.
@ CUSTOM
User-defined modulation function.
@ ZERO_ON_FALSE
Silence when logic is false: out = logic ? buffer : 0.0.
@ INVERT_ON_TRUE
Invert signal when logic is true: out = logic ? -buffer : buffer.
@ ADD
Offset buffer: out = logic + buffer.
@ CROSSFADE
Smooth interpolation: out = lerp(0.0, buffer, logic)
@ HOLD_ON_FALSE
Hold last value when logic is false: out = logic ? buffer : last_value.
@ THRESHOLD_REMAP
Binary value selection: out = logic ? high_val : low_val.
std::vector< double > m_logic_data
Stored logic processing results.
bool m_wrap
Whether the automaton's block ends meet.
std::vector< uint8_t > m_next_cells
Automaton cells of the next generation.
bool m_has_generated_data
Whether logic data has been generated.
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.
void generate_temporal(size_t num_samples, const std::vector< double > &input_data)
void set_neighbourhood(size_t radius, size_t generations=1, bool wrap=true, bool memory=false)
Treats the samples of a block as cells of an automaton.
std::vector< uint8_t > m_previous_cells
Last automaton result, kept when memory is on.
double m_high_value
High value for THRESHOLD_REMAP.
std::function< double(double, double)> ModulationFunction
Function type for custom digital signal transformations.
std::vector< uint8_t > m_cells
Automaton cells.
bool generate(size_t num_samples, const std::vector< double > &input_data)
Generates discrete logic data from input without modifying any buffer.
size_t m_radius
Neighbourhood radius of the automaton.
uint32_t m_plane_bits
Word width when processing bit planes, 0 when off.
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.
void generate_planes(size_t num_samples, const std::vector< double > &input_data)
void generate_keyed(size_t num_samples, const std::vector< double > &input_data)
double m_last_logic_value
Previous logic value for change detection.
void set_bit_planes(uint32_t bits)
Processes every sample as a word of bit streams, one node per bit.
ModulationType m_modulation_type
How logic values modulate buffer content.
void set_modulation_function(ModulationFunction func)
Set custom modulation function.
bool m_memory
Whether blocks perturb the previous automaton result.
void clear_keys()
Removes every key.
std::vector< std::weak_ptr< AudioBuffer > > m_keys
Buffers read as extra input slots.
std::shared_ptr< LogicProcessor > clone() const
An independent processor over a clone of the node.
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.
void clear_neighbourhood()
Turns the automaton off.
bool m_use_internal
Whether to use internal logic node.
void add_key(const std::shared_ptr< AudioBuffer > &buffer)
Reads another buffer as an extra input slot of a parallel node.
void processing_function(const std::shared_ptr< Buffer > &buffer) override
Processes a buffer through the logic node.
bool apply(const std::shared_ptr< Buffer > &buffer, ModulationFunction modulation_func=nullptr)
Applies stored logic data to the given buffer.
bool m_reset_between_buffers
Whether to reset logic between buffers.
void generate_spatial(size_t num_samples, const std::vector< double > &input_data)
double from_word(uint32_t word, uint32_t bits) noexcept
The value of an offset binary word; bits above the width are ignored.
Definition Word.cpp:22
uint32_t to_word(double value, uint32_t bits) noexcept
The offset binary word of a value, clamped to [-1, 1].
Definition Word.cpp:16
@ MULTI_INPUT
Parallel evaluation of multiple input signals.
@ INACTIVE
Engine is not processing this node.
Definition NodeSpec.hpp:44