diff --git a/doc/classes/AudioStreamRandomPitch.xml b/doc/classes/AudioStreamRandomPitch.xml deleted file mode 100644 index 0f580699e91..00000000000 --- a/doc/classes/AudioStreamRandomPitch.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - Plays audio with random pitch shifting. - - - Randomly varies pitch on each start. - - - - - - The current [AudioStream]. - - - The intensity of random pitch variation. - - - diff --git a/doc/classes/AudioStreamRandomizer.xml b/doc/classes/AudioStreamRandomizer.xml new file mode 100644 index 00000000000..90471a033ed --- /dev/null +++ b/doc/classes/AudioStreamRandomizer.xml @@ -0,0 +1,90 @@ + + + + Wraps a pool of audio streams with pitch and volume shifting. + + + Picks a random AudioStream from the pool, depending on the playback mode, and applies random pitch shifting and volume shifting during playback. + + + + + + + + + Insert a stream at the specified index. + + + + + + + Returns the stream at the specified index. + + + + + + + Returns the probability weight associated with the stream at the given index. + + + + + + + + Move a stream from one index to another. + + + + + + + Remove the stream at the specified index. + + + + + + + + Set the AudioStream at the specified index. + + + + + + + + Set the probability weight of the stream at the specified index. The higher this value, the more likely that the randomizer will choose this stream during random playback modes. + + + + + + Controls how this AudioStreamRandomizer picks which AudioStream to play next. + + + The intensity of random pitch variation. A value of 1 means no variation. + + + The intensity of random volume variation. A value of 0 means no variation. + + + The number of streams in the stream pool. + + + + + Pick a stream at random according to the probability weights chosen for each stream, but avoid playing the same stream twice in a row whenever possible. + + + Pick a stream at random according to the probability weights chosen for each stream. + + + Play streams in the order they appear in the stream pool. + + + diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 0951199ab83..10ce7228e01 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -128,6 +128,7 @@ #include "editor/plugins/animation_tree_editor_plugin.h" #include "editor/plugins/asset_library_editor_plugin.h" #include "editor/plugins/audio_stream_editor_plugin.h" +#include "editor/plugins/audio_stream_randomizer_editor_plugin.h" #include "editor/plugins/camera_3d_editor_plugin.h" #include "editor/plugins/canvas_item_editor_plugin.h" #include "editor/plugins/collision_polygon_2d_editor_plugin.h" @@ -7013,6 +7014,7 @@ EditorNode::EditorNode() { add_editor_plugin(memnew(TextureLayeredEditorPlugin(this))); add_editor_plugin(memnew(Texture3DEditorPlugin(this))); add_editor_plugin(memnew(AudioStreamEditorPlugin(this))); + add_editor_plugin(memnew(AudioStreamRandomizerEditorPlugin(this))); add_editor_plugin(memnew(AudioBusesEditorPlugin(audio_bus_editor))); add_editor_plugin(memnew(Skeleton3DEditorPlugin(this))); add_editor_plugin(memnew(SkeletonIK3DEditorPlugin(this))); diff --git a/editor/plugins/audio_stream_randomizer_editor_plugin.cpp b/editor/plugins/audio_stream_randomizer_editor_plugin.cpp new file mode 100644 index 00000000000..fea3c29967b --- /dev/null +++ b/editor/plugins/audio_stream_randomizer_editor_plugin.cpp @@ -0,0 +1,119 @@ +/*************************************************************************/ +/* audio_stream_randomizer_editor_plugin.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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_randomizer_editor_plugin.h" + +void AudioStreamRandomizerEditorPlugin::edit(Object *p_object) { +} + +bool AudioStreamRandomizerEditorPlugin::handles(Object *p_object) const { + return false; +} + +void AudioStreamRandomizerEditorPlugin::make_visible(bool p_visible) { +} + +void AudioStreamRandomizerEditorPlugin::_move_stream_array_element(Object *p_undo_redo, Object *p_edited, String p_array_prefix, int p_from_index, int p_to_pos) { + UndoRedo *undo_redo = Object::cast_to(p_undo_redo); + ERR_FAIL_COND(!undo_redo); + + AudioStreamRandomizer *randomizer = Object::cast_to(p_edited); + if (!randomizer) { + return; + } + + // Compute the array indices to save. + int begin = 0; + int end; + if (p_array_prefix == "stream_") { + end = randomizer->get_streams_count(); + } else { + ERR_FAIL_MSG("Invalid array prefix for AudioStreamRandomizer."); + } + if (p_from_index < 0) { + // Adding new. + if (p_to_pos >= 0) { + begin = p_to_pos; + } else { + end = 0; // Nothing to save when adding at the end. + } + } else if (p_to_pos < 0) { + // Removing. + begin = p_from_index; + } else { + // Moving. + begin = MIN(p_from_index, p_to_pos); + end = MIN(MAX(p_from_index, p_to_pos) + 1, end); + } + +#define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, obj->get(property)); + // Save layers' properties. + if (p_from_index < 0) { + undo_redo->add_undo_method(randomizer, "remove_stream", p_to_pos < 0 ? randomizer->get_streams_count() : p_to_pos); + } else if (p_to_pos < 0) { + undo_redo->add_undo_method(randomizer, "add_stream", p_from_index); + } + + List properties; + randomizer->get_property_list(&properties); + for (PropertyInfo pi : properties) { + if (pi.name.begins_with(p_array_prefix)) { + String str = pi.name.trim_prefix(p_array_prefix); + int to_char_index = 0; + while (to_char_index < str.length()) { + if (str[to_char_index] < '0' || str[to_char_index] > '9') { + break; + } + to_char_index++; + } + if (to_char_index > 0) { + int array_index = str.left(to_char_index).to_int(); + if (array_index >= begin && array_index < end) { + ADD_UNDO(randomizer, pi.name); + } + } + } + } +#undef ADD_UNDO + + if (p_from_index < 0) { + undo_redo->add_do_method(randomizer, "add_stream", p_to_pos); + } else if (p_to_pos < 0) { + undo_redo->add_do_method(randomizer, "remove_stream", p_from_index); + } else { + undo_redo->add_do_method(randomizer, "move_stream", p_from_index, p_to_pos); + } +} + +AudioStreamRandomizerEditorPlugin::AudioStreamRandomizerEditorPlugin(EditorNode *p_node) { + EditorNode::get_singleton()->get_editor_data().add_move_array_element_function(SNAME("AudioStreamRandomizer"), callable_mp(this, &AudioStreamRandomizerEditorPlugin::_move_stream_array_element)); +} + +AudioStreamRandomizerEditorPlugin::~AudioStreamRandomizerEditorPlugin() {} diff --git a/editor/plugins/audio_stream_randomizer_editor_plugin.h b/editor/plugins/audio_stream_randomizer_editor_plugin.h new file mode 100644 index 00000000000..490af7e2c25 --- /dev/null +++ b/editor/plugins/audio_stream_randomizer_editor_plugin.h @@ -0,0 +1,57 @@ +/*************************************************************************/ +/* audio_stream_randomizer_editor_plugin.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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. */ +/*************************************************************************/ + +#ifndef AUDIO_STREAM_RANDOMIZER_EDITOR_PLUGIN_H +#define AUDIO_STREAM_RANDOMIZER_EDITOR_PLUGIN_H + +#include "editor/editor_node.h" +#include "editor/editor_plugin.h" +#include "servers/audio/audio_stream.h" + +class AudioStreamRandomizerEditorPlugin : public EditorPlugin { + GDCLASS(AudioStreamRandomizerEditorPlugin, EditorPlugin); + + EditorNode *editor; + +private: + void _move_stream_array_element(Object *p_undo_redo, Object *p_edited, String p_array_prefix, int p_from_index, int p_to_pos); + +public: + virtual String get_name() const override { return "AudioStreamRandomizer"; } + bool has_main_screen() const override { return false; } + virtual void edit(Object *p_object) override; + virtual bool handles(Object *p_object) const override; + virtual void make_visible(bool p_visible) override; + + AudioStreamRandomizerEditorPlugin(EditorNode *p_node); + ~AudioStreamRandomizerEditorPlugin(); +}; + +#endif // AUDIO_STREAM_RANDOMIZER_EDITOR_PLUGIN_H diff --git a/servers/audio/audio_stream.cpp b/servers/audio/audio_stream.cpp index 170a7ef9676..39060286a42 100644 --- a/servers/audio/audio_stream.cpp +++ b/servers/audio/audio_stream.cpp @@ -351,100 +351,372 @@ AudioStreamPlaybackMicrophone::AudioStreamPlaybackMicrophone() { //////////////////////////////// -void AudioStreamRandomPitch::set_audio_stream(const Ref &p_audio_stream) { - audio_stream = p_audio_stream; - if (audio_stream.is_valid()) { - for (Set::Element *E = playbacks.front(); E; E = E->next()) { - E->get()->playback = audio_stream->instance_playback(); - } +void AudioStreamRandomizer::add_stream(int p_index) { + if (p_index < 0) { + p_index = audio_stream_pool.size(); } + ERR_FAIL_COND(p_index > audio_stream_pool.size()); + PoolEntry entry{ nullptr, 1.0f }; + audio_stream_pool.insert(p_index, entry); + emit_signal(SNAME("changed")); + notify_property_list_changed(); } -Ref AudioStreamRandomPitch::get_audio_stream() const { - return audio_stream; +void AudioStreamRandomizer::move_stream(int p_index_from, int p_index_to) { + ERR_FAIL_COND(p_index_from < 0); + ERR_FAIL_COND(p_index_from >= audio_stream_pool.size()); + ERR_FAIL_COND(p_index_to < 0); + ERR_FAIL_COND(p_index_to > audio_stream_pool.size()); + audio_stream_pool.insert(p_index_to, audio_stream_pool[p_index_from]); + // If 'from' is strictly after 'to' we need to increment the index by one because of the insertion. + if (p_index_from > p_index_to) { + p_index_from++; + } + audio_stream_pool.remove_at(p_index_from); + emit_signal(SNAME("changed")); + notify_property_list_changed(); } -void AudioStreamRandomPitch::set_random_pitch(float p_pitch) { +void AudioStreamRandomizer::remove_stream(int p_index) { + ERR_FAIL_COND(p_index < 0); + ERR_FAIL_COND(p_index >= audio_stream_pool.size()); + audio_stream_pool.remove_at(p_index); + emit_signal(SNAME("changed")); + notify_property_list_changed(); +} + +void AudioStreamRandomizer::set_stream(int p_index, Ref p_stream) { + ERR_FAIL_COND(p_index < 0); + ERR_FAIL_COND(p_index >= audio_stream_pool.size()); + audio_stream_pool.write[p_index].stream = p_stream; + emit_signal(SNAME("changed")); +} + +Ref AudioStreamRandomizer::get_stream(int p_index) const { + ERR_FAIL_COND_V(p_index < 0, nullptr); + ERR_FAIL_COND_V(p_index >= audio_stream_pool.size(), nullptr); + return audio_stream_pool[p_index].stream; +} + +void AudioStreamRandomizer::set_stream_probability_weight(int p_index, float p_weight) { + ERR_FAIL_COND(p_index < 0); + ERR_FAIL_COND(p_index >= audio_stream_pool.size()); + audio_stream_pool.write[p_index].weight = p_weight; + emit_signal(SNAME("changed")); +} + +float AudioStreamRandomizer::get_stream_probability_weight(int p_index) const { + ERR_FAIL_COND_V(p_index < 0, 0); + ERR_FAIL_COND_V(p_index >= audio_stream_pool.size(), 0); + return audio_stream_pool[p_index].weight; +} + +void AudioStreamRandomizer::set_streams_count(int p_count) { + audio_stream_pool.resize(p_count); +} + +int AudioStreamRandomizer::get_streams_count() const { + return audio_stream_pool.size(); +} + +void AudioStreamRandomizer::set_random_pitch(float p_pitch) { if (p_pitch < 1) { p_pitch = 1; } - random_pitch = p_pitch; + random_pitch_scale = p_pitch; } -float AudioStreamRandomPitch::get_random_pitch() const { - return random_pitch; +float AudioStreamRandomizer::get_random_pitch() const { + return random_pitch_scale; } -Ref AudioStreamRandomPitch::instance_playback() { - Ref playback; - playback.instantiate(); - if (audio_stream.is_valid()) { - playback->playback = audio_stream->instance_playback(); +void AudioStreamRandomizer::set_random_volume_offset_db(float p_volume_offset_db) { + if (p_volume_offset_db < 0) { + p_volume_offset_db = 0; } + random_volume_offset_db = p_volume_offset_db; +} +float AudioStreamRandomizer::get_random_volume_offset_db() const { + return random_volume_offset_db; +} + +void AudioStreamRandomizer::set_playback_mode(PlaybackMode p_playback_mode) { + playback_mode = p_playback_mode; +} + +AudioStreamRandomizer::PlaybackMode AudioStreamRandomizer::get_playback_mode() const { + return playback_mode; +} + +Ref AudioStreamRandomizer::instance_playback_random() { + Ref playback; + playback.instantiate(); playbacks.insert(playback.ptr()); - playback->random_pitch = Ref((AudioStreamRandomPitch *)this); + playback->randomizer = Ref((AudioStreamRandomizer *)this); + + double total_weight = 0; + Vector local_pool; + for (const PoolEntry &entry : audio_stream_pool) { + if (entry.stream.is_valid() && entry.weight > 0) { + local_pool.push_back(entry); + total_weight += entry.weight; + } + } + if (local_pool.is_empty()) { + return playback; + } + double chosen_cumulative_weight = Math::random(0.0, total_weight); + double cumulative_weight = 0; + for (PoolEntry &entry : local_pool) { + cumulative_weight += entry.weight; + if (cumulative_weight > chosen_cumulative_weight) { + playback->playback = entry.stream->instance_playback(); + last_playback = entry.stream; + break; + } + } + if (playback->playback.is_null()) { + // This indicates a floating point error. Take the last element. + last_playback = local_pool[local_pool.size() - 1].stream; + playback->playback = local_pool.write[local_pool.size() - 1].stream->instance_playback(); + } return playback; } -String AudioStreamRandomPitch::get_stream_name() const { - if (audio_stream.is_valid()) { - return "Random: " + audio_stream->get_name(); +Ref AudioStreamRandomizer::instance_playback_no_repeats() { + Ref playback; + + double total_weight = 0; + Vector local_pool; + for (const PoolEntry &entry : audio_stream_pool) { + if (entry.stream == last_playback) { + continue; + } + if (entry.stream.is_valid() && entry.weight > 0) { + local_pool.push_back(entry); + total_weight += entry.weight; + } } - return "RandomPitch"; + if (local_pool.is_empty()) { + playback = instance_playback_random(); + WARN_PRINT("Playback stream pool is too small to prevent repeats."); + return playback; + } + + playback.instantiate(); + playbacks.insert(playback.ptr()); + playback->randomizer = Ref((AudioStreamRandomizer *)this); + double chosen_cumulative_weight = Math::random(0.0, total_weight); + double cumulative_weight = 0; + for (PoolEntry &entry : local_pool) { + cumulative_weight += entry.weight; + if (cumulative_weight > chosen_cumulative_weight) { + last_playback = entry.stream; + playback->playback = entry.stream->instance_playback(); + break; + } + } + if (playback->playback.is_null()) { + // This indicates a floating point error. Take the last element. + last_playback = local_pool[local_pool.size() - 1].stream; + playback->playback = local_pool.write[local_pool.size() - 1].stream->instance_playback(); + } + return playback; } -float AudioStreamRandomPitch::get_length() const { - if (audio_stream.is_valid()) { - return audio_stream->get_length(); - } +Ref AudioStreamRandomizer::instance_playback_sequential() { + Ref playback; + playback.instantiate(); + playbacks.insert(playback.ptr()); + playback->randomizer = Ref((AudioStreamRandomizer *)this); + Vector> local_pool; + for (const PoolEntry &entry : audio_stream_pool) { + if (entry.stream.is_null()) { + continue; + } + if (local_pool.find(entry.stream) != -1) { + WARN_PRINT("Duplicate stream in sequential playback pool"); + continue; + } + local_pool.push_back(entry.stream); + } + if (local_pool.is_empty()) { + return playback; + } + bool found_last_stream = false; + for (Ref &entry : local_pool) { + if (found_last_stream) { + last_playback = entry; + playback->playback = entry->instance_playback(); + break; + } + if (entry == last_playback) { + found_last_stream = true; + } + } + if (playback->playback.is_null()) { + // Wrap around + last_playback = local_pool[0]; + playback->playback = local_pool.write[0]->instance_playback(); + } + return playback; +} + +Ref AudioStreamRandomizer::instance_playback() { + switch (playback_mode) { + case PLAYBACK_RANDOM: + return instance_playback_random(); + case PLAYBACK_RANDOM_NO_REPEATS: + return instance_playback_no_repeats(); + case PLAYBACK_SEQUENTIAL: + return instance_playback_sequential(); + default: + ERR_FAIL_V_MSG(nullptr, "Unhandled playback mode."); + } +} + +String AudioStreamRandomizer::get_stream_name() const { + return "Randomizer"; +} + +float AudioStreamRandomizer::get_length() const { return 0; } -bool AudioStreamRandomPitch::is_monophonic() const { - if (audio_stream.is_valid()) { - return audio_stream->is_monophonic(); +bool AudioStreamRandomizer::is_monophonic() const { + for (const PoolEntry &entry : audio_stream_pool) { + if (entry.stream.is_valid() && entry.stream->is_monophonic()) { + return true; + } } - - return true; // It doesn't really matter what we return here, but no sense instancing a many playbacks of a null stream. + return false; } -void AudioStreamRandomPitch::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_audio_stream", "stream"), &AudioStreamRandomPitch::set_audio_stream); - ClassDB::bind_method(D_METHOD("get_audio_stream"), &AudioStreamRandomPitch::get_audio_stream); +bool AudioStreamRandomizer::_get(const StringName &p_name, Variant &r_ret) const { + if (AudioStream::_get(p_name, r_ret)) { + return true; + } + Vector components = String(p_name).split("/", true, 2); + if (components.size() == 2 && components[0].begins_with("stream_") && components[0].trim_prefix("stream_").is_valid_int()) { + int index = components[0].trim_prefix("stream_").to_int(); + if (index < 0 || index >= (int)audio_stream_pool.size()) { + return false; + } - ClassDB::bind_method(D_METHOD("set_random_pitch", "scale"), &AudioStreamRandomPitch::set_random_pitch); - ClassDB::bind_method(D_METHOD("get_random_pitch"), &AudioStreamRandomPitch::get_random_pitch); + if (components[1] == "stream") { + r_ret = get_stream(index); + return true; + } else if (components[1] == "weight") { + r_ret = get_stream_probability_weight(index); + return true; + } else { + return false; + } + } + return false; +} - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "audio_stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_audio_stream", "get_audio_stream"); +bool AudioStreamRandomizer::_set(const StringName &p_name, const Variant &p_value) { + if (AudioStream::_set(p_name, p_value)) { + return true; + } + Vector components = String(p_name).split("/", true, 2); + if (components.size() == 2 && components[0].begins_with("stream_") && components[0].trim_prefix("stream_").is_valid_int()) { + int index = components[0].trim_prefix("stream_").to_int(); + if (index < 0 || index >= (int)audio_stream_pool.size()) { + return false; + } + + if (components[1] == "stream") { + set_stream(index, p_value); + return true; + } else if (components[1] == "weight") { + set_stream_probability_weight(index, p_value); + return true; + } else { + return false; + } + } + return false; +} + +void AudioStreamRandomizer::_get_property_list(List *p_list) const { + AudioStream::_get_property_list(p_list); // Define the trivial scalar properties. + p_list->push_back(PropertyInfo(Variant::NIL, "Streams", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); + for (int i = 0; i < audio_stream_pool.size(); i++) { + p_list->push_back(PropertyInfo(Variant::OBJECT, vformat("stream_%d/stream", i), PROPERTY_HINT_RESOURCE_TYPE, "AudioStream")); + p_list->push_back(PropertyInfo(Variant::FLOAT, vformat("stream_%d/weight", i), PROPERTY_HINT_RANGE, "0,100,0.001,or_greater")); + } +} + +void AudioStreamRandomizer::_bind_methods() { + ClassDB::bind_method(D_METHOD("add_stream", "index"), &AudioStreamRandomizer::add_stream); + ClassDB::bind_method(D_METHOD("move_stream", "index_from", "index_to"), &AudioStreamRandomizer::move_stream); + ClassDB::bind_method(D_METHOD("remove_stream", "index"), &AudioStreamRandomizer::remove_stream); + + ClassDB::bind_method(D_METHOD("set_stream", "index", "stream"), &AudioStreamRandomizer::set_stream); + ClassDB::bind_method(D_METHOD("get_stream", "index"), &AudioStreamRandomizer::get_stream); + ClassDB::bind_method(D_METHOD("set_stream_probability_weight", "index", "weight"), &AudioStreamRandomizer::set_stream_probability_weight); + ClassDB::bind_method(D_METHOD("get_stream_probability_weight", "index"), &AudioStreamRandomizer::get_stream_probability_weight); + + ClassDB::bind_method(D_METHOD("set_streams_count", "count"), &AudioStreamRandomizer::set_streams_count); + ClassDB::bind_method(D_METHOD("get_streams_count"), &AudioStreamRandomizer::get_streams_count); + + ClassDB::bind_method(D_METHOD("set_random_pitch", "scale"), &AudioStreamRandomizer::set_random_pitch); + ClassDB::bind_method(D_METHOD("get_random_pitch"), &AudioStreamRandomizer::get_random_pitch); + + ClassDB::bind_method(D_METHOD("set_random_volume_offset_db", "db_offset"), &AudioStreamRandomizer::set_random_volume_offset_db); + ClassDB::bind_method(D_METHOD("get_random_volume_offset_db"), &AudioStreamRandomizer::get_random_volume_offset_db); + + ClassDB::bind_method(D_METHOD("set_playback_mode", "mode"), &AudioStreamRandomizer::set_playback_mode); + ClassDB::bind_method(D_METHOD("get_playback_mode"), &AudioStreamRandomizer::get_playback_mode); + + ADD_ARRAY("streams", "stream_"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "streams_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_streams_count", "get_streams_count"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "playback_mode", PROPERTY_HINT_ENUM, "Random (Avoid Repeats),Random,Sequential"), "set_playback_mode", "get_playback_mode"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "random_pitch", PROPERTY_HINT_RANGE, "1,16,0.01"), "set_random_pitch", "get_random_pitch"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "random_volume_offset_db", PROPERTY_HINT_RANGE, "0,40,0"), "set_random_volume_offset_db", "get_random_volume_offset_db"); + + BIND_ENUM_CONSTANT(PLAYBACK_RANDOM_NO_REPEATS); + BIND_ENUM_CONSTANT(PLAYBACK_RANDOM); + BIND_ENUM_CONSTANT(PLAYBACK_SEQUENTIAL); } -AudioStreamRandomPitch::AudioStreamRandomPitch() { - random_pitch = 1.1; +AudioStreamRandomizer::AudioStreamRandomizer() { + random_pitch_scale = 1.1; + random_volume_offset_db = 5; } -void AudioStreamPlaybackRandomPitch::start(float p_from_pos) { +void AudioStreamPlaybackRandomizer::start(float p_from_pos) { playing = playback; - float range_from = 1.0 / random_pitch->random_pitch; - float range_to = random_pitch->random_pitch; + { + float range_from = 1.0 / randomizer->random_pitch_scale; + float range_to = randomizer->random_pitch_scale; - pitch_scale = range_from + Math::randf() * (range_to - range_from); + pitch_scale = range_from + Math::randf() * (range_to - range_from); + } + { + float range_from = -randomizer->random_volume_offset_db; + float range_to = randomizer->random_volume_offset_db; + + float volume_offset_db = range_from + Math::randf() * (range_to - range_from); + volume_scale = Math::db2linear(volume_offset_db); + } if (playing.is_valid()) { playing->start(p_from_pos); } } -void AudioStreamPlaybackRandomPitch::stop() { +void AudioStreamPlaybackRandomizer::stop() { if (playing.is_valid()) { playing->stop(); - ; } } -bool AudioStreamPlaybackRandomPitch::is_playing() const { +bool AudioStreamPlaybackRandomizer::is_playing() const { if (playing.is_valid()) { return playing->is_playing(); } @@ -452,7 +724,7 @@ bool AudioStreamPlaybackRandomPitch::is_playing() const { return false; } -int AudioStreamPlaybackRandomPitch::get_loop_count() const { +int AudioStreamPlaybackRandomizer::get_loop_count() const { if (playing.is_valid()) { return playing->get_loop_count(); } @@ -460,7 +732,7 @@ int AudioStreamPlaybackRandomPitch::get_loop_count() const { return 0; } -float AudioStreamPlaybackRandomPitch::get_playback_position() const { +float AudioStreamPlaybackRandomizer::get_playback_position() const { if (playing.is_valid()) { return playing->get_playback_position(); } @@ -468,13 +740,13 @@ float AudioStreamPlaybackRandomPitch::get_playback_position() const { return 0; } -void AudioStreamPlaybackRandomPitch::seek(float p_time) { +void AudioStreamPlaybackRandomizer::seek(float p_time) { if (playing.is_valid()) { playing->seek(p_time); } } -int AudioStreamPlaybackRandomPitch::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) { +int AudioStreamPlaybackRandomizer::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) { if (playing.is_valid()) { return playing->mix(p_buffer, p_rate_scale * pitch_scale, p_frames); } else { @@ -485,7 +757,7 @@ int AudioStreamPlaybackRandomPitch::mix(AudioFrame *p_buffer, float p_rate_scale } } -AudioStreamPlaybackRandomPitch::~AudioStreamPlaybackRandomPitch() { - random_pitch->playbacks.erase(this); +AudioStreamPlaybackRandomizer::~AudioStreamPlaybackRandomizer() { + randomizer->playbacks.erase(this); } ///////////////////////////////////////////// diff --git a/servers/audio/audio_stream.h b/servers/audio/audio_stream.h index 32159e96efc..ce9bcabb9c3 100644 --- a/servers/audio/audio_stream.h +++ b/servers/audio/audio_stream.h @@ -167,43 +167,86 @@ public: // -class AudioStreamPlaybackRandomPitch; +class AudioStreamPlaybackRandomizer; -class AudioStreamRandomPitch : public AudioStream { - GDCLASS(AudioStreamRandomPitch, AudioStream); - friend class AudioStreamPlaybackRandomPitch; +class AudioStreamRandomizer : public AudioStream { + GDCLASS(AudioStreamRandomizer, AudioStream); - Set playbacks; - Ref audio_stream; - float random_pitch; +public: + enum PlaybackMode { + PLAYBACK_RANDOM_NO_REPEATS, + PLAYBACK_RANDOM, + PLAYBACK_SEQUENTIAL, + }; + +private: + friend class AudioStreamPlaybackRandomizer; + + struct PoolEntry { + Ref stream; + float weight; + }; + + Set playbacks; + Vector audio_stream_pool; + float random_pitch_scale; + float random_volume_offset_db; + + Ref instance_playback_random(); + Ref instance_playback_no_repeats(); + Ref instance_playback_sequential(); + + Ref last_playback = nullptr; + PlaybackMode playback_mode = PLAYBACK_RANDOM_NO_REPEATS; protected: static void _bind_methods(); -public: - void set_audio_stream(const Ref &p_audio_stream); - Ref get_audio_stream() const; + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List *p_list) const; - void set_random_pitch(float p_pitch); +public: + void add_stream(int p_index); + void move_stream(int p_index_from, int p_index_to); + void remove_stream(int p_index); + + void set_stream(int p_index, Ref p_stream); + Ref get_stream(int p_index) const; + void set_stream_probability_weight(int p_index, float p_weight); + float get_stream_probability_weight(int p_index) const; + + void set_streams_count(int p_count); + int get_streams_count() const; + + void set_random_pitch(float p_pitch_scale); float get_random_pitch() const; + void set_random_volume_offset_db(float p_volume_offset_db); + float get_random_volume_offset_db() const; + + void set_playback_mode(PlaybackMode p_playback_mode); + PlaybackMode get_playback_mode() const; + virtual Ref instance_playback() override; virtual String get_stream_name() const override; virtual float get_length() const override; //if supported, otherwise return 0 virtual bool is_monophonic() const override; - AudioStreamRandomPitch(); + AudioStreamRandomizer(); }; -class AudioStreamPlaybackRandomPitch : public AudioStreamPlayback { - GDCLASS(AudioStreamPlaybackRandomPitch, AudioStreamPlayback); - friend class AudioStreamRandomPitch; +class AudioStreamPlaybackRandomizer : public AudioStreamPlayback { + GDCLASS(AudioStreamPlaybackRandomizer, AudioStreamPlayback); + friend class AudioStreamRandomizer; - Ref random_pitch; + Ref randomizer; Ref playback; Ref playing; + float pitch_scale; + float volume_scale; public: virtual void start(float p_from_pos = 0.0) override; @@ -217,7 +260,9 @@ public: virtual int mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) override; - ~AudioStreamPlaybackRandomPitch(); + ~AudioStreamPlaybackRandomizer(); }; +VARIANT_ENUM_CAST(AudioStreamRandomizer::PlaybackMode); + #endif // AUDIO_STREAM_H diff --git a/servers/register_server_types.cpp b/servers/register_server_types.cpp index bf8b6379d2e..3726bcde35d 100644 --- a/servers/register_server_types.cpp +++ b/servers/register_server_types.cpp @@ -140,7 +140,7 @@ void register_server_types() { GDREGISTER_CLASS(AudioStreamPlayback); GDREGISTER_VIRTUAL_CLASS(AudioStreamPlaybackResampled); GDREGISTER_CLASS(AudioStreamMicrophone); - GDREGISTER_CLASS(AudioStreamRandomPitch); + GDREGISTER_CLASS(AudioStreamRandomizer); GDREGISTER_VIRTUAL_CLASS(AudioEffect); GDREGISTER_VIRTUAL_CLASS(AudioEffectInstance); GDREGISTER_CLASS(AudioEffectEQ);