MayaFlux 0.4.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
CursorAccessProcessor.hpp
Go to the documentation of this file.
1#pragma once
2
5
6namespace MayaFlux::Kakshya {
7
8/**
9 * @class CursorAccessProcessor
10 * @brief Independent cursor reader for DynamicSoundStream, writing exclusively
11 * to a dynamic slot allocated at attach time.
12 *
13 * Intended for use cases where multiple independent cursors must read the same
14 * DynamicSoundStream simultaneously -- for example StreamSliceProcessor holding
15 * N slots over the same loaded buffer. Each instance allocates one dynamic slot
16 * via DynamicSoundStream::allocate_dynamic_slot() and writes only into that slot
17 * via get_dynamic_data(m_slot_index). The stream's processed_data, processing
18 * state, and ready/consumed machinery are never touched. Callers read output
19 * directly from get_dynamic_data(get_slot_index()).
20 *
21 * on_attach does three things only: cache the container structure, allocate the
22 * dynamic slot, and initialise the cursor. It does not set the stream as ready
23 * for processing or register any state callbacks. on_detach releases the slot.
24 * All other container lifecycle is the caller's responsibility.
25 *
26 * For default single-cursor sequential reads driven by the container's own
27 * processing state, use ContiguousAccessProcessor instead.
28 *
29 * The stream is treated as immutable memory after load. Block size is fixed at
30 * construction or via set_frames_per_block(). When inactive, process() writes
31 * silence without advancing the cursor. Activation and cursor reset are explicit
32 * via reset(), keeping trigger logic decoupled from the processor.
33 */
34class MAYAFLUX_API CursorAccessProcessor : public DataProcessor {
35public:
36 /**
37 * @brief Construct with a fixed output block size.
38 * @param frames_per_block Number of frames extracted per process() call.
39 * One frame = one sample per channel. Should match
40 * the engine buffer size in frames.
41 */
42 explicit CursorAccessProcessor(uint64_t frames_per_block);
43
44 ~CursorAccessProcessor() override = default;
45
46 void on_attach(const std::shared_ptr<SignalSourceContainer>& container) override;
47 void on_detach(const std::shared_ptr<SignalSourceContainer>& container) override;
48
49 /**
50 * @brief Extract one block of frames into container processed_data[0].
51 *
52 * If inactive, fills processed_data[0] with silence and returns.
53 * Advances m_cursor by m_frames_per_block after each active read.
54 * On reaching the loop end, wraps to m_loop_start if looping, otherwise
55 * deactivates and fires m_on_end if set.
56 *
57 * @param container Must be the DynamicSoundStream passed to on_attach.
58 */
59 void process(const std::shared_ptr<SignalSourceContainer>& container) override;
60
61 [[nodiscard]] bool is_processing() const override { return m_is_processing.load(); }
62
63 /**
64 * @brief Reset cursor to loop start and activate the processor.
65 * Safe to call from any thread between process() calls.
66 */
67 void reset();
68
69 /**
70 * @brief Deactivate without resetting the cursor position.
71 */
72 void stop();
73
74 /**
75 * @brief Set the output block size in frames.
76 * @param frames_per_block Must be > 0.
77 */
78 void set_frames_per_block(uint64_t frames_per_block);
79
80 /**
81 * @brief Enable or disable looping within the loop region.
82 * @param enable True to loop, false for one-shot.
83 */
84 void set_looping(bool enable) { m_looping = enable; }
85
86 /**
87 * @brief Set the loop region in frames.
88 *
89 * Defaults to [0, total_frames) on attach. Both values are clamped to the
90 * container's frame count on the next process() call. All units are frames
91 * (one frame = num_channels samples); conversion to sample offsets for
92 * get_region_data happens internally.
93 *
94 * @param start_frame Inclusive loop start, in frames.
95 * @param end_frame Exclusive loop end, in frames.
96 */
97 void set_loop_region(uint64_t start_frame, uint64_t end_frame);
98
99 /**
100 * @brief Set the number of loops to play before stopping.
101 * Loop count is decremented on each loop completion; when it reaches zero,
102 * the processor deactivates and fires m_on_end if set. Loop count is
103 * ignored if looping is disabled.
104 *
105 * @param n Number of loops to play (0 for infinite).
106 */
107 void set_loop_count(size_t n);
108
109 /**
110 * @brief Register a callback fired when one-shot playback reaches the end.
111 * @param cb Callback with no arguments. Called from the process() thread.
112 */
113 void set_on_end(std::function<void()> cb) { m_on_end = std::move(cb); }
114
115 /**
116 * @brief Set the playback speed relative to nominal rate.
117 *
118 * Speed is applied as a fractional frame accumulator: each process() call
119 * advances the cursor by m_frames_per_block * m_speed frames, with the
120 * sub-frame remainder carried in m_speed_remainder for the next call.
121 * Speed 1.0 is the default (no accumulator overhead). Values <= 0.0 are
122 * ignored.
123 *
124 * @param speed Playback speed multiplier (1.0 = normal).
125 */
126 void set_speed(double speed);
127
128 [[nodiscard]] bool is_active() const { return m_active; }
129 [[nodiscard]] uint64_t cursor() const { return m_cursor[0]; }
130 [[nodiscard]] uint64_t loop_start() const { return m_loop_start; }
131 [[nodiscard]] uint64_t loop_end() const { return m_loop_end; }
132 [[nodiscard]] uint32_t get_slot_index() const { return m_slot_index; }
133
134private:
136 std::vector<uint64_t> m_cursor {};
137 uint64_t m_loop_start {};
138 uint64_t m_loop_end {};
139 bool m_looping {};
140 bool m_active {};
141 size_t m_loop_count {};
142 size_t m_loops_remaining {};
143 double m_speed_remainder {};
144 double m_speed { 1.0 };
145
146 uint32_t m_slot_index { std::numeric_limits<uint32_t>::max() };
147
148 std::atomic<bool> m_is_processing { false };
149
151
152 std::function<void()> m_on_end;
153};
154
155} // namespace MayaFlux::Kakshya
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.
Independent cursor reader for DynamicSoundStream, writing exclusively to a dynamic slot allocated at ...
Interface for processing data within SignalSourceContainer objects.
Container structure for consistent dimension ordering.