MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
CursorAccessProcessor.cpp
Go to the documentation of this file.
2
5
7
9
10namespace MayaFlux::Kakshya {
11
13 : m_frames_per_block(frames_per_block)
14{
15}
16
17void CursorAccessProcessor::on_attach(const std::shared_ptr<SignalSourceContainer>& container)
18{
19 auto stream = std::dynamic_pointer_cast<DynamicSoundStream>(container);
20 if (!stream) {
22 "CursorAccessProcessor requires a DynamicSoundStream");
23 return;
24 }
25
26 m_structure = container->get_structure();
27
28 m_slot_index = stream->allocate_dynamic_slot();
29
30 const uint64_t num_channels = m_structure.get_channel_count();
31 m_cursor.assign(num_channels, m_loop_start);
32}
33
34void CursorAccessProcessor::on_detach(const std::shared_ptr<SignalSourceContainer>& container)
35{
36 auto stream = std::dynamic_pointer_cast<DynamicSoundStream>(container);
37 if (!stream) {
39 "CursorAccessProcessor requires a DynamicSoundStream, slot index can potentially leak");
40 return;
41 }
42
43 m_active = false;
44 m_cursor.clear();
45 stream->release_dynamic_slot(m_slot_index);
46}
47
48void CursorAccessProcessor::process(const std::shared_ptr<SignalSourceContainer>& container)
49{
50 m_is_processing.store(true, std::memory_order_relaxed);
51
52 auto stream = std::dynamic_pointer_cast<DynamicSoundStream>(container);
53 if (!stream) {
55 "CursorAccessProcessor requires a DynamicSoundStream");
56 m_is_processing.store(false, std::memory_order_relaxed);
57 return;
58 }
59
60 auto& pd = stream->get_dynamic_data(m_slot_index);
61
62 if (!m_active) {
63 const uint64_t num_channels = m_structure.get_channel_count();
65 pd.resize(1);
66 std::get<std::vector<double>>(pd[0]).assign(m_frames_per_block * num_channels, 0.0);
67 } else {
68 pd.resize(num_channels);
69 for (auto& ch : pd)
70 std::get<std::vector<double>>(ch).assign(m_frames_per_block, 0.0);
71 }
72 m_is_processing.store(false, std::memory_order_relaxed);
73 return;
74 }
75
76 if (m_time_map && m_time_map->fn) {
78 m_is_processing.store(false, std::memory_order_relaxed);
79 return;
80 }
81
82 const uint64_t frame = m_cursor.empty() ? 0 : m_cursor[0];
83
84 const uint64_t total_frames = m_structure.get_samples_count_per_channel();
85 const uint64_t read_end = std::min(frame + m_frames_per_block - 1, total_frames - 1);
86
87 const Region region {
88 std::vector<uint64_t> { frame, 0 },
89 std::vector<uint64_t> { read_end, m_structure.get_channel_count() - 1 }
90 };
91
92 auto region_data = container->get_region_data(region);
93
95 pd.resize(1);
96 if (!region_data.empty())
97 safe_copy_data_variant(region_data[0], pd[0]);
98 } else {
99 const uint64_t ch_count = std::min(
100 static_cast<uint64_t>(region_data.size()),
102 pd.resize(ch_count);
103 for (size_t c = 0; c < ch_count; ++c)
104 safe_copy_data_variant(region_data[c], pd[c]);
105 }
106
107 const double raw_advance = static_cast<double>(m_frames_per_block) * m_speed + m_speed_remainder;
108 const auto int_advance = static_cast<uint64_t>(raw_advance);
109 m_speed_remainder = raw_advance - static_cast<double>(int_advance);
110
111 const uint64_t next = frame + int_advance;
112 const uint64_t loop_end = m_loop_end;
113
114 if (next >= loop_end) {
115 if (m_looping) {
116 if (m_loop_count > 0) {
118 if (m_loops_remaining == 0) {
119 m_active = false;
120 m_speed_remainder = 0.0;
121 m_cursor.assign(m_cursor.size(), m_loop_start);
122 if (m_on_end)
123 m_on_end();
124 m_is_processing.store(false, std::memory_order_relaxed);
125 return;
126 }
127 }
128 m_speed_remainder = 0.0;
129 m_cursor.assign(m_cursor.size(), m_loop_start);
130 } else {
131 m_active = false;
132 m_speed_remainder = 0.0;
133 m_cursor.assign(m_cursor.size(), m_loop_start);
134 if (m_on_end)
135 m_on_end();
136 }
137 } else {
138 m_cursor.assign(m_cursor.size(), next);
139 }
140
141 m_is_processing.store(false, std::memory_order_relaxed);
142}
143
145{
146 const uint64_t block = m_frames_per_block;
147 const uint64_t channels = m_structure.get_channel_count();
149
150 const auto rate = static_cast<double>(stream.get_sample_rate());
151 const auto lo = static_cast<double>(m_loop_start);
152 const auto hi = static_cast<double>(m_loop_end);
153 m_gather_frames.resize(2 * block);
154 m_gather_weights.resize(block);
155
156 const bool repeating = m_repeat && m_repeat->fn;
157 bool exited = m_loop_end <= m_loop_start;
158 double last_position = lo;
159 uint64_t valid = 0;
160
161 while (!exited && valid < block) {
162 const uint64_t clock = m_clock_frames + valid;
163 uint64_t stop = block;
164
165 if (repeating) {
166 if (m_repeat_length == 0 || clock - m_repeat_start >= m_repeat_length) {
167 m_repeat_start = clock;
168 const double seconds = std::max((*m_repeat)(static_cast<double>(clock) / rate), 0.0);
169 m_repeat_length = std::max<uint64_t>(1, static_cast<uint64_t>(std::llround(seconds * rate)));
170 }
171
172 stop = std::min(block, valid + (m_repeat_start + m_repeat_length - clock));
173 }
174
175 const uint64_t origin = repeating ? m_repeat_start : 0;
176 const uint64_t begin = valid;
177 const uint64_t frames = stop - begin;
178 const auto span = static_cast<double>(frames);
179 const double p_a = (*m_time_map)(static_cast<double>(clock - origin) / rate);
180 const double p_b = (*m_time_map)(static_cast<double>(clock + frames - origin) / rate);
181 last_position = p_b;
182
183 for (; valid < stop; ++valid) {
184 const double u = p_a + (p_b - p_a) * static_cast<double>(valid - begin) / span;
185
186 if (!m_looping && (u < lo || u >= hi)) {
187 exited = true;
188 break;
189 }
190
191 const double s = m_looping ? Kinesis::wrap<double>(u, lo, hi) : u;
192 const double whole = std::floor(s);
193 const uint64_t i = std::min(static_cast<uint64_t>(whole), m_loop_end - 1);
194 const uint64_t next = i + 1 < m_loop_end ? i + 1 : (m_looping ? m_loop_start : i);
195
196 m_gather_frames[2 * valid] = i;
197 m_gather_frames[2 * valid + 1] = next;
198 m_gather_weights[valid] = s - whole;
199 }
200 }
201
202 m_gather_taps.resize(2 * block * channels);
203 stream.gather_frames(
204 std::span<const uint64_t>(m_gather_frames.data(), 2 * valid),
205 std::span<double>(m_gather_taps.data(), 2 * valid * channels));
206
207 const auto blend = [&](uint64_t n, uint64_t c) {
208 const double w = m_gather_weights[n];
209 return m_gather_taps[(2 * n) * channels + c] * (1.0 - w)
210 + m_gather_taps[(2 * n + 1) * channels + c] * w;
211 };
212
213 if (interleaved) {
214 pd.resize(1);
215 auto& out = std::get<std::vector<double>>(pd[0]);
216 out.assign(block * channels, 0.0);
217 for (uint64_t n = 0; n < valid; ++n) {
218 for (uint64_t c = 0; c < channels; ++c) {
219 out[n * channels + c] = blend(n, c);
220 }
221 }
222 } else {
223 pd.resize(channels);
224 for (uint64_t c = 0; c < channels; ++c) {
225 auto& out = std::get<std::vector<double>>(pd[c]);
226 out.assign(block, 0.0);
227 for (uint64_t n = 0; n < valid; ++n) {
228 out[n] = blend(n, c);
229 }
230 }
231 }
232
233 m_clock_frames += block;
234
235 if (exited) {
236 m_active = false;
237 m_clock_frames = 0;
238 m_cursor.assign(m_cursor.size(), m_loop_start);
239 if (m_on_end)
240 m_on_end();
241 return;
242 }
243
244 const double end = m_looping ? Kinesis::wrap<double>(last_position, lo, hi) : std::clamp(last_position, lo, hi - 1.0);
245 m_cursor.assign(m_cursor.size(), static_cast<uint64_t>(std::max(end, lo)));
246}
247
248void CursorAccessProcessor::set_time_map(const std::shared_ptr<const Kinesis::TimeMap>& map)
249{
250 if (map == m_time_map)
251 return;
252
253 m_time_map = map;
254 m_clock_frames = 0;
255 m_repeat_start = 0;
256 m_repeat_length = 0;
257}
258
259void CursorAccessProcessor::set_repeat(const std::shared_ptr<const Kinesis::TimeMap>& length)
260{
261 if (length == m_repeat)
262 return;
263
265 m_repeat_start = 0;
266 m_repeat_length = 0;
267}
268
270{
271 m_cursor.assign(m_cursor.size(), m_loop_start);
273 m_clock_frames = 0;
274 m_repeat_start = 0;
275 m_repeat_length = 0;
276 m_active = true;
277}
278
280{
281 if (speed > 0.0)
282 m_speed = speed;
283}
284
286{
287 m_active = false;
288}
289
295
296void CursorAccessProcessor::set_frames_per_block(uint64_t frames_per_block)
297{
298 m_frames_per_block = frames_per_block;
299}
300
301void CursorAccessProcessor::set_loop_region(uint64_t start_frame, uint64_t end_frame)
302{
303 m_loop_start = start_frame;
304 m_loop_end = end_frame;
305}
306
307} // namespace MayaFlux::Kakshya
#define MF_ERROR(comp, ctx,...)
#define MF_RT_ERROR(comp, ctx,...)
uint64_t length
Definition Composite.cpp:11
Core::GlobalStreamInfo stream
Definition Config.cpp:40
float rate
std::vector< float > * out
uint32_t channels
std::shared_ptr< const Kinesis::TimeMap > m_time_map
void process(const std::shared_ptr< SignalSourceContainer > &container) override
Extract one block of frames into container processed_data[0].
CursorAccessProcessor(uint64_t frames_per_block)
Construct with a fixed output block size.
void on_detach(const std::shared_ptr< SignalSourceContainer > &container) override
Called when this processor is detached from a container.
void set_repeat(const std::shared_ptr< const Kinesis::TimeMap > &length)
Restart the time map over and over, with a length that can change.
void set_loop_count(size_t n)
Set the number of loops to play before stopping.
void stop()
Deactivate without resetting the cursor position.
void set_frames_per_block(uint64_t frames_per_block)
Set the output block size in frames.
void set_speed(double speed)
Set the playback speed relative to nominal rate.
void reset()
Reset cursor to loop start and activate the processor.
void process_mapped(DynamicSoundStream &stream, std::vector< DataVariant > &pd)
void set_time_map(const std::shared_ptr< const Kinesis::TimeMap > &map)
Drive the read position from a function of time instead of speed.
void set_loop_region(uint64_t start_frame, uint64_t end_frame)
Set the loop region in frames.
std::shared_ptr< const Kinesis::TimeMap > m_repeat
void on_attach(const std::shared_ptr< SignalSourceContainer > &container) override
Called when this processor is attached to a container.
Dynamic capacity streaming audio container with automatic resizing and circular buffering.
@ ContainerProcessing
Container operations (Kakshya - file/stream/region processing)
@ Kakshya
Containers[Signalsource, Stream, File], Regions, DataProcessors.
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
@ INTERLEAVED
Single DataVariant with interleaved data (LRLRLR for stereo)
static uint64_t get_channel_count(const std::vector< DataDimension > &dimensions)
Extract channel count from dimensions.
static uint64_t get_samples_count_per_channel(const std::vector< DataDimension > &dimensions)
Get samples per channel (time dimension only).
Represents a point or span in N-dimensional space.
Definition Region.hpp:73