MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
CursorAccessProcessor.hpp
Go to the documentation of this file.
1#pragma once
2
5
7
8namespace MayaFlux::Kakshya {
9
10class DynamicSoundStream;
11
12/**
13 * @class CursorAccessProcessor
14 * @brief Independent cursor reader for DynamicSoundStream, writing exclusively
15 * to a dynamic slot allocated at attach time.
16 *
17 * Intended for use cases where multiple independent cursors must read the same
18 * DynamicSoundStream simultaneously -- for example StreamSliceProcessor holding
19 * N slots over the same loaded buffer. Each instance allocates one dynamic slot
20 * via DynamicSoundStream::allocate_dynamic_slot() and writes only into that slot
21 * via get_dynamic_data(m_slot_index). The stream's processed_data, processing
22 * state, and ready/consumed machinery are never touched. Callers read output
23 * directly from get_dynamic_data(get_slot_index()).
24 *
25 * on_attach does three things only: cache the container structure, allocate the
26 * dynamic slot, and initialise the cursor. It does not set the stream as ready
27 * for processing or register any state callbacks. on_detach releases the slot.
28 * All other container lifecycle is the caller's responsibility.
29 *
30 * For default single-cursor sequential reads driven by the container's own
31 * processing state, use ContiguousAccessProcessor instead.
32 *
33 * The stream is treated as immutable memory after load. Block size is fixed at
34 * construction or via set_frames_per_block(). When inactive, process() writes
35 * silence without advancing the cursor. Activation and cursor reset are explicit
36 * via reset(), keeping trigger logic decoupled from the processor.
37 */
38class MAYAFLUX_API CursorAccessProcessor : public DataProcessor {
39public:
40 /**
41 * @brief Construct with a fixed output block size.
42 * @param frames_per_block Number of frames extracted per process() call.
43 * One frame = one sample per channel. Should match
44 * the engine buffer size in frames.
45 */
46 explicit CursorAccessProcessor(uint64_t frames_per_block);
47
48 ~CursorAccessProcessor() override = default;
49
50 void on_attach(const std::shared_ptr<SignalSourceContainer>& container) override;
51 void on_detach(const std::shared_ptr<SignalSourceContainer>& container) override;
52
53 /**
54 * @brief Extract one block of frames into container processed_data[0].
55 *
56 * If inactive, fills processed_data[0] with silence and returns.
57 * Advances m_cursor by m_frames_per_block after each active read.
58 * On reaching the loop end, wraps to m_loop_start if looping, otherwise
59 * deactivates and fires m_on_end if set.
60 *
61 * @param container Must be the DynamicSoundStream passed to on_attach.
62 */
63 void process(const std::shared_ptr<SignalSourceContainer>& container) override;
64
65 [[nodiscard]] bool is_processing() const override { return m_is_processing.load(); }
66
67 /**
68 * @brief Reset cursor to loop start and activate the processor.
69 * Safe to call from any thread between process() calls.
70 */
71 void reset();
72
73 /**
74 * @brief Deactivate without resetting the cursor position.
75 */
76 void stop();
77
78 /**
79 * @brief Set the output block size in frames.
80 * @param frames_per_block Must be > 0.
81 */
82 void set_frames_per_block(uint64_t frames_per_block);
83
84 /**
85 * @brief Enable or disable looping within the loop region.
86 * @param enable True to loop, false for one-shot.
87 */
88 void set_looping(bool enable) { m_looping = enable; }
89
90 /**
91 * @brief Set the loop region in frames.
92 *
93 * Defaults to [0, total_frames) on attach. Both values are clamped to the
94 * container's frame count on the next process() call. All units are frames
95 * (one frame = num_channels samples); conversion to sample offsets for
96 * get_region_data happens internally.
97 *
98 * @param start_frame Inclusive loop start, in frames.
99 * @param end_frame Exclusive loop end, in frames.
100 */
101 void set_loop_region(uint64_t start_frame, uint64_t end_frame);
102
103 /**
104 * @brief Set the number of loops to play before stopping.
105 * Loop count is decremented on each loop completion; when it reaches zero,
106 * the processor deactivates and fires m_on_end if set. Loop count is
107 * ignored if looping is disabled.
108 *
109 * @param n Number of loops to play (0 for infinite).
110 */
111 void set_loop_count(size_t n);
112
113 /**
114 * @brief Register a callback fired when one-shot playback reaches the end.
115 * @param cb Callback with no arguments. Called from the process() thread.
116 */
117 void set_on_end(std::function<void()> cb) { m_on_end = std::move(cb); }
118
119 /**
120 * @brief Set the playback speed relative to nominal rate.
121 *
122 * Speed is applied as a fractional frame accumulator: each process() call
123 * advances the cursor by m_frames_per_block * m_speed frames, with the
124 * sub-frame remainder carried in m_speed_remainder for the next call.
125 * Speed 1.0 is the default (no accumulator overhead). Values <= 0.0 are
126 * ignored.
127 *
128 * @param speed Playback speed multiplier (1.0 = normal).
129 */
130 void set_speed(double speed);
131
132 /**
133 * @brief Drive the read position from a function of time instead of speed.
134 *
135 * The map takes seconds since the last reset() and returns a source
136 * position in frames, read with linear interpolation. Reverse, scrubbing
137 * and a fixed position are all valid. Speed and loop count are ignored
138 * while a map is set. Positions outside the loop region wrap if looping,
139 * otherwise the voice ends. A different map restarts its clock at zero.
140 *
141 * @param map Time map, or null to go back to speed based reading.
142 */
143 void set_time_map(const std::shared_ptr<const Kinesis::TimeMap>& map);
144
145 /**
146 * @brief Restart the time map over and over, with a length that can change.
147 *
148 * Needs a time map. The length map takes seconds since the last reset() and
149 * returns how long, in seconds, the repeat that starts then should last. It
150 * is evaluated once per repeat. Each repeat restarts the time map from zero
151 * and plays its first stretch of that length, with the jump back exact to
152 * the frame, so a shrinking length gives a stutter or bouncing repeat.
153 * Passing the map already set is a no-op.
154 *
155 * @param length Repeat length in seconds, or null for no repeating.
156 */
157 void set_repeat(const std::shared_ptr<const Kinesis::TimeMap>& length);
158
159 [[nodiscard]] bool is_active() const { return m_active; }
160 [[nodiscard]] uint64_t cursor() const { return m_cursor[0]; }
161 [[nodiscard]] uint64_t loop_start() const { return m_loop_start; }
162 [[nodiscard]] uint64_t loop_end() const { return m_loop_end; }
163 [[nodiscard]] size_t loop_count() const { return m_loop_count; }
164 [[nodiscard]] uint32_t get_slot_index() const { return m_slot_index; }
165
166private:
168 std::vector<uint64_t> m_cursor {};
169 uint64_t m_loop_start {};
170 uint64_t m_loop_end {};
171 bool m_looping {};
172 bool m_active {};
173 size_t m_loop_count {};
174 size_t m_loops_remaining {};
175 double m_speed_remainder {};
176 double m_speed { 1.0 };
177
178 std::shared_ptr<const Kinesis::TimeMap> m_time_map;
179 uint64_t m_clock_frames {};
180 std::shared_ptr<const Kinesis::TimeMap> m_repeat;
181 uint64_t m_repeat_start {};
182 uint64_t m_repeat_length {};
183 std::vector<uint64_t> m_gather_frames;
184 std::vector<double> m_gather_weights;
185 std::vector<double> m_gather_taps;
186
187 void process_mapped(DynamicSoundStream& stream, std::vector<DataVariant>& pd);
188
189 uint32_t m_slot_index { std::numeric_limits<uint32_t>::max() };
190
191 std::atomic<bool> m_is_processing { false };
192
194
195 std::function<void()> m_on_end;
196};
197
198} // namespace MayaFlux::Kakshya
Core::GlobalStreamInfo stream
Definition Config.cpp:40
std::shared_ptr< const Kinesis::TimeMap > m_time_map
bool is_processing() const override
Checks if the processor is currently performing processing.
void set_looping(bool enable)
Enable or disable looping within the loop region.
void set_on_end(std::function< void()> cb)
Register a callback fired when one-shot playback reaches the end.
std::shared_ptr< const Kinesis::TimeMap > m_repeat
Independent cursor reader for DynamicSoundStream, writing exclusively to a dynamic slot allocated at ...
Interface for processing data within SignalSourceContainer objects.
Dynamic capacity streaming audio container with automatic resizing and circular buffering.
Container structure for consistent dimension ordering.