diff --git a/export_presets.cfg b/export_presets.cfg
new file mode 100644
index 0000000..38710e7
--- /dev/null
+++ b/export_presets.cfg
@@ -0,0 +1,64 @@
+[preset.0]
+
+name="Export Extension (PCK)"
+platform="Windows Desktop"
+runnable=true
+dedicated_server=false
+custom_features=""
+export_filter="all_resources"
+include_filter="*.json"
+exclude_filter="res://src/Extensions/Audia/API/*"
+export_path=""
+encryption_include_filters=""
+encryption_exclude_filters=""
+encrypt_pck=false
+encrypt_directory=false
+
+[preset.0.options]
+
+custom_template/debug=""
+custom_template/release=""
+debug/export_console_wrapper=1
+binary_format/embed_pck=false
+texture_format/bptc=false
+texture_format/s3tc=true
+texture_format/etc=false
+texture_format/etc2=false
+binary_format/architecture="x86_64"
+codesign/enable=false
+codesign/timestamp=true
+codesign/timestamp_server_url=""
+codesign/digest_algorithm=1
+codesign/description=""
+codesign/custom_options=PackedStringArray()
+application/modify_resources=true
+application/icon=""
+application/console_wrapper_icon=""
+application/icon_interpolation=4
+application/file_version=""
+application/product_version=""
+application/company_name=""
+application/product_name=""
+application/file_description=""
+application/copyright=""
+application/trademarks=""
+application/export_angle=0
+ssh_remote_deploy/enabled=false
+ssh_remote_deploy/host="user@host_ip"
+ssh_remote_deploy/port="22"
+ssh_remote_deploy/extra_args_ssh=""
+ssh_remote_deploy/extra_args_scp=""
+ssh_remote_deploy/run_script="Expand-Archive -LiteralPath '{temp_dir}\\{archive_name}' -DestinationPath '{temp_dir}'
+$action = New-ScheduledTaskAction -Execute '{temp_dir}\\{exe_name}' -Argument '{cmd_args}'
+$trigger = New-ScheduledTaskTrigger -Once -At 00:00
+$settings = New-ScheduledTaskSettingsSet
+$task = New-ScheduledTask -Action $action -Trigger $trigger -Settings $settings
+Register-ScheduledTask godot_remote_debug -InputObject $task -Force:$true
+Start-ScheduledTask -TaskName godot_remote_debug
+while (Get-ScheduledTask -TaskName godot_remote_debug | ? State -eq running) { Start-Sleep -Milliseconds 100 }
+Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue"
+ssh_remote_deploy/cleanup_script="Stop-ScheduledTask -TaskName godot_remote_debug -ErrorAction:SilentlyContinue
+Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue
+Remove-Item -Recurse -Force '{temp_dir}'"
+binary_format/64_bits=true
+texture_format/no_bptc_fallbacks=true
diff --git a/project.godot b/project.godot
new file mode 100644
index 0000000..7c234b3
--- /dev/null
+++ b/project.godot
@@ -0,0 +1,29 @@
+; Engine configuration file.
+; It's best edited using the editor UI and not directly,
+; since the parameters that go here are not all obvious.
+;
+; Format:
+; [section] ; section goes between []
+; param=value ; assign values to parameters
+
+config_version=5
+
+[application]
+
+config/name="Audia"
+config/description="A pixelorama Extention (The \"Name\" and \"Description\" field are not related to extention system so they can be anything)"
+config/features=PackedStringArray("4.2")
+" field are not related to extention system so they can be anything)run/main_scene"="res://src/Extensions/Audia/Main.tscn"
+
+[autoload]
+
+ExtensionsApi="*res://src/Extensions/Audia/API/ExtensionsApi.gd"
+
+[physics]
+
+common/enable_pause_aware_picking=true
+
+[rendering]
+
+quality/driver/driver_name="GLES2"
+vram_compression/import_etc=true
diff --git a/src/Extensions/Audia/API/EmptyClasses/AnimationTag.gd b/src/Extensions/Audia/API/EmptyClasses/AnimationTag.gd
new file mode 100644
index 0000000..730583a
--- /dev/null
+++ b/src/Extensions/Audia/API/EmptyClasses/AnimationTag.gd
@@ -0,0 +1,7 @@
+class_name AnimationTag
+extends RefCounted
+# A class for frame tag properties
+
+
+func _init(_name, _color, _from, _to) -> void:
+ pass
diff --git a/src/Extensions/Audia/API/EmptyClasses/BaseCel.gd b/src/Extensions/Audia/API/EmptyClasses/BaseCel.gd
new file mode 100644
index 0000000..8b0c6b4
--- /dev/null
+++ b/src/Extensions/Audia/API/EmptyClasses/BaseCel.gd
@@ -0,0 +1,5 @@
+class_name BaseCel
+extends RefCounted
+## Base class for cel properties.
+## The term "cel" comes from "celluloid" (https://en.wikipedia.org/wiki/Cel).
+
diff --git a/src/Extensions/Audia/API/EmptyClasses/BaseLayer.gd b/src/Extensions/Audia/API/EmptyClasses/BaseLayer.gd
new file mode 100644
index 0000000..8fcdea2
--- /dev/null
+++ b/src/Extensions/Audia/API/EmptyClasses/BaseLayer.gd
@@ -0,0 +1,2 @@
+class_name BaseLayer
+extends RefCounted
diff --git a/src/Extensions/Audia/API/EmptyClasses/BaseTool.gd b/src/Extensions/Audia/API/EmptyClasses/BaseTool.gd
new file mode 100644
index 0000000..f1b9840
--- /dev/null
+++ b/src/Extensions/Audia/API/EmptyClasses/BaseTool.gd
@@ -0,0 +1,2 @@
+class_name BaseTool
+extends VBoxContainer
diff --git a/src/Extensions/Audia/API/EmptyClasses/Cel3D.gd b/src/Extensions/Audia/API/EmptyClasses/Cel3D.gd
new file mode 100644
index 0000000..9e2870b
--- /dev/null
+++ b/src/Extensions/Audia/API/EmptyClasses/Cel3D.gd
@@ -0,0 +1,6 @@
+class_name Cel3D
+extends BaseCel
+
+
+func _init(_size: Vector2, from_pxo := false, _object_prop := {}, _scene_prop := {}) -> void:
+ pass
diff --git a/src/Extensions/Audia/API/EmptyClasses/Cel3DObject.gd b/src/Extensions/Audia/API/EmptyClasses/Cel3DObject.gd
new file mode 100644
index 0000000..e18c4cb
--- /dev/null
+++ b/src/Extensions/Audia/API/EmptyClasses/Cel3DObject.gd
@@ -0,0 +1,3 @@
+class_name Cel3DObject
+extends Node3D
+
diff --git a/src/Extensions/Audia/API/EmptyClasses/Drawers.gd b/src/Extensions/Audia/API/EmptyClasses/Drawers.gd
new file mode 100644
index 0000000..3c3b2bd
--- /dev/null
+++ b/src/Extensions/Audia/API/EmptyClasses/Drawers.gd
@@ -0,0 +1 @@
+class_name Drawer
diff --git a/src/Extensions/Audia/API/EmptyClasses/Frame.gd b/src/Extensions/Audia/API/EmptyClasses/Frame.gd
new file mode 100644
index 0000000..45c7252
--- /dev/null
+++ b/src/Extensions/Audia/API/EmptyClasses/Frame.gd
@@ -0,0 +1,8 @@
+class_name Frame
+extends RefCounted
+# A class for frame properties.
+# A frame is a collection of cels, for each layer.
+
+
+func _init(_cels := [], _duration := 1.0) -> void:
+ pass
diff --git a/src/Extensions/Audia/API/EmptyClasses/GIFAnimationExporter.gd b/src/Extensions/Audia/API/EmptyClasses/GIFAnimationExporter.gd
new file mode 100644
index 0000000..69ab483
--- /dev/null
+++ b/src/Extensions/Audia/API/EmptyClasses/GIFAnimationExporter.gd
@@ -0,0 +1,8 @@
+class_name GIFAnimationExporter
+# Acts as the interface between the AImgIO format-independent interface and gdgifexporter.
+# Note that if the interface needs changing for new features, do just change it!
+
+
+func _init():
+ pass
+
diff --git a/src/Extensions/Audia/API/EmptyClasses/GroupCel.gd b/src/Extensions/Audia/API/EmptyClasses/GroupCel.gd
new file mode 100644
index 0000000..129e4e5
--- /dev/null
+++ b/src/Extensions/Audia/API/EmptyClasses/GroupCel.gd
@@ -0,0 +1,8 @@
+class_name GroupCel
+extends BaseCel
+# A class for the properties of cels in GroupLayers.
+# The term "cel" comes from "celluloid" (https://en.wikipedia.org/wiki/Cel).
+
+
+func _init(_opacity := 1.0) -> void:
+ pass
diff --git a/src/Extensions/Audia/API/EmptyClasses/GroupLayer.gd b/src/Extensions/Audia/API/EmptyClasses/GroupLayer.gd
new file mode 100644
index 0000000..be84d97
--- /dev/null
+++ b/src/Extensions/Audia/API/EmptyClasses/GroupLayer.gd
@@ -0,0 +1,9 @@
+class_name GroupLayer
+extends BaseLayer
+# A class for group layer properties
+
+var expanded := true
+
+
+func _init(_project, _name := "") -> void:
+ pass
diff --git a/src/Extensions/Audia/API/EmptyClasses/ImageEffect.gd b/src/Extensions/Audia/API/EmptyClasses/ImageEffect.gd
new file mode 100644
index 0000000..cc7fb42
--- /dev/null
+++ b/src/Extensions/Audia/API/EmptyClasses/ImageEffect.gd
@@ -0,0 +1,4 @@
+class_name ImageEffect
+extends ConfirmationDialog
+## Parent class for all image effects
+## Methods that have "pass" are meant to be replaced by the inherited scripts
diff --git a/src/Extensions/Audia/API/EmptyClasses/Layer3D.gd b/src/Extensions/Audia/API/EmptyClasses/Layer3D.gd
new file mode 100644
index 0000000..209ac8a
--- /dev/null
+++ b/src/Extensions/Audia/API/EmptyClasses/Layer3D.gd
@@ -0,0 +1,6 @@
+class_name Layer3D
+extends BaseLayer
+
+
+func _init(_project, _name := "") -> void:
+ pass
diff --git a/src/Extensions/Audia/API/EmptyClasses/ObjParse.gd b/src/Extensions/Audia/API/EmptyClasses/ObjParse.gd
new file mode 100644
index 0000000..3167903
--- /dev/null
+++ b/src/Extensions/Audia/API/EmptyClasses/ObjParse.gd
@@ -0,0 +1,10 @@
+class_name ObjParse
+
+# Obj parser made by Ezcha, updated by Deakcor
+# Created on 7/11/2018
+# https://ezcha.net
+# https://github.com/Ezcha/gd-obj
+# MIT License
+# https://github.com/Ezcha/gd-obj/blob/master/LICENSE
+
+# Returns an array of materials from a MTL file
diff --git a/src/Extensions/Audia/API/EmptyClasses/PixelCel.gd b/src/Extensions/Audia/API/EmptyClasses/PixelCel.gd
new file mode 100644
index 0000000..e7b21ec
--- /dev/null
+++ b/src/Extensions/Audia/API/EmptyClasses/PixelCel.gd
@@ -0,0 +1,9 @@
+class_name PixelCel
+extends BaseCel
+# A class for the properties of cels in PixelLayers.
+# The term "cel" comes from "celluloid" (https://en.wikipedia.org/wiki/Cel).
+# The "image" variable is where the image data of each cel are.
+
+
+func _init(_image := Image.new(), _opacity := 1.0, _image_texture: ImageTexture = null) -> void:
+ pass
diff --git a/src/Extensions/Audia/API/EmptyClasses/PixelLayer.gd b/src/Extensions/Audia/API/EmptyClasses/PixelLayer.gd
new file mode 100644
index 0000000..9327b2b
--- /dev/null
+++ b/src/Extensions/Audia/API/EmptyClasses/PixelLayer.gd
@@ -0,0 +1,7 @@
+class_name PixelLayer
+extends BaseLayer
+# A class for standard pixel layer properties.
+
+
+func _init(_project, _name := "") -> void:
+ pass
diff --git a/src/Extensions/Audia/API/EmptyClasses/Project.gd b/src/Extensions/Audia/API/EmptyClasses/Project.gd
new file mode 100644
index 0000000..feb640b
--- /dev/null
+++ b/src/Extensions/Audia/API/EmptyClasses/Project.gd
@@ -0,0 +1,8 @@
+# gdlint: ignore=max-public-methods
+class_name Project
+extends RefCounted
+# A class for project properties.
+
+
+func _init(_frames := [], _name := tr("untitled"), _size := Vector2(64, 64)) -> void:
+ pass
diff --git a/src/Extensions/Audia/API/EmptyClasses/SelectionMap.gd b/src/Extensions/Audia/API/EmptyClasses/SelectionMap.gd
new file mode 100644
index 0000000..9a76904
--- /dev/null
+++ b/src/Extensions/Audia/API/EmptyClasses/SelectionMap.gd
@@ -0,0 +1,2 @@
+class_name SelectionMap
+extends Image
diff --git a/src/Extensions/Audia/API/EmptyClasses/SelectionTool.gd b/src/Extensions/Audia/API/EmptyClasses/SelectionTool.gd
new file mode 100644
index 0000000..1c659c0
--- /dev/null
+++ b/src/Extensions/Audia/API/EmptyClasses/SelectionTool.gd
@@ -0,0 +1,2 @@
+class_name SelectionTool
+extends BaseTool
diff --git a/src/Extensions/Audia/API/EmptyClasses/ShaderImageEffect.gd b/src/Extensions/Audia/API/EmptyClasses/ShaderImageEffect.gd
new file mode 100644
index 0000000..b62177e
--- /dev/null
+++ b/src/Extensions/Audia/API/EmptyClasses/ShaderImageEffect.gd
@@ -0,0 +1,3 @@
+class_name ShaderImageEffect
+extends RefCounted
+# Helper class to generate image effects using shaders
diff --git a/src/Extensions/Audia/API/EmptyClasses/Tiles.gd b/src/Extensions/Audia/API/EmptyClasses/Tiles.gd
new file mode 100644
index 0000000..299c732
--- /dev/null
+++ b/src/Extensions/Audia/API/EmptyClasses/Tiles.gd
@@ -0,0 +1,6 @@
+class_name Tiles
+extends RefCounted
+
+
+func _init(size: Vector2):
+ pass
diff --git a/src/Extensions/Audia/API/ExtensionsApi.gd b/src/Extensions/Audia/API/ExtensionsApi.gd
new file mode 100644
index 0000000..6c66dc5
--- /dev/null
+++ b/src/Extensions/Audia/API/ExtensionsApi.gd
@@ -0,0 +1,473 @@
+extends Node
+# NOTE: Goto File-->Save then type "ExtensionsApi" in "Search Help" to read the
+# curated documentation of the Api.
+# If it still doesn't show try again after doing Project-->Reload current project
+
+## The Official ExtensionsAPI for pixelorama.
+##
+## This Api gives you the essentials to develop a working extension for Pixelorama.[br]
+## The Api consists of many smaller Apis, each giving access to different areas of the Software.
+## [br][br]
+## Keep in mind that this API is targeted towards users who are not fully familiar with Pixelorama's
+## source code. If you need to do something more complicated and more low-level, you would need to
+## interact directly with the source code.
+## [br][br]
+## To access this anywhere in the extension use [code]get_node_or_null("/root/ExtensionsApi")[/code]
+##
+## @tutorial(Add Tutorial here): https://the/tutorial1/url.com
+
+## Gives access to the general, app related functions of pixelorama
+## such as Autoloads, Software Version, Config file etc...
+var general := GeneralAPI.new()
+var menu := MenuAPI.new() ## Gives ability to add/remove items from menus in the top bar.
+var dialog := DialogAPI.new() ## Gives access to Dialog related functions.
+var panel := PanelAPI.new() ## Gives access to Tabs and Dockable Container related functions.
+var theme := ThemeAPI.new() ## Gives access to theme related functions.
+var tools := ToolAPI.new() ## Gives ability to add/remove tools.
+var selection := SelectionAPI.new() ## Gives access to pixelorama's selection system.
+var project := ProjectAPI.new() ## Gives access to project manipulation.
+var export := ExportAPI.new() ## Gives access to adding custom exporters.
+var import := ImportAPI.new() ## Gives access to adding custom import options.
+var palette := PaletteAPI.new() ## Gives access to palettes.
+var signals := SignalsAPI.new() ## Gives access to the basic commonly used signals.
+
+
+# The API Methods Start Here
+## Returns the version of the ExtensionsApi.
+func get_api_version() -> int:
+ return 4
+
+
+## Returns the initial nodes of an extension named [param extension_name].
+## initial nodes are the nodes whose paths are in the [code]nodes[/code] key of an
+## extension.json file.
+func get_main_nodes(extension_name: StringName) -> Array[Node]:
+ return []
+
+
+## Gives Access to the general stuff.
+##
+## This part of Api provides stuff like commonly used Autoloads, App's version info etc
+## the most basic (but important) stuff.
+class GeneralAPI:
+ ## Returns the current version of pixelorama.
+ func get_pixelorama_version() -> String:
+ return "v1.0-stable"
+
+ ## Returns the [ConfigFile] contains all the settings (Brushes, sizes, preferences, etc...).
+ func get_config_file() -> ConfigFile:
+ return
+
+ ## Returns the Global autoload used by Pixelorama.[br]
+ ## Contains references to almost all UI Elements, Variables that indicate different
+ ## settings etc..., In short it is the most important autoload of Pixelorama.
+ func get_global():
+ return
+
+ ## Returns the DrawingAlgos autoload, contains different drawing algorithms used by Pixelorama.
+ func get_drawing_algos():
+ return
+
+ ## Gives you a new ShaderImageEffect class. this class can apply shader to an image.[br]
+ ## It contains method:
+ ## [code]generate_image(img: Image, shader: Shader, params: Dictionary, size: Vector2)[/code]
+ ## [br]Whose parameters are identified as:
+ ## [br][param img] --> image that the shader will be pasted to (Empty Image of size same as
+ ## project).
+ ## [br][param shader] --> preload of the shader.
+ ## [br][param params] --> a dictionary of params used by the shader.
+ ## [br][param size] --> It is the project's size.
+ func get_new_shader_image_effect() -> ShaderImageEffect:
+ return ShaderImageEffect.new()
+
+ ## Returns parent of the nodes listed in extension.json -> "nodes".
+ func get_extensions_node() -> Node:
+ return
+
+ ## Returns the main [code]Canvas[/code] node,
+ ## normally used to add a custom preview to the canvas.
+ func get_canvas():
+ return
+
+
+## Gives ability to add/remove items from menus in the top bar.
+class MenuAPI:
+ enum { FILE, EDIT, SELECT, IMAGE, EFFECTS, VIEW, WINDOW, HELP }
+
+ ## Adds a menu item of title [param item_name] to the [param menu_type] defined by
+ ## [enum @unnamed_enums].
+ ## [br][param item_metadata] is usually a window node you want to appear when you click the
+ ## [param item_name]. That window node should also have a [param menu_item_clicked]
+ ## function inside its script.[br]
+ ## Index of the added item is returned (which can be used to remove menu item later on).
+ func add_menu_item(menu_type: int, item_name: String, item_metadata, item_id := -1) -> int:
+ return 0
+
+ ## Removes a menu item at index [param item_idx] from the [param menu_type] defined by
+ ## [enum @unnamed_enums].
+ func remove_menu_item(menu_type: int, item_idx: int) -> void:
+ return
+
+
+## Gives access to common dialog related functions.
+class DialogAPI:
+ ## Shows an alert dialog with the given [param text].
+ ## Useful for displaying messages like "Incompatible API" etc...
+ func show_error(text: String) -> void:
+ return
+
+ ## Returns the node that is the parent of dialogs used in pixelorama.
+ func get_dialogs_parent_node() -> Node:
+ return
+
+ ## Tells pixelorama that some dialog is about to open or close.
+ func dialog_open(open: bool) -> void:
+ return
+
+
+## Gives access to Tabs and Dockable Container related functions.
+class PanelAPI:
+ ## Sets the visibility of dockable tabs.
+ var tabs_visible: bool:
+ set(value):
+ pass
+ get:
+ return false
+
+ ## Adds the [param node] as a tab. Initially it's placed on the same panel as the tools tab,
+ ## but can be changed through adding custom layouts.
+ func add_node_as_tab(node: Node) -> void:
+ return
+
+ ## Removes the [param node] from the DockableContainer.
+ func remove_node_from_tab(node: Node) -> void:
+ return
+
+
+## Gives access to theme related functions.
+class ThemeAPI:
+ ## Adds the [param theme] to [code]Edit -> Preferences -> Interface -> Themes[/code].
+ func add_theme(theme: Theme) -> void:
+ return
+
+ ## Returns index of the [param theme] in preferences.
+ func find_theme_index(theme: Theme) -> int:
+ return 0
+
+ ## Returns the current theme resource.
+ func get_theme() -> Theme:
+ return
+
+ ## Sets a theme located at a given [param idx] in preferences. If theme set successfully then
+ ## return [code]true[/code], else [code]false[/code].
+ func set_theme(idx: int) -> bool:
+ return false
+
+ ## Remove the [param theme] from preferences.
+ func remove_theme(theme: Theme) -> void:
+ return
+
+
+## Gives ability to add/remove tools.
+class ToolAPI:
+ # gdlint: ignore=constant-name
+ enum LayerTypes { PIXEL, GROUP, THREE_D }
+
+ ## Adds a tool to pixelorama with name [param tool_name] (without spaces),
+ ## display name [param display_name], tool scene [param scene], layers that the tool works
+ ## on [param layer_types] defined by [constant LayerTypes],
+ ## [param extra_hint] (text that appears when mouse havers tool icon), primary shortcut
+ ## name [param shortcut] and any extra shortcuts [param extra_shortcuts].
+ ## [br][br]At the moment extensions can't make their own shortcuts so you can ignore
+ ## [param shortcut] and [param extra_shortcuts].
+ ## [br] to determine the position of tool in tool list, use [param insert_point]
+ ## (if you leave it empty then the added tool will be placed at bottom)
+ func add_tool(
+ tool_name: String,
+ display_name: String,
+ scene: String,
+ layer_types: PackedInt32Array = [],
+ extra_hint := "",
+ shortcut: String = "",
+ extra_shortcuts: PackedStringArray = [],
+ insert_point := -1
+ ) -> void:
+ return
+
+ ## Removes a tool with name [param tool_name]
+ ## and assign Pencil as left tool, Eraser as right tool.
+ func remove_tool(tool_name: String) -> void:
+ return
+
+
+## Gives access to pixelorama's selection system.
+class SelectionAPI:
+ ## Clears the selection.
+ func clear_selection() -> void:
+ return
+
+ ## Select the entire region of current cel.
+ func select_all() -> void:
+ return
+
+ ## Selects a portion defined by [param rect] of the current cel.
+ ## [param operation] influences it's behaviour with previous selection rects
+ ## (0 for adding, 1 for subtracting, 2 for intersection).
+ func select_rect(rect: Rect2i, operation := 0) -> void:
+ return
+
+ ## Moves a selection to [param destination],
+ ## with content if [param with_content] is [code]true[/code].
+ ## If [param transform_standby] is [code]true[/code] then the transformation will not be
+ ## applied immediatelyunless [kbd]Enter[/kbd] is pressed.
+ func move_selection(
+ destination: Vector2i, with_content := true, transform_standby := false
+ ) -> void:
+ return
+
+ ## Resizes the selection to [param new_size],
+ ## with content if [param with_content] is [code]true[/code].
+ ## If [param transform_standby] is [code]true[/code] then the transformation will not be
+ ## applied immediately unless [kbd]Enter[/kbd] is pressed.
+ func resize_selection(
+ new_size: Vector2i, with_content := true, transform_standby := false
+ ) -> void:
+ return
+
+ ## Inverts the selection.
+ func invert() -> void:
+ return
+
+ ## Makes a project brush out of the current selection's content.
+ func make_brush() -> void:
+ return
+
+ ## Returns the portion of current cel's image enclosed by the selection.
+ ## It's similar to [method make_brush] but it returns the image instead.
+ func get_enclosed_image() -> Image:
+ return
+
+ ## Copies the selection content (works in or between pixelorama instances only).
+ func copy() -> void:
+ return
+
+ ## Pastes the selection content.
+ func paste(in_place := false) -> void:
+ return
+
+ ## Deletes the drawing on current cel enclosed within the selection's area.
+ func delete_content(selected_cels := true) -> void:
+ return
+
+
+## Gives access to basic project manipulation functions.
+class ProjectAPI:
+ ## The project currently in focus
+ var current_project: Project:
+ set(value):
+ pass
+ get:
+ return
+
+ ## Creates a new project in a new tab with one starting layer and frame,
+ ## name [param name], size [param size], fill color [param fill_color] and
+ ## frames [param frames]. The created project also gets returned.[br][br]
+ ## [param frames] is an [Array] of type [Frame]. Usually it can be left as [code][][/code].
+ func new_project(
+ frames: Array[Frame] = [],
+ name := tr("untitled"),
+ size := Vector2(64, 64),
+ fill_color := Color.TRANSPARENT
+ ) -> Project:
+ return
+
+ ## Creates and returns a new [Project] in a new tab, with an optional [param name].
+ ## Unlike [method new_project], no starting frame/layer gets created.
+ ## Useful if you want to deserialize project data.
+ func new_empty_project(name := tr("untitled")) -> Project:
+ return
+
+ ## Returns a dictionary containing all the project information.
+ func get_project_info(project: Project) -> Dictionary:
+ return {}
+
+ ## Selects the cels and makes the last entry of [param selected_array] as the current cel
+ ## [param selected_array] is an [Array] of [Arrays] of 2 integers (frame & layer).[br]
+ ## Frames are counted from left to right, layers are counted from bottom to top.
+ ## Frames/layers start at "0" and end at [param project.frames.size() - 1] and
+ ## [param project.layers.size() - 1] respectively.
+ func select_cels(selected_array := [[0, 0]]) -> void:
+ return
+
+ ## Returns the current cel.
+ ## Cel type can be checked using function [method get_class_name] inside the cel
+ ## type can be GroupCel, PixelCel, Cel3D, or BaseCel.
+ func get_current_cel() -> BaseCel:
+ return
+
+ ## Frames are counted from left to right, layers are counted from bottom to top.
+ ## Frames/layers start at "0" and end at [param project.frames.size() - 1] and
+ ## [param project.layers.size() - 1] respectively.
+ func get_cel_at(project: Project, frame: int, layer: int) -> BaseCel:
+ return
+
+ ## Sets an [param image] at [param frame] and [param layer] on the current project.
+ ## Frames are counted from left to right, layers are counted from bottom to top.
+ func set_pixelcel_image(image: Image, frame: int, layer: int) -> void:
+ return
+
+ ## Adds a new frame in the current project after frame [param after_frame].
+ func add_new_frame(after_frame: int) -> void:
+ print("invalid (after_frame)")
+ return
+
+
+ ## Adds a new Layer of name [param name] in the current project above layer [param above_layer]
+ ## ([param above_layer] = 0 is the bottom-most layer and so on).
+ ## [br][param type] = 0 --> PixelLayer,
+ ## [br][param type] = 1 --> GroupLayer,
+ ## [br][param type] = 2 --> 3DLayer
+ func add_new_layer(above_layer: int, name := "", type := 0) -> void:
+ print("invalid (type)")
+ print("invalid (above_layer)")
+ return
+
+
+## Gives access to adding custom exporters.
+class ExportAPI:
+ # gdlint: ignore=constant-name
+ enum ExportTab { IMAGE = 0, SPRITESHEET = 1}
+
+ ## [param format_info] has keys: [code]extension[/code] and [code]description[/code]
+ ## whose values are of type [String] e.g:[codeblock]
+ ## format_info = {"extension": ".gif", "description": "GIF Image"}
+ ## [/codeblock]
+ ## [param exporter_generator] is a node with a script containing the method
+ ## [method override_export] which takes 1 argument of type Dictionary which is automatically
+ ## passed to [method override_export] at time of export and contains
+ ## keys: [code]processed_images[/code], [code]export_dialog[/code],
+ ## [code]export_paths[/code], [code]project[/code][br]
+ ## (Note: [code]processed_images[/code] is an array of ProcessedImage resource which further
+ ## has parameters [param image] and [param duration])[br]
+ ## If the value of [param tab] is not in [constant ExportTab] then the format will be added to
+ ## both tabs. Returns the index of exporter, which can be used to remove exporter later.
+ func add_export_option(
+ format_info: Dictionary,
+ exporter_generator: Object,
+ tab := ExportTab.IMAGE,
+ is_animated := true
+ ) -> int:
+ return 0
+
+ ## Removes the exporter with [param id] from Pixelorama.
+ func remove_export_option(id: int) -> void:
+ return
+
+
+## Gives access to adding custom import options.
+class ImportAPI:
+ ## [param import_scene] is a scene preload that will be instanced and added to "import options"
+ ## section of pixelorama's import dialogs and will appears whenever [param import_name] is
+ ## chosen from import menu.
+ ## [br]
+ ## [param import_scene] must have a a script containing:[br]
+ ## 1. An optional variable named [code]import_preview_dialog[/code] of type [ConfirmationDialog],
+ ## If present, it will automatically be assigned a reference to the relevant import dialog's
+ ## [code]ImportPreviewDialog[/code] class so that you can easily access variables and
+ ## methods of that class. (This variable is meant to be read-only)[br]
+ ## 2. The method [method initiate_import] which takes 2 arguments: [code]path[/code],
+ ## [code]image[/code], which are automatically passed to [method initiate_import] at
+ ## time of import.
+ func add_import_option(import_name: StringName, import_scene_preload: PackedScene) -> int:
+ return 0
+
+ ## Removes the import option with [param id] from Pixelorama.
+ func remove_import_option(id: int) -> void:
+ return
+
+
+## Gives access to palettes.
+class PaletteAPI:
+ ## Creates and adds a new [Palette] with name [param palette_name] with [param data]
+ ## in the form of a [Dictionary].
+ func create_palette_from_data(palette_name: String, data: Dictionary) -> void:
+ return
+
+
+## Gives access to the basic commonly used signals.
+##
+## Gives access to the basic commonly used signals.
+## Some less common signals are not mentioned in Api but could be accessed through source directly.
+class SignalsAPI:
+ # APP RELATED SIGNALS
+ ## Connects/disconnects a signal to [param callable], that emits
+ ## when pixelorama is just opened.
+ func signal_pixelorama_opened(callable: Callable, is_disconnecting := false) -> void:
+ return
+
+ ## Connects/disconnects a signal to [param callable], that emits
+ ## when pixelorama is about to close.
+ func signal_pixelorama_about_to_close(callable: Callable, is_disconnecting := false) -> void:
+ return
+
+ # PROJECT RELATED SIGNALS
+ ## Connects/disconnects a signal to [param callable], that emits
+ ## whenever a new project is created.[br]
+ ## [b]Binds: [/b]It has one bind of type [code]Project[/code] which is the newly created project
+ func signal_project_created(callable: Callable, is_disconnecting := false) -> void:
+ return
+
+ ## Connects/disconnects a signal to [param callable], that emits
+ ## after a project is saved.
+ func signal_project_saved(callable: Callable, is_disconnecting := false) -> void:
+ return
+
+ ## Connects/disconnects a signal to [param callable], that emits
+ ## whenever you switch to some other project.
+ func signal_project_switched(callable: Callable, is_disconnecting := false) -> void:
+ return
+
+ ## Connects/disconnects a signal to [param callable], that emits
+ ## whenever you select a different cel.
+ func signal_cel_switched(callable: Callable, is_disconnecting := false) -> void:
+ return
+
+ ## Connects/disconnects a signal to [param callable], that emits
+ ## whenever the project data are being modified.
+ func signal_project_data_changed(callable: Callable, is_disconnecting := false) -> void:
+ return
+
+ # TOOL RELATED SIGNALS
+ ## Connects/disconnects a signal to [param callable], that emits
+ ## whenever a tool changes color.[br]
+ ## [b]Binds: [/b] It has two bind of type [Color] (indicating new color)
+ ## and [int] (Indicating button that tool is assigned to, see [enum @GlobalScope.MouseButton])
+ func signal_tool_color_changed(callable: Callable, is_disconnecting := false) -> void:
+ return
+
+ # TIMELINE RELATED SIGNALS
+ ## Connects/disconnects a signal to [param callable], that emits
+ ## whenever timeline animation starts.[br]
+ ## [b]Binds: [/b] It has one bind of type [bool] which indicated if animation is in
+ ## forward direction ([code]true[/code]) or backward direction ([code]false[/code])
+ func signal_timeline_animation_started(callable: Callable, is_disconnecting := false) -> void:
+ return
+
+ ## Connects/disconnects a signal to [param callable], that emits
+ ## whenever timeline animation stops.
+ func signal_timeline_animation_finished(callable: Callable, is_disconnecting := false) -> void:
+ return
+
+ # UPDATER SIGNALS
+ ## Connects/disconnects a signal to [param callable], that emits
+ ## whenever texture of the currently focused cel changes.
+ func signal_current_cel_texture_changed(callable: Callable, is_disconnecting := false) -> void:
+ return
+
+ ## Connects/disconnects a signal to [param callable], that emits
+ ## whenever preview is about to be drawn.[br]
+ ## [b]Binds: [/b]It has one bind of type [Dictionary] with keys: [code]exporter_id[/code],
+ ## [code]export_tab[/code], [code]preview_images[/code], [code]durations[/code]
+ ## [br] Use this if you plan on changing preview of export
+ func signal_export_about_to_preview(callable: Callable, is_disconnecting := false) -> void:
+ return
diff --git a/src/Extensions/Audia/Classes/ShotcutMaker.gd b/src/Extensions/Audia/Classes/ShotcutMaker.gd
new file mode 100644
index 0000000..6a072df
--- /dev/null
+++ b/src/Extensions/Audia/Classes/ShotcutMaker.gd
@@ -0,0 +1,87 @@
+extends RefCounted
+
+var p_path: String
+var dir_path: String
+
+const STARTING = ("""
+
+ """)
+
+var producers := []
+
+const MIDDLE = ("""
+ 2
+ 1""")
+
+var entry := []
+
+const END = ("""
+""")
+
+
+func compile(size: Vector2):
+ var final = STARTING.replace("", str(size.x)).replace("", str(size.y))
+ for data in producers:
+ final += data
+ final += MIDDLE
+ for data in entry:
+ final += data
+ final += END
+ if p_path:
+ var file = FileAccess.open(p_path, FileAccess.WRITE)
+ file.store_string(final)
+ file.close()
+
+
+func add_item_to_playlist(path: String, clip_duration: float):
+ if !p_path:
+ p_path = path.replace(path.get_file(), "project.mlt")
+ var raw = ("""\n
+
+ pause
+
+ 1
+ 1
+ 1
+ 2
+ qimage
+ was here
+ """)
+ var entry_raw = (""" """)
+ raw = raw.replace("", str("producer", producers.size()))
+ raw = raw.replace("", calculate_duration(clip_duration))
+ entry_raw = entry_raw.replace("", str("producer", producers.size()))
+ entry_raw = entry_raw.replace("", calculate_duration(clip_duration))
+ raw = raw.replace("", path.get_file())
+ producers.append(raw)
+ entry.append(entry_raw)
+
+
+func calculate_duration(clip_duration: float) -> String:
+ clip_duration = snappedf(clip_duration, 0.01)
+ var millis = clip_duration - floorf(clip_duration)
+ if millis > 0:
+ var millis_test = str_to_var(str(millis).get_slice(".", 1))
+ var adder = ""
+ if millis_test < 100:
+ adder = "0"
+ if millis_test < 10:
+ adder += "0"
+ if millis < 0.1:
+ millis = str(adder, millis_test)
+ else:
+ millis = str(millis_test, adder)
+ else:
+ millis = "000"
+ var secs = floor(clip_duration)
+ var minutes = floor(secs / 60.0)
+ var hours = floor(minutes / 60.0)
+ secs = int(secs) % 60
+ minutes = int(minutes) % 60
+ if secs < 10:
+ secs = str("0", secs)
+ if minutes < 10:
+ minutes = str("0", minutes)
+ if hours < 10:
+ hours = str("0", hours)
+ return str(hours, ":", minutes, ":", secs, ".", millis)
diff --git a/src/Extensions/Audia/Elements/3rd party/GDScriptAudioImport.gd b/src/Extensions/Audia/Elements/3rd party/GDScriptAudioImport.gd
new file mode 100644
index 0000000..9b09e01
--- /dev/null
+++ b/src/Extensions/Audia/Elements/3rd party/GDScriptAudioImport.gd
@@ -0,0 +1,254 @@
+#GDScriptAudioImport v0.1 (using a fork that updates to godot 4)
+# upstream: https://github.com/Stoxis/GDScriptAudioImport-Godot4/blob/master/GDScriptAudioImport.gd
+# commit: 21b7d2922d37fd1e8606489e044465e9d8a2f810
+# changed lines 157-161 according to comment https://github.com/Gianclgar/GDScriptAudioImport/pull/20#issuecomment-2067591508
+# commented lines 34, 154, 160, 166
+
+
+#GDScriptAudioImport v0.1
+
+#MIT License
+#
+#Copyright (c) 2020 Gianclgar (Giannino Clemente) gianclgar@gmail.com
+#
+#Permission is hereby granted, free of charge, to any person obtaining a copy
+#of this software and associated documentation files (the "Software"), to deal
+#in the Software without restriction, including without limitation the rights
+#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+#copies of the Software, and to permit persons to whom the Software is
+#furnished to do so, subject to the following conditions:
+#
+#The above copyright notice and this permission notice shall be included in all
+#copies or substantial portions of the Software.
+#
+#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+#SOFTWARE.
+
+#I honestly don't care that much, Kopimi ftw, but it's my little baby and I want it to look nice :3
+
+#class_name AudioLoader
+
+func report_errors(err, filepath):
+ # See: https://docs.godotengine.org/en/latest/classes/class_@globalscope.html#enum-globalscope-error
+ var result_hash = {
+ ERR_FILE_NOT_FOUND: "File: not found",
+ ERR_FILE_BAD_DRIVE: "File: Bad drive error",
+ ERR_FILE_BAD_PATH: "File: Bad path error.",
+ ERR_FILE_NO_PERMISSION: "File: No permission error.",
+ ERR_FILE_ALREADY_IN_USE: "File: Already in use error.",
+ ERR_FILE_CANT_OPEN: "File: Can't open error.",
+ ERR_FILE_CANT_WRITE: "File: Can't write error.",
+ ERR_FILE_CANT_READ: "File: Can't read error.",
+ ERR_FILE_UNRECOGNIZED: "File: Unrecognized error.",
+ ERR_FILE_CORRUPT: "File: Corrupt error.",
+ ERR_FILE_MISSING_DEPENDENCIES: "File: Missing dependencies error.",
+ ERR_FILE_EOF: "File: End of file (EOF) error."
+ }
+ if err in result_hash:
+ print("Error: ", result_hash[err], " ", filepath)
+ else:
+ print("Unknown error with file ", filepath, " error code: ", err)
+
+func loadfile(filepath):
+ var file = FileAccess.open(filepath, FileAccess.READ)
+ var err = file.get_error()
+ if err != OK:
+ report_errors(err, filepath)
+ file.close()
+ return AudioStreamWAV.new()
+
+ var bytes = file.get_buffer(file.get_length())
+ # if File is wav
+ if filepath.ends_with(".wav"):
+ var newstream = AudioStreamWAV.new()
+
+ #---------------------------
+ #parrrrseeeeee!!! :D
+
+ var bits_per_sample = 0
+
+ for i in range(0, 100):
+ var those4bytes = str(char(bytes[i])+char(bytes[i+1])+char(bytes[i+2])+char(bytes[i+3]))
+
+ if those4bytes == "RIFF":
+ print ("RIFF OK at bytes " + str(i) + "-" + str(i+3))
+ #RIP bytes 4-7 integer for now
+ if those4bytes == "WAVE":
+ print ("WAVE OK at bytes " + str(i) + "-" + str(i+3))
+
+ if those4bytes == "fmt ":
+ print ("fmt OK at bytes " + str(i) + "-" + str(i+3))
+
+ #get format subchunk size, 4 bytes next to "fmt " are an int32
+ var formatsubchunksize = bytes[i+4] + (bytes[i+5] << 8) + (bytes[i+6] << 16) + (bytes[i+7] << 24)
+ print ("Format subchunk size: " + str(formatsubchunksize))
+
+ #using formatsubchunk index so it's easier to understand what's going on
+ var fsc0 = i+8 #fsc0 is byte 8 after start of "fmt "
+
+ #get format code [Bytes 0-1]
+ var format_code = bytes[fsc0] + (bytes[fsc0+1] << 8)
+ var format_name
+ if format_code == 0: format_name = "8_BITS"
+ elif format_code == 1: format_name = "16_BITS"
+ elif format_code == 2: format_name = "IMA_ADPCM"
+ else:
+ format_name = "UNKNOWN (trying to interpret as 16_BITS)"
+ format_code = 1
+ print ("Format: " + str(format_code) + " " + format_name)
+ #assign format to our AudioStreamSample
+ newstream.format = format_code
+
+ #get channel num [Bytes 2-3]
+ var channel_num = bytes[fsc0+2] + (bytes[fsc0+3] << 8)
+ print ("Number of channels: " + str(channel_num))
+ #set our AudioStreamSample to stereo if needed
+ if channel_num == 2: newstream.stereo = true
+
+ #get sample rate [Bytes 4-7]
+ var sample_rate = bytes[fsc0+4] + (bytes[fsc0+5] << 8) + (bytes[fsc0+6] << 16) + (bytes[fsc0+7] << 24)
+ print ("Sample rate: " + str(sample_rate))
+ #set our AudioStreamSample mixrate
+ newstream.mix_rate = sample_rate
+
+ #get byte_rate [Bytes 8-11] because we can
+ var byte_rate = bytes[fsc0+8] + (bytes[fsc0+9] << 8) + (bytes[fsc0+10] << 16) + (bytes[fsc0+11] << 24)
+ print ("Byte rate: " + str(byte_rate))
+
+ #same with bits*sample*channel [Bytes 12-13]
+ var bits_sample_channel = bytes[fsc0+12] + (bytes[fsc0+13] << 8)
+ print ("BitsPerSample * Channel / 8: " + str(bits_sample_channel))
+
+ #aaaand bits per sample/bitrate [Bytes 14-15]
+ bits_per_sample = bytes[fsc0+14] + (bytes[fsc0+15] << 8)
+ print ("Bits per sample: " + str(bits_per_sample))
+
+ if those4bytes == "data":
+ assert(bits_per_sample != 0)
+
+ var audio_data_size = bytes[i+4] + (bytes[i+5] << 8) + (bytes[i+6] << 16) + (bytes[i+7] << 24)
+ print ("Audio data/stream size is " + str(audio_data_size) + " bytes")
+
+ var data_entry_point = (i+8)
+ print ("Audio data starts at byte " + str(data_entry_point))
+
+ var data = bytes.slice(data_entry_point, data_entry_point + audio_data_size)
+
+ if bits_per_sample in [24, 32]:
+ newstream.data = convert_to_16bit(data, bits_per_sample)
+ else:
+ newstream.data = data
+ # end of parsing
+ #---------------------------
+
+ #Calculate the size of each sample based on bits_per_sample
+ var sample_size = bits_per_sample / 8
+ #get samples and set loop end
+ var samplenum = newstream.data.size() / sample_size
+ newstream.loop_end = samplenum
+ #newstream.loop_mode = 0 #change to 0 or delete this line if you don't want loop, also check out modes 2 and 3 in the docs
+ return newstream #:D
+
+ #if file is ogg
+ elif filepath.ends_with(".ogg"):
+ var newstream = AudioStreamOggVorbis.load_from_buffer(bytes)
+ #newstream.loop = true #set to false or delete this line if you don't want to loop
+ return newstream
+
+ #if file is mp3
+ elif filepath.ends_with(".mp3"):
+ var newstream = AudioStreamMP3.new()
+ #newstream.loop = true #set to false or delete this line if you don't want to loop
+ newstream.data = bytes
+ return newstream
+
+ else:
+ print ("ERROR: Wrong filetype or format")
+ file.close()
+
+# Converts .wav data from 24 or 32 bits to 16
+#
+# These conversions are SLOW in GDScript
+# on my one test song, 32 -> 16 was around 3x slower than 24 -> 16
+#
+# I couldn't get threads to help very much
+# They made the 24bit case about 2x faster in my test file
+# And the 32bit case abour 50% slower
+# I don't wanna risk it always being slower on other files
+# And really, the solution would be to handle it in a low-level language
+func convert_to_16bit(data: PackedByteArray, from: int) -> PackedByteArray:
+ print("converting to 16-bit from %d" % from)
+ var time = Time.get_ticks_msec()
+ if from == 24:
+ var j = 0
+ for i in range(0, data.size(), 3):
+ data[j] = data[i+1]
+ data[j+1] = data[i+2]
+ j += 2
+ data.resize(data.size() * 2 / 3)
+ if from == 32:
+ var spb := StreamPeerBuffer.new()
+ var single_float: float
+ var value: int
+ for i in range(0, data.size(), 4):
+ var sub_array = data.slice(i, i+4)
+ spb.data_array = PackedByteArray(sub_array)
+ single_float = spb.get_float()
+ value = single_float * 32768
+ data[i/2] = value
+ data[i/2+1] = value >> 8
+ data.resize(data.size() / 2)
+ print("Took %f seconds for slow conversion" % ((Time.get_ticks_msec() - time) / 1000.0))
+ return data
+
+# ---------- REFERENCE ---------------
+# note: typical values doesn't always match
+
+#Positions Typical Value Description
+#
+#1 - 4 "RIFF" Marks the file as a RIFF multimedia file.
+# Characters are each 1 byte long.
+#
+#5 - 8 (integer) The overall file size in bytes (32-bit integer)
+# minus 8 bytes. Typically, you'd fill this in after
+# file creation is complete.
+#
+#9 - 12 "WAVE" RIFF file format header. For our purposes, it
+# always equals "WAVE".
+#
+#13-16 "fmt " Format sub-chunk marker. Includes trailing null.
+#
+#17-20 16 Length of the rest of the format sub-chunk below.
+#
+#21-22 1 Audio format code, a 2 byte (16 bit) integer.
+# 1 = PCM (pulse code modulation).
+#
+#23-24 2 Number of channels as a 2 byte (16 bit) integer.
+# 1 = mono, 2 = stereo, etc.
+#
+#25-28 44100 Sample rate as a 4 byte (32 bit) integer. Common
+# values are 44100 (CD), 48000 (DAT). Sample rate =
+# number of samples per second, or Hertz.
+#
+#29-32 176400 (SampleRate * BitsPerSample * Channels) / 8
+# This is the Byte rate.
+#
+#33-34 4 (BitsPerSample * Channels) / 8
+# 1 = 8 bit mono, 2 = 8 bit stereo or 16 bit mono, 4
+# = 16 bit stereo.
+#
+#35-36 16 Bits per sample.
+#
+#37-40 "data" Data sub-chunk header. Marks the beginning of the
+# raw data section.
+#
+#41-44 (integer) The number of bytes of the data section below this
+# point. Also equal to (#ofSamples * #ofChannels *
+# BitsPerSample) / 8
+#
+#45+ The raw audio data.
diff --git a/src/Extensions/Audia/Elements/AudioManager/AudioController.gd b/src/Extensions/Audia/Elements/AudioManager/AudioController.gd
new file mode 100644
index 0000000..b551a16
--- /dev/null
+++ b/src/Extensions/Audia/Elements/AudioManager/AudioController.gd
@@ -0,0 +1,31 @@
+extends AudioStreamPlayer
+
+var timer: Timer
+var end_time: float
+
+func start(current_frame: int, tag: AnimationTag, streamer: AudioStream):
+ var project = ExtensionsApi.project.current_project
+ timer = ExtensionsApi.general.get_global().animation_timer
+
+ name = tag.name
+
+ var start_time: float = 0
+ for frame_idx in range(tag.from - 1, tag.to):
+ var frame: Frame = project.frames[frame_idx]
+ var duration = frame.duration * (1.0 / project.fps)
+ if frame_idx < current_frame:
+ start_time += duration
+ else:
+ end_time += duration
+ if streamer:
+ stream = streamer
+ $Timer.wait_time = end_time
+ $Timer.start()
+ play(start_time)
+
+
+func _on_AudioController_finished() -> void:
+ queue_free()
+
+func _on_Timer_timeout() -> void:
+ stop()
diff --git a/src/Extensions/Audia/Elements/AudioManager/AudioController.tscn b/src/Extensions/Audia/Elements/AudioManager/AudioController.tscn
new file mode 100644
index 0000000..b823196
--- /dev/null
+++ b/src/Extensions/Audia/Elements/AudioManager/AudioController.tscn
@@ -0,0 +1,12 @@
+[gd_scene load_steps=2 format=3 uid="uid://b3lucmb1cwcl2"]
+
+[ext_resource type="Script" path="res://src/Extensions/Audia/Elements/AudioManager/AudioController.gd" id="1"]
+
+[node name="AudioController" type="AudioStreamPlayer"]
+script = ExtResource("1")
+
+[node name="Timer" type="Timer" parent="."]
+one_shot = true
+
+[connection signal="finished" from="." to="." method="_on_AudioController_finished"]
+[connection signal="timeout" from="Timer" to="." method="_on_Timer_timeout"]
diff --git a/src/Extensions/Audia/Elements/MusicButton/MusicEntry.gd b/src/Extensions/Audia/Elements/MusicButton/MusicEntry.gd
new file mode 100644
index 0000000..a135ef0
--- /dev/null
+++ b/src/Extensions/Audia/Elements/MusicButton/MusicEntry.gd
@@ -0,0 +1,96 @@
+extends PanelContainer
+
+var _path: String
+
+@onready var path_label: Label = $"%Path3D"
+@onready var identifier_label: LineEdit = $"%Identifier"
+
+var old_p_size := Vector2.ZERO
+var Audioloader = load("res://src/Extensions/Audia/Elements/3rd party/GDScriptAudioImport.gd")
+var reference_data: Dictionary # only used to see which is which
+var audio_stream: AudioStream
+
+
+func _ready() -> void:
+ ExtensionsApi.signals.signal_cel_switched(check_if_timeline_refreshed)
+
+
+func serialize() -> Dictionary:
+ var data = {
+ "path": _path,
+ "identifier": identifier_label.text,
+ }
+ return data
+
+
+func deserialize(data: Dictionary) -> void:
+ if data.has("path"):
+ _path = data["path"]
+ path_label.text = _path
+ path_label.tooltip_text = _path
+ var new_loader = Audioloader.new()
+ audio_stream = new_loader.loadfile(_path)
+ if !FileAccess.file_exists(_path):
+ path_label.self_modulate = Color.ORANGE_RED
+ if data.has("identifier"):
+ identifier_label.text = data["identifier"]
+ reference_data = data
+ _update_tag(identifier_label.text)
+
+
+func prepare_stream():
+ var new_loader = Audioloader.new()
+ audio_stream = new_loader.loadfile(_path)
+
+
+func _on_Identifier_text_changed(_new_text: String) -> void:
+ var project: Project = ExtensionsApi.project.current_project
+ var data: Array = project.get_meta("Music", [])
+ data.erase(reference_data)
+ _update_tag(reference_data["identifier"])
+ reference_data = serialize()
+ data.append(reference_data)
+
+
+func _on_Close_pressed() -> void:
+ var project: Project = ExtensionsApi.project.current_project
+ var data: Array = project.get_meta("Music", [])
+ data.erase(reference_data)
+ queue_free()
+
+
+func _update_tag(old_name: String ,full_refresh := true):
+ var tag_container = ExtensionsApi.general.get_global().tag_container
+ for child: Control in tag_container.get_children():
+ if child.tag.name == old_name:
+ for element in child.get_children():
+ if element.is_in_group("AudioIndicator"):
+ element.queue_free()
+ if full_refresh:
+ if child.tag.name == identifier_label.text:
+ var new_indicator := TextureRect.new()
+ child.add_child(new_indicator)
+ new_indicator.add_to_group("AudioIndicator")
+ new_indicator.modulate = child.tag.color
+ new_indicator.texture = preload("res://src/Extensions/Audia/music_icon.png")
+ new_indicator.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
+ new_indicator.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
+ new_indicator.size = Vector2.ONE * child.size.y / 1.5
+
+
+func check_if_timeline_refreshed():
+ var project = ExtensionsApi.project.current_project
+ var new_size = Vector2(project.frames.size(), project.layers.size())
+ if old_p_size != new_size:
+ old_p_size = new_size
+ await get_tree().process_frame
+ refresh_self()
+
+
+func refresh_self():
+ _update_tag(identifier_label.text)
+
+
+func _exit_tree() -> void:
+ _update_tag(identifier_label.text, false)
+ ExtensionsApi.signals.signal_cel_switched(check_if_timeline_refreshed, true)
diff --git a/src/Extensions/Audia/Elements/MusicButton/MusicEntry.tscn b/src/Extensions/Audia/Elements/MusicButton/MusicEntry.tscn
new file mode 100644
index 0000000..90989c9
--- /dev/null
+++ b/src/Extensions/Audia/Elements/MusicButton/MusicEntry.tscn
@@ -0,0 +1,57 @@
+[gd_scene load_steps=2 format=3 uid="uid://b6m6whsl7rckf"]
+
+[ext_resource type="Script" path="res://src/Extensions/Audia/Elements/MusicButton/MusicEntry.gd" id="1"]
+
+[node name="MusicEntry" type="PanelContainer"]
+custom_minimum_size = Vector2(200, 70)
+offset_right = 200.0
+offset_bottom = 70.0
+script = ExtResource("1")
+
+[node name="Panel" type="Panel" parent="."]
+self_modulate = Color(1, 1, 1, 0.309804)
+layout_mode = 2
+
+[node name="Options" type="VBoxContainer" parent="Panel"]
+layout_mode = 0
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = 3.0
+offset_top = 3.0
+offset_right = -3.0
+offset_bottom = -1.0
+
+[node name="HBoxContainer" type="HBoxContainer" parent="Panel/Options"]
+layout_mode = 2
+
+[node name="Path3D" type="Label" parent="Panel/Options/HBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+mouse_filter = 0
+text = "Path3D"
+clip_text = true
+max_lines_visible = 2
+
+[node name="Close" type="Button" parent="Panel/Options/HBoxContainer"]
+layout_mode = 2
+text = "X"
+
+[node name="HBoxContainer2" type="HBoxContainer" parent="Panel/Options"]
+layout_mode = 2
+
+[node name="Label" type="Label" parent="Panel/Options/HBoxContainer2"]
+layout_mode = 2
+text = "Play on Tag: "
+
+[node name="Identifier" type="LineEdit" parent="Panel/Options/HBoxContainer2"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+tooltip_text = "treats a tag with this name as an audio clip"
+placeholder_text = "Identifier (Use Tag name)"
+caret_blink = true
+caret_blink_interval = 0.5
+
+[connection signal="pressed" from="Panel/Options/HBoxContainer/Close" to="." method="_on_Close_pressed"]
+[connection signal="text_changed" from="Panel/Options/HBoxContainer2/Identifier" to="." method="_on_Identifier_text_changed"]
diff --git a/src/Extensions/Audia/Elements/MusicListContainer.gd b/src/Extensions/Audia/Elements/MusicListContainer.gd
new file mode 100644
index 0000000..69afd42
--- /dev/null
+++ b/src/Extensions/Audia/Elements/MusicListContainer.gd
@@ -0,0 +1,210 @@
+extends PanelContainer
+
+
+# references from pixelorama
+var timer: Timer
+
+var audio_tags: Dictionary
+var stream_player = preload("res://src/Extensions/Audia/Elements/AudioManager/AudioController.tscn")
+var current_playing_frame := -1
+var is_playing := false
+var streams_are_prepared := false
+
+@onready var option_button: OptionButton = $"%OptionButton"
+@onready var list: VBoxContainer = $"%List"
+@onready var audios: Node = $Audios
+
+
+func _ready() -> void:
+ await get_tree().process_frame
+ # update the music library
+ update_list()
+ # Get references to widely used nodes
+ timer = ExtensionsApi.general.get_global().animation_timer
+ # signal connections
+ ExtensionsApi.signals.signal_project_switched(update_list)
+ ExtensionsApi.general.get_global().project_about_to_switch.connect(disconnect_old_project_signals)
+ ExtensionsApi.signals.signal_cel_switched(check_sanity)
+ timer.timeout.connect(manage_next_frame_music)
+ ExtensionsApi.general.get_global().animation_timeline.animation_started.connect(manage_setup)
+ ExtensionsApi.general.get_global().animation_timeline.animation_finished.connect(manage_setup)
+ get_tree().root.files_dropped.connect(add_audios)
+ # setup audio drivers list
+ setup_audio_drivers()
+
+
+func disconnect_old_project_signals():
+ var project := ExtensionsApi.project.current_project
+ project.undo_redo.version_changed.disconnect(check_tag_update)
+
+
+func _exit_tree() -> void:
+ # disconnect signals connected in this script
+ ExtensionsApi.signals.signal_project_switched(update_list, true)
+ ExtensionsApi.signals.signal_cel_switched(check_sanity, true)
+ timer.timeout.disconnect(manage_next_frame_music)
+ ExtensionsApi.general.get_global().animation_timeline.animation_started.disconnect(manage_setup)
+ ExtensionsApi.general.get_global().animation_timeline.animation_finished.disconnect(manage_setup)
+ get_tree().root.files_dropped.disconnect(add_audios)
+ disconnect_old_project_signals()
+
+
+func add_audios(file_paths: PackedStringArray, _screen: int = 0):
+ var project: Project = ExtensionsApi.project.current_project
+ var data: Array = project.get_meta("Music", [])
+ var valid_exts = ["mp3", "ogg", "wav"]
+ var audio_added := false
+ var old_paths := PackedStringArray()
+ for entry in data:
+ old_paths.append(entry["path"])
+ for path in file_paths:
+ if path.get_extension().to_lower() in valid_exts:
+ audio_added = true
+ ExtensionsApi.dialog.show_error("Added asuccessfully")
+ if path in old_paths:
+ continue
+ var music_dict = {
+ "path": path,
+ "identifier": path.get_file().trim_suffix(str(".", path.get_extension())),
+ }
+ data.append(music_dict)
+ if audio_added:
+ project.set_meta("Music", data)
+ update_list()
+
+
+func update_list():
+ var project: Project = ExtensionsApi.project.current_project
+ # the project has changed, so change the undo_redo as well
+ if not project.undo_redo.version_changed.is_connected(check_tag_update):
+ project.undo_redo.version_changed.connect(check_tag_update)
+
+ var data = project.get_meta("Music", [])
+ for old_entry in list.get_children():
+ old_entry.queue_free()
+ for entry_data in data:
+ var entry = preload("res://src/Extensions/Audia/Elements/MusicButton/MusicEntry.tscn").instantiate()
+ list.add_child(entry)
+ entry.deserialize(entry_data)
+
+
+func check_tag_update():
+ var undo_redo = ExtensionsApi.project.current_project.undo_redo
+ if undo_redo.get_current_action_name() in ["Modify Frame Tag", "Resize Frame Tag"]:
+ var tag_container = ExtensionsApi.general.get_global().tag_container
+ # clear old indicators
+ for child in tag_container.get_children():
+ var indicator = child.get_node_or_null("AudioIndicator")
+ if indicator:
+ indicator.queue_free()
+ for child in list.get_children():
+ child.refresh_self()
+
+
+func manage_setup(play_forward: bool = false):
+ audio_tags = {}
+ if play_forward:
+ for music_entry in list.get_children():
+ music_entry.prepare_stream()
+ var data = music_entry.serialize()
+ if !data["identifier"] in audio_tags.keys():
+ audio_tags[data["identifier"]] = music_entry.audio_stream.duplicate()
+ manage_start_stop(play_forward)
+
+
+# manages initial setup when farward play button is pressed
+func manage_start_stop(play_forward: bool):
+ if !play_forward:
+ for player in audios.get_children():
+ player.queue_free()
+ current_playing_frame = -1
+ is_playing = false
+ return
+ if is_playing:
+ return
+ is_playing = true
+
+ var animation_tags: Array = ExtensionsApi.project.current_project.animation_tags
+ var current_frame: int = ExtensionsApi.project.current_project.current_frame
+ current_playing_frame = current_frame
+ for tag in animation_tags:
+ if current_frame + 1 >= tag.from && current_frame + 1 <= tag.to:
+ if tag.name in audio_tags.keys():
+ var new_stream_player = stream_player.instantiate()
+ audios.add_child(new_stream_player)
+ new_stream_player.start(current_frame, tag, audio_tags[tag.name])
+
+
+# manages if a music should be played or not (i-e checks if we entered an audio tag)
+func manage_next_frame_music():
+ var animation_tags: Array = ExtensionsApi.project.current_project.animation_tags
+ var current_frame: int = ExtensionsApi.project.current_project.current_frame
+ for tag in animation_tags:
+ if current_frame + 1 == tag.from:
+ if tag.name in audio_tags.keys() and current_playing_frame != -1:
+ var new_stream_player = stream_player.instantiate()
+ audios.add_child(new_stream_player)
+ new_stream_player.start(current_frame, tag, audio_tags[tag.name])
+ is_playing = true
+ if !is_playing and current_playing_frame != -1:
+ manage_start_stop(true)
+ current_playing_frame = current_frame
+
+
+# Detects if user clicked on another frame during playing
+func check_sanity():
+ var real_current_frame: int = ExtensionsApi.project.current_project.current_frame
+ var expected_current_frame: int = current_playing_frame + 1
+ if real_current_frame != expected_current_frame:
+ manage_start_stop(false)
+ if (
+ real_current_frame > expected_current_frame
+ and current_playing_frame != -1
+ ):
+ manage_start_stop(true)
+
+
+# An Audio driver has been chosen
+func _on_OptionButton_item_selected(index: int) -> void:
+ ProjectSettings.set_initial_value("audio/driver/driver", "Dummy")
+ var driver_name = get_real_driver_name(option_button.get_item_text(index))
+ ProjectSettings.set_setting("audio/driver/driver", driver_name)
+ ProjectSettings.save_custom(ExtensionsApi.general.get_global().OVERRIDE_FILE)
+ ExtensionsApi.dialog.show_error("Please restart to allow changes to take effect")
+
+
+func setup_audio_drivers():
+ var current_audio_driver = ProjectSettings.get_setting("audio/driver/driver")
+ var to_select: int
+ # (commenting this till this gets resolved)
+ #for i in OS.get_audio_driver_count():
+ #var driver_name = OS.get_audio_driver_name(i)
+ #option_button.add_item(OS.get_audio_driver_name(i))
+ #if driver_name == current_audio_driver:
+ #to_select = i
+ var drivers = ["Dummy (No Audio)", "System Default"]
+ for i in drivers.size():
+ var driver_name = drivers[i]
+ option_button.add_item(drivers[i])
+ if get_real_driver_name(driver_name) == current_audio_driver:
+ to_select = i
+ if to_select:
+ option_button.select(to_select)
+
+
+## Workarround for missing OS.get_audio_driver_count() and OS.get_audio_driver_name()
+func get_real_driver_name(d_name) -> String:
+ match d_name:
+ "Dummy (No Audio)":
+ return "Dummy"
+ "System Default":
+ return ""
+ return ""
+
+
+func _on_Load_pressed() -> void:
+ $Popups/FileDialog.popup_centered()
+
+
+func _on_window_close_requested() -> void:
+ get_parent().visible = false
diff --git a/src/Extensions/Audia/Elements/MusicListContainer.tscn b/src/Extensions/Audia/Elements/MusicListContainer.tscn
new file mode 100644
index 0000000..c020419
--- /dev/null
+++ b/src/Extensions/Audia/Elements/MusicListContainer.tscn
@@ -0,0 +1,75 @@
+[gd_scene load_steps=2 format=3 uid="uid://c82pwekjvftdi"]
+
+[ext_resource type="Script" path="res://src/Extensions/Audia/Elements/MusicListContainer.gd" id="1"]
+
+[node name="Window" type="Window"]
+position = Vector2i(0, 36)
+size = Vector2i(435, 385)
+visible = false
+exclusive = true
+
+[node name="MusicListContainer" type="PanelContainer" parent="."]
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+script = ExtResource("1")
+
+[node name="VBoxContainer" type="VBoxContainer" parent="MusicListContainer"]
+layout_mode = 2
+
+[node name="Label" type="Label" parent="MusicListContainer/VBoxContainer"]
+layout_mode = 2
+theme_type_variation = &"HeaderSmall"
+text = "Audio Driver:"
+
+[node name="OptionButton" type="OptionButton" parent="MusicListContainer/VBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+
+[node name="Label2" type="RichTextLabel" parent="MusicListContainer/VBoxContainer"]
+self_modulate = Color(0.8, 0.8, 0.8, 1)
+layout_mode = 2
+text = "Accepts: mp3, ogg, wav
+Usage: drag and drop. Set identifier to any tag's name."
+fit_content = true
+scroll_active = false
+
+[node name="Label3" type="Label" parent="MusicListContainer/VBoxContainer"]
+layout_mode = 2
+theme_type_variation = &"HeaderSmall"
+text = "Music Library:"
+
+[node name="ScrollContainer" type="ScrollContainer" parent="MusicListContainer/VBoxContainer"]
+layout_mode = 2
+size_flags_vertical = 3
+
+[node name="List" type="VBoxContainer" parent="MusicListContainer/VBoxContainer/ScrollContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+theme_override_constants/separation = 6
+
+[node name="Load" type="Button" parent="MusicListContainer/VBoxContainer"]
+layout_mode = 2
+text = "Open Audios"
+
+[node name="Popups" type="Control" parent="MusicListContainer"]
+layout_mode = 2
+mouse_filter = 2
+
+[node name="FileDialog" type="FileDialog" parent="MusicListContainer/Popups"]
+mode = 1
+title = "Open File(s)"
+size = Vector2i(651, 414)
+ok_button_text = "Open"
+file_mode = 1
+access = 2
+filters = PackedStringArray("*.mp3", "*.ogg", "*.wav")
+
+[node name="Audios" type="Node" parent="MusicListContainer"]
+
+[connection signal="close_requested" from="." to="MusicListContainer" method="_on_window_close_requested"]
+[connection signal="item_selected" from="MusicListContainer/VBoxContainer/OptionButton" to="MusicListContainer" method="_on_OptionButton_item_selected"]
+[connection signal="pressed" from="MusicListContainer/VBoxContainer/Load" to="MusicListContainer" method="_on_Load_pressed"]
+[connection signal="files_selected" from="MusicListContainer/Popups/FileDialog" to="MusicListContainer" method="add_audios"]
diff --git a/src/Extensions/Audia/Main.gd b/src/Extensions/Audia/Main.gd
new file mode 100644
index 0000000..f17eff1
--- /dev/null
+++ b/src/Extensions/Audia/Main.gd
@@ -0,0 +1,88 @@
+extends Node
+
+# some references to nodes that will be created later
+var music_list_container_dialog: Window
+var exporter_id: int
+var menu_id: int
+
+# This script acts as a setup for the extension
+func _enter_tree() -> void:
+ # add a test panel as a tab (this is an example) the tab is located at the same
+ # place as the (Tools tab) by default
+ music_list_container_dialog = preload(
+ "res://src/Extensions/Audia/Elements/MusicListContainer.tscn"
+ ).instantiate()
+
+# ExtensionsApi.panel.add_node_as_tab(music_list_container_dialog)
+ ExtensionsApi.dialog.get_dialogs_parent_node().add_child(music_list_container_dialog)
+ menu_id = ExtensionsApi.menu.add_menu_item(ExtensionsApi.menu.WINDOW, "Audia", self)
+
+ var info := {
+ "extension": ".png",
+ "description": "Shotcut"
+ }
+ var export_tab := ExtensionsApi.export.ExportTab.IMAGE
+ exporter_id = ExtensionsApi.export.add_export_option(info, self, export_tab, false)
+
+
+func menu_item_clicked():
+ music_list_container_dialog.popup_centered()
+
+
+func override_export(data: Dictionary):
+ # set variables
+ var dir_path: String
+ var project = data["project"]
+ var project_maker = load("res://src/Extensions/Audia/Classes/ShotcutMaker.gd").new()
+ var moved_audios := []
+ var p_size: Vector2
+
+ # obtaining audios used in project
+ var audio_tags := {}
+ for child in music_list_container_dialog.get_node("MusicListContainer").list.get_children():
+ var dict = child.serialize()
+ var tag = dict["identifier"]
+ var path = dict["path"]
+ if !tag in audio_tags.keys():
+ audio_tags[tag] = path
+
+ # save pngs
+ for image_idx in data["processed_images"].size():
+ var save_path = data["export_paths"][image_idx]
+ var image: Image = data["processed_images"][image_idx].image
+ var duration = data["processed_images"][image_idx].duration
+ image.save_png(save_path)
+ # set the dir path and project size (one time setup)
+ if !dir_path:
+ dir_path = save_path.replace(save_path.get_file(), "")
+ p_size = image.get_size()
+
+ # check if audio is to be played for this frame
+ for tag in project.animation_tags:
+ if tag.name in audio_tags.keys() and tag.from == image_idx + 1: # Audio Detected
+ var audio_path: String = audio_tags[tag.name]
+ var new_name = str(tag.name, ".", audio_path.get_extension())
+ var new_path: String = dir_path.path_join(new_name)
+ # calculate duration for audio
+ var end_time := 0.0
+ for frame_idx in range(tag.from - 1, tag.to):
+ var frame: Frame = project.frames[frame_idx]
+ var audio_duration = frame.duration * (1.0 / project.fps)
+ end_time += audio_duration
+ # if audio wasn't moved to aseet folder yet then move it there
+ if !new_path in moved_audios:
+ DirAccess.copy_absolute(audio_path, new_path)
+ moved_audios.append(new_path)
+ project_maker.add_item_to_playlist(new_path, end_time)
+ project_maker.add_item_to_playlist(data["export_paths"][image_idx], duration)
+ # Now Compile all this information into a ShotCut project
+ project_maker.compile(p_size)
+ return true
+
+
+func _exit_tree() -> void: # Extension is being uninstalled or disabled
+ # remember to remove things that you added using this extension
+# ExtensionsApi.panel.remove_node_from_tab(music_list_container_dialog)
+ ExtensionsApi.menu.remove_menu_item(ExtensionsApi.menu.WINDOW, menu_id)
+ music_list_container_dialog.queue_free()
+ ExtensionsApi.export.remove_export_option(exporter_id)
diff --git a/src/Extensions/Audia/Main.tscn b/src/Extensions/Audia/Main.tscn
new file mode 100644
index 0000000..469d837
--- /dev/null
+++ b/src/Extensions/Audia/Main.tscn
@@ -0,0 +1,6 @@
+[gd_scene load_steps=2 format=2]
+
+[ext_resource path="res://src/Extensions/Audia/Main.gd" type="Script" id=1]
+
+[node name="Main" type="Node"]
+script = ExtResource( 1 )
diff --git a/src/Extensions/Audia/extension.json b/src/Extensions/Audia/extension.json
new file mode 100644
index 0000000..2b61cef
--- /dev/null
+++ b/src/Extensions/Audia/extension.json
@@ -0,0 +1,10 @@
+{
+"author": "Variable",
+"description": "Implements Audio reference",
+"display_name": "Audia",
+"license": "MIT",
+"name": "Audia",
+"nodes": [ "Main.tscn" ],
+"supported_api_versions": [ 4 ],
+"version": "1.0"
+}
diff --git a/src/Extensions/Audia/music_icon.png b/src/Extensions/Audia/music_icon.png
new file mode 100644
index 0000000..e26e4eb
Binary files /dev/null and b/src/Extensions/Audia/music_icon.png differ
diff --git a/src/Extensions/Audia/music_icon.png.import b/src/Extensions/Audia/music_icon.png.import
new file mode 100644
index 0000000..07c6f51
--- /dev/null
+++ b/src/Extensions/Audia/music_icon.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bjhrjnoyri24c"
+path="res://.godot/imported/music_icon.png-577b83091e6209b57164a59506d3eb9a.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://src/Extensions/Audia/music_icon.png"
+dest_files=["res://.godot/imported/music_icon.png-577b83091e6209b57164a59506d3eb9a.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1