From a40fe168667cbb6f9542d421b5c8e4c0a83ad1e4 Mon Sep 17 00:00:00 2001 From: Juan Linietsky Date: Sat, 23 Dec 2023 17:30:32 +0100 Subject: [PATCH] Implement audio stream playback parameters. Implements a way for audio stream playback to be configured via parameters directly in the edited AudioStreamPlayer[2D/3D]. Currently, configuring the playback stream is not possible (or is sometimes hacky as the user has to obtain the currently played stream, which is not always immediately available). This PR only implements this new feature to control looping in stream playback instances (a commonly requested feature, which was lost in the transition from Godot 2 to Godot 3). But the idea is that it can do a lot more: * If effects are bundled to the stream, control per playback instance parameters such as cutoff or resoance, or any other exposed effect parameter per playback instance. * For the upcoming interactive music PR (#64488), this exposes an easy way to change the active clip, which was not possible before. * For the upcoming parametrizable audio support (https://github.com/godotengine/godot-proposals/issues/3394) this allows editing and animating audio graph parameters. In any case, this PR is required to complete #64488. Update modules/vorbis/audio_stream_ogg_vorbis.h Co-authored-by: A Thousand Ships <96648715+AThousandShips@users.noreply.github.com> Update modules/minimp3/audio_stream_mp3.h Co-authored-by: A Thousand Ships <96648715+AThousandShips@users.noreply.github.com> Update modules/minimp3/audio_stream_mp3.h Co-authored-by: A Thousand Ships <96648715+AThousandShips@users.noreply.github.com> Update modules/vorbis/audio_stream_ogg_vorbis.h Co-authored-by: A Thousand Ships <96648715+AThousandShips@users.noreply.github.com> Update doc/classes/AudioStream.xml Co-authored-by: A Thousand Ships <96648715+AThousandShips@users.noreply.github.com> --- doc/classes/AudioStream.xml | 13 ++++ doc/classes/AudioStreamPlayback.xml | 15 +++++ modules/minimp3/audio_stream_mp3.cpp | 29 ++++++++- modules/minimp3/audio_stream_mp3.h | 7 +++ modules/vorbis/audio_stream_ogg_vorbis.cpp | 32 ++++++++-- modules/vorbis/audio_stream_ogg_vorbis.h | 7 +++ scene/2d/audio_stream_player_2d.cpp | 69 ++++++++++++++++++++++ scene/2d/audio_stream_player_2d.h | 12 ++++ scene/3d/audio_stream_player_3d.cpp | 68 +++++++++++++++++++++ scene/3d/audio_stream_player_3d.h | 12 ++++ scene/audio/audio_stream_player.cpp | 68 +++++++++++++++++++++ scene/audio/audio_stream_player.h | 12 ++++ servers/audio/audio_stream.cpp | 25 ++++++++ servers/audio/audio_stream.h | 19 ++++++ 14 files changed, 382 insertions(+), 6 deletions(-) diff --git a/doc/classes/AudioStream.xml b/doc/classes/AudioStream.xml index 12e09b235fe6..6e30775fee1e 100644 --- a/doc/classes/AudioStream.xml +++ b/doc/classes/AudioStream.xml @@ -28,6 +28,12 @@ + + + + Return the controllable parameters of this stream. This array contains dictionaries with a property info description format (see [method Object.get_property_list]). Additionally, the default value for this parameter must be added tho each dictionary in "default_value" field. + + @@ -62,4 +68,11 @@ + + + + Signal to be emitted to notify when the parameter list changed. + + + diff --git a/doc/classes/AudioStreamPlayback.xml b/doc/classes/AudioStreamPlayback.xml index 7692690b5ed7..a090989194cf 100644 --- a/doc/classes/AudioStreamPlayback.xml +++ b/doc/classes/AudioStreamPlayback.xml @@ -15,6 +15,13 @@ + + + + + Return the current value of a playback parameter by name (see [method AudioStream._get_parameter_list]). + + @@ -39,6 +46,14 @@ + + + + + + Set the current value of a playback parameter by name (see [method AudioStream._get_parameter_list]). + + diff --git a/modules/minimp3/audio_stream_mp3.cpp b/modules/minimp3/audio_stream_mp3.cpp index 4efa4d329ea0..a46a1c93b52c 100644 --- a/modules/minimp3/audio_stream_mp3.cpp +++ b/modules/minimp3/audio_stream_mp3.cpp @@ -46,7 +46,9 @@ int AudioStreamPlaybackMP3::_mix_internal(AudioFrame *p_buffer, int p_frames) { int frames_mixed_this_step = p_frames; int beat_length_frames = -1; - bool beat_loop = mp3_stream->has_loop() && mp3_stream->get_bpm() > 0 && mp3_stream->get_beat_count() > 0; + bool use_loop = looping_override ? looping : mp3_stream->loop; + + bool beat_loop = use_loop && mp3_stream->get_bpm() > 0 && mp3_stream->get_beat_count() > 0; if (beat_loop) { beat_length_frames = mp3_stream->get_beat_count() * mp3_stream->sample_rate * 60 / mp3_stream->get_bpm(); } @@ -82,7 +84,7 @@ int AudioStreamPlaybackMP3::_mix_internal(AudioFrame *p_buffer, int p_frames) { else { //EOF - if (mp3_stream->loop) { + if (use_loop) { seek(mp3_stream->loop_offset); loops++; } else { @@ -143,6 +145,25 @@ void AudioStreamPlaybackMP3::tag_used_streams() { mp3_stream->tag_used(get_playback_position()); } +void AudioStreamPlaybackMP3::set_parameter(const StringName &p_name, const Variant &p_value) { + if (p_name == SNAME("looping")) { + if (p_value == Variant()) { + looping_override = false; + looping = false; + } else { + looping_override = true; + looping = p_value; + } + } +} + +Variant AudioStreamPlaybackMP3::get_parameter(const StringName &p_name) const { + if (looping_override && p_name == SNAME("looping")) { + return looping; + } + return Variant(); +} + AudioStreamPlaybackMP3::~AudioStreamPlaybackMP3() { if (mp3d) { mp3dec_ex_close(mp3d); @@ -232,6 +253,10 @@ bool AudioStreamMP3::is_monophonic() const { return false; } +void AudioStreamMP3::get_parameter_list(List *r_parameters) { + r_parameters->push_back(Parameter(PropertyInfo(Variant::BOOL, "looping", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_CHECKABLE), Variant())); +} + void AudioStreamMP3::set_bpm(double p_bpm) { ERR_FAIL_COND(p_bpm < 0); bpm = p_bpm; diff --git a/modules/minimp3/audio_stream_mp3.h b/modules/minimp3/audio_stream_mp3.h index 30760703e376..7d85e0a3219d 100644 --- a/modules/minimp3/audio_stream_mp3.h +++ b/modules/minimp3/audio_stream_mp3.h @@ -47,6 +47,8 @@ class AudioStreamPlaybackMP3 : public AudioStreamPlaybackResampled { AudioFrame loop_fade[FADE_SIZE]; int loop_fade_remaining = FADE_SIZE; + bool looping_override = false; + bool looping = false; mp3dec_ex_t *mp3d = nullptr; uint32_t frames_mixed = 0; bool active = false; @@ -72,6 +74,9 @@ public: virtual void tag_used_streams() override; + virtual void set_parameter(const StringName &p_name, const Variant &p_value) override; + virtual Variant get_parameter(const StringName &p_name) const override; + AudioStreamPlaybackMP3() {} ~AudioStreamPlaybackMP3(); }; @@ -126,6 +131,8 @@ public: virtual bool is_monophonic() const override; + virtual void get_parameter_list(List *r_parameters) override; + AudioStreamMP3(); virtual ~AudioStreamMP3(); }; diff --git a/modules/vorbis/audio_stream_ogg_vorbis.cpp b/modules/vorbis/audio_stream_ogg_vorbis.cpp index 7ec0b697bf7c..e6003f35df0c 100644 --- a/modules/vorbis/audio_stream_ogg_vorbis.cpp +++ b/modules/vorbis/audio_stream_ogg_vorbis.cpp @@ -46,8 +46,9 @@ int AudioStreamPlaybackOggVorbis::_mix_internal(AudioFrame *p_buffer, int p_fram int todo = p_frames; int beat_length_frames = -1; - bool beat_loop = vorbis_stream->has_loop(); - if (beat_loop && vorbis_stream->get_bpm() > 0 && vorbis_stream->get_beat_count() > 0) { + bool use_loop = looping_override ? looping : vorbis_stream->loop; + + if (use_loop && vorbis_stream->get_bpm() > 0 && vorbis_stream->get_beat_count() > 0) { beat_length_frames = vorbis_stream->get_beat_count() * vorbis_data->get_sampling_rate() * 60 / vorbis_stream->get_bpm(); } @@ -99,7 +100,7 @@ int AudioStreamPlaybackOggVorbis::_mix_internal(AudioFrame *p_buffer, int p_fram } else **/ - if (beat_loop && beat_length_frames <= (int)frames_mixed) { + if (use_loop && beat_length_frames <= (int)frames_mixed) { // End of file when doing beat-based looping. <= used instead of == because importer editing if (!have_packets_left && !have_samples_left) { //Nothing remaining, so do nothing. @@ -125,7 +126,7 @@ int AudioStreamPlaybackOggVorbis::_mix_internal(AudioFrame *p_buffer, int p_fram if (!have_packets_left && !have_samples_left) { // Actual end of file! bool is_not_empty = mixed > 0 || vorbis_stream->get_length() > 0; - if (vorbis_stream->loop && is_not_empty) { + if (use_loop && is_not_empty) { //loop seek(vorbis_stream->loop_offset); @@ -257,6 +258,25 @@ void AudioStreamPlaybackOggVorbis::tag_used_streams() { vorbis_stream->tag_used(get_playback_position()); } +void AudioStreamPlaybackOggVorbis::set_parameter(const StringName &p_name, const Variant &p_value) { + if (p_name == SNAME("looping")) { + if (p_value == Variant()) { + looping_override = false; + looping = false; + } else { + looping_override = true; + looping = p_value; + } + } +} + +Variant AudioStreamPlaybackOggVorbis::get_parameter(const StringName &p_name) const { + if (looping_override && p_name == SNAME("looping")) { + return looping; + } + return Variant(); +} + void AudioStreamPlaybackOggVorbis::seek(double p_time) { ERR_FAIL_COND(!ready); ERR_FAIL_COND(vorbis_stream.is_null()); @@ -493,6 +513,10 @@ bool AudioStreamOggVorbis::is_monophonic() const { return false; } +void AudioStreamOggVorbis::get_parameter_list(List *r_parameters) { + r_parameters->push_back(Parameter(PropertyInfo(Variant::BOOL, "looping", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_CHECKABLE), Variant())); +} + void AudioStreamOggVorbis::_bind_methods() { ClassDB::bind_static_method("AudioStreamOggVorbis", D_METHOD("load_from_buffer", "buffer"), &AudioStreamOggVorbis::load_from_buffer); ClassDB::bind_static_method("AudioStreamOggVorbis", D_METHOD("load_from_file", "path"), &AudioStreamOggVorbis::load_from_file); diff --git a/modules/vorbis/audio_stream_ogg_vorbis.h b/modules/vorbis/audio_stream_ogg_vorbis.h index 6abaeaaebdf6..64a7815b578d 100644 --- a/modules/vorbis/audio_stream_ogg_vorbis.h +++ b/modules/vorbis/audio_stream_ogg_vorbis.h @@ -44,6 +44,8 @@ class AudioStreamPlaybackOggVorbis : public AudioStreamPlaybackResampled { uint32_t frames_mixed = 0; bool active = false; + bool looping_override = false; + bool looping = false; int loops = 0; enum { @@ -95,6 +97,9 @@ public: virtual void tag_used_streams() override; + virtual void set_parameter(const StringName &p_name, const Variant &p_value) override; + virtual Variant get_parameter(const StringName &p_name) const override; + AudioStreamPlaybackOggVorbis() {} ~AudioStreamPlaybackOggVorbis(); }; @@ -152,6 +157,8 @@ public: virtual bool is_monophonic() const override; + virtual void get_parameter_list(List *r_parameters) override; + AudioStreamOggVorbis(); virtual ~AudioStreamOggVorbis(); }; diff --git a/scene/2d/audio_stream_player_2d.cpp b/scene/2d/audio_stream_player_2d.cpp index afc5748ac59d..a99964c0e046 100644 --- a/scene/2d/audio_stream_player_2d.cpp +++ b/scene/2d/audio_stream_player_2d.cpp @@ -36,6 +36,8 @@ #include "scene/main/window.h" #include "scene/resources/world_2d.h" +#define PARAM_PREFIX "parameters/" + void AudioStreamPlayer2D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { @@ -222,9 +224,36 @@ void AudioStreamPlayer2D::_update_panning() { last_mix_count = AudioServer::get_singleton()->get_mix_count(); } +void AudioStreamPlayer2D::_update_stream_parameters() { + if (stream.is_null()) { + return; + } + + List parameters; + stream->get_parameter_list(¶meters); + for (const AudioStream::Parameter &K : parameters) { + const PropertyInfo &pi = K.property; + StringName key = PARAM_PREFIX + pi.name; + if (!playback_parameters.has(key)) { + ParameterData pd; + pd.path = pi.name; + pd.value = K.default_value; + playback_parameters.insert(key, pd); + } + } +} + void AudioStreamPlayer2D::set_stream(Ref p_stream) { + if (stream.is_valid()) { + stream->disconnect(SNAME("parameter_list_changed"), callable_mp(this, &AudioStreamPlayer2D::_update_stream_parameters)); + } stop(); stream = p_stream; + _update_stream_parameters(); + if (stream.is_valid()) { + stream->connect(SNAME("parameter_list_changed"), callable_mp(this, &AudioStreamPlayer2D::_update_stream_parameters)); + } + notify_property_list_changed(); } Ref AudioStreamPlayer2D::get_stream() const { @@ -262,6 +291,10 @@ void AudioStreamPlayer2D::play(float p_from_pos) { Ref stream_playback = stream->instantiate_playback(); ERR_FAIL_COND_MSG(stream_playback.is_null(), "Failed to instantiate playback."); + for (const KeyValue &K : playback_parameters) { + stream_playback->set_parameter(K.value.path, K.value.value); + } + stream_playbacks.push_back(stream_playback); setplayback = stream_playback; setplay.set(p_from_pos); @@ -430,6 +463,42 @@ void AudioStreamPlayer2D::_on_bus_renamed(int p_bus_index, const StringName &p_o notify_property_list_changed(); } +bool AudioStreamPlayer2D::_set(const StringName &p_name, const Variant &p_value) { + HashMap::Iterator I = playback_parameters.find(p_name); + if (!I) { + return false; + } + ParameterData &pd = I->value; + pd.value = p_value; + for (Ref &playback : stream_playbacks) { + playback->set_parameter(pd.path, pd.value); + } + return true; +} + +bool AudioStreamPlayer2D::_get(const StringName &p_name, Variant &r_ret) const { + HashMap::ConstIterator I = playback_parameters.find(p_name); + if (!I) { + return false; + } + + r_ret = I->value.value; + return true; +} + +void AudioStreamPlayer2D::_get_property_list(List *p_list) const { + if (stream.is_null()) { + return; + } + List parameters; + stream->get_parameter_list(¶meters); + for (const AudioStream::Parameter &K : parameters) { + PropertyInfo pi = K.property; + pi.name = PARAM_PREFIX + pi.name; + p_list->push_back(pi); + } +} + void AudioStreamPlayer2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_stream", "stream"), &AudioStreamPlayer2D::set_stream); ClassDB::bind_method(D_METHOD("get_stream"), &AudioStreamPlayer2D::get_stream); diff --git a/scene/2d/audio_stream_player_2d.h b/scene/2d/audio_stream_player_2d.h index 0766f2f77d4f..267d6a625b47 100644 --- a/scene/2d/audio_stream_player_2d.h +++ b/scene/2d/audio_stream_player_2d.h @@ -89,11 +89,23 @@ private: float panning_strength = 1.0f; float cached_global_panning_strength = 0.5f; + struct ParameterData { + StringName path; + Variant value; + }; + + HashMap playback_parameters; + void _update_stream_parameters(); + protected: void _validate_property(PropertyInfo &p_property) const; void _notification(int p_what); static void _bind_methods(); + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List *p_list) const; + public: void set_stream(Ref p_stream); Ref get_stream() const; diff --git a/scene/3d/audio_stream_player_3d.cpp b/scene/3d/audio_stream_player_3d.cpp index 3971e615a175..bfdbd14cc95c 100644 --- a/scene/3d/audio_stream_player_3d.cpp +++ b/scene/3d/audio_stream_player_3d.cpp @@ -37,6 +37,8 @@ #include "scene/main/viewport.h" #include "scene/scene_string_names.h" +#define PARAM_PREFIX "parameters/" + // Based on "A Novel Multichannel Panning Method for Standard and Arbitrary Loudspeaker Configurations" by Ramy Sadek and Chris Kyriakakis (2004) // Speaker-Placement Correction Amplitude Panning (SPCAP) class Spcap { @@ -525,9 +527,35 @@ Vector AudioStreamPlayer3D::_update_panning() { return output_volume_vector; } +void AudioStreamPlayer3D::_update_stream_parameters() { + if (stream.is_null()) { + return; + } + List parameters; + stream->get_parameter_list(¶meters); + for (const AudioStream::Parameter &K : parameters) { + const PropertyInfo &pi = K.property; + StringName key = PARAM_PREFIX + pi.name; + if (!playback_parameters.has(key)) { + ParameterData pd; + pd.path = pi.name; + pd.value = K.default_value; + playback_parameters.insert(key, pd); + } + } +} + void AudioStreamPlayer3D::set_stream(Ref p_stream) { + if (stream.is_valid()) { + stream->disconnect(SNAME("parameter_list_changed"), callable_mp(this, &AudioStreamPlayer3D::_update_stream_parameters)); + } stop(); stream = p_stream; + _update_stream_parameters(); + if (stream.is_valid()) { + stream->connect(SNAME("parameter_list_changed"), callable_mp(this, &AudioStreamPlayer3D::_update_stream_parameters)); + } + notify_property_list_changed(); } Ref AudioStreamPlayer3D::get_stream() const { @@ -579,6 +607,10 @@ void AudioStreamPlayer3D::play(float p_from_pos) { Ref stream_playback = stream->instantiate_playback(); ERR_FAIL_COND_MSG(stream_playback.is_null(), "Failed to instantiate playback."); + for (const KeyValue &K : playback_parameters) { + stream_playback->set_parameter(K.value.path, K.value.value); + } + stream_playbacks.push_back(stream_playback); setplayback = stream_playback; setplay.set(p_from_pos); @@ -818,6 +850,42 @@ void AudioStreamPlayer3D::_on_bus_renamed(int p_bus_index, const StringName &p_o notify_property_list_changed(); } +bool AudioStreamPlayer3D::_set(const StringName &p_name, const Variant &p_value) { + HashMap::Iterator I = playback_parameters.find(p_name); + if (!I) { + return false; + } + ParameterData &pd = I->value; + pd.value = p_value; + for (Ref &playback : stream_playbacks) { + playback->set_parameter(pd.path, pd.value); + } + return true; +} + +bool AudioStreamPlayer3D::_get(const StringName &p_name, Variant &r_ret) const { + HashMap::ConstIterator I = playback_parameters.find(p_name); + if (!I) { + return false; + } + + r_ret = I->value.value; + return true; +} + +void AudioStreamPlayer3D::_get_property_list(List *p_list) const { + if (stream.is_null()) { + return; + } + List parameters; + stream->get_parameter_list(¶meters); + for (const AudioStream::Parameter &K : parameters) { + PropertyInfo pi = K.property; + pi.name = PARAM_PREFIX + pi.name; + p_list->push_back(pi); + } +} + void AudioStreamPlayer3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_stream", "stream"), &AudioStreamPlayer3D::set_stream); ClassDB::bind_method(D_METHOD("get_stream"), &AudioStreamPlayer3D::get_stream); diff --git a/scene/3d/audio_stream_player_3d.h b/scene/3d/audio_stream_player_3d.h index f20170e63b62..facded1b9c42 100644 --- a/scene/3d/audio_stream_player_3d.h +++ b/scene/3d/audio_stream_player_3d.h @@ -121,11 +121,23 @@ private: float panning_strength = 1.0f; float cached_global_panning_strength = 0.5f; + struct ParameterData { + StringName path; + Variant value; + }; + + HashMap playback_parameters; + void _update_stream_parameters(); + protected: void _validate_property(PropertyInfo &p_property) const; void _notification(int p_what); static void _bind_methods(); + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List *p_list) const; + public: void set_stream(Ref p_stream); Ref get_stream() const; diff --git a/scene/audio/audio_stream_player.cpp b/scene/audio/audio_stream_player.cpp index f3c986f9b388..bd4731d8dd84 100644 --- a/scene/audio/audio_stream_player.cpp +++ b/scene/audio/audio_stream_player.cpp @@ -34,6 +34,8 @@ #include "core/math/audio_frame.h" #include "servers/audio_server.h" +#define PARAM_PREFIX "parameters/" + void AudioStreamPlayer::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { @@ -88,9 +90,71 @@ void AudioStreamPlayer::_notification(int p_what) { } } +void AudioStreamPlayer::_update_stream_parameters() { + if (stream.is_null()) { + return; + } + List parameters; + stream->get_parameter_list(¶meters); + for (const AudioStream::Parameter &K : parameters) { + const PropertyInfo &pi = K.property; + StringName key = PARAM_PREFIX + pi.name; + if (!playback_parameters.has(key)) { + ParameterData pd; + pd.path = pi.name; + pd.value = K.default_value; + playback_parameters.insert(key, pd); + } + } +} + void AudioStreamPlayer::set_stream(Ref p_stream) { + if (stream.is_valid()) { + stream->disconnect(SNAME("parameter_list_changed"), callable_mp(this, &AudioStreamPlayer::_update_stream_parameters)); + } stop(); stream = p_stream; + _update_stream_parameters(); + if (stream.is_valid()) { + stream->connect(SNAME("parameter_list_changed"), callable_mp(this, &AudioStreamPlayer::_update_stream_parameters)); + } + notify_property_list_changed(); +} + +bool AudioStreamPlayer::_set(const StringName &p_name, const Variant &p_value) { + HashMap::Iterator I = playback_parameters.find(p_name); + if (!I) { + return false; + } + ParameterData &pd = I->value; + pd.value = p_value; + for (Ref &playback : stream_playbacks) { + playback->set_parameter(pd.path, pd.value); + } + return true; +} + +bool AudioStreamPlayer::_get(const StringName &p_name, Variant &r_ret) const { + HashMap::ConstIterator I = playback_parameters.find(p_name); + if (!I) { + return false; + } + + r_ret = I->value.value; + return true; +} + +void AudioStreamPlayer::_get_property_list(List *p_list) const { + if (stream.is_null()) { + return; + } + List parameters; + stream->get_parameter_list(¶meters); + for (const AudioStream::Parameter &K : parameters) { + PropertyInfo pi = K.property; + pi.name = PARAM_PREFIX + pi.name; + p_list->push_back(pi); + } } Ref AudioStreamPlayer::get_stream() const { @@ -144,6 +208,10 @@ void AudioStreamPlayer::play(float p_from_pos) { Ref stream_playback = stream->instantiate_playback(); ERR_FAIL_COND_MSG(stream_playback.is_null(), "Failed to instantiate playback."); + for (const KeyValue &K : playback_parameters) { + stream_playback->set_parameter(K.value.path, K.value.value); + } + AudioServer::get_singleton()->start_playback_stream(stream_playback, bus, _get_volume_vector(), p_from_pos, pitch_scale); stream_playbacks.push_back(stream_playback); active.set(); diff --git a/scene/audio/audio_stream_player.h b/scene/audio/audio_stream_player.h index 9dbdccdc6951..404d6fbebfb1 100644 --- a/scene/audio/audio_stream_player.h +++ b/scene/audio/audio_stream_player.h @@ -68,11 +68,23 @@ private: Vector _get_volume_vector(); + struct ParameterData { + StringName path; + Variant value; + }; + + HashMap playback_parameters; + void _update_stream_parameters(); + protected: void _validate_property(PropertyInfo &p_property) const; void _notification(int p_what); static void _bind_methods(); + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List *p_list) const; + public: void set_stream(Ref p_stream); Ref get_stream() const; diff --git a/servers/audio/audio_stream.cpp b/servers/audio/audio_stream.cpp index 65d88a0eba97..3e3a7d2381b0 100644 --- a/servers/audio/audio_stream.cpp +++ b/servers/audio/audio_stream.cpp @@ -80,6 +80,16 @@ void AudioStreamPlayback::tag_used_streams() { GDVIRTUAL_CALL(_tag_used_streams); } +void AudioStreamPlayback::set_parameter(const StringName &p_name, const Variant &p_value) { + GDVIRTUAL_CALL(_set_parameter, p_name, p_value); +} + +Variant AudioStreamPlayback::get_parameter(const StringName &p_name) const { + Variant ret; + GDVIRTUAL_CALL(_get_parameter, p_name, ret); + return ret; +} + void AudioStreamPlayback::_bind_methods() { GDVIRTUAL_BIND(_start, "from_pos") GDVIRTUAL_BIND(_stop) @@ -89,6 +99,8 @@ void AudioStreamPlayback::_bind_methods() { GDVIRTUAL_BIND(_seek, "position") GDVIRTUAL_BIND(_mix, "buffer", "rate_scale", "frames"); GDVIRTUAL_BIND(_tag_used_streams); + GDVIRTUAL_BIND(_set_parameter, "name", "value"); + GDVIRTUAL_BIND(_get_parameter, "name"); } ////////////////////////////// @@ -249,6 +261,16 @@ float AudioStream::get_tagged_frame_offset(int p_index) const { return tagged_offsets[p_index]; } +void AudioStream::get_parameter_list(List *r_parameters) { + TypedArray ret; + GDVIRTUAL_CALL(_get_parameter_list, ret); + for (int i = 0; i < ret.size(); i++) { + Dictionary d = ret[i]; + ERR_CONTINUE(!d.has("default_value")); + r_parameters->push_back(Parameter(PropertyInfo::from_dict(d), d["default_value"])); + } +} + void AudioStream::_bind_methods() { ClassDB::bind_method(D_METHOD("get_length"), &AudioStream::get_length); ClassDB::bind_method(D_METHOD("is_monophonic"), &AudioStream::is_monophonic); @@ -259,6 +281,9 @@ void AudioStream::_bind_methods() { GDVIRTUAL_BIND(_is_monophonic); GDVIRTUAL_BIND(_get_bpm) GDVIRTUAL_BIND(_get_beat_count) + GDVIRTUAL_BIND(_get_parameter_list) + + ADD_SIGNAL(MethodInfo("parameter_list_changed")); } //////////////////////////////// diff --git a/servers/audio/audio_stream.h b/servers/audio/audio_stream.h index 015e89fc8ee5..f8123fbe15e5 100644 --- a/servers/audio/audio_stream.h +++ b/servers/audio/audio_stream.h @@ -38,6 +38,7 @@ #include "core/object/gdvirtual.gen.inc" #include "core/variant/native_ptr.h" +#include "core/variant/typed_array.h" class AudioStream; @@ -54,6 +55,9 @@ protected: GDVIRTUAL1(_seek, double) GDVIRTUAL3R(int, _mix, GDExtensionPtr, float, int) GDVIRTUAL0(_tag_used_streams) + GDVIRTUAL2(_set_parameter, const StringName &, const Variant &) + GDVIRTUAL1RC(Variant, _get_parameter, const StringName &) + public: virtual void start(double p_from_pos = 0.0); virtual void stop(); @@ -66,6 +70,9 @@ public: virtual void tag_used_streams(); + virtual void set_parameter(const StringName &p_name, const Variant &p_value); + virtual Variant get_parameter(const StringName &p_name) const; + virtual int mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames); }; @@ -124,6 +131,7 @@ protected: GDVIRTUAL0RC(bool, _has_loop) GDVIRTUAL0RC(int, _get_bar_beats) GDVIRTUAL0RC(int, _get_beat_count) + GDVIRTUAL0RC(TypedArray, _get_parameter_list) public: virtual Ref instantiate_playback(); @@ -141,6 +149,17 @@ public: uint64_t get_tagged_frame() const; uint32_t get_tagged_frame_count() const; float get_tagged_frame_offset(int p_index) const; + + struct Parameter { + PropertyInfo property; + Variant default_value; + Parameter(const PropertyInfo &p_info = PropertyInfo(), const Variant &p_default_value = Variant()) { + property = p_info; + default_value = p_default_value; + } + }; + + virtual void get_parameter_list(List *r_parameters); }; // Microphone