diff options
36 files changed, 918 insertions, 596 deletions
diff --git a/doc/classes/AudioStream.xml b/doc/classes/AudioStream.xml index a954a06117..32e51603ee 100644 --- a/doc/classes/AudioStream.xml +++ b/doc/classes/AudioStream.xml @@ -28,12 +28,23 @@ <description> </description> </method> + <method name="_is_monophonic" qualifiers="virtual const"> + <return type="bool" /> + <description> + </description> + </method> <method name="get_length" qualifiers="const"> <return type="float" /> <description> Returns the length of the audio stream in seconds. </description> </method> + <method name="is_monophonic" qualifiers="const"> + <return type="bool" /> + <description> + Returns true if this audio stream only supports monophonic playback, or false if the audio stream supports polyphony. + </description> + </method> </methods> <constants> </constants> diff --git a/doc/classes/AudioStreamPlayer.xml b/doc/classes/AudioStreamPlayer.xml index a6c437f875..b692ae858e 100644 --- a/doc/classes/AudioStreamPlayer.xml +++ b/doc/classes/AudioStreamPlayer.xml @@ -56,6 +56,9 @@ <member name="bus" type="StringName" setter="set_bus" getter="get_bus" default="&"Master""> Bus on which this audio is playing. </member> + <member name="max_polyphony" type="int" setter="set_max_polyphony" getter="get_max_polyphony" default="1"> + The maximum number of sounds this node can play at the same time. Playing additional sounds after this value is reached will cut off the oldest sounds. + </member> <member name="mix_target" type="int" setter="set_mix_target" getter="get_mix_target" enum="AudioStreamPlayer.MixTarget" default="0"> If the audio configuration has more than two speakers, this sets the target channels. See [enum MixTarget] constants. </member> diff --git a/doc/classes/AudioStreamPlayer2D.xml b/doc/classes/AudioStreamPlayer2D.xml index c40c223091..e36a428499 100644 --- a/doc/classes/AudioStreamPlayer2D.xml +++ b/doc/classes/AudioStreamPlayer2D.xml @@ -61,6 +61,9 @@ <member name="max_distance" type="float" setter="set_max_distance" getter="get_max_distance" default="2000.0"> Maximum distance from which audio is still hearable. </member> + <member name="max_polyphony" type="int" setter="set_max_polyphony" getter="get_max_polyphony" default="1"> + The maximum number of sounds this node can play at the same time. Playing additional sounds after this value is reached will cut off the oldest sounds. + </member> <member name="pitch_scale" type="float" setter="set_pitch_scale" getter="get_pitch_scale" default="1.0"> The pitch and the tempo of the audio, as a multiplier of the audio sample's sample rate. </member> diff --git a/doc/classes/AudioStreamPlayer3D.xml b/doc/classes/AudioStreamPlayer3D.xml index 584f03399c..fa2fa5a8e3 100644 --- a/doc/classes/AudioStreamPlayer3D.xml +++ b/doc/classes/AudioStreamPlayer3D.xml @@ -83,6 +83,9 @@ <member name="max_distance" type="float" setter="set_max_distance" getter="get_max_distance" default="0.0"> Sets the distance from which the [member out_of_range_mode] takes effect. Has no effect if set to 0. </member> + <member name="max_polyphony" type="int" setter="set_max_polyphony" getter="get_max_polyphony" default="1"> + The maximum number of sounds this node can play at the same time. Playing additional sounds after this value is reached will cut off the oldest sounds. + </member> <member name="out_of_range_mode" type="int" setter="set_out_of_range_mode" getter="get_out_of_range_mode" enum="AudioStreamPlayer3D.OutOfRangeMode" default="0"> Decides if audio should pause when source is outside of [member max_distance] range. </member> diff --git a/doc/classes/Camera2D.xml b/doc/classes/Camera2D.xml index d0ff66ae06..a3a891cdcb 100644 --- a/doc/classes/Camera2D.xml +++ b/doc/classes/Camera2D.xml @@ -5,6 +5,7 @@ </brief_description> <description> Camera node for 2D scenes. It forces the screen (current layer) to scroll following this node. This makes it easier (and faster) to program scrollable scenes than manually changing the position of [CanvasItem]-based nodes. + Cameras register themselves in the nearest [Viewport] node (when ascending the tree). Only one camera can be active per viewport. If no viewport is available ascending the tree, the camera will register in the global viewport. This node is intended to be a simple helper to get things going quickly, but more functionality may be desired to change how the camera works. To make your own custom camera node, inherit it from [Node2D] and change the transform of the canvas by setting [member Viewport.canvas_transform] in [Viewport] (you can obtain the current [Viewport] by using [method Node.get_viewport]). Note that the [Camera2D] node's [code]position[/code] doesn't represent the actual position of the screen, which may differ due to applied smoothing or limits. You can use [method get_camera_screen_center] to get the real position. </description> @@ -81,7 +82,7 @@ The Camera2D's anchor point. See [enum AnchorMode] constants. </member> <member name="current" type="bool" setter="set_current" getter="is_current" default="false"> - If [code]true[/code], the camera is the active camera for the current scene. Only one camera can be current, so setting a different camera [code]current[/code] will disable this one. + If [code]true[/code], the camera acts as the active camera for its [Viewport] ancestor. Only one camera can be current in a given viewport, so setting a different camera in the same viewport [code]current[/code] will disable whatever camera was already active in that viewport. </member> <member name="custom_viewport" type="Node" setter="set_custom_viewport" getter="get_custom_viewport"> The custom [Viewport] node attached to the [Camera2D]. If [code]null[/code] or not a [Viewport], uses the default viewport instead. diff --git a/doc/classes/HTTPClient.xml b/doc/classes/HTTPClient.xml index 861627b526..29aaf3c756 100644 --- a/doc/classes/HTTPClient.xml +++ b/doc/classes/HTTPClient.xml @@ -8,6 +8,7 @@ [b]Note:[/b] This client only needs to connect to a host once (see [method connect_to_host]) to send multiple requests. Because of this, methods that take URLs usually take just the part after the host instead of the full URL, as the client is already connected to a host. See [method request] for a full example and to get started. A [HTTPClient] should be reused between multiple requests or to connect to different hosts instead of creating one client per request. Supports SSL and SSL server certificate verification. HTTP status codes in the 2xx range indicate success, 3xx redirection (i.e. "try again, but over here"), 4xx something was wrong with the request, and 5xx something went wrong on the server's side. For more information on HTTP, see https://developer.mozilla.org/en-US/docs/Web/HTTP (or read RFC 2616 to get it straight from the source: https://tools.ietf.org/html/rfc2616). + [b]Note:[/b] It's recommended to use transport encryption (SSL/TLS) and to avoid sending sensitive information (such as login credentials) in HTTP GET URL parameters. Consider using HTTP POST requests or HTTP headers for such information instead. [b]Note:[/b] When performing HTTP requests from a project exported to HTML5, keep in mind the remote server may not allow requests from foreign origins due to [url=https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS]CORS[/url]. If you host the server in question, you should modify its backend to allow requests from foreign origins by adding the [code]Access-Control-Allow-Origin: *[/code] HTTP header. [b]Note:[/b] SSL/TLS support is currently limited to TLS 1.0, TLS 1.1, and TLS 1.2. Attempting to connect to a TLS 1.3-only server will return an error. [b]Warning:[/b] SSL/TLS certificate revocation and certificate pinning are currently not supported. Revoked certificates are accepted as long as they are otherwise valid. If this is a concern, you may want to use automatically managed certificates with a short validity period. diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index 66f291bd3e..d3841ad6c0 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -2843,10 +2843,8 @@ void EditorInspector::update_tree() { if (ep) { // Eventually, set other properties/signals after the property editor got added to the tree. - ep->connect("property_changed", callable_mp(this, &EditorInspector::_property_changed)); - if (p.usage & PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED) { - ep->connect("property_changed", callable_mp(this, &EditorInspector::_property_changed_update_all), varray(), CONNECT_DEFERRED); - } + bool update_all = (p.usage & PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED); + ep->connect("property_changed", callable_mp(this, &EditorInspector::_property_changed), varray(update_all)); ep->connect("property_keyed", callable_mp(this, &EditorInspector::_property_keyed)); ep->connect("property_deleted", callable_mp(this, &EditorInspector::_property_deleted), varray(), CONNECT_DEFERRED); ep->connect("property_keyed_with_value", callable_mp(this, &EditorInspector::_property_keyed_with_value)); @@ -3175,14 +3173,14 @@ void EditorInspector::_edit_set(const String &p_name, const Variant &p_value, bo } } -void EditorInspector::_property_changed(const String &p_path, const Variant &p_value, const String &p_name, bool p_changing) { +void EditorInspector::_property_changed(const String &p_path, const Variant &p_value, const String &p_name, bool p_changing, bool p_update_all) { // The "changing" variable must be true for properties that trigger events as typing occurs, // like "text_changed" signal. E.g. text property of Label, Button, RichTextLabel, etc. if (p_changing) { this->changing++; } - _edit_set(p_path, p_value, false, p_name); + _edit_set(p_path, p_value, p_update_all, p_name); if (p_changing) { this->changing--; @@ -3193,10 +3191,6 @@ void EditorInspector::_property_changed(const String &p_path, const Variant &p_v } } -void EditorInspector::_property_changed_update_all(const String &p_path, const Variant &p_value, const String &p_name, bool p_changing) { - update_tree(); -} - void EditorInspector::_multiple_properties_changed(Vector<String> p_paths, Array p_values, bool p_changing) { ERR_FAIL_COND(p_paths.size() == 0 || p_values.size() == 0); ERR_FAIL_COND(p_paths.size() != p_values.size()); diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h index f6b4303f38..5992c23f8c 100644 --- a/editor/editor_inspector.h +++ b/editor/editor_inspector.h @@ -457,8 +457,7 @@ class EditorInspector : public ScrollContainer { void _edit_set(const String &p_name, const Variant &p_value, bool p_refresh_all, const String &p_changed_field); - void _property_changed(const String &p_path, const Variant &p_value, const String &p_name = "", bool p_changing = false); - void _property_changed_update_all(const String &p_path, const Variant &p_value, const String &p_name = "", bool p_changing = false); + void _property_changed(const String &p_path, const Variant &p_value, const String &p_name = "", bool p_changing = false, bool p_update_all = false); void _multiple_properties_changed(Vector<String> p_paths, Array p_values, bool p_changing = false); void _property_keyed(const String &p_path, bool p_advance); void _property_keyed_with_value(const String &p_path, const Variant &p_value, bool p_advance); diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index c44760807f..48239a5d99 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -158,7 +158,6 @@ void ScriptTextEditor::enable_editor() { editor_enabled = true; _enable_code_editor(); - _set_theme_for_script(); _validate_script(); } diff --git a/editor/plugins/tiles/tile_data_editors.cpp b/editor/plugins/tiles/tile_data_editors.cpp index d406c2514c..fd5c59af34 100644 --- a/editor/plugins/tiles/tile_data_editors.cpp +++ b/editor/plugins/tiles/tile_data_editors.cpp @@ -1465,12 +1465,13 @@ void TileDataTerrainsEditor::_tile_set_changed() { ERR_FAIL_COND(!tile_set.is_valid()); // Fix if wrong values are selected. - if (int(dummy_object->get("terrain_set")) > tile_set->get_terrain_sets_count()) { + int terrain_set = int(dummy_object->get("terrain_set")); + if (terrain_set >= tile_set->get_terrain_sets_count()) { + terrain_set = -1; dummy_object->set("terrain_set", -1); } - int terrain_set = int(dummy_object->get("terrain")); if (terrain_set >= 0) { - if (int(dummy_object->get("terrain")) > tile_set->get_terrains_count(terrain_set)) { + if (int(dummy_object->get("terrain")) >= tile_set->get_terrains_count(terrain_set)) { dummy_object->set("terrain", -1); } } diff --git a/editor/scene_tree_editor.cpp b/editor/scene_tree_editor.cpp index e7ba80677d..d54bf73028 100644 --- a/editor/scene_tree_editor.cpp +++ b/editor/scene_tree_editor.cpp @@ -291,10 +291,12 @@ bool SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent, bool p_scroll } } + // Display the node name in all tooltips so that long node names can be previewed + // without having to rename them. if (p_node == get_scene_node() && p_node->get_scene_inherited_state().is_valid()) { item->add_button(0, get_theme_icon(SNAME("InstanceOptions"), SNAME("EditorIcons")), BUTTON_SUBSCENE, false, TTR("Open in Editor")); - String tooltip = TTR("Inherits:") + " " + p_node->get_scene_inherited_state()->get_path() + "\n" + TTR("Type:") + " " + p_node->get_class(); + String tooltip = String(p_node->get_name()) + "\n" + TTR("Inherits:") + " " + p_node->get_scene_inherited_state()->get_path() + "\n" + TTR("Type:") + " " + p_node->get_class(); if (p_node->get_editor_description() != String()) { tooltip += "\n\n" + p_node->get_editor_description(); } @@ -303,7 +305,7 @@ bool SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent, bool p_scroll } else if (p_node != get_scene_node() && p_node->get_filename() != "" && can_open_instance) { item->add_button(0, get_theme_icon(SNAME("InstanceOptions"), SNAME("EditorIcons")), BUTTON_SUBSCENE, false, TTR("Open in Editor")); - String tooltip = TTR("Instance:") + " " + p_node->get_filename() + "\n" + TTR("Type:") + " " + p_node->get_class(); + String tooltip = String(p_node->get_name()) + "\n" + TTR("Instance:") + " " + p_node->get_filename() + "\n" + TTR("Type:") + " " + p_node->get_class(); if (p_node->get_editor_description() != String()) { tooltip += "\n\n" + p_node->get_editor_description(); } @@ -315,7 +317,7 @@ bool SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent, bool p_scroll type = p_node->get_class(); } - String tooltip = TTR("Type:") + " " + type; + String tooltip = String(p_node->get_name()) + "\n" + TTR("Type:") + " " + type; if (p_node->get_editor_description() != String()) { tooltip += "\n\n" + p_node->get_editor_description(); } diff --git a/modules/camera/camera_osx.mm b/modules/camera/camera_osx.mm index 4875eb578a..02f7287d1b 100644 --- a/modules/camera/camera_osx.mm +++ b/modules/camera/camera_osx.mm @@ -33,6 +33,7 @@ #include "camera_osx.h" #include "servers/camera/camera_feed.h" + #import <AVFoundation/AVFoundation.h> ////////////////////////////////////////////////////////////////////////// @@ -253,10 +254,25 @@ CameraFeedOSX::~CameraFeedOSX() { bool CameraFeedOSX::activate_feed() { if (capture_session) { - // already recording! + // Already recording! } else { - // start camera capture - capture_session = [[MyCaptureSession alloc] initForFeed:this andDevice:device]; + // Start camera capture, check permission. + if (@available(macOS 10.14, *)) { + AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]; + if (status == AVAuthorizationStatusAuthorized) { + capture_session = [[MyCaptureSession alloc] initForFeed:this andDevice:device]; + } else if (status == AVAuthorizationStatusNotDetermined) { + // Request permission. + [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo + completionHandler:^(BOOL granted) { + if (granted) { + capture_session = [[MyCaptureSession alloc] initForFeed:this andDevice:device]; + } + }]; + } + } else { + capture_session = [[MyCaptureSession alloc] initForFeed:this andDevice:device]; + } }; return true; diff --git a/modules/minimp3/audio_stream_mp3.cpp b/modules/minimp3/audio_stream_mp3.cpp index fb741f6266..7b52ef178a 100644 --- a/modules/minimp3/audio_stream_mp3.cpp +++ b/modules/minimp3/audio_stream_mp3.cpp @@ -214,6 +214,10 @@ float AudioStreamMP3::get_length() const { return length; } +bool AudioStreamMP3::is_monophonic() const { + return false; +} + void AudioStreamMP3::_bind_methods() { ClassDB::bind_method(D_METHOD("set_data", "data"), &AudioStreamMP3::set_data); ClassDB::bind_method(D_METHOD("get_data"), &AudioStreamMP3::get_data); diff --git a/modules/minimp3/audio_stream_mp3.h b/modules/minimp3/audio_stream_mp3.h index 5dd88779f8..3c8bdd8c53 100644 --- a/modules/minimp3/audio_stream_mp3.h +++ b/modules/minimp3/audio_stream_mp3.h @@ -103,6 +103,8 @@ public: virtual float get_length() const override; + virtual bool is_monophonic() const override; + AudioStreamMP3(); virtual ~AudioStreamMP3(); }; diff --git a/modules/stb_vorbis/audio_stream_ogg_vorbis.cpp b/modules/stb_vorbis/audio_stream_ogg_vorbis.cpp index 3a938200e9..6554c6e274 100644 --- a/modules/stb_vorbis/audio_stream_ogg_vorbis.cpp +++ b/modules/stb_vorbis/audio_stream_ogg_vorbis.cpp @@ -252,6 +252,10 @@ float AudioStreamOGGVorbis::get_length() const { return length; } +bool AudioStreamOGGVorbis::is_monophonic() const { + return false; +} + void AudioStreamOGGVorbis::_bind_methods() { ClassDB::bind_method(D_METHOD("set_data", "data"), &AudioStreamOGGVorbis::set_data); ClassDB::bind_method(D_METHOD("get_data"), &AudioStreamOGGVorbis::get_data); diff --git a/modules/stb_vorbis/audio_stream_ogg_vorbis.h b/modules/stb_vorbis/audio_stream_ogg_vorbis.h index 756c241d1f..1311c4ce7a 100644 --- a/modules/stb_vorbis/audio_stream_ogg_vorbis.h +++ b/modules/stb_vorbis/audio_stream_ogg_vorbis.h @@ -105,6 +105,8 @@ public: virtual float get_length() const override; //if supported, otherwise return 0 + virtual bool is_monophonic() const override; + AudioStreamOGGVorbis(); virtual ~AudioStreamOGGVorbis(); }; diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp index 78a87be971..19e94adf68 100644 --- a/modules/text_server_adv/text_server_adv.cpp +++ b/modules/text_server_adv/text_server_adv.cpp @@ -440,266 +440,260 @@ bool TextServerAdvanced::is_locale_right_to_left(const String &p_locale) { } } -struct FeatureInfo { - int32_t tag; - String name; -}; +static Map<StringName, int32_t> feature_sets; -static FeatureInfo feature_set[] = { +static void _insert_feature_sets() { // Registered OpenType feature tags. - { HB_TAG('a', 'a', 'l', 't'), "access_all_alternates" }, - { HB_TAG('a', 'b', 'v', 'f'), "above_base_forms" }, - { HB_TAG('a', 'b', 'v', 'm'), "above_base_mark_positioning" }, - { HB_TAG('a', 'b', 'v', 's'), "above_base_substitutions" }, - { HB_TAG('a', 'f', 'r', 'c'), "alternative_fractions" }, - { HB_TAG('a', 'k', 'h', 'n'), "akhands" }, - { HB_TAG('b', 'l', 'w', 'f'), "below_base_forms" }, - { HB_TAG('b', 'l', 'w', 'm'), "below_base_mark_positioning" }, - { HB_TAG('b', 'l', 'w', 's'), "below_base_substitutions" }, - { HB_TAG('c', 'a', 'l', 't'), "contextual_alternates" }, - { HB_TAG('c', 'a', 's', 'e'), "case_sensitive_forms" }, - { HB_TAG('c', 'c', 'm', 'p'), "glyph_composition" }, - { HB_TAG('c', 'f', 'a', 'r'), "conjunct_form_after_ro" }, - { HB_TAG('c', 'j', 'c', 't'), "conjunct_forms" }, - { HB_TAG('c', 'l', 'i', 'g'), "contextual_ligatures" }, - { HB_TAG('c', 'p', 'c', 't'), "centered_cjk_punctuation" }, - { HB_TAG('c', 'p', 's', 'p'), "capital_spacing" }, - { HB_TAG('c', 's', 'w', 'h'), "contextual_swash" }, - { HB_TAG('c', 'u', 'r', 's'), "cursive_positioning" }, - { HB_TAG('c', 'v', '0', '1'), "character_variant_01" }, - { HB_TAG('c', 'v', '0', '2'), "character_variant_02" }, - { HB_TAG('c', 'v', '0', '3'), "character_variant_03" }, - { HB_TAG('c', 'v', '0', '4'), "character_variant_04" }, - { HB_TAG('c', 'v', '0', '5'), "character_variant_05" }, - { HB_TAG('c', 'v', '0', '6'), "character_variant_06" }, - { HB_TAG('c', 'v', '0', '7'), "character_variant_07" }, - { HB_TAG('c', 'v', '0', '8'), "character_variant_08" }, - { HB_TAG('c', 'v', '0', '9'), "character_variant_09" }, - { HB_TAG('c', 'v', '1', '0'), "character_variant_10" }, - { HB_TAG('c', 'v', '1', '1'), "character_variant_11" }, - { HB_TAG('c', 'v', '1', '2'), "character_variant_12" }, - { HB_TAG('c', 'v', '1', '3'), "character_variant_13" }, - { HB_TAG('c', 'v', '1', '4'), "character_variant_14" }, - { HB_TAG('c', 'v', '1', '5'), "character_variant_15" }, - { HB_TAG('c', 'v', '1', '6'), "character_variant_16" }, - { HB_TAG('c', 'v', '1', '7'), "character_variant_17" }, - { HB_TAG('c', 'v', '1', '8'), "character_variant_18" }, - { HB_TAG('c', 'v', '1', '9'), "character_variant_19" }, - { HB_TAG('c', 'v', '2', '0'), "character_variant_20" }, - { HB_TAG('c', 'v', '2', '1'), "character_variant_21" }, - { HB_TAG('c', 'v', '2', '2'), "character_variant_22" }, - { HB_TAG('c', 'v', '2', '3'), "character_variant_23" }, - { HB_TAG('c', 'v', '2', '4'), "character_variant_24" }, - { HB_TAG('c', 'v', '2', '5'), "character_variant_25" }, - { HB_TAG('c', 'v', '2', '6'), "character_variant_26" }, - { HB_TAG('c', 'v', '2', '7'), "character_variant_27" }, - { HB_TAG('c', 'v', '2', '8'), "character_variant_28" }, - { HB_TAG('c', 'v', '2', '9'), "character_variant_29" }, - { HB_TAG('c', 'v', '3', '0'), "character_variant_30" }, - { HB_TAG('c', 'v', '3', '1'), "character_variant_31" }, - { HB_TAG('c', 'v', '3', '2'), "character_variant_32" }, - { HB_TAG('c', 'v', '3', '3'), "character_variant_33" }, - { HB_TAG('c', 'v', '3', '4'), "character_variant_34" }, - { HB_TAG('c', 'v', '3', '5'), "character_variant_35" }, - { HB_TAG('c', 'v', '3', '6'), "character_variant_36" }, - { HB_TAG('c', 'v', '3', '7'), "character_variant_37" }, - { HB_TAG('c', 'v', '3', '8'), "character_variant_38" }, - { HB_TAG('c', 'v', '3', '9'), "character_variant_39" }, - { HB_TAG('c', 'v', '4', '0'), "character_variant_40" }, - { HB_TAG('c', 'v', '4', '1'), "character_variant_41" }, - { HB_TAG('c', 'v', '4', '2'), "character_variant_42" }, - { HB_TAG('c', 'v', '4', '3'), "character_variant_43" }, - { HB_TAG('c', 'v', '4', '4'), "character_variant_44" }, - { HB_TAG('c', 'v', '4', '5'), "character_variant_45" }, - { HB_TAG('c', 'v', '4', '6'), "character_variant_46" }, - { HB_TAG('c', 'v', '4', '7'), "character_variant_47" }, - { HB_TAG('c', 'v', '4', '8'), "character_variant_48" }, - { HB_TAG('c', 'v', '4', '9'), "character_variant_49" }, - { HB_TAG('c', 'v', '5', '0'), "character_variant_50" }, - { HB_TAG('c', 'v', '5', '1'), "character_variant_51" }, - { HB_TAG('c', 'v', '5', '2'), "character_variant_52" }, - { HB_TAG('c', 'v', '5', '3'), "character_variant_53" }, - { HB_TAG('c', 'v', '5', '4'), "character_variant_54" }, - { HB_TAG('c', 'v', '5', '5'), "character_variant_55" }, - { HB_TAG('c', 'v', '5', '6'), "character_variant_56" }, - { HB_TAG('c', 'v', '5', '7'), "character_variant_57" }, - { HB_TAG('c', 'v', '5', '8'), "character_variant_58" }, - { HB_TAG('c', 'v', '5', '9'), "character_variant_59" }, - { HB_TAG('c', 'v', '6', '0'), "character_variant_60" }, - { HB_TAG('c', 'v', '6', '1'), "character_variant_61" }, - { HB_TAG('c', 'v', '6', '2'), "character_variant_62" }, - { HB_TAG('c', 'v', '6', '3'), "character_variant_63" }, - { HB_TAG('c', 'v', '6', '4'), "character_variant_64" }, - { HB_TAG('c', 'v', '6', '5'), "character_variant_65" }, - { HB_TAG('c', 'v', '6', '6'), "character_variant_66" }, - { HB_TAG('c', 'v', '6', '7'), "character_variant_67" }, - { HB_TAG('c', 'v', '6', '8'), "character_variant_68" }, - { HB_TAG('c', 'v', '6', '9'), "character_variant_69" }, - { HB_TAG('c', 'v', '7', '0'), "character_variant_70" }, - { HB_TAG('c', 'v', '7', '1'), "character_variant_71" }, - { HB_TAG('c', 'v', '7', '2'), "character_variant_72" }, - { HB_TAG('c', 'v', '7', '3'), "character_variant_73" }, - { HB_TAG('c', 'v', '7', '4'), "character_variant_74" }, - { HB_TAG('c', 'v', '7', '5'), "character_variant_75" }, - { HB_TAG('c', 'v', '7', '6'), "character_variant_76" }, - { HB_TAG('c', 'v', '7', '7'), "character_variant_77" }, - { HB_TAG('c', 'v', '7', '8'), "character_variant_78" }, - { HB_TAG('c', 'v', '7', '9'), "character_variant_79" }, - { HB_TAG('c', 'v', '8', '0'), "character_variant_80" }, - { HB_TAG('c', 'v', '8', '1'), "character_variant_81" }, - { HB_TAG('c', 'v', '8', '2'), "character_variant_82" }, - { HB_TAG('c', 'v', '8', '3'), "character_variant_83" }, - { HB_TAG('c', 'v', '8', '4'), "character_variant_84" }, - { HB_TAG('c', 'v', '8', '5'), "character_variant_85" }, - { HB_TAG('c', 'v', '8', '6'), "character_variant_86" }, - { HB_TAG('c', 'v', '8', '7'), "character_variant_87" }, - { HB_TAG('c', 'v', '8', '8'), "character_variant_88" }, - { HB_TAG('c', 'v', '8', '9'), "character_variant_89" }, - { HB_TAG('c', 'v', '9', '0'), "character_variant_90" }, - { HB_TAG('c', 'v', '9', '1'), "character_variant_91" }, - { HB_TAG('c', 'v', '9', '2'), "character_variant_92" }, - { HB_TAG('c', 'v', '9', '3'), "character_variant_93" }, - { HB_TAG('c', 'v', '9', '4'), "character_variant_94" }, - { HB_TAG('c', 'v', '9', '5'), "character_variant_95" }, - { HB_TAG('c', 'v', '9', '6'), "character_variant_96" }, - { HB_TAG('c', 'v', '9', '7'), "character_variant_97" }, - { HB_TAG('c', 'v', '9', '8'), "character_variant_98" }, - { HB_TAG('c', 'v', '9', '9'), "character_variant_99" }, - { HB_TAG('c', '2', 'p', 'c'), "petite_capitals_from_capitals" }, - { HB_TAG('c', '2', 's', 'c'), "small_capitals_from_capitals" }, - { HB_TAG('d', 'i', 's', 't'), "distances" }, - { HB_TAG('d', 'l', 'i', 'g'), "discretionary_ligatures" }, - { HB_TAG('d', 'n', 'o', 'm'), "denominators" }, - { HB_TAG('d', 't', 'l', 's'), "dotless_forms" }, - { HB_TAG('e', 'x', 'p', 't'), "expert_forms" }, - { HB_TAG('f', 'a', 'l', 't'), "final_glyph_on_line_alternates" }, - { HB_TAG('f', 'i', 'n', '2'), "terminal_forms_2" }, - { HB_TAG('f', 'i', 'n', '3'), "terminal_forms_3" }, - { HB_TAG('f', 'i', 'n', 'a'), "terminal_forms" }, - { HB_TAG('f', 'l', 'a', 'c'), "flattened_accent_forms" }, - { HB_TAG('f', 'r', 'a', 'c'), "fractions" }, - { HB_TAG('f', 'w', 'i', 'd'), "full_widths" }, - { HB_TAG('h', 'a', 'l', 'f'), "half_forms" }, - { HB_TAG('h', 'a', 'l', 'n'), "halant_forms" }, - { HB_TAG('h', 'a', 'l', 't'), "alternate_half_widths" }, - { HB_TAG('h', 'i', 's', 't'), "historical_forms" }, - { HB_TAG('h', 'k', 'n', 'a'), "horizontal_kana_alternates" }, - { HB_TAG('h', 'l', 'i', 'g'), "historical_ligatures" }, - { HB_TAG('h', 'n', 'g', 'l'), "hangul" }, - { HB_TAG('h', 'o', 'j', 'o'), "hojo_kanji_forms" }, - { HB_TAG('h', 'w', 'i', 'd'), "half_widths" }, - { HB_TAG('i', 'n', 'i', 't'), "initial_forms" }, - { HB_TAG('i', 's', 'o', 'l'), "isolated_forms" }, - { HB_TAG('i', 't', 'a', 'l'), "italics" }, - { HB_TAG('j', 'a', 'l', 't'), "justification_alternates" }, - { HB_TAG('j', 'p', '7', '8'), "jis78_forms" }, - { HB_TAG('j', 'p', '8', '3'), "jis83_forms" }, - { HB_TAG('j', 'p', '9', '0'), "jis90_forms" }, - { HB_TAG('j', 'p', '0', '4'), "jis2004_forms" }, - { HB_TAG('k', 'e', 'r', 'n'), "kerning" }, - { HB_TAG('l', 'f', 'b', 'd'), "left_bounds" }, - { HB_TAG('l', 'i', 'g', 'a'), "standard_ligatures" }, - { HB_TAG('l', 'j', 'm', 'o'), "leading_jamo_forms" }, - { HB_TAG('l', 'n', 'u', 'm'), "lining_figures" }, - { HB_TAG('l', 'o', 'c', 'l'), "localized_forms" }, - { HB_TAG('l', 't', 'r', 'a'), "left_to_right_alternates" }, - { HB_TAG('l', 't', 'r', 'm'), "left_to_right_mirrored_forms" }, - { HB_TAG('m', 'a', 'r', 'k'), "mark_positioning" }, - { HB_TAG('m', 'e', 'd', '2'), "medial_forms_2" }, - { HB_TAG('m', 'e', 'd', 'i'), "medial_forms" }, - { HB_TAG('m', 'g', 'r', 'k'), "mathematical_greek" }, - { HB_TAG('m', 'k', 'm', 'k'), "mark_to_mark_positioning" }, - { HB_TAG('m', 's', 'e', 't'), "mark_positioning_via_substitution" }, - { HB_TAG('n', 'a', 'l', 't'), "alternate_annotation_forms" }, - { HB_TAG('n', 'l', 'c', 'k'), "nlc_kanji_forms" }, - { HB_TAG('n', 'u', 'k', 't'), "nukta_forms" }, - { HB_TAG('n', 'u', 'm', 'r'), "numerators" }, - { HB_TAG('o', 'n', 'u', 'm'), "oldstyle_figures" }, - { HB_TAG('o', 'p', 'b', 'd'), "optical_bounds" }, - { HB_TAG('o', 'r', 'd', 'n'), "ordinals" }, - { HB_TAG('o', 'r', 'n', 'm'), "ornaments" }, - { HB_TAG('p', 'a', 'l', 't'), "proportional_alternate_widths" }, - { HB_TAG('p', 'c', 'a', 'p'), "petite_capitals" }, - { HB_TAG('p', 'k', 'n', 'a'), "proportional_kana" }, - { HB_TAG('p', 'n', 'u', 'm'), "proportional_figures" }, - { HB_TAG('p', 'r', 'e', 'f'), "pre_base_forms" }, - { HB_TAG('p', 'r', 'e', 's'), "pre_base_substitutions" }, - { HB_TAG('p', 's', 't', 'f'), "post_base_forms" }, - { HB_TAG('p', 's', 't', 's'), "post_base_substitutions" }, - { HB_TAG('p', 'w', 'i', 'd'), "proportional_widths" }, - { HB_TAG('q', 'w', 'i', 'd'), "quarter_widths" }, - { HB_TAG('r', 'a', 'n', 'd'), "randomize" }, - { HB_TAG('r', 'c', 'l', 't'), "required_contextual_alternates" }, - { HB_TAG('r', 'k', 'r', 'f'), "rakar_forms" }, - { HB_TAG('r', 'l', 'i', 'g'), "required_ligatures" }, - { HB_TAG('r', 'p', 'h', 'f'), "reph_forms" }, - { HB_TAG('r', 't', 'b', 'd'), "right_bounds" }, - { HB_TAG('r', 't', 'l', 'a'), "right_to_left_alternates" }, - { HB_TAG('r', 't', 'l', 'm'), "right_to_left_mirrored_forms" }, - { HB_TAG('r', 'u', 'b', 'y'), "ruby_notation_forms" }, - { HB_TAG('r', 'v', 'r', 'n'), "required_variation_alternates" }, - { HB_TAG('s', 'a', 'l', 't'), "stylistic_alternates" }, - { HB_TAG('s', 'i', 'n', 'f'), "scientific_inferiors" }, - { HB_TAG('s', 'i', 'z', 'e'), "optical_size" }, - { HB_TAG('s', 'm', 'c', 'p'), "small_capitals" }, - { HB_TAG('s', 'm', 'p', 'l'), "simplified_forms" }, - { HB_TAG('s', 's', '0', '1'), "stylistic_set_01" }, - { HB_TAG('s', 's', '0', '2'), "stylistic_set_02" }, - { HB_TAG('s', 's', '0', '3'), "stylistic_set_03" }, - { HB_TAG('s', 's', '0', '4'), "stylistic_set_04" }, - { HB_TAG('s', 's', '0', '5'), "stylistic_set_05" }, - { HB_TAG('s', 's', '0', '6'), "stylistic_set_06" }, - { HB_TAG('s', 's', '0', '7'), "stylistic_set_07" }, - { HB_TAG('s', 's', '0', '8'), "stylistic_set_08" }, - { HB_TAG('s', 's', '0', '9'), "stylistic_set_09" }, - { HB_TAG('s', 's', '1', '0'), "stylistic_set_10" }, - { HB_TAG('s', 's', '1', '1'), "stylistic_set_11" }, - { HB_TAG('s', 's', '1', '2'), "stylistic_set_12" }, - { HB_TAG('s', 's', '1', '3'), "stylistic_set_13" }, - { HB_TAG('s', 's', '1', '4'), "stylistic_set_14" }, - { HB_TAG('s', 's', '1', '5'), "stylistic_set_15" }, - { HB_TAG('s', 's', '1', '6'), "stylistic_set_16" }, - { HB_TAG('s', 's', '1', '7'), "stylistic_set_17" }, - { HB_TAG('s', 's', '1', '8'), "stylistic_set_18" }, - { HB_TAG('s', 's', '1', '9'), "stylistic_set_19" }, - { HB_TAG('s', 's', '2', '0'), "stylistic_set_20" }, - { HB_TAG('s', 's', 't', 'y'), "math_script_style_alternates" }, - { HB_TAG('s', 't', 'c', 'h'), "stretching_glyph_decomposition" }, - { HB_TAG('s', 'u', 'b', 's'), "subscript" }, - { HB_TAG('s', 'u', 'p', 's'), "superscript" }, - { HB_TAG('s', 'w', 's', 'h'), "swash" }, - { HB_TAG('t', 'i', 't', 'l'), "titling" }, - { HB_TAG('t', 'j', 'm', 'o'), "trailing_jamo_forms" }, - { HB_TAG('t', 'n', 'a', 'm'), "traditional_name_forms" }, - { HB_TAG('t', 'n', 'u', 'm'), "tabular_figures" }, - { HB_TAG('t', 'r', 'a', 'd'), "traditional_forms" }, - { HB_TAG('t', 'w', 'i', 'd'), "third_widths" }, - { HB_TAG('u', 'n', 'i', 'c'), "unicase" }, - { HB_TAG('v', 'a', 'l', 't'), "alternate_vertical_metrics" }, - { HB_TAG('v', 'a', 't', 'u'), "vattu_variants" }, - { HB_TAG('v', 'e', 'r', 't'), "vertical_writing" }, - { HB_TAG('v', 'h', 'a', 'l'), "alternate_vertical_half_metrics" }, - { HB_TAG('v', 'j', 'm', 'o'), "vowel_jamo_forms" }, - { HB_TAG('v', 'k', 'n', 'a'), "vertical_kana_alternates" }, - { HB_TAG('v', 'k', 'r', 'n'), "vertical_kerning" }, - { HB_TAG('v', 'p', 'a', 'l'), "proportional_alternate_vertical_metrics" }, - { HB_TAG('v', 'r', 't', '2'), "vertical_alternates_and_rotation" }, - { HB_TAG('v', 'r', 't', 'r'), "vertical_alternates_for_rotation" }, - { HB_TAG('z', 'e', 'r', 'o'), "slashed_zero" }, - // Registered OpenType variation tags. - { HB_TAG('i', 't', 'a', 'l'), "italic" }, - { HB_TAG('o', 'p', 's', 'z'), "optical_size" }, - { HB_TAG('s', 'l', 'n', 't'), "slant" }, - { HB_TAG('w', 'd', 't', 'h'), "width" }, - { HB_TAG('w', 'g', 'h', 't'), "weight" }, - { 0, String() }, -}; + feature_sets.insert("access_all_alternates", HB_TAG('a', 'a', 'l', 't')); + feature_sets.insert("above_base_forms", HB_TAG('a', 'b', 'v', 'f')); + feature_sets.insert("above_base_mark_positioning", HB_TAG('a', 'b', 'v', 'm')); + feature_sets.insert("above_base_substitutions", HB_TAG('a', 'b', 'v', 's')); + feature_sets.insert("alternative_fractions", HB_TAG('a', 'f', 'r', 'c')); + feature_sets.insert("akhands", HB_TAG('a', 'k', 'h', 'n')); + feature_sets.insert("below_base_forms", HB_TAG('b', 'l', 'w', 'f')); + feature_sets.insert("below_base_mark_positioning", HB_TAG('b', 'l', 'w', 'm')); + feature_sets.insert("below_base_substitutions", HB_TAG('b', 'l', 'w', 's')); + feature_sets.insert("contextual_alternates", HB_TAG('c', 'a', 'l', 't')); + feature_sets.insert("case_sensitive_forms", HB_TAG('c', 'a', 's', 'e')); + feature_sets.insert("glyph_composition", HB_TAG('c', 'c', 'm', 'p')); + feature_sets.insert("conjunct_form_after_ro", HB_TAG('c', 'f', 'a', 'r')); + feature_sets.insert("conjunct_forms", HB_TAG('c', 'j', 'c', 't')); + feature_sets.insert("contextual_ligatures", HB_TAG('c', 'l', 'i', 'g')); + feature_sets.insert("centered_cjk_punctuation", HB_TAG('c', 'p', 'c', 't')); + feature_sets.insert("capital_spacing", HB_TAG('c', 'p', 's', 'p')); + feature_sets.insert("contextual_swash", HB_TAG('c', 's', 'w', 'h')); + feature_sets.insert("cursive_positioning", HB_TAG('c', 'u', 'r', 's')); + feature_sets.insert("character_variant_01", HB_TAG('c', 'v', '0', '1')); + feature_sets.insert("character_variant_02", HB_TAG('c', 'v', '0', '2')); + feature_sets.insert("character_variant_03", HB_TAG('c', 'v', '0', '3')); + feature_sets.insert("character_variant_04", HB_TAG('c', 'v', '0', '4')); + feature_sets.insert("character_variant_05", HB_TAG('c', 'v', '0', '5')); + feature_sets.insert("character_variant_06", HB_TAG('c', 'v', '0', '6')); + feature_sets.insert("character_variant_07", HB_TAG('c', 'v', '0', '7')); + feature_sets.insert("character_variant_08", HB_TAG('c', 'v', '0', '8')); + feature_sets.insert("character_variant_09", HB_TAG('c', 'v', '0', '9')); + feature_sets.insert("character_variant_10", HB_TAG('c', 'v', '1', '0')); + feature_sets.insert("character_variant_11", HB_TAG('c', 'v', '1', '1')); + feature_sets.insert("character_variant_12", HB_TAG('c', 'v', '1', '2')); + feature_sets.insert("character_variant_13", HB_TAG('c', 'v', '1', '3')); + feature_sets.insert("character_variant_14", HB_TAG('c', 'v', '1', '4')); + feature_sets.insert("character_variant_15", HB_TAG('c', 'v', '1', '5')); + feature_sets.insert("character_variant_16", HB_TAG('c', 'v', '1', '6')); + feature_sets.insert("character_variant_17", HB_TAG('c', 'v', '1', '7')); + feature_sets.insert("character_variant_18", HB_TAG('c', 'v', '1', '8')); + feature_sets.insert("character_variant_19", HB_TAG('c', 'v', '1', '9')); + feature_sets.insert("character_variant_20", HB_TAG('c', 'v', '2', '0')); + feature_sets.insert("character_variant_21", HB_TAG('c', 'v', '2', '1')); + feature_sets.insert("character_variant_22", HB_TAG('c', 'v', '2', '2')); + feature_sets.insert("character_variant_23", HB_TAG('c', 'v', '2', '3')); + feature_sets.insert("character_variant_24", HB_TAG('c', 'v', '2', '4')); + feature_sets.insert("character_variant_25", HB_TAG('c', 'v', '2', '5')); + feature_sets.insert("character_variant_26", HB_TAG('c', 'v', '2', '6')); + feature_sets.insert("character_variant_27", HB_TAG('c', 'v', '2', '7')); + feature_sets.insert("character_variant_28", HB_TAG('c', 'v', '2', '8')); + feature_sets.insert("character_variant_29", HB_TAG('c', 'v', '2', '9')); + feature_sets.insert("character_variant_30", HB_TAG('c', 'v', '3', '0')); + feature_sets.insert("character_variant_31", HB_TAG('c', 'v', '3', '1')); + feature_sets.insert("character_variant_32", HB_TAG('c', 'v', '3', '2')); + feature_sets.insert("character_variant_33", HB_TAG('c', 'v', '3', '3')); + feature_sets.insert("character_variant_34", HB_TAG('c', 'v', '3', '4')); + feature_sets.insert("character_variant_35", HB_TAG('c', 'v', '3', '5')); + feature_sets.insert("character_variant_36", HB_TAG('c', 'v', '3', '6')); + feature_sets.insert("character_variant_37", HB_TAG('c', 'v', '3', '7')); + feature_sets.insert("character_variant_38", HB_TAG('c', 'v', '3', '8')); + feature_sets.insert("character_variant_39", HB_TAG('c', 'v', '3', '9')); + feature_sets.insert("character_variant_40", HB_TAG('c', 'v', '4', '0')); + feature_sets.insert("character_variant_41", HB_TAG('c', 'v', '4', '1')); + feature_sets.insert("character_variant_42", HB_TAG('c', 'v', '4', '2')); + feature_sets.insert("character_variant_43", HB_TAG('c', 'v', '4', '3')); + feature_sets.insert("character_variant_44", HB_TAG('c', 'v', '4', '4')); + feature_sets.insert("character_variant_45", HB_TAG('c', 'v', '4', '5')); + feature_sets.insert("character_variant_46", HB_TAG('c', 'v', '4', '6')); + feature_sets.insert("character_variant_47", HB_TAG('c', 'v', '4', '7')); + feature_sets.insert("character_variant_48", HB_TAG('c', 'v', '4', '8')); + feature_sets.insert("character_variant_49", HB_TAG('c', 'v', '4', '9')); + feature_sets.insert("character_variant_50", HB_TAG('c', 'v', '5', '0')); + feature_sets.insert("character_variant_51", HB_TAG('c', 'v', '5', '1')); + feature_sets.insert("character_variant_52", HB_TAG('c', 'v', '5', '2')); + feature_sets.insert("character_variant_53", HB_TAG('c', 'v', '5', '3')); + feature_sets.insert("character_variant_54", HB_TAG('c', 'v', '5', '4')); + feature_sets.insert("character_variant_55", HB_TAG('c', 'v', '5', '5')); + feature_sets.insert("character_variant_56", HB_TAG('c', 'v', '5', '6')); + feature_sets.insert("character_variant_57", HB_TAG('c', 'v', '5', '7')); + feature_sets.insert("character_variant_58", HB_TAG('c', 'v', '5', '8')); + feature_sets.insert("character_variant_59", HB_TAG('c', 'v', '5', '9')); + feature_sets.insert("character_variant_60", HB_TAG('c', 'v', '6', '0')); + feature_sets.insert("character_variant_61", HB_TAG('c', 'v', '6', '1')); + feature_sets.insert("character_variant_62", HB_TAG('c', 'v', '6', '2')); + feature_sets.insert("character_variant_63", HB_TAG('c', 'v', '6', '3')); + feature_sets.insert("character_variant_64", HB_TAG('c', 'v', '6', '4')); + feature_sets.insert("character_variant_65", HB_TAG('c', 'v', '6', '5')); + feature_sets.insert("character_variant_66", HB_TAG('c', 'v', '6', '6')); + feature_sets.insert("character_variant_67", HB_TAG('c', 'v', '6', '7')); + feature_sets.insert("character_variant_68", HB_TAG('c', 'v', '6', '8')); + feature_sets.insert("character_variant_69", HB_TAG('c', 'v', '6', '9')); + feature_sets.insert("character_variant_70", HB_TAG('c', 'v', '7', '0')); + feature_sets.insert("character_variant_71", HB_TAG('c', 'v', '7', '1')); + feature_sets.insert("character_variant_72", HB_TAG('c', 'v', '7', '2')); + feature_sets.insert("character_variant_73", HB_TAG('c', 'v', '7', '3')); + feature_sets.insert("character_variant_74", HB_TAG('c', 'v', '7', '4')); + feature_sets.insert("character_variant_75", HB_TAG('c', 'v', '7', '5')); + feature_sets.insert("character_variant_76", HB_TAG('c', 'v', '7', '6')); + feature_sets.insert("character_variant_77", HB_TAG('c', 'v', '7', '7')); + feature_sets.insert("character_variant_78", HB_TAG('c', 'v', '7', '8')); + feature_sets.insert("character_variant_79", HB_TAG('c', 'v', '7', '9')); + feature_sets.insert("character_variant_80", HB_TAG('c', 'v', '8', '0')); + feature_sets.insert("character_variant_81", HB_TAG('c', 'v', '8', '1')); + feature_sets.insert("character_variant_82", HB_TAG('c', 'v', '8', '2')); + feature_sets.insert("character_variant_83", HB_TAG('c', 'v', '8', '3')); + feature_sets.insert("character_variant_84", HB_TAG('c', 'v', '8', '4')); + feature_sets.insert("character_variant_85", HB_TAG('c', 'v', '8', '5')); + feature_sets.insert("character_variant_86", HB_TAG('c', 'v', '8', '6')); + feature_sets.insert("character_variant_87", HB_TAG('c', 'v', '8', '7')); + feature_sets.insert("character_variant_88", HB_TAG('c', 'v', '8', '8')); + feature_sets.insert("character_variant_89", HB_TAG('c', 'v', '8', '9')); + feature_sets.insert("character_variant_90", HB_TAG('c', 'v', '9', '0')); + feature_sets.insert("character_variant_91", HB_TAG('c', 'v', '9', '1')); + feature_sets.insert("character_variant_92", HB_TAG('c', 'v', '9', '2')); + feature_sets.insert("character_variant_93", HB_TAG('c', 'v', '9', '3')); + feature_sets.insert("character_variant_94", HB_TAG('c', 'v', '9', '4')); + feature_sets.insert("character_variant_95", HB_TAG('c', 'v', '9', '5')); + feature_sets.insert("character_variant_96", HB_TAG('c', 'v', '9', '6')); + feature_sets.insert("character_variant_97", HB_TAG('c', 'v', '9', '7')); + feature_sets.insert("character_variant_98", HB_TAG('c', 'v', '9', '8')); + feature_sets.insert("character_variant_99", HB_TAG('c', 'v', '9', '9')); + feature_sets.insert("petite_capitals_from_capitals", HB_TAG('c', '2', 'p', 'c')); + feature_sets.insert("small_capitals_from_capitals", HB_TAG('c', '2', 's', 'c')); + feature_sets.insert("distances", HB_TAG('d', 'i', 's', 't')); + feature_sets.insert("discretionary_ligatures", HB_TAG('d', 'l', 'i', 'g')); + feature_sets.insert("denominators", HB_TAG('d', 'n', 'o', 'm')); + feature_sets.insert("dotless_forms", HB_TAG('d', 't', 'l', 's')); + feature_sets.insert("expert_forms", HB_TAG('e', 'x', 'p', 't')); + feature_sets.insert("final_glyph_on_line_alternates", HB_TAG('f', 'a', 'l', 't')); + feature_sets.insert("terminal_forms_2", HB_TAG('f', 'i', 'n', '2')); + feature_sets.insert("terminal_forms_3", HB_TAG('f', 'i', 'n', '3')); + feature_sets.insert("terminal_forms", HB_TAG('f', 'i', 'n', 'a')); + feature_sets.insert("flattened_accent_forms", HB_TAG('f', 'l', 'a', 'c')); + feature_sets.insert("fractions", HB_TAG('f', 'r', 'a', 'c')); + feature_sets.insert("full_widths", HB_TAG('f', 'w', 'i', 'd')); + feature_sets.insert("half_forms", HB_TAG('h', 'a', 'l', 'f')); + feature_sets.insert("halant_forms", HB_TAG('h', 'a', 'l', 'n')); + feature_sets.insert("alternate_half_widths", HB_TAG('h', 'a', 'l', 't')); + feature_sets.insert("historical_forms", HB_TAG('h', 'i', 's', 't')); + feature_sets.insert("horizontal_kana_alternates", HB_TAG('h', 'k', 'n', 'a')); + feature_sets.insert("historical_ligatures", HB_TAG('h', 'l', 'i', 'g')); + feature_sets.insert("hangul", HB_TAG('h', 'n', 'g', 'l')); + feature_sets.insert("hojo_kanji_forms", HB_TAG('h', 'o', 'j', 'o')); + feature_sets.insert("half_widths", HB_TAG('h', 'w', 'i', 'd')); + feature_sets.insert("initial_forms", HB_TAG('i', 'n', 'i', 't')); + feature_sets.insert("isolated_forms", HB_TAG('i', 's', 'o', 'l')); + feature_sets.insert("italics", HB_TAG('i', 't', 'a', 'l')); + feature_sets.insert("justification_alternates", HB_TAG('j', 'a', 'l', 't')); + feature_sets.insert("jis78_forms", HB_TAG('j', 'p', '7', '8')); + feature_sets.insert("jis83_forms", HB_TAG('j', 'p', '8', '3')); + feature_sets.insert("jis90_forms", HB_TAG('j', 'p', '9', '0')); + feature_sets.insert("jis2004_forms", HB_TAG('j', 'p', '0', '4')); + feature_sets.insert("kerning", HB_TAG('k', 'e', 'r', 'n')); + feature_sets.insert("left_bounds", HB_TAG('l', 'f', 'b', 'd')); + feature_sets.insert("standard_ligatures", HB_TAG('l', 'i', 'g', 'a')); + feature_sets.insert("leading_jamo_forms", HB_TAG('l', 'j', 'm', 'o')); + feature_sets.insert("lining_figures", HB_TAG('l', 'n', 'u', 'm')); + feature_sets.insert("localized_forms", HB_TAG('l', 'o', 'c', 'l')); + feature_sets.insert("left_to_right_alternates", HB_TAG('l', 't', 'r', 'a')); + feature_sets.insert("left_to_right_mirrored_forms", HB_TAG('l', 't', 'r', 'm')); + feature_sets.insert("mark_positioning", HB_TAG('m', 'a', 'r', 'k')); + feature_sets.insert("medial_forms_2", HB_TAG('m', 'e', 'd', '2')); + feature_sets.insert("medial_forms", HB_TAG('m', 'e', 'd', 'i')); + feature_sets.insert("mathematical_greek", HB_TAG('m', 'g', 'r', 'k')); + feature_sets.insert("mark_to_mark_positioning", HB_TAG('m', 'k', 'm', 'k')); + feature_sets.insert("mark_positioning_via_substitution", HB_TAG('m', 's', 'e', 't')); + feature_sets.insert("alternate_annotation_forms", HB_TAG('n', 'a', 'l', 't')); + feature_sets.insert("nlc_kanji_forms", HB_TAG('n', 'l', 'c', 'k')); + feature_sets.insert("nukta_forms", HB_TAG('n', 'u', 'k', 't')); + feature_sets.insert("numerators", HB_TAG('n', 'u', 'm', 'r')); + feature_sets.insert("oldstyle_figures", HB_TAG('o', 'n', 'u', 'm')); + feature_sets.insert("optical_bounds", HB_TAG('o', 'p', 'b', 'd')); + feature_sets.insert("ordinals", HB_TAG('o', 'r', 'd', 'n')); + feature_sets.insert("ornaments", HB_TAG('o', 'r', 'n', 'm')); + feature_sets.insert("proportional_alternate_widths", HB_TAG('p', 'a', 'l', 't')); + feature_sets.insert("petite_capitals", HB_TAG('p', 'c', 'a', 'p')); + feature_sets.insert("proportional_kana", HB_TAG('p', 'k', 'n', 'a')); + feature_sets.insert("proportional_figures", HB_TAG('p', 'n', 'u', 'm')); + feature_sets.insert("pre_base_forms", HB_TAG('p', 'r', 'e', 'f')); + feature_sets.insert("pre_base_substitutions", HB_TAG('p', 'r', 'e', 's')); + feature_sets.insert("post_base_forms", HB_TAG('p', 's', 't', 'f')); + feature_sets.insert("post_base_substitutions", HB_TAG('p', 's', 't', 's')); + feature_sets.insert("proportional_widths", HB_TAG('p', 'w', 'i', 'd')); + feature_sets.insert("quarter_widths", HB_TAG('q', 'w', 'i', 'd')); + feature_sets.insert("randomize", HB_TAG('r', 'a', 'n', 'd')); + feature_sets.insert("required_contextual_alternates", HB_TAG('r', 'c', 'l', 't')); + feature_sets.insert("rakar_forms", HB_TAG('r', 'k', 'r', 'f')); + feature_sets.insert("required_ligatures", HB_TAG('r', 'l', 'i', 'g')); + feature_sets.insert("reph_forms", HB_TAG('r', 'p', 'h', 'f')); + feature_sets.insert("right_bounds", HB_TAG('r', 't', 'b', 'd')); + feature_sets.insert("right_to_left_alternates", HB_TAG('r', 't', 'l', 'a')); + feature_sets.insert("right_to_left_mirrored_forms", HB_TAG('r', 't', 'l', 'm')); + feature_sets.insert("ruby_notation_forms", HB_TAG('r', 'u', 'b', 'y')); + feature_sets.insert("required_variation_alternates", HB_TAG('r', 'v', 'r', 'n')); + feature_sets.insert("stylistic_alternates", HB_TAG('s', 'a', 'l', 't')); + feature_sets.insert("scientific_inferiors", HB_TAG('s', 'i', 'n', 'f')); + feature_sets.insert("optical_size", HB_TAG('s', 'i', 'z', 'e')); + feature_sets.insert("small_capitals", HB_TAG('s', 'm', 'c', 'p')); + feature_sets.insert("simplified_forms", HB_TAG('s', 'm', 'p', 'l')); + feature_sets.insert("stylistic_set_01", HB_TAG('s', 's', '0', '1')); + feature_sets.insert("stylistic_set_02", HB_TAG('s', 's', '0', '2')); + feature_sets.insert("stylistic_set_03", HB_TAG('s', 's', '0', '3')); + feature_sets.insert("stylistic_set_04", HB_TAG('s', 's', '0', '4')); + feature_sets.insert("stylistic_set_05", HB_TAG('s', 's', '0', '5')); + feature_sets.insert("stylistic_set_06", HB_TAG('s', 's', '0', '6')); + feature_sets.insert("stylistic_set_07", HB_TAG('s', 's', '0', '7')); + feature_sets.insert("stylistic_set_08", HB_TAG('s', 's', '0', '8')); + feature_sets.insert("stylistic_set_09", HB_TAG('s', 's', '0', '9')); + feature_sets.insert("stylistic_set_10", HB_TAG('s', 's', '1', '0')); + feature_sets.insert("stylistic_set_11", HB_TAG('s', 's', '1', '1')); + feature_sets.insert("stylistic_set_12", HB_TAG('s', 's', '1', '2')); + feature_sets.insert("stylistic_set_13", HB_TAG('s', 's', '1', '3')); + feature_sets.insert("stylistic_set_14", HB_TAG('s', 's', '1', '4')); + feature_sets.insert("stylistic_set_15", HB_TAG('s', 's', '1', '5')); + feature_sets.insert("stylistic_set_16", HB_TAG('s', 's', '1', '6')); + feature_sets.insert("stylistic_set_17", HB_TAG('s', 's', '1', '7')); + feature_sets.insert("stylistic_set_18", HB_TAG('s', 's', '1', '8')); + feature_sets.insert("stylistic_set_19", HB_TAG('s', 's', '1', '9')); + feature_sets.insert("stylistic_set_20", HB_TAG('s', 's', '2', '0')); + feature_sets.insert("math_script_style_alternates", HB_TAG('s', 's', 't', 'y')); + feature_sets.insert("stretching_glyph_decomposition", HB_TAG('s', 't', 'c', 'h')); + feature_sets.insert("subscript", HB_TAG('s', 'u', 'b', 's')); + feature_sets.insert("superscript", HB_TAG('s', 'u', 'p', 's')); + feature_sets.insert("swash", HB_TAG('s', 'w', 's', 'h')); + feature_sets.insert("titling", HB_TAG('t', 'i', 't', 'l')); + feature_sets.insert("trailing_jamo_forms", HB_TAG('t', 'j', 'm', 'o')); + feature_sets.insert("traditional_name_forms", HB_TAG('t', 'n', 'a', 'm')); + feature_sets.insert("tabular_figures", HB_TAG('t', 'n', 'u', 'm')); + feature_sets.insert("traditional_forms", HB_TAG('t', 'r', 'a', 'd')); + feature_sets.insert("third_widths", HB_TAG('t', 'w', 'i', 'd')); + feature_sets.insert("unicase", HB_TAG('u', 'n', 'i', 'c')); + feature_sets.insert("alternate_vertical_metrics", HB_TAG('v', 'a', 'l', 't')); + feature_sets.insert("vattu_variants", HB_TAG('v', 'a', 't', 'u')); + feature_sets.insert("vertical_writing", HB_TAG('v', 'e', 'r', 't')); + feature_sets.insert("alternate_vertical_half_metrics", HB_TAG('v', 'h', 'a', 'l')); + feature_sets.insert("vowel_jamo_forms", HB_TAG('v', 'j', 'm', 'o')); + feature_sets.insert("vertical_kana_alternates", HB_TAG('v', 'k', 'n', 'a')); + feature_sets.insert("vertical_kerning", HB_TAG('v', 'k', 'r', 'n')); + feature_sets.insert("proportional_alternate_vertical_metrics", HB_TAG('v', 'p', 'a', 'l')); + feature_sets.insert("vertical_alternates_and_rotation", HB_TAG('v', 'r', 't', '2')); + feature_sets.insert("vertical_alternates_for_rotation", HB_TAG('v', 'r', 't', 'r')); + feature_sets.insert("slashed_zero", HB_TAG('z', 'e', 'r', 'o')); + // Registered OpenType variation tag. + feature_sets.insert("italic", HB_TAG('i', 't', 'a', 'l')); + feature_sets.insert("optical_size", HB_TAG('o', 'p', 's', 'z')); + feature_sets.insert("slant", HB_TAG('s', 'l', 'n', 't')); + feature_sets.insert("width", HB_TAG('w', 'd', 't', 'h')); + feature_sets.insert("weight", HB_TAG('w', 'g', 'h', 't')); +} int32_t TextServerAdvanced::name_to_tag(const String &p_name) const { - for (int i = 0; feature_set[i].tag != 0; i++) { - if (feature_set[i].name == p_name) { - return feature_set[i].tag; - } + if (feature_sets.has(p_name)) { + return feature_sets[p_name]; } // No readable name, use tag string. @@ -707,9 +701,9 @@ int32_t TextServerAdvanced::name_to_tag(const String &p_name) const { } String TextServerAdvanced::tag_to_name(int32_t p_tag) const { - for (int i = 0; feature_set[i].tag != 0; i++) { - if (feature_set[i].tag == p_tag) { - return feature_set[i].name; + for (const KeyValue<StringName, int32_t> &E : feature_sets) { + if (E.value == p_tag) { + return E.key; } } @@ -4633,30 +4627,76 @@ real_t TextServerAdvanced::shaped_text_get_underline_thickness(RID p_shaped) con } struct num_system_data { - String lang; + Set<String> lang; String digits; String percent_sign; String exp; }; static num_system_data num_systems[]{ - { "ar,ar_AR,ar_BH,ar_DJ,ar_EG,ar_ER,ar_IL,ar_IQ,ar_JO,ar_KM,ar_KW,ar_LB,ar_MR,ar_OM,ar_PS,ar_QA,ar_SA,ar_SD,ar_SO,ar_SS,ar_SY,ar_TD,ar_YE", U"٠١٢٣٤٥٦٧٨٩٫", U"٪", U"اس" }, - { "fa,ks,pa_Arab,ps,ug,ur_IN,ur,uz_Arab", U"۰۱۲۳۴۵۶۷۸۹٫", U"٪", U"اس" }, - { "as,bn,mni", U"০১২৩৪৫৬৭৮৯.", U"%", U"e" }, - { "mr,ne", U"०१२३४५६७८९.", U"%", U"e" }, - { "dz", U"༠༡༢༣༤༥༦༧༨༩.", U"%", U"e" }, - { "sat", U"᱐᱑᱒᱓᱔᱕᱖᱗᱘᱙.", U"%", U"e" }, - { "my", U"၀၁၂၃၄၅၆၇၈၉.", U"%", U"e" }, - { String(), String(), String(), String() }, + { Set<String>(), U"٠١٢٣٤٥٦٧٨٩٫", U"٪", U"اس" }, + { Set<String>(), U"۰۱۲۳۴۵۶۷۸۹٫", U"٪", U"اس" }, + { Set<String>(), U"০১২৩৪৫৬৭৮৯.", U"%", U"e" }, + { Set<String>(), U"०१२३४५६७८९.", U"%", U"e" }, + { Set<String>(), U"༠༡༢༣༤༥༦༧༨༩.", U"%", U"e" }, + { Set<String>(), U"᱐᱑᱒᱓᱔᱕᱖᱗᱘᱙.", U"%", U"e" }, + { Set<String>(), U"၀၁၂၃၄၅၆၇၈၉.", U"%", U"e" }, + { Set<String>(), String(), String(), String() }, }; +static void _insert_num_systems_lang() { + num_systems[0].lang.insert(StringName("ar")); + num_systems[0].lang.insert(StringName("ar_AR")); + num_systems[0].lang.insert(StringName("ar_BH")); + num_systems[0].lang.insert(StringName("ar_DJ")); + num_systems[0].lang.insert(StringName("ar_EG")); + num_systems[0].lang.insert(StringName("ar_ER")); + num_systems[0].lang.insert(StringName("ar_IL")); + num_systems[0].lang.insert(StringName("ar_IQ")); + num_systems[0].lang.insert(StringName("ar_JO")); + num_systems[0].lang.insert(StringName("ar_KM")); + num_systems[0].lang.insert(StringName("ar_KW")); + num_systems[0].lang.insert(StringName("ar_LB")); + num_systems[0].lang.insert(StringName("ar_MR")); + num_systems[0].lang.insert(StringName("ar_OM")); + num_systems[0].lang.insert(StringName("ar_PS")); + num_systems[0].lang.insert(StringName("ar_QA")); + num_systems[0].lang.insert(StringName("ar_SA")); + num_systems[0].lang.insert(StringName("ar_SD")); + num_systems[0].lang.insert(StringName("ar_SO")); + num_systems[0].lang.insert(StringName("ar_SS")); + num_systems[0].lang.insert(StringName("ar_SY")); + num_systems[0].lang.insert(StringName("ar_TD")); + num_systems[0].lang.insert(StringName("ar_YE")); + + num_systems[1].lang.insert(StringName("fa")); + num_systems[1].lang.insert(StringName("ks")); + num_systems[1].lang.insert(StringName("pa_Arab")); + num_systems[1].lang.insert(StringName("ug")); + num_systems[1].lang.insert(StringName("ur_IN")); + num_systems[1].lang.insert(StringName("ur")); + num_systems[1].lang.insert(StringName("uz_Arab")); + + num_systems[2].lang.insert(StringName("as")); + num_systems[2].lang.insert(StringName("bn")); + num_systems[2].lang.insert(StringName("mni")); + + num_systems[3].lang.insert(StringName("mr")); + num_systems[3].lang.insert(StringName("ne")); + + num_systems[4].lang.insert(StringName("dz")); + + num_systems[5].lang.insert(StringName("sat")); + + num_systems[6].lang.insert(StringName("my")); +} + String TextServerAdvanced::format_number(const String &p_string, const String &p_language) const { - String lang = (p_language == "") ? TranslationServer::get_singleton()->get_tool_locale() : p_language; + const StringName lang = (p_language == "") ? TranslationServer::get_singleton()->get_tool_locale() : p_language; String res = p_string; - for (int i = 0; num_systems[i].lang != String(); i++) { - Vector<String> langs = num_systems[i].lang.split(","); - if (langs.has(lang)) { + for (int i = 0; num_systems[i].lang.size() != 0; i++) { + if (num_systems[i].lang.has(lang)) { if (num_systems[i].digits == String()) { return p_string; } @@ -4677,12 +4717,11 @@ String TextServerAdvanced::format_number(const String &p_string, const String &p } String TextServerAdvanced::parse_number(const String &p_string, const String &p_language) const { - String lang = (p_language == "") ? TranslationServer::get_singleton()->get_tool_locale() : p_language; + const StringName lang = (p_language == "") ? TranslationServer::get_singleton()->get_tool_locale() : p_language; String res = p_string; - for (int i = 0; num_systems[i].lang != String(); i++) { - Vector<String> langs = num_systems[i].lang.split(","); - if (langs.has(lang)) { + for (int i = 0; num_systems[i].lang.size() != 0; i++) { + if (num_systems[i].lang.has(lang)) { if (num_systems[i].digits == String()) { return p_string; } @@ -4706,11 +4745,10 @@ String TextServerAdvanced::parse_number(const String &p_string, const String &p_ } String TextServerAdvanced::percent_sign(const String &p_language) const { - String lang = (p_language == "") ? TranslationServer::get_singleton()->get_tool_locale() : p_language; + const StringName lang = (p_language == "") ? TranslationServer::get_singleton()->get_tool_locale() : p_language; - for (int i = 0; num_systems[i].lang != String(); i++) { - Vector<String> langs = num_systems[i].lang.split(","); - if (langs.has(lang)) { + for (int i = 0; num_systems[i].lang.size() != 0; i++) { + if (num_systems[i].lang.has(lang)) { if (num_systems[i].percent_sign == String()) { return "%"; } @@ -4730,6 +4768,8 @@ void TextServerAdvanced::register_server() { } TextServerAdvanced::TextServerAdvanced() { + _insert_num_systems_lang(); + _insert_feature_sets(); hb_bmp_create_font_funcs(); } diff --git a/platform/android/java/app/build.gradle b/platform/android/java/app/build.gradle index 18e07c3762..9640887399 100644 --- a/platform/android/java/app/build.gradle +++ b/platform/android/java/app/build.gradle @@ -34,9 +34,8 @@ allprojects { } dependencies { - implementation libraries.supportCoreUtils implementation libraries.kotlinStdLib - implementation libraries.v4Support + implementation libraries.androidxFragment if (rootProject.findProject(":lib")) { implementation project(":lib") diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle index fad64c675f..fcee54e493 100644 --- a/platform/android/java/app/config.gradle +++ b/platform/android/java/app/config.gradle @@ -4,9 +4,8 @@ ext.versions = [ minSdk : 19, targetSdk : 30, buildTools : '30.0.3', - supportCoreUtils : '1.0.0', kotlinVersion : '1.5.10', - v4Support : '1.0.0', + fragmentVersion : '1.3.6', javaVersion : 1.8, ndkVersion : '21.4.7075529' // Also update 'platform/android/detect.py#get_project_ndk_version()' when this is updated. @@ -14,10 +13,9 @@ ext.versions = [ ext.libraries = [ androidGradlePlugin: "com.android.tools.build:gradle:$versions.androidGradlePlugin", - supportCoreUtils : "androidx.legacy:legacy-support-core-utils:$versions.supportCoreUtils", kotlinGradlePlugin : "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlinVersion", kotlinStdLib : "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$versions.kotlinVersion", - v4Support : "androidx.legacy:legacy-support-v4:$versions.v4Support" + androidxFragment : "androidx.fragment:fragment:$versions.fragmentVersion", ] ext.getExportPackageName = { -> diff --git a/platform/android/java/lib/build.gradle b/platform/android/java/lib/build.gradle index 663ba73d40..fbed4ed078 100644 --- a/platform/android/java/lib/build.gradle +++ b/platform/android/java/lib/build.gradle @@ -2,9 +2,8 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' dependencies { - implementation libraries.supportCoreUtils implementation libraries.kotlinStdLib - implementation libraries.v4Support + implementation libraries.androidxFragment } def pathToRootDir = "../../../../" diff --git a/scene/2d/audio_stream_player_2d.cpp b/scene/2d/audio_stream_player_2d.cpp index ea491e8b0e..9280e35c3f 100644 --- a/scene/2d/audio_stream_player_2d.cpp +++ b/scene/2d/audio_stream_player_2d.cpp @@ -59,33 +59,47 @@ void AudioStreamPlayer2D::_notification(int p_what) { if (p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS) { //update anything related to position first, if possible of course + if (setplay.get() > 0 || (active.is_set() && last_mix_count != AudioServer::get_singleton()->get_mix_count())) { + _update_panning(); + } - if (!stream_playback.is_valid()) { - return; + if (setplay.get() >= 0 && stream.is_valid()) { + active.set(); + Ref<AudioStreamPlayback> new_playback = stream->instance_playback(); + ERR_FAIL_COND_MSG(new_playback.is_null(), "Failed to instantiate playback."); + AudioServer::get_singleton()->start_playback_stream(new_playback, _get_actual_bus(), volume_vector, setplay.get()); + stream_playbacks.push_back(new_playback); + setplay.set(-1); } - if (setplay.get() >= 0 || (active.is_set() && last_mix_count != AudioServer::get_singleton()->get_mix_count())) { - _update_panning(); - if (setplay.get() >= 0) { - active.set(); - AudioServer::get_singleton()->start_playback_stream(stream_playback, _get_actual_bus(), volume_vector, setplay.get()); - setplay.set(-1); + + if (!stream_playbacks.is_empty() && active.is_set()) { + // Stop playing if no longer active. + Vector<Ref<AudioStreamPlayback>> playbacks_to_remove; + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + if (playback.is_valid() && !AudioServer::get_singleton()->is_playback_active(playback) && !AudioServer::get_singleton()->is_playback_paused(playback)) { + emit_signal(SNAME("finished")); + playbacks_to_remove.push_back(playback); + } + } + // Now go through and remove playbacks that have finished. Removing elements from a Vector in a range based for is asking for trouble. + for (Ref<AudioStreamPlayback> &playback : playbacks_to_remove) { + stream_playbacks.erase(playback); + } + if (!playbacks_to_remove.is_empty() && stream_playbacks.is_empty()) { + // This node is no longer actively playing audio. + active.clear(); + set_physics_process_internal(false); } } - // Stop playing if no longer active. - if (active.is_set() && !AudioServer::get_singleton()->is_playback_active(stream_playback)) { - active.clear(); - set_physics_process_internal(false); - emit_signal(SNAME("finished")); + while (stream_playbacks.size() > max_polyphony) { + AudioServer::get_singleton()->stop_playback_stream(stream_playbacks[0]); + stream_playbacks.remove(0); } } } StringName AudioStreamPlayer2D::_get_actual_bus() { - if (!stream_playback.is_valid()) { - return SNAME("Master"); - } - Vector2 global_pos = get_global_position(); //check if any area is diverting sound into a bus @@ -113,12 +127,10 @@ StringName AudioStreamPlayer2D::_get_actual_bus() { } void AudioStreamPlayer2D::_update_panning() { - if (!stream_playback.is_valid()) { + if (!active.is_set() || stream.is_null()) { return; } - last_mix_count = AudioServer::get_singleton()->get_mix_count(); - Ref<World2D> world_2d = get_world_2d(); ERR_FAIL_COND(world_2d.is_null()); @@ -164,28 +176,20 @@ void AudioStreamPlayer2D::_update_panning() { volume_vector.write[0] = AudioFrame(l, r) * multiplier; } - AudioServer::get_singleton()->set_playback_bus_exclusive(stream_playback, _get_actual_bus(), volume_vector); -} - -void AudioStreamPlayer2D::set_stream(Ref<AudioStream> p_stream) { - if (stream_playback.is_valid()) { - stop(); + for (const Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_bus_exclusive(playback, _get_actual_bus(), volume_vector); } - stream_playback.unref(); - stream.unref(); - if (p_stream.is_valid()) { - stream_playback = p_stream->instance_playback(); - if (stream_playback.is_valid()) { - stream = p_stream; - } else { - stream.unref(); - } + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_pitch_scale(playback, pitch_scale); } - if (p_stream.is_valid() && stream_playback.is_null()) { - stream.unref(); - } + last_mix_count = AudioServer::get_singleton()->get_mix_count(); +} + +void AudioStreamPlayer2D::set_stream(Ref<AudioStream> p_stream) { + stop(); + stream = p_stream; } Ref<AudioStream> AudioStreamPlayer2D::get_stream() const { @@ -203,8 +207,8 @@ float AudioStreamPlayer2D::get_volume_db() const { void AudioStreamPlayer2D::set_pitch_scale(float p_pitch_scale) { ERR_FAIL_COND(p_pitch_scale <= 0.0); pitch_scale = p_pitch_scale; - if (stream_playback.is_valid()) { - AudioServer::get_singleton()->set_playback_pitch_scale(stream_playback, p_pitch_scale); + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_pitch_scale(playback, p_pitch_scale); } } @@ -213,44 +217,50 @@ float AudioStreamPlayer2D::get_pitch_scale() const { } void AudioStreamPlayer2D::play(float p_from_pos) { - stop(); - if (stream.is_valid()) { - stream_playback = stream->instance_playback(); + if (stream.is_null()) { + return; } - if (stream_playback.is_valid()) { - setplay.set(p_from_pos); - set_physics_process_internal(true); + ERR_FAIL_COND_MSG(!is_inside_tree(), "Playback can only happen when a node is inside the scene tree"); + if (stream->is_monophonic() && is_playing()) { + stop(); } + + setplay.set(p_from_pos); + active.set(); + set_physics_process_internal(true); } void AudioStreamPlayer2D::seek(float p_seconds) { - if (stream_playback.is_valid() && active.is_set()) { + if (is_playing()) { + stop(); play(p_seconds); } } void AudioStreamPlayer2D::stop() { - if (stream_playback.is_valid()) { - active.clear(); - AudioServer::get_singleton()->stop_playback_stream(stream_playback); - set_physics_process_internal(false); - setplay.set(-1); + setplay.set(-1); + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->stop_playback_stream(playback); } + stream_playbacks.clear(); + active.clear(); + set_physics_process_internal(false); } bool AudioStreamPlayer2D::is_playing() const { - if (stream_playback.is_valid()) { - return AudioServer::get_singleton()->is_playback_active(stream_playback); + for (const Ref<AudioStreamPlayback> &playback : stream_playbacks) { + if (AudioServer::get_singleton()->is_playback_active(playback)) { + return true; + } } - return false; } float AudioStreamPlayer2D::get_playback_position() { - if (stream_playback.is_valid()) { - return AudioServer::get_singleton()->get_playback_position(stream_playback); + // Return the playback position of the most recently started playback stream. + if (!stream_playbacks.is_empty()) { + return AudioServer::get_singleton()->get_playback_position(stream_playbacks[stream_playbacks.size() - 1]); } - return 0; } @@ -284,11 +294,7 @@ void AudioStreamPlayer2D::_set_playing(bool p_enable) { } bool AudioStreamPlayer2D::_is_active() const { - if (stream_playback.is_valid()) { - // TODO make sure this doesn't change any behavior w.r.t. pauses. Is a paused stream active? - return AudioServer::get_singleton()->is_playback_active(stream_playback); - } - return false; + return active.is_set(); } void AudioStreamPlayer2D::_validate_property(PropertyInfo &property) const { @@ -336,21 +342,35 @@ uint32_t AudioStreamPlayer2D::get_area_mask() const { } void AudioStreamPlayer2D::set_stream_paused(bool p_pause) { - // TODO this does not have perfect recall, fix that maybe? If the stream isn't set, we can't persist this bool. - if (stream_playback.is_valid()) { - AudioServer::get_singleton()->set_playback_paused(stream_playback, p_pause); + // TODO this does not have perfect recall, fix that maybe? If there are zero playbacks registered with the AudioServer, this bool isn't persisted. + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_paused(playback, p_pause); } } bool AudioStreamPlayer2D::get_stream_paused() const { - if (stream_playback.is_valid()) { - return AudioServer::get_singleton()->is_playback_paused(stream_playback); + // There's currently no way to pause some playback streams but not others. Check the first and don't bother looking at the rest. + if (!stream_playbacks.is_empty()) { + return AudioServer::get_singleton()->is_playback_paused(stream_playbacks[0]); } return false; } Ref<AudioStreamPlayback> AudioStreamPlayer2D::get_stream_playback() { - return stream_playback; + if (!stream_playbacks.is_empty()) { + return stream_playbacks[stream_playbacks.size() - 1]; + } + return nullptr; +} + +void AudioStreamPlayer2D::set_max_polyphony(int p_max_polyphony) { + if (p_max_polyphony > 0) { + max_polyphony = p_max_polyphony; + } +} + +int AudioStreamPlayer2D::get_max_polyphony() const { + return max_polyphony; } void AudioStreamPlayer2D::_bind_methods() { @@ -391,6 +411,9 @@ void AudioStreamPlayer2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_stream_paused", "pause"), &AudioStreamPlayer2D::set_stream_paused); ClassDB::bind_method(D_METHOD("get_stream_paused"), &AudioStreamPlayer2D::get_stream_paused); + ClassDB::bind_method(D_METHOD("set_max_polyphony", "max_polyphony"), &AudioStreamPlayer2D::set_max_polyphony); + ClassDB::bind_method(D_METHOD("get_max_polyphony"), &AudioStreamPlayer2D::get_max_polyphony); + ClassDB::bind_method(D_METHOD("get_stream_playback"), &AudioStreamPlayer2D::get_stream_playback); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream"); @@ -401,6 +424,7 @@ void AudioStreamPlayer2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stream_paused", PROPERTY_HINT_NONE, ""), "set_stream_paused", "get_stream_paused"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_distance", PROPERTY_HINT_RANGE, "1,4096,1,or_greater,exp"), "set_max_distance", "get_max_distance"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "attenuation", PROPERTY_HINT_EXP_EASING, "attenuation"), "set_attenuation", "get_attenuation"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "max_polyphony", PROPERTY_HINT_NONE, ""), "set_max_polyphony", "get_max_polyphony"); ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "bus", PROPERTY_HINT_ENUM, ""), "set_bus", "get_bus"); ADD_PROPERTY(PropertyInfo(Variant::INT, "area_mask", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_area_mask", "get_area_mask"); diff --git a/scene/2d/audio_stream_player_2d.h b/scene/2d/audio_stream_player_2d.h index 6428fbe017..5360fd4934 100644 --- a/scene/2d/audio_stream_player_2d.h +++ b/scene/2d/audio_stream_player_2d.h @@ -51,10 +51,10 @@ private: Viewport *viewport = nullptr; //pointer only used for reference to previous mix }; - Ref<AudioStreamPlayback> stream_playback; + Vector<Ref<AudioStreamPlayback>> stream_playbacks; Ref<AudioStream> stream; - SafeFlag active; + SafeFlag active{ false }; SafeNumeric<float> setplay{ -1.0 }; Vector<AudioFrame> volume_vector; @@ -64,7 +64,8 @@ private: float volume_db = 0.0; float pitch_scale = 1.0; bool autoplay = false; - StringName default_bus = "Master"; + StringName default_bus = SNAME("Master"); + int max_polyphony = 1; void _set_playing(bool p_enable); bool _is_active() const; @@ -119,6 +120,9 @@ public: void set_stream_paused(bool p_pause); bool get_stream_paused() const; + void set_max_polyphony(int p_max_polyphony); + int get_max_polyphony() const; + Ref<AudioStreamPlayback> get_stream_playback(); AudioStreamPlayer2D(); diff --git a/scene/3d/audio_stream_player_3d.cpp b/scene/3d/audio_stream_player_3d.cpp index 16d5b9da3e..907c6cd03a 100644 --- a/scene/3d/audio_stream_player_3d.cpp +++ b/scene/3d/audio_stream_player_3d.cpp @@ -271,28 +271,45 @@ void AudioStreamPlayer3D::_notification(int p_what) { if (p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS) { //update anything related to position first, if possible of course - - if (!stream_playback.is_valid()) { - return; + Vector<AudioFrame> volume_vector; + if (setplay.get() > 0 || (active.is_set() && last_mix_count != AudioServer::get_singleton()->get_mix_count())) { + volume_vector = _update_panning(); } - //start playing if requested - if (setplay.get() >= 0) { - Vector<AudioFrame> volume_vector = _update_panning(); - AudioServer::get_singleton()->start_playback_stream(stream_playback, _get_actual_bus(), volume_vector, setplay.get()); + if (setplay.get() >= 0 && stream.is_valid()) { active.set(); + Ref<AudioStreamPlayback> new_playback = stream->instance_playback(); + ERR_FAIL_COND_MSG(new_playback.is_null(), "Failed to instantiate playback."); + Map<StringName, Vector<AudioFrame>> bus_map; + bus_map[_get_actual_bus()] = volume_vector; + AudioServer::get_singleton()->start_playback_stream(new_playback, bus_map, setplay.get(), linear_attenuation, attenuation_filter_cutoff_hz, actual_pitch_scale); + stream_playbacks.push_back(new_playback); setplay.set(-1); } - if (active.is_set() && last_mix_count != AudioServer::get_singleton()->get_mix_count()) { - _update_panning(); - last_mix_count = AudioServer::get_singleton()->get_mix_count(); + if (!stream_playbacks.is_empty() && active.is_set()) { + // Stop playing if no longer active. + Vector<Ref<AudioStreamPlayback>> playbacks_to_remove; + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + if (playback.is_valid() && !AudioServer::get_singleton()->is_playback_active(playback) && !AudioServer::get_singleton()->is_playback_paused(playback)) { + emit_signal(SNAME("finished")); + playbacks_to_remove.push_back(playback); + } + } + // Now go through and remove playbacks that have finished. Removing elements from a Vector in a range based for is asking for trouble. + for (Ref<AudioStreamPlayback> &playback : playbacks_to_remove) { + stream_playbacks.erase(playback); + } + if (!playbacks_to_remove.is_empty() && stream_playbacks.is_empty()) { + // This node is no longer actively playing audio. + active.clear(); + set_physics_process_internal(false); + } } - // Stop playing if no longer active. - if (!active.is_set()) { - set_physics_process_internal(false); - emit_signal(SNAME("finished")); + while (stream_playbacks.size() > max_polyphony) { + AudioServer::get_singleton()->stop_playback_stream(stream_playbacks[0]); + stream_playbacks.remove(0); } } } @@ -330,9 +347,6 @@ Area3D *AudioStreamPlayer3D::_get_overriding_area() { } StringName AudioStreamPlayer3D::_get_actual_bus() { - if (!stream_playback.is_valid()) { - return SNAME("Master"); - } Area3D *overriding_area = _get_overriding_area(); if (overriding_area && overriding_area->is_overriding_audio_bus() && !overriding_area->is_using_reverb_bus()) { return overriding_area->get_audio_bus_name(); @@ -347,7 +361,9 @@ Vector<AudioFrame> AudioStreamPlayer3D::_update_panning() { frame = AudioFrame(0, 0); } - ERR_FAIL_COND_V(stream_playback.is_null(), output_volume_vector); + if (!active.is_set() || stream.is_null()) { + return output_volume_vector; + } Vector3 linear_velocity; @@ -422,7 +438,10 @@ Vector<AudioFrame> AudioStreamPlayer3D::_update_panning() { } } - AudioServer::get_singleton()->set_playback_highshelf_params(stream_playback, Math::db2linear(db_att), attenuation_filter_cutoff_hz); + linear_attenuation = Math::db2linear(db_att); + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_highshelf_params(playback, linear_attenuation, attenuation_filter_cutoff_hz); + } //TODO: The lower the second parameter (tightness) the more the sound will "enclose" the listener (more undirected / playing from // speakers not facing the source) - this could be made distance dependent. _calc_output_vol(local_pos.normalized(), 4.0, output_volume_vector); @@ -447,7 +466,10 @@ Vector<AudioFrame> AudioStreamPlayer3D::_update_panning() { } else { bus_volumes[bus] = output_volume_vector; } - AudioServer::get_singleton()->set_playback_bus_volumes_linear(stream_playback, bus_volumes); + + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_bus_volumes_linear(playback, bus_volumes); + } if (doppler_tracking != DOPPLER_TRACKING_DISABLED) { Vector3 listener_velocity; @@ -458,9 +480,7 @@ Vector<AudioFrame> AudioStreamPlayer3D::_update_panning() { Vector3 local_velocity = listener_node->get_global_transform().orthonormalized().basis.xform_inv(linear_velocity - listener_velocity); - if (local_velocity == Vector3()) { - AudioServer::get_singleton()->set_playback_pitch_scale(stream_playback, pitch_scale); - } else { + if (local_velocity != Vector3()) { float approaching = local_pos.normalized().dot(local_velocity.normalized()); float velocity = local_velocity.length(); float speed_of_sound = 343.0; @@ -468,34 +488,23 @@ Vector<AudioFrame> AudioStreamPlayer3D::_update_panning() { float doppler_pitch_scale = pitch_scale * speed_of_sound / (speed_of_sound + velocity * approaching); doppler_pitch_scale = CLAMP(doppler_pitch_scale, (1 / 8.0), 8.0); //avoid crazy stuff - AudioServer::get_singleton()->set_playback_pitch_scale(stream_playback, doppler_pitch_scale); + actual_pitch_scale = doppler_pitch_scale; + } else { + actual_pitch_scale = pitch_scale; } } else { - AudioServer::get_singleton()->set_playback_pitch_scale(stream_playback, pitch_scale); + actual_pitch_scale = pitch_scale; + } + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_pitch_scale(playback, actual_pitch_scale); } } return output_volume_vector; } void AudioStreamPlayer3D::set_stream(Ref<AudioStream> p_stream) { - if (stream_playback.is_valid()) { - stop(); - stream_playback.unref(); - stream.unref(); - } - - if (p_stream.is_valid()) { - stream_playback = p_stream->instance_playback(); - if (stream_playback.is_valid()) { - stream = p_stream; - } else { - stream.unref(); - } - } - - if (p_stream.is_valid() && stream_playback.is_null()) { - stream.unref(); - } + stop(); + stream = p_stream; } Ref<AudioStream> AudioStreamPlayer3D::get_stream() const { @@ -536,40 +545,47 @@ float AudioStreamPlayer3D::get_pitch_scale() const { } void AudioStreamPlayer3D::play(float p_from_pos) { - if (stream_playback.is_valid()) { - setplay.set(p_from_pos); - set_physics_process_internal(true); + if (stream.is_null()) { + return; + } + ERR_FAIL_COND_MSG(!is_inside_tree(), "Playback can only happen when a node is inside the scene tree"); + if (stream->is_monophonic() && is_playing()) { + stop(); } + setplay.set(p_from_pos); + active.set(); + set_physics_process_internal(true); } void AudioStreamPlayer3D::seek(float p_seconds) { - if (stream_playback.is_valid() && active.is_set()) { - play(p_seconds); - } + stop(); + play(p_seconds); } void AudioStreamPlayer3D::stop() { - if (stream_playback.is_valid()) { - active.clear(); - AudioServer::get_singleton()->stop_playback_stream(stream_playback); - set_physics_process_internal(false); - setplay.set(-1); + setplay.set(-1); + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->stop_playback_stream(playback); } + stream_playbacks.clear(); + active.clear(); + set_physics_process_internal(false); } bool AudioStreamPlayer3D::is_playing() const { - if (stream_playback.is_valid()) { - return active.is_set() || setplay.get() >= 0; + for (const Ref<AudioStreamPlayback> &playback : stream_playbacks) { + if (AudioServer::get_singleton()->is_playback_active(playback)) { + return true; + } } - return false; } float AudioStreamPlayer3D::get_playback_position() { - if (stream_playback.is_valid()) { - return AudioServer::get_singleton()->get_playback_position(stream_playback); + // Return the playback position of the most recently started playback stream. + if (!stream_playbacks.is_empty()) { + return AudioServer::get_singleton()->get_playback_position(stream_playbacks[stream_playbacks.size() - 1]); } - return 0; } @@ -729,20 +745,35 @@ AudioStreamPlayer3D::DopplerTracking AudioStreamPlayer3D::get_doppler_tracking() } void AudioStreamPlayer3D::set_stream_paused(bool p_pause) { - if (stream_playback.is_valid()) { - AudioServer::get_singleton()->set_playback_paused(stream_playback, p_pause); + // TODO this does not have perfect recall, fix that maybe? If there are zero playbacks registered with the AudioServer, this bool isn't persisted. + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_paused(playback, p_pause); } } bool AudioStreamPlayer3D::get_stream_paused() const { - if (stream_playback.is_valid()) { - return AudioServer::get_singleton()->is_playback_paused(stream_playback); + // There's currently no way to pause some playback streams but not others. Check the first and don't bother looking at the rest. + if (!stream_playbacks.is_empty()) { + return AudioServer::get_singleton()->is_playback_paused(stream_playbacks[0]); } return false; } Ref<AudioStreamPlayback> AudioStreamPlayer3D::get_stream_playback() { - return stream_playback; + if (!stream_playbacks.is_empty()) { + return stream_playbacks[stream_playbacks.size() - 1]; + } + return nullptr; +} + +void AudioStreamPlayer3D::set_max_polyphony(int p_max_polyphony) { + if (p_max_polyphony > 0) { + max_polyphony = p_max_polyphony; + } +} + +int AudioStreamPlayer3D::get_max_polyphony() const { + return max_polyphony; } void AudioStreamPlayer3D::_bind_methods() { @@ -810,6 +841,9 @@ void AudioStreamPlayer3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_stream_paused", "pause"), &AudioStreamPlayer3D::set_stream_paused); ClassDB::bind_method(D_METHOD("get_stream_paused"), &AudioStreamPlayer3D::get_stream_paused); + ClassDB::bind_method(D_METHOD("set_max_polyphony", "max_polyphony"), &AudioStreamPlayer3D::set_max_polyphony); + ClassDB::bind_method(D_METHOD("get_max_polyphony"), &AudioStreamPlayer3D::get_max_polyphony); + ClassDB::bind_method(D_METHOD("get_stream_playback"), &AudioStreamPlayer3D::get_stream_playback); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream"); @@ -823,6 +857,7 @@ void AudioStreamPlayer3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stream_paused", PROPERTY_HINT_NONE, ""), "set_stream_paused", "get_stream_paused"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_distance", PROPERTY_HINT_RANGE, "0,4096,1,or_greater,exp"), "set_max_distance", "get_max_distance"); ADD_PROPERTY(PropertyInfo(Variant::INT, "out_of_range_mode", PROPERTY_HINT_ENUM, "Mix,Pause"), "set_out_of_range_mode", "get_out_of_range_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "max_polyphony", PROPERTY_HINT_NONE, ""), "set_max_polyphony", "get_max_polyphony"); ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "bus", PROPERTY_HINT_ENUM, ""), "set_bus", "get_bus"); ADD_PROPERTY(PropertyInfo(Variant::INT, "area_mask", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_area_mask", "get_area_mask"); ADD_GROUP("Emission Angle", "emission_angle"); diff --git a/scene/3d/audio_stream_player_3d.h b/scene/3d/audio_stream_player_3d.h index f915abad2b..abd5a841b2 100644 --- a/scene/3d/audio_stream_player_3d.h +++ b/scene/3d/audio_stream_player_3d.h @@ -31,6 +31,7 @@ #ifndef AUDIO_STREAM_PLAYER_3D_H #define AUDIO_STREAM_PLAYER_3D_H +#include "core/os/mutex.h" #include "scene/3d/area_3d.h" #include "scene/3d/node_3d.h" #include "scene/3d/velocity_tracker_3d.h" @@ -68,10 +69,10 @@ private: }; - Ref<AudioStreamPlayback> stream_playback; + Vector<Ref<AudioStreamPlayback>> stream_playbacks; Ref<AudioStream> stream; - SafeFlag active; + SafeFlag active{ false }; SafeNumeric<float> setplay{ -1.0 }; AttenuationModel attenuation_model = ATTENUATION_INVERSE_DISTANCE; @@ -79,8 +80,11 @@ private: float unit_size = 10.0; float max_db = 3.0; float pitch_scale = 1.0; + // Internally used to take doppler tracking into account. + float actual_pitch_scale = 1.0; bool autoplay = false; - StringName bus = "Master"; + StringName bus = SNAME("Master"); + int max_polyphony = 1; uint64_t last_mix_count = -1; @@ -106,6 +110,8 @@ private: float attenuation_filter_cutoff_hz = 5000.0; float attenuation_filter_db = -24.0; + float linear_attenuation = 0; + float max_distance = 0.0; Ref<VelocityTracker3D> velocity_tracker; @@ -146,6 +152,9 @@ public: void set_bus(const StringName &p_bus); StringName get_bus() const; + void set_max_polyphony(int p_max_polyphony); + int get_max_polyphony() const; + void set_autoplay(bool p_enable); bool is_autoplay_enabled(); diff --git a/scene/audio/audio_stream_player.cpp b/scene/audio/audio_stream_player.cpp index f7b7604fd5..0334ba667b 100644 --- a/scene/audio/audio_stream_player.cpp +++ b/scene/audio/audio_stream_player.cpp @@ -42,17 +42,29 @@ void AudioStreamPlayer::_notification(int p_what) { } if (p_what == NOTIFICATION_INTERNAL_PROCESS) { - if (stream_playback.is_valid() && active.is_set() && !AudioServer::get_singleton()->is_playback_active(stream_playback)) { + Vector<Ref<AudioStreamPlayback>> playbacks_to_remove; + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + if (playback.is_valid() && !AudioServer::get_singleton()->is_playback_active(playback) && !AudioServer::get_singleton()->is_playback_paused(playback)) { + emit_signal(SNAME("finished")); + playbacks_to_remove.push_back(playback); + } + } + // Now go through and remove playbacks that have finished. Removing elements from a Vector in a range based for is asking for trouble. + for (Ref<AudioStreamPlayback> &playback : playbacks_to_remove) { + stream_playbacks.erase(playback); + } + if (!playbacks_to_remove.is_empty() && stream_playbacks.is_empty()) { + // This node is no longer actively playing audio. active.clear(); set_process_internal(false); - emit_signal(SNAME("finished")); } } if (p_what == NOTIFICATION_EXIT_TREE) { - if (stream_playback.is_valid()) { - AudioServer::get_singleton()->stop_playback_stream(stream_playback); + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->stop_playback_stream(playback); } + stream_playbacks.clear(); } if (p_what == NOTIFICATION_PAUSED) { @@ -68,24 +80,8 @@ void AudioStreamPlayer::_notification(int p_what) { } void AudioStreamPlayer::set_stream(Ref<AudioStream> p_stream) { - if (stream_playback.is_valid()) { - stop(); - stream_playback.unref(); - stream.unref(); - } - - if (p_stream.is_valid()) { - stream_playback = p_stream->instance_playback(); - if (stream_playback.is_valid()) { - stream = p_stream; - } else { - stream.unref(); - } - } - - if (p_stream.is_valid() && stream_playback.is_null()) { - stream.unref(); - } + stop(); + stream = p_stream; } Ref<AudioStream> AudioStreamPlayer::get_stream() const { @@ -95,7 +91,10 @@ Ref<AudioStream> AudioStreamPlayer::get_stream() const { void AudioStreamPlayer::set_volume_db(float p_volume) { volume_db = p_volume; - AudioServer::get_singleton()->set_playback_all_bus_volumes_linear(stream_playback, _get_volume_vector()); + Vector<AudioFrame> volume_vector = _get_volume_vector(); + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_all_bus_volumes_linear(playback, volume_vector); + } } float AudioStreamPlayer::get_volume_db() const { @@ -106,57 +105,83 @@ void AudioStreamPlayer::set_pitch_scale(float p_pitch_scale) { ERR_FAIL_COND(p_pitch_scale <= 0.0); pitch_scale = p_pitch_scale; - AudioServer::get_singleton()->set_playback_pitch_scale(stream_playback, pitch_scale); + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_pitch_scale(playback, pitch_scale); + } } float AudioStreamPlayer::get_pitch_scale() const { return pitch_scale; } +void AudioStreamPlayer::set_max_polyphony(int p_max_polyphony) { + if (p_max_polyphony > 0) { + max_polyphony = p_max_polyphony; + } +} + +int AudioStreamPlayer::get_max_polyphony() const { + return max_polyphony; +} + void AudioStreamPlayer::play(float p_from_pos) { - stop(); - if (stream.is_valid()) { - stream_playback = stream->instance_playback(); + if (stream.is_null()) { + return; + } + ERR_FAIL_COND_MSG(!is_inside_tree(), "Playback can only happen when a node is inside the scene tree"); + if (stream->is_monophonic() && is_playing()) { + stop(); } - if (stream_playback.is_valid()) { - AudioServer::get_singleton()->start_playback_stream(stream_playback, bus, _get_volume_vector(), p_from_pos); - active.set(); + Ref<AudioStreamPlayback> stream_playback = stream->instance_playback(); + ERR_FAIL_COND_MSG(stream_playback.is_null(), "Failed to instantiate playback."); + + AudioServer::get_singleton()->start_playback_stream(stream_playback, bus, _get_volume_vector(), p_from_pos); + stream_playbacks.push_back(stream_playback); + active.set(); + set_process_internal(true); + while (stream_playbacks.size() > max_polyphony) { + AudioServer::get_singleton()->stop_playback_stream(stream_playbacks[0]); + stream_playbacks.remove(0); } } void AudioStreamPlayer::seek(float p_seconds) { - if (stream_playback.is_valid() && active.is_set()) { + if (is_playing()) { + stop(); play(p_seconds); } } void AudioStreamPlayer::stop() { - if (stream_playback.is_valid()) { - active.clear(); - AudioServer::get_singleton()->stop_playback_stream(stream_playback); + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->stop_playback_stream(playback); } + stream_playbacks.clear(); + active.clear(); + set_process_internal(false); } bool AudioStreamPlayer::is_playing() const { - if (stream_playback.is_valid()) { - return AudioServer::get_singleton()->is_playback_active(stream_playback); + for (const Ref<AudioStreamPlayback> &playback : stream_playbacks) { + if (AudioServer::get_singleton()->is_playback_active(playback)) { + return true; + } } - return false; } float AudioStreamPlayer::get_playback_position() { - if (stream_playback.is_valid()) { - return AudioServer::get_singleton()->get_playback_position(stream_playback); + // Return the playback position of the most recently started playback stream. + if (!stream_playbacks.is_empty()) { + return AudioServer::get_singleton()->get_playback_position(stream_playbacks[stream_playbacks.size() - 1]); } - return 0; } void AudioStreamPlayer::set_bus(const StringName &p_bus) { bus = p_bus; - if (stream_playback.is_valid()) { - AudioServer::get_singleton()->set_playback_bus_exclusive(stream_playback, p_bus, _get_volume_vector()); + for (const Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_bus_exclusive(playback, p_bus, _get_volume_vector()); } } @@ -166,7 +191,7 @@ StringName AudioStreamPlayer::get_bus() const { return bus; } } - return "Master"; + return SNAME("Master"); } void AudioStreamPlayer::set_autoplay(bool p_enable) { @@ -194,22 +219,25 @@ void AudioStreamPlayer::_set_playing(bool p_enable) { } bool AudioStreamPlayer::_is_active() const { - if (stream_playback.is_valid()) { - return AudioServer::get_singleton()->is_playback_active(stream_playback); + for (const Ref<AudioStreamPlayback> &playback : stream_playbacks) { + if (AudioServer::get_singleton()->is_playback_active(playback)) { + return true; + } } return false; } void AudioStreamPlayer::set_stream_paused(bool p_pause) { - // TODO this does not have perfect recall, fix that maybe? If the stream isn't set, we can't persist this bool. - if (stream_playback.is_valid()) { - AudioServer::get_singleton()->set_playback_paused(stream_playback, p_pause); + // TODO this does not have perfect recall, fix that maybe? If there are zero playbacks registered with the AudioServer, this bool isn't persisted. + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_paused(playback, p_pause); } } bool AudioStreamPlayer::get_stream_paused() const { - if (stream_playback.is_valid()) { - return AudioServer::get_singleton()->is_playback_paused(stream_playback); + // There's currently no way to pause some playback streams but not others. Check the first and don't bother looking at the rest. + if (!stream_playbacks.is_empty()) { + return AudioServer::get_singleton()->is_playback_paused(stream_playbacks[0]); } return false; } @@ -271,7 +299,10 @@ void AudioStreamPlayer::_bus_layout_changed() { } Ref<AudioStreamPlayback> AudioStreamPlayer::get_stream_playback() { - return stream_playback; + if (!stream_playbacks.is_empty()) { + return stream_playbacks[stream_playbacks.size() - 1]; + } + return nullptr; } void AudioStreamPlayer::_bind_methods() { @@ -306,6 +337,9 @@ void AudioStreamPlayer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_stream_paused", "pause"), &AudioStreamPlayer::set_stream_paused); ClassDB::bind_method(D_METHOD("get_stream_paused"), &AudioStreamPlayer::get_stream_paused); + ClassDB::bind_method(D_METHOD("set_max_polyphony", "max_polyphony"), &AudioStreamPlayer::set_max_polyphony); + ClassDB::bind_method(D_METHOD("get_max_polyphony"), &AudioStreamPlayer::get_max_polyphony); + ClassDB::bind_method(D_METHOD("get_stream_playback"), &AudioStreamPlayer::get_stream_playback); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream"); @@ -315,6 +349,7 @@ void AudioStreamPlayer::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autoplay"), "set_autoplay", "is_autoplay_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stream_paused", PROPERTY_HINT_NONE, ""), "set_stream_paused", "get_stream_paused"); ADD_PROPERTY(PropertyInfo(Variant::INT, "mix_target", PROPERTY_HINT_ENUM, "Stereo,Surround,Center"), "set_mix_target", "get_mix_target"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "max_polyphony", PROPERTY_HINT_NONE, ""), "set_max_polyphony", "get_max_polyphony"); ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "bus", PROPERTY_HINT_ENUM, ""), "set_bus", "get_bus"); ADD_SIGNAL(MethodInfo("finished")); diff --git a/scene/audio/audio_stream_player.h b/scene/audio/audio_stream_player.h index 76b6698c9d..7205fce204 100644 --- a/scene/audio/audio_stream_player.h +++ b/scene/audio/audio_stream_player.h @@ -46,7 +46,7 @@ public: }; private: - Ref<AudioStreamPlayback> stream_playback; + Vector<Ref<AudioStreamPlayback>> stream_playbacks; Ref<AudioStream> stream; SafeFlag active; @@ -54,7 +54,8 @@ private: float pitch_scale = 1.0; float volume_db = 0.0; bool autoplay = false; - StringName bus = "Master"; + StringName bus = SNAME("Master"); + int max_polyphony = 1; MixTarget mix_target = MIX_TARGET_STEREO; @@ -85,6 +86,9 @@ public: void set_pitch_scale(float p_pitch_scale); float get_pitch_scale() const; + void set_max_polyphony(int p_max_polyphony); + int get_max_polyphony() const; + void play(float p_from_pos = 0.0); void seek(float p_seconds); void stop(); diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index b9e9a4d450..f64c07df76 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -37,6 +37,7 @@ #include "core/object/script_language.h" #include "core/os/keyboard.h" #include "core/os/os.h" +#include "core/string/string_builder.h" #include "core/string/translation.h" #include "scene/main/window.h" @@ -78,7 +79,11 @@ void TextEdit::Text::set_font_size(int p_font_size) { } void TextEdit::Text::set_tab_size(int p_tab_size) { + if (tab_size == p_tab_size) { + return; + } tab_size = p_tab_size; + tab_size_dirty = true; } int TextEdit::Text::get_tab_size() const { @@ -118,10 +123,8 @@ int TextEdit::Text::get_line_width(int p_line, int p_wrap_index) const { return text[p_line].data_buf->get_size().x; } -int TextEdit::Text::get_line_height(int p_line, int p_wrap_index) const { - ERR_FAIL_INDEX_V(p_line, text.size(), 0); - - return text[p_line].data_buf->get_line_size(p_wrap_index).y; +int TextEdit::Text::get_line_height() const { + return line_height; } void TextEdit::Text::set_width(float p_width) { @@ -153,6 +156,36 @@ _FORCE_INLINE_ const String &TextEdit::Text::operator[](int p_line) const { return text[p_line].data; } +void TextEdit::Text::_calculate_line_height() { + int height = 0; + for (int i = 0; i < text.size(); i++) { + // Found another line with the same height...nothing to update. + if (text[i].height == line_height) { + height = line_height; + break; + } + height = MAX(height, text[i].height); + } + line_height = height; +} + +void TextEdit::Text::_calculate_max_line_width() { + int width = 0; + for (int i = 0; i < text.size(); i++) { + if (is_hidden(i)) { + continue; + } + + // Found another line with the same width...nothing to update. + if (text[i].width == max_width) { + width = max_width; + break; + } + width = MAX(width, text[i].width); + } + max_width = width; +} + void TextEdit::Text::invalidate_cache(int p_line, int p_column, const String &p_ime_text, const Vector<Vector2i> &p_bidi_override) { ERR_FAIL_INDEX(p_line, text.size()); @@ -182,17 +215,54 @@ void TextEdit::Text::invalidate_cache(int p_line, int p_column, const String &p_ tabs.push_back(font->get_char_size(' ', 0, font_size).width * tab_size); text.write[p_line].data_buf->tab_align(tabs); } + + // Update height. + const int old_height = text.write[p_line].height; + const int wrap_amount = get_line_wrap_amount(p_line); + int height = font->get_height(font_size); + for (int i = 0; i <= wrap_amount; i++) { + height = MAX(height, text[p_line].data_buf->get_line_size(i).y); + } + text.write[p_line].height = height; + + // If this line has shrunk, this may no longer the the tallest line. + if (old_height == line_height && height < line_height) { + _calculate_line_height(); + } else { + line_height = MAX(height, line_height); + } + + // Update width. + const int old_width = text.write[p_line].width; + int width = get_line_width(p_line); + text.write[p_line].width = width; + + // If this line has shrunk, this may no longer the the longest line. + if (old_width == max_width && width < max_width) { + _calculate_max_line_width(); + } else if (!is_hidden(p_line)) { + max_width = MAX(width, max_width); + } } void TextEdit::Text::invalidate_all_lines() { for (int i = 0; i < text.size(); i++) { text.write[i].data_buf->set_width(width); - if (tab_size > 0) { - Vector<float> tabs; - tabs.push_back(font->get_char_size(' ', 0, font_size).width * tab_size); - text.write[i].data_buf->tab_align(tabs); + if (tab_size_dirty) { + if (tab_size > 0) { + Vector<float> tabs; + tabs.push_back(font->get_char_size(' ', 0, font_size).width * tab_size); + text.write[i].data_buf->tab_align(tabs); + } + // Tabs have changes, force width update. + text.write[i].width = get_line_width(i); } } + + if (tab_size_dirty) { + _calculate_max_line_width(); + tab_size_dirty = false; + } } void TextEdit::Text::invalidate_all() { @@ -211,16 +281,8 @@ void TextEdit::Text::clear() { insert(0, "", Vector<Vector2i>()); } -int TextEdit::Text::get_max_width(bool p_exclude_hidden) const { - // Quite some work, but should be fast enough. - - int max = 0; - for (int i = 0; i < text.size(); i++) { - if (!p_exclude_hidden || !is_hidden(i)) { - max = MAX(max, get_line_width(i)); - } - } - return max; +int TextEdit::Text::get_max_width() const { + return max_width; } void TextEdit::Text::set(int p_line, const String &p_text, const Vector<Vector2i> &p_bidi_override) { @@ -243,7 +305,20 @@ void TextEdit::Text::insert(int p_at, const String &p_text, const Vector<Vector2 } void TextEdit::Text::remove(int p_at) { + int height = text[p_at].height; + int width = text[p_at].width; + text.remove(p_at); + + // If this is the tallest line, we need to get the next tallest. + if (height == line_height) { + _calculate_line_height(); + } + + // If this is the longest line, we need to get the next longest. + if (width == max_width) { + _calculate_max_line_width(); + } } void TextEdit::Text::add_gutter(int p_at) { @@ -293,7 +368,7 @@ void TextEdit::_notification(int p_what) { case NOTIFICATION_VISIBILITY_CHANGED: { if (is_visible()) { call_deferred(SNAME("_update_scrollbars")); - call_deferred(SNAME("_update_wrap_at")); + call_deferred(SNAME("_update_wrap_at_column")); } } break; case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: @@ -644,8 +719,9 @@ void TextEdit::_notification(int p_what) { int characters = 0; int tabs = 0; for (int j = 0; j < str.length(); j++) { - if (color_map.has(last_wrap_column + j)) { - current_color = color_map[last_wrap_column + j].get("color"); + const Variant *color_data = color_map.getptr(last_wrap_column + j); + if (color_data != nullptr) { + current_color = (color_data->operator Dictionary()).get("color", font_color); if (!editable) { current_color.a = font_readonly_color.a; } @@ -1023,8 +1099,9 @@ void TextEdit::_notification(int p_what) { char_ofs = 0; } for (int j = 0; j < gl_size; j++) { - if (color_map.has(glyphs[j].start)) { - current_color = color_map[glyphs[j].start].get("color"); + const Variant *color_data = color_map.getptr(glyphs[j].start); + if (color_data != nullptr) { + current_color = (color_data->operator Dictionary()).get("color", font_color); if (!editable && current_color.a > font_readonly_color.a) { current_color.a = font_readonly_color.a; } @@ -2548,15 +2625,15 @@ void TextEdit::set_text(const String &p_text) { } String TextEdit::get_text() const { - String longthing; - int len = text.size(); - for (int i = 0; i < len; i++) { - longthing += text[i]; - if (i != len - 1) { - longthing += "\n"; + StringBuilder ret_text; + const int text_size = text.size(); + for (int i = 0; i < text_size; i++) { + ret_text += text[i]; + if (i != text_size - 1) { + ret_text += "\n"; } } - return longthing; + return ret_text.as_string(); } int TextEdit::get_line_count() const { @@ -2592,13 +2669,7 @@ int TextEdit::get_line_width(int p_line, int p_wrap_index) const { } int TextEdit::get_line_height() const { - int height = font->get_height(font_size); - for (int i = 0; i < text.size(); i++) { - for (int j = 0; j <= text.get_line_wrap_amount(i); j++) { - height = MAX(height, text.get_line_height(i, j)); - } - } - return height + line_spacing; + return text.get_line_height() + line_spacing; } int TextEdit::get_indent_level(int p_line) const { @@ -4151,6 +4222,9 @@ TextEdit::GutterType TextEdit::get_gutter_type(int p_gutter) const { void TextEdit::set_gutter_width(int p_gutter, int p_width) { ERR_FAIL_INDEX(p_gutter, gutters.size()); + if (gutters[p_gutter].width == p_width) { + return; + } gutters.write[p_gutter].width = p_width; _update_gutter_width(); } @@ -4166,6 +4240,9 @@ int TextEdit::get_total_gutter_width() const { void TextEdit::set_gutter_draw(int p_gutter, bool p_draw) { ERR_FAIL_INDEX(p_gutter, gutters.size()); + if (gutters[p_gutter].draw == p_draw) { + return; + } gutters.write[p_gutter].draw = p_draw; _update_gutter_width(); } @@ -5458,7 +5535,7 @@ void TextEdit::_update_scrollbars() { } int visible_width = size.width - style_normal->get_minimum_size().width; - int total_width = text.get_max_width(true) + vmin.x + gutters_width + gutter_padding; + int total_width = text.get_max_width() + vmin.x + gutters_width + gutter_padding; if (draw_minimap) { total_width += minimap_width; @@ -5910,8 +5987,6 @@ void TextEdit::_base_insert_text(int p_line, int p_char, const String &p_text, i text.set_hidden(p_line, false); } - text.invalidate_cache(p_line); - r_end_line = p_line + substrings.size() - 1; r_end_column = text[r_end_line].length() - postinsert_text.length(); @@ -5968,8 +6043,6 @@ void TextEdit::_base_remove_text(int p_from_line, int p_from_column, int p_to_li } text.set(p_from_line, pre_text + post_text, structured_text_parser(st_parser, st_args, pre_text + post_text)); - text.invalidate_cache(p_from_line); - if (!text_changed_dirty && !setting_text) { if (is_inside_tree()) { MessageQueue::get_singleton()->push_call(this, "_text_changed_emit"); @@ -5986,7 +6059,6 @@ TextEdit::TextEdit() { set_default_cursor_shape(CURSOR_IBEAM); text.set_tab_size(text.get_tab_size()); - text.clear(); h_scroll = memnew(HScrollBar); v_scroll = memnew(VScrollBar); diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index 07999c2442..e996bba983 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -144,6 +144,8 @@ private: Color background_color = Color(0, 0, 0, 0); bool hidden = false; + int height = 0; + int width = 0; Line() { data_buf.instantiate(); @@ -152,6 +154,7 @@ private: private: bool is_dirty = false; + bool tab_size_dirty = false; mutable Vector<Line> text; Ref<Font> font; @@ -162,11 +165,16 @@ private: TextServer::Direction direction = TextServer::DIRECTION_AUTO; bool draw_control_chars = false; + int line_height = -1; + int max_width = -1; int width = -1; int tab_size = 4; int gutter_count = 0; + void _calculate_line_height(); + void _calculate_max_line_width(); + public: void set_tab_size(int p_tab_size); int get_tab_size() const; @@ -176,9 +184,9 @@ private: void set_direction_and_language(TextServer::Direction p_direction, const String &p_language); void set_draw_control_chars(bool p_draw_control_chars); - int get_line_height(int p_line, int p_wrap_index) const; + int get_line_height() const; int get_line_width(int p_line, int p_wrap_index = -1) const; - int get_max_width(bool p_exclude_hidden = false) const; + int get_max_width() const; void set_width(float p_width); int get_line_wrap_amount(int p_line) const; @@ -187,7 +195,14 @@ private: const Ref<TextParagraph> get_line_data(int p_line) const; void set(int p_line, const String &p_text, const Vector<Vector2i> &p_bidi_override); - void set_hidden(int p_line, bool p_hidden) { text.write[p_line].hidden = p_hidden; } + void set_hidden(int p_line, bool p_hidden) { + text.write[p_line].hidden = p_hidden; + if (!p_hidden && text[p_line].width > max_width) { + max_width = text[p_line].width; + } else if (p_hidden && text[p_line].width == max_width) { + _calculate_max_line_width(); + } + } bool is_hidden(int p_line) const { return text[p_line].hidden; } void insert(int p_at, const String &p_text, const Vector<Vector2i> &p_bidi_override); void remove(int p_at); diff --git a/scene/resources/audio_stream_sample.cpp b/scene/resources/audio_stream_sample.cpp index 2ab9b7b5a4..d018103e64 100644 --- a/scene/resources/audio_stream_sample.cpp +++ b/scene/resources/audio_stream_sample.cpp @@ -480,6 +480,10 @@ float AudioStreamSample::get_length() const { return float(len) / mix_rate; } +bool AudioStreamSample::is_monophonic() const { + return false; +} + void AudioStreamSample::set_data(const Vector<uint8_t> &p_data) { AudioServer::get_singleton()->lock(); if (data) { diff --git a/scene/resources/audio_stream_sample.h b/scene/resources/audio_stream_sample.h index 8bf3d29123..24198e3c98 100644 --- a/scene/resources/audio_stream_sample.h +++ b/scene/resources/audio_stream_sample.h @@ -136,6 +136,8 @@ public: virtual float get_length() const override; //if supported, otherwise return 0 + virtual bool is_monophonic() const override; + void set_data(const Vector<uint8_t> &p_data); Vector<uint8_t> get_data() const; diff --git a/servers/audio/audio_stream.cpp b/servers/audio/audio_stream.cpp index e1b391b823..c098a97906 100644 --- a/servers/audio/audio_stream.cpp +++ b/servers/audio/audio_stream.cpp @@ -189,11 +189,21 @@ float AudioStream::get_length() const { return 0; } +bool AudioStream::is_monophonic() const { + bool ret; + if (GDVIRTUAL_CALL(_is_monophonic, ret)) { + return ret; + } + return true; +} + void AudioStream::_bind_methods() { ClassDB::bind_method(D_METHOD("get_length"), &AudioStream::get_length); + ClassDB::bind_method(D_METHOD("is_monophonic"), &AudioStream::is_monophonic); GDVIRTUAL_BIND(_instance_playback); GDVIRTUAL_BIND(_get_stream_name); GDVIRTUAL_BIND(_get_length); + GDVIRTUAL_BIND(_is_monophonic); } //////////////////////////////// @@ -221,6 +231,10 @@ float AudioStreamMicrophone::get_length() const { return 0; } +bool AudioStreamMicrophone::is_monophonic() const { + return true; +} + void AudioStreamMicrophone::_bind_methods() { } @@ -388,6 +402,14 @@ float AudioStreamRandomPitch::get_length() const { return 0; } +bool AudioStreamRandomPitch::is_monophonic() const { + if (audio_stream.is_valid()) { + return audio_stream->is_monophonic(); + } + + return true; // It doesn't really matter what we return here, but no sense instancing a many playbacks of a null stream. +} + 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); diff --git a/servers/audio/audio_stream.h b/servers/audio/audio_stream.h index 922335508e..12d4343f5c 100644 --- a/servers/audio/audio_stream.h +++ b/servers/audio/audio_stream.h @@ -102,12 +102,14 @@ protected: GDVIRTUAL0RC(Ref<AudioStreamPlayback>, _instance_playback) GDVIRTUAL0RC(String, _get_stream_name) GDVIRTUAL0RC(float, _get_length) + GDVIRTUAL0RC(bool, _is_monophonic) public: virtual Ref<AudioStreamPlayback> instance_playback(); virtual String get_stream_name() const; virtual float get_length() const; + virtual bool is_monophonic() const; }; // Microphone @@ -129,6 +131,8 @@ public: virtual float get_length() const override; //if supported, otherwise return 0 + virtual bool is_monophonic() const override; + AudioStreamMicrophone(); }; @@ -187,6 +191,7 @@ public: 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(); }; diff --git a/servers/audio/effects/audio_stream_generator.cpp b/servers/audio/effects/audio_stream_generator.cpp index edb5c6d2dd..447acf53a4 100644 --- a/servers/audio/effects/audio_stream_generator.cpp +++ b/servers/audio/effects/audio_stream_generator.cpp @@ -64,6 +64,10 @@ float AudioStreamGenerator::get_length() const { return 0; } +bool AudioStreamGenerator::is_monophonic() const { + return true; +} + void AudioStreamGenerator::_bind_methods() { ClassDB::bind_method(D_METHOD("set_mix_rate", "hz"), &AudioStreamGenerator::set_mix_rate); ClassDB::bind_method(D_METHOD("get_mix_rate"), &AudioStreamGenerator::get_mix_rate); diff --git a/servers/audio/effects/audio_stream_generator.h b/servers/audio/effects/audio_stream_generator.h index 6bec744081..918589f6d0 100644 --- a/servers/audio/effects/audio_stream_generator.h +++ b/servers/audio/effects/audio_stream_generator.h @@ -54,6 +54,7 @@ public: virtual String get_stream_name() const override; virtual float get_length() const override; + virtual bool is_monophonic() const override; AudioStreamGenerator(); }; diff --git a/servers/audio_server.cpp b/servers/audio_server.cpp index 81735d522f..758ce766c3 100644 --- a/servers/audio_server.cpp +++ b/servers/audio_server.cpp @@ -363,10 +363,10 @@ void AudioServer::_mix_step() { if (mixed_frames != buffer_size) { // We know we have at least the size of our lookahead buffer for fade-out purposes. - float fadeout_base = 0.87; + float fadeout_base = 0.94; float fadeout_coefficient = 1; - static_assert(LOOKAHEAD_BUFFER_SIZE == 32, "Update fadeout_base and comment here if you change LOOKAHEAD_BUFFER_SIZE."); - // 0.87 ^ 32 = 0.0116. There might still be a pop but it'll be way better than if we didn't do this. + static_assert(LOOKAHEAD_BUFFER_SIZE == 64, "Update fadeout_base and comment here if you change LOOKAHEAD_BUFFER_SIZE."); + // 0.94 ^ 64 = 0.01906. There might still be a pop but it'll be way better than if we didn't do this. for (unsigned int idx = mixed_frames; idx < buffer_size; idx++) { fadeout_coefficient *= fadeout_base; buf[idx] *= fadeout_coefficient; @@ -381,29 +381,24 @@ void AudioServer::_mix_step() { } } - ERR_FAIL_COND(playback->bus_details.load() == nullptr); + AudioStreamPlaybackBusDetails *ptr = playback->bus_details.load(); + ERR_FAIL_COND(ptr == nullptr); // By putting null into the bus details pointers, we're taking ownership of their memory for the duration of this mix. - AudioStreamPlaybackBusDetails *bus_details = nullptr; - { - std::atomic<AudioStreamPlaybackBusDetails *> bus_details_atomic = nullptr; - bus_details = playback->bus_details.exchange(bus_details_atomic); - } - ERR_FAIL_COND(bus_details == nullptr); - AudioStreamPlaybackBusDetails *prev_bus_details = playback->prev_bus_details; + AudioStreamPlaybackBusDetails bus_details = *ptr; // Mix to any active buses. for (int idx = 0; idx < MAX_BUSES_PER_PLAYBACK; idx++) { - if (!bus_details->bus_active[idx]) { + if (!bus_details.bus_active[idx]) { continue; } - int bus_idx = thread_find_bus_index(bus_details->bus[idx]); + int bus_idx = thread_find_bus_index(bus_details.bus[idx]); int prev_bus_idx = -1; for (int search_idx = 0; search_idx < MAX_BUSES_PER_PLAYBACK; search_idx++) { - if (!prev_bus_details->bus_active[search_idx]) { + if (!playback->prev_bus_details->bus_active[search_idx]) { continue; } - if (prev_bus_details->bus[search_idx].hash() == bus_details->bus[idx].hash()) { + if (playback->prev_bus_details->bus[search_idx].hash() == bus_details.bus[idx].hash()) { prev_bus_idx = search_idx; } } @@ -411,13 +406,13 @@ void AudioServer::_mix_step() { for (int channel_idx = 0; channel_idx < channel_count; channel_idx++) { AudioFrame *channel_buf = thread_get_channel_mix_buffer(bus_idx, channel_idx); if (fading_out) { - bus_details->volume[idx][channel_idx] = AudioFrame(0, 0); + bus_details.volume[idx][channel_idx] = AudioFrame(0, 0); } - AudioFrame channel_vol = bus_details->volume[idx][channel_idx]; + AudioFrame channel_vol = bus_details.volume[idx][channel_idx]; AudioFrame prev_channel_vol = AudioFrame(0, 0); if (prev_bus_idx != -1) { - prev_channel_vol = prev_bus_details->volume[prev_bus_idx][channel_idx]; + prev_channel_vol = playback->prev_bus_details->volume[prev_bus_idx][channel_idx]; } _mix_step_for_channel(channel_buf, buf, prev_channel_vol, channel_vol, playback->attenuation_filter_cutoff_hz.get(), playback->highshelf_gain.get(), &playback->filter_process[channel_idx * 2], &playback->filter_process[channel_idx * 2 + 1]); } @@ -425,14 +420,14 @@ void AudioServer::_mix_step() { // Now go through and fade-out any buses that were being played to previously that we missed by going through current data. for (int idx = 0; idx < MAX_BUSES_PER_PLAYBACK; idx++) { - if (!prev_bus_details->bus_active[idx]) { + if (!playback->prev_bus_details->bus_active[idx]) { continue; } - int bus_idx = thread_find_bus_index(prev_bus_details->bus[idx]); + int bus_idx = thread_find_bus_index(playback->prev_bus_details->bus[idx]); int current_bus_idx = -1; for (int search_idx = 0; search_idx < MAX_BUSES_PER_PLAYBACK; search_idx++) { - if (bus_details->bus[search_idx] == prev_bus_details->bus[idx]) { + if (bus_details.bus[search_idx] == playback->prev_bus_details->bus[idx]) { current_bus_idx = search_idx; } } @@ -443,24 +438,17 @@ void AudioServer::_mix_step() { for (int channel_idx = 0; channel_idx < channel_count; channel_idx++) { AudioFrame *channel_buf = thread_get_channel_mix_buffer(bus_idx, channel_idx); - AudioFrame prev_channel_vol = prev_bus_details->volume[idx][channel_idx]; + AudioFrame prev_channel_vol = playback->prev_bus_details->volume[idx][channel_idx]; // Fade out to silence _mix_step_for_channel(channel_buf, buf, prev_channel_vol, AudioFrame(0, 0), playback->attenuation_filter_cutoff_hz.get(), playback->highshelf_gain.get(), &playback->filter_process[channel_idx * 2], &playback->filter_process[channel_idx * 2 + 1]); } } // Copy the bus details we mixed with to the previous bus details to maintain volume ramps. - std::copy(std::begin(bus_details->bus_active), std::end(bus_details->bus_active), std::begin(prev_bus_details->bus_active)); - std::copy(std::begin(bus_details->bus), std::end(bus_details->bus), std::begin(prev_bus_details->bus)); + std::copy(std::begin(bus_details.bus_active), std::end(bus_details.bus_active), std::begin(playback->prev_bus_details->bus_active)); + std::copy(std::begin(bus_details.bus), std::end(bus_details.bus), std::begin(playback->prev_bus_details->bus)); for (int bus_idx = 0; bus_idx < MAX_BUSES_PER_PLAYBACK; bus_idx++) { - std::copy(std::begin(bus_details->volume[bus_idx]), std::end(bus_details->volume[bus_idx]), std::begin(prev_bus_details->volume[bus_idx])); - } - - AudioStreamPlaybackBusDetails *bus_details_expected = nullptr; - // Only put the bus details pointer back if it hasn't been updated already. - if (!playback->bus_details.compare_exchange_strong(/* expected= */ bus_details_expected, /* new= */ bus_details)) { - // If it *has* been updated already, queue the old one for deletion. - bus_details_graveyard.insert(bus_details); + std::copy(std::begin(bus_details.volume[bus_idx]), std::end(bus_details.volume[bus_idx]), std::begin(playback->prev_bus_details->volume[bus_idx])); } switch (playback->state.load()) { @@ -1132,7 +1120,7 @@ void AudioServer::start_playback_stream(Ref<AudioStreamPlayback> p_playback, Str start_playback_stream(p_playback, map, p_start_time); } -void AudioServer::start_playback_stream(Ref<AudioStreamPlayback> p_playback, Map<StringName, Vector<AudioFrame>> p_bus_volumes, float p_start_time) { +void AudioServer::start_playback_stream(Ref<AudioStreamPlayback> p_playback, Map<StringName, Vector<AudioFrame>> p_bus_volumes, float p_start_time, float p_highshelf_gain, float p_attenuation_cutoff_hz, float p_pitch_scale) { ERR_FAIL_COND(p_playback.is_null()); AudioStreamPlaybackListNode *playback_node = new AudioStreamPlaybackListNode(); @@ -1154,10 +1142,9 @@ void AudioServer::start_playback_stream(Ref<AudioStreamPlayback> p_playback, Map playback_node->bus_details = new_bus_details; playback_node->prev_bus_details = new AudioStreamPlaybackBusDetails(); - playback_node->setseek.set(-1); - playback_node->pitch_scale.set(1); - playback_node->highshelf_gain.set(0); - playback_node->attenuation_filter_cutoff_hz.set(0); + playback_node->pitch_scale.set(p_pitch_scale); + playback_node->highshelf_gain.set(p_highshelf_gain); + playback_node->attenuation_filter_cutoff_hz.set(p_attenuation_cutoff_hz); memset(playback_node->prev_bus_details->volume, 0, sizeof(playback_node->prev_bus_details->volume)); @@ -1181,6 +1168,9 @@ void AudioServer::stop_playback_stream(Ref<AudioStreamPlayback> p_playback) { AudioStreamPlaybackListNode::PlaybackState new_state, old_state; do { old_state = playback_node->state.load(); + if (old_state == AudioStreamPlaybackListNode::AWAITING_DELETION) { + break; // Don't fade out again. + } new_state = AudioStreamPlaybackListNode::FADE_OUT_TO_DELETION; } while (!playback_node->state.compare_exchange_strong(old_state, new_state)); @@ -1206,6 +1196,9 @@ void AudioServer::set_playback_bus_volumes_linear(Ref<AudioStreamPlayback> p_pla int idx = 0; for (KeyValue<StringName, Vector<AudioFrame>> pair : p_bus_volumes) { + if (idx >= MAX_BUSES_PER_PLAYBACK) { + break; + } ERR_FAIL_COND(pair.value.size() < channel_count); ERR_FAIL_COND(pair.value.size() != MAX_CHANNELS_PER_BUS); @@ -1214,6 +1207,7 @@ void AudioServer::set_playback_bus_volumes_linear(Ref<AudioStreamPlayback> p_pla for (int channel_idx = 0; channel_idx < MAX_CHANNELS_PER_BUS; channel_idx++) { new_bus_details->volume[idx][channel_idx] = pair.value[channel_idx]; } + idx++; } do { @@ -1260,17 +1254,18 @@ void AudioServer::set_playback_paused(Ref<AudioStreamPlayback> p_playback, bool if (!playback_node) { return; } - if (!p_paused && playback_node->state == AudioStreamPlaybackListNode::PLAYING) { - return; // No-op. - } - if (p_paused && (playback_node->state == AudioStreamPlaybackListNode::PAUSED || playback_node->state == AudioStreamPlaybackListNode::FADE_OUT_TO_PAUSE)) { - return; // No-op. - } AudioStreamPlaybackListNode::PlaybackState new_state, old_state; do { old_state = playback_node->state.load(); new_state = p_paused ? AudioStreamPlaybackListNode::FADE_OUT_TO_PAUSE : AudioStreamPlaybackListNode::PLAYING; + if (!p_paused && old_state == AudioStreamPlaybackListNode::PLAYING) { + return; // No-op. + } + if (p_paused && (old_state == AudioStreamPlaybackListNode::PAUSED || old_state == AudioStreamPlaybackListNode::FADE_OUT_TO_PAUSE)) { + return; // No-op. + } + } while (!playback_node->state.compare_exchange_strong(old_state, new_state)); } @@ -1444,10 +1439,15 @@ void AudioServer::update() { update_callback_list.maybe_cleanup(); listener_changed_callback_list.maybe_cleanup(); playback_list.maybe_cleanup(); + for (AudioStreamPlaybackBusDetails *bus_details : bus_details_graveyard_frame_old) { + bus_details_graveyard_frame_old.erase(bus_details, [](AudioStreamPlaybackBusDetails *d) { delete d; }); + } for (AudioStreamPlaybackBusDetails *bus_details : bus_details_graveyard) { - bus_details_graveyard.erase(bus_details, [](AudioStreamPlaybackBusDetails *d) { delete d; }); + bus_details_graveyard_frame_old.insert(bus_details); + bus_details_graveyard.erase(bus_details); } bus_details_graveyard.maybe_cleanup(); + bus_details_graveyard_frame_old.maybe_cleanup(); } void AudioServer::load_default_bus_layout() { diff --git a/servers/audio_server.h b/servers/audio_server.h index affcb3df7b..a60d4ae4c4 100644 --- a/servers/audio_server.h +++ b/servers/audio_server.h @@ -163,7 +163,7 @@ public: AUDIO_DATA_INVALID_ID = -1, MAX_CHANNELS_PER_BUS = 4, MAX_BUSES_PER_PLAYBACK = 6, - LOOKAHEAD_BUFFER_SIZE = 32, + LOOKAHEAD_BUFFER_SIZE = 64, }; typedef void (*AudioCallback)(void *p_userdata); @@ -262,6 +262,9 @@ private: SafeList<AudioStreamPlaybackListNode *> playback_list; SafeList<AudioStreamPlaybackBusDetails *> bus_details_graveyard; + // TODO document if this is necessary. + SafeList<AudioStreamPlaybackBusDetails *> bus_details_graveyard_frame_old; + Vector<Vector<AudioFrame>> temp_buffer; //temp_buffer for each level Vector<AudioFrame> mix_buffer; Vector<Bus *> buses; @@ -364,8 +367,10 @@ public: void set_playback_speed_scale(float p_scale); float get_playback_speed_scale() const; + // Convenience method. void start_playback_stream(Ref<AudioStreamPlayback> p_playback, StringName p_bus, Vector<AudioFrame> p_volume_db_vector, float p_start_time = 0); - void start_playback_stream(Ref<AudioStreamPlayback> p_playback, Map<StringName, Vector<AudioFrame>> p_bus_volumes, float p_start_time = 0); + // Expose all parameters. + void start_playback_stream(Ref<AudioStreamPlayback> p_playback, Map<StringName, Vector<AudioFrame>> p_bus_volumes, float p_start_time = 0, float p_highshelf_gain = 0, float p_attenuation_cutoff_hz = 0, float p_pitch_scale = 1); void stop_playback_stream(Ref<AudioStreamPlayback> p_playback); void set_playback_bus_exclusive(Ref<AudioStreamPlayback> p_playback, StringName p_bus, Vector<AudioFrame> p_volumes); |