Browse Source

Implement blending audio feature to AnimationTree

Silc Renew 2 years ago
parent
commit
75330887d7

+ 15 - 0
doc/classes/Animation.xml

@@ -104,6 +104,13 @@
 				[param stream] is the [AudioStream] resource to play. [param start_offset] is the number of seconds cut off at the beginning of the audio stream, while [param end_offset] is at the ending.
 			</description>
 		</method>
+		<method name="audio_track_is_use_blend" qualifiers="const">
+			<return type="bool" />
+			<param index="0" name="track_idx" type="int" />
+			<description>
+				Returns [code]true[/code] if the track at [code]idx[/code] will be blended with other animations.
+			</description>
+		</method>
 		<method name="audio_track_set_key_end_offset">
 			<return type="void" />
 			<param index="0" name="track_idx" type="int" />
@@ -131,6 +138,14 @@
 				Sets the stream of the key identified by [param key_idx] to value [param stream]. The [param track_idx] must be the index of an Audio Track.
 			</description>
 		</method>
+		<method name="audio_track_set_use_blend">
+			<return type="void" />
+			<param index="0" name="track_idx" type="int" />
+			<param index="1" name="enable" type="bool" />
+			<description>
+				Sets whether the track will be blended with other animations. If [code]true[/code], the audio playback volume changes depending on the blend value.
+			</description>
+		</method>
 		<method name="bezier_track_get_key_in_handle" qualifiers="const">
 			<return type="Vector2" />
 			<param index="0" name="track_idx" type="int" />

+ 4 - 0
doc/classes/AnimationPlayer.xml

@@ -232,6 +232,10 @@
 		<member name="assigned_animation" type="String" setter="set_assigned_animation" getter="get_assigned_animation">
 			If playing, the the current animation's key, otherwise, the animation last played. When set, this changes the animation, but will not play it unless already playing. See also [member current_animation].
 		</member>
+		<member name="audio_max_polyphony" type="int" setter="set_audio_max_polyphony" getter="get_audio_max_polyphony" default="32">
+			The number of possible simultaneous sounds for each of the assigned AudioStreamPlayers.
+			For example, if this value is [code]32[/code] and the animation has two audio tracks, the two [AudioStreamPlayer]s assigned can play simultaneously up to [code]32[/code] voices each.
+		</member>
 		<member name="autoplay" type="String" setter="set_autoplay" getter="get_autoplay" default="&quot;&quot;">
 			The key of the animation to play when the scene loads.
 		</member>

+ 4 - 0
doc/classes/AnimationTree.xml

@@ -110,6 +110,10 @@
 		<member name="anim_player" type="NodePath" setter="set_animation_player" getter="get_animation_player" default="NodePath(&quot;&quot;)">
 			The path to the [AnimationPlayer] used for animating.
 		</member>
+		<member name="audio_max_polyphony" type="int" setter="set_audio_max_polyphony" getter="get_audio_max_polyphony" default="32">
+			The number of possible simultaneous sounds for each of the assigned AudioStreamPlayers.
+			For example, if this value is [code]32[/code] and the animation has two audio tracks, the two [AudioStreamPlayer]s assigned can play simultaneously up to [code]32[/code] voices each.
+		</member>
 		<member name="process_callback" type="int" setter="set_process_callback" getter="get_process_callback" enum="AnimationTree.AnimationProcessCallback" default="1">
 			The process mode of this [AnimationTree]. See [enum AnimationProcessCallback] for available modes.
 		</member>

+ 6 - 0
doc/classes/AudioStreamPlayer.xml

@@ -28,6 +28,12 @@
 				Returns the [AudioStreamPlayback] object associated with this [AudioStreamPlayer].
 			</description>
 		</method>
+		<method name="has_stream_playback">
+			<return type="bool" />
+			<description>
+				Returns whether the [AudioStreamPlayer] can return the [AudioStreamPlayback] object or not.
+			</description>
+		</method>
 		<method name="play">
 			<return type="void" />
 			<param index="0" name="from_position" type="float" default="0.0" />

+ 6 - 0
doc/classes/AudioStreamPlayer2D.xml

@@ -25,6 +25,12 @@
 				Returns the [AudioStreamPlayback] object associated with this [AudioStreamPlayer2D].
 			</description>
 		</method>
+		<method name="has_stream_playback">
+			<return type="bool" />
+			<description>
+				Returns whether the [AudioStreamPlayer] can return the [AudioStreamPlayback] object or not.
+			</description>
+		</method>
 		<method name="play">
 			<return type="void" />
 			<param index="0" name="from_position" type="float" default="0.0" />

+ 6 - 0
doc/classes/AudioStreamPlayer3D.xml

@@ -25,6 +25,12 @@
 				Returns the [AudioStreamPlayback] object associated with this [AudioStreamPlayer3D].
 			</description>
 		</method>
+		<method name="has_stream_playback">
+			<return type="bool" />
+			<description>
+				Returns whether the [AudioStreamPlayer] can return the [AudioStreamPlayback] object or not.
+			</description>
+		</method>
 		<method name="play">
 			<return type="void" />
 			<param index="0" name="from_position" type="float" default="0.0" />

+ 43 - 7
editor/animation_track_editor.cpp

@@ -1954,6 +1954,10 @@ void AnimationTrackEdit::_notification(int p_what) {
 					get_theme_icon(SNAME("TrackDiscrete"), SNAME("EditorIcons")),
 					get_theme_icon(SNAME("TrackCapture"), SNAME("EditorIcons"))
 				};
+				Ref<Texture2D> blend_icon[2] = {
+					get_theme_icon(SNAME("UseBlendEnable"), SNAME("EditorIcons")),
+					get_theme_icon(SNAME("UseBlendDisable"), SNAME("EditorIcons")),
+				};
 
 				int ofs = get_size().width - timeline->get_buttons_width();
 
@@ -1982,6 +1986,11 @@ void AnimationTrackEdit::_notification(int p_what) {
 					if (!animation->track_is_compressed(track) && animation->track_get_type(track) == Animation::TYPE_VALUE) {
 						draw_texture(update_icon, update_mode_rect.position);
 					}
+					if (animation->track_get_type(track) == Animation::TYPE_AUDIO) {
+						Ref<Texture2D> use_blend_icon = blend_icon[animation->audio_track_is_use_blend(track) ? 0 : 1];
+						Vector2 use_blend_icon_pos = update_mode_rect.position + (update_mode_rect.size - use_blend_icon->get_size()) / 2;
+						draw_texture(use_blend_icon, use_blend_icon_pos);
+					}
 					// Make it easier to click.
 					update_mode_rect.position.y = 0;
 					update_mode_rect.size.y = get_size().height;
@@ -1990,13 +1999,12 @@ void AnimationTrackEdit::_notification(int p_what) {
 					update_mode_rect.size.x += hsep / 2;
 
 					if (!read_only) {
-						if (animation->track_get_type(track) == Animation::TYPE_VALUE) {
+						if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_AUDIO) {
 							draw_texture(down_icon, Vector2(ofs, int(get_size().height - down_icon->get_height()) / 2));
 							update_mode_rect.size.x += down_icon->get_width();
 						} else if (animation->track_get_type(track) == Animation::TYPE_BEZIER) {
 							Ref<Texture2D> bezier_icon = get_theme_icon(SNAME("EditBezier"), SNAME("EditorIcons"));
 							update_mode_rect.size.x += down_icon->get_width();
-
 							update_mode_rect = Rect2();
 						} else {
 							update_mode_rect = Rect2();
@@ -2439,7 +2447,11 @@ String AnimationTrackEdit::get_tooltip(const Point2 &p_pos) const {
 	}
 
 	if (update_mode_rect.has_point(p_pos)) {
-		return TTR("Update Mode (How this property is set)");
+		if (animation->track_get_type(track) == Animation::TYPE_AUDIO) {
+			return TTR("Use Blend");
+		} else {
+			return TTR("Update Mode (How this property is set)");
+		}
 	}
 
 	if (interp_mode_rect.has_point(p_pos)) {
@@ -2641,9 +2653,14 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
 					menu->connect("id_pressed", callable_mp(this, &AnimationTrackEdit::_menu_selected));
 				}
 				menu->clear();
-				menu->add_icon_item(get_theme_icon(SNAME("TrackContinuous"), SNAME("EditorIcons")), TTR("Continuous"), MENU_CALL_MODE_CONTINUOUS);
-				menu->add_icon_item(get_theme_icon(SNAME("TrackDiscrete"), SNAME("EditorIcons")), TTR("Discrete"), MENU_CALL_MODE_DISCRETE);
-				menu->add_icon_item(get_theme_icon(SNAME("TrackCapture"), SNAME("EditorIcons")), TTR("Capture"), MENU_CALL_MODE_CAPTURE);
+				if (animation->track_get_type(track) == Animation::TYPE_AUDIO) {
+					menu->add_icon_item(get_theme_icon(SNAME("UseBlendEnable"), SNAME("EditorIcons")), TTR("Use Blend"), MENU_USE_BLEND_ENABLED);
+					menu->add_icon_item(get_theme_icon(SNAME("UseBlendDisable"), SNAME("EditorIcons")), TTR("Don't Use Blend"), MENU_USE_BLEND_DISABLED);
+				} else {
+					menu->add_icon_item(get_theme_icon(SNAME("TrackContinuous"), SNAME("EditorIcons")), TTR("Continuous"), MENU_CALL_MODE_CONTINUOUS);
+					menu->add_icon_item(get_theme_icon(SNAME("TrackDiscrete"), SNAME("EditorIcons")), TTR("Discrete"), MENU_CALL_MODE_DISCRETE);
+					menu->add_icon_item(get_theme_icon(SNAME("TrackCapture"), SNAME("EditorIcons")), TTR("Capture"), MENU_CALL_MODE_CAPTURE);
+				}
 				menu->reset_size();
 
 				Vector2 popup_pos = get_screen_position() + update_mode_rect.position + Vector2(0, update_mode_rect.size.height);
@@ -2662,7 +2679,7 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
 				menu->add_icon_item(get_theme_icon(SNAME("InterpRaw"), SNAME("EditorIcons")), TTR("Nearest"), MENU_INTERPOLATION_NEAREST);
 				menu->add_icon_item(get_theme_icon(SNAME("InterpLinear"), SNAME("EditorIcons")), TTR("Linear"), MENU_INTERPOLATION_LINEAR);
 				menu->add_icon_item(get_theme_icon(SNAME("InterpCubic"), SNAME("EditorIcons")), TTR("Cubic"), MENU_INTERPOLATION_CUBIC);
-				// Check is angle property.
+				// Check whether it is angle property.
 				AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
 				if (ape) {
 					AnimationPlayer *ap = ape->get_player();
@@ -3055,6 +3072,16 @@ void AnimationTrackEdit::_menu_selected(int p_index) {
 			emit_signal(SNAME("delete_request"));
 
 		} break;
+		case MENU_USE_BLEND_ENABLED:
+		case MENU_USE_BLEND_DISABLED: {
+			bool use_blend = p_index == MENU_USE_BLEND_ENABLED;
+			EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
+			undo_redo->create_action(TTR("Change Animation Use Blend"));
+			undo_redo->add_do_method(animation.ptr(), "audio_track_set_use_blend", track, use_blend);
+			undo_redo->add_undo_method(animation.ptr(), "audio_track_set_use_blend", track, animation->audio_track_is_use_blend(track));
+			undo_redo->commit_action();
+			queue_redraw();
+		} break;
 	}
 }
 
@@ -3488,6 +3515,9 @@ void AnimationTrackEditor::_animation_track_remove_request(int p_track, Ref<Anim
 		if (p_from_animation->track_get_type(idx) == Animation::TYPE_VALUE) {
 			undo_redo->add_undo_method(p_from_animation.ptr(), "value_track_set_update_mode", idx, p_from_animation->value_track_get_update_mode(idx));
 		}
+		if (animation->track_get_type(idx) == Animation::TYPE_AUDIO) {
+			undo_redo->add_undo_method(animation.ptr(), "audio_track_set_use_blend", idx, animation->audio_track_is_use_blend(idx));
+		}
 
 		undo_redo->commit_action();
 	}
@@ -5618,6 +5648,9 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) {
 						if (tc.track_type == Animation::TYPE_VALUE) {
 							tc.update_mode = animation->value_track_get_update_mode(idx);
 						}
+						if (tc.track_type == Animation::TYPE_AUDIO) {
+							tc.use_blend = animation->audio_track_is_use_blend(idx);
+						}
 						tc.loop_wrap = animation->track_get_interpolation_loop_wrap(idx);
 						tc.enabled = animation->track_is_enabled(idx);
 						for (int i = 0; i < animation->track_get_key_count(idx); i++) {
@@ -5662,6 +5695,9 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) {
 				if (track_clipboard[i].track_type == Animation::TYPE_VALUE) {
 					undo_redo->add_do_method(animation.ptr(), "value_track_set_update_mode", base_track, track_clipboard[i].update_mode);
 				}
+				if (track_clipboard[i].track_type == Animation::TYPE_AUDIO) {
+					undo_redo->add_do_method(animation.ptr(), "audio_track_set_use_blend", base_track, track_clipboard[i].use_blend);
+				}
 
 				for (int j = 0; j < track_clipboard[i].keys.size(); j++) {
 					undo_redo->add_do_method(animation.ptr(), "track_insert_key", base_track, track_clipboard[i].keys[j].time, track_clipboard[i].keys[j].value, track_clipboard[i].keys[j].transition);

+ 4 - 1
editor/animation_track_editor.h

@@ -220,7 +220,9 @@ class AnimationTrackEdit : public Control {
 		MENU_KEY_INSERT,
 		MENU_KEY_DUPLICATE,
 		MENU_KEY_ADD_RESET,
-		MENU_KEY_DELETE
+		MENU_KEY_DELETE,
+		MENU_USE_BLEND_ENABLED,
+		MENU_USE_BLEND_DISABLED,
 	};
 
 	AnimationTimelineEdit *timeline = nullptr;
@@ -566,6 +568,7 @@ class AnimationTrackEditor : public VBoxContainer {
 		Animation::LoopMode loop_mode = Animation::LOOP_PINGPONG;
 		bool loop_wrap = false;
 		bool enabled = false;
+		bool use_blend = false;
 
 		struct Key {
 			float time = 0;

+ 1 - 0
editor/icons/UseBlendDisable.svg

@@ -0,0 +1 @@
+<svg enable-background="new -595.5 420.5 16 16" height="16" viewBox="-595.5 420.5 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m-591 421.5h7v14h-7z" fill="#a3e595" opacity=".5"/><g fill="none" stroke="#a3e595" stroke-linecap="square" stroke-width="2"><path d="m-585 434.5v-12"/><path d="m-590 422.5v12"/><path d="m-581.5 422.5h-12"/></g></svg>

+ 1 - 0
editor/icons/UseBlendEnable.svg

@@ -0,0 +1 @@
+<svg enable-background="new -595.5 420.5 16 16" height="16" viewBox="-595.5 420.5 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m-587.5 423.244c-3.995 2.354-7 6.775-7 11.256v1h14v-1c0-4.48-3.005-8.901-7-11.256z" fill="#95c6e8" opacity=".5"/><g fill="none" stroke="#95c6e8" stroke-linecap="square" stroke-width="2"><path d="m-581.5 422.5c-6 0-12 6-12 12"/><path d="m-581.5 434.5c0-6-6-12-12-12"/></g></svg>

+ 12 - 5
scene/2d/audio_stream_player_2d.cpp

@@ -72,12 +72,10 @@ void AudioStreamPlayer2D::_notification(int p_what) {
 				_update_panning();
 			}
 
-			if (setplay.get() >= 0 && stream.is_valid()) {
+			if (setplayback.is_valid() && setplay.get() >= 0) {
 				active.set();
-				Ref<AudioStreamPlayback> new_playback = stream->instantiate_playback();
-				ERR_FAIL_COND_MSG(new_playback.is_null(), "Failed to instantiate playback.");
-				AudioServer::get_singleton()->start_playback_stream(new_playback, _get_actual_bus(), volume_vector, setplay.get(), pitch_scale);
-				stream_playbacks.push_back(new_playback);
+				AudioServer::get_singleton()->start_playback_stream(setplayback, _get_actual_bus(), volume_vector, setplay.get(), pitch_scale);
+				setplayback.unref();
 				setplay.set(-1);
 			}
 
@@ -255,7 +253,11 @@ void AudioStreamPlayer2D::play(float p_from_pos) {
 	if (stream->is_monophonic() && is_playing()) {
 		stop();
 	}
+	Ref<AudioStreamPlayback> stream_playback = stream->instantiate_playback();
+	ERR_FAIL_COND_MSG(stream_playback.is_null(), "Failed to instantiate playback.");
 
+	stream_playbacks.push_back(stream_playback);
+	setplayback = stream_playback;
 	setplay.set(p_from_pos);
 	active.set();
 	set_physics_process_internal(true);
@@ -390,6 +392,10 @@ bool AudioStreamPlayer2D::get_stream_paused() const {
 	return false;
 }
 
+bool AudioStreamPlayer2D::has_stream_playback() {
+	return !stream_playbacks.is_empty();
+}
+
 Ref<AudioStreamPlayback> AudioStreamPlayer2D::get_stream_playback() {
 	ERR_FAIL_COND_V_MSG(stream_playbacks.is_empty(), Ref<AudioStreamPlayback>(), "Player is inactive. Call play() before requesting get_stream_playback().");
 	return stream_playbacks[stream_playbacks.size() - 1];
@@ -458,6 +464,7 @@ void AudioStreamPlayer2D::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_panning_strength", "panning_strength"), &AudioStreamPlayer2D::set_panning_strength);
 	ClassDB::bind_method(D_METHOD("get_panning_strength"), &AudioStreamPlayer2D::get_panning_strength);
 
+	ClassDB::bind_method(D_METHOD("has_stream_playback"), &AudioStreamPlayer2D::has_stream_playback);
 	ClassDB::bind_method(D_METHOD("get_stream_playback"), &AudioStreamPlayer2D::get_stream_playback);
 
 	ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream");

+ 2 - 0
scene/2d/audio_stream_player_2d.h

@@ -56,6 +56,7 @@ private:
 
 	SafeFlag active{ false };
 	SafeNumeric<float> setplay{ -1.0 };
+	Ref<AudioStreamPlayback> setplayback;
 
 	Vector<AudioFrame> volume_vector;
 
@@ -129,6 +130,7 @@ public:
 	void set_panning_strength(float p_panning_strength);
 	float get_panning_strength() const;
 
+	bool has_stream_playback();
 	Ref<AudioStreamPlayback> get_stream_playback();
 
 	AudioStreamPlayer2D();

+ 17 - 7
scene/3d/audio_stream_player_3d.cpp

@@ -284,14 +284,12 @@ void AudioStreamPlayer3D::_notification(int p_what) {
 				volume_vector = _update_panning();
 			}
 
-			if (setplay.get() >= 0 && stream.is_valid()) {
+			if (setplayback.is_valid() && setplay.get() >= 0) {
 				active.set();
-				Ref<AudioStreamPlayback> new_playback = stream->instantiate_playback();
-				ERR_FAIL_COND_MSG(new_playback.is_null(), "Failed to instantiate playback.");
 				HashMap<StringName, Vector<AudioFrame>> bus_map;
 				bus_map[_get_actual_bus()] = volume_vector;
-				AudioServer::get_singleton()->start_playback_stream(new_playback, bus_map, setplay.get(), actual_pitch_scale, linear_attenuation, attenuation_filter_cutoff_hz);
-				stream_playbacks.push_back(new_playback);
+				AudioServer::get_singleton()->start_playback_stream(setplayback, bus_map, setplay.get(), actual_pitch_scale, linear_attenuation, attenuation_filter_cutoff_hz);
+				setplayback.unref();
 				setplay.set(-1);
 			}
 
@@ -580,14 +578,21 @@ void AudioStreamPlayer3D::play(float p_from_pos) {
 	if (stream->is_monophonic() && is_playing()) {
 		stop();
 	}
+	Ref<AudioStreamPlayback> stream_playback = stream->instantiate_playback();
+	ERR_FAIL_COND_MSG(stream_playback.is_null(), "Failed to instantiate playback.");
+
+	stream_playbacks.push_back(stream_playback);
+	setplayback = stream_playback;
 	setplay.set(p_from_pos);
 	active.set();
 	set_physics_process_internal(true);
 }
 
 void AudioStreamPlayer3D::seek(float p_seconds) {
-	stop();
-	play(p_seconds);
+	if (is_playing()) {
+		stop();
+		play(p_seconds);
+	}
 }
 
 void AudioStreamPlayer3D::stop() {
@@ -783,6 +788,10 @@ bool AudioStreamPlayer3D::get_stream_paused() const {
 	return false;
 }
 
+bool AudioStreamPlayer3D::has_stream_playback() {
+	return !stream_playbacks.is_empty();
+}
+
 Ref<AudioStreamPlayback> AudioStreamPlayer3D::get_stream_playback() {
 	ERR_FAIL_COND_V_MSG(stream_playbacks.is_empty(), Ref<AudioStreamPlayback>(), "Player is inactive. Call play() before requesting get_stream_playback().");
 	return stream_playbacks[stream_playbacks.size() - 1];
@@ -875,6 +884,7 @@ void AudioStreamPlayer3D::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_panning_strength", "panning_strength"), &AudioStreamPlayer3D::set_panning_strength);
 	ClassDB::bind_method(D_METHOD("get_panning_strength"), &AudioStreamPlayer3D::get_panning_strength);
 
+	ClassDB::bind_method(D_METHOD("has_stream_playback"), &AudioStreamPlayer3D::has_stream_playback);
 	ClassDB::bind_method(D_METHOD("get_stream_playback"), &AudioStreamPlayer3D::get_stream_playback);
 
 	ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream");

+ 2 - 0
scene/3d/audio_stream_player_3d.h

@@ -69,6 +69,7 @@ private:
 
 	SafeFlag active{ false };
 	SafeNumeric<float> setplay{ -1.0 };
+	Ref<AudioStreamPlayback> setplayback;
 
 	AttenuationModel attenuation_model = ATTENUATION_INVERSE_DISTANCE;
 	float volume_db = 0.0;
@@ -188,6 +189,7 @@ public:
 	void set_panning_strength(float p_panning_strength);
 	float get_panning_strength() const;
 
+	bool has_stream_playback();
 	Ref<AudioStreamPlayback> get_stream_playback();
 
 	AudioStreamPlayer3D();

+ 149 - 85
scene/animation/animation_player.cpp

@@ -431,6 +431,17 @@ void AnimationPlayer::_ensure_node_caches(AnimationData *p_anim, Node *p_root_ov
 			}
 		}
 
+		if (a->track_get_type(i) == Animation::TYPE_AUDIO) {
+			if (!node_cache->audio_anim.has(a->track_get_path(i).get_concatenated_names())) {
+				TrackNodeCache::AudioAnim aa;
+				aa.object = (Object *)child;
+				aa.audio_stream.instantiate();
+				aa.audio_stream->set_polyphony(audio_max_polyphony);
+
+				node_cache->audio_anim[a->track_get_path(i).get_concatenated_names()] = aa;
+			}
+		}
+
 		node_cache->last_setup_pass = setup_pass;
 	}
 }
@@ -820,52 +831,40 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double
 				if (!nc->node || is_stopping) {
 					continue;
 				}
-
-				if (p_seeked) {
 #ifdef TOOLS_ENABLED
-					if (!can_call) {
-						continue; // To avoid spamming the preview in editor.
-					}
+				if (p_seeked && !can_call) {
+					continue; // To avoid spamming the preview in editor.
+				}
 #endif // TOOLS_ENABLED
-					int idx = a->track_find_key(i, p_time);
-					if (idx < 0) {
-						continue;
-					}
-
-					Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx);
-					if (!stream.is_valid()) {
-						nc->node->call(SNAME("stop"));
-						nc->audio_playing = false;
-						playing_caches.erase(nc);
-					} else {
-						float start_ofs = a->audio_track_get_key_start_offset(i, idx);
-						start_ofs += p_time - a->track_get_key_time(i, idx);
-						float end_ofs = a->audio_track_get_key_end_offset(i, idx);
-						float len = stream->get_length();
-
-						if (start_ofs > len - end_ofs) {
-							nc->node->call(SNAME("stop"));
-							nc->audio_playing = false;
-							playing_caches.erase(nc);
-							continue;
-						}
-
-						nc->node->call(SNAME("set_stream"), stream);
-						nc->node->call(SNAME("play"), start_ofs);
+				HashMap<StringName, TrackNodeCache::AudioAnim>::Iterator E = nc->audio_anim.find(a->track_get_path(i).get_concatenated_names());
+				ERR_CONTINUE(!E); //should it continue, or create a new one?
 
-						nc->audio_playing = true;
-						playing_caches.insert(nc);
-						if (len && end_ofs > 0) { //force an end at a time
-							nc->audio_len = len - start_ofs - end_ofs;
-						} else {
-							nc->audio_len = 0;
-						}
+				TrackNodeCache::AudioAnim *aa = &E->value;
+				Node *asp = Object::cast_to<Node>(aa->object);
+				if (!asp) {
+					continue;
+				}
+				aa->length = a->get_length();
+				aa->time = p_time;
+				aa->loop = a->get_loop_mode() != Animation::LOOP_NONE;
+				aa->backward = backward;
+				if (aa->accum_pass != accum_pass) {
+					ERR_CONTINUE(cache_update_audio_size >= NODE_CACHE_UPDATE_MAX);
+					cache_update_audio[cache_update_audio_size++] = aa;
+					aa->accum_pass = accum_pass;
+				}
 
-						nc->audio_start = p_time;
+				HashMap<int, TrackNodeCache::PlayingAudioStreamInfo> &map = aa->playing_streams;
+				// Find stream.
+				int idx = -1;
+				if (p_seeked) {
+					idx = a->track_find_key(i, p_time);
+					// Discard previous stream when seeking.
+					if (map.has(idx)) {
+						aa->audio_stream_playback->stop_stream(map[idx].index);
+						map.erase(idx);
 					}
-
 				} else {
-					//find stuff to play
 					List<int> to_play;
 					if (p_started) {
 						int first_key = a->track_find_key(i, p_prev_time, Animation::FIND_MODE_EXACT);
@@ -875,55 +874,47 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double
 					}
 					a->track_get_key_indices_in_range(i, p_time, p_delta, &to_play, p_looped_flag);
 					if (to_play.size()) {
-						int idx = to_play.back()->get();
-
-						Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx);
-						if (!stream.is_valid()) {
-							nc->node->call(SNAME("stop"));
-							nc->audio_playing = false;
-							playing_caches.erase(nc);
-						} else {
-							float start_ofs = a->audio_track_get_key_start_offset(i, idx);
-							float end_ofs = a->audio_track_get_key_end_offset(i, idx);
-							float len = stream->get_length();
-
-							nc->node->call(SNAME("set_stream"), stream);
-							nc->node->call(SNAME("play"), start_ofs);
-
-							nc->audio_playing = true;
-							playing_caches.insert(nc);
-							if (len && end_ofs > 0) { //force an end at a time
-								nc->audio_len = len - start_ofs - end_ofs;
-							} else {
-								nc->audio_len = 0;
-							}
-
-							nc->audio_start = p_time;
-						}
-					} else if (nc->audio_playing) {
-						bool loop = a->get_loop_mode() != Animation::LOOP_NONE;
-
-						bool stop = false;
-
-						if (!loop) {
-							if ((p_time < nc->audio_start && !backward) || (p_time > nc->audio_start && backward)) {
-								stop = true;
-							}
-						} else if (nc->audio_len > 0) {
-							float len = nc->audio_start > p_time ? (a->get_length() - nc->audio_start) + p_time : p_time - nc->audio_start;
+						idx = to_play.back()->get();
+					}
+				}
+				if (idx < 0) {
+					continue;
+				}
 
-							if (len > nc->audio_len) {
-								stop = true;
-							}
+				// Play stream.
+				Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx);
+				if (stream.is_valid()) {
+					double start_ofs = a->audio_track_get_key_start_offset(i, idx);
+					double end_ofs = a->audio_track_get_key_end_offset(i, idx);
+					double len = stream->get_length();
+
+					if (aa->object->call(SNAME("get_stream")) != aa->audio_stream) {
+						aa->object->call(SNAME("set_stream"), aa->audio_stream);
+						aa->audio_stream_playback.unref();
+						if (!playing_audio_stream_players.has(asp)) {
+							playing_audio_stream_players.push_back(asp);
 						}
+					}
+					if (!aa->object->call(SNAME("is_playing"))) {
+						aa->object->call(SNAME("play"));
+					}
+					if (!aa->object->call(SNAME("has_stream_playback"))) {
+						aa->audio_stream_playback.unref();
+						continue;
+					}
+					if (aa->audio_stream_playback.is_null()) {
+						aa->audio_stream_playback = aa->object->call(SNAME("get_stream_playback"));
+					}
 
-						if (stop) {
-							//time to stop
-							nc->node->call(SNAME("stop"));
-							nc->audio_playing = false;
-							playing_caches.erase(nc);
-						}
+					TrackNodeCache::PlayingAudioStreamInfo pasi;
+					pasi.index = aa->audio_stream_playback->play_stream(stream, start_ofs);
+					pasi.start = p_time;
+					if (len && end_ofs > 0) { // Force an end at a time.
+						pasi.len = len - start_ofs - end_ofs;
+					} else {
+						pasi.len = 0;
 					}
+					map[idx] = pasi;
 				}
 
 			} break;
@@ -1223,6 +1214,53 @@ void AnimationPlayer::_animation_update_transforms() {
 		ERR_CONTINUE(ba->accum_pass != accum_pass);
 		ba->object->set_indexed(ba->bezier_property, ba->bezier_accum);
 	}
+
+	for (int i = 0; i < cache_update_audio_size; i++) {
+		TrackNodeCache::AudioAnim *aa = cache_update_audio[i];
+
+		ERR_CONTINUE(aa->accum_pass != accum_pass);
+
+		// Audio ending process.
+		LocalVector<int> erase_list;
+		for (const KeyValue<int, TrackNodeCache::PlayingAudioStreamInfo> &K : aa->playing_streams) {
+			TrackNodeCache::PlayingAudioStreamInfo pasi = K.value;
+
+			bool stop = false;
+			if (!aa->audio_stream_playback->is_stream_playing(pasi.index)) {
+				stop = true;
+			}
+			if (!aa->loop) {
+				if (!aa->backward) {
+					if (aa->time < pasi.start) {
+						stop = true;
+					}
+				} else if (aa->backward) {
+					if (aa->time > pasi.start) {
+						stop = true;
+					}
+				}
+			}
+			if (pasi.len > 0) {
+				double len = 0.0;
+				if (!aa->backward) {
+					len = pasi.start > aa->time ? (aa->length - pasi.start) + aa->time : aa->time - pasi.start;
+				} else {
+					len = pasi.start < aa->time ? (aa->length - aa->time) + pasi.start : pasi.start - aa->time;
+				}
+				if (len > pasi.len) {
+					stop = true;
+				}
+			}
+			if (stop) {
+				// Time to stop.
+				aa->audio_stream_playback->stop_stream(pasi.index);
+				erase_list.push_back(K.key);
+			}
+		}
+		for (uint32_t erase_idx = 0; erase_idx < erase_list.size(); erase_idx++) {
+			aa->playing_streams.erase(erase_list[erase_idx]);
+		}
+	}
 }
 
 void AnimationPlayer::_animation_process(double p_delta) {
@@ -1238,6 +1276,7 @@ void AnimationPlayer::_animation_process(double p_delta) {
 		cache_update_size = 0;
 		cache_update_prop_size = 0;
 		cache_update_bezier_size = 0;
+		cache_update_audio_size = 0;
 
 		AnimationData *prev_from = playback.current.from;
 		_animation_process2(p_delta, started);
@@ -1675,6 +1714,7 @@ void AnimationPlayer::play(const StringName &p_name, double p_custom_blend, floa
 	}
 
 	if (get_current_animation() != p_name) {
+		_clear_audio_streams();
 		_stop_playing_caches(false);
 	}
 
@@ -1856,6 +1896,7 @@ void AnimationPlayer::_node_removed(Node *p_node) {
 }
 
 void AnimationPlayer::clear_caches() {
+	_clear_audio_streams();
 	_stop_playing_caches(true);
 
 	node_cache_map.clear();
@@ -1867,10 +1908,19 @@ void AnimationPlayer::clear_caches() {
 	cache_update_size = 0;
 	cache_update_prop_size = 0;
 	cache_update_bezier_size = 0;
+	cache_update_audio_size = 0;
 
 	emit_signal(SNAME("caches_cleared"));
 }
 
+void AnimationPlayer::_clear_audio_streams() {
+	for (int i = 0; i < playing_audio_stream_players.size(); i++) {
+		playing_audio_stream_players[i]->call(SNAME("stop"));
+		playing_audio_stream_players[i]->call(SNAME("set_stream"), Ref<AudioStream>());
+	}
+	playing_audio_stream_players.clear();
+}
+
 void AnimationPlayer::set_active(bool p_active) {
 	if (active == p_active) {
 		return;
@@ -1950,6 +2000,15 @@ AnimationPlayer::AnimationMethodCallMode AnimationPlayer::get_method_call_mode()
 	return method_call_mode;
 }
 
+void AnimationPlayer::set_audio_max_polyphony(int p_audio_max_polyphony) {
+	ERR_FAIL_COND(p_audio_max_polyphony < 0 || p_audio_max_polyphony > 128);
+	audio_max_polyphony = p_audio_max_polyphony;
+}
+
+int AnimationPlayer::get_audio_max_polyphony() const {
+	return audio_max_polyphony;
+}
+
 void AnimationPlayer::set_movie_quit_on_finish_enabled(bool p_enabled) {
 	movie_quit_on_finish = p_enabled;
 }
@@ -1978,6 +2037,7 @@ void AnimationPlayer::_set_process(bool p_process, bool p_force) {
 }
 
 void AnimationPlayer::_stop_internal(bool p_reset, bool p_keep_state) {
+	_clear_audio_streams();
 	_stop_playing_caches(p_reset);
 	Playback &c = playback;
 	c.blend.clear();
@@ -2198,6 +2258,9 @@ void AnimationPlayer::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_method_call_mode", "mode"), &AnimationPlayer::set_method_call_mode);
 	ClassDB::bind_method(D_METHOD("get_method_call_mode"), &AnimationPlayer::get_method_call_mode);
 
+	ClassDB::bind_method(D_METHOD("set_audio_max_polyphony", "max_polyphony"), &AnimationPlayer::set_audio_max_polyphony);
+	ClassDB::bind_method(D_METHOD("get_audio_max_polyphony"), &AnimationPlayer::get_audio_max_polyphony);
+
 	ClassDB::bind_method(D_METHOD("set_movie_quit_on_finish_enabled", "enabled"), &AnimationPlayer::set_movie_quit_on_finish_enabled);
 	ClassDB::bind_method(D_METHOD("is_movie_quit_on_finish_enabled"), &AnimationPlayer::is_movie_quit_on_finish_enabled);
 
@@ -2223,6 +2286,7 @@ void AnimationPlayer::_bind_methods() {
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playback_active", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_active", "is_active");
 	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "speed_scale", PROPERTY_HINT_RANGE, "-64,64,0.01"), "set_speed_scale", "get_speed_scale");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "method_call_mode", PROPERTY_HINT_ENUM, "Deferred,Immediate"), "set_method_call_mode", "get_method_call_mode");
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "audio_max_polyphony", PROPERTY_HINT_RANGE, "1,127,1"), "set_audio_max_polyphony", "get_audio_max_polyphony");
 
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "movie_quit_on_finish"), "set_movie_quit_on_finish_enabled", "is_movie_quit_on_finish_enabled");
 

+ 29 - 0
scene/animation/animation_player.h

@@ -37,6 +37,7 @@
 #include "scene/3d/skeleton_3d.h"
 #include "scene/resources/animation.h"
 #include "scene/resources/animation_library.h"
+#include "scene/resources/audio_stream_polyphonic.h"
 
 #ifdef TOOLS_ENABLED
 class AnimatedValuesBackup : public RefCounted {
@@ -147,6 +148,26 @@ private:
 
 		HashMap<StringName, BezierAnim> bezier_anim;
 
+		struct PlayingAudioStreamInfo {
+			int64_t index = -1;
+			double start = 0.0;
+			double len = 0.0;
+		};
+
+		struct AudioAnim {
+			Ref<AudioStreamPolyphonic> audio_stream;
+			Ref<AudioStreamPlaybackPolyphonic> audio_stream_playback;
+			HashMap<int, PlayingAudioStreamInfo> playing_streams;
+			Object *object = nullptr;
+			uint64_t accum_pass = 0;
+			double length = 0.0;
+			double time = 0.0;
+			bool loop = false;
+			bool backward = false;
+		};
+
+		HashMap<StringName, AudioAnim> audio_anim;
+
 		uint32_t last_setup_pass = 0;
 		TrackNodeCache() {}
 	};
@@ -187,7 +208,10 @@ private:
 	int cache_update_prop_size = 0;
 	TrackNodeCache::BezierAnim *cache_update_bezier[NODE_CACHE_UPDATE_MAX];
 	int cache_update_bezier_size = 0;
+	TrackNodeCache::AudioAnim *cache_update_audio[NODE_CACHE_UPDATE_MAX];
+	int cache_update_audio_size = 0;
 	HashSet<TrackNodeCache *> playing_caches;
+	Vector<Node *> playing_audio_stream_players;
 
 	uint64_t accum_pass = 1;
 	float speed_scale = 1.0;
@@ -263,6 +287,7 @@ private:
 	bool reset_on_save = true;
 	AnimationProcessCallback process_callback = ANIMATION_PROCESS_IDLE;
 	AnimationMethodCallMode method_call_mode = ANIMATION_METHOD_CALL_DEFERRED;
+	int audio_max_polyphony = 32;
 	bool movie_quit_on_finish = false;
 	bool processing = false;
 	bool active = true;
@@ -278,6 +303,7 @@ private:
 	void _animation_process(double p_delta);
 
 	void _node_removed(Node *p_node);
+	void _clear_audio_streams();
 	void _stop_playing_caches(bool p_reset);
 
 	// bind helpers
@@ -377,6 +403,9 @@ public:
 	void set_method_call_mode(AnimationMethodCallMode p_mode);
 	AnimationMethodCallMode get_method_call_mode() const;
 
+	void set_audio_max_polyphony(int p_audio_max_polyphony);
+	int get_audio_max_polyphony() const;
+
 	void set_movie_quit_on_finish_enabled(bool p_enabled);
 	bool is_movie_quit_on_finish_enabled() const;
 

+ 191 - 102
scene/animation/animation_tree.cpp

@@ -486,13 +486,7 @@ void AnimationTree::set_active(bool p_active) {
 	}
 
 	if (!active && is_inside_tree()) {
-		for (const TrackCache *E : playing_caches) {
-			if (ObjectDB::get_instance(E->object_id)) {
-				E->object->call(SNAME("stop"));
-			}
-		}
-
-		playing_caches.clear();
+		_clear_caches();
 	}
 }
 
@@ -531,6 +525,7 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) {
 	if (!player->has_node(player->get_root())) {
 		ERR_PRINT("AnimationTree: AnimationPlayer root is invalid.");
 		set_active(false);
+		_clear_caches();
 		return false;
 	}
 	Node *parent = player->get_node(player->get_root());
@@ -763,6 +758,8 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) {
 
 						track_audio->object = child;
 						track_audio->object_id = track_audio->object->get_instance_id();
+						track_audio->audio_stream.instantiate();
+						track_audio->audio_stream->set_polyphony(audio_max_polyphony);
 
 						track = track_audio;
 
@@ -860,14 +857,32 @@ void AnimationTree::_animation_player_changed() {
 }
 
 void AnimationTree::_clear_caches() {
+	_clear_audio_streams();
+	_clear_playing_caches();
 	for (KeyValue<NodePath, TrackCache *> &K : track_cache) {
 		memdelete(K.value);
 	}
-	playing_caches.clear();
 	track_cache.clear();
 	cache_valid = false;
 }
 
+void AnimationTree::_clear_audio_streams() {
+	for (int i = 0; i < playing_audio_stream_players.size(); i++) {
+		playing_audio_stream_players[i]->call(SNAME("stop"));
+		playing_audio_stream_players[i]->call(SNAME("set_stream"), Ref<AudioStream>());
+	}
+	playing_audio_stream_players.clear();
+}
+
+void AnimationTree::_clear_playing_caches() {
+	for (const TrackCache *E : playing_caches) {
+		if (ObjectDB::get_instance(E->object_id)) {
+			E->object->call(SNAME("stop"));
+		}
+	}
+	playing_caches.clear();
+}
+
 static void _call_object(Object *p_object, const StringName &p_method, const Vector<Variant> &p_params, bool p_deferred) {
 	// Separate function to use alloca() more efficiently
 	const Variant **argptrs = (const Variant **)alloca(sizeof(const Variant **) * p_params.size());
@@ -1007,6 +1022,13 @@ void AnimationTree::_process_graph(double p_delta) {
 					TrackCacheBezier *t = static_cast<TrackCacheBezier *>(track);
 					t->value = t->init_value;
 				} break;
+				case Animation::TYPE_AUDIO: {
+					TrackCacheAudio *t = static_cast<TrackCacheAudio *>(track);
+					for (KeyValue<ObjectID, PlayingAudioTrackInfo> &L : t->playing_streams) {
+						PlayingAudioTrackInfo &track_info = L.value;
+						track_info.volume = 0.0;
+					}
+				} break;
 				default: {
 				} break;
 			}
@@ -1026,8 +1048,8 @@ void AnimationTree::_process_graph(double p_delta) {
 			bool seeked = as.seeked;
 			Animation::LoopedFlag looped_flag = as.looped_flag;
 			bool is_external_seeking = as.is_external_seeking;
+			bool backward = signbit(delta); // This flag is used by the root motion calculates or detecting the end of audio stream.
 #ifndef _3D_DISABLED
-			bool backward = signbit(delta); // This flag is required only for the root motion since it calculates the difference between the previous and current frames.
 			bool calc_root = !seeked || is_external_seeking;
 #endif // _3D_DISABLED
 
@@ -1046,9 +1068,6 @@ void AnimationTree::_process_graph(double p_delta) {
 				int blend_idx = state.track_map[path];
 				ERR_CONTINUE(blend_idx < 0 || blend_idx >= state.track_count);
 				real_t blend = (*as.track_blends)[blend_idx] * weight;
-				if (Math::is_zero_approx(blend)) {
-					continue; // Nothing to blend.
-				}
 
 				Animation::TrackType ttype = a->track_get_type(i);
 				if (ttype != Animation::TYPE_POSITION_3D && ttype != Animation::TYPE_ROTATION_3D && ttype != Animation::TYPE_SCALE_3D && track->type != ttype) {
@@ -1060,6 +1079,9 @@ void AnimationTree::_process_graph(double p_delta) {
 				switch (ttype) {
 					case Animation::TYPE_POSITION_3D: {
 #ifndef _3D_DISABLED
+						if (Math::is_zero_approx(blend)) {
+							continue; // Nothing to blend.
+						}
 						TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track);
 						if (track->root_motion && calc_root) {
 							double prev_time = time - delta;
@@ -1151,6 +1173,9 @@ void AnimationTree::_process_graph(double p_delta) {
 					} break;
 					case Animation::TYPE_ROTATION_3D: {
 #ifndef _3D_DISABLED
+						if (Math::is_zero_approx(blend)) {
+							continue; // Nothing to blend.
+						}
 						TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track);
 						if (track->root_motion && calc_root) {
 							double prev_time = time - delta;
@@ -1241,6 +1266,9 @@ void AnimationTree::_process_graph(double p_delta) {
 					} break;
 					case Animation::TYPE_SCALE_3D: {
 #ifndef _3D_DISABLED
+						if (Math::is_zero_approx(blend)) {
+							continue; // Nothing to blend.
+						}
 						TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track);
 						if (track->root_motion && calc_root) {
 							double prev_time = time - delta;
@@ -1332,6 +1360,9 @@ void AnimationTree::_process_graph(double p_delta) {
 					} break;
 					case Animation::TYPE_BLEND_SHAPE: {
 #ifndef _3D_DISABLED
+						if (Math::is_zero_approx(blend)) {
+							continue; // Nothing to blend.
+						}
 						TrackCacheBlendShape *t = static_cast<TrackCacheBlendShape *>(track);
 
 						float value;
@@ -1348,6 +1379,9 @@ void AnimationTree::_process_graph(double p_delta) {
 #endif // _3D_DISABLED
 					} break;
 					case Animation::TYPE_VALUE: {
+						if (Math::is_zero_approx(blend)) {
+							continue; // Nothing to blend.
+						}
 						TrackCacheValue *t = static_cast<TrackCacheValue *>(track);
 
 						Animation::UpdateMode update_mode = a->value_track_get_update_mode(i);
@@ -1414,6 +1448,9 @@ void AnimationTree::_process_graph(double p_delta) {
 							continue;
 						}
 #endif // TOOLS_ENABLED
+						if (Math::is_zero_approx(blend)) {
+							continue; // Nothing to blend.
+						}
 						TrackCacheMethod *t = static_cast<TrackCacheMethod *>(track);
 
 						if (seeked) {
@@ -1435,6 +1472,9 @@ void AnimationTree::_process_graph(double p_delta) {
 						}
 					} break;
 					case Animation::TYPE_BEZIER: {
+						if (Math::is_zero_approx(blend)) {
+							continue; // Nothing to blend.
+						}
 						TrackCacheBezier *t = static_cast<TrackCacheBezier *>(track);
 
 						real_t bezier = a->bezier_track_interpolate(i, time);
@@ -1445,110 +1485,87 @@ void AnimationTree::_process_graph(double p_delta) {
 					case Animation::TYPE_AUDIO: {
 						TrackCacheAudio *t = static_cast<TrackCacheAudio *>(track);
 
-						if (seeked) {
-							int idx = a->track_find_key(i, time, is_external_seeking ? Animation::FIND_MODE_NEAREST : Animation::FIND_MODE_EXACT);
-							if (idx < 0) {
-								continue;
-							}
-
-							Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx);
-							if (!stream.is_valid()) {
-								t->object->call(SNAME("stop"));
-								t->playing = false;
-								playing_caches.erase(t);
-							} else {
-								double start_ofs = a->audio_track_get_key_start_offset(i, idx);
-								start_ofs += time - a->track_get_key_time(i, idx);
-								double end_ofs = a->audio_track_get_key_end_offset(i, idx);
-								double len = stream->get_length();
-
-								if (start_ofs > len - end_ofs) {
-									t->object->call(SNAME("stop"));
-									t->playing = false;
-									playing_caches.erase(t);
-									continue;
-								}
-
-								t->object->call(SNAME("set_stream"), stream);
-								t->object->call(SNAME("play"), start_ofs);
-
-								t->playing = true;
-								playing_caches.insert(t);
-								if (len && end_ofs > 0) { //force an end at a time
-									t->len = len - start_ofs - end_ofs;
-								} else {
-									t->len = 0;
-								}
+						Node *asp = Object::cast_to<Node>(t->object);
+						if (!asp) {
+							t->playing_streams.clear();
+							continue;
+						}
 
-								t->start = time;
+						ObjectID oid = a->get_instance_id();
+						if (!t->playing_streams.has(oid)) {
+							t->playing_streams[oid] = PlayingAudioTrackInfo();
+						}
+						// The end of audio should be observed even if the blend value is 0, build up the information and store to the cache for that.
+						PlayingAudioTrackInfo &track_info = t->playing_streams[oid];
+						track_info.length = a->get_length();
+						track_info.time = time;
+						track_info.volume += blend;
+						track_info.loop = a->get_loop_mode() != Animation::LOOP_NONE;
+						track_info.backward = backward;
+						track_info.use_blend = a->audio_track_is_use_blend(i);
+
+						HashMap<int, PlayingAudioStreamInfo> &map = track_info.stream_info;
+						// Find stream.
+						int idx = -1;
+						if (seeked) {
+							idx = a->track_find_key(i, time, is_external_seeking ? Animation::FIND_MODE_NEAREST : Animation::FIND_MODE_EXACT);
+							// Discard previous stream when seeking.
+							if (map.has(idx)) {
+								t->audio_stream_playback->stop_stream(map[idx].index);
+								map.erase(idx);
 							}
-
 						} else {
-							//find stuff to play
 							List<int> to_play;
 							a->track_get_key_indices_in_range(i, time, delta, &to_play, looped_flag);
 							if (to_play.size()) {
-								int idx = to_play.back()->get();
-
-								Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx);
-								if (!stream.is_valid()) {
-									t->object->call(SNAME("stop"));
-									t->playing = false;
-									playing_caches.erase(t);
-								} else {
-									double start_ofs = a->audio_track_get_key_start_offset(i, idx);
-									double end_ofs = a->audio_track_get_key_end_offset(i, idx);
-									double len = stream->get_length();
-
-									t->object->call(SNAME("set_stream"), stream);
-									t->object->call(SNAME("play"), start_ofs);
-
-									t->playing = true;
-									playing_caches.insert(t);
-									if (len && end_ofs > 0) { //force an end at a time
-										t->len = len - start_ofs - end_ofs;
-									} else {
-										t->len = 0;
-									}
+								idx = to_play.back()->get();
+							}
+						}
+						if (idx < 0) {
+							continue;
+						}
 
-									t->start = time;
-								}
-							} else if (t->playing) {
-								bool loop = a->get_loop_mode() != Animation::LOOP_NONE;
-
-								bool stop = false;
-
-								if (!loop) {
-									if (delta > 0) {
-										if (time < t->start) {
-											stop = true;
-										}
-									} else if (delta < 0) {
-										if (time > t->start) {
-											stop = true;
-										}
-									}
-								} else if (t->len > 0) {
-									double len = t->start > time ? (a->get_length() - t->start) + time : time - t->start;
-
-									if (len > t->len) {
-										stop = true;
-									}
+						// Play stream.
+						Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx);
+						if (stream.is_valid()) {
+							double start_ofs = a->audio_track_get_key_start_offset(i, idx);
+							double end_ofs = a->audio_track_get_key_end_offset(i, idx);
+							double len = stream->get_length();
+
+							if (t->object->call(SNAME("get_stream")) != t->audio_stream) {
+								t->object->call(SNAME("set_stream"), t->audio_stream);
+								t->audio_stream_playback.unref();
+								if (!playing_audio_stream_players.has(asp)) {
+									playing_audio_stream_players.push_back(asp);
 								}
+							}
+							if (!t->object->call(SNAME("is_playing"))) {
+								t->object->call(SNAME("play"));
+							}
+							if (!t->object->call(SNAME("has_stream_playback"))) {
+								t->audio_stream_playback.unref();
+								continue;
+							}
+							if (t->audio_stream_playback.is_null()) {
+								t->audio_stream_playback = t->object->call(SNAME("get_stream_playback"));
+							}
 
-								if (stop) {
-									//time to stop
-									t->object->call(SNAME("stop"));
-									t->playing = false;
-									playing_caches.erase(t);
-								}
+							PlayingAudioStreamInfo pasi;
+							pasi.index = t->audio_stream_playback->play_stream(stream, start_ofs);
+							pasi.start = time;
+							if (len && end_ofs > 0) { // Force an end at a time.
+								pasi.len = len - start_ofs - end_ofs;
+							} else {
+								pasi.len = 0;
 							}
+							map[idx] = pasi;
 						}
 
-						real_t db = Math::linear_to_db(MAX(blend, 0.00001));
-						t->object->call(SNAME("set_volume_db"), db);
 					} break;
 					case Animation::TYPE_ANIMATION: {
+						if (Math::is_zero_approx(blend)) {
+							continue; // Nothing to blend.
+						}
 						TrackCacheAnimation *t = static_cast<TrackCacheAnimation *>(track);
 
 						AnimationPlayer *player2 = Object::cast_to<AnimationPlayer>(t->object);
@@ -1694,6 +1711,64 @@ void AnimationTree::_process_graph(double p_delta) {
 					t->object->set_indexed(t->subpath, t->value);
 
 				} break;
+				case Animation::TYPE_AUDIO: {
+					TrackCacheAudio *t = static_cast<TrackCacheAudio *>(track);
+
+					// Audio ending process.
+					LocalVector<ObjectID> erase_maps;
+					for (KeyValue<ObjectID, PlayingAudioTrackInfo> &L : t->playing_streams) {
+						PlayingAudioTrackInfo &track_info = L.value;
+						float db = Math::linear_to_db(track_info.use_blend ? track_info.volume : 1.0);
+						LocalVector<int> erase_streams;
+						HashMap<int, PlayingAudioStreamInfo> &map = track_info.stream_info;
+						for (const KeyValue<int, PlayingAudioStreamInfo> &M : map) {
+							PlayingAudioStreamInfo pasi = M.value;
+
+							bool stop = false;
+							if (!t->audio_stream_playback->is_stream_playing(pasi.index)) {
+								stop = true;
+							}
+							if (!track_info.loop) {
+								if (!track_info.backward) {
+									if (track_info.time < pasi.start) {
+										stop = true;
+									}
+								} else if (track_info.backward) {
+									if (track_info.time > pasi.start) {
+										stop = true;
+									}
+								}
+							}
+							if (pasi.len > 0) {
+								double len = 0.0;
+								if (!track_info.backward) {
+									len = pasi.start > track_info.time ? (track_info.length - pasi.start) + track_info.time : track_info.time - pasi.start;
+								} else {
+									len = pasi.start < track_info.time ? (track_info.length - track_info.time) + pasi.start : pasi.start - track_info.time;
+								}
+								if (len > pasi.len) {
+									stop = true;
+								}
+							}
+							if (stop) {
+								// Time to stop.
+								t->audio_stream_playback->stop_stream(pasi.index);
+								erase_streams.push_back(M.key);
+							} else {
+								t->audio_stream_playback->set_stream_volume(pasi.index, db);
+							}
+						}
+						for (uint32_t erase_idx = 0; erase_idx < erase_streams.size(); erase_idx++) {
+							map.erase(erase_streams[erase_idx]);
+						}
+						if (map.size() == 0) {
+							erase_maps.push_back(L.key);
+						}
+					}
+					for (uint32_t erase_idx = 0; erase_idx < erase_maps.size(); erase_idx++) {
+						t->playing_streams.erase(erase_maps[erase_idx]);
+					}
+				} break;
 				default: {
 				} //the rest don't matter
 			}
@@ -1819,6 +1894,15 @@ NodePath AnimationTree::get_advance_expression_base_node() const {
 	return advance_expression_base_node;
 }
 
+void AnimationTree::set_audio_max_polyphony(int p_audio_max_polyphony) {
+	ERR_FAIL_COND(p_audio_max_polyphony < 0 || p_audio_max_polyphony > 128);
+	audio_max_polyphony = p_audio_max_polyphony;
+}
+
+int AnimationTree::get_audio_max_polyphony() const {
+	return audio_max_polyphony;
+}
+
 bool AnimationTree::is_state_invalid() const {
 	return !state.valid;
 }
@@ -2034,6 +2118,9 @@ void AnimationTree::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_root_motion_track", "path"), &AnimationTree::set_root_motion_track);
 	ClassDB::bind_method(D_METHOD("get_root_motion_track"), &AnimationTree::get_root_motion_track);
 
+	ClassDB::bind_method(D_METHOD("set_audio_max_polyphony", "max_polyphony"), &AnimationTree::set_audio_max_polyphony);
+	ClassDB::bind_method(D_METHOD("get_audio_max_polyphony"), &AnimationTree::get_audio_max_polyphony);
+
 	ClassDB::bind_method(D_METHOD("get_root_motion_position"), &AnimationTree::get_root_motion_position);
 	ClassDB::bind_method(D_METHOD("get_root_motion_rotation"), &AnimationTree::get_root_motion_rotation);
 	ClassDB::bind_method(D_METHOD("get_root_motion_scale"), &AnimationTree::get_root_motion_scale);
@@ -2052,6 +2139,8 @@ void AnimationTree::_bind_methods() {
 
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "active"), "set_active", "is_active");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "process_callback", PROPERTY_HINT_ENUM, "Physics,Idle,Manual"), "set_process_callback", "get_process_callback");
+	ADD_GROUP("Audio", "audio_");
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "audio_max_polyphony", PROPERTY_HINT_RANGE, "1,127,1"), "set_audio_max_polyphony", "get_audio_max_polyphony");
 	ADD_GROUP("Root Motion", "root_motion_");
 	ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "root_motion_track"), "set_root_motion_track", "get_root_motion_track");
 

+ 26 - 2
scene/animation/animation_tree.h

@@ -35,6 +35,7 @@
 #include "scene/3d/node_3d.h"
 #include "scene/3d/skeleton_3d.h"
 #include "scene/resources/animation.h"
+#include "scene/resources/audio_stream_polyphonic.h"
 
 class AnimationNodeBlendTree;
 class AnimationNodeStartState;
@@ -252,10 +253,26 @@ private:
 		}
 	};
 
-	struct TrackCacheAudio : public TrackCache {
-		bool playing = false;
+	struct PlayingAudioStreamInfo {
+		int64_t index = -1;
 		double start = 0.0;
 		double len = 0.0;
+	};
+
+	struct PlayingAudioTrackInfo {
+		HashMap<int, PlayingAudioStreamInfo> stream_info;
+		double length = 0.0;
+		double time = 0.0;
+		real_t volume = 0.0;
+		bool loop = false;
+		bool backward = false;
+		bool use_blend = false;
+	};
+
+	struct TrackCacheAudio : public TrackCache {
+		Ref<AudioStreamPolyphonic> audio_stream;
+		Ref<AudioStreamPlaybackPolyphonic> audio_stream_playback;
+		HashMap<ObjectID, PlayingAudioTrackInfo> playing_streams; // Animation resource RID & AudioTrack key index: PlayingAudioStreamInfo.
 
 		TrackCacheAudio() {
 			type = Animation::TYPE_AUDIO;
@@ -272,6 +289,7 @@ private:
 
 	HashMap<NodePath, TrackCache *> track_cache;
 	HashSet<TrackCache *> playing_caches;
+	Vector<Node *> playing_audio_stream_players;
 
 	Ref<AnimationNode> root;
 	NodePath advance_expression_base_node = NodePath(String("."));
@@ -279,6 +297,7 @@ private:
 	AnimationProcessCallback process_callback = ANIMATION_PROCESS_IDLE;
 	bool active = false;
 	NodePath animation_player;
+	int audio_max_polyphony = 32;
 
 	AnimationNode::State state;
 	bool cache_valid = false;
@@ -287,6 +306,8 @@ private:
 	void _setup_animation_player();
 	void _animation_player_changed();
 	void _clear_caches();
+	void _clear_playing_caches();
+	void _clear_audio_streams();
 	bool _update_caches(AnimationPlayer *player);
 	void _process_graph(double p_delta);
 
@@ -348,6 +369,9 @@ public:
 	void set_advance_expression_base_node(const NodePath &p_advance_expression_base_node);
 	NodePath get_advance_expression_base_node() const;
 
+	void set_audio_max_polyphony(int p_audio_max_polyphony);
+	int get_audio_max_polyphony() const;
+
 	PackedStringArray get_configuration_warnings() const override;
 
 	bool is_state_invalid() const;

+ 5 - 0
scene/audio/audio_stream_player.cpp

@@ -307,6 +307,10 @@ void AudioStreamPlayer::_bus_layout_changed() {
 	notify_property_list_changed();
 }
 
+bool AudioStreamPlayer::has_stream_playback() {
+	return !stream_playbacks.is_empty();
+}
+
 Ref<AudioStreamPlayback> AudioStreamPlayer::get_stream_playback() {
 	ERR_FAIL_COND_V_MSG(stream_playbacks.is_empty(), Ref<AudioStreamPlayback>(), "Player is inactive. Call play() before requesting get_stream_playback().");
 	return stream_playbacks[stream_playbacks.size() - 1];
@@ -347,6 +351,7 @@ void AudioStreamPlayer::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_max_polyphony", "max_polyphony"), &AudioStreamPlayer::set_max_polyphony);
 	ClassDB::bind_method(D_METHOD("get_max_polyphony"), &AudioStreamPlayer::get_max_polyphony);
 
+	ClassDB::bind_method(D_METHOD("has_stream_playback"), &AudioStreamPlayer::has_stream_playback);
 	ClassDB::bind_method(D_METHOD("get_stream_playback"), &AudioStreamPlayer::get_stream_playback);
 
 	ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream");

+ 1 - 0
scene/audio/audio_stream_player.h

@@ -107,6 +107,7 @@ public:
 	void set_stream_paused(bool p_pause);
 	bool get_stream_paused() const;
 
+	bool has_stream_playback();
 	Ref<AudioStreamPlayback> get_stream_playback();
 
 	AudioStreamPlayer();

+ 34 - 1
scene/resources/animation.cpp

@@ -127,6 +127,10 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) {
 				}
 			}
 			return true;
+		} else if (what == "use_blend") {
+			if (track_get_type(track) == TYPE_AUDIO) {
+				audio_track_set_use_blend(track, p_value);
+			}
 		} else if (what == "interp") {
 			track_set_interpolation_type(track, InterpolationType(p_value.operator int()));
 		} else if (what == "loop_wrap") {
@@ -528,7 +532,10 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const {
 			}
 
 			return true;
-
+		} else if (what == "use_blend") {
+			if (track_get_type(track) == TYPE_AUDIO) {
+				r_ret = audio_track_is_use_blend(track);
+			}
 		} else if (what == "interp") {
 			r_ret = track_get_interpolation_type(track);
 		} else if (what == "loop_wrap") {
@@ -834,6 +841,9 @@ void Animation::_get_property_list(List<PropertyInfo> *p_list) const {
 			p_list->push_back(PropertyInfo(Variant::BOOL, "tracks/" + itos(i) + "/loop_wrap", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
 			p_list->push_back(PropertyInfo(Variant::ARRAY, "tracks/" + itos(i) + "/keys", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
 		}
+		if (track_get_type(i) == TYPE_AUDIO) {
+			p_list->push_back(PropertyInfo(Variant::BOOL, "tracks/" + itos(i) + "/use_blend", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
+		}
 	}
 }
 
@@ -3581,6 +3591,27 @@ real_t Animation::audio_track_get_key_end_offset(int p_track, int p_key) const {
 	return at->values[p_key].value.end_offset;
 }
 
+void Animation::audio_track_set_use_blend(int p_track, bool p_enable) {
+	ERR_FAIL_INDEX(p_track, tracks.size());
+	Track *t = tracks[p_track];
+	ERR_FAIL_COND(t->type != TYPE_AUDIO);
+
+	AudioTrack *at = static_cast<AudioTrack *>(t);
+
+	at->use_blend = p_enable;
+	emit_changed();
+}
+
+bool Animation::audio_track_is_use_blend(int p_track) const {
+	ERR_FAIL_INDEX_V(p_track, tracks.size(), false);
+	Track *t = tracks[p_track];
+	ERR_FAIL_COND_V(t->type != TYPE_AUDIO, false);
+
+	AudioTrack *at = static_cast<AudioTrack *>(t);
+
+	return at->use_blend;
+}
+
 //
 
 int Animation::animation_track_insert_key(int p_track, double p_time, const StringName &p_animation) {
@@ -3813,6 +3844,8 @@ void Animation::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("audio_track_get_key_stream", "track_idx", "key_idx"), &Animation::audio_track_get_key_stream);
 	ClassDB::bind_method(D_METHOD("audio_track_get_key_start_offset", "track_idx", "key_idx"), &Animation::audio_track_get_key_start_offset);
 	ClassDB::bind_method(D_METHOD("audio_track_get_key_end_offset", "track_idx", "key_idx"), &Animation::audio_track_get_key_end_offset);
+	ClassDB::bind_method(D_METHOD("audio_track_set_use_blend", "track_idx", "enable"), &Animation::audio_track_set_use_blend);
+	ClassDB::bind_method(D_METHOD("audio_track_is_use_blend", "track_idx"), &Animation::audio_track_is_use_blend);
 
 	ClassDB::bind_method(D_METHOD("animation_track_insert_key", "track_idx", "time", "animation"), &Animation::animation_track_insert_key);
 	ClassDB::bind_method(D_METHOD("animation_track_set_key_animation", "track_idx", "key_idx", "animation"), &Animation::animation_track_set_key_animation);

+ 3 - 0
scene/resources/animation.h

@@ -213,6 +213,7 @@ private:
 
 	struct AudioTrack : public Track {
 		Vector<TKey<AudioKey>> values;
+		bool use_blend = true;
 
 		AudioTrack() {
 			type = TYPE_AUDIO;
@@ -447,6 +448,8 @@ public:
 	Ref<Resource> audio_track_get_key_stream(int p_track, int p_key) const;
 	real_t audio_track_get_key_start_offset(int p_track, int p_key) const;
 	real_t audio_track_get_key_end_offset(int p_track, int p_key) const;
+	void audio_track_set_use_blend(int p_track, bool p_enable);
+	bool audio_track_is_use_blend(int p_track) const;
 
 	int animation_track_insert_key(int p_track, double p_time, const StringName &p_animation);
 	void animation_track_set_key_animation(int p_track, int p_key, const StringName &p_animation);