/**************************************************************************/ /* audio_stream_interactive.cpp */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /**************************************************************************/ /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ /* "Software"), to deal in the Software without restriction, including */ /* without limitation the rights to use, copy, modify, merge, publish, */ /* distribute, sublicense, and/or sell copies of the Software, and to */ /* permit persons to whom the Software is furnished to do so, subject to */ /* the following conditions: */ /* */ /* The above copyright notice and this permission notice shall be */ /* included in all copies or substantial portions of the Software. */ /* */ /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ #include "audio_stream_interactive.h" #include "core/math/math_funcs.h" #include "core/string/print_string.h" AudioStreamInteractive::AudioStreamInteractive() { } Ref AudioStreamInteractive::instantiate_playback() { Ref playback_transitioner; playback_transitioner.instantiate(); playback_transitioner->stream = Ref(this); return playback_transitioner; } String AudioStreamInteractive::get_stream_name() const { return "Transitioner"; } void AudioStreamInteractive::set_clip_count(int p_count) { ERR_FAIL_COND(p_count < 0 || p_count > MAX_CLIPS); AudioServer::get_singleton()->lock(); if (p_count < clip_count) { // Removing should stop players. version++; } #ifdef TOOLS_ENABLED stream_name_cache = ""; if (p_count < clip_count) { for (int i = 0; i < clip_count; i++) { if (clips[i].auto_advance_next_clip >= p_count) { clips[i].auto_advance_next_clip = 0; clips[i].auto_advance = AUTO_ADVANCE_DISABLED; } } for (KeyValue &K : transition_map) { if (K.value.filler_clip >= p_count) { K.value.use_filler_clip = false; K.value.filler_clip = 0; } } if (initial_clip >= p_count) { initial_clip = 0; } } #endif clip_count = p_count; AudioServer::get_singleton()->unlock(); notify_property_list_changed(); emit_signal(SNAME("parameter_list_changed")); } void AudioStreamInteractive::set_initial_clip(int p_clip) { ERR_FAIL_INDEX(p_clip, clip_count); initial_clip = p_clip; } int AudioStreamInteractive::get_initial_clip() const { return initial_clip; } int AudioStreamInteractive::get_clip_count() const { return clip_count; } void AudioStreamInteractive::set_clip_name(int p_clip, const StringName &p_name) { ERR_FAIL_INDEX(p_clip, MAX_CLIPS); clips[p_clip].name = p_name; } StringName AudioStreamInteractive::get_clip_name(int p_clip) const { ERR_FAIL_COND_V(p_clip < -1 || p_clip >= MAX_CLIPS, StringName()); if (p_clip == CLIP_ANY) { return RTR("All Clips"); } return clips[p_clip].name; } void AudioStreamInteractive::set_clip_stream(int p_clip, const Ref &p_stream) { ERR_FAIL_INDEX(p_clip, MAX_CLIPS); AudioServer::get_singleton()->lock(); if (clips[p_clip].stream.is_valid()) { version++; } clips[p_clip].stream = p_stream; AudioServer::get_singleton()->unlock(); #ifdef TOOLS_ENABLED if (Engine::get_singleton()->is_editor_hint()) { if (clips[p_clip].name == StringName() && p_stream.is_valid()) { String n; if (!clips[p_clip].stream->get_name().is_empty()) { n = clips[p_clip].stream->get_name().replace(",", " "); } else if (clips[p_clip].stream->get_path().is_resource_file()) { n = clips[p_clip].stream->get_path().get_file().get_basename().replace(",", " "); n = n.capitalize(); } if (n != "") { clips[p_clip].name = n; } } } #endif #ifdef TOOLS_ENABLED stream_name_cache = ""; notify_property_list_changed(); // Hints change if stream changes. emit_signal(SNAME("parameter_list_changed")); #endif } Ref AudioStreamInteractive::get_clip_stream(int p_clip) const { ERR_FAIL_INDEX_V(p_clip, MAX_CLIPS, Ref()); return clips[p_clip].stream; } void AudioStreamInteractive::set_clip_auto_advance(int p_clip, AutoAdvanceMode p_mode) { ERR_FAIL_INDEX(p_clip, MAX_CLIPS); ERR_FAIL_INDEX(p_mode, 3); clips[p_clip].auto_advance = p_mode; notify_property_list_changed(); } AudioStreamInteractive::AutoAdvanceMode AudioStreamInteractive::get_clip_auto_advance(int p_clip) const { ERR_FAIL_INDEX_V(p_clip, MAX_CLIPS, AUTO_ADVANCE_DISABLED); return clips[p_clip].auto_advance; } void AudioStreamInteractive::set_clip_auto_advance_next_clip(int p_clip, int p_index) { ERR_FAIL_INDEX(p_clip, MAX_CLIPS); clips[p_clip].auto_advance_next_clip = p_index; } int AudioStreamInteractive::get_clip_auto_advance_next_clip(int p_clip) const { ERR_FAIL_INDEX_V(p_clip, MAX_CLIPS, -1); return clips[p_clip].auto_advance_next_clip; } // TRANSITIONS void AudioStreamInteractive::_set_transitions(const Dictionary &p_transitions) { List keys; p_transitions.get_key_list(&keys); for (const Variant &K : keys) { Vector2i k = K; Dictionary data = p_transitions[K]; ERR_CONTINUE(!data.has("from_time")); ERR_CONTINUE(!data.has("to_time")); ERR_CONTINUE(!data.has("fade_mode")); ERR_CONTINUE(!data.has("fade_beats")); bool use_filler_clip = false; int filler_clip = 0; if (data.has("use_filler_clip") && data.has("filler_clip")) { use_filler_clip = data["use_filler_clip"]; filler_clip = data["filler_clip"]; } bool hold_previous = data.has("hold_previous") ? bool(data["hold_previous"]) : false; add_transition(k.x, k.y, TransitionFromTime(int(data["from_time"])), TransitionToTime(int(data["to_time"])), FadeMode(int(data["fade_mode"])), data["fade_beats"], use_filler_clip, filler_clip, hold_previous); } } Dictionary AudioStreamInteractive::_get_transitions() const { Vector keys; for (const KeyValue &K : transition_map) { keys.push_back(Vector2i(K.key.from_clip, K.key.to_clip)); } keys.sort(); Dictionary ret; for (int i = 0; i < keys.size(); i++) { const Transition &tr = transition_map[TransitionKey(keys[i].x, keys[i].y)]; Dictionary data; data["from_time"] = tr.from_time; data["to_time"] = tr.to_time; data["fade_mode"] = tr.fade_mode; data["fade_beats"] = tr.fade_beats; if (tr.use_filler_clip) { data["use_filler_clip"] = true; data["filler_clip"] = tr.filler_clip; } if (tr.hold_previous) { data["hold_previous"] = true; } ret[keys[i]] = data; } return ret; } bool AudioStreamInteractive::has_transition(int p_from_clip, int p_to_clip) const { TransitionKey tk(p_from_clip, p_to_clip); return transition_map.has(tk); } void AudioStreamInteractive::erase_transition(int p_from_clip, int p_to_clip) { TransitionKey tk(p_from_clip, p_to_clip); ERR_FAIL_COND(!transition_map.has(tk)); AudioDriver::get_singleton()->lock(); transition_map.erase(tk); AudioDriver::get_singleton()->unlock(); } PackedInt32Array AudioStreamInteractive::get_transition_list() const { PackedInt32Array ret; for (const KeyValue &K : transition_map) { ret.push_back(K.key.from_clip); ret.push_back(K.key.to_clip); } return ret; } void AudioStreamInteractive::add_transition(int p_from_clip, int p_to_clip, TransitionFromTime p_from_time, TransitionToTime p_to_time, FadeMode p_fade_mode, float p_fade_beats, bool p_use_filler_flip, int p_filler_clip, bool p_hold_previous) { ERR_FAIL_COND(p_from_clip < CLIP_ANY || p_from_clip >= clip_count); ERR_FAIL_COND(p_to_clip < CLIP_ANY || p_to_clip >= clip_count); ERR_FAIL_UNSIGNED_INDEX(p_from_time, TRANSITION_FROM_TIME_MAX); ERR_FAIL_UNSIGNED_INDEX(p_to_time, TRANSITION_TO_TIME_MAX); ERR_FAIL_UNSIGNED_INDEX(p_fade_mode, FADE_MAX); Transition tr; tr.from_time = p_from_time; tr.to_time = p_to_time; tr.fade_mode = p_fade_mode; tr.fade_beats = p_fade_beats; tr.use_filler_clip = p_use_filler_flip; tr.filler_clip = p_filler_clip; tr.hold_previous = p_hold_previous; TransitionKey tk(p_from_clip, p_to_clip); AudioDriver::get_singleton()->lock(); transition_map[tk] = tr; AudioDriver::get_singleton()->unlock(); } AudioStreamInteractive::TransitionFromTime AudioStreamInteractive::get_transition_from_time(int p_from_clip, int p_to_clip) const { TransitionKey tk(p_from_clip, p_to_clip); ERR_FAIL_COND_V(!transition_map.has(tk), TRANSITION_FROM_TIME_END); return transition_map[tk].from_time; } AudioStreamInteractive::TransitionToTime AudioStreamInteractive::get_transition_to_time(int p_from_clip, int p_to_clip) const { TransitionKey tk(p_from_clip, p_to_clip); ERR_FAIL_COND_V(!transition_map.has(tk), TRANSITION_TO_TIME_START); return transition_map[tk].to_time; } AudioStreamInteractive::FadeMode AudioStreamInteractive::get_transition_fade_mode(int p_from_clip, int p_to_clip) const { TransitionKey tk(p_from_clip, p_to_clip); ERR_FAIL_COND_V(!transition_map.has(tk), FADE_DISABLED); return transition_map[tk].fade_mode; } float AudioStreamInteractive::get_transition_fade_beats(int p_from_clip, int p_to_clip) const { TransitionKey tk(p_from_clip, p_to_clip); ERR_FAIL_COND_V(!transition_map.has(tk), -1); return transition_map[tk].fade_beats; } bool AudioStreamInteractive::is_transition_using_filler_clip(int p_from_clip, int p_to_clip) const { TransitionKey tk(p_from_clip, p_to_clip); ERR_FAIL_COND_V(!transition_map.has(tk), false); return transition_map[tk].use_filler_clip; } int AudioStreamInteractive::get_transition_filler_clip(int p_from_clip, int p_to_clip) const { TransitionKey tk(p_from_clip, p_to_clip); ERR_FAIL_COND_V(!transition_map.has(tk), -1); return transition_map[tk].filler_clip; } bool AudioStreamInteractive::is_transition_holding_previous(int p_from_clip, int p_to_clip) const { TransitionKey tk(p_from_clip, p_to_clip); ERR_FAIL_COND_V(!transition_map.has(tk), false); return transition_map[tk].hold_previous; } #ifdef TOOLS_ENABLED PackedStringArray AudioStreamInteractive::_get_linked_undo_properties(const String &p_property, const Variant &p_new_value) const { PackedStringArray ret; if (p_property.begins_with("clip_") && p_property.ends_with("/stream")) { int clip = p_property.get_slicec('_', 1).to_int(); if (clip < clip_count) { ret.push_back("clip_" + itos(clip) + "/name"); } } if (p_property == "clip_count") { int new_clip_count = p_new_value; if (new_clip_count < clip_count) { for (int i = 0; i < clip_count; i++) { if (clips[i].auto_advance_next_clip >= new_clip_count) { ret.push_back("clip_" + itos(i) + "/auto_advance"); ret.push_back("clip_" + itos(i) + "/next_clip"); } } ret.push_back("_transitions"); if (initial_clip >= new_clip_count) { ret.push_back("initial_clip"); } } } return ret; } template static void _test_and_swap(T &p_elem, uint32_t p_a, uint32_t p_b) { if ((uint32_t)p_elem == p_a) { p_elem = p_b; } else if (uint32_t(p_elem) == p_b) { p_elem = p_a; } } void AudioStreamInteractive::_inspector_array_swap_clip(uint32_t p_item_a, uint32_t p_item_b) { ERR_FAIL_UNSIGNED_INDEX(p_item_a, (uint32_t)clip_count); ERR_FAIL_UNSIGNED_INDEX(p_item_b, (uint32_t)clip_count); for (int i = 0; i < clip_count; i++) { _test_and_swap(clips[i].auto_advance_next_clip, p_item_a, p_item_b); } Vector to_remove; HashMap to_add; for (KeyValue &K : transition_map) { if (K.key.from_clip == p_item_a || K.key.from_clip == p_item_b || K.key.to_clip == p_item_a || K.key.to_clip == p_item_b) { to_remove.push_back(K.key); TransitionKey new_key = K.key; _test_and_swap(new_key.from_clip, p_item_a, p_item_b); _test_and_swap(new_key.to_clip, p_item_a, p_item_b); to_add[new_key] = K.value; } } for (int i = 0; i < to_remove.size(); i++) { transition_map.erase(to_remove[i]); } for (KeyValue &K : to_add) { transition_map.insert(K.key, K.value); } SWAP(clips[p_item_a], clips[p_item_b]); stream_name_cache = ""; notify_property_list_changed(); emit_signal(SNAME("parameter_list_changed")); } String AudioStreamInteractive::_get_streams_hint() const { if (!stream_name_cache.is_empty()) { return stream_name_cache; } for (int i = 0; i < clip_count; i++) { if (i > 0) { stream_name_cache += ","; } String n = String(clips[i].name).replace(",", " "); if (n == "" && clips[i].stream.is_valid()) { if (!clips[i].stream->get_name().is_empty()) { n = clips[i].stream->get_name().replace(",", " "); } else if (clips[i].stream->get_path().is_resource_file()) { n = clips[i].stream->get_path().get_file().replace(",", " "); } } if (n == "") { n = "Clip " + itos(i); } stream_name_cache += n; } return stream_name_cache; } #endif void AudioStreamInteractive::_validate_property(PropertyInfo &r_property) const { String prop = r_property.name; #ifdef TOOLS_ENABLED if (prop == "switch_to") { r_property.hint_string = _get_streams_hint(); return; } #endif if (prop == "initial_clip") { #ifdef TOOLS_ENABLED r_property.hint_string = _get_streams_hint(); #endif } else if (prop.begins_with("clip_") && prop != "clip_count") { int clip = prop.get_slicec('_', 1).to_int(); if (clip >= clip_count) { r_property.usage = PROPERTY_USAGE_INTERNAL; } else if (prop == "clip_" + itos(clip) + "/next_clip") { if (clips[clip].auto_advance != AUTO_ADVANCE_ENABLED) { r_property.usage = 0; } else { #ifdef TOOLS_ENABLED r_property.hint_string = _get_streams_hint(); #endif } } } } void AudioStreamInteractive::get_parameter_list(List *r_parameters) { String clip_names; for (int i = 0; i < clip_count; i++) { clip_names += ","; clip_names += clips[i].name; } r_parameters->push_back(Parameter(PropertyInfo(Variant::STRING, "switch_to_clip", PROPERTY_HINT_ENUM, clip_names, PROPERTY_USAGE_EDITOR), "")); } void AudioStreamInteractive::_bind_methods() { #ifdef TOOLS_ENABLED ClassDB::bind_method(D_METHOD("_get_linked_undo_properties", "for_property", "for_value"), &AudioStreamInteractive::_get_linked_undo_properties); ClassDB::bind_method(D_METHOD("_inspector_array_swap_clip", "a", "b"), &AudioStreamInteractive::_inspector_array_swap_clip); #endif // CLIPS ClassDB::bind_method(D_METHOD("set_clip_count", "clip_count"), &AudioStreamInteractive::set_clip_count); ClassDB::bind_method(D_METHOD("get_clip_count"), &AudioStreamInteractive::get_clip_count); ClassDB::bind_method(D_METHOD("set_initial_clip", "clip_index"), &AudioStreamInteractive::set_initial_clip); ClassDB::bind_method(D_METHOD("get_initial_clip"), &AudioStreamInteractive::get_initial_clip); ClassDB::bind_method(D_METHOD("set_clip_name", "clip_index", "name"), &AudioStreamInteractive::set_clip_name); ClassDB::bind_method(D_METHOD("get_clip_name", "clip_index"), &AudioStreamInteractive::get_clip_name); ClassDB::bind_method(D_METHOD("set_clip_stream", "clip_index", "stream"), &AudioStreamInteractive::set_clip_stream); ClassDB::bind_method(D_METHOD("get_clip_stream", "clip_index"), &AudioStreamInteractive::get_clip_stream); ClassDB::bind_method(D_METHOD("set_clip_auto_advance", "clip_index", "mode"), &AudioStreamInteractive::set_clip_auto_advance); ClassDB::bind_method(D_METHOD("get_clip_auto_advance", "clip_index"), &AudioStreamInteractive::get_clip_auto_advance); ClassDB::bind_method(D_METHOD("set_clip_auto_advance_next_clip", "clip_index", "auto_advance_next_clip"), &AudioStreamInteractive::set_clip_auto_advance_next_clip); ClassDB::bind_method(D_METHOD("get_clip_auto_advance_next_clip", "clip_index"), &AudioStreamInteractive::get_clip_auto_advance_next_clip); ADD_PROPERTY(PropertyInfo(Variant::INT, "initial_clip", PROPERTY_HINT_ENUM, "", PROPERTY_USAGE_DEFAULT), "set_initial_clip", "get_initial_clip"); ADD_PROPERTY(PropertyInfo(Variant::INT, "clip_count", PROPERTY_HINT_RANGE, "1," + itos(MAX_CLIPS), PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_ARRAY, "Clips,clip_,page_size=999,unfoldable,numbered,swap_method=_inspector_array_swap_clip,add_button_text=" + String(RTR("Add Clip"))), "set_clip_count", "get_clip_count"); for (int i = 0; i < MAX_CLIPS; i++) { ADD_PROPERTYI(PropertyInfo(Variant::STRING_NAME, "clip_" + itos(i) + "/name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL), "set_clip_name", "get_clip_name", i); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "clip_" + itos(i) + "/stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL), "set_clip_stream", "get_clip_stream", i); ADD_PROPERTYI(PropertyInfo(Variant::INT, "clip_" + itos(i) + "/auto_advance", PROPERTY_HINT_ENUM, "Disabled,Enabled,ReturnToHold", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL), "set_clip_auto_advance", "get_clip_auto_advance", i); ADD_PROPERTYI(PropertyInfo(Variant::INT, "clip_" + itos(i) + "/next_clip", PROPERTY_HINT_ENUM, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL), "set_clip_auto_advance_next_clip", "get_clip_auto_advance_next_clip", i); } // TRANSITIONS ClassDB::bind_method(D_METHOD("add_transition", "from_clip", "to_clip", "from_time", "to_time", "fade_mode", "fade_beats", "use_filler_clip", "filler_clip", "hold_previous"), &AudioStreamInteractive::add_transition, DEFVAL(false), DEFVAL(-1), DEFVAL(false)); ClassDB::bind_method(D_METHOD("has_transition", "from_clip", "to_clip"), &AudioStreamInteractive::has_transition); ClassDB::bind_method(D_METHOD("erase_transition", "from_clip", "to_clip"), &AudioStreamInteractive::erase_transition); ClassDB::bind_method(D_METHOD("get_transition_list"), &AudioStreamInteractive::get_transition_list); ClassDB::bind_method(D_METHOD("get_transition_from_time", "from_clip", "to_clip"), &AudioStreamInteractive::get_transition_from_time); ClassDB::bind_method(D_METHOD("get_transition_to_time", "from_clip", "to_clip"), &AudioStreamInteractive::get_transition_to_time); ClassDB::bind_method(D_METHOD("get_transition_fade_mode", "from_clip", "to_clip"), &AudioStreamInteractive::get_transition_fade_mode); ClassDB::bind_method(D_METHOD("get_transition_fade_beats", "from_clip", "to_clip"), &AudioStreamInteractive::get_transition_fade_beats); ClassDB::bind_method(D_METHOD("is_transition_using_filler_clip", "from_clip", "to_clip"), &AudioStreamInteractive::is_transition_using_filler_clip); ClassDB::bind_method(D_METHOD("get_transition_filler_clip", "from_clip", "to_clip"), &AudioStreamInteractive::get_transition_filler_clip); ClassDB::bind_method(D_METHOD("is_transition_holding_previous", "from_clip", "to_clip"), &AudioStreamInteractive::is_transition_holding_previous); ClassDB::bind_method(D_METHOD("_set_transitions", "transitions"), &AudioStreamInteractive::_set_transitions); ClassDB::bind_method(D_METHOD("_get_transitions"), &AudioStreamInteractive::_get_transitions); ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "_transitions", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_transitions", "_get_transitions"); BIND_ENUM_CONSTANT(TRANSITION_FROM_TIME_IMMEDIATE); BIND_ENUM_CONSTANT(TRANSITION_FROM_TIME_NEXT_BEAT); BIND_ENUM_CONSTANT(TRANSITION_FROM_TIME_NEXT_BAR); BIND_ENUM_CONSTANT(TRANSITION_FROM_TIME_END); BIND_ENUM_CONSTANT(TRANSITION_TO_TIME_SAME_POSITION); BIND_ENUM_CONSTANT(TRANSITION_TO_TIME_START); BIND_ENUM_CONSTANT(FADE_DISABLED); BIND_ENUM_CONSTANT(FADE_IN); BIND_ENUM_CONSTANT(FADE_OUT); BIND_ENUM_CONSTANT(FADE_CROSS); BIND_ENUM_CONSTANT(FADE_AUTOMATIC); BIND_ENUM_CONSTANT(AUTO_ADVANCE_DISABLED); BIND_ENUM_CONSTANT(AUTO_ADVANCE_ENABLED); BIND_ENUM_CONSTANT(AUTO_ADVANCE_RETURN_TO_HOLD); BIND_CONSTANT(CLIP_ANY); } /////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////// AudioStreamPlaybackInteractive::AudioStreamPlaybackInteractive() { } AudioStreamPlaybackInteractive::~AudioStreamPlaybackInteractive() { } void AudioStreamPlaybackInteractive::stop() { if (!active) { return; } active = false; for (int i = 0; i < AudioStreamInteractive::MAX_CLIPS; i++) { if (states[i].playback.is_valid()) { states[i].playback->stop(); } states[i].fade_speed = 0.0; states[i].fade_volume = 0.0; states[i].fade_wait = 0.0; states[i].reset_fade(); states[i].active = false; states[i].auto_advance = -1; states[i].first_mix = true; } } void AudioStreamPlaybackInteractive::start(double p_from_pos) { if (active) { stop(); } if (version != stream->version) { for (int i = 0; i < AudioStreamInteractive::MAX_CLIPS; i++) { Ref src_stream; if (i < stream->clip_count) { src_stream = stream->clips[i].stream; } if (states[i].stream != src_stream) { states[i].stream.unref(); states[i].playback.unref(); states[i].stream = src_stream; states[i].playback = src_stream->instantiate_playback(); } } version = stream->version; } int current = stream->initial_clip; if (current < 0 || current >= stream->clip_count) { return; // No playback possible. } if (!states[current].playback.is_valid()) { return; //no playback possible } active = true; _queue(current, false); } void AudioStreamPlaybackInteractive::_queue(int p_to_clip_index, bool p_is_auto_advance) { ERR_FAIL_INDEX(p_to_clip_index, stream->clip_count); ERR_FAIL_COND(states[p_to_clip_index].playback.is_null()); if (playback_current == -1) { // Nothing to do, start. int current = p_to_clip_index; State &state = states[current]; state.active = true; state.fade_wait = 0; state.fade_volume = 1.0; state.fade_speed = 0; state.first_mix = true; state.playback->start(0); playback_current = current; if (stream->clips[current].auto_advance == AudioStreamInteractive::AUTO_ADVANCE_ENABLED && stream->clips[current].auto_advance_next_clip >= 0 && stream->clips[current].auto_advance_next_clip < stream->clip_count && stream->clips[current].auto_advance_next_clip != current) { //prepare auto advance state.auto_advance = stream->clips[current].auto_advance_next_clip; } return; } for (int i = 0; i < stream->clip_count; i++) { if (i == playback_current || i == p_to_clip_index) { continue; } if (states[i].active && states[i].fade_wait > 0) { // Waiting to kick in, terminate because change of plans. states[i].playback->stop(); states[i].reset_fade(); states[i].active = false; } } State &from_state = states[playback_current]; State &to_state = states[p_to_clip_index]; AudioStreamInteractive::Transition transition; // Use an empty transition by default AudioStreamInteractive::TransitionKey tkeys[4] = { AudioStreamInteractive::TransitionKey(playback_current, p_to_clip_index), AudioStreamInteractive::TransitionKey(playback_current, AudioStreamInteractive::CLIP_ANY), AudioStreamInteractive::TransitionKey(AudioStreamInteractive::CLIP_ANY, p_to_clip_index), AudioStreamInteractive::TransitionKey(AudioStreamInteractive::CLIP_ANY, AudioStreamInteractive::CLIP_ANY) }; for (int i = 0; i < 4; i++) { if (stream->transition_map.has(tkeys[i])) { transition = stream->transition_map[tkeys[i]]; break; } } if (transition.fade_mode == AudioStreamInteractive::FADE_AUTOMATIC) { // Adjust automatic mode based on context. if (transition.to_time == AudioStreamInteractive::TRANSITION_TO_TIME_START) { transition.fade_mode = AudioStreamInteractive::FADE_OUT; } else { transition.fade_mode = AudioStreamInteractive::FADE_CROSS; } } if (p_is_auto_advance) { transition.from_time = AudioStreamInteractive::TRANSITION_FROM_TIME_END; if (transition.to_time == AudioStreamInteractive::TRANSITION_TO_TIME_SAME_POSITION) { transition.to_time = AudioStreamInteractive::TRANSITION_TO_TIME_START; } } // Prepare the fadeout float current_pos = from_state.playback->get_playback_position(); float src_fade_wait = 0; float dst_seek_to = 0; float fade_speed = 0; bool src_no_loop = false; if (from_state.stream->get_bpm()) { // Check if source speed has BPM, if so, transition syncs to BPM float beat_sec = 60 / float(from_state.stream->get_bpm()); switch (transition.from_time) { case AudioStreamInteractive::TRANSITION_FROM_TIME_IMMEDIATE: { src_fade_wait = 0; } break; case AudioStreamInteractive::TRANSITION_FROM_TIME_NEXT_BEAT: { float remainder = Math::fmod(current_pos, beat_sec); src_fade_wait = beat_sec - remainder; } break; case AudioStreamInteractive::TRANSITION_FROM_TIME_NEXT_BAR: { float bar_sec = beat_sec * from_state.stream->get_bar_beats(); float remainder = Math::fmod(current_pos, bar_sec); src_fade_wait = bar_sec - remainder; } break; case AudioStreamInteractive::TRANSITION_FROM_TIME_END: { float end = from_state.stream->get_beat_count() > 0 ? float(from_state.stream->get_beat_count() * beat_sec) : from_state.stream->get_length(); if (end == 0) { // Stream does not have a length. src_fade_wait = 0; } else { src_fade_wait = end - current_pos; } if (!from_state.stream->has_loop()) { src_no_loop = true; } } break; default: { } } // Fade speed also aligned to BPM fade_speed = 1.0 / (transition.fade_beats * beat_sec); } else { // Source has no BPM, so just simple transition. if (transition.from_time == AudioStreamInteractive::TRANSITION_FROM_TIME_END && from_state.stream->get_length() > 0) { float end = from_state.stream->get_length(); src_fade_wait = end - current_pos; if (!from_state.stream->has_loop()) { src_no_loop = true; } } else { src_fade_wait = 0; } fade_speed = 1.0 / transition.fade_beats; } if (transition.to_time == AudioStreamInteractive::TRANSITION_TO_TIME_PREVIOUS_POSITION && to_state.stream->get_length() > 0.0) { dst_seek_to = to_state.previous_position; } else if (transition.to_time == AudioStreamInteractive::TRANSITION_TO_TIME_SAME_POSITION && transition.from_time != AudioStreamInteractive::TRANSITION_FROM_TIME_END && to_state.stream->get_length() > 0.0) { // Seeking to basically same position as when we start fading. dst_seek_to = current_pos + src_fade_wait; float end; if (to_state.stream->get_bpm() > 0 && to_state.stream->get_beat_count()) { float beat_sec = 60 / float(to_state.stream->get_bpm()); end = to_state.stream->get_beat_count() * beat_sec; } else { end = to_state.stream->get_length(); } if (dst_seek_to > end) { // Seeking too far away. dst_seek_to = 0; //past end, loop to beginning. } } else { // Seek to Start dst_seek_to = 0.0; } if (transition.fade_mode == AudioStreamInteractive::FADE_DISABLED || transition.fade_mode == AudioStreamInteractive::FADE_IN) { if (src_no_loop) { // If there is no fade in the source stream, then let it continue until it ends. from_state.fade_wait = 0; from_state.fade_speed = 0; } else { // Otherwise force a very quick fade to avoid clicks from_state.fade_wait = src_fade_wait; from_state.fade_speed = 1.0 / -0.001; } } else { // Regular fade. from_state.fade_wait = src_fade_wait; from_state.fade_speed = -fade_speed; } // keep volume, since it may have been fading in from something else. to_state.playback->start(dst_seek_to); to_state.active = true; to_state.fade_volume = 0.0; to_state.first_mix = true; int auto_advance_to = -1; if (stream->clips[p_to_clip_index].auto_advance == AudioStreamInteractive::AUTO_ADVANCE_ENABLED) { int next_clip = stream->clips[p_to_clip_index].auto_advance_next_clip; if (next_clip >= 0 && next_clip < (int)stream->clip_count && states[next_clip].playback.is_valid() && next_clip != p_to_clip_index && (!transition.use_filler_clip || next_clip != transition.filler_clip)) { auto_advance_to = next_clip; } } if (return_memory != -1 && stream->clips[p_to_clip_index].auto_advance == AudioStreamInteractive::AUTO_ADVANCE_RETURN_TO_HOLD) { auto_advance_to = return_memory; return_memory = -1; } if (transition.hold_previous) { return_memory = playback_current; } if (transition.use_filler_clip && transition.filler_clip >= 0 && transition.filler_clip < (int)stream->clip_count && states[transition.filler_clip].playback.is_valid() && playback_current != transition.filler_clip && p_to_clip_index != transition.filler_clip) { State &filler_state = states[transition.filler_clip]; filler_state.playback->start(0); filler_state.active = true; // Filler state does not fade (bake fade in the audio clip if you want fading. filler_state.fade_volume = 1.0; filler_state.fade_speed = 0.0; filler_state.fade_wait = src_fade_wait; filler_state.first_mix = true; float filler_end; if (filler_state.stream->get_bpm() > 0 && filler_state.stream->get_beat_count() > 0) { float filler_beat_sec = 60 / float(filler_state.stream->get_bpm()); filler_end = filler_beat_sec * filler_state.stream->get_beat_count(); } else { filler_end = filler_state.stream->get_length(); } if (!filler_state.stream->has_loop()) { src_no_loop = true; } if (transition.fade_mode == AudioStreamInteractive::FADE_DISABLED || transition.fade_mode == AudioStreamInteractive::FADE_OUT) { // No fading, immediately start at full volume. to_state.fade_volume = 0.0; to_state.fade_speed = 1.0; //start at full volume, as filler is meant as a transition. } else { // Fade enable, prepare fade. to_state.fade_volume = 0.0; to_state.fade_speed = fade_speed; } to_state.fade_wait = src_fade_wait + filler_end; } else { to_state.fade_wait = src_fade_wait; if (transition.fade_mode == AudioStreamInteractive::FADE_DISABLED || transition.fade_mode == AudioStreamInteractive::FADE_OUT) { to_state.fade_volume = 1.0; to_state.fade_speed = 0.0; } else { to_state.fade_volume = 0.0; to_state.fade_speed = fade_speed; } to_state.auto_advance = auto_advance_to; } } void AudioStreamPlaybackInteractive::seek(double p_time) { // Seek not supported } int AudioStreamPlaybackInteractive::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) { if (active && version != stream->version) { stop(); } if (switch_request != -1) { _queue(switch_request, false); switch_request = -1; } if (!active) { return 0; } int todo = p_frames; while (todo) { int to_mix = MIN(todo, BUFFER_SIZE); _mix_internal(to_mix); for (int i = 0; i < to_mix; i++) { p_buffer[i] = mix_buffer[i]; } p_buffer += to_mix; todo -= to_mix; } return p_frames; } void AudioStreamPlaybackInteractive::_mix_internal(int p_frames) { for (int i = 0; i < p_frames; i++) { mix_buffer[i] = AudioFrame(0, 0); } for (int i = 0; i < stream->clip_count; i++) { if (!states[i].active) { continue; } _mix_internal_state(i, p_frames); } } void AudioStreamPlaybackInteractive::_mix_internal_state(int p_state_idx, int p_frames) { State &state = states[p_state_idx]; double mix_rate = double(AudioServer::get_singleton()->get_mix_rate()); double frame_inc = 1.0 / mix_rate; int from_frame = 0; int queue_next = -1; if (state.first_mix) { // Did not start mixing yet, wait. double mix_time = p_frames * frame_inc; if (state.fade_wait < mix_time) { // time to start! from_frame = state.fade_wait * mix_rate; state.fade_wait = 0; if (state.fade_speed == 0.0) { queue_next = state.auto_advance; } playback_current = p_state_idx; state.first_mix = false; } else { // This is for fade in of new stream. state.fade_wait -= mix_time; return; // Nothing to do } } state.previous_position = state.playback->get_playback_position(); state.playback->mix(temp_buffer + from_frame, 1.0, p_frames - from_frame); double frame_fade_inc = state.fade_speed * frame_inc; for (int i = from_frame; i < p_frames; i++) { if (state.fade_wait) { // This is for fade out of existing stream; state.fade_wait -= frame_inc; if (state.fade_wait < 0.0) { state.fade_wait = 0.0; } } else if (frame_fade_inc > 0) { state.fade_volume += frame_fade_inc; if (state.fade_volume >= 1.0) { state.fade_speed = 0.0; frame_fade_inc = 0.0; state.fade_volume = 1.0; queue_next = state.auto_advance; } } else if (frame_fade_inc < 0.0) { state.fade_volume += frame_fade_inc; if (state.fade_volume <= 0.0) { state.fade_speed = 0.0; frame_fade_inc = 0.0; state.fade_volume = 0.0; state.playback->stop(); // Stop playback and break, no point to continue mixing break; } } mix_buffer[i] += temp_buffer[i] * state.fade_volume; state.previous_position += frame_inc; } if (!state.playback->is_playing()) { // It finished because it either reached end or faded out, so deactivate and continue. state.active = false; } if (queue_next != -1) { _queue(queue_next, true); } } void AudioStreamPlaybackInteractive::tag_used_streams() { for (int i = 0; i < stream->clip_count; i++) { if (states[i].active && !states[i].first_mix && states[i].playback->is_playing()) { states[i].stream->tag_used(states[i].playback->get_playback_position()); } } stream->tag_used(0); } void AudioStreamPlaybackInteractive::switch_to_clip_by_name(const StringName &p_name) { if (p_name == StringName()) { switch_request = -1; return; } ERR_FAIL_COND_MSG(stream.is_null(), "Attempted to switch while not playing back any stream."); for (int i = 0; i < stream->get_clip_count(); i++) { if (stream->get_clip_name(i) == p_name) { switch_request = i; return; } } ERR_FAIL_MSG("Clip not found: " + String(p_name)); } void AudioStreamPlaybackInteractive::set_parameter(const StringName &p_name, const Variant &p_value) { if (p_name == SNAME("switch_to_clip")) { switch_to_clip_by_name(p_value); } } Variant AudioStreamPlaybackInteractive::get_parameter(const StringName &p_name) const { if (p_name == SNAME("switch_to_clip")) { for (int i = 0; i < stream->get_clip_count(); i++) { if (switch_request != -1) { if (switch_request == i) { return String(stream->get_clip_name(i)); } } else if (playback_current == i) { return String(stream->get_clip_name(i)); } } return ""; } return Variant(); } void AudioStreamPlaybackInteractive::switch_to_clip(int p_index) { switch_request = p_index; } int AudioStreamPlaybackInteractive::get_loop_count() const { return 0; // Looping not supported } double AudioStreamPlaybackInteractive::get_playback_position() const { return 0.0; } bool AudioStreamPlaybackInteractive::is_playing() const { return active; } void AudioStreamPlaybackInteractive::_bind_methods() { ClassDB::bind_method(D_METHOD("switch_to_clip_by_name", "clip_name"), &AudioStreamPlaybackInteractive::switch_to_clip_by_name); ClassDB::bind_method(D_METHOD("switch_to_clip", "clip_index"), &AudioStreamPlaybackInteractive::switch_to_clip); }