MayaFlux 0.2.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
SoundContainerBuffer.cpp
Go to the documentation of this file.
2
4
6
8
9namespace MayaFlux::Buffers {
10
11SoundStreamReader::SoundStreamReader(const std::shared_ptr<Kakshya::StreamContainer>& container)
12 : m_container(container)
13{
14 if (container) {
15 auto structure = container->get_structure();
16 m_num_channels = structure.get_channel_count();
17
18 container->register_state_change_callback(
19 [this](auto c, auto s) {
20 this->on_container_state_change(c, s);
21 });
22 }
23}
24
25void SoundStreamReader::processing_function(const std::shared_ptr<Buffer>& buffer)
26{
27 if (!m_container || !buffer) {
28 return;
29 }
30
31 if (m_container->is_at_end()) {
32 buffer->mark_for_removal();
33 return;
34 }
35
36 try {
37 auto state = m_container->get_processing_state();
38
40 if (m_update_flags) {
41 buffer->mark_for_removal();
42 }
43 return;
44 }
45
48 if (m_container->try_acquire_processing_token(m_source_channel)) {
49 m_container->process_default();
50 }
51 }
52 }
53
54 auto audio_buffer = std::dynamic_pointer_cast<AudioBuffer>(buffer);
55 auto& buffer_data = audio_buffer->get_data();
56 uint32_t buffer_size = audio_buffer->get_num_samples();
57
58 auto read_positions = m_container->get_read_position();
59 uint64_t current_pos = (m_source_channel < read_positions.size())
60 ? read_positions[m_source_channel]
61 : 0;
62
63 if (buffer_data.size() != buffer_size) {
64 buffer_data.resize(buffer_size);
65 }
66
67 extract_channel_data(buffer_data);
68
69 if (m_auto_advance) {
70 m_container->update_read_position_for_channel(m_source_channel, current_pos + buffer_size);
71 }
72
73 if (m_update_flags) {
74 buffer->mark_for_processing(true);
75 }
76
77 m_container->mark_dimension_consumed(m_source_channel, m_reader_id);
78
79 if (m_container->all_dimensions_consumed()) {
80 m_container->update_processing_state(Kakshya::ProcessingState::READY);
81 std::dynamic_pointer_cast<Kakshya::SoundFileContainer>(m_container)->clear_all_consumption();
82 m_container->reset_processing_token();
83 }
84
85 } catch (const std::exception& e) {
87 "SoundStreamReader: Error during processing: {}", e.what());
88 }
89}
90
91void SoundStreamReader::extract_channel_data(std::span<double> output)
92{
93 if (!m_container) {
94 std::ranges::fill(output, 0.0);
95 return;
96 }
97
98 auto sound_container = std::dynamic_pointer_cast<Kakshya::SoundStreamContainer>(m_container);
99 if (!sound_container) {
100 std::ranges::fill(output, 0.0);
101 return;
102 }
103
104 auto& processed_data = sound_container->get_processed_data();
105 if (processed_data.empty()) {
106 std::ranges::fill(output, 0.0);
107 return;
108 }
109
110 auto structure = sound_container->get_structure();
111
112 if (structure.organization == Kakshya::OrganizationStrategy::INTERLEAVED) {
113 thread_local std::vector<double> temp_storage;
114 auto data_span = Kakshya::extract_from_variant<double>(processed_data[0], temp_storage);
115
116 auto num_channels = structure.get_channel_count();
117 auto samples_to_copy = std::min(static_cast<size_t>(output.size()),
118 static_cast<size_t>(data_span.size() / num_channels));
119
120 for (auto i : std::views::iota(0UZ, samples_to_copy)) {
121 auto interleaved_idx = i * num_channels + m_source_channel;
122 output[i] = (interleaved_idx < data_span.size()) ? data_span[interleaved_idx] : 0.0;
123 }
124
125 if (samples_to_copy < output.size()) {
126 std::ranges::fill(output | std::views::drop(samples_to_copy), 0.0);
127 }
128
129 } else {
130 if (m_source_channel >= processed_data.size()) {
131 std::ranges::fill(output, 0.0);
132 return;
133 }
134
135 thread_local std::vector<double> temp_storage;
136 auto channel_data_span = Kakshya::extract_from_variant<double>(processed_data[m_source_channel], temp_storage);
137
138 auto samples_to_copy = std::min(output.size(), channel_data_span.size());
139 std::ranges::copy_n(channel_data_span.begin(), samples_to_copy, output.begin());
140
141 if (samples_to_copy < output.size()) {
142 std::ranges::fill(output | std::views::drop(samples_to_copy), 0.0);
143 }
144 }
145}
146
147void SoundStreamReader::on_attach(const std::shared_ptr<Buffer>& buffer)
148{
149 if (!m_container || !buffer) {
150 return;
151 }
152
153 m_reader_id = m_container->register_dimension_reader(m_source_channel);
154
155 if (!m_container->is_ready_for_processing()) {
157 std::source_location::current(),
158 "SoundStreamReader: Container not ready for processing");
159 }
160
161 try {
162 auto& buffer_data = std::dynamic_pointer_cast<AudioBuffer>(buffer)->get_data();
163 uint32_t num_samples = std::dynamic_pointer_cast<AudioBuffer>(buffer)->get_num_samples();
164
165 extract_channel_data(buffer_data);
166
167 if (m_update_flags) {
168 buffer->mark_for_processing(true);
169 }
170
171 } catch (const std::exception& e) {
173 "SoundStreamReader: Error pre-filling buffer: {}", e.what());
174 }
175}
176
177void SoundStreamReader::on_detach(const std::shared_ptr<Buffer>& /*buffer*/)
178{
179 if (m_container) {
180 m_container->unregister_state_change_callback();
181 m_container->unregister_dimension_reader(m_source_channel);
182 }
183}
184
185void SoundStreamReader::set_source_channel(uint32_t channel_index)
186{
187 if (channel_index >= m_num_channels) {
189 std::source_location::current(),
190 "SoundStreamReader: Channel index {} exceeds container channel count {}",
191 channel_index, m_num_channels);
192 }
193 m_source_channel = channel_index;
194}
195
196void SoundStreamReader::set_container(const std::shared_ptr<Kakshya::StreamContainer>& container)
197{
198 if (m_container) {
199 m_container->unregister_state_change_callback();
200 }
201
202 m_container = container;
203
204 if (container) {
205 auto structure = container->get_structure();
206 m_num_channels = structure.get_channel_count();
207
208 container->register_state_change_callback(
209 [this](std::shared_ptr<Kakshya::SignalSourceContainer> c, Kakshya::ProcessingState s) {
210 this->on_container_state_change(c, s);
211 });
212 }
213}
214
216 const std::shared_ptr<Kakshya::SignalSourceContainer>& /*container*/,
218{
219 switch (state) {
221 break;
222
225 "SoundStreamReader: Container entered ERROR state");
226 break;
227
228 default:
229 break;
230 }
231}
232
233SoundContainerBuffer::SoundContainerBuffer(uint32_t channel_id, uint32_t num_samples,
234 const std::shared_ptr<Kakshya::StreamContainer>& container,
235 uint32_t source_channel)
236 : AudioBuffer(channel_id, num_samples)
237 , m_container(container)
238 , m_source_channel(source_channel)
239{
240 if (!m_container) {
241 error<std::invalid_argument>(Journal::Component::Buffers, Journal::Context::Init,
242 std::source_location::current(),
243 "SoundContainerBuffer: container must not be null");
244 }
245
246 m_pending_adapter = std::make_shared<SoundStreamReader>(m_container);
247 std::dynamic_pointer_cast<SoundStreamReader>(m_pending_adapter)->set_source_channel(m_source_channel);
248
250}
251
260
261void SoundContainerBuffer::set_container(const std::shared_ptr<Kakshya::StreamContainer>& container)
262{
263 m_container = container;
264
265 if (auto adapter = std::dynamic_pointer_cast<SoundStreamReader>(m_default_processor)) {
266 adapter->set_container(container);
267 }
268
270}
271
273{
274 // Check if we can use zero-copy mode
275 // This would be possible if:
276 // 1. Container data is contiguous doubles
277 // 2. Channel is deinterleaved (column-major for audio)
278 // 3. Buffer size matches container frame size
279
280 if (!m_container) {
281 m_zero_copy_mode = false;
282 return;
283 }
284
285 auto dimensions = m_container->get_dimensions();
286 auto layout = m_container->get_memory_layout();
287
288 m_zero_copy_mode = false;
289
290 // TODO: Implement zero-copy when container provides direct memory access
291}
292
293std::shared_ptr<BufferProcessor> SoundContainerBuffer::create_default_processor()
294{
295 if (m_pending_adapter) {
296 return m_pending_adapter;
297 }
298
299 auto adapter = std::make_shared<SoundStreamReader>(m_container);
300 adapter->set_source_channel(m_source_channel);
301 return adapter;
302}
303
304} // namespace MayaFlux::Buffers
#define MF_ERROR(comp, ctx,...)
std::shared_ptr< BufferProcessor > m_default_processor
Default audio transformation processor for this buffer.
virtual void set_default_processor(const std::shared_ptr< BufferProcessor > &processor) override
Sets the default audio transformation processor for this buffer.
virtual void enforce_default_processing(bool should_process) override
Controls whether the audio buffer should use default processing.
Concrete audio implementation of the Buffer interface for double-precision audio data.
void initialize()
Initialize the buffer after construction.
void setup_zero_copy_if_possible()
Attempt to enable zero-copy operation if container layout allows.
std::shared_ptr< Kakshya::StreamContainer > m_container
void set_container(const std::shared_ptr< Kakshya::StreamContainer > &container)
Update the container reference.
SoundContainerBuffer(uint32_t channel_id, uint32_t num_samples, const std::shared_ptr< Kakshya::StreamContainer > &container, uint32_t source_channel=0)
Construct a SoundContainerBuffer for a specific channel and container.
std::shared_ptr< BufferProcessor > m_pending_adapter
std::shared_ptr< BufferProcessor > create_default_processor() override
Create the default processor (SoundStreamReader) for this buffer.
void set_container(const std::shared_ptr< Kakshya::StreamContainer > &container)
Set the container to adapt.
void on_detach(const std::shared_ptr< Buffer > &buffer) override
Detach the adapter from its AudioBuffer.
std::shared_ptr< Kakshya::StreamContainer > m_container
void extract_channel_data(std::span< double > output)
Extract channel data from the container into the output buffer.
void on_attach(const std::shared_ptr< Buffer > &buffer) override
Attach the adapter to an AudioBuffer.
void processing_function(const std::shared_ptr< Buffer > &buffer) override
Extracts and processes data from the container into the target AudioBuffer.
void on_container_state_change(const std::shared_ptr< Kakshya::SignalSourceContainer > &container, Kakshya::ProcessingState state)
Respond to container state changes (e.g., READY, PROCESSED, NEEDS_REMOVAL).
void set_source_channel(uint32_t channel_index)
Set which channel dimension to extract from the container.
SoundStreamReader(const std::shared_ptr< Kakshya::StreamContainer > &container)
@ BufferProcessing
Buffer processing (Buffers::BufferManager, processing chains)
@ Init
Engine/subsystem initialization.
@ Buffers
Buffers, Managers, processors and processing chains.
ProcessingState
Represents the current processing lifecycle state of a container.
@ READY
Container has data loaded and is ready for processing.
@ NEEDS_REMOVAL
Container is marked for removal from the system.
@ ERROR
Container is in an error state and cannot proceed.
@ PROCESSED
Container has completed processing and results are available.
@ INTERLEAVED
Single DataVariant with interleaved data (LRLRLR for stereo)