Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement audio stream playback parameters. #86473

Merged
merged 1 commit into from Jan 16, 2024

Conversation

reduz
Copy link
Member

@reduz reduz commented Dec 23, 2023

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).

All you can do is configure the AudioStream itself, which affects all instances in the game. With this PR, configuring playback parameters per each stream player is possible, giving users far more flexibility and freedom to configure playback or animate parameters.

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 (Add interactive music support #64488), this exposes an easy way to change the active clip, which was not possible before (so this PR is a prerequisite for that one).
  • For the upcoming parametrizable audio support (Add procedural and parametrizable audio support godot-proposals#3394) this allows editing and animating audio graph parameters.

Screenshot of how it looks:

image

Supersedes #65797, implements godotengine/godot-proposals#3120.

@reduz reduz requested review from a team as code owners December 23, 2023 16:41
@Calinou Calinou added this to the 4.x milestone Dec 23, 2023
modules/vorbis/audio_stream_ogg_vorbis.h Outdated Show resolved Hide resolved
modules/minimp3/audio_stream_mp3.h Outdated Show resolved Hide resolved
modules/minimp3/audio_stream_mp3.h Outdated Show resolved Hide resolved
modules/vorbis/audio_stream_ogg_vorbis.h Outdated Show resolved Hide resolved
@KoBeWi
Copy link
Member

KoBeWi commented Dec 23, 2023

Supersedes #65797 I guess.

@reduz
Copy link
Member Author

reduz commented Dec 23, 2023

@AThousandShips Thanks!! Merged and squashed.

@reduz
Copy link
Member Author

reduz commented Jan 8, 2024

Did some changes, looping is now something you can check to override, to make it less confusing.

I think this is ready for review and merge.

@adamscott adamscott self-requested a review January 8, 2024 20:43
@aaronfranke
Copy link
Member

I actually implemented per-player audio looping in The Mirror's fork of Godot a few months ago so that audio players can be looped by users, but I didn't know if this was desired to have upstream. So from that perspective, it's great to have this in the engine, less stuff for us to maintain :P

From the GLTF perspective, the latest iteration of the audio standard has looping as a part of the audio source (what Godot would call the audio stream), so this PR isn't useful for GLTF audio, but it doesn't hurt it either.

@reduz
Copy link
Member Author

reduz commented Jan 9, 2024

@aaronfranke The problem we have in the audio side is that parameters exposed at stream level are very tricky because:

  1. Streams many times are imported, so you can't edit them directly.
  2. Even if you can edit them directly, they are shared across all players.

So, this makes editing or interacting with local instances very hard. This PR is created to make this easy and allow exposing parameters that can be edited locally. Additionally, as they appear at node level (similar to how animationtree works), you can animate them and edit them more freely.

servers/audio/audio_stream.h Outdated Show resolved Hide resolved
doc/classes/AudioStream.xml Outdated Show resolved Hide resolved
doc/classes/AudioStream.xml Outdated Show resolved Hide resolved
@KoBeWi
Copy link
Member

KoBeWi commented Jan 9, 2024

When you set a parameter in AudioStreamPlayer and then uncheck it, it will be stored in the scene as null.
image

modules/minimp3/audio_stream_mp3.cpp Show resolved Hide resolved
servers/audio/audio_stream.cpp Show resolved Hide resolved
scene/2d/audio_stream_player_2d.cpp Outdated Show resolved Hide resolved
@KoBeWi
Copy link
Member

KoBeWi commented Jan 10, 2024

Now the parameter is always null and can't be changed.

@@ -28,6 +28,12 @@
<description>
</description>
</method>
<method name="_get_parameter_list" qualifiers="virtual const">
<return type="Array" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<return type="Array" />
<return type="Dictionary[]" />

Based on the failing CI prompt.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might make sense to also slightly tweak the description below after this change since "array contains dictionaries" will be redundant.

Copy link
Contributor

@ellenhp ellenhp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM for audio. I have not built or tested so it would be good if someone else could confirm that they've played with it, but the approach seems sound and I think this'll make a lot of people really happy.

@@ -28,6 +28,12 @@
<description>
</description>
</method>
<method name="_get_parameter_list" qualifiers="virtual const">
<return type="Array" />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might make sense to also slightly tweak the description below after this change since "array contains dictionaries" will be redundant.

@reduz
Copy link
Member Author

reduz commented Jan 11, 2024

@ellenhp Oh, very happy to see you back Ellen and thanks for the review! I still have a few bugs to fix, but should be able to get this one done soon.

Comment on lines 468 to 480
if (I) {
ParameterData &pd = I->value;
pd.value = p_value;
for (Ref<AudioStreamPlayback> &playback : stream_playbacks) {
playback->set_parameter(pd.path, pd.value);
}
if (p_value.is_null()) {
playback_parameters.erase(p_name);
}
return true;
}

return false;
Copy link
Member

@adamscott adamscott Jan 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would use the return early pattern instead of a huge if.

Suggested change
if (I) {
ParameterData &pd = I->value;
pd.value = p_value;
for (Ref<AudioStreamPlayback> &playback : stream_playbacks) {
playback->set_parameter(pd.path, pd.value);
}
if (p_value.is_null()) {
playback_parameters.erase(p_name);
}
return true;
}
return false;
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);
}
if (p_value.is_null()) {
playback_parameters.erase(p_name);
}
return true;

Comment on lines 855 to 867
if (I) {
ParameterData &pd = I->value;
pd.value = p_value;
for (Ref<AudioStreamPlayback> &playback : stream_playbacks) {
playback->set_parameter(pd.path, pd.value);
}
if (p_value.is_null()) {
playback_parameters.erase(p_name);
}
return true;
}

return false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same return early comment here.

Copy link
Member Author

@reduz reduz Jan 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we will need to have a discussion about these things because I don't think we need to unify so much in style for this type of code. To me early return is not necessarily more readable always. It also makes more sense in a context like this one where all the 3 functions (_get _set _get_property list) work the same. I will do it for this PR, but I think we need to lax the review requirements a bit.

Comment on lines 126 to 138
if (I) {
ParameterData &pd = I->value;
pd.value = p_value;
for (Ref<AudioStreamPlayback> &playback : stream_playbacks) {
playback->set_parameter(pd.path, pd.value);
}
if (p_value.is_null()) {
playback_parameters.erase(p_name);
}
return true;
}

return false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same return early comment here.

@reduz
Copy link
Member Author

reduz commented Jan 13, 2024

Now the parameter is always null and can't be changed.

Ah shit, I thought It worked, let me test again 😆

@reduz
Copy link
Member Author

reduz commented Jan 13, 2024

Yeah nevermind, I was misled thinking it worked, can't do it like this. Guess the null will have to stay if unassigned, else the logic gets more hairy due to using checkable logic.

@KoBeWi
Copy link
Member

KoBeWi commented Jan 13, 2024

Maybe you can handle it using _validate_property(). The null only appears after toggling the parameter, so you get a new value that can't be removed anymore.

@reduz
Copy link
Member Author

reduz commented Jan 13, 2024

@KoBeWi The problem is that this is very parameter dependant, and this null comes from a checkable parameter in the OGG/MP3 streams, other parameters may not necessarily be like this.
I could still add a hack in _validate_property and make a parameter not serializable if its null, but it feels too workaroundish and not sure if this is worth it.

@KoBeWi
Copy link
Member

KoBeWi commented Jan 13, 2024

We'll get a report as soon as someone finds out a parameter in their scene that they can't remove.
EDIT:
Actually the parameter appears right after assigning the stream. Maybe a proper fix is tweaking the behavior of checkable properties, but that's out of scope of this PR.

@Mickeon
Copy link
Contributor

Mickeon commented Jan 13, 2024

The fact that null is saved as is now needs to be addressed before this gets merged, otherwise it's not going to leave a great impression, in my opinion. The feature would feel underbaked right away. There's many cases of properties saving when they don't need to, and I wouldn't want this to contribute to the pile, either.

@KoBeWi
Copy link
Member

KoBeWi commented Jan 13, 2024

I could still add a hack in _validate_property and make a parameter not serializable if its null, but it feels too workaroundish and not sure if this is worth it.

Actually this should check if the value is equal to the default value defined in the parameter.

Copy link
Member

@KoBeWi KoBeWi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, aside from the null issue, looks fine.
I'll try to address it when I update #87061.

MJacred

This comment was marked as resolved.

@akien-mga akien-mga modified the milestones: 4.x, 4.3 Jan 15, 2024
Copy link
Contributor

@MJacred MJacred left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up from previous review

modules/vorbis/audio_stream_ogg_vorbis.cpp Outdated Show resolved Hide resolved
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>
@reduz
Copy link
Member Author

reduz commented Jan 16, 2024

Alright, should be ready to merge once it passes CI.

@akien-mga akien-mga merged commit 3df0c5b into godotengine:master Jan 16, 2024
15 checks passed
@akien-mga
Copy link
Member

Thanks!

@reduz
Copy link
Member Author

reduz commented Jan 16, 2024

🎉

Thanks all involved for giving me feedback on this!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

10 participants