Path: blob/master/modules/interactive_music/audio_stream_interactive.cpp
10277 views
/**************************************************************************/1/* audio_stream_interactive.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "audio_stream_interactive.h"3132#include "core/math/math_funcs.h"3334AudioStreamInteractive::AudioStreamInteractive() {35}3637Ref<AudioStreamPlayback> AudioStreamInteractive::instantiate_playback() {38Ref<AudioStreamPlaybackInteractive> playback_transitioner;39playback_transitioner.instantiate();40playback_transitioner->stream = Ref<AudioStreamInteractive>(this);41return playback_transitioner;42}4344String AudioStreamInteractive::get_stream_name() const {45return "Transitioner";46}4748void AudioStreamInteractive::set_clip_count(int p_count) {49ERR_FAIL_COND(p_count < 0 || p_count > MAX_CLIPS);5051AudioServer::get_singleton()->lock();5253if (p_count < clip_count) {54// Removing should stop players.55version++;56}5758#ifdef TOOLS_ENABLED59stream_name_cache = "";60if (p_count < clip_count) {61for (int i = 0; i < clip_count; i++) {62if (clips[i].auto_advance_next_clip >= p_count) {63clips[i].auto_advance_next_clip = 0;64clips[i].auto_advance = AUTO_ADVANCE_DISABLED;65}66}6768for (KeyValue<TransitionKey, Transition> &K : transition_map) {69if (K.value.filler_clip >= p_count) {70K.value.use_filler_clip = false;71K.value.filler_clip = 0;72}73}74if (initial_clip >= p_count) {75initial_clip = 0;76}77}78#endif79clip_count = p_count;80AudioServer::get_singleton()->unlock();8182notify_property_list_changed();83emit_signal(SNAME("parameter_list_changed"));84}8586void AudioStreamInteractive::set_initial_clip(int p_clip) {87ERR_FAIL_INDEX(p_clip, clip_count);88initial_clip = p_clip;89}9091int AudioStreamInteractive::get_initial_clip() const {92return initial_clip;93}9495int AudioStreamInteractive::get_clip_count() const {96return clip_count;97}9899void AudioStreamInteractive::set_clip_name(int p_clip, const StringName &p_name) {100ERR_FAIL_INDEX(p_clip, MAX_CLIPS);101clips[p_clip].name = p_name;102}103104StringName AudioStreamInteractive::get_clip_name(int p_clip) const {105ERR_FAIL_COND_V(p_clip < -1 || p_clip >= MAX_CLIPS, StringName());106if (p_clip == CLIP_ANY) {107return RTR("All Clips");108}109return clips[p_clip].name;110}111112void AudioStreamInteractive::set_clip_stream(int p_clip, const Ref<AudioStream> &p_stream) {113ERR_FAIL_INDEX(p_clip, MAX_CLIPS);114AudioServer::get_singleton()->lock();115if (clips[p_clip].stream.is_valid()) {116version++;117}118clips[p_clip].stream = p_stream;119AudioServer::get_singleton()->unlock();120#ifdef TOOLS_ENABLED121if (Engine::get_singleton()->is_editor_hint()) {122if (clips[p_clip].name == StringName() && p_stream.is_valid()) {123String n;124if (!clips[p_clip].stream->get_name().is_empty()) {125n = clips[p_clip].stream->get_name().replace_char(',', ' ');126} else if (clips[p_clip].stream->get_path().is_resource_file()) {127n = clips[p_clip].stream->get_path().get_file().get_basename().replace_char(',', ' ');128n = n.capitalize();129}130131if (n != "") {132clips[p_clip].name = n;133}134}135}136#endif137138#ifdef TOOLS_ENABLED139stream_name_cache = "";140notify_property_list_changed(); // Hints change if stream changes.141emit_signal(SNAME("parameter_list_changed"));142#endif143}144145Ref<AudioStream> AudioStreamInteractive::get_clip_stream(int p_clip) const {146ERR_FAIL_INDEX_V(p_clip, MAX_CLIPS, Ref<AudioStream>());147return clips[p_clip].stream;148}149150void AudioStreamInteractive::set_clip_auto_advance(int p_clip, AutoAdvanceMode p_mode) {151ERR_FAIL_INDEX(p_clip, MAX_CLIPS);152ERR_FAIL_INDEX(p_mode, 3);153clips[p_clip].auto_advance = p_mode;154notify_property_list_changed();155}156157AudioStreamInteractive::AutoAdvanceMode AudioStreamInteractive::get_clip_auto_advance(int p_clip) const {158ERR_FAIL_INDEX_V(p_clip, MAX_CLIPS, AUTO_ADVANCE_DISABLED);159return clips[p_clip].auto_advance;160}161162void AudioStreamInteractive::set_clip_auto_advance_next_clip(int p_clip, int p_index) {163ERR_FAIL_INDEX(p_clip, MAX_CLIPS);164clips[p_clip].auto_advance_next_clip = p_index;165}166167int AudioStreamInteractive::get_clip_auto_advance_next_clip(int p_clip) const {168ERR_FAIL_INDEX_V(p_clip, MAX_CLIPS, -1);169return clips[p_clip].auto_advance_next_clip;170}171172// TRANSITIONS173174void AudioStreamInteractive::_set_transitions(const Dictionary &p_transitions) {175for (const KeyValue<Variant, Variant> &kv : p_transitions) {176Vector2i k = kv.key;177Dictionary data = kv.value;178ERR_CONTINUE(!data.has("from_time"));179ERR_CONTINUE(!data.has("to_time"));180ERR_CONTINUE(!data.has("fade_mode"));181ERR_CONTINUE(!data.has("fade_beats"));182bool use_filler_clip = false;183int filler_clip = 0;184if (data.has("use_filler_clip") && data.has("filler_clip")) {185use_filler_clip = data["use_filler_clip"];186filler_clip = data["filler_clip"];187}188bool hold_previous = data.has("hold_previous") ? bool(data["hold_previous"]) : false;189190add_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);191}192}193194Dictionary AudioStreamInteractive::_get_transitions() const {195Vector<Vector2i> keys;196197for (const KeyValue<TransitionKey, Transition> &K : transition_map) {198keys.push_back(Vector2i(K.key.from_clip, K.key.to_clip));199}200keys.sort();201Dictionary ret;202for (int i = 0; i < keys.size(); i++) {203const Transition &tr = transition_map[TransitionKey(keys[i].x, keys[i].y)];204Dictionary data;205data["from_time"] = tr.from_time;206data["to_time"] = tr.to_time;207data["fade_mode"] = tr.fade_mode;208data["fade_beats"] = tr.fade_beats;209if (tr.use_filler_clip) {210data["use_filler_clip"] = true;211data["filler_clip"] = tr.filler_clip;212}213if (tr.hold_previous) {214data["hold_previous"] = true;215}216217ret[keys[i]] = data;218}219return ret;220}221222bool AudioStreamInteractive::has_transition(int p_from_clip, int p_to_clip) const {223TransitionKey tk(p_from_clip, p_to_clip);224return transition_map.has(tk);225}226227void AudioStreamInteractive::erase_transition(int p_from_clip, int p_to_clip) {228TransitionKey tk(p_from_clip, p_to_clip);229ERR_FAIL_COND(!transition_map.has(tk));230AudioDriver::get_singleton()->lock();231transition_map.erase(tk);232AudioDriver::get_singleton()->unlock();233}234235PackedInt32Array AudioStreamInteractive::get_transition_list() const {236PackedInt32Array ret;237238for (const KeyValue<TransitionKey, Transition> &K : transition_map) {239ret.push_back(K.key.from_clip);240ret.push_back(K.key.to_clip);241}242return ret;243}244245void 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) {246ERR_FAIL_COND(p_from_clip < CLIP_ANY || p_from_clip >= clip_count);247ERR_FAIL_COND(p_to_clip < CLIP_ANY || p_to_clip >= clip_count);248ERR_FAIL_UNSIGNED_INDEX(p_from_time, TRANSITION_FROM_TIME_MAX);249ERR_FAIL_UNSIGNED_INDEX(p_to_time, TRANSITION_TO_TIME_MAX);250ERR_FAIL_UNSIGNED_INDEX(p_fade_mode, FADE_MAX);251252Transition tr;253tr.from_time = p_from_time;254tr.to_time = p_to_time;255tr.fade_mode = p_fade_mode;256tr.fade_beats = p_fade_beats;257tr.use_filler_clip = p_use_filler_flip;258tr.filler_clip = p_filler_clip;259tr.hold_previous = p_hold_previous;260261TransitionKey tk(p_from_clip, p_to_clip);262263AudioDriver::get_singleton()->lock();264transition_map[tk] = tr;265AudioDriver::get_singleton()->unlock();266}267268AudioStreamInteractive::TransitionFromTime AudioStreamInteractive::get_transition_from_time(int p_from_clip, int p_to_clip) const {269TransitionKey tk(p_from_clip, p_to_clip);270ERR_FAIL_COND_V(!transition_map.has(tk), TRANSITION_FROM_TIME_END);271return transition_map[tk].from_time;272}273274AudioStreamInteractive::TransitionToTime AudioStreamInteractive::get_transition_to_time(int p_from_clip, int p_to_clip) const {275TransitionKey tk(p_from_clip, p_to_clip);276ERR_FAIL_COND_V(!transition_map.has(tk), TRANSITION_TO_TIME_START);277return transition_map[tk].to_time;278}279280AudioStreamInteractive::FadeMode AudioStreamInteractive::get_transition_fade_mode(int p_from_clip, int p_to_clip) const {281TransitionKey tk(p_from_clip, p_to_clip);282ERR_FAIL_COND_V(!transition_map.has(tk), FADE_DISABLED);283return transition_map[tk].fade_mode;284}285286float AudioStreamInteractive::get_transition_fade_beats(int p_from_clip, int p_to_clip) const {287TransitionKey tk(p_from_clip, p_to_clip);288ERR_FAIL_COND_V(!transition_map.has(tk), -1);289return transition_map[tk].fade_beats;290}291292bool AudioStreamInteractive::is_transition_using_filler_clip(int p_from_clip, int p_to_clip) const {293TransitionKey tk(p_from_clip, p_to_clip);294ERR_FAIL_COND_V(!transition_map.has(tk), false);295return transition_map[tk].use_filler_clip;296}297298int AudioStreamInteractive::get_transition_filler_clip(int p_from_clip, int p_to_clip) const {299TransitionKey tk(p_from_clip, p_to_clip);300ERR_FAIL_COND_V(!transition_map.has(tk), -1);301return transition_map[tk].filler_clip;302}303304bool AudioStreamInteractive::is_transition_holding_previous(int p_from_clip, int p_to_clip) const {305TransitionKey tk(p_from_clip, p_to_clip);306ERR_FAIL_COND_V(!transition_map.has(tk), false);307return transition_map[tk].hold_previous;308}309310#ifdef TOOLS_ENABLED311312PackedStringArray AudioStreamInteractive::_get_linked_undo_properties(const String &p_property, const Variant &p_new_value) const {313PackedStringArray ret;314315if (p_property.begins_with("clip_") && p_property.ends_with("/stream")) {316int clip = p_property.get_slicec('_', 1).to_int();317if (clip < clip_count) {318ret.push_back("clip_" + itos(clip) + "/name");319}320}321322if (p_property == "clip_count") {323int new_clip_count = p_new_value;324325if (new_clip_count < clip_count) {326for (int i = 0; i < clip_count; i++) {327if (clips[i].auto_advance_next_clip >= new_clip_count) {328ret.push_back("clip_" + itos(i) + "/auto_advance");329ret.push_back("clip_" + itos(i) + "/next_clip");330}331}332333ret.push_back("_transitions");334if (initial_clip >= new_clip_count) {335ret.push_back("initial_clip");336}337}338}339return ret;340}341342template <class T>343static void _test_and_swap(T &p_elem, uint32_t p_a, uint32_t p_b) {344if ((uint32_t)p_elem == p_a) {345p_elem = p_b;346} else if (uint32_t(p_elem) == p_b) {347p_elem = p_a;348}349}350351void AudioStreamInteractive::_inspector_array_swap_clip(uint32_t p_item_a, uint32_t p_item_b) {352ERR_FAIL_UNSIGNED_INDEX(p_item_a, (uint32_t)clip_count);353ERR_FAIL_UNSIGNED_INDEX(p_item_b, (uint32_t)clip_count);354355for (int i = 0; i < clip_count; i++) {356_test_and_swap(clips[i].auto_advance_next_clip, p_item_a, p_item_b);357}358359Vector<TransitionKey> to_remove;360HashMap<TransitionKey, Transition, TransitionKeyHasher> to_add;361362for (KeyValue<TransitionKey, Transition> &K : transition_map) {363if (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) {364to_remove.push_back(K.key);365TransitionKey new_key = K.key;366_test_and_swap(new_key.from_clip, p_item_a, p_item_b);367_test_and_swap(new_key.to_clip, p_item_a, p_item_b);368to_add[new_key] = K.value;369}370}371372for (int i = 0; i < to_remove.size(); i++) {373transition_map.erase(to_remove[i]);374}375376for (KeyValue<TransitionKey, Transition> &K : to_add) {377transition_map.insert(K.key, K.value);378}379380SWAP(clips[p_item_a], clips[p_item_b]);381382stream_name_cache = "";383384notify_property_list_changed();385emit_signal(SNAME("parameter_list_changed"));386}387388String AudioStreamInteractive::_get_streams_hint() const {389if (!stream_name_cache.is_empty()) {390return stream_name_cache;391}392393for (int i = 0; i < clip_count; i++) {394if (i > 0) {395stream_name_cache += ",";396}397String n = String(clips[i].name).replace_char(',', ' ');398399if (n == "" && clips[i].stream.is_valid()) {400if (!clips[i].stream->get_name().is_empty()) {401n = clips[i].stream->get_name().replace_char(',', ' ');402} else if (clips[i].stream->get_path().is_resource_file()) {403n = clips[i].stream->get_path().get_file().replace_char(',', ' ');404}405}406407if (n == "") {408n = "Clip " + itos(i);409}410411stream_name_cache += n;412}413414return stream_name_cache;415}416417#endif418419void AudioStreamInteractive::_validate_property(PropertyInfo &r_property) const {420String prop = r_property.name;421422if (Engine::get_singleton()->is_editor_hint() && prop == "switch_to") {423#ifdef TOOLS_ENABLED424r_property.hint_string = _get_streams_hint();425#endif426return;427}428429if (Engine::get_singleton()->is_editor_hint() && prop == "initial_clip") {430#ifdef TOOLS_ENABLED431r_property.hint_string = _get_streams_hint();432#endif433} else if (prop.begins_with("clip_") && prop != "clip_count") {434int clip = prop.get_slicec('_', 1).to_int();435if (clip >= clip_count) {436r_property.usage = PROPERTY_USAGE_INTERNAL;437} else if (prop == "clip_" + itos(clip) + "/next_clip") {438if (clips[clip].auto_advance != AUTO_ADVANCE_ENABLED) {439r_property.usage = 0;440} else if (Engine::get_singleton()->is_editor_hint()) {441#ifdef TOOLS_ENABLED442r_property.hint_string = _get_streams_hint();443#endif444}445}446}447}448449void AudioStreamInteractive::get_parameter_list(List<Parameter> *r_parameters) {450String clip_names;451for (int i = 0; i < clip_count; i++) {452clip_names += ",";453clip_names += clips[i].name;454}455r_parameters->push_back(Parameter(PropertyInfo(Variant::STRING, "switch_to_clip", PROPERTY_HINT_ENUM, clip_names, PROPERTY_USAGE_EDITOR), ""));456}457458void AudioStreamInteractive::_bind_methods() {459#ifdef TOOLS_ENABLED460ClassDB::bind_method(D_METHOD("_get_linked_undo_properties", "for_property", "for_value"), &AudioStreamInteractive::_get_linked_undo_properties);461ClassDB::bind_method(D_METHOD("_inspector_array_swap_clip", "a", "b"), &AudioStreamInteractive::_inspector_array_swap_clip);462#endif463464// CLIPS465466ClassDB::bind_method(D_METHOD("set_clip_count", "clip_count"), &AudioStreamInteractive::set_clip_count);467ClassDB::bind_method(D_METHOD("get_clip_count"), &AudioStreamInteractive::get_clip_count);468469ClassDB::bind_method(D_METHOD("set_initial_clip", "clip_index"), &AudioStreamInteractive::set_initial_clip);470ClassDB::bind_method(D_METHOD("get_initial_clip"), &AudioStreamInteractive::get_initial_clip);471472ClassDB::bind_method(D_METHOD("set_clip_name", "clip_index", "name"), &AudioStreamInteractive::set_clip_name);473ClassDB::bind_method(D_METHOD("get_clip_name", "clip_index"), &AudioStreamInteractive::get_clip_name);474475ClassDB::bind_method(D_METHOD("set_clip_stream", "clip_index", "stream"), &AudioStreamInteractive::set_clip_stream);476ClassDB::bind_method(D_METHOD("get_clip_stream", "clip_index"), &AudioStreamInteractive::get_clip_stream);477478ClassDB::bind_method(D_METHOD("set_clip_auto_advance", "clip_index", "mode"), &AudioStreamInteractive::set_clip_auto_advance);479ClassDB::bind_method(D_METHOD("get_clip_auto_advance", "clip_index"), &AudioStreamInteractive::get_clip_auto_advance);480481ClassDB::bind_method(D_METHOD("set_clip_auto_advance_next_clip", "clip_index", "auto_advance_next_clip"), &AudioStreamInteractive::set_clip_auto_advance_next_clip);482ClassDB::bind_method(D_METHOD("get_clip_auto_advance_next_clip", "clip_index"), &AudioStreamInteractive::get_clip_auto_advance_next_clip);483484ADD_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(TTRC("Add Clip"))), "set_clip_count", "get_clip_count");485for (int i = 0; i < MAX_CLIPS; i++) {486ADD_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);487ADD_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);488ADD_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);489ADD_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);490}491492// Needs to be registered after `clip_*` properties, as it depends on them.493ADD_PROPERTY(PropertyInfo(Variant::INT, "initial_clip", PROPERTY_HINT_ENUM, "", PROPERTY_USAGE_DEFAULT), "set_initial_clip", "get_initial_clip");494495// TRANSITIONS496497ClassDB::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));498ClassDB::bind_method(D_METHOD("has_transition", "from_clip", "to_clip"), &AudioStreamInteractive::has_transition);499ClassDB::bind_method(D_METHOD("erase_transition", "from_clip", "to_clip"), &AudioStreamInteractive::erase_transition);500ClassDB::bind_method(D_METHOD("get_transition_list"), &AudioStreamInteractive::get_transition_list);501502ClassDB::bind_method(D_METHOD("get_transition_from_time", "from_clip", "to_clip"), &AudioStreamInteractive::get_transition_from_time);503ClassDB::bind_method(D_METHOD("get_transition_to_time", "from_clip", "to_clip"), &AudioStreamInteractive::get_transition_to_time);504ClassDB::bind_method(D_METHOD("get_transition_fade_mode", "from_clip", "to_clip"), &AudioStreamInteractive::get_transition_fade_mode);505ClassDB::bind_method(D_METHOD("get_transition_fade_beats", "from_clip", "to_clip"), &AudioStreamInteractive::get_transition_fade_beats);506ClassDB::bind_method(D_METHOD("is_transition_using_filler_clip", "from_clip", "to_clip"), &AudioStreamInteractive::is_transition_using_filler_clip);507ClassDB::bind_method(D_METHOD("get_transition_filler_clip", "from_clip", "to_clip"), &AudioStreamInteractive::get_transition_filler_clip);508ClassDB::bind_method(D_METHOD("is_transition_holding_previous", "from_clip", "to_clip"), &AudioStreamInteractive::is_transition_holding_previous);509510ClassDB::bind_method(D_METHOD("_set_transitions", "transitions"), &AudioStreamInteractive::_set_transitions);511ClassDB::bind_method(D_METHOD("_get_transitions"), &AudioStreamInteractive::_get_transitions);512513ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "_transitions", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_transitions", "_get_transitions");514515BIND_ENUM_CONSTANT(TRANSITION_FROM_TIME_IMMEDIATE);516BIND_ENUM_CONSTANT(TRANSITION_FROM_TIME_NEXT_BEAT);517BIND_ENUM_CONSTANT(TRANSITION_FROM_TIME_NEXT_BAR);518BIND_ENUM_CONSTANT(TRANSITION_FROM_TIME_END);519520BIND_ENUM_CONSTANT(TRANSITION_TO_TIME_SAME_POSITION);521BIND_ENUM_CONSTANT(TRANSITION_TO_TIME_START);522523BIND_ENUM_CONSTANT(FADE_DISABLED);524BIND_ENUM_CONSTANT(FADE_IN);525BIND_ENUM_CONSTANT(FADE_OUT);526BIND_ENUM_CONSTANT(FADE_CROSS);527BIND_ENUM_CONSTANT(FADE_AUTOMATIC);528529BIND_ENUM_CONSTANT(AUTO_ADVANCE_DISABLED);530BIND_ENUM_CONSTANT(AUTO_ADVANCE_ENABLED);531BIND_ENUM_CONSTANT(AUTO_ADVANCE_RETURN_TO_HOLD);532533BIND_CONSTANT(CLIP_ANY);534}535536///////////////////////////////////////////////////////////537///////////////////////////////////////////////////////////538///////////////////////////////////////////////////////////539AudioStreamPlaybackInteractive::AudioStreamPlaybackInteractive() {540}541542AudioStreamPlaybackInteractive::~AudioStreamPlaybackInteractive() {543}544545void AudioStreamPlaybackInteractive::stop() {546if (!active) {547return;548}549550active = false;551552for (int i = 0; i < AudioStreamInteractive::MAX_CLIPS; i++) {553if (states[i].playback.is_valid()) {554states[i].playback->stop();555}556states[i].fade_speed = 0.0;557states[i].fade_volume = 0.0;558states[i].fade_wait = 0.0;559states[i].reset_fade();560states[i].active = false;561states[i].auto_advance = -1;562states[i].first_mix = true;563}564}565566void AudioStreamPlaybackInteractive::start(double p_from_pos) {567if (active) {568stop();569}570571if (version != stream->version) {572for (int i = 0; i < AudioStreamInteractive::MAX_CLIPS; i++) {573Ref<AudioStream> src_stream;574if (i < stream->clip_count) {575src_stream = stream->clips[i].stream;576}577if (states[i].stream != src_stream) {578states[i].stream.unref();579states[i].playback.unref();580581states[i].stream = src_stream;582states[i].playback = src_stream->instantiate_playback();583}584}585586version = stream->version;587}588589int current = stream->initial_clip;590if (current < 0 || current >= stream->clip_count) {591return; // No playback possible.592}593if (states[current].playback.is_null()) {594return; //no playback possible595}596active = true;597598_queue(current, false);599}600601void AudioStreamPlaybackInteractive::_queue(int p_to_clip_index, bool p_is_auto_advance) {602ERR_FAIL_INDEX(p_to_clip_index, stream->clip_count);603ERR_FAIL_COND(states[p_to_clip_index].playback.is_null());604605if (playback_current == -1) {606// Nothing to do, start.607int current = p_to_clip_index;608State &state = states[current];609state.active = true;610state.fade_wait = 0;611state.fade_volume = 1.0;612state.fade_speed = 0;613state.first_mix = true;614615state.playback->start(0);616617playback_current = current;618619if (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) {620//prepare auto advance621state.auto_advance = stream->clips[current].auto_advance_next_clip;622}623return;624}625626for (int i = 0; i < stream->clip_count; i++) {627if (i == playback_current || i == p_to_clip_index) {628continue;629}630if (states[i].active && states[i].fade_wait > 0) { // Waiting to kick in, terminate because change of plans.631states[i].playback->stop();632states[i].reset_fade();633states[i].active = false;634}635}636637State &from_state = states[playback_current];638State &to_state = states[p_to_clip_index];639640AudioStreamInteractive::Transition transition; // Use an empty transition by default641642AudioStreamInteractive::TransitionKey tkeys[4] = {643AudioStreamInteractive::TransitionKey(playback_current, p_to_clip_index),644AudioStreamInteractive::TransitionKey(playback_current, AudioStreamInteractive::CLIP_ANY),645AudioStreamInteractive::TransitionKey(AudioStreamInteractive::CLIP_ANY, p_to_clip_index),646AudioStreamInteractive::TransitionKey(AudioStreamInteractive::CLIP_ANY, AudioStreamInteractive::CLIP_ANY)647};648649for (int i = 0; i < 4; i++) {650if (stream->transition_map.has(tkeys[i])) {651transition = stream->transition_map[tkeys[i]];652break;653}654}655656if (transition.fade_mode == AudioStreamInteractive::FADE_AUTOMATIC) {657// Adjust automatic mode based on context.658if (transition.to_time == AudioStreamInteractive::TRANSITION_TO_TIME_START) {659transition.fade_mode = AudioStreamInteractive::FADE_OUT;660} else {661transition.fade_mode = AudioStreamInteractive::FADE_CROSS;662}663}664665if (p_is_auto_advance) {666transition.from_time = AudioStreamInteractive::TRANSITION_FROM_TIME_END;667if (transition.to_time == AudioStreamInteractive::TRANSITION_TO_TIME_SAME_POSITION) {668transition.to_time = AudioStreamInteractive::TRANSITION_TO_TIME_START;669}670}671672// Prepare the fadeout673float current_pos = from_state.playback->get_playback_position();674675float src_fade_wait = 0;676float dst_seek_to = 0;677float fade_speed = 0;678bool src_no_loop = false;679680if (from_state.stream->get_bpm()) {681// Check if source speed has BPM, if so, transition syncs to BPM682float beat_sec = 60 / float(from_state.stream->get_bpm());683switch (transition.from_time) {684case AudioStreamInteractive::TRANSITION_FROM_TIME_IMMEDIATE: {685src_fade_wait = 0;686} break;687case AudioStreamInteractive::TRANSITION_FROM_TIME_NEXT_BEAT: {688float remainder = Math::fmod(current_pos, beat_sec);689src_fade_wait = beat_sec - remainder;690} break;691case AudioStreamInteractive::TRANSITION_FROM_TIME_NEXT_BAR: {692if (from_state.stream->get_bar_beats() > 0) {693float bar_sec = beat_sec * from_state.stream->get_bar_beats();694float remainder = Math::fmod(current_pos, bar_sec);695src_fade_wait = bar_sec - remainder;696} else {697// Stream does not have a number of beats per bar - avoid NaN, and play immediately.698src_fade_wait = 0;699}700} break;701case AudioStreamInteractive::TRANSITION_FROM_TIME_END: {702float end = from_state.stream->get_beat_count() > 0 ? float(from_state.stream->get_beat_count() * beat_sec) : from_state.stream->get_length();703if (end == 0) {704// Stream does not have a length.705src_fade_wait = 0;706} else {707src_fade_wait = end - current_pos;708}709710if (!from_state.stream->has_loop()) {711src_no_loop = true;712}713714} break;715default: {716}717}718// Fade speed also aligned to BPM719fade_speed = 1.0 / (transition.fade_beats * beat_sec);720} else {721// Source has no BPM, so just simple transition.722if (transition.from_time == AudioStreamInteractive::TRANSITION_FROM_TIME_END && from_state.stream->get_length() > 0) {723float end = from_state.stream->get_length();724src_fade_wait = end - current_pos;725if (!from_state.stream->has_loop()) {726src_no_loop = true;727}728} else {729src_fade_wait = 0;730}731fade_speed = 1.0 / transition.fade_beats;732}733734if (transition.to_time == AudioStreamInteractive::TRANSITION_TO_TIME_PREVIOUS_POSITION && to_state.stream->get_length() > 0.0) {735dst_seek_to = to_state.previous_position;736} 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) {737// Seeking to basically same position as when we start fading.738dst_seek_to = current_pos + src_fade_wait;739float end;740if (to_state.stream->get_bpm() > 0 && to_state.stream->get_beat_count()) {741float beat_sec = 60 / float(to_state.stream->get_bpm());742end = to_state.stream->get_beat_count() * beat_sec;743} else {744end = to_state.stream->get_length();745}746747if (dst_seek_to > end) {748// Seeking too far away.749dst_seek_to = 0; //past end, loop to beginning.750}751752} else {753// Seek to Start754dst_seek_to = 0.0;755}756757if (transition.fade_mode == AudioStreamInteractive::FADE_DISABLED || transition.fade_mode == AudioStreamInteractive::FADE_IN) {758if (src_no_loop) {759// If there is no fade in the source stream, then let it continue until it ends.760from_state.fade_wait = 0;761from_state.fade_speed = 0;762} else {763// Otherwise force a very quick fade to avoid clicks764from_state.fade_wait = src_fade_wait;765from_state.fade_speed = 1.0 / -0.001;766}767} else {768// Regular fade.769from_state.fade_wait = src_fade_wait;770from_state.fade_speed = -fade_speed;771}772// keep volume, since it may have been fading in from something else.773774to_state.playback->start(dst_seek_to);775to_state.active = true;776to_state.fade_volume = 0.0;777to_state.first_mix = true;778779int auto_advance_to = -1;780781if (stream->clips[p_to_clip_index].auto_advance == AudioStreamInteractive::AUTO_ADVANCE_ENABLED) {782int next_clip = stream->clips[p_to_clip_index].auto_advance_next_clip;783if (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)) {784auto_advance_to = next_clip;785}786}787788if (return_memory != -1 && stream->clips[p_to_clip_index].auto_advance == AudioStreamInteractive::AUTO_ADVANCE_RETURN_TO_HOLD) {789auto_advance_to = return_memory;790return_memory = -1;791}792793if (transition.hold_previous) {794return_memory = playback_current;795}796797if (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) {798State &filler_state = states[transition.filler_clip];799800filler_state.playback->start(0);801filler_state.active = true;802803// Filler state does not fade (bake fade in the audio clip if you want fading.804filler_state.fade_volume = 1.0;805filler_state.fade_speed = 0.0;806807filler_state.fade_wait = src_fade_wait;808filler_state.first_mix = true;809810float filler_end;811if (filler_state.stream->get_bpm() > 0 && filler_state.stream->get_beat_count() > 0) {812float filler_beat_sec = 60 / float(filler_state.stream->get_bpm());813filler_end = filler_beat_sec * filler_state.stream->get_beat_count();814} else {815filler_end = filler_state.stream->get_length();816}817818if (!filler_state.stream->has_loop()) {819src_no_loop = true;820}821822if (transition.fade_mode == AudioStreamInteractive::FADE_DISABLED || transition.fade_mode == AudioStreamInteractive::FADE_OUT) {823// No fading, immediately start at full volume.824to_state.fade_volume = 0.0;825to_state.fade_speed = 1.0; //start at full volume, as filler is meant as a transition.826} else {827// Fade enable, prepare fade.828to_state.fade_volume = 0.0;829to_state.fade_speed = fade_speed;830}831832to_state.fade_wait = src_fade_wait + filler_end;833834} else {835to_state.fade_wait = src_fade_wait;836837if (transition.fade_mode == AudioStreamInteractive::FADE_DISABLED || transition.fade_mode == AudioStreamInteractive::FADE_OUT) {838to_state.fade_volume = 1.0;839to_state.fade_speed = 0.0;840} else {841to_state.fade_volume = 0.0;842to_state.fade_speed = fade_speed;843}844845to_state.auto_advance = auto_advance_to;846}847}848849void AudioStreamPlaybackInteractive::seek(double p_time) {850// Seek not supported851}852853int AudioStreamPlaybackInteractive::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) {854if (active && version != stream->version) {855stop();856}857858if (switch_request != -1) {859_queue(switch_request, false);860switch_request = -1;861}862863if (!active) {864return 0;865}866867int todo = p_frames;868869while (todo) {870int to_mix = MIN(todo, BUFFER_SIZE);871_mix_internal(to_mix);872for (int i = 0; i < to_mix; i++) {873p_buffer[i] = mix_buffer[i];874}875p_buffer += to_mix;876todo -= to_mix;877}878879return p_frames;880}881882void AudioStreamPlaybackInteractive::_mix_internal(int p_frames) {883for (int i = 0; i < p_frames; i++) {884mix_buffer[i] = AudioFrame(0, 0);885}886887for (int i = 0; i < stream->clip_count; i++) {888if (!states[i].active) {889continue;890}891892_mix_internal_state(i, p_frames);893}894}895896void AudioStreamPlaybackInteractive::_mix_internal_state(int p_state_idx, int p_frames) {897State &state = states[p_state_idx];898double mix_rate = double(AudioServer::get_singleton()->get_mix_rate());899double frame_inc = 1.0 / mix_rate;900901int from_frame = 0;902int queue_next = -1;903904if (state.first_mix) {905// Did not start mixing yet, wait.906double mix_time = p_frames * frame_inc;907if (state.fade_wait < mix_time) {908// time to start!909from_frame = state.fade_wait * mix_rate;910state.fade_wait = 0;911if (state.fade_speed == 0.0) {912queue_next = state.auto_advance;913}914playback_current = p_state_idx;915state.first_mix = false;916} else {917// This is for fade in of new stream.918state.fade_wait -= mix_time;919return; // Nothing to do920}921}922923state.previous_position = state.playback->get_playback_position();924state.playback->mix(temp_buffer + from_frame, 1.0, p_frames - from_frame);925926double frame_fade_inc = state.fade_speed * frame_inc;927for (int i = from_frame; i < p_frames; i++) {928if (state.fade_wait) {929// This is for fade out of existing stream;930state.fade_wait -= frame_inc;931if (state.fade_wait < 0.0) {932state.fade_wait = 0.0;933}934} else if (frame_fade_inc > 0) {935state.fade_volume += frame_fade_inc;936if (state.fade_volume >= 1.0) {937state.fade_speed = 0.0;938frame_fade_inc = 0.0;939state.fade_volume = 1.0;940queue_next = state.auto_advance;941}942} else if (frame_fade_inc < 0.0) {943state.fade_volume += frame_fade_inc;944if (state.fade_volume <= 0.0) {945state.fade_speed = 0.0;946frame_fade_inc = 0.0;947state.fade_volume = 0.0;948state.playback->stop(); // Stop playback and break, no point to continue mixing949break;950}951}952953mix_buffer[i] += temp_buffer[i] * state.fade_volume;954state.previous_position += frame_inc;955}956957if (!state.playback->is_playing()) {958// It finished because it either reached end or faded out, so deactivate and continue.959state.active = false;960}961if (queue_next != -1) {962_queue(queue_next, true);963}964}965966void AudioStreamPlaybackInteractive::tag_used_streams() {967for (int i = 0; i < stream->clip_count; i++) {968if (states[i].active && !states[i].first_mix && states[i].playback->is_playing()) {969states[i].stream->tag_used(states[i].playback->get_playback_position());970}971}972stream->tag_used(0);973}974975void AudioStreamPlaybackInteractive::switch_to_clip_by_name(const StringName &p_name) {976if (p_name == StringName()) {977switch_request = -1;978return;979}980981ERR_FAIL_COND_MSG(stream.is_null(), "Attempted to switch while not playing back any stream.");982983for (int i = 0; i < stream->get_clip_count(); i++) {984if (stream->get_clip_name(i) == p_name) {985switch_request = i;986return;987}988}989ERR_FAIL_MSG("Clip not found: " + String(p_name));990}991992void AudioStreamPlaybackInteractive::set_parameter(const StringName &p_name, const Variant &p_value) {993if (p_name == SNAME("switch_to_clip")) {994switch_to_clip_by_name(p_value);995}996}997998Variant AudioStreamPlaybackInteractive::get_parameter(const StringName &p_name) const {999if (p_name == SNAME("switch_to_clip")) {1000for (int i = 0; i < stream->get_clip_count(); i++) {1001if (switch_request != -1) {1002if (switch_request == i) {1003return String(stream->get_clip_name(i));1004}1005} else if (playback_current == i) {1006return String(stream->get_clip_name(i));1007}1008}1009return "";1010}10111012return Variant();1013}10141015void AudioStreamPlaybackInteractive::switch_to_clip(int p_index) {1016switch_request = p_index;1017}10181019int AudioStreamPlaybackInteractive::get_current_clip_index() const {1020return playback_current;1021}10221023int AudioStreamPlaybackInteractive::get_loop_count() const {1024return 0; // Looping not supported1025}10261027double AudioStreamPlaybackInteractive::get_playback_position() const {1028return 0.0;1029}10301031bool AudioStreamPlaybackInteractive::is_playing() const {1032return active;1033}10341035void AudioStreamPlaybackInteractive::_bind_methods() {1036ClassDB::bind_method(D_METHOD("switch_to_clip_by_name", "clip_name"), &AudioStreamPlaybackInteractive::switch_to_clip_by_name);1037ClassDB::bind_method(D_METHOD("switch_to_clip", "clip_index"), &AudioStreamPlaybackInteractive::switch_to_clip);1038ClassDB::bind_method(D_METHOD("get_current_clip_index"), &AudioStreamPlaybackInteractive::get_current_clip_index);1039}104010411042