MayaFlux 0.4.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
GranularWorkflow.cpp
Go to the documentation of this file.
2
5
8
10
12
13namespace {
14
15 std::string resolve_qualifier(AnalysisType type, const std::string& qualifier)
16 {
17 if (!qualifier.empty()) {
18 if (type == AnalysisType::FEATURE && qualifier == "rms")
19 return "mean_energy";
20 if (type == AnalysisType::STATISTICAL && qualifier == "mean")
21 return "mean_stat";
22 return qualifier;
23 }
24 switch (type) {
26 return "mean_energy";
28 return "mean_stat";
29 default:
30 error<std::invalid_argument>(
32 std::source_location::current(),
33 "resolve_qualifier: no default qualifier for AnalysisType {}",
34 static_cast<int>(type));
35 }
36 }
37
38 std::shared_ptr<Kakshya::SoundFileContainer> run_to_container(
39 const std::shared_ptr<Kakshya::SignalSourceContainer>& container,
40 const ExecutionContext& ctx,
41 ComputationContext attribution_context,
43 GrainTaper taper = {})
44 {
45 auto matrix = make_granular_matrix(attribution_context, output, std::move(taper));
46 auto seg_op = matrix->get_operation<SegmentOp>("segment");
47 auto attr_op = matrix->get_operation<AttributeOp>("attribute");
48 auto sort_op = matrix->get_operation<SortOp>("sort");
49 apply_context_parameters(seg_op, ctx);
50 apply_context_parameters(attr_op, ctx);
51 apply_context_parameters(sort_op, ctx);
52
53 auto input = make_granular_input(container);
54 auto seg = seg_op->apply_operation(input);
55 auto attr = attr_op->apply_operation(seg);
56 auto sorted = sort_op->apply_operation(attr);
57
58 std::any result_any;
60 ExecutionContext patched = ctx;
61 if (taper) {
62 patched.execution_metadata["grain_taper"] = taper;
63 }
64 result_any = reconstruct_grains_additive(std::any(sorted), patched);
65 } else {
66 result_any = reconstruct_grains(std::any(sorted), ctx);
67 }
68
69 auto result = safe_any_cast_or_throw<
71
72 return std::dynamic_pointer_cast<Kakshya::SoundFileContainer>(result.data);
73 }
74
75 std::shared_ptr<Kakshya::DynamicSoundStream> run_to_stream(
76 const std::shared_ptr<Kakshya::SignalSourceContainer>& container,
77 const ExecutionContext& ctx,
78 ComputationContext attribution_context,
80 GrainTaper taper = {})
81 {
82 auto matrix = make_granular_matrix(attribution_context, output, std::move(taper));
83 auto seg_op = matrix->get_operation<SegmentOp>("segment");
84 auto attr_op = matrix->get_operation<AttributeOp>("attribute");
85 auto sort_op = matrix->get_operation<SortOp>("sort");
86 apply_context_parameters(seg_op, ctx);
87 apply_context_parameters(attr_op, ctx);
88 apply_context_parameters(sort_op, ctx);
89
90 auto input = make_granular_input(container);
91 auto seg = seg_op->apply_operation(input);
92 auto attr = attr_op->apply_operation(seg);
93 auto sorted = sort_op->apply_operation(attr);
94
95 std::any result_any;
96 if (output == GranularOutput::STREAM_ADDITIVE) {
97 ExecutionContext patched = ctx;
98 if (taper)
99 patched.execution_metadata["grain_taper"] = taper;
100 result_any = reconstruct_grains_additive_stream(std::any(sorted), patched);
101 } else {
102 result_any = reconstruct_grains_stream(std::any(sorted), ctx);
103 }
104
105 auto result = safe_any_cast_or_throw<
107
108 return std::dynamic_pointer_cast<Kakshya::DynamicSoundStream>(result.data);
109 }
110
111} // namespace
112
114 [](const std::any& input, const ExecutionContext& /*ctx*/) -> std::any {
115 auto datum = safe_any_cast_or_throw<Datum<Kakshya::RegionGroup>>(input);
116 const auto& regions = datum.data.regions;
117
118 if (regions.empty()) {
120 std::source_location::current(), "reconstruct_grains: RegionGroup contains no grains");
121 }
122
123 if (!datum.container || !*datum.container) {
125 std::source_location::current(), "reconstruct_grains: no source container in datum");
126 }
127
128 auto source = *datum.container;
129 const auto num_ch = static_cast<uint32_t>(source->get_structure().get_channel_count());
130 const auto org = source->get_structure().organization;
131 const uint32_t sample_rate = [&]() -> uint32_t {
132 if (auto sc = std::dynamic_pointer_cast<Kakshya::SoundStreamContainer>(source))
133 return sc->get_sample_rate();
134 return 48000U;
135 }();
136
137 const auto grain_sz = safe_any_cast_or_default<uint32_t>(
138 datum.data.get_attribute<uint32_t>("grain_size").has_value()
139 ? std::any(datum.data.get_attribute<uint32_t>("grain_size").value())
140 : std::any {},
141 1024U);
142
143 std::vector<std::vector<double>> channel_data(num_ch);
144 for (uint32_t ch = 0; ch < num_ch; ++ch)
145 channel_data[ch].reserve(regions.size() * grain_sz);
146
147 Kakshya::iterate_region_channels(regions, source, num_ch, {},
148 [&channel_data](size_t /*gi*/, uint32_t ch, std::span<double> samples) {
149 channel_data[ch].insert(channel_data[ch].end(), samples.begin(), samples.end());
150 });
151
153 std::dynamic_pointer_cast<Kakshya::SignalSourceContainer>(
154 Kakshya::make_sound_file_container(std::move(channel_data), num_ch, sample_rate, org)),
155 {}
156 };
157};
158
160 [](const std::any& input, const ExecutionContext& ctx) -> std::any {
161 auto datum = safe_any_cast_or_throw<Datum<Kakshya::RegionGroup>>(input);
162 const auto& regions = datum.data.regions;
163
164 if (regions.empty()) {
166 std::source_location::current(), "reconstruct_grains_additive: RegionGroup contains no grains");
167 }
168
169 if (!datum.container || !*datum.container) {
171 std::source_location::current(), "reconstruct_grains_additive: no source container in datum");
172 }
173
174 auto source = *datum.container;
175 const auto num_ch = static_cast<uint32_t>(source->get_structure().get_channel_count());
176 const auto org = source->get_structure().organization;
177 const uint32_t sample_rate = [&]() -> uint32_t {
178 if (auto sc = std::dynamic_pointer_cast<Kakshya::SoundStreamContainer>(source))
179 return sc->get_sample_rate();
180 return 48000U;
181 }();
182
183 const auto grain_sz = safe_any_cast_or_default<uint32_t>(
184 datum.data.get_attribute<uint32_t>("grain_size").has_value()
185 ? std::any(datum.data.get_attribute<uint32_t>("grain_size").value())
186 : std::any {},
187 1024U);
188 const auto hop_sz = safe_any_cast_or_default<uint32_t>(
189 datum.data.get_attribute<uint32_t>("hop_size").has_value()
190 ? std::any(datum.data.get_attribute<uint32_t>("hop_size").value())
191 : std::any {},
192 grain_sz);
193
194 const auto taper = safe_any_cast_or_default<GrainTaper>(
195 ctx.execution_metadata.contains("grain_taper")
196 ? ctx.execution_metadata.at("grain_taper")
197 : std::any {},
198 GrainTaper {});
199
200 const size_t out_len = (regions.size() - 1) * hop_sz + grain_sz;
201 std::vector<std::vector<double>> channel_data(num_ch, std::vector<double>(out_len, 0.0));
202
203 Kakshya::iterate_region_channels(regions, source, num_ch, taper,
204 [&channel_data, hop_sz](size_t gi, uint32_t ch, std::span<double> samples) {
205 const size_t offset = gi * hop_sz;
206 auto& dest = channel_data[ch];
207 for (size_t s = 0; s < samples.size() && offset + s < dest.size(); ++s)
208 dest[offset + s] += samples[s];
209 });
210
212 std::dynamic_pointer_cast<Kakshya::SignalSourceContainer>(
213 Kakshya::make_sound_file_container(std::move(channel_data), num_ch, sample_rate, org)),
214 {}
215 };
216};
217
219 [](const std::any& input, const ExecutionContext& /*ctx*/) -> std::any {
220 auto datum = safe_any_cast_or_throw<Datum<Kakshya::RegionGroup>>(input);
221 const auto& regions = datum.data.regions;
222
223 if (regions.empty()) {
225 std::source_location::current(), "reconstruct_grains_stream: RegionGroup contains no grains");
226 }
227
228 if (!datum.container || !*datum.container) {
230 std::source_location::current(), "reconstruct_grains_stream: no source container in datum");
231 }
232
233 auto source = *datum.container;
234 const auto num_ch = static_cast<uint32_t>(source->get_structure().get_channel_count());
235 const uint32_t sample_rate = [&]() -> uint32_t {
236 if (auto sc = std::dynamic_pointer_cast<Kakshya::SoundStreamContainer>(source))
237 return sc->get_sample_rate();
238 return 48000U;
239 }();
240
241 const auto grain_sz = safe_any_cast_or_default<uint32_t>(
242 datum.data.get_attribute<uint32_t>("grain_size").has_value()
243 ? std::any(datum.data.get_attribute<uint32_t>("grain_size").value())
244 : std::any {},
245 1024U);
246
247 std::vector<std::vector<double>> channel_data(num_ch);
248 for (uint32_t ch = 0; ch < num_ch; ++ch)
249 channel_data[ch].reserve(regions.size() * grain_sz);
250
251 Kakshya::iterate_region_channels(regions, source, num_ch, {},
252 [&channel_data](size_t /*gi*/, uint32_t ch, std::span<double> samples) {
253 channel_data[ch].insert(channel_data[ch].end(), samples.begin(), samples.end());
254 });
255
256 auto stream = std::make_shared<Kakshya::DynamicSoundStream>(sample_rate, num_ch);
257
258 std::vector<std::span<const double>> spans;
259 spans.reserve(num_ch);
260 for (auto& ch : channel_data)
261 spans.emplace_back(ch);
262
263 stream->write_frames(spans, 0);
264
266 std::dynamic_pointer_cast<Kakshya::SignalSourceContainer>(stream), {}
267 };
268};
269
271 [](const std::any& input, const ExecutionContext& ctx) -> std::any {
272 auto datum = safe_any_cast_or_throw<Datum<Kakshya::RegionGroup>>(input);
273 const auto& regions = datum.data.regions;
274
275 if (regions.empty()) {
277 std::source_location::current(), "reconstruct_grains_additive_stream: RegionGroup contains no grains");
278 }
279
280 if (!datum.container || !*datum.container) {
282 std::source_location::current(), "reconstruct_grains_additive_stream: no source container in datum");
283 }
284
285 auto source = *datum.container;
286 const auto num_ch = static_cast<uint32_t>(source->get_structure().get_channel_count());
287 const uint32_t sample_rate = [&]() -> uint32_t {
288 if (auto sc = std::dynamic_pointer_cast<Kakshya::SoundStreamContainer>(source))
289 return sc->get_sample_rate();
290 return 48000U;
291 }();
292
293 const auto grain_sz = safe_any_cast_or_default<uint32_t>(
294 datum.data.get_attribute<uint32_t>("grain_size").has_value()
295 ? std::any(datum.data.get_attribute<uint32_t>("grain_size").value())
296 : std::any {},
297 1024U);
298 const auto hop_sz = safe_any_cast_or_default<uint32_t>(
299 datum.data.get_attribute<uint32_t>("hop_size").has_value()
300 ? std::any(datum.data.get_attribute<uint32_t>("hop_size").value())
301 : std::any {},
302 grain_sz);
303
304 const auto taper = safe_any_cast_or_default<GrainTaper>(
305 ctx.execution_metadata.contains("grain_taper")
306 ? ctx.execution_metadata.at("grain_taper")
307 : std::any {},
308 GrainTaper {});
309
310 const uint64_t out_frames = hop_sz * regions.size() + grain_sz;
311 std::vector<std::vector<double>> channel_data(num_ch, std::vector<double>(out_frames, 0.0));
312
313 Kakshya::iterate_region_channels(regions, source, num_ch, {},
314 [&](size_t gi, uint32_t ch, std::span<double> samples) {
315 std::vector<double> grain(samples.begin(), samples.end());
316 if (taper)
317 taper(grain);
318 const uint64_t offset = gi * hop_sz;
319 for (uint64_t i = 0; i < grain.size() && offset + i < out_frames; ++i)
320 channel_data[ch][offset + i] += grain[i];
321 });
322
323 auto stream = std::make_shared<Kakshya::DynamicSoundStream>(sample_rate, num_ch);
324
325 std::vector<std::span<const double>> spans;
326 spans.reserve(num_ch);
327 for (auto& ch : channel_data)
328 spans.emplace_back(ch);
329
330 stream->write_frames(spans, 0);
331
333 std::dynamic_pointer_cast<Kakshya::SignalSourceContainer>(stream), {}
334 };
335};
336
337// ============================================================================
338// SegmentOp
339// ============================================================================
340
342 const Datum<Kakshya::RegionGroup>& input)
343{
344 std::shared_ptr<Kakshya::SignalSourceContainer> container;
345
346 if (input.container && *input.container) {
347 container = *input.container;
348 } else {
349 auto param = this->get_parameter("container");
350 if (param.has_value()) {
351 container = safe_any_cast_or_default<std::shared_ptr<Kakshya::SignalSourceContainer>>(
352 param, nullptr);
353 }
354 }
355
356 if (!container) {
357 error<std::runtime_error>(
359 std::source_location::current(),
360 "SegmentOp: no container available via Datum or parameters");
361 }
362
363 const auto grain_size = this->template get_parameter_or_default<uint32_t>("grain_size", 1024U);
364 const auto hop_size = this->template get_parameter_or_default<uint32_t>("hop_size", 512U);
365 const auto channel = this->template get_parameter_or_default<uint32_t>("channel", 0U);
366
367 const auto& structure = container->get_structure();
368
369 if (channel >= structure.get_channel_count()) {
370 error<std::runtime_error>(
372 std::source_location::current(),
373 "SegmentOp: channel {} out of range (container has {})",
374 channel, structure.get_channel_count());
375 }
376
377 const auto num_channels = structure.get_channel_count();
378 const auto total = container->get_num_frames();
379
380 Datum<Kakshya::RegionGroup> out { input.data, container };
381 out.data.regions.clear();
382 out.data.current_region_index = 0;
383 out.data.active_indices.clear();
384
385 for (uint64_t onset = 0; onset + grain_size <= total; onset += hop_size) {
386 Kakshya::Region grain {
387 std::vector<uint64_t> { onset, 0 },
388 std::vector<uint64_t> { onset + grain_size - 1, num_channels - 1 }
389 };
390 grain.set_attribute("onset", static_cast<double>(onset));
391 out.data.regions.push_back(std::move(grain));
392 }
393
394 out.data.set_attribute<uint32_t>("grain_size", grain_size);
395 out.data.set_attribute<uint32_t>("hop_size", hop_size);
396 out.data.set_attribute<uint32_t>("channel", channel);
397
398 return out;
399}
400
401// ============================================================================
402// AttributeOp — private helpers
403// ============================================================================
404
405std::shared_ptr<RegionGroupAnalyzer<Kakshya::RegionGroup>>
407{
408 switch (type) {
410 return std::make_shared<EnergyAnalyzer<Kakshya::RegionGroup, Kakshya::RegionGroup>>();
412 return std::make_shared<StatisticalAnalyzer<Kakshya::RegionGroup, Kakshya::RegionGroup>>();
413 default:
414 error<std::invalid_argument>(
416 std::source_location::current(),
417 "make_default_analyzer: AnalysisType {} has no registered default analyzer",
418 static_cast<int>(type));
419 }
420}
421
423 const std::any& analysis_result,
424 AnalysisType type,
425 const std::string& qualifier)
426{
427 const std::string q = resolve_qualifier(type, qualifier);
428
429 if (type == AnalysisType::FEATURE) {
430 const auto& ea = safe_any_cast_or_throw<EnergyAnalysis>(analysis_result);
431 if (ea.channels.empty())
432 return 0.0;
433 const auto& ch = ea.channels[0];
434
435 if (q == "rms" || q == "mean")
436 return ch.mean_energy;
437 if (q == "peak" || q == "max")
438 return ch.max_energy;
439 if (q == "min")
440 return ch.min_energy;
441 if (q == "variance")
442 return ch.variance;
443 if (q == "dynamic_range")
444 return ch.max_energy - ch.min_energy;
445 if (q == "zero_crossing")
446 return ch.event_positions.empty() ? 0.0 : static_cast<double>(ch.event_positions.size());
447
448 return ch.mean_energy;
449 }
450
451 if (type == AnalysisType::STATISTICAL) {
452 const auto& sa = safe_any_cast_or_throw<StatisticalAnalysis>(analysis_result);
453 if (sa.channel_statistics.empty())
454 return 0.0;
455 const auto& ch = sa.channel_statistics[0];
456
457 if (q == "mean")
458 return ch.mean_stat;
459 if (q == "std_dev")
460 return ch.stat_std_dev;
461 if (q == "variance")
462 return ch.stat_variance;
463 if (q == "kurtosis")
464 return ch.kurtosis;
465 if (q == "skewness")
466 return ch.skewness;
467 if (q == "median")
468 return ch.median;
469 if (q == "max")
470 return ch.max_stat;
471 if (q == "min")
472 return ch.min_stat;
473
474 return ch.mean_stat;
475 }
476
477 return 0.0;
478}
479
481 const Kakshya::Region& grain,
482 const std::shared_ptr<Kakshya::SignalSourceContainer>& container,
483 uint32_t channel,
484 const ExecutionContext& ctx) const
485{
487
488 if (ctx.execution_metadata.contains("attribute_executor")) {
489 exec = safe_any_cast_or_default<AttributeExecutor>(
490 ctx.execution_metadata.at("attribute_executor"), nullptr);
491 }
492
493 if (!exec) {
494 if (auto p = this->get_parameter("attribute_executor"); p.has_value())
495 exec = safe_any_cast_or_default<AttributeExecutor>(p, nullptr);
496 }
497
498 if (exec) {
499 const auto samples = Kakshya::extract_region_channel(grain, container, channel);
500 return exec(std::span<const double>(samples.data(), samples.size()), ctx);
501 }
502
503 const auto grain_variants = container->get_region_data(grain);
504
505 const std::string qualifier = [&]() -> std::string {
506 if (ctx.execution_metadata.contains("analyzer_qualifier")) {
507 return safe_any_cast_or_default<std::string>(
508 ctx.execution_metadata.at("analyzer_qualifier"), {});
509 }
510 auto p = this->get_parameter("analyzer_qualifier");
511 return p.has_value() ? safe_any_cast_or_default<std::string>(p, {}) : std::string {};
512 }();
513
514 const AnalysisType type = [&]() {
515 if (ctx.execution_metadata.contains("analysis_type")) {
516 return safe_any_cast_or_default<AnalysisType>(
517 ctx.execution_metadata.at("analysis_type"), AnalysisType::FEATURE);
518 }
519
520 auto p = this->get_parameter("analysis_type");
521 return p.has_value()
522 ? safe_any_cast_or_default<AnalysisType>(p, AnalysisType::FEATURE)
524 }();
525
526 std::shared_ptr<void> raw_analyzer;
527 if (ctx.execution_metadata.contains("analyzer")) {
528 raw_analyzer = safe_any_cast_or_default<std::shared_ptr<void>>(
529 ctx.execution_metadata.at("analyzer"), nullptr);
530 }
531
532 if (!raw_analyzer) {
533 if (auto p = this->get_parameter("analyzer"); p.has_value())
534 raw_analyzer = safe_any_cast_or_default<std::shared_ptr<void>>(p, nullptr);
535 }
536
537 if (raw_analyzer) {
538 if (type == AnalysisType::FEATURE) {
540 std::static_pointer_cast<VariantEnergyAnalyzer<>>(raw_analyzer)
541 ->analyze_energy(grain_variants),
542 qualifier);
543 }
544
545 if (type == AnalysisType::STATISTICAL) {
547 std::static_pointer_cast<VariantStatisticalAnalyzer<>>(raw_analyzer)
548 ->analyze_statistics(grain_variants),
549 qualifier);
550 }
551 return 0.0;
552 }
553
554 if (type == AnalysisType::FEATURE) {
556 m_energy_analyzer.analyze_energy(grain_variants), qualifier);
557 }
558
559 if (type == AnalysisType::STATISTICAL) {
561 m_stat_analyzer.analyze_statistics(grain_variants), qualifier);
562 }
563
564 return 0.0;
565}
566
567// ============================================================================
568// AttributeOp::analyze_implementation
569// ============================================================================
570
572 const Datum<Kakshya::RegionGroup>& input)
573{
574 if (input.data.regions.empty())
575 return input;
576
577 std::shared_ptr<Kakshya::SignalSourceContainer> container;
578
579 if (input.container && *input.container) {
580 container = *input.container;
581 } else {
582 auto param = this->get_parameter("container");
583 if (param.has_value()) {
584 container = safe_any_cast_or_default<std::shared_ptr<Kakshya::SignalSourceContainer>>(
585 param, nullptr);
586 }
587 }
588
589 if (!container) {
590 error<std::runtime_error>(
592 std::source_location::current(),
593 "AttributeOp: no container available via Datum or parameters");
594 }
595
596 const auto channel = this->template get_parameter_or_default<uint32_t>("channel", 0U);
597 const auto feature_key = this->template get_parameter_or_default<std::string>("feature_key", std::string("feature"));
598
600 for (const auto& [k, v] : this->get_all_analysis_parameters())
601 ctx.execution_metadata[k] = v;
602
603 Datum<Kakshya::RegionGroup> out { input.data, container };
604
605 for (auto& grain : out.data.regions) {
606 const double value = compute_grain_attribute(grain, container, channel, ctx);
607 grain.set_attribute(feature_key, value);
608 }
609
610 return out;
611}
612
613// ============================================================================
614// SortOp
615// ============================================================================
616
618 const Datum<Kakshya::RegionGroup>& input)
619{
620 const auto feature_key = this->template get_parameter_or_default<std::string>("feature_key", std::string("feature"));
621 const bool ascending = this->template get_parameter_or_default<bool>("ascending", true);
622 const auto gpu_thresh = this->template get_parameter_or_default<uint32_t>("gpu_sort_threshold", 0U);
623 const auto n = static_cast<uint32_t>(input.data.regions.size());
624
625 Datum<Kakshya::RegionGroup> out = input;
626
627 if (n == 0)
628 return out;
629
630 if (gpu_thresh > 0 && n >= gpu_thresh) {
631 struct SortPC {
632 uint32_t element_count;
633 uint32_t pass_number;
634 uint32_t ascending;
635 };
636
637 std::vector<float> values(n);
638 for (uint32_t i = 0; i < n; ++i) {
639 auto attr = out.data.regions[i].get_attribute<double>(feature_key);
640 values[i] = attr ? static_cast<float>(*attr) : 0.0F;
641 }
642
643 std::vector<uint32_t> indices(n);
644 std::iota(indices.begin(), indices.end(), 0U);
645
646 auto executor = std::make_shared<ShaderExecutionContext<>>(
648 .shader_path = "sort_by_attribute.comp",
649 .workgroup_size = { 256, 1, 1 },
650 .push_constant_size = sizeof(SortPC) });
651
652 executor->in_out(0, values, GpuBufferBinding::ElementType::FLOAT32)
653 .in_out(1, indices, GpuBufferBinding::ElementType::UINT32)
654 .set_output_size(1, n * sizeof(uint32_t));
655
656 const uint32_t n_passes = n * 2;
657 executor->set_multipass(n_passes,
658 [n, ascending](uint32_t pass, void* pc_data) {
659 const SortPC pc {
660 .element_count = n,
661 .pass_number = pass & 1U,
662 .ascending = ascending ? 1U : 0U,
663 };
664 std::memcpy(pc_data, &pc, sizeof(SortPC));
665 });
666
667 auto gpu_sorter = std::make_shared<GpuSorter<>>(executor);
668
670 { Kakshya::DataVariant(std::vector<double>(values.begin(), values.end())) }
671 };
672
673 auto result = gpu_sorter->apply_operation(sort_input);
674 const auto sorted_indices = ShaderExecutionContext<>::read_output<uint32_t>(result, 1);
675
676 std::vector<Kakshya::Region> reordered(n);
677 for (uint32_t i = 0; i < n; ++i)
678 reordered[i] = out.data.regions[sorted_indices[i]];
679
680 out.data.regions = std::move(reordered);
681 out.data.current_region_index = 0;
682 out.data.active_indices.clear();
683 return out;
684 }
685
686 out.data.sort_by_attribute(feature_key);
687
688 if (!ascending)
689 std::ranges::reverse(out.data.regions);
690
691 out.data.current_region_index = 0;
692 out.data.active_indices.clear();
693
694 return out;
695}
696
697// ============================================================================
698// Grammar rule executors
699// ============================================================================
700
702 [](const std::any& input, const ExecutionContext& ctx) -> std::any {
703 auto datum = safe_any_cast_or_throw<Datum<Kakshya::RegionGroup>>(input);
704 auto op = std::make_shared<SegmentOp>();
706 return op->apply_operation(datum);
707};
708
710 [](const std::any& input, const ExecutionContext& ctx) -> std::any {
711 auto datum = safe_any_cast_or_throw<Datum<Kakshya::RegionGroup>>(input);
712 auto op = std::make_shared<AttributeOp>();
714 return op->apply_operation(datum);
715};
716
718 [](const std::any& input, const ExecutionContext& ctx) -> std::any {
719 auto datum = safe_any_cast_or_throw<Datum<Kakshya::RegionGroup>>(input);
720 auto op = std::make_shared<SortOp>();
722 return op->apply_operation(datum);
723};
724
725// ============================================================================
726// make_granular_matrix
727// ============================================================================
728
729std::shared_ptr<GranularMatrix> make_granular_matrix(
730 ComputationContext attribution_context,
731 GranularOutput output,
732 GrainTaper taper)
733{
734 auto matrix = std::make_shared<GranularMatrix>();
735
736 matrix->create_operation<SegmentOp>("segment");
737 matrix->create_operation<AttributeOp>("attribute");
738 matrix->create_operation<SortOp>("sort");
739
740 matrix->get_grammar()->create_rule("segment") //
741 .with_context(ComputationContext::TEMPORAL)
742 .with_priority(100)
743 .with_description("Fixed hop-size grain segmentation")
744 .matches_type<Kakshya::RegionGroup>()
745 .executes(segment_grains)
746 .build();
747
748 matrix->get_grammar()->create_rule("attribute") //
749 .with_context(attribution_context)
750 .with_priority(75)
751 .with_description("Per-grain feature attribution via analyzer or lambda")
752 .matches_type<Kakshya::RegionGroup>()
753 .executes(attribute_grains)
754 .build();
755
756 matrix->get_grammar()->create_rule("sort") //
757 .with_context(ComputationContext::STRUCTURAL)
758 .with_priority(50)
759 .with_description("Attribute-keyed grain sort, CPU or GPU")
760 .matches_type<Kakshya::RegionGroup>()
761 .executes(sort_grains)
762 .build();
763
764 if (output == GranularOutput::CONTAINER) {
765 matrix->get_grammar()->create_rule("reconstruct") //
766 .with_context(ComputationContext::STRUCTURAL)
767 .with_priority(25)
768 .with_description("Stitch sorted grains into a SoundFileContainer")
769 .matches_type<Kakshya::RegionGroup>()
770 .executes(reconstruct_grains)
771 .build();
772 } else if (output == GranularOutput::CONTAINER_ADDITIVE) {
773 auto ola_executor =
774 [t = std::move(taper)](const std::any& in, const ExecutionContext& ctx) -> std::any {
775 ExecutionContext patched = ctx;
776 if (t) {
777 patched.execution_metadata["grain_taper"] = t;
778 }
779 return reconstruct_grains_additive(in, patched);
780 };
781
782 matrix->get_grammar()->create_rule("reconstruct") //
783 .with_context(ComputationContext::STRUCTURAL)
784 .with_priority(25)
785 .with_description("Additive grain reconstruction into SoundFileContainer")
786 .matches_type<Kakshya::RegionGroup>()
787 .executes(ola_executor)
788 .build();
789 } else if (output == GranularOutput::STREAM) {
790 matrix->get_grammar()->create_rule("reconstruct") //
791 .with_context(ComputationContext::STRUCTURAL)
792 .with_priority(25)
793 .with_description("Stitch sorted grains into a DynamicSoundStream")
794 .matches_type<Kakshya::RegionGroup>()
796 .build();
797 } else if (output == GranularOutput::STREAM_ADDITIVE) {
798 auto ola_executor =
799 [t = std::move(taper)](const std::any& in, const ExecutionContext& ctx) -> std::any {
800 ExecutionContext patched = ctx;
801 if (t)
802 patched.execution_metadata["grain_taper"] = t;
803 return reconstruct_grains_additive_stream(in, patched);
804 };
805
806 matrix->get_grammar()->create_rule("reconstruct") //
807 .with_context(ComputationContext::STRUCTURAL)
808 .with_priority(25)
809 .with_description("Additive grain reconstruction into DynamicSoundStream")
810 .matches_type<Kakshya::RegionGroup>()
811 .executes(ola_executor)
812 .build();
813 }
814
815 return matrix;
816}
817
818// ============================================================================
819// process — AnalysisType path
820// ============================================================================
821
823 const std::shared_ptr<Kakshya::SignalSourceContainer>& container,
824 AnalysisType analysis_type,
825 const GranularConfig& config,
826 const std::string& qualifier)
827{
828 auto matrix = make_granular_matrix(config.attribution_context);
829
830 auto ctx = make_granular_context(config, analysis_type, qualifier);
831
832 auto seg_op = matrix->get_operation<SegmentOp>("segment");
833 auto attr_op = matrix->get_operation<AttributeOp>("attribute");
834 auto sort_op = matrix->get_operation<SortOp>("sort");
835 apply_context_parameters(seg_op, ctx);
836 apply_context_parameters(attr_op, ctx);
837 apply_context_parameters(sort_op, ctx);
838
839 return matrix->with(make_granular_input(container))
840 .template then<SegmentOp>("segment")
841 .template then<AttributeOp>("attribute")
842 .template then<SortOp>("sort")
843 .to_io();
844}
845
846// ============================================================================
847// process — AttributeExecutor path
848// ============================================================================
849
851 const std::shared_ptr<Kakshya::SignalSourceContainer>& container,
852 AttributeExecutor executor,
853 const GranularConfig& config)
854{
855 auto matrix = make_granular_matrix(config.attribution_context);
856
857 auto ctx = make_granular_context(config, std::move(executor));
858
859 auto seg_op = matrix->get_operation<SegmentOp>("segment");
860 auto attr_op = matrix->get_operation<AttributeOp>("attribute");
861 auto sort_op = matrix->get_operation<SortOp>("sort");
862 apply_context_parameters(seg_op, ctx);
863 apply_context_parameters(attr_op, ctx);
864 apply_context_parameters(sort_op, ctx);
865
866 return matrix->with(make_granular_input(container))
867 .template then<SegmentOp>("segment")
868 .template then<AttributeOp>("attribute")
869 .template then<SortOp>("sort")
870 .to_io();
871}
872
873// ============================================================================
874// process_to_container — AnalysisType path
875// ============================================================================
876
877std::shared_ptr<Kakshya::SoundFileContainer> process_to_container(
878 const std::shared_ptr<Kakshya::SignalSourceContainer>& container,
879 AnalysisType analysis_type,
880 const GranularConfig& config,
881 const std::string& qualifier,
882 GranularOutput output)
883{
884 auto ctx = make_granular_context(config, analysis_type, qualifier);
885 ctx.execution_metadata["container"] = container;
886
887 return run_to_container(container, ctx, config.attribution_context,
888 output, config.taper);
889}
890
891// ============================================================================
892// process_to_container — AttributeExecutor path
893// ============================================================================
894
895std::shared_ptr<Kakshya::SoundFileContainer> process_to_container(
896 const std::shared_ptr<Kakshya::SignalSourceContainer>& container,
897 AttributeExecutor executor,
898 const GranularConfig& config,
899 GranularOutput output)
900{
901 auto ctx = make_granular_context(config, std::move(executor));
902 ctx.execution_metadata["container"] = container;
903
904 return run_to_container(container, ctx, config.attribution_context,
905 output, config.taper);
906}
907
908// ============================================================================
909// process_to_stream — AnalysisType path
910// ============================================================================
911
912std::shared_ptr<Kakshya::DynamicSoundStream> process_to_stream(
913 const std::shared_ptr<Kakshya::SignalSourceContainer>& container,
914 AnalysisType analysis_type,
915 const GranularConfig& config,
916 const std::string& qualifier,
917 GranularOutput output)
918{
919 auto ctx = make_granular_context(config, analysis_type, qualifier);
920 ctx.execution_metadata["container"] = container;
921
922 return run_to_stream(container, ctx, config.attribution_context,
923 output, config.taper);
924}
925
926// ============================================================================
927// process_to_stream — AttributeExecutor path
928// ============================================================================
929
930std::shared_ptr<Kakshya::DynamicSoundStream> process_to_stream(
931 const std::shared_ptr<Kakshya::SignalSourceContainer>& container,
932 AttributeExecutor executor,
933 const GranularConfig& config,
934 GranularOutput output)
935{
936 auto ctx = make_granular_context(config, std::move(executor));
937 ctx.execution_metadata["container"] = container;
938
939 return run_to_stream(container, ctx, config.attribution_context,
940 output, config.taper);
941}
942
943} // namespace MayaFlux::Yantra::Granular
double q
uint32_t channel
output_type apply_operation(const input_type &input)
Public synchronous execution interface.
EnergyAnalysis analyze_energy(const input_type &data)
Type-safe energy analysis method.
static double extract_scalar(const std::any &analysis_result, AnalysisType type, const std::string &qualifier)
Extract a named scalar from a typed analysis result.
Datum< Kakshya::RegionGroup > analyze_implementation(const Datum< Kakshya::RegionGroup > &input) override
double compute_grain_attribute(const Kakshya::Region &grain, const std::shared_ptr< Kakshya::SignalSourceContainer > &container, uint32_t channel, const ExecutionContext &ctx) const
Resolve and execute attribution for a single grain.
static std::shared_ptr< RegionGroupAnalyzer< Kakshya::RegionGroup > > make_default_analyzer(AnalysisType type)
Construct a default-configured analyzer for the given AnalysisType.
Computes a scalar attribute per grain and writes it onto each Region.
Datum< Kakshya::RegionGroup > extract_implementation(const Datum< Kakshya::RegionGroup > &input) override
Segments container audio into fixed hop-size grain windows.
Datum< Kakshya::RegionGroup > sort_implementation(const Datum< Kakshya::RegionGroup > &input) override
Sorts grains by a named attribute key, with optional GPU dispatch.
Concrete GpuExecutionContext for a single fixed shader with fixed bindings.
StatisticalAnalysis analyze_statistics(const input_type &data)
Type-safe statistical analysis method.
std::any get_parameter(const std::string &name) const override
Retrieves a parameter's current value.
virtual std::map< std::string, std::any > get_all_analysis_parameters() const
std::any get_parameter(const std::string &name) const override
@ ComputeMatrix
Compute operations (Yantra - algorithms, matrices, DSP)
@ Yantra
DSP algorithms, computational units, matrix operations, Grammar.
std::variant< std::vector< double >, std::vector< float >, std::vector< uint8_t >, std::vector< uint16_t >, std::vector< uint32_t >, std::vector< std::complex< float > >, std::vector< std::complex< double > >, std::vector< glm::vec2 >, std::vector< glm::vec3 >, std::vector< glm::vec4 >, std::vector< glm::mat4 > > DataVariant
Multi-type data storage for different precision needs.
Definition NDData.hpp:76
std::shared_ptr< SoundFileContainer > make_sound_file_container(std::vector< std::vector< double > > channel_data, uint32_t num_channels, uint32_t sample_rate, OrganizationStrategy org)
Construct a fully populated SoundFileContainer from computed channel data.
void iterate_region_channels(const std::vector< Region > &regions, const std::shared_ptr< SignalSourceContainer > &source, uint32_t num_channels, const RegionTaper &taper, const RegionWriteFn &write_fn)
Iterate over a set of regions, extracting per-channel samples and dispatching them to a write callbac...
std::vector< double > extract_region_channel(const Region &region, const std::shared_ptr< SignalSourceContainer > &container, uint32_t channel)
Extract samples for a single channel within a Region from a container.
const ComputationGrammar::Rule::Executor sort_grains
Grammar rule executor for the sort step.
const ComputationGrammar::Rule::Executor reconstruct_grains_stream
Grammar rule executor — concatenative reconstruct into DynamicSoundStream.
const ComputationGrammar::Rule::Executor attribute_grains
Grammar rule executor for the attribution step.
GranularOutput
Selects the terminal output type produced by make_granular_matrix.
std::function< double(std::span< const double >, const ExecutionContext &)> AttributeExecutor
Span-level escape hatch for fully custom per-grain feature computation.
ExecutionContext make_granular_context(const GranularConfig &config, AnalysisType analysis_type, const std::string &qualifier={})
Construct an ExecutionContext for the granular pipeline using a built-in AnalysisType.
GranularDatum make_granular_input(const std::shared_ptr< Kakshya::SignalSourceContainer > &container, const std::string &group_name="grains")
Construct the initial GranularDatum from a container.
std::shared_ptr< GranularMatrix > make_granular_matrix(ComputationContext attribution_context, GranularOutput output, GrainTaper taper)
Construct a GranularMatrix with grammar rules appropriate for the requested output type.
const ComputationGrammar::Rule::Executor segment_grains
Grammar rule executor for the segmentation step.
const ComputationGrammar::Rule::Executor reconstruct_grains_additive_stream
Grammar rule executor — OLA reconstruct into DynamicSoundStream.
Kakshya::RegionTaper GrainTaper
Per-grain taper applied before accumulation in OLA reconstruction.
GranularDatum process(const std::shared_ptr< Kakshya::SignalSourceContainer > &container, AnalysisType analysis_type, const GranularConfig &config, const std::string &qualifier)
Run segment -> attribute -> sort and return the attributed RegionGroup.
const ComputationGrammar::Rule::Executor reconstruct_grains_additive
Grammar rule executor for additive grain reconstruction.
std::shared_ptr< Kakshya::SoundFileContainer > process_to_container(const std::shared_ptr< Kakshya::SignalSourceContainer > &container, AnalysisType analysis_type, const GranularConfig &config, const std::string &qualifier, GranularOutput output)
Offline granular pipeline terminating in a SoundFileContainer.
const ComputationGrammar::Rule::Executor reconstruct_grains
Grammar rule executor for the reconstruction step.
std::shared_ptr< Kakshya::DynamicSoundStream > process_to_stream(const std::shared_ptr< Kakshya::SignalSourceContainer > &container, AnalysisType analysis_type, const GranularConfig &config, const std::string &qualifier, GranularOutput output)
Offline granular pipeline terminating in a DynamicSoundStream.
AnalysisType
Categories of analysis operations for discovery and organization.
@ FEATURE
Feature extraction and characterization.
@ STATISTICAL
Mean, variance, distribution analysis.
MAYAFLUX_API double extract_scalar_energy(const EnergyAnalysis &analysis, const std::string &qualifier)
Extract a named scalar from an EnergyAnalysis result.
void apply_context_parameters(std::shared_ptr< OperationType > operation, const ExecutionContext &ctx)
Applies context parameters to an operation.
ComputationContext
Defines the computational contexts in which rules can be applied.
@ STRUCTURAL
Graph/tree operations.
@ TEMPORAL
Time-domain operations.
MAYAFLUX_API double extract_scalar_statistics(const StatisticalAnalysis &analysis, const std::string &qualifier)
Extract a named scalar from a StatisticalAnalysis result.
Organizes related signal regions into a categorized collection.
void set_attribute(const std::string &key, std::any value)
Set an attribute value by key.
Definition Region.hpp:339
Represents a point or span in N-dimensional space.
Definition Region.hpp:67
std::function< std::any(const std::any &, const ExecutionContext &)> Executor
Type alias for matcher functions used in computation rules.
T data
The actual computation data.
Definition DataIO.hpp:25
std::optional< std::shared_ptr< Kakshya::SignalSourceContainer > > container
Optional reference to container, required for regions.
Definition DataIO.hpp:31
Input/Output container for computation pipeline data flow with structure preservation.
Definition DataIO.hpp:24
std::unordered_map< std::string, std::any > execution_metadata
Arbitrary metadata parameters used by operations.
Context information controlling how a compute operation executes.
Plain-data description of the compute shader to dispatch.
Scalar parameters shared across all granular pipeline entry points.