MayaFlux 0.5.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;
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
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
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
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
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 step;
634 uint32_t stage;
635 uint32_t ascending;
636 };
637
638 std::vector<float> values(n);
639 for (uint32_t i = 0; i < n; ++i) {
640 auto attr = out.data.regions[i].get_attribute<double>(feature_key);
641 values[i] = attr ? static_cast<float>(*attr) : 0.0F;
642 }
643
644 std::vector<uint32_t> indices(n);
645 std::iota(indices.begin(), indices.end(), 0U);
646
647 /// @brief Pad element count to next power of two for bitonic correctness.
648 /// Out-of-range threads early-exit in the shader via element_count.
649 uint32_t n_padded = 1U;
650 while (n_padded < n)
651 n_padded <<= 1U;
652
653 /// @brief Total bitonic passes: sum of (stage+1) for stage in [0, stage_count).
654 uint32_t stage_count = 0U;
655 {
656 uint32_t tmp = n_padded;
657 while (tmp > 1U) {
658 ++stage_count;
659 tmp >>= 1U;
660 }
661 }
662 const uint32_t n_passes = stage_count * (stage_count + 1U) / 2U;
663
664 auto executor = std::make_shared<ShaderExecutionContext<>>(
666 .shader_path = "sort_by_attribute.comp",
667 .workgroup_size = { 256, 1, 1 },
668 .push_constant_size = sizeof(SortPC) });
669
670 executor->in_out(0, values, GpuBufferBinding::ElementType::FLOAT32)
671 .in_out(1, indices, GpuBufferBinding::ElementType::UINT32)
672 .set_output_size(1, n * sizeof(uint32_t));
673
674 executor->set_multipass(n_passes,
675 [n, ascending](uint32_t pass, void* pc_data) {
676 /// @brief Recover (stage, step) from the flat pass index.
677 /// Passes enumerate: stage=0 step=0,
678 /// stage=1 step=0,1,
679 /// stage=2 step=0,1,2, ...
680 uint32_t stage = 0U;
681 uint32_t remaining = pass;
682 while (remaining >= stage + 1U) {
683 remaining -= stage + 1U;
684 ++stage;
685 }
686 const uint32_t step = stage - remaining;
687
688 const SortPC pc {
689 .element_count = n,
690 .step = step,
691 .stage = stage,
692 .ascending = ascending ? 1U : 0U,
693 };
694 std::memcpy(pc_data, &pc, sizeof(SortPC));
695 });
696
697 auto gpu_sorter = std::make_shared<GpuSorter<>>(executor);
698
700 { Kakshya::DataVariant(std::vector<double>(values.begin(), values.end())) }
701 };
702
703 auto result = gpu_sorter->apply_operation(sort_input);
704 const auto sorted_indices = ShaderExecutionContext<>::read_output<uint32_t>(result, 1);
705
706 std::vector<Kakshya::Region> reordered(n);
707 for (uint32_t i = 0; i < n; ++i)
708 reordered[i] = out.data.regions[sorted_indices[i]];
709
710 out.data.regions = std::move(reordered);
711 out.data.current_region_index = 0;
712 out.data.active_indices.clear();
713 return out;
714 }
715
716 out.data.sort_by_attribute(feature_key);
717
718 if (!ascending)
719 std::ranges::reverse(out.data.regions);
720
721 out.data.current_region_index = 0;
722 out.data.active_indices.clear();
723
724 return out;
725}
726
727// ============================================================================
728// Grammar rule executors
729// ============================================================================
730
732 [](const std::any& input, const ExecutionContext& ctx) -> std::any {
733 auto datum = safe_any_cast_or_throw<Datum<Kakshya::RegionGroup>>(input);
734 auto op = std::make_shared<SegmentOp>();
736 return op->apply_operation(datum);
737};
738
740 [](const std::any& input, const ExecutionContext& ctx) -> std::any {
741 auto datum = safe_any_cast_or_throw<Datum<Kakshya::RegionGroup>>(input);
742 auto op = std::make_shared<AttributeOp>();
744 return op->apply_operation(datum);
745};
746
748 [](const std::any& input, const ExecutionContext& ctx) -> std::any {
749 auto datum = safe_any_cast_or_throw<Datum<Kakshya::RegionGroup>>(input);
750 auto op = std::make_shared<SortOp>();
752 return op->apply_operation(datum);
753};
754
755// ============================================================================
756// make_granular_matrix
757// ============================================================================
758
759std::shared_ptr<GranularMatrix> make_granular_matrix(
760 ComputationContext attribution_context,
762 GrainTaper taper)
763{
764 auto matrix = std::make_shared<GranularMatrix>();
765
766 matrix->create_operation<SegmentOp>("segment");
767 matrix->create_operation<AttributeOp>("attribute");
768 matrix->create_operation<SortOp>("sort");
769
770 matrix->get_grammar()->create_rule("segment") //
771 .with_context(ComputationContext::TEMPORAL)
772 .with_priority(100)
773 .with_description("Fixed hop-size grain segmentation")
774 .matches_type<Kakshya::RegionGroup>()
775 .executes(segment_grains)
776 .build();
777
778 matrix->get_grammar()->create_rule("attribute") //
779 .with_context(attribution_context)
780 .with_priority(75)
781 .with_description("Per-grain feature attribution via analyzer or lambda")
782 .matches_type<Kakshya::RegionGroup>()
783 .executes(attribute_grains)
784 .build();
785
786 matrix->get_grammar()->create_rule("sort") //
787 .with_context(ComputationContext::STRUCTURAL)
788 .with_priority(50)
789 .with_description("Attribute-keyed grain sort, CPU or GPU")
790 .matches_type<Kakshya::RegionGroup>()
791 .executes(sort_grains)
792 .build();
793
795 matrix->get_grammar()->create_rule("reconstruct") //
796 .with_context(ComputationContext::STRUCTURAL)
797 .with_priority(25)
798 .with_description("Stitch sorted grains into a SoundFileContainer")
799 .matches_type<Kakshya::RegionGroup>()
800 .executes(reconstruct_grains)
801 .build();
803 auto ola_executor =
804 [t = std::move(taper)](const std::any& in, const ExecutionContext& ctx) -> std::any {
805 ExecutionContext patched = ctx;
806 if (t) {
807 patched.execution_metadata["grain_taper"] = t;
808 }
809 return reconstruct_grains_additive(in, patched);
810 };
811
812 matrix->get_grammar()->create_rule("reconstruct") //
813 .with_context(ComputationContext::STRUCTURAL)
814 .with_priority(25)
815 .with_description("Additive grain reconstruction into SoundFileContainer")
816 .matches_type<Kakshya::RegionGroup>()
817 .executes(ola_executor)
818 .build();
819 } else if (output == GranularOutput::STREAM) {
820 matrix->get_grammar()->create_rule("reconstruct") //
821 .with_context(ComputationContext::STRUCTURAL)
822 .with_priority(25)
823 .with_description("Stitch sorted grains into a DynamicSoundStream")
824 .matches_type<Kakshya::RegionGroup>()
826 .build();
828 auto ola_executor =
829 [t = std::move(taper)](const std::any& in, const ExecutionContext& ctx) -> std::any {
830 ExecutionContext patched = ctx;
831 if (t)
832 patched.execution_metadata["grain_taper"] = t;
833 return reconstruct_grains_additive_stream(in, patched);
834 };
835
836 matrix->get_grammar()->create_rule("reconstruct") //
837 .with_context(ComputationContext::STRUCTURAL)
838 .with_priority(25)
839 .with_description("Additive grain reconstruction into DynamicSoundStream")
840 .matches_type<Kakshya::RegionGroup>()
841 .executes(ola_executor)
842 .build();
843 }
844
845 return matrix;
846}
847
848// ============================================================================
849// process — AnalysisType path
850// ============================================================================
851
853 const std::shared_ptr<Kakshya::SignalSourceContainer>& container,
854 AnalysisType analysis_type,
855 const GranularConfig& config,
856 const std::string& qualifier)
857{
858 auto matrix = make_granular_matrix(config.attribution_context);
859
860 auto ctx = make_granular_context(config, analysis_type, qualifier);
861
862 auto seg_op = matrix->get_operation<SegmentOp>("segment");
863 auto attr_op = matrix->get_operation<AttributeOp>("attribute");
864 auto sort_op = matrix->get_operation<SortOp>("sort");
865 apply_context_parameters(seg_op, ctx);
866 apply_context_parameters(attr_op, ctx);
867 apply_context_parameters(sort_op, ctx);
868
869 return matrix->with(make_granular_input(container))
870 .template then<SegmentOp>("segment")
871 .template then<AttributeOp>("attribute")
872 .template then<SortOp>("sort")
873 .to_io();
874}
875
876// ============================================================================
877// process — AttributeExecutor path
878// ============================================================================
879
881 const std::shared_ptr<Kakshya::SignalSourceContainer>& container,
882 AttributeExecutor executor,
883 const GranularConfig& config)
884{
885 auto matrix = make_granular_matrix(config.attribution_context);
886
887 auto ctx = make_granular_context(config, std::move(executor));
888
889 auto seg_op = matrix->get_operation<SegmentOp>("segment");
890 auto attr_op = matrix->get_operation<AttributeOp>("attribute");
891 auto sort_op = matrix->get_operation<SortOp>("sort");
892 apply_context_parameters(seg_op, ctx);
893 apply_context_parameters(attr_op, ctx);
894 apply_context_parameters(sort_op, ctx);
895
896 return matrix->with(make_granular_input(container))
897 .template then<SegmentOp>("segment")
898 .template then<AttributeOp>("attribute")
899 .template then<SortOp>("sort")
900 .to_io();
901}
902
903// ============================================================================
904// process_to_container — AnalysisType path
905// ============================================================================
906
907std::shared_ptr<Kakshya::SoundFileContainer> process_to_container(
908 const std::shared_ptr<Kakshya::SignalSourceContainer>& container,
909 AnalysisType analysis_type,
910 const GranularConfig& config,
911 const std::string& qualifier,
913{
914 auto ctx = make_granular_context(config, analysis_type, qualifier);
915 ctx.execution_metadata["container"] = container;
916
917 return run_to_container(container, ctx, config.attribution_context,
918 output, config.taper);
919}
920
921// ============================================================================
922// process_to_container — AttributeExecutor path
923// ============================================================================
924
925std::shared_ptr<Kakshya::SoundFileContainer> process_to_container(
926 const std::shared_ptr<Kakshya::SignalSourceContainer>& container,
927 AttributeExecutor executor,
928 const GranularConfig& config,
930{
931 auto ctx = make_granular_context(config, std::move(executor));
932 ctx.execution_metadata["container"] = container;
933
934 return run_to_container(container, ctx, config.attribution_context,
935 output, config.taper);
936}
937
938// ============================================================================
939// process_to_stream — AnalysisType path
940// ============================================================================
941
942std::shared_ptr<Kakshya::DynamicSoundStream> process_to_stream(
943 const std::shared_ptr<Kakshya::SignalSourceContainer>& container,
944 AnalysisType analysis_type,
945 const GranularConfig& config,
946 const std::string& qualifier,
948{
949 auto ctx = make_granular_context(config, analysis_type, qualifier);
950 ctx.execution_metadata["container"] = container;
951
952 return run_to_stream(container, ctx, config.attribution_context,
953 output, config.taper);
954}
955
956// ============================================================================
957// process_to_stream — AttributeExecutor path
958// ============================================================================
959
960std::shared_ptr<Kakshya::DynamicSoundStream> process_to_stream(
961 const std::shared_ptr<Kakshya::SignalSourceContainer>& container,
962 AttributeExecutor executor,
963 const GranularConfig& config,
965{
966 auto ctx = make_granular_context(config, std::move(executor));
967 ctx.execution_metadata["container"] = container;
968
969 return run_to_stream(container, ctx, config.attribution_context,
970 output, config.taper);
971}
972
973} // namespace MayaFlux::Yantra::Granular
Core::GlobalStreamInfo stream
Definition Config.cpp:36
Core::GlobalInputConfig input
Definition Config.cpp:38
double q
float value
std::shared_ptr< Core::VKImage > output
float offset
uint32_t pass
float k
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:102
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.
const ComputationGrammar::Rule::Executor reconstruct_grains_additive_stream
Grammar rule executor — OLA reconstruct into DynamicSoundStream.
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
Grammar rule executor for the reconstruction step.
Kakshya::RegionTaper GrainTaper
Per-grain taper applied before accumulation in OLA reconstruction.
const ComputationGrammar::Rule::Executor reconstruct_grains_additive
Grammar rule executor for additive grain 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.
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.
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:345
Represents a point or span in N-dimensional space.
Definition Region.hpp:73
enum MayaFlux::Portal::Graphics::GpuBufferBinding::ElementType FLOAT32
Plain-data description of the compute shader to dispatch.
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.
Scalar parameters shared across all granular pipeline entry points.