Skip to content

Commit

Permalink
Implement audio stream playback parameters.
Browse files Browse the repository at this point in the history
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 (godotengine#64488), this exposes an easy way to change the active clip, which was not possible before.
* For the upcoming parametrizable audio support (godotengine/godot-proposals#3394) this allows editing and animating audio graph parameters.

In any case, this PR is required to complete godotengine#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>
  • Loading branch information
reduz and AThousandShips committed Jan 16, 2024
1 parent 13a0d6e commit a40fe16
Show file tree
Hide file tree
Showing 14 changed files with 382 additions and 6 deletions.
13 changes: 13 additions & 0 deletions doc/classes/AudioStream.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@
<description>
</description>
</method>
<method name="_get_parameter_list" qualifiers="virtual const">
<return type="Dictionary[]" />
<description>
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.
</description>
</method>
<method name="_get_stream_name" qualifiers="virtual const">
<return type="String" />
<description>
Expand Down Expand Up @@ -62,4 +68,11 @@
</description>
</method>
</methods>
<signals>
<signal name="parameter_list_changed">
<description>
Signal to be emitted to notify when the parameter list changed.
</description>
</signal>
</signals>
</class>
15 changes: 15 additions & 0 deletions doc/classes/AudioStreamPlayback.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@
<description>
</description>
</method>
<method name="_get_parameter" qualifiers="virtual const">
<return type="Variant" />
<param index="0" name="name" type="StringName" />
<description>
Return the current value of a playback parameter by name (see [method AudioStream._get_parameter_list]).
</description>
</method>
<method name="_get_playback_position" qualifiers="virtual const">
<return type="float" />
<description>
Expand All @@ -39,6 +46,14 @@
<description>
</description>
</method>
<method name="_set_parameter" qualifiers="virtual">
<return type="void" />
<param index="0" name="name" type="StringName" />
<param index="1" name="value" type="Variant" />
<description>
Set the current value of a playback parameter by name (see [method AudioStream._get_parameter_list]).
</description>
</method>
<method name="_start" qualifiers="virtual">
<return type="void" />
<param index="0" name="from_pos" type="float" />
Expand Down
29 changes: 27 additions & 2 deletions modules/minimp3/audio_stream_mp3.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -232,6 +253,10 @@ bool AudioStreamMP3::is_monophonic() const {
return false;
}

void AudioStreamMP3::get_parameter_list(List<Parameter> *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;
Expand Down
7 changes: 7 additions & 0 deletions modules/minimp3/audio_stream_mp3.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -72,6 +74,9 @@ class AudioStreamPlaybackMP3 : public AudioStreamPlaybackResampled {

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();
};
Expand Down Expand Up @@ -126,6 +131,8 @@ class AudioStreamMP3 : public AudioStream {

virtual bool is_monophonic() const override;

virtual void get_parameter_list(List<Parameter> *r_parameters) override;

AudioStreamMP3();
virtual ~AudioStreamMP3();
};
Expand Down
32 changes: 28 additions & 4 deletions modules/vorbis/audio_stream_ogg_vorbis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand Down Expand Up @@ -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.
Expand All @@ -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);
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -493,6 +513,10 @@ bool AudioStreamOggVorbis::is_monophonic() const {
return false;
}

void AudioStreamOggVorbis::get_parameter_list(List<Parameter> *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);
Expand Down
7 changes: 7 additions & 0 deletions modules/vorbis/audio_stream_ogg_vorbis.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -95,6 +97,9 @@ class AudioStreamPlaybackOggVorbis : public AudioStreamPlaybackResampled {

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();
};
Expand Down Expand Up @@ -152,6 +157,8 @@ class AudioStreamOggVorbis : public AudioStream {

virtual bool is_monophonic() const override;

virtual void get_parameter_list(List<Parameter> *r_parameters) override;

AudioStreamOggVorbis();
virtual ~AudioStreamOggVorbis();
};
Expand Down
69 changes: 69 additions & 0 deletions scene/2d/audio_stream_player_2d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -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<AudioStream::Parameter> parameters;
stream->get_parameter_list(&parameters);
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<AudioStream> 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<AudioStream> AudioStreamPlayer2D::get_stream() const {
Expand Down Expand Up @@ -262,6 +291,10 @@ void AudioStreamPlayer2D::play(float p_from_pos) {
Ref<AudioStreamPlayback> stream_playback = stream->instantiate_playback();
ERR_FAIL_COND_MSG(stream_playback.is_null(), "Failed to instantiate playback.");

for (const KeyValue<StringName, ParameterData> &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);
Expand Down Expand Up @@ -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<StringName, ParameterData>::Iterator I = playback_parameters.find(p_name);
if (!I) {
return false;
}
ParameterData &pd = I->value;
pd.value = p_value;
for (Ref<AudioStreamPlayback> &playback : stream_playbacks) {
playback->set_parameter(pd.path, pd.value);
}
return true;
}

bool AudioStreamPlayer2D::_get(const StringName &p_name, Variant &r_ret) const {
HashMap<StringName, ParameterData>::ConstIterator I = playback_parameters.find(p_name);
if (!I) {
return false;
}

r_ret = I->value.value;
return true;
}

void AudioStreamPlayer2D::_get_property_list(List<PropertyInfo> *p_list) const {
if (stream.is_null()) {
return;
}
List<AudioStream::Parameter> parameters;
stream->get_parameter_list(&parameters);
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);
Expand Down
12 changes: 12 additions & 0 deletions scene/2d/audio_stream_player_2d.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,23 @@ class AudioStreamPlayer2D : public Node2D {
float panning_strength = 1.0f;
float cached_global_panning_strength = 0.5f;

struct ParameterData {
StringName path;
Variant value;
};

HashMap<StringName, ParameterData> 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<PropertyInfo> *p_list) const;

public:
void set_stream(Ref<AudioStream> p_stream);
Ref<AudioStream> get_stream() const;
Expand Down
Loading

0 comments on commit a40fe16

Please sign in to comment.