diff --git a/.gitignore b/.gitignore index 0af181c..49f1fe7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ # Godot 4+ specific ignores .godot/ /android/ + +**/*.xm +**/*.it diff --git a/Main.tscn b/Main.tscn index 70e1206..3d5097e 100644 --- a/Main.tscn +++ b/Main.tscn @@ -1,10 +1,20 @@ [gd_scene format=3 uid="uid://crgeyg1m54cg8"] +[ext_resource type="Script" uid="uid://dtqv683r772sd" path="res://player.gd" id="1_3dxm6"] +[ext_resource type="Texture2D" uid="uid://cqisk1m0in4a4" path="res://shield.png" id="1_03owx"] [ext_resource type="Texture2D" uid="uid://dgkao0lkjfn8x" path="res://gun-handheld-reduced.png" id="1_glv2v"] [ext_resource type="Texture2D" uid="uid://de7h5yetxbjii" path="res://gui-reduced.png" id="2_uu6xs"] [ext_resource type="Script" uid="uid://biacb353kq32o" path="res://level_mesh.gd" id="3_cm0pq"] [ext_resource type="Texture2D" uid="uid://bj63s2jg8oinu" path="res://rock22.jpg" id="4_fos0i"] [ext_resource type="Texture2D" uid="uid://cbfvjlda1hapx" path="res://metall001-new-tileable2.png" id="5_fos0i"] +[ext_resource type="Script" uid="uid://djgd6mugw2s47" path="res://godot-mod-player-g4-master/addons/modplayer/ModPlayer.gd" id="7_lgr22"] +[ext_resource type="Script" uid="uid://cc2rv3wnwas52" path="res://wall_segment.gd" id="7_trn2v"] + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_03owx"] +shading_mode = 2 +albedo_color = Color(0.1415951, 0.43394297, 0.68819517, 1) + +[sub_resource type="PrismMesh" id="PrismMesh_1065d"] [sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_lgr22"] sky_top_color = Color(0.15010819, 0.09080864, 3.6098065e-08, 1) @@ -25,7 +35,6 @@ sky = SubResource("Sky_3dxm6") ambient_light_source = 3 [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_cm0pq"] -cull_mode = 1 shading_mode = 2 specular_mode = 2 albedo_texture = ExtResource("4_fos0i") @@ -34,50 +43,86 @@ fov_override = 33.9 distance_fade_mode = 1 distance_fade_max_distance = 2.0 -[sub_resource type="ImmediateMesh" id="ImmediateMesh_fos0i"] +[sub_resource type="ImmediateMesh" id="ImmediateMesh_03owx"] [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_fos0i"] -shading_mode = 2 specular_mode = 2 albedo_texture = ExtResource("5_fos0i") -texture_filter = 2 +texture_filter = 4 fov_override = 33.9 distance_fade_mode = 2 distance_fade_max_distance = 1.0 -[sub_resource type="ImmediateMesh" id="ImmediateMesh_3dxm6"] +[sub_resource type="ImmediateMesh" id="ImmediateMesh_wkp8b"] + +[sub_resource type="ConcavePolygonShape3D" id="ConcavePolygonShape3D_20pc6"] +data = PackedVector3Array(1.9799999, 0, 0.57, 0.7, 0, 0.89, 1.916, 2.7717, 0.31399998, 0.7, 0, 0.89, 0.188, 1.6502, 1.082, 1.916, 2.7717, 0.31399998, 1.916, 2.7717, 0.31399998, 0.188, 1.6502, 1.082, 1.9799999, 5, 0.57, 0.188, 1.6502, 1.082, 0.7, 5, 0.89, 1.9799999, 5, 0.57, 0.7, 0, 0.89, 1.66, 0, 3.4499998, 0.188, 1.6502, 1.082, 1.66, 0, 3.4499998, 1.3399999, 1.4045, 3.7059999, 0.188, 1.6502, 1.082, 0.188, 1.6502, 1.082, 1.3399999, 1.4045, 3.7059999, 0.7, 5, 0.89, 1.3399999, 1.4045, 3.7059999, 1.66, 5, 3.4499998, 0.7, 5, 0.89, 1.66, 0, 3.4499998, 2.9399998, 0, 5.0499997, 1.3399999, 1.4045, 3.7059999, 2.9399998, 0, 5.0499997, 2.958, 1.2507, 5.694, 1.3399999, 1.4045, 3.7059999, 1.3399999, 1.4045, 3.7059999, 2.958, 1.2507, 5.694, 1.66, 5, 3.4499998, 2.958, 1.2507, 5.694, 2.9399998, 5, 5.0499997, 1.66, 5, 3.4499998, 2.9399998, 0, 5.0499997, 6.16, 0, 4.96, 2.958, 1.2507, 5.694, 6.16, 0, 4.96, 5.118, 1.7171999, 5.148, 2.958, 1.2507, 5.694, 2.958, 1.2507, 5.694, 5.118, 1.7171999, 5.148, 2.9399998, 5, 5.0499997, 5.118, 1.7171999, 5.148, 6.16, 5, 4.96, 2.9399998, 5, 5.0499997, 6.16, 0, 4.96, 7.1, 0, 10.17, 5.118, 1.7171999, 5.148, 7.1, 0, 10.17, 6.5239997, 1.3377, 9.658, 5.118, 1.7171999, 5.148, 5.118, 1.7171999, 5.148, 6.5239997, 1.3377, 9.658, 6.16, 5, 4.96, 6.5239997, 1.3377, 9.658, 7.1, 5, 10.17, 6.16, 5, 4.96, 7.1, 0, 10.17, 4.54, 0, 13.049999, 6.5239997, 1.3377, 9.658, 4.54, 0, 13.049999, 3.964, 2.7186, 13.497999, 6.5239997, 1.3377, 9.658, 6.5239997, 1.3377, 9.658, 3.964, 2.7186, 13.497999, 7.1, 5, 10.17, 3.964, 2.7186, 13.497999, 4.54, 5, 13.049999, 7.1, 5, 10.17, 4.54, 0, 13.049999, 6.7799997, 0, 15.929999, 3.964, 2.7186, 13.497999, 6.7799997, 0, 15.929999, 6.908, 1.9440999, 16.633999, 3.964, 2.7186, 13.497999, 3.964, 2.7186, 13.497999, 6.908, 1.9440999, 16.633999, 4.54, 5, 13.049999, 6.908, 1.9440999, 16.633999, 6.7799997, 5, 15.929999, 4.54, 5, 13.049999, 6.7799997, 0, 15.929999, 10.3, 0, 15.29, 6.908, 1.9440999, 16.633999, 10.3, 0, 15.29, 11.068, 2.4175, 15.481999, 6.908, 1.9440999, 16.633999, 6.908, 1.9440999, 16.633999, 11.068, 2.4175, 15.481999, 6.7799997, 5, 15.929999, 11.068, 2.4175, 15.481999, 10.3, 5, 15.29, 6.7799997, 5, 15.929999, 10.3, 0, 15.29, 11.259999, 0, 11.45, 11.068, 2.4175, 15.481999, 11.259999, 0, 11.45, 12.334, 2.0367, 11.054, 11.068, 2.4175, 15.481999, 11.068, 2.4175, 15.481999, 12.334, 2.0367, 11.054, 10.3, 5, 15.29, 12.334, 2.0367, 11.054, 11.259999, 5, 11.45, 10.3, 5, 15.29, 11.259999, 0, 11.45, 9.28, 0, 6.08, 12.334, 2.0367, 11.054, 9.28, 0, 6.08, 8.846, 1.1595, 7.3719997, 12.334, 2.0367, 11.054, 12.334, 2.0367, 11.054, 8.846, 1.1595, 7.3719997, 11.259999, 5, 11.45, 8.846, 1.1595, 7.3719997, 9.28, 5, 6.08, 11.259999, 5, 11.45, 9.28, 0, 6.08, 15.74, 0, 8.25, 8.846, 1.1595, 7.3719997, 15.74, 0, 8.25, 16.123999, 1.4636999, 8.506, 8.846, 1.1595, 7.3719997, 8.846, 1.1595, 7.3719997, 16.123999, 1.4636999, 8.506, 9.28, 5, 6.08, 16.123999, 1.4636999, 8.506, 15.74, 5, 8.25, 9.28, 5, 6.08, 15.74, 0, 8.25, 17.02, 0, 6.33, 16.123999, 1.4636999, 8.506, 17.02, 0, 6.33, 17.821999, 1.3002, 5.534, 16.123999, 1.4636999, 8.506, 16.123999, 1.4636999, 8.506, 17.821999, 1.3002, 5.534, 15.74, 5, 8.25, 17.821999, 1.3002, 5.534, 17.02, 5, 6.33, 15.74, 5, 8.25, 17.02, 0, 6.33, 13.04, 0, 2.32, 17.821999, 1.3002, 5.534, 13.04, 0, 2.32, 13.134, 2.981, 1.388, 17.821999, 1.3002, 5.534, 17.821999, 1.3002, 5.534, 13.134, 2.981, 1.388, 17.02, 5, 6.33, 13.134, 2.981, 1.388, 13.04, 5, 2.32, 17.02, 5, 6.33, 13.04, 0, 2.32, 8.38, 0, 1.8499999, 13.134, 2.981, 1.388, 8.38, 0, 1.8499999, 8.254, 2.2742, 1.214, 13.134, 2.981, 1.388, 13.134, 2.981, 1.388, 8.254, 2.2742, 1.214, 13.04, 5, 2.32, 8.254, 2.2742, 1.214, 8.38, 5, 1.8499999, 13.04, 5, 2.32, 8.38, 0, 1.8499999, 5.2, 0, 2.48, 8.254, 2.2742, 1.214, 5.2, 0, 2.48, 5.454, 2.3258998, 2.22, 8.254, 2.2742, 1.214, 8.254, 2.2742, 1.214, 5.454, 2.3258998, 2.22, 8.38, 5, 1.8499999, 5.454, 2.3258998, 2.22, 5.2, 5, 2.48, 8.38, 5, 1.8499999, 5.2, 0, 2.48, 3.8999999, 0, 1.2099999, 5.454, 2.3258998, 2.22, 3.8999999, 0, 1.2099999, 4.028, 2.1676, 0.826, 5.454, 2.3258998, 2.22, 5.454, 2.3258998, 2.22, 4.028, 2.1676, 0.826, 5.2, 5, 2.48, 4.028, 2.1676, 0.826, 3.8999999, 5, 1.2099999, 5.2, 5, 2.48, 3.8999999, 0, 1.2099999, 1.9799999, 0, 0.57, 4.028, 2.1676, 0.826, 1.9799999, 0, 0.57, 1.916, 2.2347999, 0.31399998, 4.028, 2.1676, 0.826, 4.028, 2.1676, 0.826, 1.916, 2.2347999, 0.31399998, 3.8999999, 5, 1.2099999, 1.916, 2.2347999, 0.31399998, 1.9799999, 5, 0.57, 3.8999999, 5, 1.2099999) +backface_collision = true [node name="Node3D" type="Node3D" unique_id=1749101855] -[node name="Camera3D" type="Camera3D" parent="." unique_id=1552004731] +[node name="WallMark" type="Sprite3D" parent="." unique_id=1606442103] +pixel_size = 0.02 +texture = ExtResource("1_03owx") + +[node name="SidePoint" type="MeshInstance3D" parent="WallMark" unique_id=909669364] +transform = Transform3D(0.2, 0, 0, 0, -3.3225813e-08, -0.19999997, 0, 0.760118, -8.742277e-09, 0, 0, 0.5) +material_override = SubResource("StandardMaterial3D_03owx") +mesh = SubResource("PrismMesh_1065d") + +[node name="FloorMark" type="Sprite3D" parent="." unique_id=1733002043] +transform = Transform3D(1, 0, 0, 0, -4.371139e-08, -1, 0, 1, -4.371139e-08, 0, 0, 0) +pixel_size = 0.02 +texture = ExtResource("1_03owx") + +[node name="SidePoint" type="MeshInstance3D" parent="FloorMark" unique_id=2041287788] +transform = Transform3D(0.2, 0, 0, 0, -3.3225813e-08, -0.19999997, 0, 0.760118, -8.742277e-09, 0, 0, 0.5) +material_override = SubResource("StandardMaterial3D_03owx") +mesh = SubResource("PrismMesh_1065d") + +[node name="Player" type="Node3D" parent="." unique_id=1544606435 node_paths=PackedStringArray("_cam", "_ray")] +script = ExtResource("1_3dxm6") +_cam = NodePath("Look") +_ray = NodePath("Look/RayCast3D") + +[node name="Look" type="Camera3D" parent="Player" unique_id=1552004731] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1.1841735) -[node name="Sprite3D2" type="Sprite3D" parent="." unique_id=2128191748] -transform = Transform3D(0.75, 0, 0, 0, 0.75, 0, 0, 0, 0.75, 0, 0, 0) +[node name="Sprite3D2" type="Sprite3D" parent="Player/Look" unique_id=2128191748] +transform = Transform3D(0.75, 0, 0, 0, 0.75, 0, 0, 0, 0.75, 0, 0, -1.1841735) billboard = 1 texture_filter = 0 texture = ExtResource("2_uu6xs") -[node name="Sprite3D" type="Sprite3D" parent="." unique_id=328920978] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.47348556, -0.6719171, 0) +[node name="Sprite3D" type="Sprite3D" parent="Player/Look" unique_id=328920978] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.47348556, -0.6719171, -1.1841735) billboard = 1 texture_filter = 0 render_priority = 1 texture = ExtResource("1_glv2v") +[node name="RayCast3D" type="RayCast3D" parent="Player/Look" unique_id=987966127] +transform = Transform3D(-100, -1.509958e-05, -6.6002363e-13, 0, -4.371139e-06, 100, -1.509958e-05, 100, 4.371139e-06, 0, 0, 0) +collision_mask = 32774 +hit_from_inside = true +collide_with_areas = true +debug_shape_thickness = 1 + [node name="WorldEnvironment" type="WorldEnvironment" parent="." unique_id=2112481184] environment = SubResource("Environment_r0du0") [node name="AreaLight3D" type="AreaLight3D" parent="." unique_id=1499708949] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.8187913, 0.6215458, 0) +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.3378613, -0.5583314, -4.607884) +light_color = Color(0.9137892, 0.027400356, 0.120897986, 1) +light_energy = 16.0 [node name="LevelMesh" type="Polygon2D" parent="." unique_id=1028271676 node_paths=PackedStringArray("_floor_mesh", "_wall_mesh")] self_modulate = Color(1, 1, 1, 0) position = Vector2(-38, -25) color = Color(0.45490196, 0.45490196, 0.45490196, 0.6) -polygon = PackedVector2Array(198, 57, 70, 89, 166, 345, 294, 505, 550, 537, 710, 1017, 454, 1305, 678, 1593, 1030, 1529, 1130.7727, 1130.3756, 902, 505, 1574, 825, 1702, 633, 1192, 256, 838, 185, 518, 249, 390, 121) +polygon = PackedVector2Array(198, 57, 70, 89, 166, 345, 294, 505, 615.9999, 495.99997, 710, 1017, 454, 1305, 678, 1593, 1030, 1529, 1126, 1145, 927.9999, 607.99994, 1574, 825, 1702, 633, 1303.9999, 232, 838, 185, 520, 248, 390, 121) uv = PackedVector2Array(198, 57, 70, 89, 166, 345, 294, 505, 550, 537, 710, 1017, 454, 1305, 678, 1593, 1030, 1529, 934, 793, 902, 473, 1574, 825, 1702, 633, 1094, 313, 794.0979, 279.67758, 518, 249, 390, 121) -polygons = [PackedInt32Array(0, 1, 2), PackedInt32Array(5, 6, 7), PackedInt32Array(5, 7, 8), PackedInt32Array(5, 8, 9), PackedInt32Array(4, 5, 10), PackedInt32Array(5, 9, 10), PackedInt32Array(10, 11, 13), PackedInt32Array(11, 12, 13), PackedInt32Array(10, 13, 14), PackedInt32Array(4, 10, 14), PackedInt32Array(4, 14, 15), PackedInt32Array(2, 3, 15), PackedInt32Array(3, 4, 15), PackedInt32Array(0, 2, 16), PackedInt32Array(2, 15, 16)] +polygons = [PackedInt32Array(2, 1, 0), PackedInt32Array(7, 6, 5), PackedInt32Array(8, 7, 5), PackedInt32Array(9, 8, 5), PackedInt32Array(10, 5, 4), PackedInt32Array(10, 9, 5), PackedInt32Array(13, 11, 10), PackedInt32Array(13, 12, 11), PackedInt32Array(14, 10, 4), PackedInt32Array(14, 13, 10), PackedInt32Array(15, 14, 4), PackedInt32Array(15, 3, 2), PackedInt32Array(15, 4, 3), PackedInt32Array(16, 2, 0), PackedInt32Array(16, 15, 2)] script = ExtResource("3_cm0pq") _floor_mesh = NodePath("FloorMesh") _wall_mesh = NodePath("WallMesh") @@ -86,9 +131,26 @@ _scale = 0.01 [node name="FloorMesh" type="MeshInstance3D" parent="LevelMesh" unique_id=961663820] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -8.325726, -1.0841981, -12.307181) material_override = SubResource("StandardMaterial3D_cm0pq") -mesh = SubResource("ImmediateMesh_fos0i") +mesh = SubResource("ImmediateMesh_03owx") [node name="WallMesh" type="MeshInstance3D" parent="LevelMesh" unique_id=653175378] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -8.325726, -1.0841981, -12.307181) material_override = SubResource("StandardMaterial3D_fos0i") -mesh = SubResource("ImmediateMesh_3dxm6") +mesh = SubResource("ImmediateMesh_wkp8b") + +[node name="WallMesh_col" type="StaticBody3D" parent="LevelMesh/WallMesh" unique_id=141738477] + +[node name="CollisionShape3D" type="CollisionShape3D" parent="LevelMesh/WallMesh/WallMesh_col" unique_id=1446575305] +shape = SubResource("ConcavePolygonShape3D_20pc6") + +[node name="WallSegments" type="Node2D" parent="LevelMesh" unique_id=1390058976] + +[node name="WallSegment" type="Line2D" parent="LevelMesh/WallSegments" unique_id=2053155144] +points = PackedVector2Array(710, 1017, 390, 1369) +script = ExtResource("7_trn2v") + +[node name="ModPlayer" type="Node" parent="." unique_id=1374096575] +script = ExtResource("7_lgr22") +file = "res://mod211-headache.xm" +playing = true +metadata/_custom_type_script = "uid://djgd6mugw2s47" diff --git a/godot-mod-player-g4-master/addons/modplayer/GMP.gd b/godot-mod-player-g4-master/addons/modplayer/GMP.gd new file mode 100644 index 0000000..b669c80 --- /dev/null +++ b/godot-mod-player-g4-master/addons/modplayer/GMP.gd @@ -0,0 +1,12 @@ +## +## Godot Mod Player Plugin by arlez80 (Yui Kinomoto) +## + +@tool +extends EditorPlugin + +# Nothing to do, the main logic to expose this to the user +# is done via `class_name` in `ModPlayer.gd`. + +func _get_plugin_name(): + return "Godot Mod Player" diff --git a/godot-mod-player-g4-master/addons/modplayer/GMP.gd.uid b/godot-mod-player-g4-master/addons/modplayer/GMP.gd.uid new file mode 100644 index 0000000..b1d94cf --- /dev/null +++ b/godot-mod-player-g4-master/addons/modplayer/GMP.gd.uid @@ -0,0 +1 @@ +uid://bfmp165qm0qf diff --git a/godot-mod-player-g4-master/addons/modplayer/Mod.gd b/godot-mod-player-g4-master/addons/modplayer/Mod.gd new file mode 100644 index 0000000..65e0439 --- /dev/null +++ b/godot-mod-player-g4-master/addons/modplayer/Mod.gd @@ -0,0 +1,271 @@ +## +## MOD parser by あるる(きのもと 結衣) @arlez80 +## +## MIT License +## + +@warning_ignore_start("untyped_declaration") +@warning_ignore_start("unsafe_method_access") +@warning_ignore_start("unused_variable") + +class_name Mod + +const period_table:Array[int] = [ + 1712,1616,1525,1440,1357,1281,1209,1141,1077,1017, 961, 907, + 856, 808, 762, 720, 678, 640, 604, 570, 538, 508, 480, 453, + 428, 404, 381, 360, 339, 320, 302, 285, 269, 254, 240, 226, + 214, 202, 190, 180, 170, 160, 151, 143, 135, 127, 120, 113, + 107, 101, 95, 90, 85, 80, 76, 71, 67, 64, 60, 57, +] + +enum ModFlags { + LINEAR_FREQUENCY_TABLE = 1, +} + +enum ModWaveFormType { + SINE_WAVEFORM = 0, + SAW_WAVEFORM = 1, + SQUARE_WAVEFORM = 2, + RAMDOM_WAVEFORM = 3, + REV_SAW_WAVEFORM = 4, +} + +enum ModLoopType { + NO_LOOP = 0, + FORWARD_LOOP = 1, + PING_PONG_LOOP = 2, +} + +class ModData: + var module_name:String + var tracker_name:String + var version:int + var flags:int = 0 + + var song_length:int + var unknown_number:int + var channel_count:int + var song_positions:PackedByteArray + var magic:String + var patterns:Array # ModPattern[][] + var instruments:Array[ModInstrument] + + var init_tick:int = 6 + var init_bpm:int = 125 + var restart_position:int = 0 + +class ModPatternNote: + var key_number:int + var note:int + var instrument:int + var volume:int = 0 + var effect_command:int + var effect_param:int + +class ModInstrument: + var name:String + var samples:Array[ModSample] + var volume_envelope:ModEnvelope + var panning_envelope:ModEnvelope + var vibrato_type:ModWaveFormType = ModWaveFormType.SINE_WAVEFORM + var vibrato_speed:int = -1 + var vibrato_depth:int = -1 + var vibrato_depth_shift:int = -1 + var volume_fadeout:int = -1 + +class ModSample: + var data:PackedByteArray + var name:String + var length:int + var loop_start:int + var loop_length:int + var volume:int + var finetune:int + var loop_type:int = ModLoopType.FORWARD_LOOP # set(ModLoopType) + var bit:int = 8 # 8 or 16 + var panning:int = -1 + var relative_note:int = 0 + +class ModEnvelope: + var points:Array[ModEnvelopePoint] + var point_count:int + var sustain_point:int + var loop_start_point:int + var loop_end_point:int + + var enabled:bool + var sustain_enabled:bool + var loop_enabled:bool + + func set_flag( f:int ): + self.enabled = ( f & 1 ) != 0 + self.sustain_enabled = ( f & 2 ) != 0 + self.loop_enabled = ( f & 4 ) != 0 + +class ModEnvelopePoint: + var frame:int + var value:int + +class ModParseResult: + var error:int = OK + var data:ModData = null + + func _init(): + pass + +## ファイルから読み込み +## @param path ファイルパス +## @return 読んだ Modファイルデータ +func read_file( path:String ) -> ModParseResult: + var result: = ModParseResult.new( ) + + var f: = FileAccess.open( path, FileAccess.READ ) + if f.get_error( ) != OK: + result.error = f.get_error( ) + return result + + var stream:StreamPeerBuffer = StreamPeerBuffer.new( ) + stream.set_data_array( f.get_buffer( f.get_length( ) ) ) + stream.big_endian = true + + result.data = self._read( stream ) + return result + +## 配列から読み込み +## @param data データ +## @return ModData +func read_data( data:PackedByteArray ) -> ModParseResult: + var stream:StreamPeerBuffer = StreamPeerBuffer.new( ) + stream.set_data_array( data ) + stream.big_endian = true + + var result: = ModParseResult.new( ) + result.data = self._read( stream ) + return result + +## ストリームから読み込み +## @param stream ストリーム +## @return 読んだ Modファイルデータ +func _read( stream:StreamPeerBuffer ) -> ModData: + var mod: = ModData.new( ) + mod.module_name = self._read_string( stream, 20 ) + mod.tracker_name = "" + mod.instruments = self._read_instruments( stream ) + mod.song_length = stream.get_u8( ) + mod.unknown_number = stream.get_u8( ) + mod.song_positions = stream.get_partial_data( 128 )[1] + var max_song_position:int = 0 + for sp in mod.song_positions: + if max_song_position < sp: + max_song_position = sp + + mod.magic = self._read_string( stream, 4 ) + var channel_count:int = 4 + match mod.magic: + "6CHN": + channel_count = 6 + "FLT8", "8CHN", "CD81", "OKTA": + channel_count = 8 + "16CN": + channel_count = 16 + "32CN": + channel_count = 32 + _: + if mod.magic.substr( 2 ) == "CH": + channel_count = int( mod.magic ) + # print( "Unknown magic" ) + # breakpoint + pass + mod.channel_count = channel_count + + mod.patterns = self._read_patterns( stream, max_song_position + 1, channel_count ) + self._read_sample_data( stream, mod.instruments ) + + return mod + +## 楽器データを読み込む +## @param stream ストリーム +## @return 楽器データ +func _read_instruments( stream:StreamPeerBuffer ) -> Array[ModInstrument]: + var instruments:Array[ModInstrument] = [] + + for i in range( 31 ): + var sample: = ModSample.new( ) + sample.name = self._read_string( stream, 22 ) + sample.length = stream.get_u16( ) * 2 + var finetune:int = stream.get_u8( ) & 0x0F + if 0x08 < finetune: + finetune = finetune - 0x10 + sample.finetune = finetune * 16 + sample.volume = stream.get_u8( ) + sample.loop_start = stream.get_u16( ) * 2 + sample.loop_length = stream.get_u16( ) * 2 + if sample.loop_length < 8: + sample.loop_type = ModLoopType.NO_LOOP + else: + sample.loop_type = ModLoopType.FORWARD_LOOP + + var instrument: = ModInstrument.new( ) + instrument.name = sample.name + instrument.samples = [] + for k in range( 96 ): + instrument.samples.append( sample ) + instrument.volume_envelope = ModEnvelope.new( ) + instrument.volume_envelope.enabled = false + instrument.panning_envelope = ModEnvelope.new( ) + instrument.panning_envelope.enabled = false + + instruments.append( instrument ) + + return instruments + +## パターンを読み込む +## @param stream ストリームデータ +## @param max_positions 最大パターン数 +## @param channels 最大チャンネル数 +## @return パターンデータ +func _read_patterns( stream:StreamPeerBuffer, max_position:int, channels:int ) -> Array: # Array of Array of Array of ModPatternNote + var patterns:Array = [] # Array of Array of Array of ModPatternNote + + for position in ( max_position ): + var pattern:Array = [] # Array of Array of ModPatternNote + for i in range( 64 ): + var line:Array[ModPatternNote] = [] + for ch in ( channels ): + var v1:int = stream.get_u16( ) + var v2:int = stream.get_u16( ) + var mod_pattern_note: = ModPatternNote.new( ) + mod_pattern_note.instrument = ( ( v1 >> 8 ) & 0xF0 ) | ( ( v2 >> 12 ) & 0x0F ) + mod_pattern_note.key_number = v1 & 0x0FFF + mod_pattern_note.effect_command = ( v2 >> 8 ) & 0x0F + mod_pattern_note.effect_param = v2 & 0x0FF + # TODO: 遅かったら二分探索にでもしませんか + if 0 < mod_pattern_note.key_number: + mod_pattern_note.note = 23 + for k in period_table: + mod_pattern_note.note += 1 + if k <= mod_pattern_note.key_number: + break + else: + mod_pattern_note.note = 0 + line.append( mod_pattern_note ) + pattern.append( line ) + patterns.append( pattern ) + + return patterns + +## 楽器のデータに基づき、波形を読み込む +## @param stream ストリーム +## @param instruments 波形を読み込む楽器データリスト +func _read_sample_data( stream:StreamPeerBuffer, instruments:Array[ModInstrument] ) -> void: + for instrument in instruments: + var sample:Mod.ModSample = instrument.samples[0] + if 0 < sample.length: + sample.data = stream.get_partial_data( sample.length )[1] + +## 文字列の読み込み +## @param stream ストリーム +## @param size 文字列サイズ +## @return 読み込んだ文字列を返す +func _read_string( stream:StreamPeerBuffer, size:int ) -> String: + return stream.get_partial_data( size )[1].get_string_from_ascii( ) diff --git a/godot-mod-player-g4-master/addons/modplayer/Mod.gd.uid b/godot-mod-player-g4-master/addons/modplayer/Mod.gd.uid new file mode 100644 index 0000000..0aadd98 --- /dev/null +++ b/godot-mod-player-g4-master/addons/modplayer/Mod.gd.uid @@ -0,0 +1 @@ +uid://ii8p1hyx5nbe diff --git a/godot-mod-player-g4-master/addons/modplayer/ModPlayer.gd b/godot-mod-player-g4-master/addons/modplayer/ModPlayer.gd new file mode 100644 index 0000000..bb51389 --- /dev/null +++ b/godot-mod-player-g4-master/addons/modplayer/ModPlayer.gd @@ -0,0 +1,1065 @@ +## +## 100% pure GDScript software Mod Player [Godot Mod Player] +## by あるる(きのもと 結衣) @arlez80 +## +## MIT License +## + +@warning_ignore_start("untyped_declaration") +@warning_ignore_start("unsafe_method_access") +@warning_ignore_start("unused_variable") +@warning_ignore_start("unsafe_property_access") +@warning_ignore_start("shadowed_variable") +@warning_ignore_start("return_value_discarded") +@warning_ignore_start("unused_parameter") + +@icon("icon.png") +class_name ModPlayer + +extends Node + +# ------------------------------------------------------- +# 定数 +const mod_master_bus_name:String = "arlez80_GModP_MASTER_BUS" +const mod_channel_bus_name:String = "arlez80_GModP_CHANNEL_BUS%d" +const default_mix_rate:int = 4144 +const default_linear_mix_rate:int = 8363 +const chip_speed:float = 50.0 +const volume_table:Array[float] = [ + -144.0,-36.1,-30.1,-26.6,-24.1,-22.1,-20.6, + -19.2,-18.1,-17.0,-16.1,-15.3,-14.5,-13.8,-13.2, + -12.6,-12.0,-11.5,-11.0,-10.5,-10.1,-9.7,-9.3, + -8.9,-8.5,-8.2,-7.8,-7.5,-7.2,-6.9,-6.6,-6.3, + -6.0,-5.8,-5.5,-5.2,-5.0,-4.8,-4.5,-4.3,-4.1, + -3.9,-3.7,-3.5,-3.3,-3.1,-2.9,-2.7,-2.5,-2.3, + -2.1,-2.0,-1.8,-1.6,-1.5,-1.3,-1.2,-1.0,-0.9, + -0.7,-0.6,-0.4,-0.3,-0.1,0.0 +] + +# ----------------------------------------------------------------------------- +# Signals + +signal note_on( channel_number, note ) +signal looped + +# ----------------------------------------------------------------------------- +# Classes +class GodotModPlayerChannelAudioEffect: + var ae_panner:AudioEffectPanner = null + +class GodotModPlayerInstrument: + var source:Mod.ModInstrument + var array_asw:Array[AudioStreamWAV] + +class GodotModPlayerPitch: + const center_key_freq:float = 856.0 + + ## 現在値 + var value:int + ## 変化目標(ポルタメント用) + var dest:int + ## 変化量(ポルタメント用) + var speed:int + ## 最後の変化量(ポルタメント用) + var last_speed:int + ## ポルタメント量 + var porta_value:int + + ## アルペジオリスト + var arpeggio:Array[int] + ## アルペジオカウンタ + var arpeggio_count:int + ## アルペジオ有効 + var arpeggio_enabled:bool + + var linear_freq:bool = false + + func _init(): + self.value = 0 + self.dest = 0 + self.speed = 0 + self.last_speed = 0 + self.porta_value = 0 + + self.arpeggio = [0,0,0] + self.arpeggio_count = 0 + self.arpeggio_enabled = false + + func update( ): + if self.dest < self.value: + self.value -= self.speed + if self.value <= self.dest: + self.value = self.dest + self.speed = 0 + elif self.value < self.dest: + self.value += self.speed + if self.dest <= self.value: + self.value = self.dest + self.speed = 0 + + ## 現在のピッチスケールを得る + ## @param vibrato ビブラート値 + ## @return 計算したピッチスケール + func get_pitch_scale( vibrato:int = 0 ) -> float: + var v:int = self.value + + if self.arpeggio_enabled: + v += self.arpeggio[self.arpeggio_count] + self.arpeggio_count = ( self.arpeggio_count + 1 ) % 3 + + if self.linear_freq: + return pow( 2.0, ( ( 4608 - v ) / 768.0 ) - ( self.porta_value / 255.0 ) + ( vibrato / 255.0 / 12.0 * 5.0 ) ) + + v += vibrato + self.porta_value + if v == 0: + return 0.0 + + return center_key_freq / v + + ## ファインピッチスケールを取得 + ## @return 現在のファインピッチスケール + func get_fine_pitch_scale( ) -> float: + return pow( 2.0, ( self.value / 128.0 ) / 12.0 ) + +class GodotModPlayerEffect: + var type:int = 0 + var phase:int = 0 + var speed:int = 0 + var depth:int = 0 + var depth_shift:int = 0 + + var value:int = 0 + + ## 更新 + func update( ) -> void: + var v:int = 0 + match self.type: + Mod.ModWaveFormType.SINE_WAVEFORM: # 正弦波 + # TODO: そのうちPackedByteArrayのtableにでもしておく + v = int( sin( self.phase * ( PI / 32.0 ) ) * 255.0 ) + Mod.ModWaveFormType.SAW_WAVEFORM: # のこぎり + v = 255 - ( ( self.phase + 0x20 ) & 0x3F ) * 8 + Mod.ModWaveFormType.SQUARE_WAVEFORM: # 矩形波 + v = 255 - ( self.phase & 0x20 ) * 16 + Mod.ModWaveFormType.RAMDOM_WAVEFORM: # 乱数 + v = self.rng.randi( 511 ) - 255 + Mod.ModWaveFormType.REV_SAW_WAVEFORM: # 逆のこぎり + v = ( ( self.phase + 0x20 ) & 0x3F ) * 8 + + self.phase += self.speed + + self.value = ( v * self.depth ) >> self.depth_shift + +class GodotModPlayerEnvelope: + var source:Mod.ModEnvelope + + var frame:int = 0 + var value:int = 0 + var init_value:int = 0 + var sustain:bool = false + var enabled:bool = false + + ## ノートオン + ## @param _source エンベロープデータ + func note_on( _source:Mod.ModEnvelope ) -> void: + self.source = _source + self.sustain = true + self.frame = 0 + if self.source != null and self.source.enabled: + self.value = self.source.points[0].value + else: + self.value = self.init_value + + ## ノートオフ + func note_off( ) -> void: + self.sustain = false + + ## 更新 + func update( ) -> void: + if self.source == null: + self.value = self.init_value + return + + self.enabled = self.source.enabled + + if not self.enabled: + self.value = self.init_value + return + + var current_point:int = 0 + var prev_sum_frame:int = 0 + var sum_frame:int = 0 + var loop_start_frame:int = 0 + for i in Vector2i( 1, self.source.point_count ): + sum_frame += self.source.points[i].frame + if self.source.loop_start_point == i: + loop_start_frame = sum_frame + if self.frame < sum_frame: + var t:float = float( self.frame - prev_sum_frame ) / float( sum_frame - prev_sum_frame ) + var s:float = 1.0 - t + self.value = int( self.source.points[i-1].value * s + self.source.points[i].value * t ) + break + prev_sum_frame = sum_frame + current_point = i + + if self.source.sustain_enabled and self.sustain and self.source.sustain_point == current_point: + pass + elif self.source.loop_enabled and self.source.loop_end_point == current_point: + self.frame = loop_start_frame + elif current_point == self.source.point_count and sum_frame < self.frame: + self.frame = sum_frame + else: + self.frame += 1 + +class GodotModPlayerChannelStatus: + const head_silent_second:float = 1.0 + const gap_second:float = 1024.0 / 44100.0 + + var source_inst:Mod.ModInstrument + var source_sample:Mod.ModSample + var asps:Array[AudioStreamPlayer] + var asp_switcher:int + + var channel_number:int + var last_instrument:int = -1 + var last_key_number:int = -1 + var mute:bool + var pitch:GodotModPlayerPitch + var fine_pitch:GodotModPlayerPitch + var relative_pitch:float = 1.0 + + var volume:int + var panning:int + var amplified:int + var fx_count:int + var vibrato:GodotModPlayerEffect + var tremolo:GodotModPlayerEffect + + var volume_env:GodotModPlayerEnvelope + var panning_env:GodotModPlayerEnvelope + + var last_porta_up:int = 0 + var last_porta_down:int = 0 + var last_volume_slide:int = 0 + var last_panning_slide:int = 0 + var last_multi_retrig_note:int = 0 + + var force_note_off:bool = false + + ## コンストラクタ + ## @param _channel_number チャンネル番号 + ## @param linear_freq 周波数指定が線形か? + func _init(_channel_number:int,linear_freq:bool): + self.mute = false + self.force_note_off = false + + self.channel_number = _channel_number + self.panning = 128 + + for i in range( 2 ): + var asp: = AudioStreamPlayer.new( ) + asp.bus = mod_channel_bus_name % self.channel_number + self.asps.append( asp ) + self.asp_switcher = 0 + + self.pitch = GodotModPlayerPitch.new( ) + self.fine_pitch = GodotModPlayerPitch.new( ) + self.pitch.linear_freq = linear_freq + self.fine_pitch.linear_freq = linear_freq + self.vibrato = GodotModPlayerEffect.new( ) + self.vibrato.depth_shift = 6 if linear_freq else 7 # XXX: 多分 Linear Frequency の時に大きめに設定してある気がした。 + self.tremolo = GodotModPlayerEffect.new( ) + self.tremolo.depth_shift = 6 + self.volume_env = GodotModPlayerEnvelope.new( ) + self.volume_env.init_value = 64 + self.panning_env = GodotModPlayerEnvelope.new( ) + self.panning_env.init_value = 32 + + ## Tick更新 + func tick_update( ) -> void: + self.volume_env.update( ) + self.panning_env.update( ) + + ## 更新 + func update( ) -> void: + if self.mute: + for asp in self.asps: + if asp.is_playing( ): + asp.stop( ) + return + + for i in ( self.asps.size( ) ): + var asp:AudioStreamPlayer = self.asps[i] + if self.asp_switcher == i and ( not self.force_note_off ): + if asp.is_playing( ): + asp.volume_db = self.get_volume_db( ) + var pitch:float = self.get_pitch_scale( ) + if pitch < 0.01: + asp.stream_paused = true + else: + if asp.stream_paused: + asp.stream_paused = false + asp.pitch_scale = pitch + else: + if asp.is_playing( ): + asp.volume_db -= 4.0 + if asp.volume_db < -100.0: + asp.stop( ) + + ## volume_dbを取得 + ## @return 現在のvolume_dbを返す + func get_volume_db( ) -> float: + var v:float = ( clampf( self.volume + self.tremolo.value, 0.0, 64.0 ) / 64.0 ) * ( self.volume_env.value / 64.0 ) + return volume_table[int(v * 64)] + + ## ピッチスケールを取得 + ## @return 現在のピッチスケールを返す + func get_pitch_scale( ) -> float: + return self.pitch.get_pitch_scale( self.vibrato.value ) * self.relative_pitch * self.fine_pitch.get_fine_pitch_scale( ) + + ## ボリュームスライドを実行 + ## @param value 値 + func execute_volume_slide( value:int ) -> void: + if value == 0: + value = self.last_volume_slide + else: + self.last_volume_slide = value + self.volume = clampi( self.volume + ( value >> 4 ) - ( value & 0x0F ), 0, 64 ) + + ## ノートオフ + func note_off( ) -> void: + self.volume_env.note_off( ) + self.panning_env.note_off( ) + + ## 再トリガー + func retrig( ) -> void: + self.asp_switcher = ( self.asp_switcher + 1 ) % self.asps.size( ) + var asp:AudioStreamPlayer = self.asps[self.asp_switcher] + var pitch_scale:float = self.get_pitch_scale( ) + asp.pitch_scale = pitch_scale + asp.play( maxf( 0.0, self.head_silent_second - clampf( self.gap_second - AudioServer.get_time_to_next_mix( ), 0.0, self.gap_second ) * pitch_scale ) ) + + ## ノートオン + ## @param inst 楽器データ + ## @param effect_command エフェクトコマンド + ## @param key_number 周波数 + ## @param note ノート番号 + ## @param sample_offset サンプルオフセット + ## @param reset_inst 楽器データ再設定 + func note_on( inst:GodotModPlayerInstrument, effect_command:int, key_number:int, note:int, sample_offset:int, reset_inst:bool ) -> void: + self.last_key_number = key_number + + if self.mute: + return + + var tone_porta:bool = effect_command == 0x03 || effect_command == 0x05 + + self.force_note_off = false + + self.source_inst = inst.source + self.source_sample = self.source_inst.samples[note-1] + if self.source_sample.panning != -1: + self.panning = self.source_sample.panning + + if not tone_porta: + self.asp_switcher = ( self.asp_switcher + 1 ) % self.asps.size( ) + + var asp:AudioStreamPlayer = self.asps[self.asp_switcher] + var asw:AudioStreamWAV = inst.array_asw[note-1] + if tone_porta: + self.pitch.value += self.pitch.porta_value + self.pitch.dest = key_number + else: + asp.stop( ) + asp.stream = asw + self.pitch.value = key_number + self.pitch.porta_value = 0 + self.fine_pitch.value = self.source_sample.finetune + self.relative_pitch = pow( 2.0, self.source_sample.relative_note / 12.0 ) + + #if self.source_inst.vibrato_type != -1: + #self.vibrato.type = self.source_inst.vibrato_type + #self.vibrato.speed = self.source_inst.vibrato_speed + #self.vibrato.depth = self.source_inst.vibrato_depth + #self.vibrato.depth_shift = 7 - self.source_inst.vibrato_depth_shift + + if reset_inst: + self.volume = self.source_sample.volume + self.volume_env.note_on( self.source_inst.volume_envelope ) + self.panning_env.note_on( self.source_inst.panning_envelope ) + + asp.volume_db = self.get_volume_db( ) + var pitch_scale:float = self.get_pitch_scale( ) + if 0.0 < pitch_scale: + asp.pitch_scale = pitch_scale + if not tone_porta: + asp.play( maxf( 0.0, self.head_silent_second + ( float(sample_offset) / float(asw.mix_rate) ) - clampf( self.gap_second - AudioServer.get_time_to_next_mix( ), 0.0, self.gap_second ) * pitch_scale ) ) + if pitch_scale < 0.01: + asp.stream_paused = true + +# ----------------------------------------------------------------------------- +# Export + +## ファイル +@export_file ("*.mod", "*.xm") var file:String = "" : set = set_file +## 再生中か? +@export var playing:bool = false +## 音量 +@export_range(-144.0, 0.0) var volume_db:float = -20.0 : set = set_volume_db +## ループフラグ +@export var loop:bool = false +## mix_target same as AudioStreamPlayer's one +@export var mix_target:AudioStreamPlayer.MixTarget = AudioStreamPlayer.MIX_TARGET_STEREO +## bus same as AudioStreamPlayer's one +@export var bus:StringName = &"Master" + +# ----------------------------------------------------------------------------- +# 変数 + +## Modデータ +var mod_data:Mod.ModData = null : set = set_mod_data +## テンポ +var tempo:float = 125.0 : set = set_tempo +## 行毎秒 +var row_per_second:float = 0.02 +## tick毎行 +var tick_per_row:int = 4 +## tick毎秒 +var tick_per_second:float = 1.0 / chip_speed +## 次の行への秒数 +var next_row_remain_second:float = 0.0 +## 次のtickへの秒数 +var next_tick_remain_second:float = 0.0 +## 追加tick +var extra_tick:int = 0 +## tick処理済み回数 +var processed_tick_count:int = 0 +## 位置(秒) +var position:float = 0.0 +## 曲位置 +var song_position:int = 0 +## パターン内位置 +var pattern_position:int = 0 +## 次の行 +var pattern_position_on_next_row:int = 0 +## 楽器 +var instruments:Array[GodotModPlayerInstrument] = [] +## チャンネル +var channel_status:Array[GodotModPlayerChannelStatus] = [] +## Modチャンネルエフェクト +var channel_audio_effects:Array[GodotModPlayerChannelAudioEffect] = [] +## パターンジャンプ先 +var pattern_position_jump_point:int = 0 +## パターンループカウンタ +var pattern_loop_count:int = 0 +## パターンループ先 +var pattern_loop_origin:int = 0 +## 乱数 +var rng:RandomNumberGenerator +## グローバル音量コマンドを有効にするか? +var enable_global_volume_command:bool = true +## グローバル音量 (mod/xm指定の数字) +var global_volume:int = 64 +## グローバル音量スライド最終パラメータ +var global_volume_slide_last_param:int = 0x00 +## グローバル音量 (計算用db) +var global_volume_db:float = 0.0 + +## 準備 +func _ready( ): + self.rng = RandomNumberGenerator.new( ) + + if AudioServer.get_bus_index( self.mod_master_bus_name ) == -1: + AudioServer.add_bus( -1 ) + var mod_master_bus_idx:int = AudioServer.get_bus_count( ) - 1 + AudioServer.set_bus_name( mod_master_bus_idx, self.mod_master_bus_name ) + AudioServer.set_bus_send( mod_master_bus_idx, self.bus ) + AudioServer.set_bus_volume_db( AudioServer.get_bus_index( self.mod_master_bus_name ), self.volume_db ) + + for i in range( 32 ): + AudioServer.add_bus( -1 ) + var mod_channel_bus_idx:int = AudioServer.get_bus_count( ) - 1 + AudioServer.set_bus_name( mod_channel_bus_idx, self.mod_channel_bus_name % i ) + AudioServer.set_bus_send( mod_channel_bus_idx, self.mod_master_bus_name ) + AudioServer.set_bus_volume_db( mod_channel_bus_idx, 0.0 ) + + var cae: = GodotModPlayerChannelAudioEffect.new( ) + cae.ae_panner = AudioEffectPanner.new( ) + AudioServer.add_bus_effect( mod_channel_bus_idx, cae.ae_panner ) + self.channel_audio_effects.append( cae ) + else: + for i in range( 32 ): + var mod_channel_bus_idx:int = 0 + for k in ( AudioServer.get_bus_count( ) ): + if AudioServer.get_bus_name( k ) == self.mod_channel_bus_name % i: + mod_channel_bus_idx = k + break + + var cae: = GodotModPlayerChannelAudioEffect.new( ) + for k in ( AudioServer.get_bus_effect_count( mod_channel_bus_idx ) ): + var ae: = AudioServer.get_bus_effect( mod_channel_bus_idx, k ) + if ae is AudioEffectPanner: + cae.ae_panner = ae + self.channel_audio_effects.append( cae ) + + if self.playing: + self.play( ) + +## 通知 +## @param what 通知要因 +func _notification( what:int ): + # 破棄時 + if what == NOTIFICATION_PREDELETE: + pass + # 削除せずに使いまわす + #AudioServer.remove_bus( AudioServer.get_bus_index( self.mod_master_bus_name ) ) + #for i in range( 16 ): + # AudioServer.remove_bus( AudioServer.get_bus_index( self.midi_channel_bus_name % i ) ) + +## 再生前の初期化 +func _prepare_to_play( ) -> void: + # ファイル読み込み + if self.mod_data == null: + match self.file.get_extension( ): + "mod": + var mod_reader: = Mod.new( ) + self.mod_data = mod_reader.read_file( self.file ).data + "xm": + var xm_reader: = XM.new( ) + var m:Object = xm_reader.read_file( self.file ).data + self.mod_data = m + _: + self.mod_data = null + + if self.mod_data == null: + self.stop( ) + return + + if self.channel_status != null: + for t in self.channel_status: + for asp in t.asps: + self.remove_child( asp ) + + self.set_volume_db( self.volume_db ) + + self.instruments = [] + var temp_head_silent:Array[int] = [] + var head_silent_samples:int = default_mix_rate + if self.mod_data.flags & Mod.ModFlags.LINEAR_FREQUENCY_TABLE != 0: + head_silent_samples = default_linear_mix_rate + for i in ( head_silent_samples ): + temp_head_silent.append( 0 ) + var head_silent:PackedByteArray = PackedByteArray( temp_head_silent ) + var loaded:Dictionary = {} + for t in self.mod_data.instruments: + var inst:GodotModPlayerInstrument = GodotModPlayerInstrument.new( ) + + inst.source = t + inst.array_asw = [] + + for sample in t.samples: + var id:int = sample.get_instance_id( ) + var ass:AudioStreamWAV = null + if not( id in loaded ): + ass = AudioStreamWAV.new( ) + ass.stereo = false + if self.mod_data.flags & Mod.ModFlags.LINEAR_FREQUENCY_TABLE != 0: + ass.mix_rate = self.default_linear_mix_rate + else: + ass.mix_rate = self.default_mix_rate + if sample.bit == 16: + ass.data = head_silent + head_silent + sample.data + ass.format = AudioStreamWAV.FORMAT_16_BITS + ass.loop_begin = sample.loop_start + head_silent_samples * 2 + else: + ass.data = head_silent + sample.data + ass.format = AudioStreamWAV.FORMAT_8_BITS + ass.loop_begin = sample.loop_start + head_silent_samples + ass.loop_end = ass.loop_begin + sample.loop_length + if sample.bit == 16: + ass.loop_begin /= 2 + ass.loop_end /= 2 + ass.loop_mode = AudioStreamWAV.LOOP_DISABLED + if sample.loop_type & Mod.ModLoopType.FORWARD_LOOP != 0: + ass.loop_mode = AudioStreamWAV.LOOP_FORWARD + elif sample.loop_type & Mod.ModLoopType.PING_PONG_LOOP != 0: + ass.loop_mode = AudioStreamWAV.LOOP_PINGPONG + loaded[id] = ass + else: + ass = loaded[id] + inst.array_asw.append( ass ) + + self.instruments.append( inst ) + for k in loaded.keys( ): + loaded.erase( k ) + + self.channel_status = [] + for i in ( self.mod_data.channel_count ): + var cs: = GodotModPlayerChannelStatus.new( i, self.mod_data.flags & Mod.ModFlags.LINEAR_FREQUENCY_TABLE != 0 ) + if 4 < self.mod_data.channel_count: + cs.panning = 128 + else: + cs.panning = [64,192,192,64][i] + for asp in cs.asps: + self.add_child( asp ) + self.channel_status.append( cs ) + +## 再生 +## @param from_position 再生開始位置(現在未実装) +func play( from_position:float = 0.0 ): + self._prepare_to_play( ) + if self.mod_data == null: + return + + self.playing = true + if from_position == 0.0: + self.position = 0.0 + self.song_position = 0 + self.pattern_position_jump_point = 1 + self.pattern_position = -1 + self.pattern_position_on_next_row = -1 + self.pattern_loop_count = 0 + self.pattern_loop_origin = 0 + self.tick_per_second = 1.0 / self.chip_speed + self.set_tempo( self.mod_data.init_bpm ) + self.set_tick( self.mod_data.init_tick ) + self.processed_tick_count = 10000 + self.next_tick_remain_second = 0.0 + self.next_row_remain_second = self.row_per_second + else: + self.seek( from_position ) + +## シーク +## @param to_position 再生位置 +func seek( to_position:float ) -> void: + printerr( "Godot Mod Player: seekは未実装 seek not implemented yet" ) + + self._stop_all_notes( ) + +## 停止 +func stop( ) -> void: + self._stop_all_notes( ) + self.playing = false + +## ファイル変更 +## @param path ファイルパス +func set_file( path:String ) -> void: + file = path + self.mod_data = null + if self.playing: + self.play( ) + +## Modデータ変更 +## @param md Modデータ +func set_mod_data( md:Mod.ModData ) -> void: + mod_data = md + +## 音量設定 +## @param vdb 音量 +func set_volume_db( vdb:float ) -> void: + var master_bus_id:int = AudioServer.get_bus_index( self.mod_master_bus_name ) + volume_db = vdb + if master_bus_id == -1: + return + + var gvdb:float = self.global_volume_db if self.enable_global_volume_command else 0.0 + AudioServer.set_bus_volume_db( master_bus_id, volume_db + gvdb ) + +## 全音を止める +func _stop_all_notes( ) -> void: + for t in self.channel_status: + t.note_off( ) + t.force_note_off = true + for asp in t.asps: + if asp.is_playing( ): + asp.stop( ) + +## テンポ設定 +## @param _tempo テンポ +func set_tempo( _tempo:float ) -> void: + tempo = _tempo + self.tick_per_second = 1.0 / ( self.chip_speed * ( _tempo / 125.0 ) ) + self.next_row_remain_second -= self.row_per_second + self.row_per_second = self.tick_per_row * self.tick_per_second + self.next_row_remain_second += self.row_per_second + +## tickからテンポ設定 +## @param _tick Tick数 +func set_tick( _tick:int ) -> void: + self.tick_per_row = _tick + self.next_row_remain_second -= self.row_per_second + self.row_per_second = self.tick_per_row * self.tick_per_second + self.next_row_remain_second += self.row_per_second + +## 1フレームでの処理 +## @param delta +func _process( delta:float ): + if self.mod_data == null: + return + if not self.playing: + return + + self.position += delta + self.next_row_remain_second -= delta + self.next_tick_remain_second -= delta + if self.next_row_remain_second <= 0.0: + self.next_tick_remain_second = -INF + while self.next_tick_remain_second <= 0.0 and self.processed_tick_count < self.tick_per_row + self.extra_tick: + self._process_tick( ) + self.next_tick_remain_second += self.tick_per_second + self._process_row( ) + self.processed_tick_count += 1 + if self.next_row_remain_second <= 0.0: + self.extra_tick = 0 + self._process_move_to_next_line( ) + self.next_row_remain_second = self.row_per_second + self.next_row_remain_second + self.next_tick_remain_second = 0.0 + self.processed_tick_count = 1 + self._process_update_audio_effects( ) + +## 次の行に移行する +func _process_move_to_next_line( ) -> void: + if 0 <= self.pattern_position_on_next_row: + self.pattern_position = self.pattern_position_on_next_row + self.pattern_position_on_next_row = -1 + self.song_position = self.pattern_position_jump_point + self.pattern_position_jump_point = self.song_position + 1 + else: + self.pattern_position += 1 + if len( self.mod_data.patterns[self.mod_data.song_positions[self.song_position]] ) <= self.pattern_position: + self.pattern_position = 0 + self.song_position = self.pattern_position_jump_point + self.pattern_position_jump_point = self.song_position + 1 + + if self.mod_data.song_length <= self.song_position: + if self.loop: + self.song_position = self.mod_data.restart_position + self.pattern_position_jump_point = self.song_position + 1 + self.looped.emit( ) + else: + self.song_position = 0 + self.stop( ) + return + +## 1行処理 +func _process_row( ) -> void: + var pattern_line:Array[Mod.ModPatternNote] = self.mod_data.patterns[self.mod_data.song_positions[self.song_position]][self.pattern_position] + for channel in self.channel_status: + self._process_row_for_channel( channel, pattern_line[channel.channel_number] ) + +## チャンネルごとの1行処理 +## @param channel チャンネルデータ +## @param note ノートデータ +func _process_row_for_channel( channel:GodotModPlayerChannelStatus, note:Mod.ModPatternNote ) -> void: + #printt( channel.channel_number, pattern_node.sample_number, pattern_node.key_number, pattern_node.effect_command ) + + var note_on:bool = self.processed_tick_count == 1 + var sample_offset:int = 0 + # Note関係のエフェクトコマンド + if note.effect_command == 0x0E: + match ( note.effect_param >> 4 ): + 0x09: # Retrigger Note + note_on = ( ( self.processed_tick_count - 1 ) % ( note.effect_param & 0x0F ) ) == 0 + 0x0D: # Note Delay + note_on = ( note.effect_param & 0x0F ) == self.processed_tick_count - 1 + elif note.effect_command == 0x09: + sample_offset = note.effect_param * 256 + + # 楽器設定 + if note.instrument != 0: + channel.last_instrument = note.instrument - 1 + # 発音 + if note_on: + channel.pitch.arpeggio_count = 0 + if note.note == 96: + channel.note_off( ) + elif 0 < note.key_number or note.instrument != 0: + var key_number: = note.key_number if 0 < note.key_number else channel.last_key_number + if 0 <= channel.last_instrument and channel.last_instrument < self.instruments.size( ) and 0 < key_number: + var inst:GodotModPlayerInstrument = self.instruments[channel.last_instrument] + channel.note_on( inst, note.effect_command, key_number, note.note, sample_offset, note.instrument != 0 ) + + self.note_on.emit( channel.channel_number, note ) + + if self.processed_tick_count == 1: + self._process_tick_for_channel( channel, note, true ) + +## 1tick処理 +func _process_tick( ) -> void: + if self.pattern_position < 0: + return + + var pattern_line:Array[Mod.ModPatternNote] = self.mod_data.patterns[self.mod_data.song_positions[self.song_position]][self.pattern_position] + for channel in self.channel_status: + self._process_tick_for_channel( channel, pattern_line[channel.channel_number], false ) + +## チャンネルごとの1tick処理 +## @param channel チャンネルデータ +## @param note ノートデータ +## @param disable_channel_row チャンネルイベントを無視する +func _process_tick_for_channel( channel:GodotModPlayerChannelStatus, note:Mod.ModPatternNote, disable_channel_row:bool ) -> void: + #print( "%08x %08x" % [ note.effect_command, note.effect_param ] ) + + channel.pitch.arpeggio_enabled = false + channel.vibrato.value = 0 + channel.tremolo.value = 0 + + channel.tick_update( ) + + if 0x10 <= note.volume and note.volume <= 0x50: + channel.volume = note.volume - 0x10 + elif 0x60 <= note.volume: + match note.volume >> 4: + 0x06: # Volume slide down + if not disable_channel_row: + channel.volume = clampi( channel.volume - ( note.volume & 0x0F ), 0, 64 ) + 0x07: # Volume slide up + if not disable_channel_row: + channel.volume = clampi( channel.volume + ( note.volume & 0x0F ), 0, 64 ) + 0x08: # Fine volume slide down + if not disable_channel_row: + channel.volume = clampi( channel.volume - ( note.volume & 0x0F ), 0, 64 ) + 0x09: # Fine volume slide up + if not disable_channel_row: + channel.volume = clampi( channel.volume + ( note.volume & 0x0F ), 0, 64 ) + 0x0A: # Set vibrato speed + channel.vibrato.speed = (channel.vibrato.speed & 0x0F) | ( ( note.volume & 0x0F ) << 4 ); + 0x0B: # Vibrato Depth + channel.vibrato.depth = note.volume & 0x0F + 0x0C: # Panning + var p:int = note.volume & 0x0F + p |= p << 4 + channel.panning = p + 0x0D: # Panning slide left + if not disable_channel_row: + channel.panning = clampi( channel.panning - ( note.volume & 0x0F ), 0, 255 ) + 0x0E: # Panning slide right + if not disable_channel_row: + channel.panning = clampi( channel.panning + ( note.volume & 0x0F ), 0, 255 ) + 0x0F: # Tone portamento + if 0 < note.volume & 0x0F: + var p:int = note.volume & 0x0F + p |= p << 4 + channel.pitch.speed = self._fix_tone_portament( p ) + _: + printerr( "unknown volume effect command: %02x" % note.volume ) + + # エフェクトコマンド + match note.effect_command: + 0x00: # Arpeggio + if note.effect_param != 0: + if channel.pitch.linear_freq: + const linear_freq_factor:int = -64 # -( 768 / 12 ) + channel.pitch.arpeggio[1] = ( note.effect_param >> 4 ) * linear_freq_factor + channel.pitch.arpeggio[2] = ( note.effect_param & 0x0F ) * linear_freq_factor + else: + channel.pitch.arpeggio[1] = int( channel.pitch.value / pow( 2.0, ( note.effect_param >> 4 ) / 12.0 ) ) - channel.pitch.value + channel.pitch.arpeggio[2] = int( channel.pitch.value / pow( 2.0, ( note.effect_param & 0x0F ) / 12.0 ) ) - channel.pitch.value + channel.pitch.arpeggio_enabled = true + else: + channel.pitch.arpeggio_enabled = false + 0x01: # Portament up + var value:int = note.effect_param + if value == 0: + value = channel.last_porta_up + else: + channel.last_porta_up = value + if not disable_channel_row: + channel.pitch.porta_value -= value + 0x02: # Portament down + var value:int = note.effect_param + if value == 0: + value = channel.last_porta_down + else: + channel.last_porta_down = value + if not disable_channel_row: + channel.pitch.porta_value += value + 0x03: # Portament speed + if note.effect_param != 0: + channel.pitch.speed = self._fix_tone_portament( note.effect_param ) + channel.pitch.last_speed = channel.pitch.speed + else: + channel.pitch.speed = channel.pitch.last_speed + channel.pitch.update( ) + 0x04: # Vibrato + if ( note.effect_param & 0xF0 ) != 0: + channel.vibrato.speed = note.effect_param >> 4 + if ( note.effect_param & 0x0F ) != 0: + channel.vibrato.depth = note.effect_param & 0x0F + channel.vibrato.update( ) + 0x05: # Portament + Volume slide + channel.pitch.update( ) + if not disable_channel_row: + channel.execute_volume_slide( note.effect_param ) + 0x06: # Vibrato + Volume slide + channel.vibrato.update( ) + if not disable_channel_row: + channel.execute_volume_slide( note.effect_param ) + 0x07: # Tremolo + if note.effect_param & 0xF0 != 0: + channel.tremolo.speed = note.effect_param >> 4 + if note.effect_param & 0x0F != 0: + channel.tremolo.depth = note.effect_param & 0x0F + channel.tremolo.update( ) + 0x08: # Panning + channel.panning = clampi( note.effect_param * 2, 0, 255 ) + 0x09: # Sample offset + # 発音時に処理 + pass + 0x0A: # Volume slide + if not disable_channel_row: + channel.execute_volume_slide( note.effect_param ) + 0x0B: # Pattern jump + if disable_channel_row: + self.pattern_position_jump_point = note.effect_param + self.pattern_position_on_next_row = 0 + 0x0C: # Volume + channel.volume = clampi( note.effect_param, 0, 64 ) + 0x0D: # Pattern break + if disable_channel_row: + self.pattern_position_jump_point = self.song_position + 1 + self.pattern_position_on_next_row = ( note.effect_param >> 4 ) * 10 + ( note.effect_param & 0x0F ) + if 64 <= self.pattern_position_on_next_row: + self.pattern_position_on_next_row = 0 + 0x0E: # 拡張コマンド + match ( note.effect_param >> 4 ): + 0x01: # Fine portamento up + if note.effect_param & 0x0F != 0: + channel.fine_pitch.speed = note.effect_param & 0x0F + channel.fine_pitch.update( ) + 0x02: # Fine portamento down + if note.effect_param & 0x0F != 0: + channel.fine_pitch.speed = - (note.effect_param & 0x0F) + channel.fine_pitch.update( ) + 0x04: # Vibrato type + channel.vibrato.type = note.effect_param + channel.vibrato.update( ) + 0x06: # Pattern loop + if disable_channel_row: + if 0 < ( note.effect_param & 0x0F ): + if ( note.effect_param & 0x0F ) == self.pattern_loop_count: + self.pattern_loop_count = 0 + else: + self.pattern_loop_count += 1 + self.pattern_position_jump_point = self.song_position + self.pattern_position_on_next_row = self.pattern_loop_origin + else: + self.pattern_loop_origin = self.pattern_position + 0x07: # Tremoro type + channel.tremolo.type = note.effect_param + channel.tremolo.update( ) + 0x09: # Retrigger Note + pass + 0x0A: # Fine volume slide up + if not disable_channel_row: + channel.volume = clampi( channel.volume + ( note.volume & 0x0F ), 0, 64 ) + 0x0B: # Fine volume slide down + if not disable_channel_row: + channel.volume = clampi( channel.volume - ( note.volume & 0x0F ), 0, 64 ) + 0x0C: # Note cut + if self.processed_tick_count - 1 == note.effect_param & 0x0F: + channel.volume = 0 + 0x0D: # Note delay (上で処理する) + pass + 0x0E: # Pattern delay + var r:int = ( note.effect_param & 0x0F ) + self.extra_tick = r * self.tick_per_row + if disable_channel_row: + self.next_row_remain_second += self.row_per_second * r + _: + printerr( "unknown extended command: %04x" % [ note.effect_param ] ) + 0x0F: # Tick / Tempo + if disable_channel_row: + if note.effect_param < 0x20: + self.set_tick( note.effect_param ) + else: + self.set_tempo( note.effect_param ) + 0x10: # Global volume + self.global_volume = clampi( note.effect_param, 0, 64 ) + self.global_volume_db = self.volume_table[self.global_volume] + self.set_volume_db( self.volume_db ) + 0x11: # Global volume slide + if note.effect_param != 0x00: + self.global_volume_slide_last_param = note.effect_param + self.global_volume = clampi( self.global_volume + ( self.global_volume_slide_last_param >> 4 ) - ( self.global_volume_slide_last_param & 0x0F ), 0, 64 ) + self.global_volume_db = self.volume_table[self.global_volume] + self.set_volume_db( self.volume_db ) + 0x14: # Key unchecked + printerr( "not implemented: Kxx Key unchecked" ) + 0x15: # Set envelope position + channel.tremolo.phase = note.effect_param + 0x19: # Pannning slide + if not disable_channel_row: + var value:int = note.effect_param + if value == 0: + value = channel.last_panning_slide + else: + channel.last_panning_slide = value + channel.panning = clampi( channel.panning + ( value >> 4 ) - ( value & 0x0F ), 0, 255 ) + 0x1B: # Multi retrig note + var value:int = note.effect_param + if value == 0: + value = channel.last_multi_retrig_note + else: + channel.last_multi_retrig_note = value + if ( ( value & 0xF0 ) >> 4 ) == 0 or ( ( self.processed_tick_count - 1 ) % ( ( value & 0xF0 ) >> 4 ) ) == 0: + channel.retrig( ) + if disable_channel_row: + match value & 0x0F: + 0: + pass + 1: + self.global_volume = clampi( self.global_volume - 1, 0, 64 ) + 2: + self.global_volume = clampi( self.global_volume - 2, 0, 64 ) + 3: + self.global_volume = clampi( self.global_volume - 4, 0, 64 ) + 4: + self.global_volume = clampi( self.global_volume - 8, 0, 64 ) + 5: + self.global_volume = clampi( self.global_volume - 16, 0, 64 ) + 6: + self.global_volume = clampi( floori( self.global_volume * 2.0 / 3.0 ), 0, 64 ) + 7: + self.global_volume = clampi( floori( self.global_volume / 2.0 ), 0, 64 ) + 8: + printerr( "error: can't use 8 volume command in multi retrig note (0x1B)" ) + 9: + self.global_volume = clampi( self.global_volume + 1, 0, 64 ) + 10: + self.global_volume = clampi( self.global_volume + 2, 0, 64 ) + 11: + self.global_volume = clampi( self.global_volume + 4, 0, 64 ) + 12: + self.global_volume = clampi( self.global_volume + 8, 0, 64 ) + 13: + self.global_volume = clampi( self.global_volume + 16, 0, 64 ) + 14: + self.global_volume = clampi( floori( self.global_volume * 3.0 / 2.0 ), 0, 64 ) + 15: + self.global_volume = clampi( floori( self.global_volume * 2.0 ), 0, 64 ) + 0x1D: # Tremor + printerr( "not implemented: Txy Tremor" ) + 0x21: # Extra Fine Porta Up + printerr( "not implemented: Xxx Extra Fine Porta Up" ) + _: + printerr( "unknown command: %02x : %04x" % [ note.effect_command, note.effect_param ] ) + +## ポルタメント補正 +## @param v 元値 +## @return 補正後値 +func _fix_tone_portament( v:int ) -> int: + # Linear Frequency + if self.mod_data.flags & Mod.ModFlags.LINEAR_FREQUENCY_TABLE != 0: + # XXX: この4倍には根拠なし。耳コピでこのぐらいだと思った。 + return v * 4 + + # Amiga Frequency + return v + +## Godotのオーディオエフェクト更新 +func _process_update_audio_effects( ) -> void: + for channel in self.channel_status: + channel.update( ) + var cae:GodotModPlayerChannelAudioEffect = self.channel_audio_effects[channel.channel_number] + cae.ae_panner.pan = clampf( ( ( channel.panning - 128 ) / 128.0 ) + ( ( channel.panning_env.value - 32 ) / 32.0 ), -1.0, 1.0 ) diff --git a/godot-mod-player-g4-master/addons/modplayer/ModPlayer.gd.uid b/godot-mod-player-g4-master/addons/modplayer/ModPlayer.gd.uid new file mode 100644 index 0000000..2ede1ec --- /dev/null +++ b/godot-mod-player-g4-master/addons/modplayer/ModPlayer.gd.uid @@ -0,0 +1 @@ +uid://djgd6mugw2s47 diff --git a/godot-mod-player-g4-master/addons/modplayer/ModPlayer.tscn b/godot-mod-player-g4-master/addons/modplayer/ModPlayer.tscn new file mode 100644 index 0000000..23ad2b2 --- /dev/null +++ b/godot-mod-player-g4-master/addons/modplayer/ModPlayer.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3 uid="uid://x5sjwrimtdmi"] + +[ext_resource type="Script" path="res://addons/modplayer/ModPlayer.gd" id="1"] + +[node name="ModPlayer" type="Node"] +script = ExtResource("1") diff --git a/godot-mod-player-g4-master/addons/modplayer/XM.gd b/godot-mod-player-g4-master/addons/modplayer/XM.gd new file mode 100644 index 0000000..e1ac6c4 --- /dev/null +++ b/godot-mod-player-g4-master/addons/modplayer/XM.gd @@ -0,0 +1,275 @@ +## +## XM parser by あるる(きのもと 結衣) @arlez80 +## +## This script required Mod.gd for declaring the data structures. +## +## MIT License +## + +@warning_ignore_start("untyped_declaration") +@warning_ignore_start("unsafe_method_access") +@warning_ignore_start("unused_variable") +@warning_ignore_start("int_as_enum_without_cast") +@warning_ignore_start("return_value_discarded") + +class_name XM + +enum XMNoteFlags { + NOTE = 0x01, + INSTRUMENT = 0x02, + VOLUME = 0x04, + EFFECT_COMMAND = 0x08, + EFFECT_PARAM = 0x10, + + FIRST_BYTE_AS_FLAGS = 0x80, +} + +## ファイルから読み込む +## @param path ファイルパス +## @return xm +func read_file( path:String ) -> Mod.ModParseResult: + var result: = Mod.ModParseResult.new( ) + + var f: = FileAccess.open( path, FileAccess.READ ) + if f.get_error( ) != OK: + result.error = f.get_error( ) + return result + + var stream:StreamPeerBuffer = StreamPeerBuffer.new( ) + stream.set_data_array( f.get_buffer( f.get_length( ) ) ) + + result.data = self._read( stream ) + if result.data == null: + result.error = ERR_PARSE_ERROR + return result + +## 配列から読み込む +## @param data データ +## @return xm +func read_data( data:PackedByteArray ) -> Mod.ModParseResult: + var stream:StreamPeerBuffer = StreamPeerBuffer.new( ) + stream.set_data_array( data ) + + var result: = Mod.ModParseResult.new( ) + result.data = self._read( stream ) + if result.data == null: + result.error = ERR_PARSE_ERROR + return result + +## ストリームから読み込み +## @param stream ストリーム +## @return xm +func _read( stream:StreamPeerBuffer ) -> Mod.ModData: + var xm: = Mod.ModData.new( ) + + if self._read_string( stream, 17 ) != "Extended Module: ": + return null + xm.module_name = self._read_string( stream, 20 ) + if stream.get_u8( ) != 0x1A: + return null + xm.tracker_name = self._read_string( stream, 20 ) + xm.version = stream.get_u16( ) + + var header_size:int = stream.get_u32( ) + xm.song_length = stream.get_u16( ) + xm.restart_position = stream.get_u16( ) + xm.channel_count = stream.get_u16( ) + + var pattern_count:int = stream.get_u16( ) + var instrument_count:int = stream.get_u16( ) + xm.flags = stream.get_u16( ) + xm.init_tick = stream.get_u16( ) + xm.init_bpm = stream.get_u16( ) + + xm.song_positions = stream.get_partial_data( header_size - 20 )[1] + + xm.patterns = self._read_patterns( stream, xm.flags, pattern_count, xm.channel_count ) + if len( xm.patterns ) == 0: + return null + + xm.instruments = self._read_instruments( stream, instrument_count ) + if len( xm.instruments ) == 0: + return null + + return xm + +## パターン読み込み +## @param stream ストリーム +## @param xm_file_flags XMデータに関するフラグ +## @param pattern_count パターン数 +## @param channles 最大チャンネル数 +## @return パターンデータ +func _read_patterns( stream:StreamPeerBuffer, xm_file_flags:int, pattern_count:int, channels:int ) -> Array: # Array of Array of Array of ModPatternNote + var patterns:Array = [] # Array of Array of Array of ModPatternNote + + for i in ( pattern_count ): + var pattern:Array = [] # Array of Array of ModPatternNote + + if stream.get_u32( ) != 9: + return [] + if stream.get_u8( ) != 0: + return [] + var row_count:int = stream.get_u16( ) + var size:int = stream.get_u16( ) + for k in ( row_count ): + var line:Array[Mod.ModPatternNote] = [] + for ch in ( channels ): + var patr: = Mod.ModPatternNote.new( ) + if 0 < size: + var first:int = stream.get_u8( ) + size -= 1 + if first & XMNoteFlags.FIRST_BYTE_AS_FLAGS != 0: + if first & XMNoteFlags.NOTE != 0: + patr.note = stream.get_u8( ) + size -= 1 + else: + patr.note = first + first = 0xFF + if 0 < patr.note: + patr.note -= 1 + if first & XMNoteFlags.INSTRUMENT != 0: + patr.instrument = stream.get_u8( ) + size -= 1 + if first & XMNoteFlags.VOLUME != 0: + patr.volume = stream.get_u8( ) + size -= 1 + if first & XMNoteFlags.EFFECT_COMMAND != 0: + patr.effect_command = stream.get_u8( ) + size -= 1 + if first & XMNoteFlags.EFFECT_PARAM != 0: + patr.effect_param = stream.get_u8( ) + size -= 1 + if 0 < patr.note: + if xm_file_flags & Mod.ModFlags.LINEAR_FREQUENCY_TABLE != 0: + patr.key_number = self._conv_linear_freq( patr.note ) + else: + patr.key_number = self._conv_amiga_freq( patr.note ) + line.append( patr ) + pattern.append( line ) + patterns.append( pattern ) + + return patterns + +## ノート番号からAMIGA式で周波数計算する +## @param note ノート番号 +## @return 周波数 +func _conv_amiga_freq( note:int ) -> int: + return int( 6848.0 / pow( 2.0, note / 12.0 ) ) + +## ノート番号から線形で周波数計算する +## @param note ノート番号 +## @return 周波数 +func _conv_linear_freq( note:int ) -> int: + return 7680 - note * 64 + +## 楽器データを読み込む +## @param stream ストリーム +## @param instrument_count 楽器数 +## @return 楽器データ +func _read_instruments( stream:StreamPeerBuffer, instrument_count:int ) -> Array[Mod.ModInstrument]: + var instruments:Array[Mod.ModInstrument] = [] + + for i in ( instrument_count ): + var inst: = Mod.ModInstrument.new( ) + var size:int = stream.get_u32( ) + var remain_size:int = size + inst.name = self._read_string( stream, 22 ) + #print( "%d/%d %s" % [ i, instrument_count, inst.name] ) + var instrument_type:int = stream.get_u8( ) + if instrument_type != 0: + # 0以外のやつもいるらしいので無視 + #return [] + pass + + var sample_count:int = stream.get_u16( ) + var sample_numbers:Array = [] + remain_size -= 4 + 22 + 1 + 2 + if 0 < sample_count: + var sample_header_size:int = stream.get_u32( ) + sample_numbers = stream.get_partial_data( 96 )[1] + inst.volume_envelope = Mod.ModEnvelope.new( ) + for k in range( 12 ): + var ve: = Mod.ModEnvelopePoint.new( ) + ve.frame = stream.get_u16( ) + ve.value = stream.get_u16( ) + inst.volume_envelope.points.append( ve ) + inst.panning_envelope = Mod.ModEnvelope.new( ) + for k in range( 12 ): + var pe: = Mod.ModEnvelopePoint.new( ) + pe.frame = stream.get_u16( ) + pe.value = stream.get_u16( ) + inst.panning_envelope.points.append( pe ) + remain_size -= 4 + 96 + 48 + 48 + inst.volume_envelope.point_count = stream.get_u8( ) + inst.panning_envelope.point_count = stream.get_u8( ) + inst.volume_envelope.sustain_point = stream.get_u8( ) + inst.volume_envelope.loop_start_point = stream.get_u8( ) + inst.volume_envelope.loop_end_point = stream.get_u8( ) + inst.panning_envelope.sustain_point = stream.get_u8( ) + inst.panning_envelope.loop_start_point = stream.get_u8( ) + inst.panning_envelope.loop_end_point = stream.get_u8( ) + inst.volume_envelope.set_flag( stream.get_u8( ) ) + inst.panning_envelope.set_flag( stream.get_u8( ) ) + inst.vibrato_type = stream.get_u8( ) + inst.vibrato_speed = stream.get_u8( ) + inst.vibrato_depth = stream.get_u8( ) + inst.vibrato_depth_shift = stream.get_u8( ) + inst.volume_fadeout = stream.get_u16( ) + remain_size -= 16 + + if 0 < remain_size: + stream.get_partial_data( remain_size ) # reserved + + if 0 < sample_count: + var sounds:Array[Mod.ModSample] = [] + # Sound Header + for k in ( sample_count ): + var xms: = Mod.ModSample.new( ) + xms.length = stream.get_u32( ) + xms.loop_start = stream.get_u32( ) + xms.loop_length = stream.get_u32( ) + xms.volume = stream.get_u8( ) + xms.finetune = stream.get_8( ) + xms.loop_type = stream.get_u8( ) + if xms.loop_type & 16 != 0: + xms.bit = 16 + xms.loop_type &= 3 + xms.panning = stream.get_u8( ) + xms.relative_note = stream.get_8( ) + stream.get_u8( ) # Reserved + xms.name = self._read_string( stream, 22 ) + sounds.append( xms ) + # Sound Data + for xms in sounds: + var d:Array[int] = [] + var p:int = 0 + + if xms.bit == 16: + for k in ( xms.length >> 1 ): + p += stream.get_16( ) + d.append( p & 0xFF ) + d.append( ( p >> 8 ) & 0xFF ) + else: + for k in ( xms.length ): + p += stream.get_8( ) + d.append( p & 0xFF ) + + xms.data = PackedByteArray( d ) + + inst.samples = [] + for k in sample_numbers: + if len( sounds ) <= k: + inst.samples.append( sounds[0] ) + else: + inst.samples.append( sounds[k] ) + + instruments.append( inst ) + + return instruments + +## 文字列の読み込み +## @param stream ストリーム +## @param size 文字列サイズ +## @return 読み込んだ文字列を返す +func _read_string( stream:StreamPeerBuffer, size:int ) -> String: + return stream.get_partial_data( size )[1].get_string_from_ascii( ) diff --git a/godot-mod-player-g4-master/addons/modplayer/XM.gd.uid b/godot-mod-player-g4-master/addons/modplayer/XM.gd.uid new file mode 100644 index 0000000..4d513da --- /dev/null +++ b/godot-mod-player-g4-master/addons/modplayer/XM.gd.uid @@ -0,0 +1 @@ +uid://cmuxvykwoxljq diff --git a/godot-mod-player-g4-master/addons/modplayer/icon.png b/godot-mod-player-g4-master/addons/modplayer/icon.png new file mode 100644 index 0000000..cfffaa4 Binary files /dev/null and b/godot-mod-player-g4-master/addons/modplayer/icon.png differ diff --git a/godot-mod-player-g4-master/addons/modplayer/icon.png.import b/godot-mod-player-g4-master/addons/modplayer/icon.png.import new file mode 100644 index 0000000..731ce85 --- /dev/null +++ b/godot-mod-player-g4-master/addons/modplayer/icon.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ba4eaiuaq67p" +path="res://.godot/imported/icon.png-1b6fd777bf08247b7c845db8b9125613.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://godot-mod-player-g4-master/addons/modplayer/icon.png" +dest_files=["res://.godot/imported/icon.png-1b6fd777bf08247b7c845db8b9125613.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/godot-mod-player-g4-master/addons/modplayer/plugin.cfg b/godot-mod-player-g4-master/addons/modplayer/plugin.cfg new file mode 100644 index 0000000..08bffc2 --- /dev/null +++ b/godot-mod-player-g4-master/addons/modplayer/plugin.cfg @@ -0,0 +1,6 @@ +[plugin] +name="Godot Mod Player" +description="A plugin that playing Mod Files for Godot Engine." +author="arlez80" +version="4.3.0" +script="GMP.gd" diff --git a/level_mesh.gd b/level_mesh.gd index 3ba7d47..033fd9e 100644 --- a/level_mesh.gd +++ b/level_mesh.gd @@ -32,25 +32,29 @@ func _prepare_polygons() -> void: var polys := Geometry2D.triangulate_delaunay(polygon) var pickled := Array() for i in polys.size() / 3: - pickled.push_front(PackedInt32Array([polys[i * 3 + 0], polys[i * 3 + 1], polys[i * 3 + 2]])) + pickled.push_back(PackedInt32Array([polys[i * 3 + 0], polys[i * 3 + 1], polys[i * 3 + 2]])) polygons = pickled - _fix_acute_polygons() + _fix_polygons() -func _fix_acute_polygons() -> void: +func _fix_polygons() -> void: # Outside triangles are in reversed winding, find and eliminate. var new := Array() for poly in polygons as Array[PackedInt32Array]: var ab := polygon[poly[1]] - polygon[poly[0]] var ac := polygon[poly[2]] - polygon[poly[0]] if (ab.x * ac.y - ab.y * ac.x) * 0.5 <= 0: - new.push_front(poly) + # Fix winding as well. + new.push_back(PackedInt32Array([poly[2],poly[1],poly[0]])) polygons = new func _prepare_mesh() -> void: _prepare_floor_mesh() _prepare_wall_mesh() + + _prepare_floor_collision() + _prepare_wall_collision() func _prepare_floor_mesh() -> void: @@ -76,6 +80,7 @@ func _prepare_floor_mesh() -> void: _floor_mesh.mesh = m +# TODO: Make last segment UV stretch to edge, tiling it. func _prepare_wall_mesh() -> void: var m := ImmediateMesh.new() m.surface_begin(Mesh.PRIMITIVE_TRIANGLES) @@ -121,3 +126,31 @@ func _prepare_wall_mesh() -> void: ndy = rng.randf_range(-1.0, 1.0) m.surface_end() _wall_mesh.mesh = m + + +func _prepare_floor_collision() -> void: + if _floor_mesh.get_node_or_null("FloorMesh_col"): + _floor_mesh.remove_child(_floor_mesh.get_node("FloorMesh_col")) + _floor_mesh.create_convex_collision() + (_floor_mesh.get_node("FloorMesh_col") as StaticBody3D).set_collision_layer_value(2, 1) + + +func _prepare_wall_collision() -> void: + if _wall_mesh.get_node_or_null("WallMesh_col"): + _wall_mesh.remove_child(_wall_mesh.get_node("WallMesh_col")) + _wall_mesh.create_trimesh_collision() + _wall_mesh.get_node("WallMesh_col").get_node("CollisionShape3D").shape.backface_collision = true + (_wall_mesh.get_node("WallMesh_col") as StaticBody3D).set_collision_layer_value(3, 1) + #var shape := ConcavePolygonShape3D.new() + #shape.backface_collision = true + #var faces := PackedVector3Array() + #for ld in polygon.size(): + #var nd := (ld + 1) % polygon.size() + #faces.push_back(Vector3(polygon[ld].x * _scale, 0, polygon[ld].y * _scale)) + #faces.push_back(Vector3(polygon[nd].x * _scale, 0, polygon[nd].y * _scale)) + #faces.push_back(Vector3(polygon[ld].x * _scale, 5, polygon[ld].y * _scale)) + #faces.push_back(Vector3(polygon[nd].x * _scale, 0, polygon[nd].y * _scale)) + #faces.push_back(Vector3(polygon[nd].x * _scale, 5, polygon[nd].y * _scale)) + #faces.push_back(Vector3(polygon[ld].x * _scale, 5, polygon[ld].y * _scale)) + #shape.set_faces(faces) + #(_wall_mesh.get_node("Area3D").get_node("CollisionShape3D") as CollisionShape3D).shape = shape diff --git a/player.gd b/player.gd new file mode 100644 index 0000000..4215307 --- /dev/null +++ b/player.gd @@ -0,0 +1,61 @@ +extends Node3D + +var _rotation_target_x : Vector3 +var _rotation_target_y : Vector3 +@export var _cam : Camera3D +@export var _ray : RayCast3D + + +# Called when the node enters the scene tree for the first time. +func _ready() -> void: + _rotation_target_y = self.rotation + _rotation_target_x = _cam.rotation + Input.mouse_mode = Input.MOUSE_MODE_CAPTURED + + +# TODO: Interpolation. +# TODO: Hiding/Popping animation. +func _physics_process(_delta: float) -> void: + var wmark := get_parent_node_3d().get_node("WallMark") as Sprite3D + var fmark := get_parent_node_3d().get_node("FloorMark") as Sprite3D + # Walls. + if _ray.is_colliding() and (_ray.get_collider() as StaticBody3D).get_collision_layer_value(3): + var normflat := Vector3(_ray.get_collision_normal().x, 0, _ray.get_collision_normal().z) + var posflat := Vector3(_ray.get_collision_point().x, 0, _ray.get_collision_point().z) + wmark.look_at_from_position(posflat, posflat + normflat, Vector3.UP) + wmark.position = posflat + normflat + wmark.show() + else: + wmark.hide() + + # Floor. + if _ray.is_colliding() and (_ray.get_collider() as StaticBody3D).get_collision_layer_value(2): + var posflat := Vector3(_ray.get_collision_point().x, 0, _ray.get_collision_point().z) + fmark.position = posflat + fmark.show() + else: + fmark.hide() + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta: float) -> void: + self.rotation.y = lerp_angle(_rotation_target_y.y, _rotation_target_y.y, 1.0 - exp(-30 * delta)) + _cam.rotation.x = lerp_angle(_cam.rotation.x, _rotation_target_x.x, 1.0 - exp(-20 * delta)) + + +func _input(event: InputEvent) -> void: + if event is InputEventMouseMotion and Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED: + var motion := event as InputEventMouseMotion + var preserve := rotation + rotation = _rotation_target_y + self.rotate_y(deg_to_rad(motion.relative.x) * -0.25) + _rotation_target_y = rotation + rotation = preserve + + preserve = _cam.rotation + _cam.rotate_x(deg_to_rad(motion.relative.y) * -0.2) + var camera_rot := _cam.rotation_degrees + camera_rot.x = clamp(camera_rot.x, -30, 30) + _cam.rotation_degrees = camera_rot + _rotation_target_x = _cam.rotation + _cam.rotation = preserve diff --git a/player.gd.uid b/player.gd.uid new file mode 100644 index 0000000..a2c9d5e --- /dev/null +++ b/player.gd.uid @@ -0,0 +1 @@ +uid://dtqv683r772sd diff --git a/shield.png b/shield.png new file mode 100644 index 0000000..0f09173 Binary files /dev/null and b/shield.png differ diff --git a/shield.png.import b/shield.png.import new file mode 100644 index 0000000..a94bf32 --- /dev/null +++ b/shield.png.import @@ -0,0 +1,41 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cqisk1m0in4a4" +path.s3tc="res://.godot/imported/shield.png-bdf8e401b772eb7c90cb1fa4b7e06cd1.s3tc.ctex" +metadata={ +"imported_formats": ["s3tc_bptc"], +"vram_texture": true +} + +[deps] + +source_file="res://shield.png" +dest_files=["res://.godot/imported/shield.png-bdf8e401b772eb7c90cb1fa4b7e06cd1.s3tc.ctex"] + +[params] + +compress/mode=2 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=true +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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=0 diff --git a/wall_segment.gd b/wall_segment.gd new file mode 100644 index 0000000..20c5728 --- /dev/null +++ b/wall_segment.gd @@ -0,0 +1,15 @@ +@tool +extends Line2D + + +var _id : int + +# Called when the node enters the scene tree for the first time. +func _ready() -> void: + pass # Replace with function body. + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta: float) -> void: + pass + diff --git a/wall_segment.gd.uid b/wall_segment.gd.uid new file mode 100644 index 0000000..db82a93 --- /dev/null +++ b/wall_segment.gd.uid @@ -0,0 +1 @@ +uid://cc2rv3wnwas52