Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
format_reader_source.hpp
Go to the documentation of this file.
1#pragma once
2
6
9
10#include <algorithm>
11#include <cstdint>
12#include <memory>
13#include <vector>
14
15namespace pulp::audio {
16
23public:
25
26 explicit AudioFormatReaderSource(std::shared_ptr<MemoryMappedAudioReader> reader)
27 : reader_(std::move(reader)) {
28 reserve_channel_scratch();
29 }
30
31 void set_reader(std::shared_ptr<MemoryMappedAudioReader> reader) {
32 reader_ = std::move(reader);
33 read_pos_ = 0;
34 reserve_channel_scratch();
35 }
36
39 MemoryMappedAudioReader* reader() const { return reader_.get(); }
40
41 // ── AudioSource ────────────────────────────────────────────────────────
42
43 void prepare_to_play(int /*samples_per_block*/, double /*sample_rate*/) override {
44 // Pre-size the channel-pointer scratch so get_next_audio_block() — an
45 // audio-thread call per the AudioSource contract — never allocates.
46 reserve_channel_scratch();
47 }
48
49 void release_resources() override {
50 // Reader lifetime is owned by the shared_ptr.
51 }
52
54 int start_sample,
55 int num_samples) override {
56 if (num_samples <= 0 || out.num_channels() == 0) return;
57 const auto num_channels = static_cast<uint32_t>(out.num_channels());
58 // Reuse the persistent channel-pointer scratch (sized in
59 // prepare_to_play()); grow only if a caller exceeds the prepared count,
60 // so the steady-state audio-thread path allocates nothing.
61 if (channel_scratch_.size() < num_channels)
62 channel_scratch_.resize(num_channels);
63 if (!reader_) {
64 zero_fill(out, num_channels, start_sample, num_samples);
65 return;
66 }
67 int frames_filled = 0;
68 while (frames_filled < num_samples) {
69 const int remaining = num_samples - frames_filled;
70 const uint64_t length = get_total_length();
71 uint64_t available = 0;
72 if (length > read_pos_) available = length - read_pos_;
73 const int chunk = (length == 0)
74 ? remaining
75 : std::min<int>(remaining, static_cast<int>(std::min<uint64_t>(
76 available,
77 static_cast<uint64_t>(remaining))));
78 if (chunk <= 0) {
79 if (looping_ && length > 0) {
80 read_pos_ = 0;
81 continue;
82 }
83 // No more data and not looping → silence the rest.
84 zero_fill(out, num_channels,
85 start_sample + frames_filled, num_samples - frames_filled);
86 return;
87 }
88 // Point the persistent scratch at this chunk's destination span.
89 float** chunk_ptrs = channel_scratch_.data();
90 for (uint32_t c = 0; c < num_channels; ++c) {
91 chunk_ptrs[c] = out.channel_ptr(c) + start_sample + frames_filled;
92 }
93 const bool ok = reader_->read_frames(chunk_ptrs,
94 num_channels,
95 read_pos_,
96 static_cast<uint64_t>(chunk));
97 if (!ok) {
98 zero_fill(out, num_channels,
99 start_sample + frames_filled, num_samples - frames_filled);
100 return;
101 }
102 read_pos_ += static_cast<uint64_t>(chunk);
103 frames_filled += chunk;
104 if (looping_ && length > 0 && read_pos_ >= length) {
105 read_pos_ = 0;
106 }
107 }
108 }
109
110 // ── PositionableAudioSource ────────────────────────────────────────────
111
112 void set_next_read_position(uint64_t new_position) override {
113 const uint64_t length = get_total_length();
114 if (length > 0 && new_position > length) new_position = length;
115 read_pos_ = new_position;
116 }
117
118 uint64_t get_next_read_position() const override { return read_pos_; }
119
120 uint64_t get_total_length() const override {
121 if (!reader_) return 0;
122 return reader_->info().num_frames;
123 }
124
125 bool is_looping() const override { return looping_; }
126 void set_looping(bool should_loop) override { looping_ = should_loop; }
127
128private:
129 // Grow (never shrink) the channel-pointer scratch to the reader's channel
130 // count, so the audio-thread block path can reuse it without allocating.
131 void reserve_channel_scratch() {
132 if (!reader_) return;
133 const auto needed = static_cast<std::size_t>(reader_->info().num_channels);
134 if (channel_scratch_.size() < needed) channel_scratch_.resize(needed);
135 }
136
137 static void zero_fill(BufferView<float>& out, uint32_t num_channels,
138 int start, int count) {
139 for (uint32_t c = 0; c < num_channels; ++c) {
140 float* p = out.channel_ptr(c) + start;
141 for (int i = 0; i < count; ++i) p[i] = 0.0f;
142 }
143 }
144
145 std::shared_ptr<MemoryMappedAudioReader> reader_;
146 // Persistent per-channel pointer scratch — reused across blocks so the
147 // audio thread does not allocate. Sized in prepare_to_play(); grows only.
148 std::vector<float*> channel_scratch_;
149 uint64_t read_pos_ = 0;
150 bool looping_ = false;
151};
152
153} // namespace pulp::audio
void get_next_audio_block(BufferView< float > out, int start_sample, int num_samples) override
Fill num_samples of out, starting at start_sample within out.
Definition format_reader_source.hpp:53
AudioFormatReaderSource(std::shared_ptr< MemoryMappedAudioReader > reader)
Definition format_reader_source.hpp:26
void set_next_read_position(uint64_t new_position) override
Move the read head to new_position (frames from the start of the source).
Definition format_reader_source.hpp:112
void prepare_to_play(int, double) override
Called before playback begins.
Definition format_reader_source.hpp:43
bool is_looping() const override
True if the source should wrap back to 0 when it reaches the end.
Definition format_reader_source.hpp:125
uint64_t get_total_length() const override
Total length in frames. 0 means "unknown" or "infinite".
Definition format_reader_source.hpp:120
MemoryMappedAudioReader * reader() const
Non-owning accessor — useful when the caller already owns the reader and wants to query info / metada...
Definition format_reader_source.hpp:39
void release_resources() override
Called when playback is finished.
Definition format_reader_source.hpp:49
void set_looping(bool should_loop) override
Enable / disable looping.
Definition format_reader_source.hpp:126
uint64_t get_next_read_position() const override
Current frame position the next get_next_audio_block call will read from.
Definition format_reader_source.hpp:118
void set_reader(std::shared_ptr< MemoryMappedAudioReader > reader)
Definition format_reader_source.hpp:31
Non-owning view over multi-channel audio sample data.
Definition buffer.hpp:30
std::size_t num_channels() const
Definition buffer.hpp:83
SampleType * channel_ptr(std::size_t index)
Raw pointer to one channel's sample data.
Definition buffer.hpp:68
Memory-mapped audio file reader.
Definition mmap_reader.hpp:19
AudioSource with a notion of position.
Definition source.hpp:60
Definition analyzer_provider.hpp:12
@ ok
Definition analyzer_provider.hpp:58
STL namespace.
Audio source hierarchy — abstract bases for streaming audio playback graphs.