Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add audio source #59

Merged
merged 5 commits into from
Oct 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion godot/.godot/global_script_class_cache.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ list=Array[Dictionary]([{
"path": "res://src/logic/content_manager.gd"
}, {
"base": &"MeshInstance3D",
"class": &"DCLMeshRenderer",
"class": &"DclMeshRenderer",
"icon": "",
"language": &"GDScript",
"path": "res://src/decentraland_components/mesh_renderer.gd"
Expand Down
80 changes: 80 additions & 0 deletions godot/src/decentraland_components/audio_source.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
extends DclAudioSource

var last_loaded_audio_clip := ""
var audio_clip_file_hash := ""
var valid := false


func apply_audio_props(action_on_playing: bool):
if not valid:
return

self.pitch_scale = dcl_pitch

if not dcl_enable:
self.volume_db = -80
else:
# TODO: Check if it should be 10 instead 20 to talk in terms of power
self.volume_db = 20 * log(dcl_volume)
# -80 = 20 log 0.00001, so muted is when (volume <= 0.00001)

if action_on_playing:
if self.playing and not dcl_playing:
self.stop()
elif not self.playing and dcl_playing:
self.play()


func _refresh_data():
dcl_audio_clip_url = dcl_audio_clip_url.to_lower()

if last_loaded_audio_clip == dcl_audio_clip_url:
apply_audio_props(true)
else:
var content_mapping = Global.scene_runner.get_scene_content_mapping(dcl_scene_id)

last_loaded_audio_clip = dcl_audio_clip_url
valid = false

audio_clip_file_hash = content_mapping.get("content", {}).get(last_loaded_audio_clip, "")
if audio_clip_file_hash.is_empty():
# TODO: log file not found
return

var fetching_resource = Global.content_manager.fetch_audio(
last_loaded_audio_clip, content_mapping
)
if not fetching_resource:
self._on_audio_loaded.call_deferred(audio_clip_file_hash)
else:
Global.content_manager.content_loading_finished.connect(
self._content_manager_resource_loaded
)


func _content_manager_resource_loaded(resource_hash: String):
# Global.content_manager.content_loading_finished.disconnect(
# self._content_manager_resource_loaded
# )
_on_audio_loaded(resource_hash)


func _on_audio_loaded(file_hash: String):
if file_hash != audio_clip_file_hash:
return

var audio_stream = Global.content_manager.get_resource_from_hash(file_hash)
if audio_stream == null:
self.stop()
self.stream = null
return

self.stream = audio_stream
valid = true

apply_audio_props(true)


func _on_finished():
if dcl_loop_activated:
play()
8 changes: 8 additions & 0 deletions godot/src/decentraland_components/audio_source.tscn
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[gd_scene load_steps=2 format=3 uid="uid://baayc6o6lb8u"]

[ext_resource type="Script" path="res://src/decentraland_components/audio_source.gd" id="1_hwkbw"]

[node name="audio_source" type="DclAudioSource"]
script = ExtResource("1_hwkbw")

[connection signal="finished" from="." to="." method="_on_finished"]
4 changes: 0 additions & 4 deletions godot/src/decentraland_components/audio_streaming.gd
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,7 @@ func stream_buffer(data: PackedVector2Array):
self.get_stream_playback().push_buffer(data)


# print(data.length())


func init(frame_rate, frames, length, format, bit_rate, frame_size, channels):
pass
print(
"audio_streaming debug init ",
frame_rate,
Expand Down
2 changes: 1 addition & 1 deletion godot/src/decentraland_components/avatar_attach.gd
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func _enter_tree():
_set_children_physics(self.get_parent(), false)


func _process(delta):
func _process(_delta):
var p: Node3D = get_parent()
if p == null:
return
Expand Down
15 changes: 10 additions & 5 deletions godot/src/decentraland_components/gltf_container.gd
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ func load_gltf():
if self.file_hash.is_empty():
gltf_state = GodotGltfState.NotFound
return

var fetching_resource = Global.content_manager.fetch_gltf(dcl_gltf_src, content_mapping)

# TODO: should we set a timeout?
Expand All @@ -42,18 +41,24 @@ func load_gltf():
if not fetching_resource:
self._on_gltf_loaded.call_deferred(self.file_hash)
else:
Global.content_manager.content_loading_finished.connect(self._on_gltf_loaded)
Global.content_manager.content_loading_finished.connect(
self._content_manager_resource_loaded
)


func _content_manager_resource_loaded(resource_hash: String):
Global.content_manager.content_loading_finished.disconnect(self._on_gltf_loaded)
_on_gltf_loaded(resource_hash)
_on_gltf_loaded(resource_hash, true)


func _on_gltf_loaded(resource_hash: String):
func _on_gltf_loaded(resource_hash: String, from_signal: bool = false):
if resource_hash != file_hash:
return

if from_signal:
Global.content_manager.content_loading_finished.disconnect(
self._content_manager_resource_loaded
)

var node = Global.content_manager.get_resource_from_hash(file_hash)
if node == null:
gltf_state = GodotGltfState.FinishedWithError
Expand Down
15 changes: 6 additions & 9 deletions godot/src/decentraland_components/mesh_renderer.gd
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
extends MeshInstance3D
class_name DCLMeshRenderer
class_name DclMeshRenderer

enum MeshRendererPrimitiveType { NONE, MRPT_BOX, MRPT_CYLINDER, MRPT_SPHERE, MRPT_PLANE }

var current_type: MeshRendererPrimitiveType = MeshRendererPrimitiveType.NONE


func _ready():
_init_primitive_shapes()
DclMeshRenderer._init_primitive_shapes()


func set_box(uvs):
Expand All @@ -17,7 +17,7 @@ func set_box(uvs):
else:
self.mesh.clear_surfaces()

var data_array = get_cube_arrays()
var data_array = DclMeshRenderer.get_cube_arrays()
var N = min(floor(uvs.size() / 2), 8)
for i in range(N):
data_array[4][i] = Vector2(uvs[i * 2], -uvs[i * 2 + 1])
Expand All @@ -32,8 +32,8 @@ func set_plane(uvs: Array):
else:
self.mesh.clear_surfaces()

var data_array = get_plane_arrays()
var N = min(floor(uvs.size() / 2), 8)
var data_array = DclMeshRenderer.get_plane_arrays()
var N = min(floor(float(uvs.size()) / 2.0), 8)
for i in range(N):
data_array[4][i] = Vector2(uvs[i * 2], -uvs[i * 2 + 1])

Expand All @@ -55,7 +55,7 @@ func set_cylinder(top_radius: float, bottom_radius: float):
else:
self.mesh.clear_surfaces()

var data_array = build_cylinder_arrays(top_radius, bottom_radius)
var data_array = DclMeshRenderer.build_cylinder_arrays(top_radius, bottom_radius)
self.mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, data_array)


Expand Down Expand Up @@ -320,16 +320,13 @@ static func build_cube_arrays() -> Array:

static func build_cylinder_arrays(radius_top: float, radius_bottom: float) -> Array:
var uvs = PackedVector2Array()
var uvs2 = PackedVector2Array()
var vertices = PackedVector3Array()
var normals = PackedVector3Array()
var triangles = PackedInt32Array()
var start: Vector3
var num_vertices = 50
var length = 1.0
var offset_pos = Vector3(0, -0.5, 0)
var num_vertices_plus_one = num_vertices + 1
var offset = 0

vertices.resize(2 * num_vertices_plus_one + (num_vertices + 1) + (num_vertices + 1))
normals.resize(2 * num_vertices_plus_one + (num_vertices + 1) + (num_vertices + 1))
Expand Down
2 changes: 1 addition & 1 deletion godot/src/global.gd
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func _ready():
# TODO: enable raycast debugger
add_child(raycast_debugger)

DCLMeshRenderer._init_primitive_shapes()
DclMeshRenderer._init_primitive_shapes()


func add_raycast(_id: int, _time: float, _from: Vector3, _to: Vector3) -> void:
Expand Down
104 changes: 103 additions & 1 deletion godot/src/logic/content_manager.gd
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ enum ContentType {
CT_TEXTURE = 2,
CT_WEARABLE_EMOTE = 3,
CT_MESHES_MATERIAL = 4,
CT_INSTACE_GLTF = 5
CT_INSTACE_GLTF = 5,
CT_AUDIO = 6
}

var loading_content: Array[Dictionary] = []
Expand Down Expand Up @@ -180,6 +181,29 @@ func fetch_texture_by_hash(file_hash: String, content_mapping: Dictionary):
return true


func fetch_audio(file_path: String, content_mapping: Dictionary):
var file_hash: String = content_mapping.get("content", {}).get(file_path, "")
var content_cached = content_cache_map.get(file_hash)
if content_cached != null:
return not content_cached.get("loaded")

content_cache_map[file_hash] = {
"loaded": false,
}

pending_content.push_back(
{
"file_path": file_path,
"file_hash": file_hash,
"content_type": ContentType.CT_AUDIO,
"content_mapping": content_mapping,
"stage": 0
}
)

return true


func _process(_dt: float) -> void:
_th_poll()

Expand Down Expand Up @@ -211,6 +235,10 @@ func _th_poll():
if not _process_loading_texture(content, _th_finished_downloads):
_th_to_delete.push_back(content)

ContentType.CT_AUDIO:
if not _process_loading_audio(content, _th_finished_downloads):
_th_to_delete.push_back(content)

ContentType.CT_WEARABLE_EMOTE:
if not _process_loading_wearable(content, _th_finished_downloads):
_th_to_delete.push_back(content)
Expand Down Expand Up @@ -526,6 +554,80 @@ func _process_loading_texture(
return true


func _process_loading_audio(
content: Dictionary, finished_downloads: Array[RequestResponse]
) -> bool:
var content_mapping = content.get("content_mapping")
var file_hash: String = content.get("file_hash")
var base_url: String = content_mapping.get("base_url", "")
var local_audio_path = "user://content/" + file_hash
var stage = content.get("stage", 0)

match stage:
# Stage 0 => request png file
0:
if FileAccess.file_exists(local_audio_path):
content["stage"] = 2
else:
if file_hash.is_empty() or base_url.is_empty():
printerr("hash or base_url is empty")
return false

var absolute_file_path = local_audio_path.replace("user:/", OS.get_user_data_dir())
content["stage"] = 1
content["request_id"] = http_requester.request_file(
0, base_url + file_hash, absolute_file_path
)

# Stage 1 => wait for the file
1:
for item in finished_downloads:
if item.id() == content["request_id"]:
if item.is_error():
printerr("audio download is_error() == true!")
return false
else:
content["stage"] = 2

# Stage 2 => process texture
2:
var file := FileAccess.open(local_audio_path, FileAccess.READ)
if file == null:
printerr("audio download fails")
return false

var file_path: String = content.get("file_path")
var bytes = file.get_buffer(file.get_length())
var audio_stream = null

if file_path.ends_with(".wav"):
audio_stream = AudioStreamWAV.new()
audio_stream.data = bytes
elif file_path.ends_with(".ogg"):
audio_stream = AudioStreamOggVorbis.new()
audio_stream.data = bytes
elif file_path.ends_with(".mp3"):
audio_stream = AudioStreamMP3.new()
audio_stream.data = bytes

if audio_stream == null:
printerr(
"Audio " + base_url + file_hash + " unrecognized format (infered by file path)"
)
return false

content_cache_map[file_hash]["resource"] = audio_stream
content_cache_map[file_hash]["loaded"] = true
content_cache_map[file_hash]["stage"] = 3
self.emit_signal.call_deferred("content_loading_finished", file_hash)
return false
_:
printerr("unknown stage ", file_hash)
return false

return true


func split_animations(_gltf_node: Node) -> void:
pass

Expand Down
1 change: 1 addition & 0 deletions godot/src/logic/player/player.tscn
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ height = 1.5
length = 1.0779

[node name="Player" type="CharacterBody3D"]
collision_layer = 0
script = ExtResource("1_5bfm2")

[node name="CollisionShape3D_Body" type="CollisionShape3D" parent="."]
Expand Down
Loading