MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
DynamicSoundStream.cpp
Go to the documentation of this file.
3
5
6namespace MayaFlux::Kakshya {
7
8DynamicSoundStream::DynamicSoundStream(uint32_t sample_rate, uint32_t num_channels)
9 : SoundStreamContainer(sample_rate, num_channels)
10 , m_auto_resize(true)
11{
12}
13
14uint64_t DynamicSoundStream::validate(std::vector<std::span<const double>>& data, uint64_t start_frame)
15{
16 if (data.empty() || data[0].empty()) {
17 return 0;
18 }
19
20 uint64_t num_frames {};
22 num_frames = data[0].size() / get_num_channels();
23
24 } else {
25 if (data.size() < get_num_channels()) {
26 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing, "Insufficient channel data for planar organization: expected {}, got {}", get_num_channels(), data.size());
27
28 return 0;
29 }
30 num_frames = data[0].size();
31
32 if (!std::ranges::all_of(data | std::views::drop(1) | std::views::take(get_num_channels() - 1),
33 [num_frames](const auto& span) { return span.size() == num_frames; })) {
34 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing, "Mismatched frame counts across channels in planar organization. Expected {} frames, but found a channel with a different frame count.", num_frames);
35 return 0;
36 }
37 }
38
39 if (num_frames == 0) {
40 MF_WARN(Journal::Component::Kakshya, Journal::Context::ContainerProcessing, "Attempting to write with insufficient data for complete frame. Data frames: {}, Channels: {}. Returning without writing.", num_frames, get_num_channels());
41 return 0;
42 }
43
44 if (uint64_t required_end_frame = start_frame + num_frames; m_auto_resize) {
45 if (required_end_frame > get_num_frames()) {
46 expand_to(required_end_frame);
47 }
48 } else {
49 uint64_t available_frames = (start_frame < get_num_frames()) ? (get_num_frames() - start_frame) : 0;
50
51 if (available_frames == 0) {
52 return 0;
53 }
54
55 if (num_frames > available_frames) {
56 num_frames = available_frames;
57 }
58
59 if (required_end_frame > m_num_frames) {
60 m_num_frames = required_end_frame;
62 }
63 }
64 return num_frames;
65}
66
67uint64_t DynamicSoundStream::validate_single_channel(std::span<const double> data, uint64_t start_frame, uint32_t channel)
68{
69 if (data.empty()) {
70 return 0;
71 }
72
73 if (channel >= get_num_channels()) {
74 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing, "Channel index {} exceeds available channels ({})", channel, get_num_channels());
75 return 0;
76 }
77
78 uint64_t num_frames = data.size();
79 uint64_t required_end_frame = start_frame + num_frames;
80
81 if (m_auto_resize) {
82 if (required_end_frame > get_num_frames()) {
83 expand_to(required_end_frame);
84 }
85 } else {
86 uint64_t available_frames = (start_frame < get_num_frames()) ? (get_num_frames() - start_frame) : 0;
87
88 if (available_frames == 0) {
89 return 0;
90 }
91
92 if (num_frames > available_frames) {
93 num_frames = available_frames;
94 }
95
96 if (required_end_frame > m_num_frames) {
97 m_num_frames = required_end_frame;
99 }
100 }
101
102 return num_frames;
103}
104
105uint64_t DynamicSoundStream::write_frames(std::vector<std::span<const double>> data, uint64_t start_frame)
106{
107 auto num_frames = validate(data, start_frame);
108
109 if (!num_frames)
110 return 0;
111
112 if (m_is_circular && start_frame + num_frames > m_circular_capacity) {
113 uint64_t frames_to_end = m_circular_capacity - start_frame;
114 uint64_t frames_from_start = num_frames - frames_to_end;
115
116 if (frames_to_end > 0) {
117 std::vector<std::span<const double>> first_part;
118 first_part.reserve(data.size());
119 for (const auto& span : data) {
120 first_part.emplace_back(span.subspan(0, frames_to_end));
121 }
122 write_frames(first_part, start_frame);
123 }
124
125 if (frames_from_start > 0) {
126 std::vector<std::span<const double>> second_part;
127 second_part.reserve(data.size());
128 for (const auto& span : data) {
129 second_part.emplace_back(span.subspan(frames_to_end, frames_from_start));
130 }
131 write_frames(second_part, 0);
132 }
133
134 return num_frames;
135 }
136
137 Region write_region {
138 { start_frame, 0 },
139 { start_frame + num_frames - 1, get_num_channels() - 1 }
140 };
141
142 std::vector<DataVariant> data_variants;
143
145 uint64_t samples_to_write = num_frames * get_num_channels();
146 data_variants.emplace_back(
147 std::vector<double>(data[0].begin(), data[0].begin() + samples_to_write));
148 } else {
149 data_variants = data
150 | std::views::take(get_num_channels())
151 | std::views::transform([num_frames](const auto& span) -> DataVariant {
152 return DataVariant(std::vector<double>(span.begin(), span.begin() + num_frames));
153 })
154 | std::ranges::to<std::vector>();
155 }
156
157 set_region_data(write_region, data_variants);
158
159 if (m_is_circular) {
161 }
162
163 return num_frames;
164}
165
167 std::span<const double> data, uint64_t start_frame, uint32_t channel)
168{
169 auto num_frames = validate_single_channel(data, start_frame, channel);
170 if (!num_frames)
171 return 0;
172
173 if (m_is_circular && start_frame + num_frames > m_circular_capacity) {
174 uint64_t frames_to_end = m_circular_capacity - start_frame;
175 uint64_t frames_from_start = num_frames - frames_to_end;
176 if (frames_to_end > 0)
177 write_frames(data.subspan(0, frames_to_end), start_frame, channel);
178 if (frames_from_start > 0)
179 write_frames(data.subspan(frames_to_end, frames_from_start), 0, channel);
180 return num_frames;
181 }
182
185 if (m_data.empty())
186 expand_to(start_frame + num_frames);
187
188 auto& interleaved_data = std::get<std::vector<double>>(m_data[0]);
189 const uint32_t num_channels = get_num_channels();
190
191 for (uint64_t frame = 0; frame < num_frames; ++frame) {
192 uint64_t idx = (start_frame + frame) * num_channels + channel;
193 if (idx < interleaved_data.size())
194 interleaved_data[idx] = data[frame];
195 }
196 } else {
198 if (channel >= m_data.size())
199 return 0;
200
201 auto dest_span = convert_variant<double>(m_data[channel]);
202 if (start_frame + num_frames > dest_span.size()) {
203 std::vector<double> current_data(dest_span.begin(), dest_span.end());
204 current_data.resize(start_frame + num_frames, 0.0);
205 m_data[channel] = DataVariant(std::move(current_data));
206 dest_span = convert_variant<double>(m_data[channel]);
207 }
208
209 std::copy(data.begin(), data.begin() + num_frames,
210 dest_span.begin() + start_frame);
211 }
212
213 if (m_is_circular)
215
217 m_double_extraction_dirty.store(true, std::memory_order_release);
218 return num_frames;
219}
220
221uint64_t DynamicSoundStream::append_frames(std::span<const double> data, uint32_t channel)
222{
223 if (data.empty() || channel >= get_num_channels()) {
224 return 0;
225 }
226
227 if (m_write_heads.size() < get_num_channels()) {
228 m_write_heads.resize(get_num_channels(), 0);
229 }
230
231 uint64_t& head = m_write_heads[channel];
232
233 if (!m_is_circular) {
234 const uint64_t written = write_frames(data, head, channel);
235 head += written;
236 return written;
237 }
238
239 if (data.size() > m_circular_capacity) {
240 const uint64_t skip = data.size() - m_circular_capacity;
241 head = (head + skip) % m_circular_capacity;
242 data = data.subspan(skip);
243 }
244
245 const uint64_t first = std::min<uint64_t>(data.size(), m_circular_capacity - head);
246 uint64_t written = write_frames(data.subspan(0, first), head, channel);
247
248 if (first < data.size()) {
249 written += write_frames(data.subspan(first), 0, channel);
250 }
251
252 head = (head + data.size()) % m_circular_capacity;
253 return written;
254}
255
256std::shared_ptr<DynamicSoundStream> DynamicSoundStream::snapshot(uint64_t frames) const
257{
258 const uint64_t head = get_write_head(0);
259 const uint64_t available = m_is_circular ? m_circular_capacity : head;
260 const uint64_t count = std::min(frames, available);
261
262 if (count == 0) {
263 return nullptr;
264 }
265
266 const uint64_t first = m_is_circular ? head + m_circular_capacity - count : head - count;
267
268 std::vector<uint64_t> indices(count);
269 for (uint64_t i = 0; i < count; ++i) {
270 indices[i] = m_is_circular ? (first + i) % m_circular_capacity : first + i;
271 }
272
273 const uint64_t channels = get_num_channels();
274 std::vector<double> taps(count * channels);
275 gather_frames(indices, taps);
276
277 auto copy = std::make_shared<DynamicSoundStream>(get_sample_rate(), get_num_channels());
278
279 if (copy->get_structure().organization == OrganizationStrategy::INTERLEAVED) {
280 copy->write_frames(std::vector<std::span<const double>> { taps }, 0);
281 return copy;
282 }
283
284 std::vector<std::vector<double>> planar(channels, std::vector<double>(count));
285 for (uint64_t i = 0; i < count; ++i) {
286 for (uint64_t c = 0; c < channels; ++c) {
287 planar[c][i] = taps[i * channels + c];
288 }
289 }
290
291 std::vector<std::span<const double>> spans(planar.begin(), planar.end());
292 copy->write_frames(std::move(spans), 0);
293 return copy;
294}
295
296std::span<const double> DynamicSoundStream::get_channel_frames(uint32_t channel, uint64_t start_frame, uint64_t num_frames) const
297{
298 if (channel >= get_num_channels())
299 return {};
300
303 "Direct span access not supported for interleaved data. "
304 "Use get_frames() or Kakshya::extract_channel_data() instead.");
305 return {};
306 }
307
308 std::span<const double> result;
309 seqlock_read_void(m_data_lock, 8, [&] {
310 if (channel >= m_data.size())
311 return;
312 const auto& channel_data = std::get<std::vector<double>>(m_data[channel]);
313 if (start_frame >= channel_data.size())
314 return;
315 uint64_t available_frames = channel_data.size() - start_frame;
316 uint64_t actual_frames = std::min(num_frames, available_frames);
317 result = { channel_data.data() + start_frame, actual_frames };
318 });
319 return result;
320}
321
322void DynamicSoundStream::get_channel_frames(std::span<double> output, uint32_t channel, uint64_t start_frame) const
323{
324 if (channel >= get_num_channels() || output.empty())
325 return;
326
327 const uint64_t num_frames = output.size();
328
330 seqlock_read_void(m_data_lock, 8, [&] {
331 if (m_data.empty()) {
332 std::ranges::fill(output, 0.0);
333 return;
334 }
335 const auto& interleaved_data = std::get<std::vector<double>>(m_data[0]);
336 const uint32_t num_channels = get_num_channels();
337 for (uint64_t frame = 0; frame < num_frames; ++frame) {
338 uint64_t idx = (start_frame + frame) * num_channels + channel;
339 output[frame] = (idx < interleaved_data.size()) ? interleaved_data[idx] : 0.0;
340 }
341 });
342 } else {
343 seqlock_read_void(m_data_lock, 8, [&] {
344 if (channel >= m_data.size()) {
345 std::ranges::fill(output, 0.0);
346 return;
347 }
348 const auto& channel_data = std::get<std::vector<double>>(m_data[channel]);
349 for (uint64_t frame = 0; frame < num_frames; ++frame) {
350 uint64_t idx = start_frame + frame;
351 output[frame] = (idx < channel_data.size()) ? channel_data[idx] : 0.0;
352 }
353 });
354 }
355}
356
357void DynamicSoundStream::ensure_capacity(uint64_t required_frames)
358{
359 if (uint64_t current_frames = get_total_elements() / get_num_channels();
360 required_frames > current_frames) {
361 expand_to(required_frames);
362 }
363}
364
366{
367 ensure_capacity(capacity);
368
369 Region circular_region {
370 { 0, 0 },
371 { capacity - 1, get_num_channels() - 1 }
372 };
373
374 set_loop_region(circular_region);
375 set_looping(true);
376
377 m_circular_capacity = capacity;
378 m_is_circular = true;
379
380 for (uint64_t& head : m_write_heads) {
381 head %= capacity;
382 }
383}
384
386{
387 for (uint32_t i = 0; i < m_dynamic_slots.size(); ++i) {
388 if (!m_dynamic_slots[i]) {
389 m_dynamic_slots[i] = true;
390 m_dynamic_data[i].clear();
391 return i;
392 }
393 }
394
395 m_dynamic_data.emplace_back();
396 m_dynamic_slots.push_back(true);
397 return static_cast<uint32_t>(m_dynamic_slots.size() - 1);
398}
399
401{
402 if (index < m_dynamic_slots.size()) {
403 m_dynamic_slots[index] = false;
404 m_dynamic_data[index].clear();
405 }
406}
407
408std::vector<DataVariant>& DynamicSoundStream::get_dynamic_data(uint32_t index)
409{
410 return m_dynamic_data.at(index);
411}
412
413const std::vector<DataVariant>& DynamicSoundStream::get_dynamic_data(uint32_t index) const
414{
415 return m_dynamic_data.at(index);
416}
417
424
425void DynamicSoundStream::set_all_data(const std::vector<DataVariant>& data)
426{
427 {
429 m_data.resize(data.size());
430 std::ranges::for_each(std::views::zip(data, m_data),
431 [](auto&& pair) {
432 auto&& [source, dest] = pair;
433 safe_copy_data_variant(source, dest);
434 });
435
436 m_num_frames = std::visit([](const auto& vec) {
437 return static_cast<uint64_t>(vec.size());
438 },
439 m_data[0]);
442
444 }
445
447}
448
450{
451 set_all_data(std::vector<DataVariant> { data });
452}
453
454void DynamicSoundStream::expand_to(uint64_t target_frames)
455{
456 uint64_t current_frames = get_total_elements() / get_num_channels();
457 uint64_t new_capacity = std::max(target_frames, current_frames * 2);
458
459 std::vector<DataVariant> new_data = create_expanded_data(new_capacity);
460 set_all_data(new_data);
461}
462
463std::vector<DataVariant> DynamicSoundStream::create_expanded_data(uint64_t new_frame_count)
464{
466 std::vector<DataVariant> expanded_data(1);
467
468 if (m_data.empty()) {
469 expanded_data[0] = DataVariant(std::vector<double>(new_frame_count * get_num_channels(), 0.0));
470 } else {
471 std::vector<double> current_data;
472 extract_from_variant(m_data[0], current_data);
473
474 std::vector<double> expanded_buffer(new_frame_count * get_num_channels(), 0.0);
475
476 std::ranges::copy_n(current_data.begin(),
477 std::min<size_t>(current_data.size(), expanded_buffer.size()),
478 expanded_buffer.begin());
479
480 expanded_data[0] = DataVariant(std::move(expanded_buffer));
481 }
482 return expanded_data;
483 }
484
485 return std::views::iota(0U, get_num_channels())
486 | std::views::transform([this, new_frame_count](uint32_t ch) -> DataVariant {
487 if (ch < m_data.size()) {
488 std::vector<double> current_channel_data;
489 extract_from_variant(m_data[ch], current_channel_data);
490
491 std::vector<double> expanded_channel(new_frame_count, 0.0);
492 std::ranges::copy_n(current_channel_data.begin(),
493 std::min<size_t>(current_channel_data.size(), expanded_channel.size()),
494 expanded_channel.begin());
495
496 return { std::move(expanded_channel) };
497 }
498
499 return { std::vector<double>(new_frame_count, 0.0) };
500 })
501 | std::ranges::to<std::vector>();
502}
503
504}
#define MF_ERROR(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
std::vector< uint32_t > indices
bool available
Definition VKDevice.cpp:41
uint32_t index
Definition VKDevice.cpp:122
size_t count
uint32_t channels
uint32_t allocate_dynamic_slot()
Allocate an independent processed data slot.
std::vector< uint64_t > m_write_heads
Next append_frames index per channel.
uint64_t validate_single_channel(std::span< const double > data, uint64_t start_frame=0, uint32_t channel=0)
bool m_auto_resize
Enable automatic capacity expansion.
DynamicSoundStream(uint32_t sample_rate=48000, uint32_t num_channels=2)
Construct a DynamicSoundStream with specified audio parameters.
std::vector< DataVariant > create_expanded_data(uint64_t new_frame_count)
bool m_is_circular
True when operating in circular buffer mode.
void enable_circular_buffer(uint64_t capacity)
Enable circular buffer mode with fixed capacity.
void ensure_capacity(uint64_t required_frames)
Pre-allocate capacity for the specified number of frames.
uint64_t get_write_head(uint32_t channel=0) const
Index of the next frame append_frames will write for a channel.
void set_all_data(const DataVariant &new_data)
void disable_circular_buffer()
Disable circular buffer mode and return to linear operation.
std::shared_ptr< DynamicSoundStream > snapshot(uint64_t frames) const
Copy the most recent frames into a new stream, oldest first.
std::span< const double > get_channel_frames(uint32_t channel, uint64_t start_frame, uint64_t num_frames) const
Get the fixed capacity of the circular buffer if enabled.
uint64_t validate(std::vector< std::span< const double > > &data, uint64_t start_frame=0)
uint64_t append_frames(std::span< const double > data, uint32_t channel=0)
Write one channel at its write head and advance the head.
uint64_t write_frames(std::span< const double > data, uint64_t start_frame=0, uint32_t channel=0)
Write audio frame data to the container with automatic capacity management.
void release_dynamic_slot(uint32_t index)
Release a previously allocated dynamic slot, clearing its data.
std::vector< std::vector< DataVariant > > m_dynamic_data
uint64_t m_circular_capacity
Fixed capacity for circular mode.
std::vector< DataVariant > & get_dynamic_data(uint32_t index)
Access a dynamic processed data slot by index.
void set_looping(bool enable) override
Enable or disable looping behavior for the stream.
uint64_t get_total_elements() const override
Get the total number of elements in the container.
void update_processing_state(ProcessingState new_state) override
Update the processing state of the container.
void gather_frames(std::span< const uint64_t > frames, std::span< double > out) const
Copy arbitrary frames into a caller-owned buffer without allocating.
void invalidate_span_cache()
Invalidate the span cache when data or layout changes.
DataAccess channel_data(size_t channel) override
Get channel data with semantic interpretation.
uint64_t get_num_frames() const override
Get the number of frames in the primary (temporal) dimension.
void set_region_data(const Region &region, const std::vector< DataVariant > &data) override
Set data for a specific region.
void set_loop_region(const Region &region) override
Set the loop region using a Region.
Concrete base implementation for streaming audio containers.
RAII guard that brackets a Seqlock write region.
Definition SeqLock.hpp:155
@ ContainerProcessing
Container operations (Kakshya - file/stream/region processing)
@ Kakshya
Containers[Signalsource, Stream, File], Regions, DataProcessors.
@ READY
Container has data loaded and is ready for processing.
std::span< T > extract_from_variant(const DataVariant &variant, std::vector< T > &storage, ComplexConversionStrategy strategy=ComplexConversionStrategy::MAGNITUDE)
Get typed span from DataVariant using concepts.
void safe_copy_data_variant(const DataVariant &input, DataVariant &output)
Safely copy data from a DataVariant to another DataVariant, handling type conversion.
Definition DataUtils.cpp:36
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:70
@ INTERLEAVED
Single DataVariant with interleaved data (LRLRLR for stereo)
Represents a point or span in N-dimensional space.
Definition Region.hpp:73