14#include <unordered_map>
35 std::shared_ptr<const AudioFileData>
data;
57 auto current = std::atomic_load_explicit(&snapshot_, std::memory_order_acquire);
61 std::lock_guard lock(diagnostics_mutex_);
67 std::lock_guard lock(diagnostics_mutex_);
75 .generation = next_generation,
77 std::lock_guard lock(diagnostics_mutex_);
80 .generation = next_generation,
81 .path = std::move(path),
82 .reason = std::move(reason),
88 uint64_t memory_budget_bytes = 0) {
91 if (memory_budget_bytes != 0 && byte_size > memory_budget_bytes) {
94 .generation = next_generation,
98 .byte_size = byte_size,
99 .memory_budget_bytes = memory_budget_bytes,
101 std::lock_guard lock(diagnostics_mutex_);
104 .generation = next_generation,
105 .path = std::move(path),
106 .reason =
"decoded sample exceeds memory budget",
110 .byte_size = byte_size,
111 .memory_budget_bytes = memory_budget_bytes,
116 auto shared = std::make_shared<AudioFileData>(std::move(data));
122 uint64_t memory_budget_bytes = 0) {
130 if (memory_budget_bytes != 0 && byte_size > memory_budget_bytes) {
133 .generation = next_generation,
134 .frame_count = data->num_frames(),
135 .channel_count = data->num_channels(),
136 .sample_rate = data->sample_rate,
137 .byte_size = byte_size,
138 .memory_budget_bytes = memory_budget_bytes,
140 std::lock_guard lock(diagnostics_mutex_);
143 .generation = next_generation,
144 .path = std::move(path),
145 .reason =
"decoded sample exceeds memory budget",
146 .frame_count = data->num_frames(),
147 .channel_count = data->num_channels(),
148 .sample_rate = data->sample_rate,
149 .byte_size = byte_size,
150 .memory_budget_bytes = memory_budget_bytes,
157 .generation = next_generation,
158 .frame_count = data->num_frames(),
159 .channel_count = data->num_channels(),
160 .sample_rate = data->sample_rate,
161 .byte_size = byte_size,
162 .memory_budget_bytes = memory_budget_bytes,
163 .data = std::move(data),
165 publish_snapshot(next);
166 std::lock_guard lock(diagnostics_mutex_);
169 .generation = next_generation,
170 .path = std::move(path),
175 .memory_budget_bytes = memory_budget_bytes,
182 for (
const auto& channel : data.channels) {
183 total +=
static_cast<uint64_t
>(channel.size() *
sizeof(
float));
190 std::shared_ptr<const SampleResourceSnapshot> next =
191 std::make_shared<SampleResourceSnapshot>(std::move(
snapshot));
192 std::atomic_store_explicit(&snapshot_, std::move(next), std::memory_order_release);
195 std::shared_ptr<const SampleResourceSnapshot> snapshot_ =
196 std::make_shared<SampleResourceSnapshot>();
197 mutable std::mutex diagnostics_mutex_;
198 SampleResourceDiagnostics diagnostics_{};
224 : limits_(normalize_limits(
limits)) {}
226 std::shared_ptr<const AudioFileData>
get(
const std::string& path) {
227 auto it = entries_.find(path);
228 if (it == entries_.end()) {
233 lru_.splice(lru_.begin(), lru_, it->second.lru);
235 return it->second.data;
239 return put(std::move(path),
240 std::make_shared<AudioFileData>(std::move(data)));
243 bool put(std::string path, std::shared_ptr<const AudioFileData> data) {
244 if (path.empty() || !data) {
250 if (limits_.max_decoded_bytes != 0 && bytes > limits_.max_decoded_bytes) {
256 lru_.push_front(path);
257 entries_.emplace(path, Entry{
258 .data = std::move(data),
259 .decoded_bytes = bytes,
262 stats_.decoded_bytes += bytes;
265 return entries_.find(path) != entries_.end();
268 bool erase(
const std::string& path) {
269 auto it = entries_.find(path);
270 if (it == entries_.end())
return false;
271 stats_.decoded_bytes -= it->second.decoded_bytes;
272 lru_.erase(it->second.lru);
281 stats_.decoded_bytes = 0;
294 std::shared_ptr<const AudioFileData> data;
295 uint64_t decoded_bytes = 0;
296 std::list<std::string>::iterator lru;
299 static SampleResourceCacheLimits normalize_limits(SampleResourceCacheLimits
limits) {
304 void evict_to_limits() {
308 const auto victim = lru_.back();
314 SampleResourceCacheLimits limits_;
315 std::list<std::string> lru_;
316 std::unordered_map<std::string, Entry> entries_;
317 SampleResourceCacheStats stats_{};
330 float sample(uint32_t channel, uint64_t frame)
const noexcept {
331 if (!
contains(frame) || channel >=
data.channels.size())
return 0.0f;
332 const auto& samples =
data.channels[channel];
334 if (local >= samples.size())
return 0.0f;
335 return samples[
static_cast<std::size_t
>(local)];
343 std::vector<SampleResourcePage>
pages;
346 uint64_t covered = 0;
347 for (
const auto& page :
pages) {
348 covered += page.data.num_frames();
354 for (
const auto& page :
pages) {
355 if (page.contains(frame))
return &page;
360 float sample(uint32_t channel, uint64_t frame)
const noexcept {
362 return page ? page->sample(channel, frame) : 0.0f;
383 auto shared = std::make_shared<SampleResourcePageWindow>(std::move(window));
387 void publish(std::shared_ptr<const SampleResourcePageWindow> window) {
388 std::atomic_store_explicit(&window_, std::move(window), std::memory_order_release);
389 page_misses_.store(0, std::memory_order_relaxed);
390 last_miss_frame_.store(0, std::memory_order_relaxed);
394 std::shared_ptr<const SampleResourcePageWindow> empty;
395 std::atomic_store_explicit(&window_, std::move(empty), std::memory_order_release);
396 page_misses_.store(0, std::memory_order_relaxed);
397 last_miss_frame_.store(0, std::memory_order_relaxed);
400 std::shared_ptr<const SampleResourcePageWindow>
snapshot()
const {
401 return std::atomic_load_explicit(&window_, std::memory_order_acquire);
411 const auto* page = window->find_page(frame);
417 return page->sample(channel, frame);
422 uint64_t start_frame,
423 uint64_t frame_count)
const noexcept {
425 for (uint64_t i = 0; i < frame_count; ++i) {
433 .generation = window ? window->generation : 0,
434 .page_count = window ? window->pages.size() : 0,
435 .covered_frame_count = window ? window->covered_frame_count() : 0,
436 .page_misses = page_misses_.load(std::memory_order_relaxed),
437 .last_miss_frame = last_miss_frame_.load(std::memory_order_relaxed),
442 void record_miss(uint64_t frame)
const noexcept {
443 page_misses_.fetch_add(1, std::memory_order_relaxed);
444 last_miss_frame_.store(frame, std::memory_order_relaxed);
447 std::shared_ptr<const SampleResourcePageWindow> window_;
448 mutable std::atomic<uint64_t> page_misses_{0};
449 mutable std::atomic<uint64_t> last_miss_frame_{0};
468 const std::string& path,
482 , decode_(
std::move(decode)) {
499 std::lock_guard lock(mutex_);
501 if (
auto cached = cache_.
get(path)) {
505 options.memory_budget_bytes);
510 return submit_decode(std::move(path), &target, options);
516 std::lock_guard lock(mutex_);
518 if (cache_.
get(path)) {
524 return submit_decode(std::move(path),
nullptr, options);
528 std::vector<Completion> completions;
530 std::lock_guard lock(mutex_);
531 completions.swap(completions_);
533 for (
auto& completion : completions) {
534 if (completion.cancelled) {
536 }
else if (completion.data) {
537 auto shared = std::make_shared<AudioFileData>(std::move(*completion.data));
538 const auto cached =
shared;
539 cache_.put(completion.path, std::move(
shared));
540 if (completion.target) {
541 completion.target->publish_loaded(std::move(cached),
543 completion.memory_budget_bytes);
545 ++stats_.decode_successes;
547 if (completion.target) {
548 completion.target->publish_missing(completion.path, completion.reason);
550 ++stats_.decode_failures;
552 ++stats_.drained_completions;
556 return completions.size();
561 uint64_t memory_budget_bytes = 0) {
562 std::lock_guard lock(mutex_);
563 if (
auto cached = cache_.get(path)) {
565 target.
publish_loaded(std::move(cached), path, memory_budget_bytes);
578 std::lock_guard lock(mutex_);
579 return cache_.stats();
583 std::lock_guard lock(mutex_);
588 return jobs_.pending_count();
592 const std::string& path,
601 uint64_t memory_budget_bytes = 0;
602 std::optional<AudioFileData> data;
604 bool cancelled =
false;
608 SampleResourceHandle* target,
609 SampleResourceLoadOptions options) {
610 auto job_path = path;
613 .name =
"sample-resource-decode:" + path,
614 .priority = options.priority,
617 path = std::move(job_path),
619 memory_budget_bytes = options.memory_budget_bytes](
620 runtime::BackgroundJobContext& context)
mutable {
621 if (context.is_cancelled()) {
623 .path = std::move(path),
625 .memory_budget_bytes = memory_budget_bytes,
631 auto decoded = decode_(path, context);
632 if (context.is_cancelled()) {
634 .path = std::move(path),
636 .memory_budget_bytes = memory_budget_bytes,
643 .path = std::move(path),
645 .memory_budget_bytes = memory_budget_bytes,
646 .data = std::move(decoded),
647 .reason = decoded ? std::string{} :
"decode failed",
652 void queue_completion(Completion completion) {
653 std::lock_guard lock(mutex_);
654 completions_.push_back(std::move(completion));
657 mutable std::mutex mutex_;
658 runtime::BackgroundJobService jobs_;
659 SampleResourceCache cache_;
661 std::vector<Completion> completions_;
662 SampleResourceServiceStats stats_{};
void clear()
Definition sample_resource.hpp:277
bool put(std::string path, AudioFileData data)
Definition sample_resource.hpp:238
bool erase(const std::string &path)
Definition sample_resource.hpp:268
bool put(std::string path, std::shared_ptr< const AudioFileData > data)
Definition sample_resource.hpp:243
SampleResourceCache(SampleResourceCacheLimits limits={})
Definition sample_resource.hpp:223
SampleResourceCacheStats stats() const
Definition sample_resource.hpp:284
std::shared_ptr< const AudioFileData > get(const std::string &path)
Definition sample_resource.hpp:226
SampleResourceCacheLimits limits() const noexcept
Definition sample_resource.hpp:290
Definition sample_resource.hpp:54
SampleResourceSnapshot snapshot() const
Definition sample_resource.hpp:56
SampleResourceDiagnostics diagnostics() const
Definition sample_resource.hpp:60
void clear()
Definition sample_resource.hpp:65
void publish_missing(std::string path, std::string reason)
Definition sample_resource.hpp:71
static uint64_t decoded_byte_size(const AudioFileData &data) noexcept
Definition sample_resource.hpp:180
bool publish_loaded(std::shared_ptr< const AudioFileData > data, std::string path, uint64_t memory_budget_bytes=0)
Definition sample_resource.hpp:120
bool publish_loaded(AudioFileData data, std::string path, uint64_t memory_budget_bytes=0)
Definition sample_resource.hpp:86
Render-thread view of immutable decoded sample pages.
Definition sample_resource.hpp:380
float sample_or_zero(uint32_t channel, uint64_t frame) const noexcept
Definition sample_resource.hpp:404
void read_channel(float *dest, uint32_t channel, uint64_t start_frame, uint64_t frame_count) const noexcept
Definition sample_resource.hpp:420
void publish(SampleResourcePageWindow window)
Definition sample_resource.hpp:382
std::shared_ptr< const SampleResourcePageWindow > snapshot() const
Definition sample_resource.hpp:400
void publish(std::shared_ptr< const SampleResourcePageWindow > window)
Definition sample_resource.hpp:387
SampleResourcePageHandoffDiagnostics diagnostics() const
Definition sample_resource.hpp:430
void clear()
Definition sample_resource.hpp:393
SampleResourceService & operator=(const SampleResourceService &)=delete
~SampleResourceService()
Definition sample_resource.hpp:486
bool publish_cached(const std::string &path, SampleResourceHandle &target, SampleResourceLoadOptions options)
Definition sample_resource.hpp:571
SampleResourceCacheStats cache_stats() const
Definition sample_resource.hpp:577
std::size_t drain_completions()
Definition sample_resource.hpp:527
std::size_t pending_count() const
Definition sample_resource.hpp:587
runtime::BackgroundJobHandle load_async(std::string path, SampleResourceHandle &target, SampleResourceLoadOptions options={})
Definition sample_resource.hpp:495
SampleResourceService(SampleResourceCacheLimits limits={}, SampleResourceDecodeFunction decode={})
Definition sample_resource.hpp:478
SampleResourceService(const SampleResourceService &)=delete
SampleResourceServiceStats stats() const
Definition sample_resource.hpp:582
bool publish_cached(const std::string &path, SampleResourceHandle &target, uint64_t memory_budget_bytes=0)
Definition sample_resource.hpp:559
static std::optional< AudioFileData > default_decode(const std::string &path, runtime::BackgroundJobContext &)
Definition sample_resource.hpp:591
runtime::BackgroundJobHandle prefetch_async(std::string path, SampleResourceLoadOptions options={})
Definition sample_resource.hpp:513
Definition background_job.hpp:33
Definition background_job.hpp:51
BackgroundJobHandle submit(BackgroundJobOptions options, BackgroundJobFunction job)
Definition analyzer_provider.hpp:12
std::optional< AudioFileData > read_audio_file(const std::string &path)
SampleResourceStatus
Definition sample_resource.hpp:20
@ Missing
Definition sample_resource.hpp:23
@ Oversized
Definition sample_resource.hpp:24
@ Loaded
Definition sample_resource.hpp:22
@ Empty
Definition sample_resource.hpp:21
std::function< std::optional< AudioFileData >( const std::string &path, runtime::BackgroundJobContext &context)> SampleResourceDecodeFunction
Definition sample_resource.hpp:467
@ shared
Definition device.hpp:32
BackgroundJobPriority
Definition background_job.hpp:21
@ normal
Definition background_job.hpp:23
Definition audio_file.hpp:24
uint64_t num_frames() const
Definition audio_file.hpp:29
uint32_t num_channels() const
Definition audio_file.hpp:28
uint32_t sample_rate
Definition audio_file.hpp:26
Definition sample_resource.hpp:201
std::size_t max_entries
Definition sample_resource.hpp:202
uint64_t max_decoded_bytes
Definition sample_resource.hpp:203
Definition sample_resource.hpp:206
uint64_t misses
Definition sample_resource.hpp:210
uint64_t decoded_bytes
Definition sample_resource.hpp:208
uint64_t puts
Definition sample_resource.hpp:211
std::size_t entries
Definition sample_resource.hpp:207
uint64_t evictions
Definition sample_resource.hpp:212
uint64_t hits
Definition sample_resource.hpp:209
uint64_t rejections
Definition sample_resource.hpp:213
Definition sample_resource.hpp:42
std::string path
Definition sample_resource.hpp:45
uint64_t frame_count
Definition sample_resource.hpp:47
uint64_t byte_size
Definition sample_resource.hpp:50
std::string reason
Definition sample_resource.hpp:46
uint32_t channel_count
Definition sample_resource.hpp:48
uint64_t memory_budget_bytes
Definition sample_resource.hpp:51
uint64_t generation
Definition sample_resource.hpp:44
uint32_t sample_rate
Definition sample_resource.hpp:49
SampleResourceStatus status
Definition sample_resource.hpp:43
Definition sample_resource.hpp:452
runtime::BackgroundJobPriority priority
Definition sample_resource.hpp:454
uint64_t memory_budget_bytes
Definition sample_resource.hpp:453
Definition sample_resource.hpp:366
uint64_t covered_frame_count
Definition sample_resource.hpp:369
std::size_t page_count
Definition sample_resource.hpp:368
uint64_t generation
Definition sample_resource.hpp:367
uint64_t page_misses
Definition sample_resource.hpp:370
uint64_t last_miss_frame
Definition sample_resource.hpp:371
Definition sample_resource.hpp:339
uint32_t channel_count
Definition sample_resource.hpp:341
std::vector< SampleResourcePage > pages
Definition sample_resource.hpp:343
float sample(uint32_t channel, uint64_t frame) const noexcept
Definition sample_resource.hpp:360
const SampleResourcePage * find_page(uint64_t frame) const noexcept
Definition sample_resource.hpp:353
uint64_t covered_frame_count() const noexcept
Definition sample_resource.hpp:345
uint32_t sample_rate
Definition sample_resource.hpp:340
uint64_t generation
Definition sample_resource.hpp:342
Definition sample_resource.hpp:320
bool contains(uint64_t frame) const noexcept
Definition sample_resource.hpp:326
uint64_t end_frame() const noexcept
Definition sample_resource.hpp:324
AudioFileData data
Definition sample_resource.hpp:322
uint64_t start_frame
Definition sample_resource.hpp:321
float sample(uint32_t channel, uint64_t frame) const noexcept
Definition sample_resource.hpp:330
Definition sample_resource.hpp:457
uint64_t submitted_prefetches
Definition sample_resource.hpp:459
uint64_t cache_hits
Definition sample_resource.hpp:460
uint64_t drained_completions
Definition sample_resource.hpp:464
uint64_t submitted_loads
Definition sample_resource.hpp:458
uint64_t cancelled
Definition sample_resource.hpp:463
uint64_t decode_failures
Definition sample_resource.hpp:462
uint64_t decode_successes
Definition sample_resource.hpp:461
Definition sample_resource.hpp:27
uint64_t generation
Definition sample_resource.hpp:29
uint32_t sample_rate
Definition sample_resource.hpp:32
uint64_t frame_count
Definition sample_resource.hpp:30
std::shared_ptr< const AudioFileData > data
Definition sample_resource.hpp:35
SampleResourceStatus status
Definition sample_resource.hpp:28
bool ready() const noexcept
Definition sample_resource.hpp:37
uint32_t channel_count
Definition sample_resource.hpp:31
uint64_t byte_size
Definition sample_resource.hpp:33
uint64_t memory_budget_bytes
Definition sample_resource.hpp:34