Build the taps and start them.
157{
158 TapSet taps;
159
162 "TapSetBuilder::start called with a null stream");
163 return taps;
164 }
165
168 }
169
170 const uint64_t frames =
m_stream->get_num_frames();
172
175 "TapSetBuilder::start region [{}, {}) is too short",
m_start_frame, end_frame);
176 return taps;
177 }
178
179 const auto rate =
static_cast<double>(
m_stream->get_sample_rate());
181 const auto last = static_cast<double>(end_frame - 1);
182 const uint32_t source_count = std::max<uint32_t>(
m_stream->get_num_channels(), 1);
183 const double shortest = 2.0 *
static_cast<double>(
m_buf_size) /
rate;
184
186 std::vector<size_t> slot_counts;
187
188 for (
const auto& spec :
m_specs) {
189 TapSet::Tap built;
191 built.velocity = std::make_shared<double>((spec.backward ? -spec.ratio : spec.ratio) *
rate);
192
194 if (spec.lag) {
196 return std::max((*
lag)(t), shortest) *
rate;
197 } };
198 }
199 const double origin =
static_cast<double>(
m_stream->get_write_head(0)) +
rate * spec.delay;
200
201 for (
const uint32_t output : spec.
channels) {
202 auto found = std::ranges::find(
channels, output);
204 auto made = std::make_shared<SamplingPipeline>(
206 made->build();
207 taps.m_pipelines.push_back(std::move(made));
209 slot_counts.push_back(0);
211 }
212
213 const auto pipeline =
static_cast<size_t>(std::distance(
channels.begin(), found));
214 const auto& sampler = taps.m_pipelines[pipeline];
215 const size_t slot = slot_counts[pipeline]++;
216
217 auto slice = sampler->slice_from_range(
m_start_frame, end_frame,
static_cast<uint8_t
>(slot));
219 slice.scale = spec.level;
220 slice.source_channel = spec.source.value_or(output % source_count);
221 if (!spec.lag) {
222 slice.repeat = spec.repeat;
223 }
224 slice.with_time_map(spec.lag
227 sampler->load(slot, std::move(slice));
228
229 built.seats.push_back({ .sampler = sampler, .slot = slot });
230 }
231
232 taps.m_taps.push_back(std::move(built));
233 }
234
235 std::vector<size_t> order(
m_specs.size());
236 for (size_t i = 0; i < order.size(); ++i) {
237 order[i] = i;
238 }
239 std::ranges::stable_sort(order, {}, [
this](
size_t i) {
return m_specs[i].delay; });
240
241 auto entries = std::make_shared<EventChain>(
m_scheduler);
242 double elapsed = 0.0;
243
244 for (const size_t i : order) {
245 const auto& seats = taps.m_taps[i].seats;
247
248 if (delay <= 0.0) {
249 for (const auto& seat : seats) {
250 seat.sampler->play(seat.slot);
251 }
252 continue;
253 }
254
255 entries->then([seats] {
256 for (const auto& seat : seats) {
257 seat.sampler->play(seat.slot);
258 }
259 },
262 }
263
264 if (entries->event_count() > 0) {
265 entries->start();
266 taps.m_entries = std::move(entries);
267 }
268
269 return taps;
270}
#define MF_ERROR(comp, ctx,...)
std::shared_ptr< Kakshya::DynamicSoundStream > m_stream
std::vector< Spec > m_specs
Buffers::BufferManager & m_mgr
TapSetBuilder & lag(double seconds)
Make the current tap trail the write head of a live ring by seconds.
Vruta::TaskScheduler & m_scheduler
@ Configuration
Configuration and parameter updates.
@ Kriya
Automatable tasks and fluent scheduling api for Nodes and Buffers.
std::vector< double > delay(std::span< const double > data, uint32_t delay_samples, double fill_value)
Prepend delay_samples zero-valued (or fill_value) samples, returning a new vector.
std::vector< double > slice(std::span< const double > data, double start_ratio, double end_ratio)
Extract a contiguous slice by ratio, returning a new vector.
TimeMap lagged(double origin, double rate, TimeMap lag)
Position of a moving reference minus a lag that can itself change.
TimeMap integrated(double start, std::shared_ptr< const double > velocity)
Position that integrates a velocity you can change while it plays.
T velocity(const Memory::HistoryBuffer< T > &history, double dt) noexcept
First difference: rate of change of position.
Tendency< double, double > TimeMap