diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h index 54d66cd7d9e..0a31c6e8d79 100644 --- a/source/blender/blenkernel/BKE_blender_version.h +++ b/source/blender/blenkernel/BKE_blender_version.h @@ -27,7 +27,7 @@ /* Blender file format version. */ #define BLENDER_FILE_VERSION BLENDER_VERSION -#define BLENDER_FILE_SUBVERSION 38 +#define BLENDER_FILE_SUBVERSION 39 /* Minimum Blender version that supports reading file written with the current * version. Older Blender versions will test this and cancel loading the file, showing a warning to diff --git a/source/blender/blenloader/intern/versioning_500.cc b/source/blender/blenloader/intern/versioning_500.cc index e8f4cdc70a1..d1c28be01ed 100644 --- a/source/blender/blenloader/intern/versioning_500.cc +++ b/source/blender/blenloader/intern/versioning_500.cc @@ -47,6 +47,8 @@ #include "BLO_read_write.hh" #include "SEQ_iterator.hh" +#include "SEQ_modifier.hh" +#include "SEQ_sequencer.hh" #include "readfile.hh" @@ -1499,6 +1501,21 @@ void blo_do_versions_500(FileData * /*fd*/, Library * /*lib*/, Main *bmain) FOREACH_NODETREE_END; } + if (!MAIN_VERSION_FILE_ATLEAST(bmain, 500, 39)) { + LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { + Editing *ed = seq::editing_get(scene); + + if (ed != nullptr) { + seq::for_each_callback(&ed->seqbase, [](Strip *strip) -> bool { + LISTBASE_FOREACH (StripModifierData *, smd, &strip->modifiers) { + seq::modifier_persistent_uid_init(*strip, *smd); + } + return true; + }); + } + } + } + /** * Always bump subversion in BKE_blender_version.h when adding versioning * code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check. diff --git a/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_sequence.cc b/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_sequence.cc index 9ecae874e14..c2c1b758934 100644 --- a/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_sequence.cc +++ b/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_sequence.cc @@ -14,6 +14,46 @@ namespace blender::deg { +StripModifierDataBackup::StripModifierDataBackup() +{ + reset(); +} + +void StripModifierDataBackup::reset() +{ + sound_in = nullptr; + sound_out = nullptr; + last_buf = nullptr; +} + +void StripModifierDataBackup::init_from_modifier(StripModifierData *smd) +{ + if (smd->type == seqModifierType_SoundEqualizer) { + sound_in = smd->runtime.last_sound_in; + sound_out = smd->runtime.last_sound_out; + last_buf = smd->runtime.last_buf; + + smd->runtime.last_sound_in = nullptr; + smd->runtime.last_sound_out = nullptr; + smd->runtime.last_buf = nullptr; + } +} + +void StripModifierDataBackup::restore_to_modifier(StripModifierData *smd) +{ + if (smd->type == seqModifierType_SoundEqualizer) { + smd->runtime.last_sound_in = sound_in; + smd->runtime.last_sound_out = sound_out; + smd->runtime.last_buf = last_buf; + } + reset(); +} + +bool StripModifierDataBackup::isEmpty() const +{ + return sound_in == nullptr && sound_out == nullptr && last_buf == nullptr; +} + StripBackup::StripBackup(const Depsgraph * /*depsgraph*/) { reset(); @@ -23,6 +63,7 @@ void StripBackup::reset() { scene_sound = nullptr; BLI_listbase_clear(&anims); + modifiers.clear(); } void StripBackup::init_from_strip(Strip *strip) @@ -30,6 +71,14 @@ void StripBackup::init_from_strip(Strip *strip) scene_sound = strip->scene_sound; anims = strip->anims; + LISTBASE_FOREACH (StripModifierData *, smd, &strip->modifiers) { + StripModifierDataBackup mod_backup; + mod_backup.init_from_modifier(smd); + if (!mod_backup.isEmpty()) { + modifiers.add(smd->persistent_uid, mod_backup); + } + } + strip->scene_sound = nullptr; BLI_listbase_clear(&strip->anims); } @@ -38,12 +87,20 @@ void StripBackup::restore_to_strip(Strip *strip) { strip->scene_sound = scene_sound; strip->anims = anims; + + LISTBASE_FOREACH (StripModifierData *, smd, &strip->modifiers) { + std::optional backup = modifiers.pop_try(smd->persistent_uid); + if (backup.has_value()) { + backup->restore_to_modifier(smd); + } + } + reset(); } bool StripBackup::isEmpty() const { - return (scene_sound == nullptr) && BLI_listbase_is_empty(&anims); + return (scene_sound == nullptr) && BLI_listbase_is_empty(&anims) && modifiers.is_empty(); } } // namespace blender::deg diff --git a/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_sequence.h b/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_sequence.h index bc52584d24d..e177403086c 100644 --- a/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_sequence.h +++ b/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_sequence.h @@ -10,12 +10,31 @@ #include "DNA_listBase.h" +#include "BLI_map.hh" + struct Strip; +struct StripModifierData; namespace blender::deg { struct Depsgraph; +class StripModifierDataBackup { + public: + StripModifierDataBackup(); + + void reset(); + + void init_from_modifier(StripModifierData *smd); + void restore_to_modifier(StripModifierData *smd); + + bool isEmpty() const; + + void *sound_in; + void *sound_out; + float *last_buf; +}; + /* Backup of a single strip. */ class StripBackup { public: @@ -30,6 +49,7 @@ class StripBackup { void *scene_sound; ListBase anims; + Map modifiers; }; } // namespace blender::deg diff --git a/source/blender/editors/space_sequencer/sequencer_modifier.cc b/source/blender/editors/space_sequencer/sequencer_modifier.cc index 929cb46fbfe..f863a076f72 100644 --- a/source/blender/editors/space_sequencer/sequencer_modifier.cc +++ b/source/blender/editors/space_sequencer/sequencer_modifier.cc @@ -42,7 +42,8 @@ static wmOperatorStatus strip_modifier_add_exec(bContext *C, wmOperator *op) Strip *strip = seq::select_active_get(scene); int type = RNA_enum_get(op->ptr, "type"); - seq::modifier_new(strip, nullptr, type); + StripModifierData *smd = seq::modifier_new(strip, nullptr, type); + seq::modifier_persistent_uid_init(*strip, *smd); seq::relations_invalidate_cache(scene, strip); WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene); @@ -241,52 +242,52 @@ enum { static wmOperatorStatus strip_modifier_copy_exec(bContext *C, wmOperator *op) { Scene *scene = CTX_data_sequencer_scene(C); - Editing *ed = scene->ed; - Strip *strip = seq::select_active_get(scene); + Strip *active_strip = seq::select_active_get(scene); const int type = RNA_enum_get(op->ptr, "type"); - if (!strip || !strip->modifiers.first) { + if (!active_strip || !active_strip->modifiers.first) { return OPERATOR_CANCELLED; } - int isSound = ELEM(strip->type, STRIP_TYPE_SOUND_RAM); + int isSound = ELEM(active_strip->type, STRIP_TYPE_SOUND_RAM); - LISTBASE_FOREACH (Strip *, strip_iter, seq::active_seqbase_get(ed)) { - if (strip_iter->flag & SELECT) { - if (strip_iter == strip) { - continue; - } - int strip_iter_is_sound = ELEM(strip_iter->type, STRIP_TYPE_SOUND_RAM); - /* If original is sound, only copy to "sound" strips - * If original is not sound, only copy to "not sound" strips - */ - if (isSound != strip_iter_is_sound) { - continue; - } + VectorSet selected = selected_strips_from_context(C); + selected.remove(active_strip); - if (type == SEQ_MODIFIER_COPY_REPLACE) { - if (strip_iter->modifiers.first) { - StripModifierData *smd_tmp, - *smd = static_cast(strip_iter->modifiers.first); - while (smd) { - smd_tmp = smd->next; - BLI_remlink(&strip_iter->modifiers, smd); - seq::modifier_free(smd); - smd = smd_tmp; - } - BLI_listbase_clear(&strip_iter->modifiers); + for (Strip *strip_iter : selected) { + int strip_iter_is_sound = ELEM(strip_iter->type, STRIP_TYPE_SOUND_RAM); + /* If original is sound, only copy to "sound" strips + * If original is not sound, only copy to "not sound" strips + */ + if (isSound != strip_iter_is_sound) { + continue; + } + + if (type == SEQ_MODIFIER_COPY_REPLACE) { + if (strip_iter->modifiers.first) { + StripModifierData *smd_tmp, + *smd = static_cast(strip_iter->modifiers.first); + while (smd) { + smd_tmp = smd->next; + BLI_remlink(&strip_iter->modifiers, smd); + seq::modifier_free(smd); + smd = smd_tmp; } + BLI_listbase_clear(&strip_iter->modifiers); } + } - seq::modifier_list_copy(strip_iter, strip); + LISTBASE_FOREACH (StripModifierData *, smd, &active_strip->modifiers) { + StripModifierData *smd_new = seq::modifier_copy(*strip_iter, smd); + seq::modifier_persistent_uid_init(*strip_iter, *smd_new); } } - if (ELEM(strip->type, STRIP_TYPE_SOUND_RAM)) { + if (ELEM(active_strip->type, STRIP_TYPE_SOUND_RAM)) { DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS | ID_RECALC_AUDIO); } else { - seq::relations_invalidate_cache(scene, strip); + seq::relations_invalidate_cache(scene, active_strip); } WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene); diff --git a/source/blender/makesdna/DNA_sequence_types.h b/source/blender/makesdna/DNA_sequence_types.h index 816515e64cf..8e77ba204c8 100644 --- a/source/blender/makesdna/DNA_sequence_types.h +++ b/source/blender/makesdna/DNA_sequence_types.h @@ -513,6 +513,18 @@ typedef struct ColorMixVars { /** \name Strip Modifiers * \{ */ +typedef struct StripModifierDataRuntime { + /* Reference parameters for optimizing updates. Sound modifiers can store parameters, sound + * inputs and outputs. When all existing parameters do match new ones, the update can be skipped + * and old sound handle may be returned. This is to prevent audio glitches, see #141595 */ + + float *last_buf; /* Equalizer frequency/volume curve buffer */ + + /* Reference sound handles (may be used by any sound modifier). */ + void *last_sound_in; + void *last_sound_out; +} StripModifierDataRuntime; + typedef struct StripModifierData { struct StripModifierData *next, *prev; int type, flag; @@ -524,6 +536,11 @@ typedef struct StripModifierData { struct Strip *mask_strip; struct Mask *mask_id; + + int persistent_uid; + char _pad[4]; + + StripModifierDataRuntime runtime; } StripModifierData; typedef struct ColorBalanceModifierData { diff --git a/source/blender/makesrna/intern/rna_sequencer.cc b/source/blender/makesrna/intern/rna_sequencer.cc index 2851cb5cd01..2772c163ad8 100644 --- a/source/blender/makesrna/intern/rna_sequencer.cc +++ b/source/blender/makesrna/intern/rna_sequencer.cc @@ -1495,6 +1495,7 @@ static StripModifierData *rna_Strip_modifier_new( StripModifierData *smd; smd = blender::seq::modifier_new(strip, name, type); + blender::seq::modifier_persistent_uid_init(*strip, *smd); blender::seq::relations_invalidate_cache(scene, strip); diff --git a/source/blender/sequencer/SEQ_modifier.hh b/source/blender/sequencer/SEQ_modifier.hh index d70e9c23be6..097ad063e2c 100644 --- a/source/blender/sequencer/SEQ_modifier.hh +++ b/source/blender/sequencer/SEQ_modifier.hh @@ -58,10 +58,12 @@ void modifier_apply_stack(const RenderData *context, const Strip *strip, ImBuf *ibuf, int timeline_frame); +StripModifierData *modifier_copy(Strip &strip_dst, StripModifierData *mod_src); void modifier_list_copy(Strip *strip_new, Strip *strip); int sequence_supports_modifiers(Strip *strip); void modifier_blend_write(BlendWriter *writer, ListBase *modbase); void modifier_blend_read_data(BlendDataReader *reader, ListBase *lb); +void modifier_persistent_uid_init(const Strip &strip, StripModifierData &smd); } // namespace blender::seq diff --git a/source/blender/sequencer/SEQ_sound.hh b/source/blender/sequencer/SEQ_sound.hh index d71a3667098..d9f50ffa608 100644 --- a/source/blender/sequencer/SEQ_sound.hh +++ b/source/blender/sequencer/SEQ_sound.hh @@ -23,7 +23,7 @@ namespace blender::seq { struct SoundModifierWorkerInfo { int type; - void *(*recreator)(Strip *strip, StripModifierData *smd, void *sound); + void *(*recreator)(Strip *strip, StripModifierData *smd, void *sound, bool &needs_update); }; #define SOUND_EQUALIZER_DEFAULT_MIN_FREQ 30.0 @@ -41,12 +41,18 @@ EQCurveMappingData *sound_equalizer_add(SoundEqualizerModifierData *semd, float void sound_blend_write(BlendWriter *writer, ListBase *soundbase); void sound_blend_read_data(BlendDataReader *reader, ListBase *lb); -void *sound_modifier_recreator(Strip *strip, StripModifierData *smd, void *sound); +void *sound_modifier_recreator(Strip *strip, + StripModifierData *smd, + void *sound, + bool &needs_update); void sound_equalizermodifier_init_data(StripModifierData *smd); void sound_equalizermodifier_free(StripModifierData *smd); void sound_equalizermodifier_copy_data(StripModifierData *target, StripModifierData *smd); -void *sound_equalizermodifier_recreator(Strip *strip, StripModifierData *smd, void *sound); +void *sound_equalizermodifier_recreator(Strip *strip, + StripModifierData *smd, + void *sound, + bool &needs_update); void sound_equalizermodifier_set_graphs(SoundEqualizerModifierData *semd, int number); const SoundModifierWorkerInfo *sound_modifier_worker_info_get(int type); EQCurveMappingData *sound_equalizermodifier_add_graph(SoundEqualizerModifierData *semd, diff --git a/source/blender/sequencer/intern/modifier.cc b/source/blender/sequencer/intern/modifier.cc index 4da37f58c35..65c6a14fdcf 100644 --- a/source/blender/sequencer/intern/modifier.cc +++ b/source/blender/sequencer/intern/modifier.cc @@ -11,9 +11,12 @@ #include #include "BLI_array.hh" +#include "BLI_hash.hh" #include "BLI_listbase.h" #include "BLI_math_geom.h" #include "BLI_math_vector.hh" +#include "BLI_rand.hh" +#include "BLI_set.hh" #include "BLI_string.h" #include "BLI_string_utils.hh" #include "BLI_task.hh" @@ -37,12 +40,57 @@ #include "BLO_read_write.hh" +#include "modifier.hh" #include "render.hh" namespace blender::seq { /* -------------------------------------------------------------------- */ +static bool modifier_has_persistent_uid(const Strip &strip, int uid) +{ + LISTBASE_FOREACH (StripModifierData *, smd, &strip.modifiers) { + if (smd->persistent_uid == uid) { + return true; + } + } + return false; +} + +void modifier_persistent_uid_init(const Strip &strip, StripModifierData &smd) +{ + uint64_t hash = blender::get_default_hash(blender::StringRef(smd.name)); + blender::RandomNumberGenerator rng{uint32_t(hash)}; + while (true) { + const int new_uid = rng.get_int32(); + if (new_uid <= 0) { + continue; + } + if (modifier_has_persistent_uid(strip, new_uid)) { + continue; + } + smd.persistent_uid = new_uid; + break; + } +} + +bool modifier_persistent_uids_are_valid(const Strip &strip) +{ + Set uids; + int modifiers_num = 0; + LISTBASE_FOREACH (StripModifierData *, smd, &strip.modifiers) { + if (smd->persistent_uid <= 0) { + return false; + } + uids.add(smd->persistent_uid); + modifiers_num++; + } + if (uids.size() != modifiers_num) { + return false; + } + return true; +} + static float4 load_pixel_premul(const uchar *ptr) { float4 res; @@ -1341,25 +1389,29 @@ void modifier_apply_stack(const RenderData *context, } } +StripModifierData *modifier_copy(Strip &strip_dst, StripModifierData *mod_src) +{ + const StripModifierTypeInfo *smti = modifier_type_info_get(mod_src->type); + StripModifierData *mod_new = static_cast(MEM_dupallocN(mod_src)); + + if (smti && smti->copy_data) { + smti->copy_data(mod_new, mod_src); + } + + BLI_addtail(&strip_dst.modifiers, mod_new); + BLI_uniquename(&strip_dst.modifiers, + mod_new, + "Strip Modifier", + '.', + offsetof(StripModifierData, name), + sizeof(StripModifierData::name)); + return mod_new; +} + void modifier_list_copy(Strip *strip_new, Strip *strip) { LISTBASE_FOREACH (StripModifierData *, smd, &strip->modifiers) { - StripModifierData *smdn; - const StripModifierTypeInfo *smti = modifier_type_info_get(smd->type); - - smdn = static_cast(MEM_dupallocN(smd)); - - if (smti && smti->copy_data) { - smti->copy_data(smdn, smd); - } - - BLI_addtail(&strip_new->modifiers, smdn); - BLI_uniquename(&strip_new->modifiers, - smdn, - "Strip Modifier", - '.', - offsetof(StripModifierData, name), - sizeof(StripModifierData::name)); + modifier_copy(*strip_new, smd); } } diff --git a/source/blender/sequencer/intern/modifier.hh b/source/blender/sequencer/intern/modifier.hh new file mode 100644 index 00000000000..10f65068738 --- /dev/null +++ b/source/blender/sequencer/intern/modifier.hh @@ -0,0 +1,17 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +/** \file + * \ingroup sequencer + */ + +struct Strip; + +namespace blender::seq { + +bool modifier_persistent_uids_are_valid(const Strip &strip); + +} // namespace blender::seq diff --git a/source/blender/sequencer/intern/sequencer.cc b/source/blender/sequencer/intern/sequencer.cc index b8e4b336ff0..67b4cf56829 100644 --- a/source/blender/sequencer/intern/sequencer.cc +++ b/source/blender/sequencer/intern/sequencer.cc @@ -59,6 +59,7 @@ #include "cache/final_image_cache.hh" #include "cache/intra_frame_cache.hh" #include "cache/source_image_cache.hh" +#include "modifier.hh" #include "prefetch.hh" #include "sequencer.hh" #include "utils.hh" @@ -548,6 +549,7 @@ static Strip *strip_duplicate(const Scene *scene_src, modifier_list_copy(strip_new, strip); } + BLI_assert(modifier_persistent_uids_are_valid(*strip)); if (is_strip_connected(strip)) { BLI_listbase_clear(&strip_new->connections); @@ -1026,14 +1028,16 @@ static void strip_update_sound_properties(const Scene *scene, const Strip *strip static void strip_update_sound_modifiers(Strip *strip) { void *sound_handle = strip->sound->playback_handle; - if (!BLI_listbase_is_empty(&strip->modifiers)) { - LISTBASE_FOREACH (StripModifierData *, smd, &strip->modifiers) { - sound_handle = sound_modifier_recreator(strip, smd, sound_handle); - } + bool needs_update = false; + + LISTBASE_FOREACH (StripModifierData *, smd, &strip->modifiers) { + sound_handle = sound_modifier_recreator(strip, smd, sound_handle, needs_update); } - /* Assign modified sound back to `strip`. */ - BKE_sound_update_sequence_handle(strip->scene_sound, sound_handle); + if (needs_update) { + /* Assign modified sound back to `strip`. */ + BKE_sound_update_sequence_handle(strip->scene_sound, sound_handle); + } } static bool must_update_strip_sound(Scene *scene, Strip *strip) @@ -1047,9 +1051,16 @@ static void seq_update_sound_strips(Scene *scene, Strip *strip) if (strip->sound == nullptr || !must_update_strip_sound(scene, strip)) { return; } + /* Ensure strip is playing correct sound. */ - BKE_sound_update_scene_sound(strip->scene_sound, strip->sound); - strip_update_sound_modifiers(strip); + if (BLI_listbase_is_empty(&strip->modifiers)) { + /* Just use playback handle from sound ID. */ + BKE_sound_update_scene_sound(strip->scene_sound, strip->sound); + } + else { + /* Use Playback handle from sound ID as input for modifier stack. */ + strip_update_sound_modifiers(strip); + } } static bool scene_sequencer_is_used(const Scene *scene, ListBase *seqbase) diff --git a/source/blender/sequencer/intern/sound.cc b/source/blender/sequencer/intern/sound.cc index 352faba2542..900eb55eabc 100644 --- a/source/blender/sequencer/intern/sound.cc +++ b/source/blender/sequencer/intern/sound.cc @@ -245,6 +245,9 @@ void sound_equalizermodifier_free(StripModifierData *smd) MEM_freeN(eqcmd); } BLI_listbase_clear(&semd->graphics); + if (smd->runtime.last_buf) { + MEM_freeN(smd->runtime.last_buf); + } } void sound_equalizermodifier_copy_data(StripModifierData *target, StripModifierData *smd) @@ -264,7 +267,10 @@ void sound_equalizermodifier_copy_data(StripModifierData *target, StripModifierD } } -void *sound_equalizermodifier_recreator(Strip *strip, StripModifierData *smd, void *sound) +void *sound_equalizermodifier_recreator(Strip *strip, + StripModifierData *smd, + void *sound_in, + bool &needs_update) { #ifdef WITH_CONVOLUTION UNUSED_VARS(strip); @@ -273,7 +279,7 @@ void *sound_equalizermodifier_recreator(Strip *strip, StripModifierData *smd, vo /* No equalizer definition. */ if (BLI_listbase_is_empty(&semd->graphics)) { - return sound; + return sound_in; } float *buf = MEM_calloc_arrayN(SOUND_EQUALIZER_SIZE_DEFINITION, "eqrecreator"); @@ -311,15 +317,28 @@ void *sound_equalizermodifier_recreator(Strip *strip, StripModifierData *smd, vo } } - AUD_Sound *equ = AUD_Sound_equalize(sound, - buf, - SOUND_EQUALIZER_SIZE_DEFINITION, - SOUND_EQUALIZER_DEFAULT_MAX_FREQ, - SOUND_EQUALIZER_SIZE_CONVERSION); + /* Only make new sound when necessary. It is faster and it prevents audio glitches. */ + if (!needs_update && smd->runtime.last_sound_in == sound_in && + smd->runtime.last_buf != nullptr && + std::memcmp(buf, smd->runtime.last_buf, SOUND_EQUALIZER_SIZE_DEFINITION) == 0) + { + MEM_freeN(buf); + return smd->runtime.last_sound_out; + } - MEM_freeN(buf); + + AUD_Sound *sound_out = AUD_Sound_equalize(sound_in, + buf, + SOUND_EQUALIZER_SIZE_DEFINITION, + SOUND_EQUALIZER_DEFAULT_MAX_FREQ, + SOUND_EQUALIZER_SIZE_CONVERSION); - return equ; + needs_update = true; + smd->runtime.last_buf = buf; + smd->runtime.last_sound_in = sound_in; + smd->runtime.last_sound_out = sound_out; + + return sound_out; #else UNUSED_VARS(strip, smd, sound); return nullptr; @@ -336,12 +355,15 @@ const SoundModifierWorkerInfo *sound_modifier_worker_info_get(int type) return nullptr; } -void *sound_modifier_recreator(Strip *strip, StripModifierData *smd, void *sound) +void *sound_modifier_recreator(Strip *strip, + StripModifierData *smd, + void *sound, + bool &needs_update) { if (!(smd->flag & SEQUENCE_MODIFIER_MUTE)) { const SoundModifierWorkerInfo *smwi = sound_modifier_worker_info_get(smd->type); - return smwi->recreator(strip, smd, sound); + return smwi->recreator(strip, smd, sound, needs_update); } return sound; }