Implement blending audio feature to AnimationTree

This commit is contained in:
Silc Renew 2023-01-28 03:25:49 +09:00
parent a43db5afa4
commit 75330887d7
22 changed files with 566 additions and 215 deletions

View file

@ -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" />

View file

@ -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>

View file

@ -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>

View file

@ -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" />

View file

@ -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" />

View file

@ -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" />

View file

@ -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);

View file

@ -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;

View file

@ -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>

After

Width:  |  Height:  |  Size: 361 B

View file

@ -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>

After

Width:  |  Height:  |  Size: 422 B

View file

@ -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");

View file

@ -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();

View file

@ -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");

View file

@ -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();

View file

@ -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;
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?
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;
}
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);
}
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);
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 {
//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();
idx = to_play.back()->get();
}
}
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);
float end_ofs = a->audio_track_get_key_end_offset(i, idx);
float len = stream->get_length();
// 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();
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;
if (len > nc->audio_len) {
stop = true;
}
}
if (stop) {
//time to stop
nc->node->call(SNAME("stop"));
nc->audio_playing = false;
playing_caches.erase(nc);
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"));
}
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");

View file

@ -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;

View file

@ -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);
Node *asp = Object::cast_to<Node>(t->object);
if (!asp) {
t->playing_streams.clear();
continue;
}
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) {
int idx = a->track_find_key(i, time, is_external_seeking ? Animation::FIND_MODE_NEAREST : Animation::FIND_MODE_EXACT);
if (idx < 0) {
continue;
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);
}
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;
}
t->start = time;
}
} 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;
}
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;
}
}
if (stop) {
//time to stop
t->object->call(SNAME("stop"));
t->playing = false;
playing_caches.erase(t);
}
idx = to_play.back()->get();
}
}
if (idx < 0) {
continue;
}
// 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"));
}
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");

View file

@ -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;

View file

@ -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");

View file

@ -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();

View file

@ -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);

View file

@ -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);