2493 lines
77 KiB
C++
2493 lines
77 KiB
C++
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
|
|
* SPDX-FileCopyrightText: 2025 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup bke
|
|
*/
|
|
|
|
#include <chrono>
|
|
#include <condition_variable>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <mutex>
|
|
#include <numeric>
|
|
#include <optional>
|
|
#include <thread>
|
|
|
|
#ifdef WITH_FFTW3
|
|
# include <fftw3.h>
|
|
#endif
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "BLI_build_config.h"
|
|
#include "BLI_enum_flags.hh"
|
|
#include "BLI_listbase.h"
|
|
#include "BLI_math_base.h"
|
|
#include "BLI_math_rotation.h"
|
|
#include "BLI_path_utils.hh"
|
|
#include "BLI_string.h"
|
|
#include "BLI_threads.h"
|
|
|
|
#include "BLT_translation.hh"
|
|
|
|
/* Allow using deprecated functionality for .blend file I/O. */
|
|
#define DNA_DEPRECATED_ALLOW
|
|
|
|
#include "DNA_anim_types.h"
|
|
#include "DNA_object_types.h"
|
|
#include "DNA_packedFile_types.h"
|
|
#include "DNA_scene_types.h"
|
|
#include "DNA_screen_types.h"
|
|
#include "DNA_sequence_types.h"
|
|
#include "DNA_sound_types.h"
|
|
#include "DNA_speaker_types.h"
|
|
#include "DNA_userdef_types.h"
|
|
|
|
#ifdef WITH_AUDASPACE
|
|
# include "BLI_set.hh"
|
|
|
|
# include <Exception.h>
|
|
# include <IReader.h>
|
|
# include <devices/DeviceManager.h>
|
|
# include <devices/IDeviceFactory.h>
|
|
# include <devices/IHandle.h>
|
|
# include <devices/NULLDevice.h>
|
|
# include <devices/ReadDevice.h>
|
|
# include <file/File.h>
|
|
# include <file/FileManager.h>
|
|
# include <file/FileWriter.h>
|
|
# include <fx/Accumulator.h>
|
|
# include <fx/AnimateableTimeStretchPitchScale.h>
|
|
# include <fx/Envelope.h>
|
|
# include <fx/Highpass.h>
|
|
# include <fx/Limiter.h>
|
|
# include <fx/Lowpass.h>
|
|
# include <fx/Sum.h>
|
|
# include <fx/Threshold.h>
|
|
# include <generator/Silence.h>
|
|
# include <plugin/PluginManager.h>
|
|
# include <respec/ChannelMapper.h>
|
|
# include <respec/LinearResample.h>
|
|
# include <sequence/Sequence.h>
|
|
# include <sequence/SequenceEntry.h>
|
|
# include <util/StreamBuffer.h>
|
|
|
|
# include <fmt/format.h>
|
|
|
|
#endif
|
|
|
|
#include "BKE_bpath.hh"
|
|
#include "BKE_global.hh"
|
|
#include "BKE_idtype.hh"
|
|
#include "BKE_lib_id.hh"
|
|
#include "BKE_lib_query.hh"
|
|
#include "BKE_library.hh"
|
|
#include "BKE_main.hh"
|
|
#include "BKE_packedFile.hh"
|
|
#include "BKE_scene_runtime.hh"
|
|
#include "BKE_sound.hh"
|
|
#include "BKE_sound_sample.hh"
|
|
|
|
#include "DEG_depsgraph.hh"
|
|
#include "DEG_depsgraph_query.hh"
|
|
|
|
#include "BLO_read_write.hh"
|
|
|
|
#include "SEQ_sequencer.hh"
|
|
#include "SEQ_sound.hh"
|
|
|
|
#include "BLI_concurrent_map.hh"
|
|
|
|
#include "CLG_log.h"
|
|
|
|
namespace blender {
|
|
|
|
namespace bke {
|
|
struct SceneAudioRuntime;
|
|
|
|
enum class SoundTags {
|
|
None = 0,
|
|
/* Do not free/reset waveform on sound load, only used by undo code. */
|
|
WaveformNoReload = 1 << 0,
|
|
WaveformLoading = 1 << 1,
|
|
};
|
|
ENUM_OPERATORS(SoundTags);
|
|
|
|
using bSoundFrequencySamplerMap =
|
|
ConcurrentMap<bSoundFrequencySampler::Key, std::shared_ptr<bSoundFrequencySampler>>;
|
|
|
|
struct SoundRuntime {
|
|
AUD_Sound handle;
|
|
AUD_Sound cache;
|
|
/* The audaspace handle that should actually be played back.
|
|
* Should be cache if cache != NULL; otherwise its handle. */
|
|
AUD_Sound playback_handle;
|
|
/* Spin-lock for asynchronous loading of sounds. */
|
|
SpinLock spinlock;
|
|
|
|
/* Note: not by-value since #sound_foreach_cache can only
|
|
* save/restore a pointer. */
|
|
Vector<float> *waveform = nullptr;
|
|
SoundTags tags = SoundTags::None;
|
|
|
|
/** Caches frequency samplers for this sound. */
|
|
bSoundFrequencySamplerMap samplers;
|
|
};
|
|
|
|
} // namespace bke
|
|
|
|
static void sound_free_audio(bSound *sound);
|
|
|
|
static void sound_init_runtime(bSound *sound)
|
|
{
|
|
sound->runtime = MEM_new<bke::SoundRuntime>(__func__);
|
|
BLI_spin_init(&sound->runtime->spinlock);
|
|
}
|
|
|
|
static void sound_free_waveform(bSound *sound)
|
|
{
|
|
bke::SoundRuntime *runtime = sound->runtime;
|
|
if (!flag_is_set(runtime->tags, bke::SoundTags::WaveformNoReload)) {
|
|
MEM_SAFE_DELETE(runtime->waveform);
|
|
}
|
|
/* This tag is only valid once. */
|
|
runtime->tags &= ~bke::SoundTags::WaveformNoReload;
|
|
}
|
|
|
|
static void sound_copy_data(Main * /*bmain*/,
|
|
std::optional<Library *> /*owner_library*/,
|
|
ID *id_dst,
|
|
const ID *id_src,
|
|
const int /*flag*/)
|
|
{
|
|
bSound *sound_dst = id_cast<bSound *>(id_dst);
|
|
const bSound *sound_src = id_cast<const bSound *>(id_src);
|
|
|
|
/* Just to be sure, should not have any value actually after reading time. */
|
|
sound_dst->newpackedfile = nullptr;
|
|
|
|
if (sound_src->packedfile != nullptr) {
|
|
sound_dst->packedfile = BKE_packedfile_duplicate(sound_src->packedfile);
|
|
}
|
|
|
|
sound_init_runtime(sound_dst);
|
|
}
|
|
|
|
static void sound_free_data(ID *id)
|
|
{
|
|
bSound *sound = id_cast<bSound *>(id);
|
|
|
|
if (sound->packedfile) {
|
|
BKE_packedfile_free(sound->packedfile);
|
|
sound->packedfile = nullptr;
|
|
}
|
|
|
|
if (sound->runtime) {
|
|
sound_free_audio(sound);
|
|
sound_free_waveform(sound);
|
|
BLI_spin_end(&sound->runtime->spinlock);
|
|
MEM_delete(sound->runtime);
|
|
}
|
|
}
|
|
|
|
static void sound_foreach_cache(ID *id,
|
|
IDTypeForeachCacheFunctionCallback function_callback,
|
|
void *user_data)
|
|
{
|
|
bSound *sound = id_cast<bSound *>(id);
|
|
IDCacheKey key = {id->session_uid, 1};
|
|
function_callback(id, &key, reinterpret_cast<void **>(&sound->runtime->waveform), 0, user_data);
|
|
}
|
|
|
|
static void sound_foreach_path(ID *id, BPathForeachPathData *bpath_data)
|
|
{
|
|
bSound *sound = id_cast<bSound *>(id);
|
|
if (sound->packedfile != nullptr && (bpath_data->flag & BKE_BPATH_FOREACH_PATH_SKIP_PACKED) != 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
/* FIXME: This does not check for empty path... */
|
|
BKE_bpath_foreach_path_fixed_process(bpath_data, sound->filepath, sizeof(sound->filepath));
|
|
}
|
|
|
|
static void sound_blend_write(BlendWriter *writer, ID *id, const void *id_address)
|
|
{
|
|
bSound *sound = id_cast<bSound *>(id);
|
|
const bool is_undo = BLO_write_is_undo(writer);
|
|
|
|
/* Clean up, important in undo case to reduce false detection of changed datablocks. */
|
|
sound->runtime = nullptr;
|
|
|
|
/* Do not store packed files in case this is a library override ID. */
|
|
if (ID_IS_OVERRIDE_LIBRARY(sound) && !is_undo) {
|
|
sound->packedfile = nullptr;
|
|
}
|
|
|
|
/* write LibData */
|
|
writer->write_id_struct(id_address, sound);
|
|
BKE_id_blend_write(writer, &sound->id);
|
|
|
|
BKE_packedfile_blend_write(writer, sound->packedfile);
|
|
}
|
|
|
|
static void sound_blend_read_data(BlendDataReader *reader, ID *id)
|
|
{
|
|
bSound *sound = id_cast<bSound *>(id);
|
|
sound_init_runtime(sound);
|
|
if (BLO_read_data_is_undo(reader)) {
|
|
sound->runtime->tags |= bke::SoundTags::WaveformNoReload;
|
|
}
|
|
|
|
BKE_packedfile_blend_read(reader, &sound->packedfile, sound->filepath);
|
|
BKE_packedfile_blend_read(reader, &sound->newpackedfile, sound->filepath);
|
|
}
|
|
|
|
IDTypeInfo IDType_ID_SO = {
|
|
.id_code = bSound::id_type,
|
|
.id_filter = FILTER_ID_SO,
|
|
.dependencies_id_types = 0,
|
|
.main_listbase_index = INDEX_ID_SO,
|
|
.struct_size = sizeof(bSound),
|
|
.name = "Sound",
|
|
.name_plural = N_("sounds"),
|
|
.translation_context = BLT_I18NCONTEXT_ID_SOUND,
|
|
.flags = IDTYPE_FLAGS_NO_ANIMDATA | IDTYPE_FLAGS_APPEND_IS_REUSABLE,
|
|
.asset_type_info = nullptr,
|
|
|
|
/* A fuzzy case, think NULLified content is OK here... */
|
|
.init_data = nullptr,
|
|
.copy_data = sound_copy_data,
|
|
.free_data = sound_free_data,
|
|
.make_local = nullptr,
|
|
.foreach_id = nullptr,
|
|
.foreach_cache = sound_foreach_cache,
|
|
.foreach_path = sound_foreach_path,
|
|
.foreach_working_space_color = nullptr,
|
|
.owner_pointer_get = nullptr,
|
|
|
|
.blend_write = sound_blend_write,
|
|
.blend_read_data = sound_blend_read_data,
|
|
.blend_read_after_liblink = nullptr,
|
|
|
|
.blend_read_undo_preserve = nullptr,
|
|
|
|
.lib_override_apply_post = nullptr,
|
|
};
|
|
|
|
#ifdef WITH_AUDASPACE
|
|
/* evil globals ;-) */
|
|
static char **audio_device_names = nullptr;
|
|
#endif
|
|
|
|
BLI_INLINE void sound_verify_evaluated_id(const ID *id)
|
|
{
|
|
UNUSED_VARS_NDEBUG(id);
|
|
/* This is a bit tricky and not quite reliable, but good enough check.
|
|
*
|
|
* We don't want audio system handles to be allocated on an original data-blocks, and only want
|
|
* them to be allocated on a data-blocks which are result of dependency graph evaluation.
|
|
*
|
|
* Data-blocks which are covered by a copy-on-evaluation system of dependency graph will have
|
|
* ID_TAG_COPIED_ON_EVAL tag set on them. But if some of data-blocks during its evaluation
|
|
* decides to re-allocate its nested one (for example, object evaluation could re-allocate mesh
|
|
* when evaluating modifier stack). Such data-blocks will have
|
|
* ID_TAG_COPIED_ON_EVAL_FINAL_RESULT tag set on them.
|
|
*
|
|
* Additionally, we also allow data-blocks outside of main database. Those can not be "original"
|
|
* and could be used as a temporary evaluated result during operations like baking.
|
|
*
|
|
* NOTE: We consider ID evaluated if ANY of those flags is set. We do NOT require ALL of them.
|
|
*/
|
|
BLI_assert(id->tag &
|
|
(ID_TAG_COPIED_ON_EVAL | ID_TAG_COPIED_ON_EVAL_FINAL_RESULT | ID_TAG_NO_MAIN));
|
|
}
|
|
|
|
bSound *BKE_sound_new_file(Main *bmain, const char *filepath, short stream_index)
|
|
{
|
|
bSound *sound;
|
|
const char *blendfile_path = BKE_main_blendfile_path(bmain);
|
|
char filepath_abs[FILE_MAX];
|
|
|
|
STRNCPY(filepath_abs, filepath);
|
|
BLI_path_abs(filepath_abs, blendfile_path);
|
|
|
|
sound = static_cast<bSound *>(BKE_libblock_alloc(bmain, ID_SO, BLI_path_basename(filepath), 0));
|
|
STRNCPY(sound->filepath, filepath);
|
|
sound_init_runtime(sound);
|
|
sound->stream_index = stream_index;
|
|
|
|
/* Extract sound specs for bSound */
|
|
SoundInfo info;
|
|
bool success = BKE_sound_info_get(bmain, sound, &info);
|
|
if (success) {
|
|
sound->samplerate = info.specs.samplerate;
|
|
sound->audio_channels = info.specs.channels;
|
|
}
|
|
|
|
return sound;
|
|
}
|
|
|
|
static bSound *sound_new_file_exists_ex(Main *bmain, const char *filepath, short stream_index)
|
|
{
|
|
bSound *sound;
|
|
char filepath_abs[FILE_MAX], filepath_test[FILE_MAX];
|
|
|
|
STRNCPY(filepath_abs, filepath);
|
|
BLI_path_abs(filepath_abs, BKE_main_blendfile_path(bmain));
|
|
|
|
/* Search for an existing sound matching both filepath and stream index. */
|
|
for (sound = static_cast<bSound *>(bmain->sounds.first); sound;
|
|
sound = static_cast<bSound *>(sound->id.next))
|
|
{
|
|
if (sound->stream_index != stream_index) {
|
|
continue;
|
|
}
|
|
STRNCPY(filepath_test, sound->filepath);
|
|
BLI_path_abs(filepath_test, ID_BLEND_PATH(bmain, &sound->id));
|
|
|
|
if (BLI_path_cmp(filepath_test, filepath_abs) == 0) {
|
|
id_us_plus(&sound->id); /* officially should not, it doesn't link here! */
|
|
return sound;
|
|
}
|
|
}
|
|
|
|
return BKE_sound_new_file(bmain, filepath, stream_index);
|
|
}
|
|
|
|
bSound *BKE_sound_new_file_exists(Main *bmain, const char *filepath, short stream_index)
|
|
{
|
|
return sound_new_file_exists_ex(bmain, filepath, stream_index);
|
|
}
|
|
|
|
static void sound_free_audio(bSound *sound)
|
|
{
|
|
#ifdef WITH_AUDASPACE
|
|
bke::SoundRuntime *runtime = sound->runtime;
|
|
runtime->handle.reset();
|
|
runtime->playback_handle.reset();
|
|
runtime->cache.reset();
|
|
#else
|
|
UNUSED_VARS(sound);
|
|
#endif /* WITH_AUDASPACE */
|
|
}
|
|
|
|
#ifdef WITH_AUDASPACE
|
|
static CLG_LogRef LOG = {"sound"};
|
|
|
|
namespace {
|
|
|
|
struct GlobalState {
|
|
const char *force_device = nullptr;
|
|
|
|
/* Parameters of the opened device */
|
|
const char *device_name = nullptr;
|
|
aud::DeviceSpecs initialized_specs;
|
|
|
|
/* Device handle and its synchronization mutex. */
|
|
AUD_Device sound_device;
|
|
int buffer_size = 0;
|
|
std::mutex sound_device_mutex;
|
|
|
|
bool need_exit = false;
|
|
bool use_delayed_close = true;
|
|
std::thread delayed_close_thread;
|
|
std::condition_variable delayed_close_cv;
|
|
|
|
int num_device_users = 0;
|
|
std::chrono::time_point<std::chrono::steady_clock> last_user_disconnect_time_point;
|
|
|
|
~GlobalState()
|
|
{
|
|
/* Ensure that we don't end up in a deadlock if the global state is being cleaned up
|
|
* before BKE_sound_exit_once has been called. (For example if someone called exit()
|
|
* to quickly close the program without cleaning up)
|
|
*
|
|
* If we don't do this, we could end up in a state where this destructor is waiting for
|
|
* other threads to let go of delayed_close_cv forever. See #146640.
|
|
*/
|
|
exit_threads();
|
|
}
|
|
|
|
void exit_threads()
|
|
{
|
|
{
|
|
std::unique_lock lock(sound_device_mutex);
|
|
need_exit = true;
|
|
}
|
|
|
|
if (delayed_close_thread.joinable()) {
|
|
delayed_close_cv.notify_all();
|
|
delayed_close_thread.join();
|
|
}
|
|
}
|
|
};
|
|
|
|
GlobalState g_state;
|
|
} // namespace
|
|
|
|
static void sound_device_close_no_lock()
|
|
{
|
|
if (g_state.sound_device) {
|
|
CLOG_DEBUG(&LOG, "Closing audio device");
|
|
bke::sound_device_exit();
|
|
g_state.sound_device = nullptr;
|
|
}
|
|
}
|
|
|
|
static void sound_device_open_no_lock(const aud::DeviceSpecs &requested_specs)
|
|
{
|
|
BLI_assert(!g_state.sound_device);
|
|
|
|
CLOG_DEBUG(&LOG, "Opening audio device name:%s", g_state.device_name);
|
|
|
|
g_state.sound_device = bke::sound_device_init(
|
|
g_state.device_name, requested_specs, g_state.buffer_size, "Blender");
|
|
if (!g_state.sound_device) {
|
|
g_state.sound_device = bke::sound_device_init(
|
|
"None", requested_specs, g_state.buffer_size, "Blender");
|
|
}
|
|
|
|
g_state.initialized_specs = g_state.sound_device->getSpecs();
|
|
}
|
|
|
|
static void sound_device_use_begin()
|
|
{
|
|
++g_state.num_device_users;
|
|
|
|
if (g_state.sound_device) {
|
|
return;
|
|
}
|
|
|
|
sound_device_open_no_lock(g_state.initialized_specs);
|
|
}
|
|
|
|
static void sound_device_use_end_after(const std::chrono::milliseconds after_ms)
|
|
{
|
|
BLI_assert(g_state.num_device_users > 0);
|
|
if (g_state.num_device_users == 0) {
|
|
return;
|
|
}
|
|
|
|
--g_state.num_device_users;
|
|
if (g_state.num_device_users == 0) {
|
|
g_state.last_user_disconnect_time_point = std::chrono::steady_clock::now() + after_ms;
|
|
g_state.delayed_close_cv.notify_one();
|
|
}
|
|
}
|
|
|
|
static void sound_device_use_end()
|
|
{
|
|
sound_device_use_end_after(std::chrono::milliseconds(0));
|
|
}
|
|
|
|
/* Return true if we need a thread which checks for device usage and closes it when it is inactive.
|
|
* Only runtime-invariant checks are done here, such as possible platform-specific requirements.
|
|
*/
|
|
static bool sound_use_close_thread()
|
|
{
|
|
/* No point starting a thread if sound is disabled and we're running headless. */
|
|
if (g_state.force_device && STREQ(g_state.force_device, "None")) {
|
|
# if defined(WITH_PYTHON_MODULE) || defined(WITH_HEADLESS)
|
|
return false;
|
|
# endif
|
|
if (G.background) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
# if OS_MAC
|
|
/* Closing audio device on macOS prior to 15.2 could lead to interference with other software.
|
|
* See #121911 for details. */
|
|
if (__builtin_available(macOS 15.2, *)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
# else
|
|
return true;
|
|
# endif
|
|
}
|
|
|
|
static void delayed_close_thread_run()
|
|
{
|
|
constexpr std::chrono::milliseconds device_close_delay{30000};
|
|
|
|
std::unique_lock lock(g_state.sound_device_mutex);
|
|
|
|
while (!g_state.need_exit) {
|
|
if (!g_state.use_delayed_close) {
|
|
CLOG_DEBUG(&LOG, "Delayed device close is disabled");
|
|
/* Don't do anything here as delayed close is disabled.
|
|
* Wait so that we don't spin around in the while loop. */
|
|
g_state.delayed_close_cv.wait(lock);
|
|
continue;
|
|
}
|
|
|
|
if (g_state.num_device_users == 0) {
|
|
if (g_state.sound_device == nullptr) {
|
|
/* There are no device users, wait until there is device to be waited for to close. */
|
|
g_state.delayed_close_cv.wait(lock);
|
|
}
|
|
else {
|
|
g_state.delayed_close_cv.wait_until(
|
|
lock, g_state.last_user_disconnect_time_point + device_close_delay);
|
|
}
|
|
}
|
|
else {
|
|
/* If there are active device users wait indefinitely, until the system is requested to be
|
|
* closed or the user stops using device.
|
|
* It is not really guaranteed that the CV is notified for every user that stops using
|
|
* device, only the last one is guaranteed to notify the CV. */
|
|
g_state.delayed_close_cv.wait(lock);
|
|
}
|
|
|
|
if (g_state.need_exit) {
|
|
CLOG_DEBUG(&LOG, "System exit requested");
|
|
break;
|
|
}
|
|
|
|
if (!g_state.use_delayed_close) {
|
|
/* Take into account corner case where you switch from a delayed close device while Blender
|
|
* is running and a delayed close has already been queued up. */
|
|
continue;
|
|
}
|
|
|
|
if (!g_state.sound_device) {
|
|
CLOG_DEBUG(&LOG, "Device is not open, nothing to do");
|
|
continue;
|
|
}
|
|
|
|
CLOG_DEBUG(&LOG, "Checking last device usage and timestamp");
|
|
|
|
if (g_state.num_device_users) {
|
|
CLOG_DEBUG(&LOG, "Device is used by %d user(s)", g_state.num_device_users);
|
|
continue;
|
|
}
|
|
|
|
const std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
|
|
if ((now - g_state.last_user_disconnect_time_point) >= device_close_delay) {
|
|
sound_device_close_no_lock();
|
|
}
|
|
}
|
|
|
|
CLOG_DEBUG(&LOG, "Delayed device close thread finished");
|
|
}
|
|
|
|
static SoundJackSyncCallback sound_jack_sync_callback = nullptr;
|
|
|
|
static void sound_sync_callback(void *data, int mode, float time)
|
|
{
|
|
if (sound_jack_sync_callback == nullptr) {
|
|
return;
|
|
}
|
|
Main *bmain = (Main *)data;
|
|
sound_jack_sync_callback(bmain, mode, time);
|
|
}
|
|
|
|
void BKE_sound_force_device(const char *device)
|
|
{
|
|
g_state.force_device = device;
|
|
}
|
|
|
|
void BKE_sound_init_once()
|
|
{
|
|
bke::sound_system_initialize();
|
|
if (sound_use_close_thread()) {
|
|
CLOG_DEBUG(&LOG, "Using delayed device close thread");
|
|
g_state.delayed_close_thread = std::thread(delayed_close_thread_run);
|
|
}
|
|
}
|
|
|
|
void BKE_sound_exit_once()
|
|
{
|
|
g_state.exit_threads();
|
|
|
|
std::lock_guard lock(g_state.sound_device_mutex);
|
|
sound_device_close_no_lock();
|
|
|
|
if (audio_device_names != nullptr) {
|
|
int i;
|
|
for (i = 0; audio_device_names[i]; i++) {
|
|
free(audio_device_names[i]);
|
|
}
|
|
free(audio_device_names);
|
|
audio_device_names = nullptr;
|
|
}
|
|
}
|
|
|
|
void BKE_sound_init(Main *bmain)
|
|
{
|
|
std::lock_guard lock(g_state.sound_device_mutex);
|
|
/* Make sure no instance of the sound system is running, otherwise we get leaks. */
|
|
sound_device_close_no_lock();
|
|
|
|
aud::DeviceSpecs requested_specs;
|
|
requested_specs.channels = aud::Channels(U.audiochannels);
|
|
requested_specs.format = aud::SampleFormat(U.audioformat);
|
|
requested_specs.rate = U.audiorate;
|
|
|
|
if (g_state.force_device == nullptr) {
|
|
char **names = BKE_sound_get_device_names();
|
|
g_state.device_name = names[0];
|
|
|
|
/* make sure device is within the bounds of the array */
|
|
for (int i = 0; names[i]; i++) {
|
|
if (i == U.audiodevice) {
|
|
g_state.device_name = names[i];
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
g_state.device_name = g_state.force_device;
|
|
}
|
|
|
|
g_state.buffer_size = U.mixbufsize < 128 ? 1024 : U.mixbufsize;
|
|
|
|
if (requested_specs.rate < double(aud::RATE_8000)) {
|
|
requested_specs.rate = aud::RATE_48000;
|
|
}
|
|
|
|
if (requested_specs.format <= aud::FORMAT_INVALID) {
|
|
requested_specs.format = aud::FORMAT_S16;
|
|
}
|
|
|
|
if (requested_specs.channels <= aud::CHANNELS_INVALID) {
|
|
requested_specs.channels = aud::CHANNELS_STEREO;
|
|
}
|
|
|
|
/* Make sure that we have our initalized_specs */
|
|
sound_device_open_no_lock(requested_specs);
|
|
if (STR_ELEM(g_state.device_name, "JACK", "PulseAudio", "PipeWire")) {
|
|
/* JACK:
|
|
* Do not close the device when using JACK. If we close it, we will not be able to
|
|
* respond to JACK audio bus commands.
|
|
*
|
|
* PulseAudio, PipeWire:
|
|
* These APIs are built around the idea that the program using them keeps the device open.
|
|
* Instead it uses audio streams to determine if something is playing back audio or not.
|
|
* These streams are only active when Audaspace is playing back, so we don't need to
|
|
* do anything manually.
|
|
* If we close these devices, it will become very hard and tedious for end users to
|
|
* control the volume or route audio from Blender.
|
|
*/
|
|
g_state.use_delayed_close = false;
|
|
aud::DeviceManager::getDevice()->setSyncCallback(sound_sync_callback, bmain);
|
|
}
|
|
else {
|
|
g_state.use_delayed_close = true;
|
|
sound_device_close_no_lock();
|
|
}
|
|
}
|
|
|
|
void BKE_sound_refresh_callback_bmain(Main *bmain)
|
|
{
|
|
std::lock_guard lock(g_state.sound_device_mutex);
|
|
if (g_state.sound_device) {
|
|
aud::DeviceManager::getDevice()->setSyncCallback(sound_sync_callback, bmain);
|
|
}
|
|
}
|
|
|
|
static void sound_load_audio(Main *bmain, bSound *sound, bool free_waveform)
|
|
{
|
|
bke::SoundRuntime *runtime = sound->runtime;
|
|
runtime->cache.reset();
|
|
runtime->handle.reset();
|
|
runtime->playback_handle.reset();
|
|
if (free_waveform) {
|
|
sound_free_waveform(sound);
|
|
}
|
|
|
|
{
|
|
char fullpath[FILE_MAX];
|
|
|
|
/* load sound */
|
|
PackedFile *pf = sound->packedfile;
|
|
|
|
/* Don't modify `sound->filepath`, only change a copy. */
|
|
STRNCPY(fullpath, sound->filepath);
|
|
BLI_path_abs(fullpath, ID_BLEND_PATH(bmain, &sound->id));
|
|
|
|
/* but we need a packed file then */
|
|
if (pf) {
|
|
runtime->handle = AUD_Sound(new aud::File((uchar *)pf->data, pf->size, sound->stream_index));
|
|
}
|
|
else {
|
|
/* or else load it from disk */
|
|
runtime->handle = AUD_Sound(new aud::File(fullpath, sound->stream_index));
|
|
}
|
|
}
|
|
if (sound->flags & SOUND_FLAGS_MONO) {
|
|
aud::DeviceSpecs specs;
|
|
specs.channels = aud::CHANNELS_MONO;
|
|
specs.rate = aud::RATE_INVALID;
|
|
specs.format = aud::FORMAT_INVALID;
|
|
runtime->handle = AUD_Sound(new aud::ChannelMapper(runtime->handle, specs));
|
|
}
|
|
|
|
if (sound->flags & SOUND_FLAGS_CACHING) {
|
|
try {
|
|
runtime->cache = AUD_Sound(new aud::StreamBuffer(runtime->handle));
|
|
}
|
|
catch (aud::Exception &) {
|
|
}
|
|
}
|
|
|
|
if (runtime->cache) {
|
|
runtime->playback_handle = runtime->cache;
|
|
}
|
|
else {
|
|
runtime->playback_handle = runtime->handle;
|
|
}
|
|
}
|
|
|
|
void BKE_sound_load(Main *bmain, bSound *sound)
|
|
{
|
|
sound_verify_evaluated_id(&sound->id);
|
|
sound_load_audio(bmain, sound, true);
|
|
}
|
|
|
|
void BKE_sound_packfile_ensure(Main *bmain, bSound *sound, ReportList *reports)
|
|
{
|
|
if (sound->packedfile != nullptr) {
|
|
/* Sound is already packed and considered unmodified, do not attempt to repack it, since its
|
|
* original file may not be available anymore on the current FS.
|
|
*
|
|
* See #152638.
|
|
*/
|
|
return;
|
|
}
|
|
|
|
sound->packedfile = BKE_packedfile_new(
|
|
reports, sound->filepath, ID_BLEND_PATH(bmain, &sound->id));
|
|
}
|
|
|
|
AUD_Device BKE_sound_mixdown(const Scene *scene,
|
|
const aud::DeviceSpecs &specs,
|
|
int start,
|
|
float volume)
|
|
{
|
|
sound_verify_evaluated_id(&scene->id);
|
|
try {
|
|
std::shared_ptr<aud::ReadDevice> device(new aud::ReadDevice(specs));
|
|
device->setQuality(aud::ResampleQuality::MEDIUM);
|
|
device->setVolume(volume);
|
|
|
|
aud::Sequence *f = dynamic_cast<aud::Sequence *>(scene->runtime->audio.sound_scene.get());
|
|
f->setSpecs(specs.specs);
|
|
|
|
AUD_Handle handle = device->play(f->createQualityReader(aud::ResampleQuality::MEDIUM));
|
|
if (handle.get()) {
|
|
handle->seek(start / scene->frames_per_second());
|
|
}
|
|
|
|
return device;
|
|
}
|
|
catch (aud::Exception &) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
void BKE_sound_create_scene(Scene *scene)
|
|
{
|
|
sound_verify_evaluated_id(&scene->id);
|
|
|
|
/* should be done in version patch, but this gets called before */
|
|
if (scene->r.frs_sec_base == 0) {
|
|
scene->r.frs_sec_base = 1;
|
|
}
|
|
|
|
bke::SceneAudioRuntime &audio = scene->runtime->audio;
|
|
|
|
aud::Specs specs;
|
|
specs.channels = aud::CHANNELS_STEREO;
|
|
specs.rate = aud::RATE_48000;
|
|
audio.sound_scene = AUD_Sequence(
|
|
new aud::Sequence(specs, scene->frames_per_second(), scene->audio.flag & AUDIO_MUTE));
|
|
audio.sound_scene->setSpeedOfSound(scene->audio.speed_of_sound);
|
|
audio.sound_scene->setDopplerFactor(scene->audio.doppler_factor);
|
|
audio.sound_scene->setDistanceModel(aud::DistanceModel(scene->audio.distance_model));
|
|
audio.playback_handle = nullptr;
|
|
audio.sound_scrub_handle = nullptr;
|
|
audio.speaker_handles.clear();
|
|
}
|
|
|
|
void BKE_sound_destroy_scene(Scene *scene)
|
|
{
|
|
bke::SceneAudioRuntime &audio = scene->runtime->audio;
|
|
if (audio.playback_handle) {
|
|
audio.playback_handle->stop();
|
|
audio.playback_handle.reset();
|
|
}
|
|
if (audio.sound_scrub_handle) {
|
|
audio.sound_scrub_handle->stop();
|
|
audio.sound_scrub_handle.reset();
|
|
}
|
|
for (AUD_SequenceEntry handle : audio.speaker_handles) {
|
|
audio.sound_scene->remove(handle);
|
|
}
|
|
audio.speaker_handles.clear();
|
|
audio.sound_scene.reset();
|
|
}
|
|
|
|
void BKE_sound_lock()
|
|
{
|
|
g_state.sound_device_mutex.lock();
|
|
if (g_state.sound_device != nullptr) {
|
|
g_state.sound_device->lock();
|
|
}
|
|
}
|
|
|
|
void BKE_sound_unlock()
|
|
{
|
|
g_state.sound_device_mutex.unlock();
|
|
if (g_state.sound_device != nullptr) {
|
|
g_state.sound_device->unlock();
|
|
}
|
|
}
|
|
|
|
void BKE_sound_reset_scene_specs(Scene *scene)
|
|
{
|
|
sound_verify_evaluated_id(&scene->id);
|
|
|
|
if (scene->runtime->audio.sound_scene) {
|
|
scene->runtime->audio.sound_scene->setSpecs(g_state.initialized_specs.specs);
|
|
}
|
|
}
|
|
|
|
void BKE_sound_mute_scene(Scene *scene, bool muted)
|
|
{
|
|
sound_verify_evaluated_id(&scene->id);
|
|
if (scene->runtime->audio.sound_scene) {
|
|
scene->runtime->audio.sound_scene->mute(muted);
|
|
}
|
|
}
|
|
|
|
void BKE_sound_update_fps(Main *bmain, Scene *scene)
|
|
{
|
|
sound_verify_evaluated_id(&scene->id);
|
|
|
|
if (scene->runtime->audio.sound_scene) {
|
|
scene->runtime->audio.sound_scene->setFPS(scene->frames_per_second());
|
|
}
|
|
|
|
seq::sound_update_length(bmain, scene);
|
|
}
|
|
|
|
void BKE_sound_update_scene_listener(Scene *scene)
|
|
{
|
|
sound_verify_evaluated_id(&scene->id);
|
|
|
|
AUD_Sequence sound = scene->runtime->audio.sound_scene;
|
|
sound->setSpeedOfSound(scene->audio.speed_of_sound);
|
|
sound->setDopplerFactor(scene->audio.doppler_factor);
|
|
sound->setDistanceModel(aud::DistanceModel(scene->audio.distance_model));
|
|
}
|
|
|
|
AUD_SequenceEntry BKE_sound_scene_add_scene_sound(Scene *scene, Strip *strip)
|
|
{
|
|
sound_verify_evaluated_id(&scene->id);
|
|
if (strip->scene && scene != strip->scene) {
|
|
int startframe = strip->left_handle();
|
|
int endframe = strip->right_handle(scene);
|
|
int frameskip = strip->startofs + strip->anim_startofs;
|
|
const double fps = scene->frames_per_second();
|
|
return AUD_SequenceEntry(
|
|
scene->runtime->audio.sound_scene->add(strip->scene->runtime->audio.sound_scene,
|
|
startframe / fps,
|
|
endframe / fps,
|
|
frameskip / fps));
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
AUD_SequenceEntry BKE_sound_add_scene_sound(Scene *scene, Strip *strip)
|
|
{
|
|
sound_verify_evaluated_id(&scene->id);
|
|
/* Happens when sequence's sound data-block was removed. */
|
|
if (strip->sound == nullptr) {
|
|
return nullptr;
|
|
}
|
|
sound_verify_evaluated_id(&strip->sound->id);
|
|
|
|
int startframe = strip->left_handle();
|
|
int endframe = strip->right_handle(scene);
|
|
int frameskip = strip->startofs + strip->anim_startofs;
|
|
|
|
const double fps = scene->frames_per_second();
|
|
const double offset_time = strip->sound->offset_time + strip->sound_offset - frameskip / fps;
|
|
if (offset_time >= 0.0f) {
|
|
return AUD_SequenceEntry(
|
|
scene->runtime->audio.sound_scene->add(strip->sound->runtime->playback_handle,
|
|
startframe / fps + offset_time,
|
|
endframe / fps,
|
|
0.0f));
|
|
}
|
|
return AUD_SequenceEntry(scene->runtime->audio.sound_scene->add(
|
|
strip->sound->runtime->playback_handle, startframe / fps, endframe / fps, -offset_time));
|
|
}
|
|
|
|
void BKE_sound_remove_scene_sound(Scene *scene, AUD_SequenceEntry handle)
|
|
{
|
|
scene->runtime->audio.sound_scene->remove(handle);
|
|
}
|
|
|
|
void BKE_sound_mute_scene_sound(AUD_SequenceEntry handle, bool mute)
|
|
{
|
|
handle->mute(mute);
|
|
}
|
|
|
|
void BKE_sound_move_scene_sound(const Scene *scene,
|
|
AUD_SequenceEntry handle,
|
|
int startframe,
|
|
int endframe,
|
|
int frameskip,
|
|
double audio_offset)
|
|
{
|
|
sound_verify_evaluated_id(&scene->id);
|
|
const double fps = scene->frames_per_second();
|
|
const double offset_time = audio_offset - frameskip / fps;
|
|
if (offset_time >= 0.0f) {
|
|
handle->move(startframe / fps + offset_time, endframe / fps, 0.0f);
|
|
}
|
|
else {
|
|
handle->move(startframe / fps, endframe / fps, -offset_time);
|
|
}
|
|
}
|
|
|
|
void BKE_sound_move_scene_sound_defaults(Scene *scene, Strip *strip)
|
|
{
|
|
sound_verify_evaluated_id(&scene->id);
|
|
if (strip->runtime->scene_sound) {
|
|
double offset_time = 0.0f;
|
|
if (strip->sound != nullptr) {
|
|
offset_time = strip->sound->offset_time + strip->sound_offset;
|
|
}
|
|
BKE_sound_move_scene_sound(scene,
|
|
strip->runtime->scene_sound,
|
|
strip->left_handle(),
|
|
strip->right_handle(scene),
|
|
strip->startofs + strip->anim_startofs,
|
|
offset_time);
|
|
}
|
|
}
|
|
|
|
void BKE_sound_update_scene_sound(AUD_SequenceEntry handle, bSound *sound)
|
|
{
|
|
handle->setSound(sound->runtime->playback_handle);
|
|
}
|
|
|
|
#endif /* WITH_AUDASPACE */
|
|
|
|
void BKE_sound_update_sequence_handle(AUD_SequenceEntry handle, AUD_Sound sound_handle)
|
|
{
|
|
#ifdef WITH_AUDASPACE
|
|
handle->setSound(sound_handle);
|
|
#else
|
|
UNUSED_VARS(handle, sound_handle);
|
|
#endif
|
|
}
|
|
|
|
#ifdef WITH_AUDASPACE
|
|
|
|
template<typename T>
|
|
static void set_audaspace_anim_property(std::shared_ptr<T> sound,
|
|
aud::AnimateablePropertyType type,
|
|
const int frame,
|
|
float *value,
|
|
bool animated)
|
|
{
|
|
aud::AnimateableProperty *prop = sound->getAnimProperty(type);
|
|
if (animated) {
|
|
if (frame >= 0) {
|
|
prop->write(value, frame, 1);
|
|
}
|
|
}
|
|
else {
|
|
prop->write(value);
|
|
}
|
|
}
|
|
|
|
void BKE_sound_set_scene_volume(Scene *scene, float volume)
|
|
{
|
|
sound_verify_evaluated_id(&scene->id);
|
|
if (scene->runtime->audio.sound_scene == nullptr) {
|
|
return;
|
|
}
|
|
const bool animated = (scene->audio.flag & AUDIO_VOLUME_ANIMATED) != 0;
|
|
const int frame = scene->r.cfra;
|
|
set_audaspace_anim_property(
|
|
scene->runtime->audio.sound_scene, aud::AP_VOLUME, frame, &volume, animated);
|
|
}
|
|
|
|
void BKE_sound_set_scene_sound_volume_at_frame(AUD_SequenceEntry handle,
|
|
const int frame,
|
|
float volume,
|
|
bool animated)
|
|
{
|
|
set_audaspace_anim_property(handle, aud::AP_VOLUME, frame, &volume, animated);
|
|
}
|
|
|
|
void BKE_sound_set_scene_sound_pitch_at_frame(AUD_SequenceEntry handle,
|
|
const int frame,
|
|
float pitch,
|
|
bool animated)
|
|
{
|
|
set_audaspace_anim_property(handle, aud::AP_PITCH, frame, &pitch, animated);
|
|
}
|
|
|
|
void BKE_sound_set_scene_sound_pitch_constant_range(AUD_SequenceEntry handle,
|
|
int frame_start,
|
|
int frame_end,
|
|
float pitch)
|
|
{
|
|
frame_start = max_ii(0, frame_start);
|
|
frame_end = max_ii(0, frame_end);
|
|
aud::AnimateableProperty *prop = handle->getAnimProperty(aud::AP_PITCH);
|
|
prop->writeConstantRange(&pitch, frame_start, frame_end);
|
|
}
|
|
|
|
void BKE_sound_set_scene_sound_pan_at_frame(AUD_SequenceEntry handle,
|
|
const int frame,
|
|
float pan,
|
|
bool animated)
|
|
{
|
|
set_audaspace_anim_property(handle, aud::AP_PANNING, frame, &pan, animated);
|
|
}
|
|
|
|
void BKE_sound_update_sequencer(Main *main, bSound *sound)
|
|
{
|
|
BLI_assert_msg(0, "is not supposed to be used, is weird function.");
|
|
|
|
Scene *scene;
|
|
|
|
for (scene = static_cast<Scene *>(main->scenes.first); scene;
|
|
scene = static_cast<Scene *>(scene->id.next))
|
|
{
|
|
seq::sound_update(scene, sound);
|
|
}
|
|
}
|
|
|
|
/* This function assumes that you have already held the g_state.sound_device mutex. */
|
|
static void sound_start_play_scene(Scene *scene)
|
|
{
|
|
sound_verify_evaluated_id(&scene->id);
|
|
|
|
bke::SceneAudioRuntime &audio = scene->runtime->audio;
|
|
if (audio.playback_handle) {
|
|
audio.playback_handle->stop();
|
|
audio.playback_handle.reset();
|
|
}
|
|
|
|
BKE_sound_reset_scene_specs(scene);
|
|
|
|
audio.playback_handle = bke::sound_device_play(g_state.sound_device, audio.sound_scene);
|
|
if (audio.playback_handle) {
|
|
audio.playback_handle->setLoopCount(-1);
|
|
}
|
|
}
|
|
|
|
void BKE_sound_play_scene(Scene *scene)
|
|
{
|
|
std::lock_guard lock(g_state.sound_device_mutex);
|
|
sound_device_use_begin();
|
|
sound_verify_evaluated_id(&scene->id);
|
|
|
|
const double cur_time = FRA2TIME(scene->r.cfra + scene->r.subframe);
|
|
|
|
g_state.sound_device->lock();
|
|
|
|
bke::SceneAudioRuntime &audio = scene->runtime->audio;
|
|
if (audio.sound_scrub_handle && audio.sound_scrub_handle->getStatus() != aud::STATUS_INVALID) {
|
|
/* If the audio scrub handle is playing back, stop to make sure it is not active.
|
|
* Otherwise, it will trigger a callback that will stop audio playback. */
|
|
audio.sound_scrub_handle->stop();
|
|
audio.sound_scrub_handle = nullptr;
|
|
/* The scrub_handle started playback with playback_handle, stop it so we can
|
|
* properly restart it. */
|
|
audio.playback_handle->pause();
|
|
}
|
|
|
|
aud::Status status = audio.playback_handle ? audio.playback_handle->getStatus() :
|
|
aud::STATUS_INVALID;
|
|
|
|
if (status == aud::STATUS_INVALID) {
|
|
sound_start_play_scene(scene);
|
|
|
|
if (!audio.playback_handle) {
|
|
g_state.sound_device->unlock();
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (status != aud::STATUS_PLAYING) {
|
|
/* Seeking the synchronizer will also seek the playback handle.
|
|
* Even if we don't have A/V sync on, keep the synchronizer and handle seek time in sync. */
|
|
aud::DeviceManager::getDevice()->seekSynchronizer(cur_time);
|
|
audio.playback_handle->seek(cur_time);
|
|
audio.playback_handle->resume();
|
|
}
|
|
|
|
if (scene->audio.flag & AUDIO_SYNC) {
|
|
aud::DeviceManager::getDevice()->playSynchronizer();
|
|
}
|
|
|
|
g_state.sound_device->unlock();
|
|
}
|
|
|
|
void BKE_sound_stop_scene(Scene *scene)
|
|
{
|
|
std::lock_guard lock(g_state.sound_device_mutex);
|
|
BLI_assert(g_state.sound_device);
|
|
if (scene->runtime->audio.playback_handle) {
|
|
scene->runtime->audio.playback_handle->pause();
|
|
|
|
if (scene->audio.flag & AUDIO_SYNC) {
|
|
aud::DeviceManager::getDevice()->stopSynchronizer();
|
|
}
|
|
}
|
|
sound_device_use_end();
|
|
}
|
|
|
|
void BKE_sound_seek_scene(Main *bmain, Scene *scene)
|
|
{
|
|
std::lock_guard lock(g_state.sound_device_mutex);
|
|
bool animation_playing = false;
|
|
for (bScreen *screen = static_cast<bScreen *>(bmain->screens.first); screen;
|
|
screen = static_cast<bScreen *>(screen->id.next))
|
|
{
|
|
if (screen->animtimer) {
|
|
animation_playing = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool do_audio_scrub = scene->audio.flag & AUDIO_SCRUB && !animation_playing;
|
|
|
|
if (do_audio_scrub) {
|
|
/* Make sure the sound device is open for scrubbing. */
|
|
sound_device_use_begin();
|
|
}
|
|
else if (g_state.sound_device == nullptr) {
|
|
/* Nothing to do if there is no sound device and we are not doing audio scrubbing. */
|
|
return;
|
|
}
|
|
sound_verify_evaluated_id(&scene->id);
|
|
|
|
g_state.sound_device->lock();
|
|
|
|
bke::SceneAudioRuntime &audio = scene->runtime->audio;
|
|
aud::Status status = audio.playback_handle ? audio.playback_handle->getStatus() :
|
|
aud::STATUS_INVALID;
|
|
if (status == aud::STATUS_INVALID) {
|
|
sound_start_play_scene(scene);
|
|
|
|
if (!audio.playback_handle) {
|
|
g_state.sound_device->unlock();
|
|
if (do_audio_scrub) {
|
|
sound_device_use_end();
|
|
}
|
|
return;
|
|
}
|
|
|
|
audio.playback_handle->pause();
|
|
}
|
|
|
|
const double one_frame = 1.0 / scene->frames_per_second() +
|
|
(U.audiorate > 0 ? U.mixbufsize / double(U.audiorate) : 0.0);
|
|
const double cur_time = FRA2TIME(scene->r.cfra);
|
|
|
|
if (do_audio_scrub) {
|
|
/* Playback one frame of audio without advancing the timeline. */
|
|
audio.playback_handle->seek(cur_time);
|
|
audio.playback_handle->resume();
|
|
if (audio.sound_scrub_handle && audio.sound_scrub_handle->getStatus() != aud::STATUS_INVALID) {
|
|
audio.sound_scrub_handle->seek(0);
|
|
}
|
|
else {
|
|
if (audio.sound_scrub_handle) {
|
|
audio.sound_scrub_handle->stop();
|
|
}
|
|
audio.sound_scrub_handle = bke::sound_pause_after(audio.playback_handle, one_frame);
|
|
}
|
|
sound_device_use_end_after(std::chrono::milliseconds(int(one_frame * 1000)));
|
|
}
|
|
else if (status == aud::STATUS_PLAYING) {
|
|
/* Seeking the synchronizer will also seek the playback handle.
|
|
* Even if we don't have A/V sync on, keep the synchronizer and handle
|
|
* seek time in sync.
|
|
*/
|
|
aud::DeviceManager::getDevice()->seekSynchronizer(cur_time);
|
|
audio.playback_handle->seek(cur_time);
|
|
}
|
|
|
|
g_state.sound_device->unlock();
|
|
}
|
|
|
|
double BKE_sound_sync_scene(Scene *scene)
|
|
{
|
|
sound_verify_evaluated_id(&scene->id);
|
|
|
|
/* Ugly: Blender doesn't like it when the animation is played back during rendering */
|
|
if (G.is_rendering) {
|
|
return NAN_FLT;
|
|
}
|
|
|
|
if (scene->runtime->audio.playback_handle) {
|
|
if (scene->audio.flag & AUDIO_SYNC) {
|
|
return aud::DeviceManager::getDevice()->getSynchronizerPosition();
|
|
}
|
|
|
|
return scene->runtime->audio.playback_handle->getPosition();
|
|
}
|
|
return NAN_FLT;
|
|
}
|
|
|
|
static int sound_read(
|
|
AUD_Sound sound, float *buffer, int length, int samples_per_second, bool *interrupt)
|
|
{
|
|
using namespace aud;
|
|
DeviceSpecs specs;
|
|
float *buf;
|
|
Buffer aBuffer;
|
|
|
|
specs.rate = RATE_INVALID;
|
|
specs.channels = CHANNELS_MONO;
|
|
specs.format = FORMAT_INVALID;
|
|
|
|
std::shared_ptr<IReader> reader = ChannelMapper(sound, specs).createReader();
|
|
|
|
specs.specs = reader->getSpecs();
|
|
int len;
|
|
float samplejump = specs.rate / samples_per_second;
|
|
float min, max, power, overallmax;
|
|
bool eos;
|
|
|
|
overallmax = 0;
|
|
|
|
for (int i = 0; i < length; i++) {
|
|
len = floor(samplejump * (i + 1)) - floor(samplejump * i);
|
|
|
|
if (*interrupt) {
|
|
return 0;
|
|
}
|
|
|
|
aBuffer.assureSize(len * AUD_SAMPLE_SIZE(specs));
|
|
buf = aBuffer.getBuffer();
|
|
|
|
reader->read(len, eos, buf);
|
|
|
|
max = min = *buf;
|
|
power = *buf * *buf;
|
|
for (int j = 1; j < len; j++) {
|
|
if (buf[j] < min) {
|
|
min = buf[j];
|
|
}
|
|
if (buf[j] > max) {
|
|
max = buf[j];
|
|
}
|
|
power += buf[j] * buf[j];
|
|
}
|
|
|
|
buffer[i * 3] = min;
|
|
buffer[i * 3 + 1] = max;
|
|
buffer[i * 3 + 2] = std::sqrt(power / len);
|
|
|
|
if (overallmax < max) {
|
|
overallmax = max;
|
|
}
|
|
if (overallmax < -min) {
|
|
overallmax = -min;
|
|
}
|
|
|
|
if (eos) {
|
|
length = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (overallmax > 1.0f) {
|
|
for (int i = 0; i < length * 3; i++) {
|
|
buffer[i] /= overallmax;
|
|
}
|
|
}
|
|
|
|
return length;
|
|
}
|
|
|
|
void BKE_sound_read_waveform(Main *bmain, bSound *sound, bool *stop)
|
|
{
|
|
bool need_close_audio_handles = false;
|
|
bke::SoundRuntime *runtime = sound->runtime;
|
|
if (runtime->playback_handle == nullptr) {
|
|
/* TODO(sergey): Make it fully independent audio handle. */
|
|
sound_load_audio(bmain, sound, true);
|
|
need_close_audio_handles = true;
|
|
}
|
|
|
|
SoundInfo info = bke::sound_info_get(runtime->playback_handle);
|
|
|
|
Vector<float> *waveform = MEM_new<Vector<float>>(__func__);
|
|
if (info.length > 0) {
|
|
int length = info.length * SOUND_WAVE_SAMPLES_PER_SECOND;
|
|
|
|
waveform->resize(3 * length);
|
|
length = sound_read(
|
|
runtime->playback_handle, waveform->data(), length, SOUND_WAVE_SAMPLES_PER_SECOND, stop);
|
|
waveform->resize(3 * length);
|
|
}
|
|
|
|
if (*stop) {
|
|
MEM_SAFE_DELETE(runtime->waveform);
|
|
BLI_spin_lock(&runtime->spinlock);
|
|
runtime->tags &= ~bke::SoundTags::WaveformLoading;
|
|
BLI_spin_unlock(&runtime->spinlock);
|
|
return;
|
|
}
|
|
|
|
sound_free_waveform(sound);
|
|
|
|
BLI_spin_lock(&runtime->spinlock);
|
|
runtime->waveform = waveform;
|
|
runtime->tags &= ~bke::SoundTags::WaveformLoading;
|
|
BLI_spin_unlock(&runtime->spinlock);
|
|
|
|
if (need_close_audio_handles) {
|
|
sound_free_audio(sound);
|
|
}
|
|
}
|
|
|
|
static void sound_update_base(Scene *scene, Object *object, Set<AUD_SequenceEntry> &new_set)
|
|
{
|
|
Speaker *speaker;
|
|
float quat[4];
|
|
|
|
sound_verify_evaluated_id(&scene->id);
|
|
sound_verify_evaluated_id(&object->id);
|
|
|
|
if ((object->type != OB_SPEAKER) || !object->adt) {
|
|
return;
|
|
}
|
|
|
|
for (NlaTrack &track : object->adt->nla_tracks) {
|
|
for (NlaStrip &strip : track.strips) {
|
|
if (strip.type != NLASTRIP_TYPE_SOUND) {
|
|
continue;
|
|
}
|
|
speaker = (Speaker *)object->data;
|
|
|
|
bke::NlaStripRuntime &strip_runtime = strip.runtime_get();
|
|
|
|
if ((strip_runtime.speaker_handle != nullptr) &&
|
|
scene->runtime->audio.speaker_handles.remove(strip_runtime.speaker_handle))
|
|
{
|
|
if (speaker->sound) {
|
|
strip_runtime.speaker_handle->move(
|
|
double(strip.start) / scene->frames_per_second(), FLT_MAX, 0);
|
|
}
|
|
else {
|
|
scene->runtime->audio.sound_scene->remove(strip_runtime.speaker_handle);
|
|
strip_runtime.speaker_handle = nullptr;
|
|
}
|
|
}
|
|
else {
|
|
if (speaker->sound) {
|
|
strip_runtime.speaker_handle = AUD_SequenceEntry(scene->runtime->audio.sound_scene->add(
|
|
speaker->sound->runtime->playback_handle,
|
|
double(strip.start) / scene->frames_per_second(),
|
|
FLT_MAX,
|
|
0));
|
|
strip_runtime.speaker_handle->setRelative(false);
|
|
}
|
|
}
|
|
|
|
if (strip_runtime.speaker_handle) {
|
|
const bool mute = ((strip.flag & NLASTRIP_FLAG_MUTED) || (speaker->flag & SPK_MUTED));
|
|
new_set.add(strip_runtime.speaker_handle);
|
|
strip_runtime.speaker_handle->setVolumeMaximum(speaker->volume_max);
|
|
strip_runtime.speaker_handle->setVolumeMinimum(speaker->volume_min);
|
|
strip_runtime.speaker_handle->setDistanceMaximum(speaker->distance_max);
|
|
strip_runtime.speaker_handle->setDistanceReference(speaker->distance_reference);
|
|
strip_runtime.speaker_handle->setAttenuation(speaker->attenuation);
|
|
strip_runtime.speaker_handle->setConeAngleOuter(speaker->cone_angle_outer);
|
|
strip_runtime.speaker_handle->setConeAngleInner(speaker->cone_angle_inner);
|
|
strip_runtime.speaker_handle->setConeVolumeOuter(speaker->cone_volume_outer);
|
|
|
|
mat4_to_quat(quat, object->object_to_world().ptr());
|
|
float3 location = object->object_to_world().location();
|
|
set_audaspace_anim_property(
|
|
strip_runtime.speaker_handle, aud::AP_LOCATION, scene->r.cfra, location, true);
|
|
set_audaspace_anim_property(
|
|
strip_runtime.speaker_handle, aud::AP_ORIENTATION, scene->r.cfra, quat, true);
|
|
set_audaspace_anim_property(
|
|
strip_runtime.speaker_handle, aud::AP_VOLUME, scene->r.cfra, &speaker->volume, true);
|
|
set_audaspace_anim_property(
|
|
strip_runtime.speaker_handle, aud::AP_PITCH, scene->r.cfra, &speaker->pitch, true);
|
|
strip_runtime.speaker_handle->setSound(speaker->sound->runtime->playback_handle);
|
|
strip_runtime.speaker_handle->mute(mute);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void BKE_sound_update_scene(Depsgraph *depsgraph, Scene *scene)
|
|
{
|
|
sound_verify_evaluated_id(&scene->id);
|
|
|
|
Set<AUD_SequenceEntry> new_set;
|
|
float quat[4];
|
|
|
|
/* cheap test to skip looping over all objects (no speakers is a common case) */
|
|
if (DEG_id_type_any_exists(depsgraph, ID_SPK)) {
|
|
DEGObjectIterSettings deg_iter_settings = {nullptr};
|
|
deg_iter_settings.depsgraph = depsgraph;
|
|
deg_iter_settings.flags = DEG_ITER_OBJECT_FLAG_LINKED_DIRECTLY |
|
|
DEG_ITER_OBJECT_FLAG_LINKED_INDIRECTLY |
|
|
DEG_ITER_OBJECT_FLAG_LINKED_VIA_SET;
|
|
DEG_OBJECT_ITER_BEGIN (°_iter_settings, object) {
|
|
sound_update_base(scene, object, new_set);
|
|
}
|
|
DEG_OBJECT_ITER_END;
|
|
}
|
|
|
|
bke::SceneAudioRuntime &audio = scene->runtime->audio;
|
|
for (AUD_SequenceEntry handle : audio.speaker_handles) {
|
|
audio.sound_scene->remove(handle);
|
|
}
|
|
audio.speaker_handles.clear();
|
|
|
|
if (scene->camera) {
|
|
mat4_to_quat(quat, scene->camera->object_to_world().ptr());
|
|
float3 location = scene->camera->object_to_world().location();
|
|
set_audaspace_anim_property(
|
|
audio.sound_scene, aud::AP_LOCATION, scene->r.cfra, location, true);
|
|
set_audaspace_anim_property(audio.sound_scene, aud::AP_ORIENTATION, scene->r.cfra, quat, true);
|
|
}
|
|
|
|
audio.speaker_handles = new_set;
|
|
}
|
|
|
|
AUD_Sound BKE_sound_get_factory(void *sound)
|
|
{
|
|
return ((bSound *)sound)->runtime->playback_handle;
|
|
}
|
|
|
|
float BKE_sound_get_length(Main *bmain, bSound *sound)
|
|
{
|
|
if (sound->runtime->playback_handle != nullptr) {
|
|
SoundInfo info = bke::sound_info_get(sound->runtime->playback_handle);
|
|
return info.length;
|
|
}
|
|
SoundInfo info;
|
|
if (!BKE_sound_info_get(bmain, sound, &info)) {
|
|
return 0.0f;
|
|
}
|
|
return info.length;
|
|
}
|
|
|
|
char **BKE_sound_get_device_names()
|
|
{
|
|
if (audio_device_names == nullptr) {
|
|
|
|
std::vector<std::string> v_names = aud::DeviceManager::getAvailableDeviceNames();
|
|
char **names = (char **)malloc(sizeof(char *) * (v_names.size() + 1));
|
|
|
|
for (int i = 0; i < v_names.size(); i++) {
|
|
std::string name = v_names[i];
|
|
const size_t name_size = sizeof(char) * (name.length() + 1);
|
|
names[i] = (char *)malloc(name_size);
|
|
memcpy(names[i], name.c_str(), name_size);
|
|
}
|
|
names[v_names.size()] = nullptr;
|
|
audio_device_names = names;
|
|
}
|
|
|
|
return audio_device_names;
|
|
}
|
|
|
|
bool BKE_sound_info_get(Main *main, bSound *sound, SoundInfo *sound_info)
|
|
{
|
|
if (sound->runtime->playback_handle != nullptr) {
|
|
*sound_info = bke::sound_info_get(sound->runtime->playback_handle);
|
|
return true;
|
|
}
|
|
/* TODO(sergey): Make it fully independent audio handle. */
|
|
/* Don't free waveforms during non-destructive queries.
|
|
* This causes unnecessary recalculation - see #69921 */
|
|
sound_load_audio(main, sound, false);
|
|
const bool result = sound->runtime->playback_handle != nullptr;
|
|
if (result) {
|
|
*sound_info = bke::sound_info_get(sound->runtime->playback_handle);
|
|
}
|
|
sound_free_audio(sound);
|
|
return result;
|
|
}
|
|
|
|
bool BKE_sound_stream_info_get(Main *main,
|
|
const char *filepath,
|
|
int stream,
|
|
SoundStreamInfo *sound_info)
|
|
{
|
|
char filepath_abs[FILE_MAX];
|
|
STRNCPY(filepath_abs, filepath);
|
|
const char *blendfile_path = BKE_main_blendfile_path(main);
|
|
BLI_path_abs(filepath_abs, blendfile_path);
|
|
|
|
std::vector<aud::StreamInfo> streams;
|
|
try {
|
|
streams = aud::FileManager::queryStreams(filepath_abs);
|
|
}
|
|
catch (aud::Exception &) {
|
|
return false;
|
|
}
|
|
|
|
if (stream < 0 || stream >= streams.size()) {
|
|
return false;
|
|
}
|
|
|
|
sound_info->start = streams[stream].start;
|
|
sound_info->duration = streams[stream].duration;
|
|
return true;
|
|
}
|
|
|
|
int BKE_sound_stream_count(Main *main, const char *filepath)
|
|
{
|
|
char filepath_abs[FILE_MAX];
|
|
STRNCPY(filepath_abs, filepath);
|
|
BLI_path_abs(filepath_abs, BKE_main_blendfile_path(main));
|
|
|
|
std::vector<aud::StreamInfo> streams;
|
|
try {
|
|
streams = aud::FileManager::queryStreams(filepath_abs);
|
|
return streams.size();
|
|
}
|
|
catch (aud::Exception &) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
# ifdef WITH_RUBBERBAND
|
|
AUD_Sound BKE_sound_ensure_time_stretch_effect(const Strip *strip, float fps)
|
|
{
|
|
seq::StripRuntime &runtime = *strip->runtime;
|
|
|
|
/* If we already have a time stretch effect with the same frame-rate, use that. */
|
|
if (runtime.sound_time_stretch != nullptr && runtime.sound_time_stretch_fps == fps) {
|
|
return runtime.sound_time_stretch;
|
|
}
|
|
|
|
/* Otherwise create the time stretch effect. */
|
|
runtime.clear_sound_time_stretch();
|
|
runtime.sound_time_stretch = AUD_Sound(
|
|
new aud::AnimateableTimeStretchPitchScale(BKE_sound_playback_handle_get(strip->sound),
|
|
fps,
|
|
1.0f,
|
|
1.0f,
|
|
aud::StretcherQuality::HIGH,
|
|
false));
|
|
runtime.sound_time_stretch_fps = fps;
|
|
return runtime.sound_time_stretch;
|
|
}
|
|
|
|
void BKE_sound_set_scene_sound_time_stretch_at_frame(AUD_Sound handle,
|
|
int frame,
|
|
float time_stretch,
|
|
bool animated)
|
|
{
|
|
std::shared_ptr<aud::AnimateableProperty> prop =
|
|
std::dynamic_pointer_cast<aud::AnimateableTimeStretchPitchScale>(handle)->getAnimProperty(
|
|
aud::AP_TIME_STRETCH);
|
|
if (animated) {
|
|
if (frame >= 0) {
|
|
prop->write(&time_stretch, frame, 1);
|
|
}
|
|
}
|
|
else {
|
|
prop->write(&time_stretch);
|
|
}
|
|
}
|
|
|
|
void BKE_sound_set_scene_sound_time_stretch_constant_range(AUD_Sound handle,
|
|
int frame_start,
|
|
int frame_end,
|
|
float time_stretch)
|
|
{
|
|
frame_start = max_ii(0, frame_start);
|
|
frame_end = max_ii(0, frame_end);
|
|
std::shared_ptr<aud::AnimateableProperty> prop =
|
|
std::dynamic_pointer_cast<aud::AnimateableTimeStretchPitchScale>(handle)->getAnimProperty(
|
|
aud::AP_TIME_STRETCH);
|
|
prop->writeConstantRange(&time_stretch, frame_start, frame_end);
|
|
}
|
|
# endif /* WITH_RUBBERBAND */
|
|
|
|
SoundInfo bke::sound_info_get(AUD_Sound sound)
|
|
{
|
|
SoundInfo res;
|
|
res.length = 0.0f;
|
|
res.specs.channels = SOUND_CHANNELS_INVALID;
|
|
res.specs.samplerate = 0;
|
|
|
|
try {
|
|
std::shared_ptr<aud::IReader> reader = sound->createReader();
|
|
if (reader.get()) {
|
|
aud::Specs specs = reader->getSpecs();
|
|
res.specs.channels = eSoundChannels(specs.channels);
|
|
res.specs.samplerate = specs.rate;
|
|
res.length = reader->getLength() / float(specs.rate);
|
|
}
|
|
}
|
|
catch (aud::Exception &) {
|
|
}
|
|
return res;
|
|
}
|
|
|
|
void bke::sound_system_initialize()
|
|
{
|
|
aud::PluginManager::loadPlugins();
|
|
aud::NULLDevice::registerPlugin();
|
|
}
|
|
|
|
AUD_Device bke::sound_device_init(const char *device,
|
|
const aud::DeviceSpecs &specs,
|
|
int buffersize,
|
|
const char *name)
|
|
{
|
|
using namespace aud;
|
|
try {
|
|
std::shared_ptr<IDeviceFactory> factory = device ? DeviceManager::getDeviceFactory(device) :
|
|
DeviceManager::getDefaultDeviceFactory();
|
|
|
|
if (factory) {
|
|
factory->setName(name);
|
|
factory->setBufferSize(buffersize);
|
|
factory->setSpecs(specs);
|
|
auto device = factory->openDevice();
|
|
DeviceManager::setDevice(device);
|
|
|
|
return AUD_Device(device);
|
|
}
|
|
}
|
|
catch (Exception &) {
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void bke::sound_device_exit()
|
|
{
|
|
aud::DeviceManager::releaseDevice();
|
|
}
|
|
|
|
AUD_Handle bke::sound_device_play(AUD_Device device, AUD_Sound sound)
|
|
{
|
|
if (!device) {
|
|
device = aud::DeviceManager::getDevice();
|
|
}
|
|
try {
|
|
return device->play(sound, true);
|
|
}
|
|
catch (aud::Exception &) {
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
bool bke::sound_device_read(AUD_Device device, uchar *buffer, int length)
|
|
{
|
|
BLI_assert(buffer);
|
|
auto read_device = std::dynamic_pointer_cast<aud::ReadDevice>(device);
|
|
if (!read_device) {
|
|
return false;
|
|
}
|
|
try {
|
|
return read_device->read(buffer, length);
|
|
}
|
|
catch (aud::Exception &) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
AUD_Handle bke::sound_pause_after(AUD_Handle handle, double seconds)
|
|
{
|
|
auto device = aud::DeviceManager::getDevice();
|
|
|
|
AUD_Sound silence = AUD_Sound(new aud::Silence(device->getSpecs().rate));
|
|
AUD_Sound limiter = AUD_Sound(new aud::Limiter(silence, 0, seconds));
|
|
|
|
std::lock_guard<aud::ILockable> lock(*device);
|
|
|
|
try {
|
|
AUD_Handle handle2 = device->play(limiter);
|
|
if (handle2.get()) {
|
|
AUD_Handle *data = new AUD_Handle(handle);
|
|
handle2->setStopCallback(
|
|
[](void *data) {
|
|
AUD_Handle *handle = (AUD_Handle *)data;
|
|
(*handle)->pause();
|
|
delete handle;
|
|
},
|
|
data);
|
|
return handle2;
|
|
}
|
|
}
|
|
catch (aud::Exception &) {
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
float *bke::sound_read_file_buffer(const char *filename,
|
|
float low,
|
|
float high,
|
|
float attack,
|
|
float release,
|
|
float threshold,
|
|
bool accumulate,
|
|
bool additive,
|
|
bool square,
|
|
float sthreshold,
|
|
double samplerate,
|
|
int stream,
|
|
int *length)
|
|
{
|
|
using namespace aud;
|
|
|
|
DeviceSpecs specs;
|
|
specs.channels = CHANNELS_MONO;
|
|
specs.rate = samplerate;
|
|
|
|
AUD_Sound file = AUD_Sound(new File(filename, stream));
|
|
int position = 0;
|
|
|
|
Buffer buffer;
|
|
|
|
try {
|
|
std::shared_ptr<IReader> reader = file->createReader();
|
|
SampleRate rate = reader->getSpecs().rate;
|
|
|
|
AUD_Sound sound = AUD_Sound(new ChannelMapper(file, specs));
|
|
|
|
if (high < rate) {
|
|
sound = AUD_Sound(new Lowpass(sound, high));
|
|
}
|
|
if (low > 0) {
|
|
sound = AUD_Sound(new Highpass(sound, low));
|
|
}
|
|
|
|
sound = AUD_Sound(new Envelope(sound, attack, release, threshold, 0.1f));
|
|
sound = AUD_Sound(new LinearResample(sound, specs));
|
|
|
|
if (square) {
|
|
sound = AUD_Sound(new Threshold(sound, sthreshold));
|
|
}
|
|
|
|
if (accumulate) {
|
|
sound = AUD_Sound(new Accumulator(sound, additive));
|
|
}
|
|
else if (additive) {
|
|
sound = AUD_Sound(new Sum(sound));
|
|
}
|
|
|
|
reader = sound->createReader();
|
|
|
|
if (!reader.get()) {
|
|
return nullptr;
|
|
}
|
|
|
|
int len;
|
|
bool eos;
|
|
do {
|
|
len = samplerate;
|
|
buffer.resize((position + len) * sizeof(float), true);
|
|
reader->read(len, eos, buffer.getBuffer() + position);
|
|
position += len;
|
|
} while (!eos);
|
|
}
|
|
catch (Exception &) {
|
|
return nullptr;
|
|
}
|
|
|
|
float *result = MEM_new_array_uninitialized<float>(position, __func__);
|
|
memcpy(result, buffer.getBuffer(), position * sizeof(float));
|
|
*length = position;
|
|
return result;
|
|
}
|
|
|
|
bool bke::sound_mixdown(AUD_Sequence sequence,
|
|
uint start,
|
|
uint length,
|
|
uint buffersize,
|
|
const char *filename,
|
|
const aud::DeviceSpecs &specs,
|
|
aud::Container format,
|
|
aud::Codec codec,
|
|
uint bitrate,
|
|
bool split_channels,
|
|
std::string &r_error,
|
|
bool (*progress_callback)(float, void *),
|
|
void *progress_data)
|
|
{
|
|
using namespace aud;
|
|
try {
|
|
sequence->setSpecs(specs.specs);
|
|
std::shared_ptr<IReader> reader = sequence->createQualityReader(ResampleQuality::MEDIUM);
|
|
reader->seek(start);
|
|
|
|
if (!split_channels) {
|
|
std::shared_ptr<IWriter> writer = FileWriter::createWriter(
|
|
filename, specs, format, codec, bitrate);
|
|
FileWriter::writeReader(
|
|
reader, writer, length, buffersize, progress_callback, progress_data);
|
|
}
|
|
else {
|
|
std::vector<std::shared_ptr<IWriter>> writers;
|
|
int channels = specs.channels;
|
|
|
|
aud::DeviceSpecs specs_mono = specs;
|
|
specs_mono.channels = CHANNELS_MONO;
|
|
for (int i = 0; i < channels; i++) {
|
|
std::string stream;
|
|
std::string fn = filename;
|
|
size_t index = fn.find_last_of('.');
|
|
size_t index_slash = fn.find_last_of('/');
|
|
size_t index_backslash = fn.find_last_of('\\');
|
|
|
|
if ((index == std::string::npos) ||
|
|
((index < index_slash) && (index_slash != std::string::npos)) ||
|
|
((index < index_backslash) && (index_backslash != std::string::npos)))
|
|
{
|
|
stream = fmt::format("{}_{}", filename, i + 1);
|
|
}
|
|
else {
|
|
stream = fmt::format("{}_{}{}", fn.substr(0, index), i + 1, fn.substr(index));
|
|
}
|
|
writers.push_back(FileWriter::createWriter(stream, specs_mono, format, codec, bitrate));
|
|
}
|
|
FileWriter::writeReader(
|
|
reader, writers, length, buffersize, progress_callback, progress_data);
|
|
}
|
|
return true;
|
|
}
|
|
catch (Exception &e) {
|
|
r_error = e.getMessage();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
#else /* WITH_AUDASPACE */
|
|
|
|
# include "BLI_utildefines.h"
|
|
|
|
void BKE_sound_force_device(const char * /*device*/) {}
|
|
void BKE_sound_init_once() {}
|
|
void BKE_sound_init(Main * /*bmain*/) {}
|
|
void BKE_sound_exit_once() {}
|
|
void BKE_sound_load(Main * /*bmain*/, bSound * /*sound*/) {}
|
|
void BKE_sound_packfile_ensure(Main * /*bmain*/, bSound * /*sound*/, ReportList * /*reports*/) {}
|
|
void BKE_sound_create_scene(Scene * /*scene*/) {}
|
|
void BKE_sound_destroy_scene(Scene * /*scene*/) {}
|
|
void BKE_sound_lock() {}
|
|
void BKE_sound_unlock() {}
|
|
void BKE_sound_refresh_callback_bmain(Main * /*bmain*/) {}
|
|
void BKE_sound_reset_scene_specs(Scene * /*scene*/) {}
|
|
void BKE_sound_mute_scene(Scene * /*scene*/, bool /*muted*/) {}
|
|
AUD_SequenceEntry BKE_sound_scene_add_scene_sound(Scene * /*scene*/, Strip * /*strip*/)
|
|
{
|
|
return nullptr;
|
|
}
|
|
AUD_SequenceEntry BKE_sound_add_scene_sound(Scene * /*scene*/, Strip * /*strip*/)
|
|
{
|
|
return nullptr;
|
|
}
|
|
void BKE_sound_remove_scene_sound(Scene * /*scene*/, AUD_SequenceEntry /*handle*/) {}
|
|
void BKE_sound_mute_scene_sound(AUD_SequenceEntry /*handle*/, bool /*mute*/) {}
|
|
void BKE_sound_move_scene_sound(const Scene * /*scene*/,
|
|
AUD_SequenceEntry /*handle*/,
|
|
int /*startframe*/,
|
|
int /*endframe*/,
|
|
int /*frameskip*/,
|
|
double /*audio_offset*/)
|
|
{
|
|
}
|
|
void BKE_sound_move_scene_sound_defaults(Scene * /*scene*/, Strip * /*strip*/) {}
|
|
void BKE_sound_play_scene(Scene * /*scene*/) {}
|
|
void BKE_sound_stop_scene(Scene * /*scene*/) {}
|
|
void BKE_sound_seek_scene(Main * /*bmain*/, Scene * /*scene*/) {}
|
|
double BKE_sound_sync_scene(Scene * /*scene*/)
|
|
{
|
|
return NAN_FLT;
|
|
}
|
|
void BKE_sound_read_waveform(Main *bmain,
|
|
bSound *sound,
|
|
/* NOLINTNEXTLINE: readability-non-const-parameter. */
|
|
bool *stop)
|
|
{
|
|
UNUSED_VARS(sound, stop, bmain);
|
|
}
|
|
|
|
void BKE_sound_update_sequencer(Main * /*main*/, bSound * /*sound*/) {}
|
|
void BKE_sound_update_scene(Depsgraph * /*depsgraph*/, Scene * /*scene*/) {}
|
|
void BKE_sound_update_scene_sound(AUD_SequenceEntry /*handle*/, bSound * /*sound*/) {}
|
|
void BKE_sound_update_scene_listener(Scene * /*scene*/) {}
|
|
void BKE_sound_update_fps(Main * /*bmain*/, Scene * /*scene*/) {}
|
|
void BKE_sound_set_scene_sound_volume_at_frame(AUD_SequenceEntry /*handle*/,
|
|
int /*frame*/,
|
|
float /*volume*/,
|
|
bool /*animated*/)
|
|
{
|
|
}
|
|
void BKE_sound_set_scene_sound_pan_at_frame(AUD_SequenceEntry /*handle*/,
|
|
int /*frame*/,
|
|
float /*pan*/,
|
|
bool /*animated*/)
|
|
{
|
|
}
|
|
void BKE_sound_set_scene_volume(Scene * /*scene*/, float /*volume*/) {}
|
|
void BKE_sound_set_scene_sound_pitch_at_frame(AUD_SequenceEntry /*handle*/,
|
|
int /*frame*/,
|
|
float /*pitch*/,
|
|
bool /*animated*/)
|
|
{
|
|
}
|
|
void BKE_sound_set_scene_sound_pitch_constant_range(AUD_SequenceEntry /*handle*/,
|
|
int /*frame_start*/,
|
|
int /*frame_end*/,
|
|
float /*pitch*/)
|
|
{
|
|
}
|
|
float BKE_sound_get_length(Main * /*bmain*/, bSound * /*sound*/)
|
|
{
|
|
return 0;
|
|
}
|
|
char **BKE_sound_get_device_names()
|
|
{
|
|
static char *names[1] = {nullptr};
|
|
return names;
|
|
}
|
|
|
|
bool BKE_sound_info_get(Main * /*main*/, bSound * /*sound*/, SoundInfo * /*sound_info*/)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool BKE_sound_stream_info_get(Main * /*main*/,
|
|
const char * /*filepath*/,
|
|
int /*stream*/,
|
|
SoundStreamInfo * /*sound_info*/)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int BKE_sound_stream_count(Main * /*main*/, const char * /*filepath*/)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
#endif /* WITH_AUDASPACE */
|
|
|
|
#if !defined(WITH_AUDASPACE) || !defined(WITH_RUBBERBAND)
|
|
AUD_Sound BKE_sound_ensure_time_stretch_effect(const Strip * /*strip*/, float /*fps*/)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
void BKE_sound_set_scene_sound_time_stretch_at_frame(AUD_Sound /*handle*/,
|
|
int /*frame*/,
|
|
float /*time_stretch*/,
|
|
bool /*animated*/)
|
|
{
|
|
}
|
|
void BKE_sound_set_scene_sound_time_stretch_constant_range(AUD_Sound /*handle*/,
|
|
int /*frame_start*/,
|
|
int /*frame_end*/,
|
|
float /*time_stretch*/)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
void BKE_sound_ensure_scene(Scene *scene)
|
|
{
|
|
if (scene->runtime->audio.sound_scene != nullptr) {
|
|
return;
|
|
}
|
|
BKE_sound_create_scene(scene);
|
|
}
|
|
|
|
static void sound_ensure_loaded(Main *bmain, bSound *sound)
|
|
{
|
|
if (sound->runtime->cache != nullptr) {
|
|
return;
|
|
}
|
|
BKE_sound_load(bmain, sound);
|
|
}
|
|
|
|
void BKE_sound_jack_sync_callback_set(SoundJackSyncCallback callback)
|
|
{
|
|
#if defined(WITH_AUDASPACE)
|
|
sound_jack_sync_callback = callback;
|
|
#else
|
|
UNUSED_VARS(callback);
|
|
#endif
|
|
}
|
|
|
|
void BKE_sound_jack_scene_update(Scene *scene, int mode, double time)
|
|
{
|
|
sound_verify_evaluated_id(&scene->id);
|
|
|
|
/* Ugly: Blender doesn't like it when the animation is played back during rendering. */
|
|
if (G.is_rendering) {
|
|
return;
|
|
}
|
|
#ifdef WITH_AUDASPACE
|
|
g_state.sound_device->lock();
|
|
|
|
if (mode) {
|
|
BKE_sound_play_scene(scene);
|
|
}
|
|
else {
|
|
BKE_sound_stop_scene(scene);
|
|
}
|
|
if (scene->runtime->audio.playback_handle != nullptr) {
|
|
scene->runtime->audio.playback_handle->seek(time);
|
|
}
|
|
g_state.sound_device->unlock();
|
|
#else
|
|
UNUSED_VARS(mode, time);
|
|
#endif
|
|
}
|
|
|
|
void BKE_sound_evaluate(Depsgraph *depsgraph, Main *bmain, bSound *sound)
|
|
{
|
|
DEG_debug_print_eval(depsgraph, __func__, sound->id.name, sound);
|
|
if (sound->id.recalc & ID_RECALC_SOURCE) {
|
|
/* Sequencer checks this flag to see if the strip sound is to be updated from the Audaspace
|
|
* side. */
|
|
sound->id.recalc |= ID_RECALC_AUDIO;
|
|
}
|
|
|
|
if (sound->id.recalc & ID_RECALC_AUDIO) {
|
|
BKE_sound_load(bmain, sound);
|
|
return;
|
|
}
|
|
sound_ensure_loaded(bmain, sound);
|
|
}
|
|
|
|
void BKE_sound_runtime_state_get_and_clear(const bSound *sound,
|
|
AUD_Sound *r_cache,
|
|
AUD_Sound *r_playback_handle,
|
|
Vector<float> **r_waveform)
|
|
{
|
|
bke::SoundRuntime *runtime = sound->runtime;
|
|
*r_cache = runtime->cache;
|
|
*r_playback_handle = runtime->playback_handle;
|
|
*r_waveform = runtime->waveform;
|
|
runtime->cache = nullptr;
|
|
runtime->playback_handle = nullptr;
|
|
runtime->waveform = nullptr;
|
|
}
|
|
|
|
void BKE_sound_runtime_state_set(const bSound *sound,
|
|
AUD_Sound cache,
|
|
AUD_Sound playback_handle,
|
|
Vector<float> *waveform)
|
|
{
|
|
bke::SoundRuntime *runtime = sound->runtime;
|
|
runtime->cache = cache;
|
|
runtime->playback_handle = playback_handle;
|
|
runtime->waveform = waveform;
|
|
}
|
|
|
|
AUD_Sound BKE_sound_playback_handle_get(const bSound *sound)
|
|
{
|
|
if (sound == nullptr) {
|
|
return nullptr;
|
|
}
|
|
return sound->runtime->playback_handle;
|
|
}
|
|
|
|
void BKE_sound_runtime_clear_waveform_loading_tag(bSound *sound)
|
|
{
|
|
bke::SoundRuntime *runtime = sound->runtime;
|
|
BLI_spin_lock(&runtime->spinlock);
|
|
runtime->tags &= ~bke::SoundTags::WaveformLoading;
|
|
BLI_spin_unlock(&runtime->spinlock);
|
|
}
|
|
|
|
bool BKE_sound_runtime_start_waveform_loading(bSound *sound)
|
|
{
|
|
bke::SoundRuntime *runtime = sound->runtime;
|
|
bool result = false;
|
|
BLI_spin_lock(&runtime->spinlock);
|
|
if (runtime->waveform == nullptr) {
|
|
/* Load the waveform data if it hasn't been loaded and cached already. */
|
|
if (!flag_is_set(runtime->tags, bke::SoundTags::WaveformLoading)) {
|
|
/* Prevent sounds from reloading. */
|
|
runtime->tags |= bke::SoundTags::WaveformLoading;
|
|
result = true;
|
|
}
|
|
}
|
|
BLI_spin_unlock(&runtime->spinlock);
|
|
return result;
|
|
}
|
|
|
|
const Vector<float> *BKE_sound_runtime_get_waveform(const bSound *sound)
|
|
{
|
|
return sound->runtime->waveform;
|
|
}
|
|
|
|
namespace bke {
|
|
|
|
const bSoundFrequencySampler *bSoundFrequencySampler::get_cached(const bSound &sound,
|
|
const Key &key)
|
|
{
|
|
#ifdef WITH_AUDASPACE
|
|
{
|
|
/* Fast common case when the sampler has been created already. */
|
|
bSoundFrequencySamplerMap::ConstAccessor accessor;
|
|
if (sound.runtime->samplers.lookup(accessor, key)) {
|
|
return accessor->second.get();
|
|
}
|
|
}
|
|
AUD_Sound sound_handle = sound.runtime->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 (key.channel.has_value()) {
|
|
const SoundInfo info = sound_info_get(sound_handle);
|
|
const int channel = *key.channel;
|
|
if (channel < 0 || channel >= info.specs.channels) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
accessor->second = std::make_shared<bSoundFrequencySampler>(sound_handle, key);
|
|
}
|
|
return accessor->second.get();
|
|
#else
|
|
UNUSED_VARS(sound, key);
|
|
return nullptr;
|
|
#endif
|
|
}
|
|
|
|
static bSoundFrequencySampler::WindowWeights compute_window_function_weights(
|
|
const bSoundFrequencySampler::WindowFunction window, const int size)
|
|
{
|
|
Array<float> weights(size);
|
|
switch (window) {
|
|
case bSoundFrequencySampler::WindowFunction::Hann: {
|
|
for (const int i : IndexRange(size)) {
|
|
weights[i] = 0.5f - 0.5f * math::cos((2.0f * std::numbers::pi * i) / (size - 1));
|
|
}
|
|
break;
|
|
}
|
|
case bSoundFrequencySampler::WindowFunction::Hamming: {
|
|
for (const int i : IndexRange(size)) {
|
|
weights[i] = 0.54f - 0.46f * math::cos((2.0f * std::numbers::pi * i) / (size - 1));
|
|
}
|
|
break;
|
|
}
|
|
case bSoundFrequencySampler::WindowFunction::Blackman: {
|
|
for (const int i : IndexRange(size)) {
|
|
weights[i] = 0.42f - 0.5f * math::cos((2.0f * std::numbers::pi * i) / (size - 1)) +
|
|
0.08f * math::cos((4.0f * std::numbers::pi * i) / (size - 1));
|
|
}
|
|
break;
|
|
}
|
|
case bSoundFrequencySampler::WindowFunction::Rectangular: {
|
|
weights.fill(1.0f);
|
|
break;
|
|
}
|
|
}
|
|
const float sum = std::accumulate(weights.begin(), weights.end(), 0.0f);
|
|
return bSoundFrequencySampler::WindowWeights{std::move(weights), sum};
|
|
}
|
|
|
|
/** Caches the window function weights so that each combination is only computed once. */
|
|
static const bSoundFrequencySampler::WindowWeights &get_window_function_weights(
|
|
const bSoundFrequencySampler::WindowFunction window, const int size)
|
|
{
|
|
static Mutex mutex;
|
|
static Map<std::pair<bSoundFrequencySampler::WindowFunction, int>,
|
|
std::unique_ptr<bSoundFrequencySampler::WindowWeights>>
|
|
map;
|
|
std::lock_guard lock{mutex};
|
|
return *map.lookup_or_add_cb({window, size}, [&]() {
|
|
return std::make_unique<bSoundFrequencySampler::WindowWeights>(
|
|
compute_window_function_weights(window, size));
|
|
});
|
|
}
|
|
|
|
bSoundFrequencySampler::bSoundFrequencySampler(AUD_Sound sound, const Key &key)
|
|
: sound_(sound),
|
|
key_(key),
|
|
window_weights_(get_window_function_weights(key.window_function, key.fft_size))
|
|
{
|
|
#ifdef WITH_AUDASPACE
|
|
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. */
|
|
window_cache_stride_ = key.fft_size / 8;
|
|
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
|
|
}
|
|
|
|
std::optional<Array<float>> bSoundFrequencySampler::compute_fft(const int start_sample) const
|
|
{
|
|
/* Since the result of the dft algorithm is symmetric in this case, only the first half is
|
|
* computed. */
|
|
const int frequencies_num = key_.fft_size / 2;
|
|
|
|
#if defined(WITH_AUDASPACE) && defined(WITH_FFTW3)
|
|
/* Read some extra samples before the ones we are actually interested in here. This is done
|
|
* 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);
|
|
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());
|
|
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;
|
|
|
|
/* Pull out the samples for the requested channel(s). */
|
|
Array<float> buffer(key_.fft_size, 0.0f);
|
|
if (key_.channel.has_value()) {
|
|
const int channel = *key_.channel;
|
|
if (channel < 0 || channel >= channels_num) {
|
|
return std::nullopt;
|
|
}
|
|
for (const int i : IndexRange(read_length)) {
|
|
buffer[i] = read_buffer[i * channels_num + channel];
|
|
}
|
|
}
|
|
else {
|
|
for (const int i : IndexRange(read_length)) {
|
|
for (const int c : IndexRange(channels_num)) {
|
|
buffer[i] += read_buffer[i * channels_num + c];
|
|
}
|
|
buffer[i] /= channels_num;
|
|
}
|
|
}
|
|
|
|
/* Apply window function which avoids spectral leakage (depending on the function). */
|
|
for (const int i : IndexRange(read_length)) {
|
|
buffer[i] *= window_weights_.weights[i];
|
|
}
|
|
|
|
/* Set up the fftw plan. */
|
|
fftwf_complex *fftwf_buffer = static_cast<fftwf_complex *>(
|
|
fftwf_malloc(sizeof(fftwf_complex) * (frequencies_num + 1)));
|
|
fftwf_plan plan = fftwf_plan_dft_r2c_1d(
|
|
key_.fft_size, buffer.data(), fftwf_buffer, FFTW_ESTIMATE);
|
|
BLI_SCOPED_DEFER([&]() {
|
|
fftwf_destroy_plan(plan);
|
|
fftwf_free(fftwf_buffer);
|
|
});
|
|
|
|
/* Actually perform the fourier transform. */
|
|
fftwf_execute(plan);
|
|
|
|
/* Compute the amplitudes of the frequencies. */
|
|
Array<float> frequency_amplitudes(frequencies_num);
|
|
/* The scaling factor is applied so that changing the fft size or window function does not affect
|
|
* the magnitude of the result. */
|
|
const float scaling_factor = 1.0f / window_weights_.weights_sum;
|
|
for (const int i : IndexRange(frequencies_num)) {
|
|
const fftwf_complex &c = fftwf_buffer[i];
|
|
/* Take real and imaginary parts into account which correspond to the sin and cos component of
|
|
* the frequency. */
|
|
frequency_amplitudes[i] = sqrt(pow2f(c[0]) + pow2f(c[1])) * scaling_factor;
|
|
}
|
|
|
|
return frequency_amplitudes;
|
|
#else
|
|
UNUSED_VARS(start_sample);
|
|
return Array<float>(frequencies_num, 0.0f);
|
|
#endif
|
|
}
|
|
|
|
static float catmul_rom_interpolation(
|
|
const float p0, const float p1, const float p2, const float p3, const float t)
|
|
{
|
|
const float t2 = t * t;
|
|
const float t3 = t2 * t;
|
|
|
|
const float value = 0.5 * ((2 * p1) + (-p0 + p2) * t + (2 * p0 - 5 * p1 + 4 * p2 - p3) * t2 +
|
|
(-p0 + 3 * p1 - 3 * p2 + p3) * t3);
|
|
return value;
|
|
}
|
|
|
|
static float bspline_interpolation(
|
|
const float p0, const float p1, const float p2, const float p3, const float t)
|
|
{
|
|
const float t2 = t * t;
|
|
const float t3 = t2 * t;
|
|
|
|
const float b0 = (1.0f - t) * (1.0f - t) * (1.0f - t) / 6.0f;
|
|
const float b1 = (3.0f * t3 - 6.0f * t2 + 4.0f) / 6.0f;
|
|
const float b2 = (-3.0f * t3 + 3.0f * t2 + 3.0f * t + 1.0f) / 6.0f;
|
|
const float b3 = t3 / 6.0f;
|
|
|
|
return (b0 * p0 + b1 * p1 + b2 * p2 + b3 * p3);
|
|
}
|
|
|
|
float bSoundFrequencySampler::sample(const float time,
|
|
const float low,
|
|
const float high,
|
|
const InterpolationMethod time_interpolation,
|
|
const InterpolationMethod frequency_interpolation) const
|
|
{
|
|
if (low >= high) {
|
|
return 0.0f;
|
|
}
|
|
const float i_float = std::max(
|
|
0.0f, (time * samples_per_second_ - key_.fft_size / 2) / window_cache_stride_);
|
|
const int i_pre = floorf(i_float);
|
|
const int i_post = i_pre + 1;
|
|
const float t = fractf(i_float);
|
|
|
|
switch (time_interpolation) {
|
|
case InterpolationMethod::Linear: {
|
|
const float v_prev = this->sample_frequency_range_in_window(
|
|
i_pre, low, high, frequency_interpolation);
|
|
const float v_next = this->sample_frequency_range_in_window(
|
|
i_post, low, high, frequency_interpolation);
|
|
|
|
return math::interpolate(v_prev, v_next, t);
|
|
}
|
|
case InterpolationMethod::CatmullRom: {
|
|
const int i_pre2 = i_pre - 1;
|
|
const int i_post2 = i_post + 1;
|
|
|
|
const float p0 = this->sample_frequency_range_in_window(
|
|
i_pre2, low, high, frequency_interpolation);
|
|
const float p1 = this->sample_frequency_range_in_window(
|
|
i_pre, low, high, frequency_interpolation);
|
|
const float p2 = this->sample_frequency_range_in_window(
|
|
i_post, low, high, frequency_interpolation);
|
|
const float p3 = this->sample_frequency_range_in_window(
|
|
i_post2, low, high, frequency_interpolation);
|
|
|
|
return catmul_rom_interpolation(p0, p1, p2, p3, t);
|
|
}
|
|
default:
|
|
case InterpolationMethod::BSpline: {
|
|
const int i_pre2 = i_pre - 1;
|
|
const int i_post2 = i_post + 1;
|
|
|
|
const float p0 = this->sample_frequency_range_in_window(
|
|
i_pre2, low, high, frequency_interpolation);
|
|
const float p1 = this->sample_frequency_range_in_window(
|
|
i_pre, low, high, frequency_interpolation);
|
|
const float p2 = this->sample_frequency_range_in_window(
|
|
i_post, low, high, frequency_interpolation);
|
|
const float p3 = this->sample_frequency_range_in_window(
|
|
i_post2, low, high, frequency_interpolation);
|
|
|
|
return bspline_interpolation(p0, p1, p2, p3, t);
|
|
}
|
|
}
|
|
}
|
|
|
|
float bSoundFrequencySampler::sample_frequency_range_in_window(int window_i,
|
|
float low,
|
|
float high,
|
|
InterpolationMethod method) const
|
|
{
|
|
const std::optional<Span<float>> window_opt = this->ensure_window_cache(window_i);
|
|
if (!window_opt.has_value()) {
|
|
return 0.0f;
|
|
}
|
|
const Span<float> window_values = *window_opt;
|
|
const float cumulative_low = this->sample_cumulative_frequency(window_values, low, method);
|
|
const float cumulative_high = this->sample_cumulative_frequency(window_values, high, method);
|
|
return cumulative_high - cumulative_low;
|
|
}
|
|
|
|
float bSoundFrequencySampler::sample_cumulative_frequency(const Span<float> window_values,
|
|
const float frequency,
|
|
const InterpolationMethod method) const
|
|
{
|
|
const int max_i = window_values.size() - 1;
|
|
const float i_float = frequency * key_.fft_size / samples_per_second_;
|
|
const int i_pre = std::clamp<int>(std::floor(i_float), 0, max_i);
|
|
const int i_post = std::min(i_pre + 1, max_i);
|
|
const float t = fractf(i_float);
|
|
|
|
switch (method) {
|
|
case InterpolationMethod::Linear: {
|
|
return math::interpolate(window_values[i_pre], window_values[i_post], t);
|
|
}
|
|
case InterpolationMethod::CatmullRom: {
|
|
const int i_pre2 = std::max(i_pre - 1, 0);
|
|
const int i_post2 = std::min(i_post + 1, max_i);
|
|
|
|
const float p0 = window_values[i_pre2];
|
|
const float p1 = window_values[i_pre];
|
|
const float p2 = window_values[i_post];
|
|
const float p3 = window_values[i_post2];
|
|
|
|
return catmul_rom_interpolation(p0, p1, p2, p3, t);
|
|
}
|
|
case InterpolationMethod::BSpline: {
|
|
const int i_pre2 = std::max(i_pre - 1, 0);
|
|
const int i_post2 = std::min(i_post + 1, max_i);
|
|
|
|
const float p0 = window_values[i_pre2];
|
|
const float p1 = window_values[i_pre];
|
|
const float p2 = window_values[i_post];
|
|
const float p3 = window_values[i_post2];
|
|
|
|
return bspline_interpolation(p0, p1, p2, p3, t);
|
|
}
|
|
}
|
|
return 0.0f;
|
|
}
|
|
|
|
std::optional<Span<float>> bSoundFrequencySampler::ensure_window_cache(int window_i) const
|
|
{
|
|
/* Clamp window so that too low or high indices get mapped to the closest valid index. */
|
|
window_i = std::clamp<int>(window_i, 0, window_caches_.size() - 1);
|
|
|
|
const WindowCache &window = window_caches_[window_i];
|
|
/* Compute the FFT of that window if that wasn't done already. */
|
|
window.mutex.ensure([&]() {
|
|
std::optional<Array<float>> fft_array = this->compute_fft(window_i * window_cache_stride_);
|
|
if (!fft_array.has_value()) {
|
|
return;
|
|
}
|
|
/* Compute prefix sum for the fft values. */
|
|
const Span<float> fft_values = fft_array->as_span();
|
|
window.cumulative_amplitudes.emplace(fft_values.size() + 1);
|
|
float sum = 0.0f;
|
|
for (const int i : fft_array->index_range()) {
|
|
const float value = std::abs(fft_values[i]);
|
|
(*window.cumulative_amplitudes)[i] = sum;
|
|
sum += value;
|
|
}
|
|
window.cumulative_amplitudes->last() = sum;
|
|
});
|
|
return window.cumulative_amplitudes;
|
|
}
|
|
|
|
} // namespace bke
|
|
|
|
} // namespace blender
|