MayaFlux 0.4.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
DynamicSoundStream.cpp
Go to the documentation of this file.
3
4namespace MayaFlux::Kakshya {
5
6DynamicSoundStream::DynamicSoundStream(uint32_t sample_rate, uint32_t num_channels)
7 : SoundStreamContainer(sample_rate, num_channels)
8 , m_auto_resize(true)
9{
10}
11
12uint64_t DynamicSoundStream::validate(std::vector<std::span<const double>>& data, uint64_t start_frame)
13{
14 if (data.empty() || data[0].empty()) {
15 return 0;
16 }
17
18 uint64_t num_frames {};
20 num_frames = data[0].size() / get_num_channels();
21
22 } else {
23 if (data.size() < get_num_channels()) {
24 std::cerr << "Insufficient channel data for planar organization" << '\n';
25 return 0;
26 }
27 num_frames = data[0].size();
28
29 if (!std::ranges::all_of(data | std::views::drop(1) | std::views::take(get_num_channels() - 1),
30 [num_frames](const auto& span) { return span.size() == num_frames; })) {
31 std::cerr << "Mismatched frame counts across channels" << '\n';
32 return 0;
33 }
34 }
35
36 if (num_frames == 0) {
37 std::cerr << "Attempting to write to container with insufficient data for complete frame. Returning" << '\n';
38 return 0;
39 }
40
41 if (uint64_t required_end_frame = start_frame + num_frames; m_auto_resize) {
42 if (required_end_frame > get_num_frames()) {
43 expand_to(required_end_frame);
44 }
45 } else {
46 uint64_t available_frames = (start_frame < get_num_frames()) ? (get_num_frames() - start_frame) : 0;
47
48 if (available_frames == 0) {
49 return 0;
50 }
51
52 if (num_frames > available_frames) {
53 num_frames = available_frames;
54 }
55
56 if (required_end_frame > m_num_frames) {
57 m_num_frames = required_end_frame;
59 }
60 }
61 return num_frames;
62}
63
64uint64_t DynamicSoundStream::validate_single_channel(std::span<const double> data, uint64_t start_frame, uint32_t channel)
65{
66 if (data.empty()) {
67 return 0;
68 }
69
70 if (channel >= get_num_channels()) {
71 std::cerr << "Channel index " << channel << " exceeds available channels (" << get_num_channels() << ")" << '\n';
72 return 0;
73 }
74
75 uint64_t num_frames = data.size();
76 uint64_t required_end_frame = start_frame + num_frames;
77
78 if (m_auto_resize) {
79 if (required_end_frame > get_num_frames()) {
80 expand_to(required_end_frame);
81 }
82 } else {
83 uint64_t available_frames = (start_frame < get_num_frames()) ? (get_num_frames() - start_frame) : 0;
84
85 if (available_frames == 0) {
86 return 0;
87 }
88
89 if (num_frames > available_frames) {
90 num_frames = available_frames;
91 }
92
93 if (required_end_frame > m_num_frames) {
94 m_num_frames = required_end_frame;
96 }
97 }
98
99 return num_frames;
100}
101
102uint64_t DynamicSoundStream::write_frames(std::vector<std::span<const double>> data, uint64_t start_frame)
103{
104 auto num_frames = validate(data, start_frame);
105
106 if (!num_frames)
107 return 0;
108
109 if (m_is_circular && start_frame + num_frames > m_circular_capacity) {
110 uint64_t frames_to_end = m_circular_capacity - start_frame;
111 uint64_t frames_from_start = num_frames - frames_to_end;
112
113 if (frames_to_end > 0) {
114 std::vector<std::span<const double>> first_part;
115 first_part.reserve(data.size());
116 for (const auto& span : data) {
117 first_part.emplace_back(span.subspan(0, frames_to_end));
118 }
119 write_frames(first_part, start_frame);
120 }
121
122 if (frames_from_start > 0) {
123 std::vector<std::span<const double>> second_part;
124 second_part.reserve(data.size());
125 for (const auto& span : data) {
126 second_part.emplace_back(span.subspan(frames_to_end, frames_from_start));
127 }
128 write_frames(second_part, 0);
129 }
130
131 return num_frames;
132 }
133
134 Region write_region {
135 { start_frame, 0 },
136 { start_frame + num_frames - 1, get_num_channels() - 1 }
137 };
138
139 std::vector<DataVariant> data_variants;
140
142 uint64_t samples_to_write = num_frames * get_num_channels();
143 data_variants.emplace_back(
144 std::vector<double>(data[0].begin(), data[0].begin() + samples_to_write));
145 } else {
146 data_variants = data
147 | std::views::take(get_num_channels())
148 | std::views::transform([num_frames](const auto& span) -> DataVariant {
149 return DataVariant(std::vector<double>(span.begin(), span.begin() + num_frames));
150 })
151 | std::ranges::to<std::vector>();
152 }
153
154 set_region_data(write_region, data_variants);
155
156 if (m_is_circular) {
158 }
159
160 return num_frames;
161}
162
163uint64_t DynamicSoundStream::write_frames(std::span<const double> data, uint64_t start_frame, uint32_t channel)
164{
165 auto num_frames = validate_single_channel(data, start_frame, channel);
166
167 if (!num_frames)
168 return 0;
169
170 if (m_is_circular && start_frame + num_frames > m_circular_capacity) {
171 uint64_t frames_to_end = m_circular_capacity - start_frame;
172 uint64_t frames_from_start = num_frames - frames_to_end;
173
174 if (frames_to_end > 0) {
175 write_frames(data.subspan(0, frames_to_end), start_frame, channel);
176 }
177
178 if (frames_from_start > 0) {
179 write_frames(data.subspan(frames_to_end, frames_from_start), 0, channel);
180 }
181
182 return num_frames;
183 }
184
186 std::unique_lock lock(m_data_mutex);
187
188 if (m_data.empty()) {
189 expand_to(start_frame + num_frames);
190 }
191
192 auto& interleaved_data = std::get<std::vector<double>>(m_data[0]);
193 uint32_t num_channels = get_num_channels();
194
195 for (uint64_t frame = 0; frame < num_frames; ++frame) {
196 uint64_t interleaved_index = (start_frame + frame) * num_channels + channel;
197 if (interleaved_index < interleaved_data.size()) {
198 interleaved_data[interleaved_index] = data[frame];
199 }
200 }
201 } else {
202 std::unique_lock lock(m_data_mutex);
203
204 if (channel >= m_data.size()) {
205 return 0;
206 }
207
208 auto dest_span = convert_variant<double>(m_data[channel]);
209
210 if (start_frame + num_frames > dest_span.size()) {
211 std::vector<double> current_data(dest_span.begin(), dest_span.end());
212 current_data.resize(start_frame + num_frames, 0.0);
213 m_data[channel] = DataVariant(std::move(current_data));
214 dest_span = convert_variant<double>(m_data[channel]);
215 }
216
217 std::copy(data.begin(), data.begin() + num_frames,
218 dest_span.begin() + start_frame);
219 }
220
221 if (m_is_circular) {
223 }
224
226 m_double_extraction_dirty.store(true, std::memory_order_release);
227
228 return num_frames;
229}
230
231std::span<const double> DynamicSoundStream::get_channel_frames(uint32_t channel, uint64_t start_frame, uint64_t num_frames) const
232{
233 if (channel >= get_num_channels()) {
234 return {};
235 }
236
238 std::cerr << "Direct span access not supported for interleaved data. Use get_frames() or Kakshya::extract_channel_data() instead." << '\n';
239 return {};
240 }
241
242 std::shared_lock lock(m_data_mutex);
243
244 if (channel >= m_data.size()) {
245 return {};
246 }
247
248 const auto& channel_data = std::get<std::vector<double>>(m_data[channel]);
249
250 if (start_frame >= channel_data.size()) {
251 return {};
252 }
253
254 uint64_t available_frames = channel_data.size() - start_frame;
255 uint64_t actual_frames = std::min(num_frames, available_frames);
256
257 return { channel_data.data() + start_frame, actual_frames };
258}
259
260void DynamicSoundStream::get_channel_frames(std::span<double> output, uint32_t channel, uint64_t start_frame) const
261{
262 if (channel >= get_num_channels() || output.empty()) {
263 return;
264 }
265
266 uint64_t num_frames = output.size();
267
269 std::shared_lock lock(m_data_mutex);
270
271 if (m_data.empty()) {
272 std::ranges::fill(output, 0.0);
273 return;
274 }
275
276 const auto& interleaved_data = std::get<std::vector<double>>(m_data[0]);
277 uint32_t num_channels = get_num_channels();
278
279 for (uint64_t frame = 0; frame < num_frames; ++frame) {
280 uint64_t interleaved_index = (start_frame + frame) * num_channels + channel;
281 if (interleaved_index < interleaved_data.size()) {
282 output[frame] = interleaved_data[interleaved_index];
283 } else {
284 output[frame] = 0.0;
285 }
286 }
287 } else {
288 std::shared_lock lock(m_data_mutex);
289
290 if (channel >= m_data.size()) {
291 std::ranges::fill(output, 0.0);
292 return;
293 }
294
295 const auto& channel_data = std::get<std::vector<double>>(m_data[channel]);
296
297 for (uint64_t frame = 0; frame < num_frames; ++frame) {
298 uint64_t data_index = start_frame + frame;
299 if (data_index < channel_data.size()) {
300 output[frame] = channel_data[data_index];
301 } else {
302 output[frame] = 0.0;
303 }
304 }
305 }
306}
307
308void DynamicSoundStream::ensure_capacity(uint64_t required_frames)
309{
310 if (uint64_t current_frames = get_total_elements() / get_num_channels();
311 required_frames > current_frames) {
312 expand_to(required_frames);
313 }
314}
315
317{
318 ensure_capacity(capacity);
319
320 Region circular_region {
321 { 0, 0 },
322 { capacity - 1, get_num_channels() - 1 }
323 };
324
325 set_loop_region(circular_region);
326 set_looping(true);
327
328 m_circular_capacity = capacity;
329 m_is_circular = true;
330}
331
333{
334 for (uint32_t i = 0; i < m_dynamic_slots.size(); ++i) {
335 if (!m_dynamic_slots[i]) {
336 m_dynamic_slots[i] = true;
337 m_dynamic_data[i].clear();
338 return i;
339 }
340 }
341
342 m_dynamic_data.emplace_back();
343 m_dynamic_slots.push_back(true);
344 return static_cast<uint32_t>(m_dynamic_slots.size() - 1);
345}
346
348{
349 if (index < m_dynamic_slots.size()) {
350 m_dynamic_slots[index] = false;
351 m_dynamic_data[index].clear();
352 }
353}
354
355std::vector<DataVariant>& DynamicSoundStream::get_dynamic_data(uint32_t index)
356{
357 return m_dynamic_data.at(index);
358}
359
360const std::vector<DataVariant>& DynamicSoundStream::get_dynamic_data(uint32_t index) const
361{
362 return m_dynamic_data.at(index);
363}
364
371
372void DynamicSoundStream::set_all_data(const std::vector<DataVariant>& data)
373{
374 std::unique_lock lock(m_data_mutex);
375 m_data.resize(data.size());
376
377 std::ranges::for_each(std::views::zip(data, m_data),
378 [](auto&& pair) {
379 auto&& [source, dest] = pair;
380 safe_copy_data_variant(source, dest);
381 });
382
383 m_num_frames = std::visit([](const auto& vec) {
384 return static_cast<uint64_t>(vec.size());
385 },
386 m_data[0]);
387
390 }
391
394}
395
397{
398 set_all_data(std::vector<DataVariant> { data });
399}
400
401void DynamicSoundStream::expand_to(uint64_t target_frames)
402{
403 uint64_t current_frames = get_total_elements() / get_num_channels();
404 uint64_t new_capacity = std::max(target_frames, current_frames * 2);
405
406 std::vector<DataVariant> new_data = create_expanded_data(new_capacity);
407 set_all_data(new_data);
408}
409
410std::vector<DataVariant> DynamicSoundStream::create_expanded_data(uint64_t new_frame_count)
411{
413 std::vector<DataVariant> expanded_data(1);
414
415 if (m_data.empty()) {
416 expanded_data[0] = DataVariant(std::vector<double>(new_frame_count * get_num_channels(), 0.0));
417 } else {
418 std::vector<double> current_data;
419 extract_from_variant(m_data[0], current_data);
420
421 std::vector<double> expanded_buffer(new_frame_count * get_num_channels(), 0.0);
422
423 std::ranges::copy_n(current_data.begin(),
424 std::min<size_t>(current_data.size(), expanded_buffer.size()),
425 expanded_buffer.begin());
426
427 expanded_data[0] = DataVariant(std::move(expanded_buffer));
428 }
429 return expanded_data;
430 }
431
432 return std::views::iota(0U, get_num_channels())
433 | std::views::transform([this, new_frame_count](uint32_t ch) -> DataVariant {
434 if (ch < m_data.size()) {
435 std::vector<double> current_channel_data;
436 extract_from_variant(m_data[ch], current_channel_data);
437
438 std::vector<double> expanded_channel(new_frame_count, 0.0);
439 std::ranges::copy_n(current_channel_data.begin(),
440 std::min<size_t>(current_channel_data.size(), expanded_channel.size()),
441 expanded_channel.begin());
442
443 return { std::move(expanded_channel) };
444 }
445
446 return { std::vector<double>(new_frame_count, 0.0) };
447 })
448 | std::ranges::to<std::vector>();
449}
450
451}
uint32_t channel
uint32_t allocate_dynamic_slot()
Allocate an independent processed data slot.
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.
void set_all_data(const DataVariant &new_data)
void disable_circular_buffer()
Disable circular buffer mode and return to linear operation.
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 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 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 lock() override
Acquire a lock for thread-safe access.
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.
@ 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.
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
@ INTERLEAVED
Single DataVariant with interleaved data (LRLRLR for stereo)
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:34
Represents a point or span in N-dimensional space.
Definition Region.hpp:67