mirror of
https://github.com/blender/blender
synced 2026-09-29 04:37:17 +03:00
Geometry Nodes: Reuse audio readers for sound frequency sampling
Reuse AUDASPACE readers between uncached Sample Sound Frequencies windows instead of creating a new reader for every window. Readers are acquired through a small cache associated with the current `bSound` runtime generation. An RAII lease automatically returns readers after use, while failed seek or read operations discard potentially invalid readers. The cache retains at most two idle readers. When all cached readers are in use, a new reader is created outside the cache lock instead of making concurrent evaluations wait. Cached samplers and their reader cache remain alive for evaluations already in progress when a sound is reloaded or freed.
This commit is contained in:
parent
ef73004e74
commit
e4218ae71a
9 changed files with 462 additions and 37 deletions
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
#include "BLI_array.hh"
|
||||
|
|
@ -14,6 +15,10 @@
|
|||
|
||||
namespace blender::bke {
|
||||
|
||||
#if defined(WITH_AUDASPACE)
|
||||
class SoundReaderCache;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* This class allows efficiently sampling an arbitrary frequency range at an arbitrary point in
|
||||
* time. This is achieved by caching the result of the fourier transform for various windows and
|
||||
|
|
@ -79,8 +84,10 @@ class bSoundFrequencySampler {
|
|||
mutable std::optional<Array<float, 0>> cumulative_amplitudes;
|
||||
};
|
||||
|
||||
AUD_Sound sound_;
|
||||
Key key_;
|
||||
#if defined(WITH_AUDASPACE)
|
||||
std::shared_ptr<SoundReaderCache> reader_cache_;
|
||||
#endif
|
||||
/** Derived from the sound. */
|
||||
int samples_per_second_;
|
||||
/**
|
||||
|
|
@ -92,15 +99,15 @@ class bSoundFrequencySampler {
|
|||
int window_cache_stride_;
|
||||
Array<WindowCache> window_caches_;
|
||||
|
||||
#if defined(WITH_AUDASPACE)
|
||||
/** Cached weights of the selected window function. */
|
||||
const WindowWeights &window_weights_;
|
||||
#endif
|
||||
|
||||
public:
|
||||
/** Construct a new sampler, prefer using #get_cached instead. */
|
||||
bSoundFrequencySampler(AUD_Sound sound, const Key &key);
|
||||
|
||||
/** Access a reusable frequency sampler for the given sound. */
|
||||
static const bSoundFrequencySampler *get_cached(const bSound &sound, const Key &key);
|
||||
static std::shared_ptr<const bSoundFrequencySampler> get_cached(const bSound &sound,
|
||||
const Key &key);
|
||||
|
||||
/** Sample the amplitude a the given time and frequency range. */
|
||||
float sample(float time,
|
||||
|
|
@ -110,6 +117,12 @@ class bSoundFrequencySampler {
|
|||
InterpolationMethod frequency_interpolation) const;
|
||||
|
||||
private:
|
||||
#if defined(WITH_AUDASPACE)
|
||||
/** Construct a sampler sharing the reader cache of its runtime generation. */
|
||||
bSoundFrequencySampler(AUD_Sound sound,
|
||||
std::shared_ptr<SoundReaderCache> reader_cache,
|
||||
const Key &key);
|
||||
#endif
|
||||
float sample_frequency_range_in_window(int window_i,
|
||||
float low,
|
||||
float high,
|
||||
|
|
|
|||
|
|
@ -269,6 +269,7 @@ set(SRC
|
|||
intern/shrinkwrap.cc
|
||||
intern/softbody.cc
|
||||
intern/sound.cc
|
||||
intern/sound_reader_cache.cc
|
||||
intern/speaker.cc
|
||||
intern/studiolight.cc
|
||||
intern/subdiv.cc
|
||||
|
|
@ -563,6 +564,7 @@ set(SRC
|
|||
intern/pbvh_intern.hh
|
||||
intern/pbvh_pixels_copy.hh
|
||||
intern/pbvh_uv_islands.hh
|
||||
intern/sound_reader_cache.hh
|
||||
intern/subdiv_converter.hh
|
||||
intern/subdiv_inline.hh
|
||||
intern/tracking_private.hh
|
||||
|
|
@ -781,6 +783,7 @@ if(WITH_GTESTS)
|
|||
intern/node_socket_value_iter_test.cc
|
||||
intern/path_templates_test.cc
|
||||
intern/scene_test.cc
|
||||
intern/sound_reader_cache_test.cc
|
||||
intern/subdiv_ccg_test.cc
|
||||
intern/tracking_test.cc
|
||||
intern/volume_test.cc
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
#include <numeric>
|
||||
#include <optional>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
#ifdef WITH_FFTW3
|
||||
# include <fftw3.h>
|
||||
|
|
@ -48,6 +49,7 @@
|
|||
|
||||
#ifdef WITH_AUDASPACE
|
||||
# include "BLI_set.hh"
|
||||
# include "BLI_vector.hh"
|
||||
|
||||
# include <Exception.h>
|
||||
# include <IReader.h>
|
||||
|
|
@ -91,6 +93,8 @@
|
|||
#include "BKE_sound.hh"
|
||||
#include "BKE_sound_sample.hh"
|
||||
|
||||
#include "sound_reader_cache.hh"
|
||||
|
||||
#include "DEG_depsgraph.hh"
|
||||
#include "DEG_depsgraph_query.hh"
|
||||
|
||||
|
|
@ -119,6 +123,14 @@ ENUM_OPERATORS(SoundTags);
|
|||
using bSoundFrequencySamplerMap =
|
||||
ConcurrentMap<bSoundFrequencySampler::Key, std::shared_ptr<bSoundFrequencySampler>>;
|
||||
|
||||
#ifdef WITH_AUDASPACE
|
||||
struct SoundRuntimeGeneration {
|
||||
AUD_Sound handle;
|
||||
bSoundFrequencySamplerMap samplers;
|
||||
std::shared_ptr<SoundReaderCache> reader_cache;
|
||||
};
|
||||
#endif
|
||||
|
||||
struct SoundRuntime {
|
||||
AUD_Sound handle;
|
||||
AUD_Sound cache;
|
||||
|
|
@ -133,8 +145,10 @@ struct SoundRuntime {
|
|||
Vector<float> *waveform = nullptr;
|
||||
SoundTags tags = SoundTags::None;
|
||||
|
||||
/** Caches frequency samplers for this sound. */
|
||||
bSoundFrequencySamplerMap samplers;
|
||||
#ifdef WITH_AUDASPACE
|
||||
std::mutex generation_mutex;
|
||||
std::shared_ptr<SoundRuntimeGeneration> generation;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace bke
|
||||
|
|
@ -147,6 +161,28 @@ static void sound_init_runtime(bSound *sound)
|
|||
BLI_spin_init(&sound->runtime->spinlock);
|
||||
}
|
||||
|
||||
#ifdef WITH_AUDASPACE
|
||||
static void sound_runtime_generation_replace(
|
||||
bSound *sound, std::shared_ptr<bke::SoundRuntimeGeneration> generation)
|
||||
{
|
||||
bke::SoundRuntime *runtime = sound->runtime;
|
||||
std::shared_ptr<bke::SoundRuntimeGeneration> old_generation;
|
||||
{
|
||||
std::lock_guard lock{runtime->generation_mutex};
|
||||
old_generation = std::move(runtime->generation);
|
||||
runtime->generation = std::move(generation);
|
||||
}
|
||||
}
|
||||
|
||||
static std::shared_ptr<bke::SoundRuntimeGeneration> sound_runtime_generation_get(
|
||||
const bSound &sound)
|
||||
{
|
||||
bke::SoundRuntime *runtime = sound.runtime;
|
||||
std::lock_guard lock{runtime->generation_mutex};
|
||||
return runtime->generation;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void sound_free_waveform(bSound *sound)
|
||||
{
|
||||
bke::SoundRuntime *runtime = sound->runtime;
|
||||
|
|
@ -363,6 +399,7 @@ static void sound_free_audio(bSound *sound)
|
|||
{
|
||||
#ifdef WITH_AUDASPACE
|
||||
bke::SoundRuntime *runtime = sound->runtime;
|
||||
sound_runtime_generation_replace(sound, nullptr);
|
||||
runtime->handle.reset();
|
||||
runtime->playback_handle.reset();
|
||||
runtime->cache.reset();
|
||||
|
|
@ -689,6 +726,7 @@ void BKE_sound_refresh_callback_bmain(Main *bmain)
|
|||
static void sound_load_audio(Main *bmain, bSound *sound, bool free_waveform)
|
||||
{
|
||||
bke::SoundRuntime *runtime = sound->runtime;
|
||||
sound_runtime_generation_replace(sound, nullptr);
|
||||
runtime->cache.reset();
|
||||
runtime->handle.reset();
|
||||
runtime->playback_handle.reset();
|
||||
|
|
@ -738,6 +776,11 @@ static void sound_load_audio(Main *bmain, bSound *sound, bool free_waveform)
|
|||
else {
|
||||
runtime->playback_handle = runtime->handle;
|
||||
}
|
||||
|
||||
auto generation = std::make_shared<bke::SoundRuntimeGeneration>();
|
||||
generation->handle = runtime->handle;
|
||||
generation->reader_cache = std::make_shared<bke::SoundReaderCache>(generation->handle);
|
||||
sound_runtime_generation_replace(sound, std::move(generation));
|
||||
}
|
||||
|
||||
void BKE_sound_load(Main *bmain, bSound *sound)
|
||||
|
|
@ -2147,25 +2190,29 @@ const Vector<float> *BKE_sound_runtime_get_waveform(const bSound *sound)
|
|||
|
||||
namespace bke {
|
||||
|
||||
const bSoundFrequencySampler *bSoundFrequencySampler::get_cached(const bSound &sound,
|
||||
const Key &key)
|
||||
std::shared_ptr<const bSoundFrequencySampler> bSoundFrequencySampler::get_cached(
|
||||
const bSound &sound, const Key &key)
|
||||
{
|
||||
#ifdef WITH_AUDASPACE
|
||||
const std::shared_ptr<SoundRuntimeGeneration> generation = sound_runtime_generation_get(sound);
|
||||
if (!generation) {
|
||||
return nullptr;
|
||||
}
|
||||
{
|
||||
/* Fast common case when the sampler has been created already. */
|
||||
bSoundFrequencySamplerMap::ConstAccessor accessor;
|
||||
if (sound.runtime->samplers.lookup(accessor, key)) {
|
||||
return accessor->second.get();
|
||||
if (generation->samplers.lookup(accessor, key)) {
|
||||
return accessor->second;
|
||||
}
|
||||
}
|
||||
AUD_Sound sound_handle = sound.runtime->handle;
|
||||
AUD_Sound sound_handle = generation->handle;
|
||||
if (!sound_handle) {
|
||||
/* Maybe try to load the sound in this case instead of relying on cache. */
|
||||
return nullptr;
|
||||
}
|
||||
/* Slower case when the sampler is newly created. */
|
||||
bSoundFrequencySamplerMap::MutableAccessor accessor;
|
||||
if (sound.runtime->samplers.add(accessor, key)) {
|
||||
if (generation->samplers.add(accessor, key)) {
|
||||
if (key.channel.has_value()) {
|
||||
const SoundInfo info = sound_info_get(sound_handle);
|
||||
const int channel = *key.channel;
|
||||
|
|
@ -2173,15 +2220,17 @@ const bSoundFrequencySampler *bSoundFrequencySampler::get_cached(const bSound &s
|
|||
return nullptr;
|
||||
}
|
||||
}
|
||||
accessor->second = std::make_shared<bSoundFrequencySampler>(sound_handle, key);
|
||||
accessor->second = std::shared_ptr<bSoundFrequencySampler>(
|
||||
new bSoundFrequencySampler(sound_handle, generation->reader_cache, key));
|
||||
}
|
||||
return accessor->second.get();
|
||||
return accessor->second;
|
||||
#else
|
||||
UNUSED_VARS(sound, key);
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef WITH_AUDASPACE
|
||||
static bSoundFrequencySampler::WindowWeights compute_window_function_weights(
|
||||
const bSoundFrequencySampler::WindowFunction window, const int size)
|
||||
{
|
||||
|
|
@ -2229,14 +2278,17 @@ static const bSoundFrequencySampler::WindowWeights &get_window_function_weights(
|
|||
compute_window_function_weights(window, size));
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
bSoundFrequencySampler::bSoundFrequencySampler(AUD_Sound sound, const Key &key)
|
||||
: sound_(sound),
|
||||
key_(key),
|
||||
#ifdef WITH_AUDASPACE
|
||||
bSoundFrequencySampler::bSoundFrequencySampler(AUD_Sound sound,
|
||||
std::shared_ptr<SoundReaderCache> reader_cache,
|
||||
const Key &key)
|
||||
: key_(key),
|
||||
reader_cache_(std::move(reader_cache)),
|
||||
window_weights_(get_window_function_weights(key.window_function, key.fft_size))
|
||||
{
|
||||
#ifdef WITH_AUDASPACE
|
||||
const SoundInfo info = bke::sound_info_get(sound_);
|
||||
const SoundInfo info = bke::sound_info_get(sound);
|
||||
samples_per_second_ = info.specs.samplerate;
|
||||
/* This could be a parameter but a single fixed value seems fine for now and makes caching much
|
||||
* simpler. */
|
||||
|
|
@ -2244,11 +2296,8 @@ bSoundFrequencySampler::bSoundFrequencySampler(AUD_Sound sound, const Key &key)
|
|||
const int window_caches_num = std::ceil(info.length * info.specs.samplerate /
|
||||
window_cache_stride_);
|
||||
window_caches_.reinitialize(window_caches_num);
|
||||
#else
|
||||
UNUSED_VARS(sound, key);
|
||||
BLI_assert_unreachable();
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
std::optional<Array<float>> bSoundFrequencySampler::compute_fft(const int start_sample) const
|
||||
{
|
||||
|
|
@ -2261,17 +2310,26 @@ std::optional<Array<float>> bSoundFrequencySampler::compute_fft(const int start_
|
|||
* because the #read function may sometimes give invalid data for the first samples. */
|
||||
const int warmup_samples = std::min(2000, start_sample);
|
||||
|
||||
/* Prepare the reader. */
|
||||
std::shared_ptr<aud::IReader> reader = sound_->createReader();
|
||||
const aud::Specs specs = reader->getSpecs();
|
||||
const int channels_num = specs.channels;
|
||||
|
||||
/* Read the raw samples from the audio stream. */
|
||||
Array<float> read_buffer_extra((key_.fft_size + warmup_samples) * channels_num);
|
||||
int channels_num;
|
||||
Array<float> read_buffer_extra;
|
||||
bool is_end_of_stream = false;
|
||||
int length = key_.fft_size + warmup_samples;
|
||||
reader->seek(std::max(start_sample - warmup_samples, 0));
|
||||
reader->read(length, is_end_of_stream, read_buffer_extra.data());
|
||||
{
|
||||
SoundReaderLease reader = reader_cache_->acquire();
|
||||
if (!reader) {
|
||||
return std::nullopt;
|
||||
}
|
||||
channels_num = reader->getSpecs().channels;
|
||||
read_buffer_extra.reinitialize((key_.fft_size + warmup_samples) * channels_num);
|
||||
try {
|
||||
reader->seek(std::max(start_sample - warmup_samples, 0));
|
||||
reader->read(length, is_end_of_stream, read_buffer_extra.data());
|
||||
}
|
||||
catch (...) {
|
||||
reader.discard();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
const Span<float> read_buffer = read_buffer_extra.as_span().drop_front(warmup_samples *
|
||||
channels_num);
|
||||
const int read_length = read_buffer.size() / channels_num;
|
||||
|
|
|
|||
121
source/blender/blenkernel/intern/sound_reader_cache.cc
Normal file
121
source/blender/blenkernel/intern/sound_reader_cache.cc
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup bke
|
||||
*/
|
||||
|
||||
#include "sound_reader_cache.hh"
|
||||
|
||||
#if defined(WITH_AUDASPACE)
|
||||
|
||||
# include <utility>
|
||||
|
||||
# include "BLI_mutex.hh"
|
||||
# include "BLI_vector.hh"
|
||||
|
||||
# include <IReader.h>
|
||||
# include <ISound.h>
|
||||
|
||||
namespace blender::bke {
|
||||
|
||||
struct SoundReaderCacheState {
|
||||
static constexpr int max_idle_readers = 2;
|
||||
|
||||
explicit SoundReaderCacheState(std::shared_ptr<aud::ISound> sound) : sound(std::move(sound)) {}
|
||||
|
||||
std::shared_ptr<aud::ISound> sound;
|
||||
Mutex mutex;
|
||||
Vector<std::shared_ptr<aud::IReader>, max_idle_readers> idle_readers;
|
||||
|
||||
void release(std::shared_ptr<aud::IReader> reader) noexcept
|
||||
{
|
||||
if (!reader) {
|
||||
return;
|
||||
}
|
||||
std::lock_guard lock{this->mutex};
|
||||
if (this->idle_readers.size() < max_idle_readers) {
|
||||
this->idle_readers.append(std::move(reader));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
SoundReaderLease::SoundReaderLease(std::shared_ptr<SoundReaderCacheState> state,
|
||||
std::shared_ptr<aud::IReader> reader)
|
||||
: state_(std::move(state)), reader_(std::move(reader))
|
||||
{
|
||||
}
|
||||
|
||||
SoundReaderLease::~SoundReaderLease()
|
||||
{
|
||||
this->release();
|
||||
}
|
||||
|
||||
SoundReaderLease::SoundReaderLease(SoundReaderLease &&other) noexcept
|
||||
: state_(std::move(other.state_)), reader_(std::move(other.reader_))
|
||||
{
|
||||
}
|
||||
|
||||
SoundReaderLease &SoundReaderLease::operator=(SoundReaderLease &&other) noexcept
|
||||
{
|
||||
if (this != &other) {
|
||||
this->release();
|
||||
this->state_ = std::move(other.state_);
|
||||
this->reader_ = std::move(other.reader_);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
SoundReaderLease::operator bool() const
|
||||
{
|
||||
return bool(this->reader_);
|
||||
}
|
||||
|
||||
aud::IReader *SoundReaderLease::operator->() const
|
||||
{
|
||||
return this->reader_.get();
|
||||
}
|
||||
|
||||
void SoundReaderLease::discard() noexcept
|
||||
{
|
||||
this->reader_.reset();
|
||||
this->state_.reset();
|
||||
}
|
||||
|
||||
void SoundReaderLease::release() noexcept
|
||||
{
|
||||
if (this->state_) {
|
||||
this->state_->release(std::move(this->reader_));
|
||||
this->state_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
SoundReaderCache::SoundReaderCache(std::shared_ptr<aud::ISound> sound)
|
||||
: state_(std::make_shared<SoundReaderCacheState>(std::move(sound)))
|
||||
{
|
||||
}
|
||||
|
||||
SoundReaderLease SoundReaderCache::acquire()
|
||||
{
|
||||
std::shared_ptr<SoundReaderCacheState> state = this->state_;
|
||||
std::shared_ptr<aud::IReader> reader;
|
||||
{
|
||||
std::lock_guard lock{state->mutex};
|
||||
if (!state->idle_readers.is_empty()) {
|
||||
reader = state->idle_readers.pop_last();
|
||||
}
|
||||
}
|
||||
|
||||
if (!reader && state->sound) {
|
||||
reader = state->sound->createReader();
|
||||
}
|
||||
if (!reader) {
|
||||
return {};
|
||||
}
|
||||
return SoundReaderLease(std::move(state), std::move(reader));
|
||||
}
|
||||
|
||||
} // namespace blender::bke
|
||||
|
||||
#endif
|
||||
76
source/blender/blenkernel/intern/sound_reader_cache.hh
Normal file
76
source/blender/blenkernel/intern/sound_reader_cache.hh
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#if defined(WITH_AUDASPACE)
|
||||
|
||||
# include <memory>
|
||||
|
||||
# include "BLI_utility_mixins.hh"
|
||||
|
||||
namespace aud {
|
||||
class IReader;
|
||||
class ISound;
|
||||
} // namespace aud
|
||||
|
||||
namespace blender::bke {
|
||||
|
||||
struct SoundReaderCacheState;
|
||||
|
||||
class SoundReaderCache;
|
||||
|
||||
/** Exclusive access to a sound reader. The reader returns to its cache on destruction. */
|
||||
class SoundReaderLease : NonCopyable {
|
||||
private:
|
||||
/**
|
||||
* Keep the shared cache state alive so a lease can return its reader after the outer cache has
|
||||
* been replaced or destroyed.
|
||||
*/
|
||||
std::shared_ptr<SoundReaderCacheState> state_;
|
||||
std::shared_ptr<aud::IReader> reader_;
|
||||
|
||||
SoundReaderLease(std::shared_ptr<SoundReaderCacheState> state,
|
||||
std::shared_ptr<aud::IReader> reader);
|
||||
void release() noexcept;
|
||||
|
||||
friend SoundReaderCache;
|
||||
|
||||
public:
|
||||
SoundReaderLease() = default;
|
||||
~SoundReaderLease();
|
||||
|
||||
SoundReaderLease(SoundReaderLease &&other) noexcept;
|
||||
SoundReaderLease &operator=(SoundReaderLease &&other) noexcept;
|
||||
|
||||
explicit operator bool() const;
|
||||
aud::IReader *operator->() const;
|
||||
|
||||
/**
|
||||
* Destroy the reader instead of returning it to the cache.
|
||||
*
|
||||
* This is used when a failed reader operation may have left its state invalid.
|
||||
*/
|
||||
void discard() noexcept;
|
||||
};
|
||||
|
||||
/**
|
||||
* Runtime-only reusable reader state associated with a #bSound runtime.
|
||||
*
|
||||
* Active readers belong to leases, not the idle-reader vector.
|
||||
*/
|
||||
class SoundReaderCache : NonCopyable {
|
||||
private:
|
||||
std::shared_ptr<SoundReaderCacheState> state_;
|
||||
|
||||
public:
|
||||
explicit SoundReaderCache(std::shared_ptr<aud::ISound> sound);
|
||||
|
||||
/** Acquire a reader. Returns an empty lease when reader creation returns null. */
|
||||
SoundReaderLease acquire();
|
||||
};
|
||||
|
||||
} // namespace blender::bke
|
||||
|
||||
#endif
|
||||
147
source/blender/blenkernel/intern/sound_reader_cache_test.cc
Normal file
147
source/blender/blenkernel/intern/sound_reader_cache_test.cc
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "sound_reader_cache.hh"
|
||||
|
||||
#include "testing/testing.h"
|
||||
|
||||
#if defined(WITH_AUDASPACE)
|
||||
|
||||
# include <memory>
|
||||
# include <utility>
|
||||
|
||||
# include <IReader.h>
|
||||
# include <ISound.h>
|
||||
# include <util/Buffer.h>
|
||||
# include <util/StreamBuffer.h>
|
||||
|
||||
namespace blender::bke::tests {
|
||||
|
||||
struct ReaderControl {
|
||||
int readers_created = 0;
|
||||
int live_readers = 0;
|
||||
};
|
||||
|
||||
class CountingSound : public aud::ISound {
|
||||
private:
|
||||
std::shared_ptr<aud::ISound> sound;
|
||||
|
||||
public:
|
||||
std::shared_ptr<ReaderControl> control = std::make_shared<ReaderControl>();
|
||||
|
||||
explicit CountingSound(std::shared_ptr<aud::ISound> sound) : sound(std::move(sound)) {}
|
||||
|
||||
std::shared_ptr<aud::IReader> createReader() override
|
||||
{
|
||||
this->control->readers_created++;
|
||||
std::shared_ptr<aud::IReader> reader = this->sound->createReader();
|
||||
this->control->live_readers++;
|
||||
aud::IReader *reader_pointer = reader.get();
|
||||
return std::shared_ptr<aud::IReader>(
|
||||
reader_pointer,
|
||||
[reader = std::move(reader), control = this->control](aud::IReader *) mutable {
|
||||
reader.reset();
|
||||
control->live_readers--;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
static std::shared_ptr<CountingSound> create_test_sound()
|
||||
{
|
||||
auto buffer = std::make_shared<aud::Buffer>(sizeof(aud::sample_t));
|
||||
auto stream = std::make_shared<aud::StreamBuffer>(
|
||||
buffer, aud::Specs{aud::RATE_48000, aud::CHANNELS_MONO});
|
||||
return std::make_shared<CountingSound>(std::move(stream));
|
||||
}
|
||||
|
||||
TEST(sound_reader_cache, ReusesReaderAndLimitsIdleReaders)
|
||||
{
|
||||
const std::shared_ptr<CountingSound> sound = create_test_sound();
|
||||
SoundReaderCache cache(sound);
|
||||
|
||||
{
|
||||
SoundReaderLease lease = cache.acquire();
|
||||
ASSERT_TRUE(lease);
|
||||
}
|
||||
{
|
||||
SoundReaderLease lease = cache.acquire();
|
||||
ASSERT_TRUE(lease);
|
||||
}
|
||||
EXPECT_EQ(sound->control->readers_created, 1);
|
||||
|
||||
SoundReaderLease first = cache.acquire();
|
||||
SoundReaderLease second = cache.acquire();
|
||||
SoundReaderLease third = cache.acquire();
|
||||
ASSERT_TRUE(first);
|
||||
ASSERT_TRUE(second);
|
||||
ASSERT_TRUE(third);
|
||||
EXPECT_EQ(sound->control->readers_created, 3);
|
||||
|
||||
first = {};
|
||||
second = {};
|
||||
third = {};
|
||||
EXPECT_EQ(sound->control->live_readers, 2);
|
||||
|
||||
SoundReaderLease reused_first = cache.acquire();
|
||||
SoundReaderLease reused_second = cache.acquire();
|
||||
ASSERT_TRUE(reused_first);
|
||||
ASSERT_TRUE(reused_second);
|
||||
EXPECT_EQ(sound->control->readers_created, 3);
|
||||
}
|
||||
|
||||
TEST(sound_reader_cache, DiscardDestroysReader)
|
||||
{
|
||||
const std::shared_ptr<CountingSound> sound = create_test_sound();
|
||||
SoundReaderCache cache(sound);
|
||||
{
|
||||
SoundReaderLease lease = cache.acquire();
|
||||
ASSERT_TRUE(lease);
|
||||
lease.discard();
|
||||
EXPECT_FALSE(lease);
|
||||
}
|
||||
EXPECT_EQ(sound->control->live_readers, 0);
|
||||
|
||||
SoundReaderLease replacement = cache.acquire();
|
||||
ASSERT_TRUE(replacement);
|
||||
EXPECT_EQ(sound->control->readers_created, 2);
|
||||
}
|
||||
|
||||
TEST(sound_reader_cache, MoveOperationsReturnEachReaderOnce)
|
||||
{
|
||||
const std::shared_ptr<CountingSound> sound = create_test_sound();
|
||||
SoundReaderCache cache(sound);
|
||||
SoundReaderLease first = cache.acquire();
|
||||
ASSERT_TRUE(first);
|
||||
|
||||
SoundReaderLease moved(std::move(first));
|
||||
EXPECT_FALSE(first);
|
||||
SoundReaderLease second = cache.acquire();
|
||||
ASSERT_TRUE(second);
|
||||
moved = std::move(second);
|
||||
EXPECT_FALSE(second);
|
||||
|
||||
moved = {};
|
||||
SoundReaderLease reused_first = cache.acquire();
|
||||
SoundReaderLease reused_second = cache.acquire();
|
||||
ASSERT_TRUE(reused_first);
|
||||
ASSERT_TRUE(reused_second);
|
||||
EXPECT_EQ(sound->control->readers_created, 2);
|
||||
}
|
||||
|
||||
TEST(sound_reader_cache, LeaseOutlivesCache)
|
||||
{
|
||||
const std::shared_ptr<CountingSound> sound = create_test_sound();
|
||||
auto cache = std::make_shared<SoundReaderCache>(sound);
|
||||
SoundReaderLease lease = cache->acquire();
|
||||
ASSERT_TRUE(lease);
|
||||
|
||||
cache.reset();
|
||||
EXPECT_EQ(sound->control->live_readers, 1);
|
||||
lease = {};
|
||||
EXPECT_EQ(sound->control->live_readers, 0);
|
||||
}
|
||||
|
||||
} // namespace blender::bke::tests
|
||||
|
||||
#endif
|
||||
|
|
@ -178,8 +178,8 @@ class SampleSoundFunction : public mf::MultiFunction {
|
|||
key.window_function = window_function_;
|
||||
key.fft_size = fft_size_;
|
||||
key.channel = *all_channels_value ? std::nullopt : channel_value;
|
||||
const bke::bSoundFrequencySampler *sampler = bke::bSoundFrequencySampler::get_cached(sound_,
|
||||
key);
|
||||
const std::shared_ptr<const bke::bSoundFrequencySampler> sampler =
|
||||
bke::bSoundFrequencySampler::get_cached(sound_, key);
|
||||
if (!sampler) {
|
||||
index_mask::masked_fill(amplitudes, 0.0f, mask);
|
||||
return;
|
||||
|
|
@ -227,8 +227,8 @@ class SampleSoundFunction : public mf::MultiFunction {
|
|||
key.window_function = window_function_;
|
||||
key.fft_size = fft_size_;
|
||||
key.channel = channel;
|
||||
const bke::bSoundFrequencySampler *sampler = bke::bSoundFrequencySampler::get_cached(sound_,
|
||||
key);
|
||||
const std::shared_ptr<const bke::bSoundFrequencySampler> sampler =
|
||||
bke::bSoundFrequencySampler::get_cached(sound_, key);
|
||||
if (!sampler) {
|
||||
amplitudes.fill_indices(indices, 0.0f);
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ab0828ab72d48aa15bd9290b98bfc07dd243713b6fbf7887f51af119b0879988
|
||||
size 103687
|
||||
|
|
@ -1281,6 +1281,10 @@ if(TEST_SRC_DIR_EXISTS)
|
|||
vector
|
||||
)
|
||||
|
||||
if(WITH_AUDASPACE AND WITH_FFTW3)
|
||||
list(APPEND geo_node_tests sound)
|
||||
endif()
|
||||
|
||||
if(WITH_GMP)
|
||||
list(APPEND geo_node_tests mesh/boolean)
|
||||
endif()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue