38 if (!std::isfinite(cutoff))
41 kernel_.build(32, 512, 10.0, cutoff);
45 return kernel_.ready();
52 kernel_.prepared_bytes() +
56 float read(std::span<const float> source,
double position)
const noexcept {
58 source.size() >
static_cast<std::size_t
>(std::numeric_limits<int>::max()) ||
59 !std::isfinite(position))
61 position = std::clamp(position, 0.0,
static_cast<double>(source.size() - 1));
62 return kernel_.read(source.data(),
static_cast<int>(source.size()), position);
67 struct IncrementalBuildTag {};
84 if (!std::isfinite(cutoff) ||
88 PreparedSampleRateConversion::IncrementalBuildTag{}, cutoff));
92 return result_ !=
nullptr;
96 return !result_ || result_->build_step();
99 std::shared_ptr<const PreparedSampleRateConversion>
take() {
100 return result_ && result_->ready() ? std::move(result_) :
nullptr;
104 std::shared_ptr<PreparedSampleRateConversion> result_;
114class PreparedVariableRateConversion;
121 std::uint64_t source_start, std::uint64_t source_frames);
125 return result_ !=
nullptr;
127 std::shared_ptr<const PreparedVariableRateConversion>
take();
130 std::shared_ptr<PreparedVariableRateConversion> result_;
132 std::size_t kernel_level_ = 0;
133 bool decimator_started_ =
false;
134 std::size_t level_ = 0;
135 std::size_t channel_ = 0;
136 std::size_t frame_ = 0;
139class PreparedVariableRateConversion {
147 static std::shared_ptr<const PreparedVariableRateConversion>
148 build(std::shared_ptr<const AudioFileData> source, std::uint64_t source_start,
149 std::uint64_t source_frames) {
151 while (!builder.
step()) {
153 return builder.
take();
156 static std::optional<std::uint64_t>
prepared_bytes(std::uint64_t source_frames,
157 std::size_t channels)
noexcept;
160 return prepared_bytes_;
164 std::uint64_t source_start,
165 std::uint64_t source_frames)
const noexcept {
166 return source_ == source && source_start_ == source_start &&
167 source_frames_ == source_frames;
170 float read(std::size_t channel,
double position,
171 double source_frames_per_output_frame)
const noexcept {
172 if (!source_ || channel >= source_->channels.size() || !std::isfinite(position) ||
173 !std::isfinite(source_frames_per_output_frame) ||
174 source_frames_per_output_frame <= 0.0 ||
178 const auto pyramid_level =
179 source_frames_per_output_frame <= 2.0
182 std::log2(source_frames_per_output_frame / 2.0))));
183 const auto scale = std::exp2(
static_cast<double>(pyramid_level));
184 const auto residual_step = source_frames_per_output_frame / scale;
185 const auto required_cutoff = std::min(1.0, 1.0 / residual_step);
186 const auto kernel_level =
static_cast<std::size_t
>(
188 if (kernel_level >= kernels_.size())
192 if (pyramid_level == 0) {
193 const auto source = std::span<const float>(source_->channels[channel])
194 .subspan(
static_cast<std::size_t
>(source_start_),
195 static_cast<std::size_t
>(source_frames_));
196 if (source.empty() ||
197 source.size() >
static_cast<std::size_t
>(std::numeric_limits<int>::max()))
199 return kernels_[kernel_level].read(source.data(),
static_cast<int>(source.size()),
202 return read_chunked(kernels_[kernel_level], pyramid_[pyramid_level - 1u][channel],
209 struct ChunkedSamples {
210 static constexpr std::size_t kFanout = 64;
211 using SampleBlock = std::array<float, kPyramidBlockSamples>;
215 for (
auto* block :
blocks)
218 std::array<SampleBlock*, kFanout>
blocks{};
237 ChunkedSamples() =
default;
238 ChunkedSamples(
const ChunkedSamples&) =
delete;
239 ChunkedSamples& operator=(
const ChunkedSamples&) =
delete;
242 for (
auto* upper : uppers)
246 bool push_back(
float sample)
noexcept {
248 const auto block_slot = block_index % kFanout;
249 block_index /= kFanout;
250 const auto leaf_slot = block_index % kFanout;
251 block_index /= kFanout;
252 const auto middle_slot = block_index % kFanout;
253 const auto upper_slot = block_index / kFanout;
254 if (upper_slot >= kFanout)
256 auto*& upper = uppers[upper_slot];
258 upper =
new (std::nothrow) UpperPage;
263 auto*& middle = upper->middles[middle_slot];
265 middle =
new (std::nothrow) MiddlePage;
270 auto*& leaf = middle->leaves[leaf_slot];
272 leaf =
new (std::nothrow) LeafPage;
277 auto*&
block = leaf->blocks[block_slot];
279 block =
new (std::nothrow) SampleBlock;
289 float operator[](std::size_t index)
const noexcept {
291 const auto block_slot = block_index % kFanout;
292 block_index /= kFanout;
293 const auto leaf_slot = block_index % kFanout;
294 block_index /= kFanout;
295 const auto middle_slot = block_index % kFanout;
296 const auto upper_slot = block_index / kFanout;
297 return (*uppers[upper_slot]
298 ->middles[middle_slot]
303 std::uint64_t allocated_bytes() const noexcept {
304 const auto allocations = upper_count + middle_count + leaf_count + block_count;
306 leaf_count *
sizeof(
LeafPage) + block_count *
sizeof(SampleBlock) +
310 std::array<UpperPage*, kFanout> uppers{};
311 std::size_t size = 0;
312 std::uint64_t upper_count = 0;
313 std::uint64_t middle_count = 0;
314 std::uint64_t leaf_count = 0;
315 std::uint64_t block_count = 0;
319 double position)
noexcept {
320 if (source.size == 0 || !std::isfinite(position))
322 position = std::clamp(position, 0.0,
static_cast<double>(source.size - 1u));
323 const auto center =
static_cast<std::int64_t
>(std::floor(position));
324 const auto fraction = position -
static_cast<double>(
center);
325 std::array<float, 64> neighborhood{};
326 const auto taps =
static_cast<std::size_t
>(kernel.taps());
327 for (std::size_t tap = 0; tap < taps; ++tap) {
329 std::clamp(center +
static_cast<std::int64_t
>(tap) - kernel.half_width() + 1,
330 std::int64_t{0},
static_cast<std::int64_t
>(source.size - 1u));
331 neighborhood[tap] = source[
static_cast<std::size_t
>(
index)];
333 return kernel.apply(neighborhood.data(), fraction);
336 PreparedVariableRateConversion(std::shared_ptr<const AudioFileData> source,
337 std::uint64_t source_start, std::uint64_t source_frames)
338 : source_(std::
move(source)), source_start_(source_start), source_frames_(source_frames) {
339 const auto bytes =
prepared_bytes(source_frames_, source_ ? source_->channels.size() : 0);
340 prepared_bytes_ = bytes.value_or(0);
343 void refresh_prepared_bytes() noexcept {
344 std::uint64_t bytes =
346 for (
const auto& kernel : kernels_)
347 bytes += kernel.prepared_bytes() +
349 const auto channels = source_->channels.size();
350 for (
const auto& level : pyramid_) {
354 for (std::size_t channel = 0; channel < channels; ++channel)
355 bytes += level[channel].allocated_bytes();
357 prepared_bytes_ = bytes;
360 std::shared_ptr<const AudioFileData> source_;
361 std::uint64_t source_start_ = 0;
362 std::uint64_t source_frames_ = 0;
363 std::uint64_t prepared_bytes_ = 0;
364 std::array<std::unique_ptr<ChunkedSamples[]>,
kPyramidLevels> pyramid_;
365 std::array<signal::SincResampler, kCutoffLevels> kernels_;
369 constexpr auto kernel_table_bytes = (512u + 1u) * (2u * 32u) *
sizeof(float);
374inline std::optional<std::uint64_t>
376 std::size_t channels)
noexcept {
377 if (source_frames == 0 ||
378 source_frames >
static_cast<std::uint64_t
>(std::numeric_limits<int>::max()) ||
381 constexpr std::uint64_t kernel_table_bytes =
386 auto level_frames = source_frames;
388 level_frames = level_frames / 2u + level_frames % 2u;
389 const auto ceil_count = [](std::uint64_t value, std::uint64_t divisor)
noexcept {
390 return value / divisor + (value % divisor != 0 ? 1u : 0u);
393 const auto leaf_count = ceil_count(block_count, ChunkedSamples::kFanout);
394 const auto middle_count = ceil_count(leaf_count, ChunkedSamples::kFanout);
395 const auto upper_count = ceil_count(middle_count, ChunkedSamples::kFanout);
396 const auto allocation_count = block_count + leaf_count + middle_count + upper_count;
397 const auto channel_bytes =
sizeof(ChunkedSamples) +
398 block_count *
sizeof(ChunkedSamples::SampleBlock) +
405 const auto available =
407 if (channels > available / channel_bytes)
415 std::shared_ptr<const AudioFileData> source, std::uint64_t source_start,
416 std::uint64_t source_frames) {
417 if (!source || source_frames == 0 || source_frames > std::numeric_limits<int>::max() ||
418 source->channels.empty())
420 const auto total_frames = source->num_frames();
421 if (total_frames == 0 || source_start > total_frames ||
422 source_frames > total_frames - source_start ||
424 source->channels.begin(), source->channels.end(),
425 [total_frames](
const auto& channel) { return channel.size() == total_frames; }))
429 result_->kernels_[0].begin_build(16, 128, 10.0, 1.0);
436 if (!result_->kernels_[kernel_level_].build_step())
441 1.0 -
static_cast<double>(kernel_level_) /
442 (2.0 *
static_cast<double>(
444 result_->kernels_[kernel_level_].begin_build(16, 128, 10.0, cutoff);
448 if (!decimator_started_) {
449 decimator_.begin_build(32, 128, 10.0, 0.5);
450 decimator_started_ =
true;
453 if (!decimator_.build_step())
455 if (!result_->pyramid_[level_]) {
456 auto* channels =
new (std::nothrow)
457 PreparedVariableRateConversion::ChunkedSamples[result_->source_->channels.size()];
462 result_->pyramid_[level_].reset(channels);
465 auto& output = result_->pyramid_[level_][channel_];
466 const auto source_frames =
467 level_ == 0 ? result_->source_frames_ : result_->pyramid_[level_ - 1u][channel_].size;
468 const auto output_frames = (source_frames + 1u) / 2u;
469 if (frame_ < output_frames) {
470 const auto position =
static_cast<double>(frame_ * 2u);
472 level_ == 0 ? decimator_.read(result_->source_->channels[channel_].data() +
473 static_cast<std::size_t
>(result_->source_start_),
474 static_cast<int>(result_->source_frames_), position)
475 : PreparedVariableRateConversion::read_chunked(
476 decimator_, result_->pyramid_[level_ - 1u][channel_], position);
477 if (!output.push_back(sample)) {
486 if (channel_ == result_->source_->channels.size()) {
496 result_->refresh_prepared_bytes();
497 return std::move(result_);
Immutable, allocation-free read kernel after control-thread construction.
Definition sample_rate_conversion.hpp:31
PreparedSampleRateConversion(double cutoff)
Definition sample_rate_conversion.hpp:37
float read(std::span< const float > source, double position) const noexcept
Definition sample_rate_conversion.hpp:56
static std::uint64_t estimated_prepared_bytes() noexcept
Definition sample_rate_conversion.hpp:368
static constexpr double kMinimumSupportedCutoff
Definition sample_rate_conversion.hpp:35
std::uint64_t prepared_bytes() const noexcept
Definition sample_rate_conversion.hpp:50
friend class SampleRateConversionBuilder
Definition sample_rate_conversion.hpp:66
bool ready() const noexcept
Definition sample_rate_conversion.hpp:44
Definition sample_rate_conversion.hpp:139
static constexpr std::size_t kPyramidLevels
Definition sample_rate_conversion.hpp:143
static std::shared_ptr< const PreparedVariableRateConversion > build(std::shared_ptr< const AudioFileData > source, std::uint64_t source_start, std::uint64_t source_frames)
Definition sample_rate_conversion.hpp:148
static constexpr std::size_t kPyramidBlockSamples
Definition sample_rate_conversion.hpp:144
float read(std::size_t channel, double position, double source_frames_per_output_frame) const noexcept
Definition sample_rate_conversion.hpp:170
friend class VariableRateConversionBuilder
Definition sample_rate_conversion.hpp:207
static constexpr std::size_t kCutoffLevels
Definition sample_rate_conversion.hpp:142
static constexpr double kMaximumSourceFramesPerOutputFrame
Definition sample_rate_conversion.hpp:145
bool matches_source_slice(const std::shared_ptr< const AudioFileData > &source, std::uint64_t source_start, std::uint64_t source_frames) const noexcept
Definition sample_rate_conversion.hpp:163
static constexpr std::size_t kLinearCutoffLevels
Definition sample_rate_conversion.hpp:141
std::uint64_t prepared_bytes() const noexcept
Definition sample_rate_conversion.hpp:159
Definition sample_rate_conversion.hpp:81
SampleRateConversionBuilder(double cutoff)
Definition sample_rate_conversion.hpp:83
std::shared_ptr< const PreparedSampleRateConversion > take()
Definition sample_rate_conversion.hpp:99
bool step()
Definition sample_rate_conversion.hpp:95
bool valid() const noexcept
Definition sample_rate_conversion.hpp:91
std::shared_ptr< const PreparedVariableRateConversion > take()
Definition sample_rate_conversion.hpp:493
VariableRateConversionBuilder(std::shared_ptr< const AudioFileData > source, std::uint64_t source_start, std::uint64_t source_frames)
Invalid/null sources and out-of-range slices produce an invalid builder; step() then completes immedi...
Definition sample_rate_conversion.hpp:414
bool valid() const noexcept
Definition sample_rate_conversion.hpp:124
bool step()
Definition sample_rate_conversion.hpp:432
bool build_step()
Definition sinc_resampler.hpp:81
void begin_build(int half_width=16, int phases=512, double beta=9.0, double cutoff=1.0)
Starts a resumable kernel-table build.
Definition sinc_resampler.hpp:67
Definition analyzer_provider.hpp:12
constexpr std::uint64_t kConverterSharedOwnershipBytes
Definition sample_rate_conversion.hpp:25
constexpr std::uint64_t kConverterAllocationOverhead
Definition sample_rate_conversion.hpp:24
@ center
Definition canvas.hpp:221
@ index
Definition gpu_pass.hpp:140
SincResamplerT< float > SincResampler
Definition sinc_resampler.hpp:199
@ move
Definition web_event_translate.hpp:110
@ block
Filled glyph cell; the covered glyph is drawn in the bg color.
Definition caret.hpp:66
Fractional-delay resampling by a Kaiser-windowed sinc kernel.
Definition sample_rate_conversion.hpp:213
std::array< SampleBlock *, kFanout > blocks
Definition sample_rate_conversion.hpp:218
~LeafPage()
Definition sample_rate_conversion.hpp:214
Definition sample_rate_conversion.hpp:221
~MiddlePage()
Definition sample_rate_conversion.hpp:222
std::array< LeafPage *, kFanout > leaves
Definition sample_rate_conversion.hpp:226
Definition sample_rate_conversion.hpp:229
std::array< MiddlePage *, kFanout > middles
Definition sample_rate_conversion.hpp:234
~UpperPage()
Definition sample_rate_conversion.hpp:230