reorganization

This commit is contained in:
adrienmalin
2019-01-06 15:48:00 +01:00
parent ea0b840f35
commit f1683613f6
62 changed files with 79 additions and 172 deletions

126
source/ExplodingLine.tscn Normal file
View File

@@ -0,0 +1,126 @@
[gd_scene load_steps=10 format=2]
[ext_resource path="res://Tetrominos/Mino/MinoMaterial.tres" type="Material" id=1]
[sub_resource type="Gradient" id=1]
offsets = PoolRealArray( 0, 1 )
colors = PoolColorArray( 0, 0, 0, 1, 1, 1, 1, 1 )
[sub_resource type="GradientTexture" id=2]
flags = 4
gradient = SubResource( 1 )
width = 2048
[sub_resource type="Gradient" id=3]
offsets = PoolRealArray( 0, 1 )
colors = PoolColorArray( 0.802765, 1, 0.451172, 1, 1, 1, 1, 0 )
[sub_resource type="GradientTexture" id=4]
flags = 4
gradient = SubResource( 3 )
width = 2048
[sub_resource type="ParticlesMaterial" id=5]
render_priority = 0
trail_divisor = 2
trail_color_modifier = SubResource( 4 )
emission_shape = 2
emission_box_extents = Vector3( 8, 0.5, 0.5 )
flag_align_y = false
flag_rotate_y = true
flag_disable_z = false
spread = 30.0
flatness = 0.0
gravity = Vector3( 0, -30, 0 )
initial_velocity = 10.0
initial_velocity_random = 0.8
angular_velocity = 97.14
angular_velocity_random = 1.0
linear_accel = 100.0
linear_accel_random = 0.84
radial_accel = 8.48
radial_accel_random = 0.85
tangential_accel = 0.0
tangential_accel_random = 0.0
damping = 0.0
damping_random = 0.0
angle = 402.7
angle_random = 0.78
scale = 0.5
scale_random = 1.0
color_ramp = SubResource( 2 )
hue_variation = 0.0
hue_variation_random = 0.0
anim_speed = 0.0
anim_speed_random = 0.0
anim_offset = 0.0
anim_offset_random = 0.0
anim_loop = false
_sections_unfolded = [ "Angular Velocity", "Color", "Emission Shape", "Gravity", "Scale", "Spread" ]
[sub_resource type="PrismMesh" id=6]
material = ExtResource( 1 )
custom_aabb = AABB( 0, 0, 0, 0, 0, 0 )
left_to_right = 2.98023e-008
size = Vector3( 0.2, 0.2, 0.2 )
subdivide_width = 2
subdivide_height = 0
subdivide_depth = 0
[sub_resource type="PrismMesh" id=7]
material = ExtResource( 1 )
custom_aabb = AABB( 0, 0, 0, 0, 0, 0 )
left_to_right = 0.5
size = Vector3( 0.2, 0.2, 0.2 )
subdivide_width = 2
subdivide_height = 0
subdivide_depth = 0
[sub_resource type="CubeMesh" id=8]
material = ExtResource( 1 )
custom_aabb = AABB( 0, 0, 0, 0, 0, 0 )
size = Vector3( 0.2, 0.2, 0.2 )
subdivide_width = 0
subdivide_height = 0
subdivide_depth = 0
[node name="ExplodingLine" type="Particles"]
layers = 1
material_override = null
cast_shadow = 1
extra_cull_margin = 0.0
use_in_baked_light = false
lod_min_distance = 0.0
lod_min_hysteresis = 0.0
lod_max_distance = 0.0
lod_max_hysteresis = 0.0
emitting = false
amount = 800
lifetime = 2.0
one_shot = true
preprocess = 0.0
speed_scale = 2.18
explosiveness = 0.9
randomness = 0.69
fixed_fps = 0
fract_delta = true
visibility_aabb = AABB( -5, -0.5, -1, 10, 1, 2 )
local_coords = false
draw_order = 0
process_material = SubResource( 5 )
draw_passes = 3
draw_pass_1 = SubResource( 6 )
draw_pass_2 = SubResource( 7 )
draw_pass_3 = SubResource( 8 )
_sections_unfolded = [ "Draw Passes", "Drawing", "Process Material", "Time", "Transform" ]

16
source/FlashText.gd Normal file
View File

@@ -0,0 +1,16 @@
extends Control
var texts = PoolStringArray()
func print(text):
texts.append(text)
if texts.size() > 4:
texts.remove(0)
$Label.text = texts.join("\n")
$AnimationPlayer.play("Flash")
func _on_AnimationPlayer_animation_finished(anim_name):
texts.resize(0)
func _on_Stats_flash_text(text):
self.print(text)

62
source/GridMap.gd Normal file
View File

@@ -0,0 +1,62 @@
extends GridMap
const Tetromino = preload("res://Tetrominos/Tetromino.gd")
const ExplodingLine = preload("res://ExplodingLine.tscn")
const EMPTY_CELL = -1
const MINO = 0
export (int) var NB_LINES = 20
export (int) var NB_COLLUMNS = 10
var exploding_lines = []
func _ready():
for y in range(NB_LINES):
exploding_lines.append(ExplodingLine.instance())
add_child(exploding_lines[y])
exploding_lines[y].translation = Vector3(NB_COLLUMNS/2, y, 1)
func clear():
for position in get_used_cells():
set_cell_item(position.x, position.y, position.z, EMPTY_CELL)
func is_free_cell(position):
return (
0 <= position.x and position.x < NB_COLLUMNS
and position.y >= 0
and get_cell_item(position.x, position.y, 0) == GridMap.INVALID_CELL_ITEM
)
func possible_positions(initial_positions, movement):
var position
var test_positions = []
for i in range(4):
position = initial_positions[i] + movement
if is_free_cell(position):
test_positions.append(position)
if test_positions.size() == Tetromino.NB_MINOES:
return test_positions
else:
return []
func lock(piece):
for position in piece.positions():
set_cell_item(position.x, position.y, 0, MINO)
func clear_lines():
var line_cleared
var lines_cleared = 0
for y in range(NB_LINES-1, -1, -1):
line_cleared = true
for x in range(NB_COLLUMNS):
if not get_cell_item(x, y, 0) == MINO:
line_cleared = false
break
if line_cleared:
for y2 in range(y, NB_LINES+2):
for x in range(NB_COLLUMNS):
set_cell_item(x, y2, 0, get_cell_item(x, y2+1, 0))
lines_cleared += 1
exploding_lines[y].restart()
return lines_cleared

245
source/Main.gd Normal file
View File

@@ -0,0 +1,245 @@
extends WorldEnvironment
const Tetromino = preload("res://Tetrominos/Tetromino.gd")
const TetroI = preload("res://Tetrominos/TetroI.tscn")
const TetroJ = preload("res://Tetrominos/TetroJ.tscn")
const TetroL = preload("res://Tetrominos/TetroL.tscn")
const TetroO = preload("res://Tetrominos/TetroO.tscn")
const TetroS = preload("res://Tetrominos/TetroS.tscn")
const TetroT = preload("res://Tetrominos/TetroT.tscn")
const TetroZ = preload("res://Tetrominos/TetroZ.tscn")
const password = "TETRIS 3000"
const NEXT_POSITION = Vector3(13, 16, 0)
const START_POSITION = Vector3(5, 20, 0)
const HOLD_POSITION = Vector3(-5, 16, 0)
const movements = {
"move_right": Vector3(1, 0, 0),
"move_left": Vector3(-1, 0, 0),
"soft_drop": Vector3(0, -1, 0)
}
var random_bag = []
var next_piece
var current_piece
var held_piece
var current_piece_held
var autoshift_action = ""
var playing = false
signal piece_dropped(score)
signal piece_locked(lines, t_spin)
func _ready():
load_user_data()
func load_user_data():
var save_game = File.new()
if not save_game.file_exists("user://data.save"):
$Stats.high_score = 0
else:
save_game.open_encrypted_with_pass("user://data.save", File.READ, password)
$Stats.high_score = int(save_game.get_line())
$Stats/VBC/HighScore.text = str($Stats.high_score)
save_game.close()
func _on_Start_start(level):
$GridMap.clear()
if held_piece:
remove_child(held_piece)
held_piece = null
current_piece_held = false
next_piece = random_piece()
new_piece()
$MidiPlayer.position = 0
$Start.visible = false
$Stats.new_game(level)
resume()
func new_piece():
if current_piece:
remove_child(current_piece)
current_piece = next_piece
current_piece.translation = START_POSITION
current_piece.emit_trail(true)
autoshift_action = ""
next_piece = random_piece()
next_piece.translation = NEXT_POSITION
if move(movements["soft_drop"]):
$DropTimer.start()
$LockDelay.start()
current_piece_held = false
else:
current_piece.translate(movements["soft_drop"])
game_over()
func random_piece():
if not random_bag:
random_bag = [
TetroI, TetroJ, TetroL, TetroO,
TetroS, TetroT, TetroZ
]
var choice = randi() % random_bag.size()
var piece = random_bag[choice].instance()
random_bag.remove(choice)
add_child(piece)
return piece
func _on_Stats_level_up():
$DropTimer.wait_time = pow(0.8 - (($Stats.level - 1) * 0.007), $Stats.level - 1)
if $Stats.level > 15:
$LockDelay.wait_time = 0.5 * pow(0.9, $Stats.level-15)
func _process(delta):
if Input.is_action_just_pressed("pause"):
if playing:
pause()
$controls_ui.visible = true
elif $controls_ui.enable_resume:
resume()
if Input.is_action_just_pressed("toggle_fullscreen"):
OS.window_fullscreen = !OS.window_fullscreen
if playing:
for action in movements:
if action == autoshift_action:
if not Input.is_action_pressed(action):
$AutoShiftDelay.stop()
$AutoShiftTimer.stop()
autoshift_action = ""
else:
if Input.is_action_pressed(action):
autoshift_action = action
process_autoshift_action()
$AutoShiftTimer.stop()
$AutoShiftDelay.start()
if Input.is_action_just_pressed("hard_drop"):
hard_drop()
if Input.is_action_just_pressed("rotate_clockwise"):
rotate(Tetromino.CLOCKWISE)
if Input.is_action_just_pressed("rotate_counterclockwise"):
rotate(Tetromino.COUNTERCLOCKWISE)
if Input.is_action_just_pressed("hold"):
hold()
func _on_AutoShiftDelay_timeout():
if playing and autoshift_action:
process_autoshift_action()
$AutoShiftTimer.start()
func _on_AutoShiftTimer_timeout():
if playing and autoshift_action:
process_autoshift_action()
func process_autoshift_action():
if move(movements[autoshift_action]):
if autoshift_action == "soft_drop":
emit_signal("piece_dropped", 1)
func hard_drop():
var score = 0
while move(movements["soft_drop"]):
score += 2
emit_signal("piece_dropped", score)
lock()
func move(movement):
if current_piece.move(movement):
$LockDelay.start()
return true
else:
return false
func rotate(direction):
if current_piece.rotate(direction):
$LockDelay.start()
return true
else:
return false
func _on_DropTimer_timeout():
move(movements["soft_drop"])
func _on_LockDelay_timeout():
if not move(movements["soft_drop"]):
lock()
func lock():
$GridMap.lock(current_piece)
emit_signal("piece_locked", $GridMap.clear_lines(), current_piece.t_spin)
new_piece()
func hold():
if not current_piece_held:
current_piece_held = true
var swap = current_piece
current_piece = held_piece
held_piece = swap
held_piece.emit_trail(false)
held_piece.translation = HOLD_POSITION
if current_piece:
current_piece.translation = START_POSITION
current_piece.emit_trail(true)
else:
new_piece()
func resume():
playing = true
$DropTimer.start()
$LockDelay.start()
$Stats.time = OS.get_system_time_secs() - $Stats.time
$Stats/Clock.start()
$MidiPlayer.resume()
$controls_ui.visible = false
$Stats.visible = true
$GridMap.visible = true
$Backs.visible = true
current_piece.visible = true
if held_piece:
held_piece.visible = true
next_piece.visible = true
func pause(hide=true):
playing = false
$Stats.time = OS.get_system_time_secs() - $Stats.time
if hide:
$Stats.visible = false
$GridMap.visible = false
$Backs.visible = false
current_piece.visible = false
if held_piece:
held_piece.visible = false
next_piece.visible = false
$MidiPlayer.stop()
$DropTimer.stop()
$LockDelay.stop()
$Stats/Clock.stop()
func game_over():
$FlashText.print("GAME\nOVER")
pause(false)
$ReplayButton.visible = true
func _on_ReplayButton_pressed():
pause()
$ReplayButton.visible = false
$Start.visible = true
func _notification(what):
match what:
MainLoop.NOTIFICATION_WM_FOCUS_OUT:
if playing:
pause()
MainLoop.NOTIFICATION_WM_QUIT_REQUEST:
save_user_data()
get_tree().quit()
func save_user_data():
var save_game = File.new()
save_game.open_encrypted_with_pass("user://data.save", File.WRITE, password)
save_game.store_line(str($Stats.high_score))
save_game.close()

557
source/Main.tscn Normal file
View File

@@ -0,0 +1,557 @@
[gd_scene load_steps=21 format=2]
[ext_resource path="res://Main.gd" type="Script" id=1]
[ext_resource path="res://night-sky-background-14391263141jp.jpg" type="Texture" id=2]
[ext_resource path="res://Tetrominos/Mino/MinoLibrary.tres" type="MeshLibrary" id=3]
[ext_resource path="res://GridMap.gd" type="Script" id=4]
[ext_resource path="res://midi/MidiPlayer.tscn" type="PackedScene" id=5]
[ext_resource path="res://MidiPlayer.gd" type="Script" id=6]
[ext_resource path="res://FlashText.gd" type="Script" id=7]
[ext_resource path="res://fonts/525-ROUN.TTF" type="DynamicFontData" id=8]
[ext_resource path="res://Stats.tscn" type="PackedScene" id=9]
[ext_resource path="res://controls.tscn" type="PackedScene" id=10]
[ext_resource path="res://fonts/Gamer.ttf" type="DynamicFontData" id=11]
[ext_resource path="res://Start.tscn" type="PackedScene" id=12]
[sub_resource type="PanoramaSky" id=1]
radiance_size = 0
[sub_resource type="Environment" id=2]
resource_local_to_scene = true
background_mode = 0
background_sky = SubResource( 1 )
background_sky_custom_fov = 0.0
background_color = Color( 0, 0, 0, 1 )
background_energy = 0.0
background_canvas_max_layer = 0
ambient_light_color = Color( 0.86908, 0.949502, 0.958984, 1 )
ambient_light_energy = 2.0
ambient_light_sky_contribution = 0.92
fog_enabled = false
fog_color = Color( 0.5, 0.6, 0.7, 1 )
fog_sun_color = Color( 1, 0.9, 0.7, 1 )
fog_sun_amount = 0.0
fog_depth_enabled = true
fog_depth_begin = 10.0
fog_depth_curve = 1.0
fog_transmit_enabled = false
fog_transmit_curve = 1.0
fog_height_enabled = false
fog_height_min = 0.0
fog_height_max = 100.0
fog_height_curve = 1.0
tonemap_mode = 3
tonemap_exposure = 1.0
tonemap_white = 1.0
auto_exposure_enabled = false
auto_exposure_scale = 0.4
auto_exposure_min_luma = 0.05
auto_exposure_max_luma = 0.26
auto_exposure_speed = 0.5
ss_reflections_enabled = false
ss_reflections_max_steps = 64
ss_reflections_fade_in = 0.15
ss_reflections_fade_out = 2.0
ss_reflections_depth_tolerance = 0.2
ss_reflections_roughness = true
ssao_enabled = false
ssao_radius = 1.0
ssao_intensity = 1.0
ssao_radius2 = 0.0
ssao_intensity2 = 1.0
ssao_bias = 0.01
ssao_light_affect = 0.0
ssao_color = Color( 0, 0, 0, 1 )
ssao_quality = 0
ssao_blur = 3
ssao_edge_sharpness = 4.0
dof_blur_far_enabled = false
dof_blur_far_distance = 10.0
dof_blur_far_transition = 5.0
dof_blur_far_amount = 0.1
dof_blur_far_quality = 1
dof_blur_near_enabled = false
dof_blur_near_distance = 2.0
dof_blur_near_transition = 1.0
dof_blur_near_amount = 0.1
dof_blur_near_quality = 1
glow_enabled = false
glow_levels/1 = false
glow_levels/2 = false
glow_levels/3 = true
glow_levels/4 = false
glow_levels/5 = true
glow_levels/6 = false
glow_levels/7 = false
glow_intensity = 6.17
glow_strength = 1.0
glow_bloom = 0.0
glow_blend_mode = 2
glow_hdr_threshold = 1.0
glow_hdr_scale = 2.0
glow_bicubic_upscale = false
adjustment_enabled = false
adjustment_brightness = 0.27
adjustment_contrast = 1.0
adjustment_saturation = 0.34
_sections_unfolded = [ "Adjustments", "Ambient Light", "Background", "Resource" ]
[sub_resource type="SpatialMaterial" id=3]
render_priority = 0
flags_transparent = true
flags_unshaded = false
flags_vertex_lighting = false
flags_no_depth_test = false
flags_use_point_size = false
flags_world_triplanar = false
flags_fixed_size = false
flags_albedo_tex_force_srgb = false
vertex_color_use_as_albedo = false
vertex_color_is_srgb = false
params_diffuse_mode = 0
params_specular_mode = 0
params_blend_mode = 1
params_cull_mode = 0
params_depth_draw_mode = 0
params_line_width = 1.0
params_point_size = 1.0
params_billboard_mode = 0
params_grow = false
params_use_alpha_scissor = false
albedo_color = Color( 0.601563, 0.775878, 1, 0.00784314 )
metallic = 0.0
metallic_specular = 0.0
metallic_texture_channel = 0
roughness = 0.46
roughness_texture_channel = 0
emission_enabled = false
normal_enabled = false
rim_enabled = false
clearcoat_enabled = false
anisotropy_enabled = false
ao_enabled = false
depth_enabled = false
subsurf_scatter_enabled = false
transmission_enabled = false
refraction_enabled = false
detail_enabled = false
uv1_scale = Vector3( 1, 1, 1 )
uv1_offset = Vector3( 0, 0, 0 )
uv1_triplanar = false
uv1_triplanar_sharpness = 1.0
uv2_scale = Vector3( 1, 1, 1 )
uv2_offset = Vector3( 0, 0, 0 )
uv2_triplanar = false
uv2_triplanar_sharpness = 1.0
proximity_fade_enable = true
proximity_fade_distance = 1.0
distance_fade_enable = false
_sections_unfolded = [ "Albedo", "Emission", "Metallic", "NormalMap", "Proximity Fade" ]
[sub_resource type="CubeMesh" id=4]
material = SubResource( 3 )
custom_aabb = AABB( 0, 0, 0, 0, 0, 0 )
size = Vector3( 1, 1, 1 )
subdivide_width = 0
subdivide_height = 0
subdivide_depth = 0
[sub_resource type="CubeMesh" id=5]
material = SubResource( 3 )
custom_aabb = AABB( 0, 0, 0, 0, 0, 0 )
size = Vector3( 1, 1, 1 )
subdivide_width = 0
subdivide_height = 0
subdivide_depth = 0
[sub_resource type="DynamicFont" id=6]
size = 30
use_mipmaps = true
use_filter = false
font_data = ExtResource( 8 )
_sections_unfolded = [ "Font", "Settings" ]
[sub_resource type="Animation" id=7]
resource_name = "Flash"
length = 1.0
loop = false
step = 0.1
tracks/0/type = "value"
tracks/0/path = NodePath("Label:custom_fonts/font:size")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/keys = {
"times": PoolRealArray( 0, 0.1, 0.9, 1 ),
"transitions": PoolRealArray( 1, 0, 1, 1 ),
"update": 0,
"values": [ 30, 30, 30, 50 ]
}
tracks/1/type = "value"
tracks/1/path = NodePath("Label:custom_colors/font_color")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/keys = {
"times": PoolRealArray( 0, 0.1, 0.9, 1 ),
"transitions": PoolRealArray( 1, 1, 1, 1 ),
"update": 0,
"values": [ Color( 0.445404, 0.710476, 0.820313, 0 ), Color( 0.445404, 0.710476, 0.820313, 0.533765 ), Color( 0.445404, 0.710476, 0.820313, 0.533765 ), Color( 0.445404, 0.710476, 0.820313, 0 ) ]
}
tracks/2/type = "value"
tracks/2/path = NodePath("Label:rect_position")
tracks/2/interp = 1
tracks/2/loop_wrap = true
tracks/2/imported = false
tracks/2/enabled = true
tracks/2/keys = {
"times": PoolRealArray( 0, 0.1, 0.9, 1 ),
"transitions": PoolRealArray( 1, 1, 1, 1 ),
"update": 0,
"values": [ Vector2( 0, 110 ), Vector2( 0, -50 ), Vector2( 0, -60 ), Vector2( 0, -100 ) ]
}
[sub_resource type="DynamicFont" id=8]
size = 20
use_mipmaps = false
use_filter = false
extra_spacing_bottom = 5
font_data = ExtResource( 11 )
_sections_unfolded = [ "Extra Spacing", "Font", "Settings" ]
[node name="Main" type="WorldEnvironment" index="0"]
environment = SubResource( 2 )
script = ExtResource( 1 )
[node name="Sprite3D" type="Sprite3D" parent="." index="0"]
transform = Transform( 19.2, 0, 0, 0, 12.8, 0, 0, 0, 1, 5, 10, -80 )
layers = 1
material_override = null
cast_shadow = 1
extra_cull_margin = 0.0
use_in_baked_light = false
lod_min_distance = 0.0
lod_min_hysteresis = 0.0
lod_max_distance = 0.0
lod_max_hysteresis = 0.0
centered = true
offset = Vector2( 0, 0 )
flip_h = false
flip_v = false
modulate = Color( 0.478431, 0.478431, 0.478431, 1 )
opacity = 0.0
pixel_size = 0.01
axis = 2
transparent = false
shaded = false
double_sided = false
alpha_cut = 0
texture = ExtResource( 2 )
vframes = 1
hframes = 1
frame = 0
region_enabled = false
region_rect = Rect2( 0, 0, 0, 0 )
_sections_unfolded = [ "Animation", "Flags", "Transform" ]
[node name="Camera" type="Camera" parent="." index="1"]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 4.5, 10, 20 )
keep_aspect = 1
cull_mask = 1048575
environment = null
h_offset = 0.0
v_offset = 0.0
doppler_tracking = 0
projection = 0
current = true
fov = 70.0
size = 1.0
near = 0.1
far = 1.29056e+006
_sections_unfolded = [ "Transform" ]
[node name="DirectionalLight" type="DirectionalLight" parent="." index="2"]
transform = Transform( 0.332668, 0.771982, -0.541642, 0.579657, 0.285656, 0.763151, 0.743861, -0.567843, -0.352456, 5, 30, 0 )
layers = 1
light_color = Color( 1, 1, 1, 1 )
light_energy = 3.0
light_indirect_energy = 1.0
light_negative = false
light_specular = 0.5
light_bake_mode = 1
light_cull_mask = -1
shadow_enabled = false
shadow_color = Color( 0, 0, 0, 1 )
shadow_bias = 0.1
shadow_contact = 0.0
shadow_reverse_cull_face = false
editor_only = false
directional_shadow_mode = 2
directional_shadow_split_1 = 0.1
directional_shadow_split_2 = 0.2
directional_shadow_split_3 = 0.5
directional_shadow_blend_splits = false
directional_shadow_normal_bias = 0.8
directional_shadow_bias_split_scale = 0.25
directional_shadow_depth_range = 0
directional_shadow_max_distance = 200.0
_sections_unfolded = [ "Light", "Transform" ]
[node name="GridMap" type="GridMap" parent="." index="3"]
theme = ExtResource( 3 )
cell_size = Vector3( 1, 1, 1 )
cell_octant_size = 8
cell_center_x = false
cell_center_y = false
cell_center_z = false
cell_scale = 1.0
collision_layer = 1
collision_mask = 1
data = {
"cells": PoolIntArray( )
}
script = ExtResource( 4 )
_sections_unfolded = [ "Cell", "Transform" ]
__meta__ = {
"_editor_clip_": 1,
"_editor_floor_": Vector3( 0, -1, 0 )
}
NB_LINES = 20
NB_COLLUMNS = 10
[node name="Backs" type="Spatial" parent="." index="4"]
editor/display_folded = true
visible = false
[node name="GridBack" type="MeshInstance" parent="Backs" index="0"]
transform = Transform( 10, 0, 0, 0, 20, 0, 0, 0, 1, 4.5, 9.5, 0 )
layers = 1
material_override = null
cast_shadow = 1
extra_cull_margin = 0.0
use_in_baked_light = false
lod_min_distance = 0.0
lod_min_hysteresis = 0.0
lod_max_distance = 0.0
lod_max_hysteresis = 0.0
mesh = SubResource( 4 )
skeleton = NodePath("..")
material/0 = null
_sections_unfolded = [ "Transform" ]
[node name="HoldBack" type="MeshInstance" parent="Backs" index="1"]
transform = Transform( 7, 0, 0, 0, 7, 0, 0, 0, 1, -5, 16, 0 )
layers = 1
material_override = null
cast_shadow = 1
extra_cull_margin = 0.0
use_in_baked_light = false
lod_min_distance = 0.0
lod_min_hysteresis = 0.0
lod_max_distance = 0.0
lod_max_hysteresis = 0.0
mesh = SubResource( 5 )
skeleton = NodePath("..")
material/0 = null
_sections_unfolded = [ "Transform" ]
[node name="NextBack" type="MeshInstance" parent="Backs" index="2"]
transform = Transform( 7, 0, 0, 0, 7, 0, 0, 0, 1, 14, 16, 0 )
layers = 1
material_override = null
cast_shadow = 1
extra_cull_margin = 0.0
use_in_baked_light = false
lod_min_distance = 0.0
lod_min_hysteresis = 0.0
lod_max_distance = 0.0
lod_max_hysteresis = 0.0
mesh = SubResource( 5 )
skeleton = NodePath("..")
material/0 = null
_sections_unfolded = [ "Transform" ]
[node name="DropTimer" type="Timer" parent="." index="5"]
process_mode = 1
wait_time = 1.0
one_shot = false
autostart = false
[node name="LockDelay" type="Timer" parent="." index="6"]
process_mode = 1
wait_time = 0.5
one_shot = true
autostart = false
[node name="AutoShiftDelay" type="Timer" parent="." index="7"]
process_mode = 1
wait_time = 0.17
one_shot = true
autostart = false
[node name="AutoShiftTimer" type="Timer" parent="." index="8"]
process_mode = 1
wait_time = 0.03
one_shot = false
autostart = false
[node name="MidiPlayer" parent="." index="9" instance=ExtResource( 5 )]
editor/display_folded = true
script = ExtResource( 6 )
file = "res://midi/Tetris - Song A.mid"
volume_db = -12
loop = true
loop_start = 1.81
soundfont = "res://midi/TimGM6mb.sf2"
[node name="LineCLearTimer" type="Timer" parent="MidiPlayer" index="1"]
process_mode = 1
wait_time = 1.41
one_shot = true
autostart = false
[node name="FlashText" type="Control" parent="." index="10"]
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
margin_left = -250.0
margin_top = -250.0
margin_right = 250.0
margin_bottom = 250.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 1
script = ExtResource( 7 )
_sections_unfolded = [ "Material", "Size Flags", "Theme" ]
[node name="Label" type="Label" parent="FlashText" index="0"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_top = 190.0
margin_right = 500.0
margin_bottom = 690.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 2
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 4
custom_fonts/font = SubResource( 6 )
custom_colors/font_color = Color( 0.445404, 0.710476, 0.820313, 0 )
align = 1
valign = 1
percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
_sections_unfolded = [ "custom_colors", "custom_fonts" ]
[node name="AnimationPlayer" type="AnimationPlayer" parent="FlashText" index="1"]
root_node = NodePath("..")
autoplay = ""
playback_process_mode = 1
playback_default_blend_time = 0.0
playback_speed = 1.0
anims/Flash = SubResource( 7 )
blend_times = [ ]
_sections_unfolded = [ "Playback Options" ]
[node name="Stats" parent="." index="11" instance=ExtResource( 9 )]
visible = false
[node name="controls_ui" parent="." index="12" instance=ExtResource( 10 )]
visible = false
[node name="ReplayButton" type="Button" parent="." index="13"]
visible = false
anchor_left = 1.0
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
margin_left = -100.0
margin_top = -60.0
margin_right = -27.0
margin_bottom = -26.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
focus_mode = 2
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 1
custom_fonts/font = SubResource( 8 )
custom_colors/font_color = Color( 1, 1, 1, 1 )
toggle_mode = false
enabled_focus_mode = 2
shortcut = null
group = null
text = "REPLAY"
flat = false
align = 1
_sections_unfolded = [ "Margin", "custom_colors", "custom_fonts" ]
[node name="Start" parent="." index="14" instance=ExtResource( 12 )]
[connection signal="piece_dropped" from="." to="Stats" method="_on_Main_piece_dropped"]
[connection signal="piece_locked" from="." to="MidiPlayer" method="_on_Main_piece_locked"]
[connection signal="piece_locked" from="." to="Stats" method="_on_Main_piece_locked"]
[connection signal="timeout" from="DropTimer" to="." method="_on_DropTimer_timeout"]
[connection signal="timeout" from="LockDelay" to="." method="_on_LockDelay_timeout"]
[connection signal="timeout" from="AutoShiftDelay" to="." method="_on_AutoShiftDelay_timeout"]
[connection signal="timeout" from="AutoShiftTimer" to="." method="_on_AutoShiftTimer_timeout"]
[connection signal="timeout" from="MidiPlayer/LineCLearTimer" to="MidiPlayer" method="_on_LineCLearTimer_timeout"]
[connection signal="animation_finished" from="FlashText/AnimationPlayer" to="FlashText" method="_on_AnimationPlayer_animation_finished"]
[connection signal="flash_text" from="Stats" to="FlashText" method="print"]
[connection signal="level_up" from="Stats" to="." method="_on_Stats_level_up"]
[connection signal="pressed" from="ReplayButton" to="." method="_on_ReplayButton_pressed"]
[connection signal="start" from="Start" to="." method="_on_Start_start"]

53
source/MidiPlayer.gd Normal file
View File

@@ -0,0 +1,53 @@
extends "midi/MidiPlayer.gd"
const Tetromino = preload("res://Tetrominos/Tetromino.gd")
const LINE_CLEAR_CHANNELS = [2, 6]
var muted_events = []
func _ready():
mute_channels(LINE_CLEAR_CHANNELS)
func _init_channel( ):
._init_channel()
for channel in max_channel:
self.muted_events.append({})
func resume():
play(position)
func _process_track_event_note_off( channel, event ):
muted_events[channel.number].erase(event.note)
._process_track_event_note_off( channel, event )
func _process_track_event_note_on( channel, event ):
if self.channel_mute[channel.number]:
muted_events[channel.number][event.note] = event
._process_track_event_note_on( channel, event )
func mute_channels(channels):
for channel_id in channels:
channel_mute[channel_id] = true
func unmute_channels(channels):
for channel_id in channels:
channel_mute[channel_id] = false
for note in muted_events[channel_id]:
_process_track_event_note_on(channel_status[channel_id], muted_events[channel_id][note])
func _on_Main_piece_locked(lines, t_spin):
if lines or t_spin:
if lines == Tetromino.NB_MINOES:
for channel in LINE_CLEAR_CHANNELS:
channel_status[channel].vomume = 127
$LineCLearTimer.wait_time = 0.86
else:
for channel in LINE_CLEAR_CHANNELS:
channel_status[channel].vomume = 100
$LineCLearTimer.wait_time = 0.43
unmute_channels(LINE_CLEAR_CHANNELS)
$LineCLearTimer.start()
func _on_LineCLearTimer_timeout():
mute_channels(LINE_CLEAR_CHANNELS)

6
source/Start.gd Normal file
View File

@@ -0,0 +1,6 @@
extends Control
signal start(level)
func _on_PlayButton_pressed():
emit_signal("start", $SpinBox.value)

123
source/Start.tscn Normal file
View File

@@ -0,0 +1,123 @@
[gd_scene load_steps=7 format=2]
[ext_resource path="res://Start.gd" type="Script" id=1]
[ext_resource path="res://fonts/TitleFont.tres" type="DynamicFont" id=2]
[ext_resource path="res://fonts/Gamer.ttf" type="DynamicFontData" id=3]
[ext_resource path="res://fonts/ButtonFont.tres" type="DynamicFont" id=4]
[sub_resource type="DynamicFont" id=1]
size = 20
use_mipmaps = false
use_filter = false
font_data = ExtResource( 3 )
_sections_unfolded = [ "Extra Spacing", "Font", "Font/fallback", "Settings" ]
[sub_resource type="Theme" id=2]
default_font = SubResource( 1 )
[node name="Start" type="Control" index="0"]
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
margin_left = -20.0
margin_top = -20.0
margin_right = 20.0
margin_bottom = 20.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 1
script = ExtResource( 1 )
[node name="Label" type="Label" parent="." index="0"]
anchor_left = 0.5
anchor_top = 0.0
anchor_right = 0.5
anchor_bottom = 0.0
margin_left = -250.0
margin_top = -100.0
margin_right = 250.0
margin_bottom = -26.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 2
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 4
custom_fonts/font = ExtResource( 2 )
custom_colors/font_color = Color( 0.443137, 0.709804, 0.819608, 0.533333 )
custom_colors/font_color_shadow = Color( 1, 1, 1, 0.101333 )
text = "TETRIS
3000"
align = 1
percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
_sections_unfolded = [ "custom_colors", "custom_fonts" ]
[node name="SpinBox" type="SpinBox" parent="." index="1"]
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
margin_left = -83.0
margin_top = -20.0
margin_right = 113.0
margin_bottom = 17.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 1
theme = SubResource( 2 )
min_value = 1.0
max_value = 15.0
step = 1.0
page = 0.0
value = 1.0
exp_edit = false
rounded = false
editable = true
prefix = "Level "
suffix = ""
_sections_unfolded = [ "Margin", "Theme" ]
[node name="PlayButton" type="Button" parent="." index="2"]
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
margin_left = -85.0
margin_top = 29.0
margin_right = 85.0
margin_bottom = 69.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
focus_mode = 2
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 1
custom_fonts/font = ExtResource( 4 )
toggle_mode = false
enabled_focus_mode = 2
shortcut = null
group = null
text = "Play"
flat = false
align = 1
_sections_unfolded = [ "custom_fonts" ]
[connection signal="pressed" from="PlayButton" to="." method="_on_PlayButton_pressed"]

88
source/Stats.gd Normal file
View File

@@ -0,0 +1,88 @@
extends MarginContainer
const SCORES = [
[0, 4, 1],
[1, 8, 2],
[3, 12],
[5, 16],
[8]
]
const LINES_CLEARED_NAMES = ["", "SINGLE", "DOUBLE", "TRIPLE", "TETRIS"]
const T_SPIN_NAMES = ["", "T-SPIN", "MINI T-SPIN"]
var level
var goal
var score
var high_score
var time
var combos
signal flash_text(text)
signal level_up
func new_game(start_level):
level = start_level - 1
goal = 0
score = 0
$VBC/Score.text = str(score)
time = 0
$VBC/Time.text = "0:00:00"
combos = -1
new_level()
func new_level():
level += 1
goal += 5 * level
$VBC/Level.text = str(level)
$VBC/Goal.text = str(goal)
emit_signal("flash_text", "Level\n%d"%level)
emit_signal("level_up")
func _on_Clock_timeout():
show_time()
func show_time():
var time_elapsed = OS.get_system_time_secs() - time
var seconds = time_elapsed % 60
var minutes = int(time_elapsed/60) % 60
var hours = int(time_elapsed/3600)
$VBC/Time.text = str(hours) + ":%02d"%minutes + ":%02d"%seconds
func _on_Main_piece_dropped(ds):
score += ds
$VBC/Score.text = str(score)
func _on_Main_piece_locked(lines, t_spin):
var ds
if lines or t_spin:
var text = T_SPIN_NAMES[t_spin]
if text:
text += " "
text += LINES_CLEARED_NAMES[lines]
emit_signal("flash_text", text)
ds = SCORES[lines][t_spin]
goal -= ds
$VBC/Goal.text = str(goal)
ds *= 100
emit_signal("flash_text", str(ds))
score += ds
$VBC/Score.text = str(score)
if score > high_score:
high_score = score
$VBC/HighScore.text = str(high_score)
# Combos
if lines:
combos += 1
if combos > 0:
if combos == 1:
emit_signal("flash_text", "COMBO")
else:
emit_signal("flash_text", "COMBO x%d"%combos)
ds = (20 if lines==1 else 50) * combos * level
emit_signal("flash_text", str(ds))
score += ds
$VBC/Score.text = str(score)
else:
combos = -1
if goal <= 0:
new_level()

290
source/Stats.tscn Normal file
View File

@@ -0,0 +1,290 @@
[gd_scene load_steps=4 format=2]
[ext_resource path="res://Stats.gd" type="Script" id=1]
[ext_resource path="res://fonts/Gamer.ttf" type="DynamicFontData" id=2]
[sub_resource type="DynamicFont" id=2]
size = 20
use_mipmaps = false
use_filter = false
font_data = ExtResource( 2 )
_sections_unfolded = [ "Font", "Settings" ]
[node name="Stats" type="MarginContainer" index="0"]
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
margin_left = -230.0
margin_top = -10.0
margin_right = -120.0
margin_bottom = 200.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 0
size_flags_vertical = 0
script = ExtResource( 1 )
_sections_unfolded = [ "Anchor", "Margin", "Size Flags" ]
[node name="Clock" type="Timer" parent="." index="0"]
process_mode = 1
wait_time = 1.0
one_shot = false
autostart = false
[node name="VBC" type="VBoxContainer" parent="." index="1"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 16.0
margin_right = 94.0
margin_bottom = 170.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 1
mouse_default_cursor_shape = 0
size_flags_horizontal = 0
size_flags_vertical = 0
custom_constants/separation = 0
alignment = 0
_sections_unfolded = [ "Anchor", "Margin", "Size Flags", "custom_constants" ]
[node name="Label" type="Label" parent="VBC" index="0"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_right = 78.0
margin_bottom = 17.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 2
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 4
custom_fonts/font = SubResource( 2 )
custom_colors/font_color = Color( 0.756214, 0.921978, 0.990234, 1 )
text = "Score:"
percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
_sections_unfolded = [ "custom_fonts" ]
[node name="Score" type="Label" parent="VBC" index="1"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_top = 17.0
margin_right = 78.0
margin_bottom = 34.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 2
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 4
custom_fonts/font = SubResource( 2 )
custom_colors/font_color = Color( 0.756214, 0.921978, 0.990234, 1 )
text = "0"
align = 2
percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
_sections_unfolded = [ "custom_fonts" ]
[node name="Label2" type="Label" parent="VBC" index="2"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_top = 34.0
margin_right = 78.0
margin_bottom = 51.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 2
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 4
custom_fonts/font = SubResource( 2 )
custom_colors/font_color = Color( 0.756214, 0.921978, 0.990234, 1 )
text = "High score:"
percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
_sections_unfolded = [ "Anchor", "custom_fonts", "custom_styles" ]
[node name="HighScore" type="Label" parent="VBC" index="3"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_top = 51.0
margin_right = 78.0
margin_bottom = 68.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 2
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 4
custom_fonts/font = SubResource( 2 )
custom_colors/font_color = Color( 0.756214, 0.921978, 0.990234, 1 )
text = "0"
align = 2
percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
_sections_unfolded = [ "custom_colors" ]
[node name="Label3" type="Label" parent="VBC" index="4"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_top = 68.0
margin_right = 78.0
margin_bottom = 85.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 2
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 4
custom_fonts/font = SubResource( 2 )
custom_colors/font_color = Color( 0.756214, 0.921978, 0.990234, 1 )
text = "Time"
percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
[node name="Time" type="Label" parent="VBC" index="5"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_top = 85.0
margin_right = 78.0
margin_bottom = 102.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 2
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 4
custom_fonts/font = SubResource( 2 )
custom_colors/font_color = Color( 0.756214, 0.921978, 0.990234, 1 )
text = "0:00:00"
align = 2
percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
[node name="Label4" type="Label" parent="VBC" index="6"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_top = 102.0
margin_right = 78.0
margin_bottom = 119.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 2
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 4
custom_fonts/font = SubResource( 2 )
custom_colors/font_color = Color( 0.756214, 0.921978, 0.990234, 1 )
text = "Level:"
percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
[node name="Level" type="Label" parent="VBC" index="7"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_top = 119.0
margin_right = 78.0
margin_bottom = 136.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 2
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 4
custom_fonts/font = SubResource( 2 )
custom_colors/font_color = Color( 0.756214, 0.921978, 0.990234, 1 )
text = "0"
align = 2
percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
[node name="Label5" type="Label" parent="VBC" index="8"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_top = 136.0
margin_right = 78.0
margin_bottom = 153.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 2
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 4
custom_fonts/font = SubResource( 2 )
custom_colors/font_color = Color( 0.756214, 0.921978, 0.990234, 1 )
text = "Goal:"
percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
[node name="Goal" type="Label" parent="VBC" index="9"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_top = 153.0
margin_right = 78.0
margin_bottom = 170.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 2
mouse_default_cursor_shape = 0
size_flags_horizontal = 1
size_flags_vertical = 4
custom_fonts/font = SubResource( 2 )
custom_colors/font_color = Color( 0.756214, 0.921978, 0.990234, 1 )
text = "0"
align = 2
percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
_sections_unfolded = [ "custom_fonts" ]
[connection signal="timeout" from="Clock" to="." method="_on_Clock_timeout"]

View File

@@ -0,0 +1,228 @@
[gd_scene load_steps=8 format=2]
[ext_resource path="res://Tetrominos/Mino/MinoMesh.tscn" type="PackedScene" id=1]
[ext_resource path="res://Tetrominos/Mino/MinoMesh.tres" type="CubeMesh" id=2]
[sub_resource type="SpatialMaterial" id=1]
render_priority = 0
flags_transparent = true
flags_unshaded = false
flags_vertex_lighting = false
flags_no_depth_test = false
flags_use_point_size = false
flags_world_triplanar = false
flags_fixed_size = false
flags_albedo_tex_force_srgb = false
vertex_color_use_as_albedo = false
vertex_color_is_srgb = false
params_diffuse_mode = 0
params_specular_mode = 0
params_blend_mode = 1
params_cull_mode = 0
params_depth_draw_mode = 0
params_line_width = 1.0
params_point_size = 1.0
params_billboard_mode = 0
params_grow = false
params_use_alpha_scissor = false
albedo_color = Color( 0.601563, 0.775878, 1, 0.270275 )
metallic = 0.68
metallic_specular = 1.0
metallic_texture_channel = 0
roughness = 0.0
roughness_texture_channel = 0
emission_enabled = false
normal_enabled = false
rim_enabled = false
clearcoat_enabled = false
anisotropy_enabled = false
ao_enabled = false
depth_enabled = false
subsurf_scatter_enabled = false
transmission_enabled = false
refraction_enabled = false
detail_enabled = false
uv1_scale = Vector3( 1, 1, 1 )
uv1_offset = Vector3( 0, 0, 0 )
uv1_triplanar = false
uv1_triplanar_sharpness = 1.0
uv2_scale = Vector3( 1, 1, 1 )
uv2_offset = Vector3( 0, 0, 0 )
uv2_triplanar = false
uv2_triplanar_sharpness = 1.0
proximity_fade_enable = true
proximity_fade_distance = 1.0
distance_fade_enable = false
_sections_unfolded = [ "Albedo", "Emission", "Metallic", "NormalMap", "Proximity Fade" ]
[sub_resource type="GradientTexture" id=2]
flags = 4
width = 2048
[sub_resource type="ParticlesMaterial" id=3]
render_priority = 0
trail_divisor = 1
emission_shape = 0
flag_align_y = false
flag_rotate_y = false
flag_disable_z = true
spread = 0.0
flatness = 0.0
gravity = Vector3( 0, 0, 0 )
initial_velocity = 0.0
initial_velocity_random = 0.0
angular_velocity = 0.0
angular_velocity_random = 0.0
orbit_velocity = 0.0
orbit_velocity_random = 0.0
linear_accel = 0.0
linear_accel_random = 0.0
radial_accel = 0.0
radial_accel_random = 0.0
tangential_accel = 0.0
tangential_accel_random = 0.0
damping = 0.0
damping_random = 0.0
angle = 0.0
angle_random = 0.0
scale = 1.0
scale_random = 0.0
color_ramp = SubResource( 2 )
hue_variation = 0.0
hue_variation_random = 0.0
anim_speed = 0.0
anim_speed_random = 0.0
anim_offset = 0.0
anim_offset_random = 0.0
anim_loop = false
_sections_unfolded = [ "Color", "Gravity" ]
[sub_resource type="SpatialMaterial" id=4]
render_priority = 0
flags_transparent = true
flags_unshaded = false
flags_vertex_lighting = false
flags_no_depth_test = false
flags_use_point_size = false
flags_world_triplanar = false
flags_fixed_size = false
flags_albedo_tex_force_srgb = false
vertex_color_use_as_albedo = false
vertex_color_is_srgb = false
params_diffuse_mode = 0
params_specular_mode = 0
params_blend_mode = 1
params_cull_mode = 0
params_depth_draw_mode = 0
params_line_width = 1.0
params_point_size = 1.0
params_billboard_mode = 0
params_grow = false
params_use_alpha_scissor = false
albedo_color = Color( 0.601563, 0.775878, 1, 0.0045098 )
metallic = 0.68
metallic_specular = 1.0
metallic_texture_channel = 0
roughness = 0.0
roughness_texture_channel = 0
emission_enabled = false
normal_enabled = false
rim_enabled = false
clearcoat_enabled = false
anisotropy_enabled = false
ao_enabled = false
depth_enabled = false
subsurf_scatter_enabled = false
transmission_enabled = false
refraction_enabled = false
detail_enabled = false
uv1_scale = Vector3( 1, 1, 1 )
uv1_offset = Vector3( 0, 0, 0 )
uv1_triplanar = false
uv1_triplanar_sharpness = 1.0
uv2_scale = Vector3( 1, 1, 1 )
uv2_offset = Vector3( 0, 0, 0 )
uv2_triplanar = false
uv2_triplanar_sharpness = 1.0
proximity_fade_enable = true
proximity_fade_distance = 1.0
distance_fade_enable = false
_sections_unfolded = [ "Albedo", "Emission", "Proximity Fade" ]
[sub_resource type="CubeMesh" id=5]
material = SubResource( 4 )
custom_aabb = AABB( 0, 0, 0, 0, 0, 0 )
size = Vector3( 0.9, 0.9, 0.9 )
subdivide_width = 0
subdivide_height = 0
subdivide_depth = 0
[node name="Mino" type="Spatial"]
transform = Transform( 0.997027, 0, 0, 0, 0.997027, 0, 0, 0, 0.997027, 0, 0, 0 )
_sections_unfolded = [ "Pause", "Transform", "Visibility" ]
[node name="MinoMesh" parent="." index="0" instance=ExtResource( 1 )]
mesh = ExtResource( 2 )
material/0 = null
_sections_unfolded = [ "Geometry", "Transform", "material" ]
[node name="Trail" type="Particles" parent="." index="1"]
layers = 1
material_override = SubResource( 1 )
cast_shadow = 1
extra_cull_margin = 0.0
use_in_baked_light = false
lod_min_distance = 0.0
lod_min_hysteresis = 0.0
lod_max_distance = 0.0
lod_max_hysteresis = 0.0
emitting = false
amount = 4
lifetime = 0.05
one_shot = false
preprocess = 0.0
speed_scale = 1.0
explosiveness = 0.0
randomness = 0.0
fixed_fps = 0
fract_delta = true
visibility_aabb = AABB( -0.5, -0.5, -0.5, 1, 1, 1 )
local_coords = false
draw_order = 0
process_material = SubResource( 3 )
draw_passes = 1
draw_pass_1 = SubResource( 5 )
_sections_unfolded = [ "Draw Passes", "Drawing", "Geometry", "LOD", "Process Material", "Time", "Transform" ]
[node name="SpotLight" type="SpotLight" parent="." index="2"]
transform = Transform( 1, 0, 0, 0, -4.37114e-008, 1, 0, -1, -4.37114e-008, 0, 0, 0.5 )
layers = 1
light_color = Color( 1, 1, 1, 1 )
light_energy = 16.0
light_indirect_energy = 1.0
light_negative = false
light_specular = 0.5
light_bake_mode = 1
light_cull_mask = -1
shadow_enabled = false
shadow_color = Color( 0, 0, 0, 1 )
shadow_bias = 0.15
shadow_contact = 0.0
shadow_reverse_cull_face = false
editor_only = false
spot_range = 20.0
spot_attenuation = 0.5
spot_angle = 5.0
spot_angle_attenuation = 2.0
_sections_unfolded = [ "Light", "Spot", "Transform" ]

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,59 @@
[gd_resource type="SpatialMaterial" format=2]
[resource]
render_priority = 0
flags_transparent = true
flags_unshaded = false
flags_vertex_lighting = false
flags_no_depth_test = false
flags_use_point_size = false
flags_world_triplanar = false
flags_fixed_size = false
flags_albedo_tex_force_srgb = false
vertex_color_use_as_albedo = false
vertex_color_is_srgb = false
params_diffuse_mode = 0
params_specular_mode = 0
params_blend_mode = 1
params_cull_mode = 0
params_depth_draw_mode = 0
params_line_width = 1.0
params_point_size = 1.0
params_billboard_mode = 0
params_grow = false
params_use_alpha_scissor = false
albedo_color = Color( 0.601563, 0.775878, 1, 0.486471 )
metallic = 1.0
metallic_specular = 1.0
metallic_texture_channel = 4
roughness = 0.46
roughness_texture_channel = 0
emission_enabled = true
emission = Color( 0.755859, 1, 0.914169, 1 )
emission_energy = 0.2
emission_operator = 0
emission_on_uv2 = false
normal_enabled = false
rim_enabled = false
clearcoat_enabled = false
anisotropy_enabled = false
ao_enabled = false
depth_enabled = false
subsurf_scatter_enabled = false
transmission_enabled = false
refraction_enabled = false
detail_enabled = false
uv1_scale = Vector3( 1, 1, 1 )
uv1_offset = Vector3( 0, 0, 0 )
uv1_triplanar = false
uv1_triplanar_sharpness = 1.0
uv2_scale = Vector3( 1, 1, 1 )
uv2_offset = Vector3( 0, 0, 0 )
uv2_triplanar = false
uv2_triplanar_sharpness = 1.0
proximity_fade_enable = true
proximity_fade_distance = 1.0
distance_fade_enable = false
_sections_unfolded = [ "Albedo", "Emission", "Metallic", "NormalMap", "Proximity Fade" ]

View File

@@ -0,0 +1,13 @@
[gd_resource type="CubeMesh" load_steps=2 format=2]
[ext_resource path="res://Tetrominos/Mino/MinoMaterial.tres" type="Material" id=1]
[resource]
material = ExtResource( 1 )
custom_aabb = AABB( 0, 0, 0, 0, 0, 0 )
size = Vector3( 0.9, 0.9, 0.9 )
subdivide_width = 0
subdivide_height = 0
subdivide_depth = 0

View File

@@ -0,0 +1,31 @@
[gd_scene load_steps=3 format=2]
[ext_resource path="res://Tetrominos/Mino/MinoMaterial.tres" type="Material" id=1]
[sub_resource type="CubeMesh" id=1]
custom_aabb = AABB( 0, 0, 0, 0, 0, 0 )
size = Vector3( 0.9, 0.9, 0.9 )
subdivide_width = 0
subdivide_height = 0
subdivide_depth = 0
[node name="Mino" type="MeshInstance" index="0"]
layers = 1
material_override = null
cast_shadow = 1
extra_cull_margin = 0.0
use_in_baked_light = false
lod_min_distance = 0.0
lod_min_hysteresis = 0.0
lod_max_distance = 0.0
lod_max_hysteresis = 0.0
mesh = SubResource( 1 )
skeleton = NodePath("..")
material/0 = ExtResource( 1 )
_sections_unfolded = [ "Transform", "material" ]

View File

@@ -0,0 +1,70 @@
extends "Tetromino.gd"
const CLOCKWISE = -1
const COUNTERCLOCKWISE = 1
const SUPER_ROTATION_SYSTEM = [
{
COUNTERCLOCKWISE: [
Vector3(0, -1, 0),
Vector3(-1, -1, 0),
Vector3(2, -1, 0),
Vector3(-1, 1, 0),
Vector3(2, -2, 0)
],
CLOCKWISE: [
Vector3(1, 0, 0),
Vector3(-1, 0, 0),
Vector3(2, 0, 0),
Vector3(-1, -1, 0),
Vector3(2, 2, 0)
],
},
{
COUNTERCLOCKWISE: [
Vector3(-1, 0, 0),
Vector3(1, 0, 0),
Vector3(-2, 0, 0),
Vector3(1, 1, 0),
Vector3(-2, -2, 0)
],
CLOCKWISE: [
Vector3(0, -1, 0),
Vector3(-1, -1, 0),
Vector3(2, -1, 0),
Vector3(-1, 1, 0),
Vector3(2, -2, 0)
],
},
{
COUNTERCLOCKWISE: [
Vector3(0, 1, 0),
Vector3(1, 1, 0),
Vector3(-2, 1, 0),
Vector3(1, -1, 0),
Vector3(-2, 2, 0)
],
CLOCKWISE: [
Vector3(-1, 0, 0),
Vector3(1, 0, 0),
Vector3(-2, 0, 0),
Vector3(1, 1, 0),
Vector3(-2, -2, 0)
],
},
{
COUNTERCLOCKWISE: [
Vector3(1, 0, 0),
Vector3(-1, 0, 0),
Vector3(2, 0, 0),
Vector3(-1, -1, 0),
Vector3(2, 2, 0)
],
CLOCKWISE: [
Vector3(0, 1, 0),
Vector3(1, 1, 0),
Vector3(-2, 1, 0),
Vector3(1, -1, 0),
Vector3(-2, 2, 0)
],
},
]

View File

@@ -0,0 +1,25 @@
[gd_scene load_steps=3 format=2]
[ext_resource path="res://Tetrominos/TetroI.gd" type="Script" id=1]
[ext_resource path="res://Tetrominos/Mino/Mino.tscn" type="PackedScene" id=2]
[node name="TetroI" type="Spatial" index="0"]
script = ExtResource( 1 )
[node name="Mino0" parent="." index="0" instance=ExtResource( 2 )]
[node name="Mino1" parent="." index="1" instance=ExtResource( 2 )]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -1, 0, 0 )
[node name="Mino2" parent="." index="2" instance=ExtResource( 2 )]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0 )
[node name="Mino3" parent="." index="3" instance=ExtResource( 2 )]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 2, 0, 0 )

View File

@@ -0,0 +1,2 @@
extends "Tetromino.gd"

View File

@@ -0,0 +1,25 @@
[gd_scene load_steps=3 format=2]
[ext_resource path="res://Tetrominos/TetroJ.gd" type="Script" id=1]
[ext_resource path="res://Tetrominos/Mino/Mino.tscn" type="PackedScene" id=2]
[node name="TetroJ" type="Spatial" index="0"]
script = ExtResource( 1 )
[node name="Mino0" parent="." index="0" instance=ExtResource( 2 )]
[node name="Mino1" parent="." index="1" instance=ExtResource( 2 )]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -1, 1, 0 )
[node name="Mino2" parent="." index="2" instance=ExtResource( 2 )]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -1, 0, 0 )
[node name="Mino3" parent="." index="3" instance=ExtResource( 2 )]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0 )

View File

@@ -0,0 +1,2 @@
extends "Tetromino.gd"

View File

@@ -0,0 +1,25 @@
[gd_scene load_steps=3 format=2]
[ext_resource path="res://Tetrominos/TetroL.gd" type="Script" id=1]
[ext_resource path="res://Tetrominos/Mino/Mino.tscn" type="PackedScene" id=2]
[node name="TetroL" type="Spatial" index="0"]
script = ExtResource( 1 )
[node name="Mino0" parent="." index="0" instance=ExtResource( 2 )]
[node name="Mino1" parent="." index="1" instance=ExtResource( 2 )]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -1, 0, 0 )
[node name="Mino2" parent="." index="2" instance=ExtResource( 2 )]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0 )
[node name="Mino3" parent="." index="3" instance=ExtResource( 2 )]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0 )

View File

@@ -0,0 +1,4 @@
extends "Tetromino.gd"
func rotate(direction):
pass

View File

@@ -0,0 +1,25 @@
[gd_scene load_steps=3 format=2]
[ext_resource path="res://Tetrominos/TetroO.gd" type="Script" id=1]
[ext_resource path="res://Tetrominos/Mino/Mino.tscn" type="PackedScene" id=2]
[node name="TetroO" type="Spatial" index="0"]
script = ExtResource( 1 )
[node name="Mino0" parent="." index="0" instance=ExtResource( 2 )]
[node name="Mino1" parent="." index="1" instance=ExtResource( 2 )]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0 )
[node name="Mino2" parent="." index="2" instance=ExtResource( 2 )]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0 )
[node name="Mino3" parent="." index="3" instance=ExtResource( 2 )]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0 )

View File

@@ -0,0 +1,2 @@
extends "Tetromino.gd"

View File

@@ -0,0 +1,25 @@
[gd_scene load_steps=3 format=2]
[ext_resource path="res://Tetrominos/TetroS.gd" type="Script" id=1]
[ext_resource path="res://Tetrominos/Mino/Mino.tscn" type="PackedScene" id=2]
[node name="TetroS" type="Spatial" index="0"]
script = ExtResource( 1 )
[node name="Mino0" parent="." index="0" instance=ExtResource( 2 )]
[node name="Mino1" parent="." index="1" instance=ExtResource( 2 )]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -1, 0, 0 )
[node name="Mino2" parent="." index="2" instance=ExtResource( 2 )]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0 )
[node name="Mino3" parent="." index="3" instance=ExtResource( 2 )]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0 )

View File

@@ -0,0 +1,25 @@
extends "Tetromino.gd"
const T_SLOT = [
Vector3(-1, 1, 0),
Vector3(1, 1, 0),
Vector3(1, -1, 0),
Vector3(-1, -1, 0)
]
func rotate(direction):
var rotation_point = .rotate(direction)
if rotation_point and t_spin != T_SPIN:
var center = to_global(minoes[0].translation)
var a = not grid_map.is_free_cell(center + T_SLOT[orientation])
var b = not grid_map.is_free_cell(center + T_SLOT[(1+orientation)%4])
var c = not grid_map.is_free_cell(center + T_SLOT[(2+orientation)%4])
var d = not grid_map.is_free_cell(center + T_SLOT[(3+orientation)%4])
if a and b and (c or d):
t_spin = T_SPIN
elif c and d and (a or b):
if rotation_point == 5:
t_spin = T_SPIN
else:
t_spin = MINI_T_SPIN
return rotation_point

View File

@@ -0,0 +1,25 @@
[gd_scene load_steps=3 format=2]
[ext_resource path="res://Tetrominos/TetroT.gd" type="Script" id=1]
[ext_resource path="res://Tetrominos/Mino/Mino.tscn" type="PackedScene" id=2]
[node name="TetroT" type="Spatial" index="0"]
script = ExtResource( 1 )
[node name="Mino0" parent="." index="0" instance=ExtResource( 2 )]
[node name="Mino1" parent="." index="1" instance=ExtResource( 2 )]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -1, 0, 0 )
[node name="Mino2" parent="." index="2" instance=ExtResource( 2 )]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0 )
[node name="Mino3" parent="." index="3" instance=ExtResource( 2 )]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0 )

View File

@@ -0,0 +1,2 @@
extends "Tetromino.gd"

View File

@@ -0,0 +1,25 @@
[gd_scene load_steps=3 format=2]
[ext_resource path="res://Tetrominos/TetroZ.gd" type="Script" id=1]
[ext_resource path="res://Tetrominos/Mino/Mino.tscn" type="PackedScene" id=2]
[node name="TetroZ" type="Spatial" index="0"]
script = ExtResource( 1 )
[node name="Mino0" parent="." index="0" instance=ExtResource( 2 )]
[node name="Mino1" parent="." index="1" instance=ExtResource( 2 )]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -1, 1, 0 )
[node name="Mino2" parent="." index="2" instance=ExtResource( 2 )]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0 )
[node name="Mino3" parent="." index="3" instance=ExtResource( 2 )]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0 )

View File

@@ -0,0 +1,131 @@
extends Spatial
const NB_MINOES = 4
const CLOCKWISE = -1
const COUNTERCLOCKWISE = 1
const NO_T_SPIN = 0
const T_SPIN = 1
const MINI_T_SPIN = 2
const SUPER_ROTATION_SYSTEM = [
{
COUNTERCLOCKWISE: [
Vector3(0, 0, 0),
Vector3(1, 0, 0),
Vector3(1, 1, 0),
Vector3(0, -2, 0),
Vector3(1, -2, 0)
],
CLOCKWISE: [
Vector3(0, 0, 0),
Vector3(-1, 0, 0),
Vector3(-1, 1, 0),
Vector3(0, -2, 0),
Vector3(-1, -2, 0)
],
},
{
COUNTERCLOCKWISE: [
Vector3(0, 0, 0),
Vector3(1, 0, 0),
Vector3(1, -1, 0),
Vector3(0, 2, 0),
Vector3(1, 2, 0)
],
CLOCKWISE: [
Vector3(0, 0, 0),
Vector3(1, 0, 0),
Vector3(1, -1, 0),
Vector3(0, 2, 0),
Vector3(1, 2, 0)
],
},
{
COUNTERCLOCKWISE: [
Vector3(0, 0, 0),
Vector3(-1, 0, 0),
Vector3(-1, 1, 0),
Vector3(0, -2, 0),
Vector3(-1, -2, 0)
],
CLOCKWISE: [
Vector3(0, 0, 0),
Vector3(1, 0, 0),
Vector3(1, 1, 0),
Vector3(0, -2, 0),
Vector3(1, -2, 0)
],
},
{
COUNTERCLOCKWISE: [
Vector3(0, 0, 0),
Vector3(-1, 0, 0),
Vector3(-1, -1, 0),
Vector3(0, 2, 0),
Vector3(-1, 2, 0)
],
CLOCKWISE: [
Vector3(0, 0, 0),
Vector3(-1, 0, 0),
Vector3(-1, -1, 0),
Vector3(0, -2, 0),
Vector3(-1, 2, 0)
]
}
]
var minoes
var grid_map
var orientation = 0
var t_spin = NO_T_SPIN
func _ready():
randomize()
minoes = [$Mino0, $Mino1, $Mino2, $Mino3]
grid_map = get_parent().get_node("GridMap")
func positions():
var p = []
for mino in minoes:
p.append(to_global(mino.translation))
return p
func rotated_positions(direction):
var translations = [to_global(minoes[0].translation) ]
for i in range(1, 4):
var v = to_global(minoes[i].translation)
v -= to_global(minoes[0].translation)
v = Vector3(-1*direction*v.y, direction*v.x, 0)
v += to_global(minoes[0].translation)
translations.append(v)
return translations
func apply_positions(positions):
for i in range(NB_MINOES):
minoes[i].translation = to_local(positions[i])
func move(movement):
if grid_map.possible_positions(positions(), movement):
translate(movement)
return true
else:
return false
func rotate(direction):
var rotated_positions = rotated_positions(direction)
var movements = SUPER_ROTATION_SYSTEM[orientation][direction]
for i in range(movements.size()):
if grid_map.possible_positions(rotated_positions, movements[i]):
orientation -= direction
orientation %= NB_MINOES
apply_positions(rotated_positions)
translate(movements[i])
return i+1
return 0
func emit_trail(visible):
var trail
for mino in minoes:
trail = mino.get_node("Trail")
trail.emitting = visible
trail.visible = visible
mino.get_node("SpotLight").visible = visible

116
source/controls.gd Normal file
View File

@@ -0,0 +1,116 @@
extends Control
# Note for the reader:
#
# This demo conveniently uses the same names for actions and for the container nodes
# that hold each remapping button. This allow to get back to the button based simply
# on the name of the corresponding action, but it might not be so simple in your project.
#
# A better approach for large-scale input remapping might be to do the connections between
# buttons and wait_for_input through the code, passing as arguments both the name of the
# action and the node, e.g.:
# button.connect("pressed", self, "wait_for_input", [ button, action ])
# Constants
const INPUT_ACTIONS = [
"move_left",
"move_right",
"rotate_clockwise",
"rotate_counterclockwise",
"soft_drop",
"hard_drop",
"hold",
"pause"
]
const CONFIG_FILE = "user://input.cfg"
# Member variables
var action # To register the action the UI is currently handling
var button # Button node corresponding to the above action
var enable_resume = true
# Load/save input mapping to a config file
# Changes done while testing the demo will be persistent, saved to CONFIG_FILE
func load_config():
var config = ConfigFile.new()
var err = config.load(CONFIG_FILE)
if err: # Assuming that file is missing, generate default config
for action_name in INPUT_ACTIONS:
var action_list = InputMap.get_action_list(action_name)
# There could be multiple actions in the list, but we save the first one by default
var scancode = OS.get_scancode_string(action_list[0].scancode)
config.set_value("input", action_name, scancode)
config.save(CONFIG_FILE)
else: # ConfigFile was properly loaded, initialize InputMap
for action_name in config.get_section_keys("input"):
# Get the key scancode corresponding to the saved human-readable string
var scancode = OS.find_scancode_from_string(config.get_value("input", action_name))
# Create a new event object based on the saved scancode
var event = InputEventKey.new()
event.scancode = scancode
# Replace old action (key) events by the new one
for old_event in InputMap.get_action_list(action_name):
if old_event is InputEventKey:
InputMap.action_erase_event(action_name, old_event)
InputMap.action_add_event(action_name, event)
func save_to_config(section, key, value):
"""Helper function to redefine a parameter in the settings file"""
var config = ConfigFile.new()
var err = config.load(CONFIG_FILE)
if err:
print("Error code when loading config file: ", err)
else:
config.set_value(section, key, value)
config.save(CONFIG_FILE)
# Input management
func wait_for_input(action_bind):
action = action_bind
# See note at the beginning of the script
button = get_node("bindings").get_node(action).get_node("Button")
#get_node("contextual_help").text = "Press a key to assign to the '" + action + "' action."
enable_resume = false
set_process_input(true)
func _input(event):
# Handle the first pressed key
if event is InputEventKey:
# Register the event as handled and stop polling
get_tree().set_input_as_handled()
set_process_input(false)
# Reinitialise the contextual help label
#get_node("contextual_help").text = "Click a key binding to reassign it, or press the Cancel action."
# Display the string corresponding to the pressed key
var scancode = OS.get_scancode_string(event.scancode)
button.text = scancode
# Start by removing previously key binding(s)
for old_event in InputMap.get_action_list(action):
InputMap.action_erase_event(action, old_event)
# Add the new key binding
InputMap.action_add_event(action, event)
save_to_config("input", action, scancode)
enable_resume = true
func _ready():
# Load config if existing, if not it will be generated with default values
load_config()
# Initialise each button with the default key binding from InputMap
for action in INPUT_ACTIONS:
# We assume that the key binding that we want is the first one (0), if there are several
var input_event = InputMap.get_action_list(action)[0]
# See note at the beginning of the script
var button = get_node("bindings").get_node(action).get_node("Button")
button.text = OS.get_scancode_string(input_event.scancode)
button.connect("pressed", self, "wait_for_input", [action])
# Do not start processing input until a button is pressed
set_process_input(false)

610
source/controls.tscn Normal file
View File

@@ -0,0 +1,610 @@
[gd_scene load_steps=6 format=2]
[ext_resource path="res://controls.gd" type="Script" id=1]
[ext_resource path="res://fonts/TitleFont.tres" type="DynamicFont" id=2]
[ext_resource path="res://fonts/Gamer.ttf" type="DynamicFontData" id=3]
[ext_resource path="res://fonts/ButtonFont.tres" type="DynamicFont" id=4]
[sub_resource type="DynamicFont" id=2]
size = 20
use_mipmaps = true
use_filter = false
extra_spacing_top = -4
extra_spacing_bottom = -4
font_data = ExtResource( 3 )
_sections_unfolded = [ "Extra Spacing", "Font", "Settings" ]
[node name="controls_ui" type="Control"]
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
margin_left = -30.0
margin_top = 10.0
margin_right = 10.0
margin_bottom = 50.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 2
size_flags_vertical = 2
script = ExtResource( 1 )
__meta__ = {
"__editor_plugin_screen__": "2D"
}
[node name="pause" type="Label" parent="." index="0"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = -40.0
margin_top = -150.0
margin_right = 110.0
margin_bottom = -111.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 2
mouse_default_cursor_shape = 0
size_flags_horizontal = 2
size_flags_vertical = 0
custom_fonts/font = ExtResource( 2 )
custom_colors/font_color = Color( 0.443137, 0.709804, 0.819608, 0.533333 )
custom_colors/font_color_shadow = Color( 1, 1, 1, 0.0968627 )
text = "PAUSE"
percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
_sections_unfolded = [ "custom_colors", "custom_fonts" ]
[node name="bindings" type="Control" parent="." index="1"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = -190.0
margin_top = -130.0
margin_right = -150.0
margin_bottom = -90.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 2
size_flags_vertical = 2
[node name="move_left" type="Control" parent="bindings" index="0"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 50.0
margin_top = 50.0
margin_right = 90.0
margin_bottom = 90.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 2
size_flags_vertical = 2
[node name="Label" type="Label" parent="bindings/move_left" index="0"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_right = 69.0
margin_bottom = 29.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 2
mouse_default_cursor_shape = 0
size_flags_horizontal = 2
size_flags_vertical = 0
custom_fonts/font = SubResource( 2 )
custom_colors/font_color = Color( 0.752941, 0.921569, 0.988235, 1 )
text = "move
left"
valign = 1
percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
_sections_unfolded = [ "Rect", "custom_fonts" ]
[node name="Button" type="Button" parent="bindings/move_left" index="1"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 80.0
margin_right = 155.0
margin_bottom = 37.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
focus_mode = 2
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 2
size_flags_vertical = 2
custom_fonts/font = ExtResource( 4 )
toggle_mode = false
enabled_focus_mode = 2
shortcut = null
group = null
flat = false
align = 1
_sections_unfolded = [ "Rect" ]
[node name="move_right" type="Control" parent="bindings" index="1"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 226.0
margin_top = 51.0
margin_right = 266.0
margin_bottom = 91.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 2
size_flags_vertical = 2
[node name="Label" type="Label" parent="bindings/move_right" index="0"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 5.0
margin_top = -1.0
margin_right = 80.0
margin_bottom = 31.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 2
mouse_default_cursor_shape = 0
size_flags_horizontal = 2
size_flags_vertical = 0
custom_fonts/font = SubResource( 2 )
custom_colors/font_color = Color( 0.752941, 0.921569, 0.988235, 1 )
text = "move
right"
valign = 1
percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
[node name="Button" type="Button" parent="bindings/move_right" index="1"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 84.0
margin_top = -1.0
margin_right = 159.0
margin_bottom = 36.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
focus_mode = 2
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 2
size_flags_vertical = 2
custom_fonts/font = ExtResource( 4 )
toggle_mode = false
enabled_focus_mode = 2
shortcut = null
group = null
flat = false
align = 1
[node name="rotate_clockwise" type="Control" parent="bindings" index="2"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 50.0
margin_top = 100.0
margin_right = 90.0
margin_bottom = 140.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 2
size_flags_vertical = 2
[node name="Label" type="Label" parent="bindings/rotate_clockwise" index="0"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_right = 118.0
margin_bottom = 32.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 2
mouse_default_cursor_shape = 0
size_flags_horizontal = 2
size_flags_vertical = 0
custom_fonts/font = SubResource( 2 )
custom_colors/font_color = Color( 0.752941, 0.921569, 0.988235, 1 )
text = "rotate
clockwise"
valign = 1
percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
[node name="Button" type="Button" parent="bindings/rotate_clockwise" index="1"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 80.0
margin_right = 155.0
margin_bottom = 37.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
focus_mode = 2
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 2
size_flags_vertical = 2
custom_fonts/font = ExtResource( 4 )
toggle_mode = false
enabled_focus_mode = 2
shortcut = null
group = null
flat = false
align = 1
_sections_unfolded = [ "custom_fonts" ]
[node name="rotate_counterclockwise" type="Control" parent="bindings" index="3"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 226.0
margin_top = 101.0
margin_right = 266.0
margin_bottom = 141.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 2
size_flags_vertical = 2
[node name="Label" type="Label" parent="bindings/rotate_counterclockwise" index="0"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 5.0
margin_top = -2.0
margin_right = 177.0
margin_bottom = 37.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 2
mouse_default_cursor_shape = 0
size_flags_horizontal = 2
size_flags_vertical = 0
custom_fonts/font = SubResource( 2 )
custom_colors/font_color = Color( 0.752941, 0.921569, 0.988235, 1 )
text = "rotate
counter
clockwise"
valign = 1
percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
[node name="Button" type="Button" parent="bindings/rotate_counterclockwise" index="1"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 84.0
margin_top = -1.0
margin_right = 159.0
margin_bottom = 36.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
focus_mode = 2
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 2
size_flags_vertical = 2
custom_fonts/font = ExtResource( 4 )
toggle_mode = false
enabled_focus_mode = 2
shortcut = null
group = null
flat = false
align = 1
_sections_unfolded = [ "Rect" ]
[node name="soft_drop" type="Control" parent="bindings" index="4"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 50.0
margin_top = 150.0
margin_right = 90.0
margin_bottom = 190.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 2
size_flags_vertical = 2
[node name="Label" type="Label" parent="bindings/soft_drop" index="0"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_right = 66.0
margin_bottom = 32.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 2
mouse_default_cursor_shape = 0
size_flags_horizontal = 2
size_flags_vertical = 0
custom_fonts/font = SubResource( 2 )
custom_colors/font_color = Color( 0.752941, 0.921569, 0.988235, 1 )
text = "soft
drop"
valign = 1
percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
[node name="Button" type="Button" parent="bindings/soft_drop" index="1"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 80.0
margin_right = 155.0
margin_bottom = 37.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
focus_mode = 2
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 2
size_flags_vertical = 2
custom_fonts/font = ExtResource( 4 )
toggle_mode = false
enabled_focus_mode = 2
shortcut = null
group = null
flat = false
align = 1
[node name="hard_drop" type="Control" parent="bindings" index="5"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 226.0
margin_top = 151.0
margin_right = 266.0
margin_bottom = 191.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 2
size_flags_vertical = 2
[node name="Label" type="Label" parent="bindings/hard_drop" index="0"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 5.0
margin_top = -1.0
margin_right = 75.0
margin_bottom = 31.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 2
mouse_default_cursor_shape = 0
size_flags_horizontal = 2
size_flags_vertical = 0
custom_fonts/font = SubResource( 2 )
custom_colors/font_color = Color( 0.752941, 0.921569, 0.988235, 1 )
text = "hard
drop"
valign = 1
percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
[node name="Button" type="Button" parent="bindings/hard_drop" index="1"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 84.0
margin_top = -1.0
margin_right = 159.0
margin_bottom = 36.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
focus_mode = 2
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 2
size_flags_vertical = 2
custom_fonts/font = ExtResource( 4 )
toggle_mode = false
enabled_focus_mode = 2
shortcut = null
group = null
flat = false
align = 1
[node name="hold" type="Control" parent="bindings" index="6"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 50.0
margin_top = 200.0
margin_right = 90.0
margin_bottom = 240.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 2
size_flags_vertical = 2
[node name="Label" type="Label" parent="bindings/hold" index="0"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_right = 55.0
margin_bottom = 40.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 2
mouse_default_cursor_shape = 0
size_flags_horizontal = 2
size_flags_vertical = 0
custom_fonts/font = SubResource( 2 )
custom_colors/font_color = Color( 0.752941, 0.921569, 0.988235, 1 )
text = "hold"
valign = 1
percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
[node name="Button" type="Button" parent="bindings/hold" index="1"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 80.0
margin_right = 155.0
margin_bottom = 37.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
focus_mode = 2
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 2
size_flags_vertical = 2
custom_fonts/font = ExtResource( 4 )
toggle_mode = false
enabled_focus_mode = 2
shortcut = null
group = null
flat = false
align = 1
[node name="pause" type="Control" parent="bindings" index="7"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 226.0
margin_top = 201.0
margin_right = 266.0
margin_bottom = 241.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 2
size_flags_vertical = 2
[node name="Label" type="Label" parent="bindings/pause" index="0"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 5.0
margin_top = 3.0
margin_right = 106.0
margin_bottom = 35.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 2
mouse_default_cursor_shape = 0
size_flags_horizontal = 2
size_flags_vertical = 0
custom_fonts/font = SubResource( 2 )
custom_colors/font_color = Color( 0.752941, 0.921569, 0.988235, 1 )
text = "pause
resume"
valign = 1
percent_visible = 1.0
lines_skipped = 0
max_lines_visible = -1
_sections_unfolded = [ "custom_fonts" ]
[node name="Button" type="Button" parent="bindings/pause" index="1"]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_left = 84.0
margin_top = -1.0
margin_right = 159.0
margin_bottom = 36.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
focus_mode = 2
mouse_filter = 0
mouse_default_cursor_shape = 0
size_flags_horizontal = 2
size_flags_vertical = 2
custom_fonts/font = ExtResource( 4 )
toggle_mode = false
enabled_focus_mode = 2
shortcut = null
group = null
flat = false
align = 1

View File

@@ -0,0 +1,11 @@
[gd_resource type="AudioBusLayout" format=2]
[resource]
bus/0/name = "Master"
bus/0/solo = false
bus/0/mute = false
bus/0/bypass_fx = false
bus/0/volume_db = 0.0
bus/0/send = ""

BIN
source/fonts/525-ROUN.TTF Normal file

Binary file not shown.

View File

@@ -0,0 +1,13 @@
[gd_resource type="DynamicFont" load_steps=2 format=2]
[ext_resource path="res://fonts/Gamer.ttf" type="DynamicFontData" id=1]
[resource]
size = 20
use_mipmaps = false
use_filter = false
extra_spacing_top = -4
font_data = ExtResource( 1 )
_sections_unfolded = [ "Extra Spacing", "Font", "Settings" ]

BIN
source/fonts/Gamer.ttf Normal file

Binary file not shown.

View File

@@ -0,0 +1,12 @@
[gd_resource type="DynamicFont" load_steps=2 format=2]
[ext_resource path="res://fonts/525-ROUN.TTF" type="DynamicFontData" id=1]
[resource]
size = 30
use_mipmaps = false
use_filter = false
font_data = ExtResource( 1 )
_sections_unfolded = [ "Font", "Settings" ]

BIN
source/icons/16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

BIN
source/icons/256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
source/icons/32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 B

BIN
source/icons/48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 B

BIN
source/icons/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

96
source/midi/ADSR.gd Normal file
View File

@@ -0,0 +1,96 @@
extends AudioStreamPlayer
"""
AudioStreamPlayer with ADSR
"""
var releasing = false
var instrument = null
var velocity = 0
var pitch_bend = 0
var mix_rate = 0
var using_timer = 0.0
var timer = 0.0
var current_volume = 0
var maximum_volume_db = -8.0
var minimum_volume_db = -108.0
var pan = 0.5
var ads_state = [
{ "time": 0, "volume": 1.0 },
{ "time": 0.2, "volume": 0.95 },
# { "time": 0.2, "jump_to": 0.0 }, # not implemented
]
var release_state = [
{ "time": 0, "volume": 0.8 },
{ "time": 0.01, "volume": 0.0 },
# { "time": 0.2, "jump_to": 0.0 }, # not implemented
]
func _ready( ):
self.stop( )
func set_instrument( instrument ):
self.instrument = instrument
self.mix_rate = instrument.mix_rate
self.stream = instrument.stream.duplicate( )
self.ads_state = instrument.ads_state
self.release_state = instrument.release_state
func play( ):
self.releasing = false
self.timer = 0.0
self.using_timer = 0.0
self.current_volume = self.ads_state[0].volume
self.stream.mix_rate = round( self.mix_rate * ( 1.0 + self.pitch_bend * 0.5 ) )
.play( 0.0 )
self._update_volume( )
func start_release( ):
self.releasing = true
self.current_volume = self.release_state[0].volume
self.timer = 0.0
self._update_volume( )
func set_pitch_bend( pb ):
self.pitch_bend = pb
var pos = self.get_playback_position( )
self.stream.mix_rate = round( self.mix_rate * ( 1.0 + self.pitch_bend * 0.5 ) )
.play( pos )
func _process( delta ):
if not self.playing:
return
self.timer += delta
self.using_timer += delta
# self.transform.origin.x = self.pan * self.get_viewport( ).size.x
# ADSR
var use_state = null
if self.releasing:
use_state = self.release_state
else:
use_state = self.ads_state
var all_states = use_state.size( )
var last_state = all_states - 1
if use_state[last_state].time <= self.timer:
self.current_volume = use_state[last_state].volume
if self.releasing:
self.stop( )
else:
for state_number in range( 1, all_states ):
var state = use_state[state_number]
if self.timer < state.time:
var pre_state = use_state[state_number-1]
var s = ( state.time - self.timer ) / ( state.time - pre_state.time )
var t = 1.0 - s
self.current_volume = pre_state.volume * s + state.volume * t
break
self._update_volume( )
func _update_volume( ):
var s = self.current_volume
var t = 1.0 - s
self.volume_db = s * self.maximum_volume_db + t * self.minimum_volume_db

15
source/midi/ADSR.tscn Normal file
View File

@@ -0,0 +1,15 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://midi/ADSR.gd" type="Script" id=1]
[node name="ADSR" type="AudioStreamPlayer"]
stream = null
volume_db = 0.0
autoplay = false
mix_target = 0
bus = "Master"
script = ExtResource( 1 )
_sections_unfolded = [ "Pause", "Transform", "Z Index" ]

326
source/midi/Bank.gd Normal file
View File

@@ -0,0 +1,326 @@
"""
Sound Bank by Yui Kinomoto @arlez80
"""
const drum_track_bank = 128
const SoundFont = preload( "SoundFont.gd" )
# デフォルト
var default_mix_rate_table = [819,868,920,974,1032,1094,1159,1228,1301,1378,1460,1547,1639,1736,1840,1949,2065,2188,2318,2456,2602,2756,2920,3094,3278,3473,3679,3898,4130,4375,4635,4911,5203,5513,5840,6188,6556,6945,7358,7796,8259,8751,9271,9822,10406,11025,11681,12375,13111,13891,14717,15592,16519,17501,18542,19644,20812,22050,23361,24750,26222,27781,29433,31183,33038,35002,37084,39289,41625,44100,46722,49501,52444,55563,58866,62367,66075,70004,74167,78577,83250,88200,93445,99001,104888,111125,117733,124734,132151,140009,148334,157155,166499,176400,186889,198002,209776,222250,235466,249467,264301,280018,296668,314309,332999,352800,373779,396005,419552,444500,470932,498935,528603,560035,593337,628618,665998,705600,747557,792009,839105,889000,941863,997869,1057205,1120070,1186673,1257236]
var default_ads_state = [
{ "time": 0, "volume": 1.0 },
{ "time": 0.2, "volume": 0.95 },
]
var default_release_state = [
{ "time": 0, "volume": 0.95 },
{ "time": 0.01, "volume": 0.0 },
]
# 音色テーブル
var presets = {}
"""
楽器
"""
func create_preset( ):
var instruments = []
for i in range( 0, 128 ):
instruments.append( null )
return {
"name": "",
"number": 0,
"instruments": instruments,
"bags": [],
}
"""
ノート
"""
func create_instrument( ):
return {
"mix_rate": 44100,
"stream": null,
"ads_state": default_ads_state,
"release_state": default_release_state,
"preset": null,
# "assine_group": 0, # reserved
}
"""
再生周波数計算
"""
func calc_mix_rate( rate, center_key, target_key ):
return round( rate * pow( 2.0, ( target_key - center_key ) / 12.0 ) )
"""
追加
"""
func set_preset_sample( program_number, base_sample, base_mix_rate ):
var mix_rate_table = default_mix_rate_table
if base_mix_rate != 44100:
print( "not implemented" )
breakpoint
var preset = self.create_preset( )
preset.name = "#%03d" % program_number
preset.number = program_number
for i in range(0,128):
var inst = self.create_instrument( )
inst.mix_rate = mix_rate_table[i]
inst.stream = base_sample
inst.preset = preset
preset.instruments[i] = inst
self.set_preset( program_number, preset )
"""
追加
"""
func set_preset( program_number, preset ):
self.presets[program_number] = preset
"""
指定した楽器を取得
"""
func get_preset( program_number, bank = 0 ):
var pc = program_number | ( bank << 7 )
if not self.presets.has( pc ):
if drum_track_bank == bank:
pc = drum_track_bank
else:
pc = program_number
if not self.presets.has( pc ):
pc = self.presets.keys( )[0]
var preset = self.presets[pc]
return preset
"""
サウンドフォント読み込み
"""
func read_soundfont( sf, need_program_numbers = null ):
var sf_insts = self._read_soundfont_pdta_inst( sf )
var bag_index = 0
var gen_index = 0
for phdr_index in range( 0, len( sf.pdta.phdr )-1 ):
var phdr = sf.pdta.phdr[phdr_index]
var preset = self.create_preset( )
var program_number = phdr.preset | ( phdr.bank << 7 )
preset.name = phdr.name
preset.number = program_number
var bag_next = sf.pdta.phdr[phdr_index+1].preset_bag_index
var bag_count = bag_index
while bag_count < bag_next:
var gen_next = sf.pdta.pbag[bag_count+1].gen_ndx
var gen_count = gen_index
var bag = {
"preset": preset,
"coarse_tune": 0,
"fine_tune": 0,
"key_range": null,
"instrument": null,
"pan": 0.5,
}
while gen_count < gen_next:
var gen = sf.pdta.pgen[gen_count]
match gen.gen_oper:
SoundFont.gen_oper_coarse_tune:
bag.coarse_tune = gen.amount
SoundFont.gen_oper_fine_tune:
bag.fine_tune = gen.amount
SoundFont.gen_oper_key_range:
bag.key_range = {
"high": gen.uamount >> 8,
"low": gen.uamount & 0xFF,
}
SoundFont.gen_oper_pan:
bag.pan = ( gen.amount + 500 ) / 1000.0
SoundFont.gen_oper_instrument:
bag.instrument = sf_insts[gen.uamount]
gen_count += 1
if bag.instrument != null:
preset.bags.append( bag )
gen_index = gen_next
bag_count += 1
bag_index = bag_next
# 追加するか?
if need_program_numbers != null:
if not( program_number in need_program_numbers ) and not( phdr.preset in need_program_numbers ):
continue
# 追加
self._read_soundfont_preset_compose_sample( sf, preset )
self.set_preset( program_number, preset )
func _read_soundfont_preset_compose_sample( sf, preset ):
"""
var samples = []
var zero_4bytes = PoolIntArray( [ 0, 0 ] )
for bag in preset.bags[0].instrument.bags:
var sample = PoolIntArray( )
var start = bag.sample.start + bag.sample_start_offset
var end = bag.sample.end + bag.sample_end_offset
for i in range( 0, ( end - start ) / 2 ):
sample.append_array( zero_4bytes )
samples.append( sample )
"""
for pbag_index in range( 0, preset.bags.size( ) ):
var pbag = preset.bags[pbag_index]
for ibag_index in range( 0, pbag.instrument.bags.size( ) ):
var ibag = pbag.instrument.bags[ibag_index]
var start = ibag.sample.start + ibag.sample_start_offset
var end = ibag.sample.end + ibag.sample_end_offset
var start_loop = ibag.sample.start_loop + ibag.sample_start_loop_offset
var end_loop = ibag.sample.end_loop + ibag.sample_end_loop_offset
var mix_rate = ibag.sample.sample_rate * pow( 2.0, ( pbag.coarse_tune + ibag.coarse_tune ) / 12.0 ) * pow( 2.0, ( pbag.fine_tune + ibag.sample.pitch_correction + ibag.fine_tune ) / 1200.0 )
var ass = AudioStreamSample.new( )
ass.data = sf.sdta.smpl.subarray( start * 2, end * 2 )
ass.format = AudioStreamSample.FORMAT_16_BITS
ass.mix_rate = mix_rate
ass.stereo = false #bag.sample.sample_type != SoundFont.sample_link_mono_sample
ass.loop_begin = start_loop - start
ass.loop_end = end_loop - start
if ibag.sample_modes == SoundFont.sample_mode_no_loop or ibag.sample_modes == SoundFont.sample_mode_unused_no_loop:
ass.loop_mode = AudioStreamSample.LOOP_DISABLED
else:
ass.loop_mode = AudioStreamSample.LOOP_FORWARD
var key_range = ibag.key_range
if pbag.key_range != null:
key_range = pbag.key_range
for key_number in range( key_range.low, key_range.high + 1 ):
#if preset.number == drum_track_bank << 7:
# if 36 <= key_number and key_number <= 40:
# print( key_number, " # ", ibag.sample.name );
if preset.instruments[key_number] != null:
continue
var instrument = self.create_instrument( )
instrument.preset = preset
if ibag.original_key == 255:
instrument.mix_rate = ibag.sample.sample_rate
else:
instrument.mix_rate = self.calc_mix_rate( mix_rate, ibag.original_key, key_number )
instrument.stream = ass
var a = ibag.adsr.attack_vol_env_time
var d = ibag.adsr.decay_vol_env_time
var s = ibag.adsr.sustain_vol_env_level
var r = ibag.adsr.release_vol_env_time
instrument.ads_state = [
{ "time": 0, "volume": 0.0 },
{ "time": a, "volume": 1.0 },
{ "time": a+d, "volume": s },
]
instrument.release_state = [
{ "time": 0, "volume": s },
{ "time": r, "volume": 0.0 },
]
preset.instruments[key_number] = instrument
func _read_soundfont_pdta_inst( sf ):
var sf_insts = []
var bag_index = 0
var gen_index = 0
for inst_index in range(0, len( sf.pdta.inst ) - 1 ):
var inst = sf.pdta.inst[inst_index]
var sf_inst = {"name": inst.name, "bags": [] }
var bag_next = sf.pdta.inst[inst_index+1].inst_bag_ndx
var bag_count = bag_index
var global_bag = {}
while bag_count < bag_next:
var bag = {
"sample": null,
"sample_id": -1,
"sample_start_offset": 0,
"sample_end_offset": 0,
"sample_start_loop_offset": 0,
"sample_end_loop_offset": 0,
"coarse_tune": 0,
"fine_tune": 0,
"original_key": 255,
"keynum": 0,
"sample_modes": 0,
"key_range": { "high": 127, "low": 0 },
"vel_range": { "high": 127, "low": 0 },
"adsr": {
"attack_vol_env_time": 0.001,
"decay_vol_env_time": 0.001,
"sustain_vol_env_level": 1.0,
"release_vol_env_time": 0.001,
},
}
var gen_next = sf.pdta.ibag[bag_count+1].gen_ndx
var gen_count = gen_index
while gen_count < gen_next:
var gen = sf.pdta.igen[gen_count]
match gen.gen_oper:
SoundFont.gen_oper_key_range:
bag.key_range.high = gen.uamount >> 8
bag.key_range.low = gen.uamount & 0xFF
SoundFont.gen_oper_vel_range:
bag.vel_range.high = gen.uamount >> 8
bag.vel_range.low = gen.uamount & 0xFF
SoundFont.gen_oper_overriding_root_key:
bag.original_key = gen.amount
SoundFont.gen_oper_start_addrs_offset:
bag.sample_start_offset += gen.amount
SoundFont.gen_oper_end_addrs_offset:
bag.sample_end_offset += gen.amount
SoundFont.gen_oper_start_addrs_coarse_offset:
bag.sample_start_offset += gen.amount * 32768
SoundFont.gen_oper_end_addrs_coarse_offset:
bag.sample_end_offset += gen.amount * 32768
SoundFont.gen_oper_startloop_addrs_offset:
bag.sample_start_loop_offset += gen.amount
SoundFont.gen_oper_endloop_addrs_offset:
bag.sample_end_loop_offset += gen.amount
SoundFont.gen_oper_startloop_addrs_coarse_offset:
bag.sample_start_loop_offset += gen.amount * 32768
SoundFont.gen_oper_endloop_addrs_coarse_offset:
bag.sample_end_loop_offset += gen.amount * 32768
SoundFont.gen_oper_coarse_tune:
bag.coarse_tune = gen.amount
SoundFont.gen_oper_fine_tune:
bag.fine_tune = gen.amount
SoundFont.gen_oper_keynum:
bag.keynum = gen.amount
SoundFont.gen_oper_attack_vol_env:
bag.adsr.attack_vol_env_time = pow( 2.0, gen.amount / 1200.0 )
SoundFont.gen_oper_decay_vol_env:
bag.adsr.decay_vol_env_time = pow( 2.0, gen.amount / 1200.0 )
SoundFont.gen_oper_release_vol_env:
bag.adsr.release_vol_env_time = pow( 2.0, gen.amount / 1200.0 )
SoundFont.gen_oper_sustain_vol_env:
var s = min( max( 0, gen.amount ), 1440 )
bag.adsr.sustain_vol_env_level = ( 1440.0 - s ) / 1440.0
SoundFont.gen_oper_sample_modes:
bag.sample_modes = gen.uamount
SoundFont.gen_oper_sample_id:
bag.sample_id = gen.uamount
bag.sample = sf.pdta.shdr[gen.amount]
if bag.original_key == 255:
bag.original_key = bag.sample.original_key
#_:
# print( gen.gen_oper )
gen_count += 1
# global zoneでない場合
if bag.sample != null:
sf_inst.bags.append( bag )
else:
global_bag = bag
gen_index = gen_next
bag_count += 1
sf_insts.append( sf_inst )
bag_index = bag_next
return sf_insts

140
source/midi/MOD.gd Normal file
View File

@@ -0,0 +1,140 @@
"""
MOD reader by Yui Kinomoto @arlez80
"""
"""
ファイルから読み込み
@param path File path
@return smf
"""
func read_file( path ):
var f = File.new( )
if not f.file_exists( path ):
print( "file %s is not found" % path )
breakpoint
f.open( path, f.READ )
var stream = StreamPeerBuffer.new( )
stream.set_data_array( f.get_buffer( f.get_len( ) ) )
stream.big_endian = true
f.close( )
return self._read( stream )
"""
配列から読み込み
@param data PoolByteArray
@return smf
"""
func read_data( data ):
var stream = StreamPeerBuffer.new( )
stream.set_data_array( data )
stream.big_endian = true
return self._read( stream )
"""
読み込み
@param stream
@return smf
"""
func _read( stream ):
var name = self._read_string( stream, 20 )
var samples = self._read_sample_informations( stream )
var song_length = stream.get_u8( )
var unknown_number = stream.get_u8( )
var song_positions = stream.get_partial_data( 128 )[1]
var max_song_position = 0
for sp in song_positions:
if max_song_position < sp:
max_song_position = sp
var magic = self._read_string( stream, 4 )
var channel_count = 4
match magic:
"6CHN":
channel_count = 6
"FLT8", "8CHN", "CD81", "OKTA":
channel_count = 8
"16CN":
channel_count = 16
"32CN":
channel_count = 32
_:
# print( "Unknown magic" )
# breakpoint
pass
var patterns = self._read_patterns( stream, max_song_position, channel_count )
self._read_sample_data( stream, samples )
return {
"name": name,
"song_length": song_length,
"unknown_number": unknown_number,
"song_positions": song_positions,
"magic": magic,
"patterns": patterns,
"samples": samples,
}
"""
サンプルのデータを読み込む
"""
func _read_sample_informations( stream ):
var samples = []
for i in range( 0, 31 ):
var sample = {}
sample.name = self._read_string( stream, 22 )
sample.length = stream.get_u16( ) * 2
sample.fine_tune = stream.get_u8( ) & 0x0F
if 0x08 < sample.fine_tune:
sample.fine_tune = 0x10 - sample.fine_tune
sample.volume = stream.get_u8( )
sample.loop_start = stream.get_u16( ) * 2
sample.loop_length = stream.get_u16( ) * 2
samples.append( sample )
return samples
"""
パターンを読み込む
"""
func _read_patterns( stream, max_position, channels ):
var patterns = []
for position in range( 0, max_position ):
var pattern = []
for i in range( 0, 64 ):
var line = []
for ch in range( 0, channels ):
var v1 = stream.get_u16( )
var v2 = stream.get_u16( )
var sample_number = ( ( v1 >> 8 ) & 0xF0 ) | ( ( v2 >> 12 ) & 0x0F )
var key_number = v1 & 0x0FFF
var effect_command = v2 & 0x0FFF
line.append({
"sample_number": sample_number,
"key_number": key_number,
"effect_command": effect_command,
})
pattern.append( line )
patterns.append( pattern )
"""
波形データ読み込む
"""
func _read_sample_data( stream, samples ):
for sample in samples:
sample.data = stream.get_partial_data( sample.length )[1]
"""
文字列の読み込み
@param stream Stream
@param size string size
@return string
"""
func _read_string( stream, size ):
return stream.get_partial_data( size )[1].get_string_from_ascii( )

400
source/midi/MidiPlayer.gd Normal file
View File

@@ -0,0 +1,400 @@
extends Node
const max_track = 16
const max_channel = 16
const max_note_number = 128
const max_program_number = 128
const drum_track_channel = 0x09
const drum_track_bank = 128
const ADSR = preload( "ADSR.tscn" )
const SMF = preload( "SMF.gd" )
const SoundFont = preload( "SoundFont.gd" )
const Bank = preload( "Bank.gd" )
export var max_polyphony = 64
export var file = ""
export var playing = false
export var channel_mute = [false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false]
export var play_speed = 1.0
export var volume_db = -30
export var key_shift = 0
export var loop = false
export var loop_start = 1.81
export var soundfont = ""
export var mix_target = AudioStreamPlayer.MIX_TARGET_STEREO
export var bus = "Master"
var smf_data = null
var tempo = 120 setget set_tempo
var seconds_to_timebase = 2.3
var position = 0
var last_position = 0
var track_status = null
var channel_status = []
var channel_volume_db = 20
var bank = null
var audio_stream_players = []
var _used_program_numbers = []
signal changed_tempo( tempo )
signal appeared_lyric( lyric )
signal appeared_marker( marker )
signal appeared_cue_point( cue_point )
signal looped
func _ready( ):
self._prepare_to_play( )
if self.playing:
self.play( )
"""
初期化
"""
func _prepare_to_play( ):
# ファイル読み込み
if self.smf_data == null:
var smf_reader = SMF.new( )
self.smf_data = smf_reader.read_file( self.file )
self._init_track( )
self._analyse_smf( )
self._init_channel( )
# 楽器
if self.bank == null:
self.bank = Bank.new( )
if self.soundfont != "":
var sf_reader = SoundFont.new( )
var sf2 = sf_reader.read_file( self.soundfont )
self.bank.read_soundfont( sf2, self._used_program_numbers )
# 発音機
if self.audio_stream_players.size( ) == 0:
for i in range( self.max_polyphony ):
var audio_stream_player = ADSR.instance( )
audio_stream_player.mix_target = self.mix_target
audio_stream_player.bus = self.bus
self.add_child( audio_stream_player )
self.audio_stream_players.append( audio_stream_player )
else:
for audio_stream_player in self.audio_stream_players:
audio_stream_player.mix_target = self.mix_target
audio_stream_player.bus = self.bus
"""
トラック初期化
"""
class TrackSorter:
static func sort(a, b):
if a.time < b.time:
return true
elif b.time < a.time:
return false
else:
if a.event.type == SMF.MIDIEventType.note_off:
return true
elif b.event.type == SMF.MIDIEventType.note_off:
return false
func _init_track( ):
self.track_status = {
"events": [],
"event_pointer": 0,
}
for track in self.smf_data.tracks:
self.track_status.events += track.events
if 1 < self.smf_data.tracks.size( ):
self.track_status.events.sort_custom( TrackSorter, "sort" )
self.last_position = self.track_status.events[len(self.track_status.events)-1].time
"""
SMF解析
"""
func _analyse_smf( ):
var channels = []
for i in range( max_channel ):
channels.append({
#"note_on": {},
#"program_number": 0,
"number": i,
"bank": 0,
})
self._used_program_numbers = []
for event_chunk in self.track_status.events:
var channel_number = event_chunk.channel_number
var channel = channels[channel_number]
var event = event_chunk.event
match event.type:
#SMF.MIDIEventType.note_off:
# channel.note_on.erase( event.note )
#SMF.MIDIEventType.note_on:
# channel.note_on[event.note] = true
SMF.MIDIEventType.program_change:
var program_number = event.number | ( channel.bank << 7 )
# channel.program_number = program_number
if not( event.number in self._used_program_numbers ):
self._used_program_numbers.append( event.number )
if not( program_number in self._used_program_numbers ):
self._used_program_numbers.append( program_number )
SMF.MIDIEventType.control_change:
match event.number:
SMF.control_number_bank_select_msb:
if channel.number == drum_track_channel:
channel.bank = drum_track_bank
else:
channel.bank = ( channel.bank & 0x7F ) | ( event.value << 7 )
SMF.control_number_bank_select_lsb:
if channel.number == drum_track_channel:
channel.bank = drum_track_bank
else:
channel.bank = ( channel.bank & 0x3F80 ) | ( event.value & 0x7F )
SMF.control_number_tkool_loop_point:
self.loop_start = event_chunk.time
"""
チャンネル初期化
"""
func _init_channel( ):
self.channel_status = []
for i in range( max_channel ):
var drum_track = ( i == drum_track_channel )
var bank = 0
if drum_track:
bank = self.drum_track_bank
self.channel_status.append({
"number": i,
"note_on": {},
"bank": bank,
"program": 0,
"volume": 1.0,
"expression": 1.0,
"pitch_bend": 0.0,
"drum_track": drum_track,
"pan": 0.5,
})
"""
再生
@param from_position
"""
func play( from_position = 0 ):
self.playing = true
self.seek( from_position )
"""
シーク
"""
func seek( to_position ):
self._stop_all_notes( )
self.position = to_position
var pointer = 0
var length = len(self.track_status.events)
while pointer < length:
var event_chunk = self.track_status.events[pointer]
if self.position < event_chunk.time:
break
pointer += 1
self.track_status.event_pointer = pointer
"""
停止
"""
func stop( ):
self._stop_all_notes( )
self.playing = false
"""
テンポ設定
"""
func set_tempo( bpm ):
tempo = bpm
self.seconds_to_timebase = tempo / 60.0
self.emit_signal( "changed_tempo", bpm )
"""
全音を止める
"""
func _stop_all_notes( ):
for audio_stream_player in self.audio_stream_players:
audio_stream_player.stop( )
for channel in self.channel_status:
channel.note_on = {}
"""
毎フレーム処理
"""
func _process( delta ):
if self.smf_data == null:
return
if not self.playing:
return
self.position += self.smf_data.timebase * delta * self.seconds_to_timebase * self.play_speed
self._process_track( )
"""
トラック処理
"""
func _process_track( ):
var track = self.track_status
if track.events == null:
return
var length = len(track.events)
if length <= track.event_pointer:
if self.loop:
self.seek( self.loop_start )
self.emit_signal( "looped" )
else:
self.playing = false
return
for channel in self.channel_status:
for key_number in channel.note_on.keys( ):
var note_on = channel.note_on[key_number]
if not note_on.playing:
channel.note_on.erase( key_number )
while track.event_pointer < length:
var event_chunk = track.events[track.event_pointer]
if self.position < event_chunk.time:
break
track.event_pointer += 1
var channel = self.channel_status[event_chunk.channel_number]
var event = event_chunk.event
match event.type:
SMF.MIDIEventType.note_off:
self._process_track_event_note_off( channel, event )
SMF.MIDIEventType.note_on:
self._process_track_event_note_on( channel, event )
SMF.MIDIEventType.program_change:
channel.program = event.number
SMF.MIDIEventType.control_change:
self._process_track_event_control_change( channel, event )
SMF.MIDIEventType.pitch_bend:
channel.pitch_bend = event.value / 8192.0 - 1.0
self._update_pitch_bend_note( channel )
SMF.MIDIEventType.system_event:
self._process_track_system_event( channel, event )
_:
# 無視
pass
func _process_track_event_note_off( channel, event ):
var key_number = event.note + self.key_shift
if channel.note_on.has( key_number ):
var note_player = channel.note_on[key_number]
if note_player != null:
note_player.start_release( )
channel.note_on.erase( key_number )
func _process_track_event_note_on( channel, event ):
if not self.channel_mute[channel.number]:
var key_number = event.note + self.key_shift
var note_volume = channel.volume * channel.expression * ( event.velocity / 127.0 )
var volume_db = note_volume * self.channel_volume_db - self.channel_volume_db + self.volume_db
var preset = self.bank.get_preset( channel.program, channel.bank )
var instrument = preset.instruments[key_number]
if instrument != null:
if channel.note_on.has( key_number ):
channel.note_on[key_number].start_release( )
var note_player = self._get_idle_player( channel.program )
if note_player != null:
note_player.velocity = event.velocity
note_player.maximum_volume_db = volume_db
note_player.pitch_bend = channel.pitch_bend
note_player.set_instrument( instrument )
note_player.play( )
if not channel.drum_track:
channel.note_on[key_number] = note_player
func _process_track_event_control_change( channel, event ):
match event.number:
SMF.control_number_volume:
channel.volume = event.value / 127.0
self._update_volume_note( channel )
SMF.control_number_expression:
channel.expression = event.value / 127.0
self._update_volume_note( channel )
SMF.control_number_pan:
channel.pan = event.value / 127.0
SMF.control_number_bank_select_msb:
if channel.drum_track:
channel.bank = self.drum_track_bank
else:
channel.bank = ( channel.bank & 0x7F ) | ( event.value << 7 )
SMF.control_number_bank_select_lsb:
if channel.drum_track:
channel.bank = self.drum_track_bank
else:
channel.bank = ( channel.bank & 0x3F80 ) | ( event.value & 0x7F )
_:
# 無視
pass
func _process_track_system_event( channel, event ):
match event.args.type:
SMF.MIDISystemEventType.set_tempo:
self.tempo = 60000000.0 / event.args.bpm
SMF.MIDISystemEventType.lyric:
self.emit_signal( "appeared_lyric", event.args.text )
SMF.MIDISystemEventType.marker:
self.emit_signal( "appeared_marker", event.args.text )
SMF.MIDISystemEventType.cue_point:
self.emit_signal( "appeared_cue_point", event.args.text )
_:
# 無視
pass
func _get_idle_player( program ):
var stopped_audio_stream_player = null
var minimum_volume = 100.0
var oldest_audio_stream_player = null
var oldest = 0.0
for audio_stream_player in self.audio_stream_players:
if not audio_stream_player.playing:
return audio_stream_player
if audio_stream_player.releasing and audio_stream_player.current_volume < minimum_volume:
stopped_audio_stream_player = audio_stream_player
minimum_volume = audio_stream_player.current_volume
if oldest < audio_stream_player.using_timer:
oldest_audio_stream_player = audio_stream_player
oldest = audio_stream_player.using_timer
if stopped_audio_stream_player != null:
return stopped_audio_stream_player
return oldest_audio_stream_player
func _update_volume_note( channel ):
for note in channel.note_on.values( ):
var note_volume = channel.volume * channel.expression * ( note.velocity / 127.0 )
var volume_db = note_volume * self.channel_volume_db - self.channel_volume_db + self.volume_db
note.maximum_volume_db = volume_db
func _update_pitch_bend_note( channel ):
for note in channel.note_on.values( ):
note.set_pitch_bend( channel.pitch_bend )
"""
現在発音中の音色数を返す
"""
func get_now_playing_polyphony( ):
var polyphony = 0
for audio_stream_player in self.audio_stream_players:
if audio_stream_player.playing:
polyphony += 1
return polyphony

View File

@@ -0,0 +1,30 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://midi/MidiPlayer.gd" type="Script" id=1]
[node name="MidiPlayer" type="Node" index="0"]
script = ExtResource( 1 )
max_polyphony = 64
file = ""
playing = false
channel_mute = [ false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false ]
play_speed = 1.0
volume_db = -30
key_shift = 0
loop = false
loop_start = 0
soundfont = ""
mix_target = 0
bus = "Master"
[node name="Default" type="AudioStreamPlayer" parent="." index="0"]
stream = null
volume_db = 0.0
pitch_scale = 1.0
autoplay = false
mix_target = 0
bus = "Master"

634
source/midi/SMF.gd Normal file
View File

@@ -0,0 +1,634 @@
"""
SMF reader/writer by Yui Kinomoto @arlez80
"""
# -----------------------------------------------------------------------------
# 定数
# Control Numbers
const control_number_bank_select_msb = 0x00
const control_number_modulation = 0x01
const control_number_breath_controller = 0x02
const control_number_foot_controller = 0x04
const control_number_portamento_time = 0x05
const control_number_data_entry = 0x06
const control_number_volume = 0x07
const control_number_balance = 0x08
const control_number_pan = 0x0A
const control_number_expression = 0x0B
const control_number_bank_select_lsb = 0x20
const control_number_modulation_lsb = 0x21
const control_number_breath_controller_lsb = 0x22
const control_number_foot_controller_lsb = 0x24
const control_number_portamento_time_lsb = 0x25
const control_number_data_entry_lsb = 0x26
const control_number_channel_volume_lsb = 0x27
const control_number_calance_lsb = 0x28
const control_number_pan_lsb = 0x2A
const control_number_expression_lsb = 0x2B
const control_number_hold = 0x40
const control_number_portament = 0x41
const control_number_sostenuto = 0x42
const control_number_soft_pedal = 0x43
const control_number_legato_foot_switch = 0x44
const control_number_freeze = 0x45
const control_number_sound_variation = 0x46
const control_number_timbre = 0x47
const control_number_release_time = 0x48
const control_number_attack_time = 0x49
const control_number_brightness = 0x4A
const control_number_vibrato_rate = 0x4B
const control_number_vibrato_depth = 0x4C
const control_number_vibrato_delay = 0x4D
const control_number_nrpn_lsb = 0x62
const control_number_nrpn_msb = 0x63
const control_number_rpn_lsb = 0x64
const control_number_rpn_msb = 0x65
const control_number_tkool_loop_point = 0x6F # CC111
# Manufacture ID
const manufacture_id_universal_nopn_realtime_sys_ex = 0x7E
const manufacture_id_universal_realtime_sys_ex = 0x7F
const manufacture_id_kawai_musical_instruments_mfg_co_ltd = 0x40
const manufacture_id_roland_corporation = 0x41
const manufacture_id_korg_inc = 0x42
const manufacture_id_yamaha_corporation = 0x43
const manufacture_id_casio_computer_co_ltd = 0x44
const manufacture_id_kamiya_studio_co_ltd = 0x46
const manufacture_id_akai_electric_co_ltd = 0x47
# Enums
enum MIDIEventType {
note_off, # 8*
note_on, # 9*
polyphonic_key_pressure, # A*
control_change, # B*
program_change, # C*
channel_pressure, # D*
pitch_bend, # E*
system_event, # F*
}
enum MIDISystemEventType {
sys_ex,
divided_sys_ex,
text_event, # 01
copyright, # 02
track_name, # 03
instrument_name, # 04
lyric, # 05
marker, # 06
cue_point, # 07
midi_channel_prefix, # 20
end_of_track, # 2F
set_tempo, # 51
smpte_offset, # 54
beat, # 58
key, # 59
unknown,
}
# -----------------------------------------------------------------------------
# 読み込み : Reader
var last_event_type
"""
ファイルから読み込み
@param path File path
@return smf or null(read error)
"""
func read_file( path ):
var f = File.new( )
if not f.file_exists( path ):
print( "file %s is not found" % path )
breakpoint
f.open( path, f.READ )
var stream = StreamPeerBuffer.new( )
stream.set_data_array( f.get_buffer( f.get_len( ) ) )
stream.big_endian = true
f.close( )
return self._read( stream )
"""
配列から読み込み
@param data PoolByteArray
@return smf or null(read error)
"""
func read_data( data ):
var stream = StreamPeerBuffer.new( )
stream.set_data_array( data )
stream.big_endian = true
return self._read( stream )
"""
読み込み
@param input
@return smf
"""
func _read( input ):
var header = self._read_chunk_data( input )
if header.id != "MThd" and header.size != 6:
print( "expected MThd header" )
return null
var format_type = header.stream.get_u16( )
var track_count = header.stream.get_u16( )
var timebase = header.stream.get_u16( )
var tracks = []
for i in range( 0, track_count ):
var track = self._read_track( input, i )
if track == null:
return null
tracks.append( track )
return {
"format_type": format_type,
"track_count": track_count,
"timebase": timebase,
"tracks": tracks,
}
"""
トラックの読み込み
@param input
@param track_number トラックナンバー
@return track data or null(read error)
"""
func _read_track( input, track_number ):
var track_chunk = self._read_chunk_data( input )
if track_chunk.id != "MTrk":
print( "Unknown chunk: " + track_chunk.id )
return null
var stream = track_chunk.stream
var time = 0
var events = []
while 0 < stream.get_available_bytes( ):
var delta_time = self._read_variable_int( stream )
time += delta_time
var event_type_byte = stream.get_u8( )
var event
if self._is_system_event( event_type_byte ):
var args = self._read_system_event( stream, event_type_byte )
if args == null: return null
event = {
"type": MIDIEventType.system_event,
"args": args
}
else:
event = self._read_event( stream, event_type_byte )
if event == null: return null
if ( event_type_byte & 0x80 ) == 0:
event_type_byte = self.last_event_type
events.append({
"time": time,
"channel_number": event_type_byte & 0x0f,
"event": event,
})
return {
"track_number": track_number,
"events": events,
}
"""
システムイベントか否かを返す
@param b event type
@return システムイベントならtrueを返す
"""
func _is_system_event( b ):
return ( b & 0xf0 ) == 0xf0
"""
システムイベントの読み込み
"""
func _read_system_event( stream, event_type_byte ):
if event_type_byte == 0xff:
var meta_type = stream.get_u8( )
var size = self._read_variable_int( stream )
match meta_type:
0x01:
return { "type": MIDISystemEventType.text_event, "text": self._read_string( stream, size ) }
0x02:
return { "type": MIDISystemEventType.copyright, "text": self._read_string( stream, size ) }
0x03:
return { "type": MIDISystemEventType.track_name, "text": self._read_string( stream, size ) }
0x04:
return { "type": MIDISystemEventType.instrument_name, "text": self._read_string( stream, size ) }
0x05:
return { "type": MIDISystemEventType.lyric, "text": self._read_string( stream, size ) }
0x06:
return { "type": MIDISystemEventType.marker, "text": self._read_string( stream, size ) }
0x07:
return { "type": MIDISystemEventType.cue_point, "text": self._read_string( stream, size ) }
0x20:
if size != 1:
print( "MIDI Channel Prefix length is not 1" )
return null
return { "type": MIDISystemEventType.midi_channel_prefix, "prefix": stream.get_u8( ) }
0x2F:
if size != 0:
print( "End of track with unknown data" )
return null
return { "type": MIDISystemEventType.end_of_track }
0x51:
if size != 3:
print( "Tempo length is not 3" )
return null
# beat per microseconds
var bpm = stream.get_u8( ) << 16
bpm |= stream.get_u8( ) << 8
bpm |= stream.get_u8( )
return { "type": MIDISystemEventType.set_tempo, "bpm": bpm }
0x54:
if size != 5:
print( "SMPTE length is not 5" )
return null
var hr = stream.get_u8( )
var mm = stream.get_u8( )
var se = stream.get_u8( )
var fr = stream.get_u8( )
var ff = stream.get_u8( )
return {
"type": MIDISystemEventType.smpte_offset,
"hr": hr,
"mm": mm,
"se": se,
"fr": fr,
"ff": ff,
}
0x58:
if size != 4:
print( "Beat length is not 4" )
return null
var numerator = stream.get_u8( )
var denominator = stream.get_u8( )
var clock = stream.get_u8( )
var beat32 = stream.get_u8( )
return {
"type": MIDISystemEventType.beat,
"numerator": numerator,
"denominator": denominator,
"clock": clock,
"beat32": beat32,
}
0x59:
if size != 2:
print( "Key length is not 2" )
return null
var sf = stream.get_u8( )
var minor = stream.get_u8( ) == 1
return {
"type": MIDISystemEventType.key,
"sf": sf,
"minor": minor,
}
_:
return {
"type": MIDISystemEventType.unknown,
"meta_type": meta_type,
"data": stream.get_partial_data( size )[1],
}
elif event_type_byte == 0xf0:
var size = self._read_variable_int( stream )
return {
"type": MIDISystemEventType.sys_ex,
"data": stream.get_partial_data( size )[1],
}
elif event_type_byte == 0xf7:
var size = self._read_variable_int( stream )
return {
"type": MIDISystemEventType.divided_sys_ex,
"data": stream.get_partial_data( size )[1],
}
print( "Unknown system event type: %x" % event_type_byte )
return null
"""
通常のイベント読み込み
@param stream
@param event_type_byte
@return MIDIEvent
"""
func _read_event( stream, event_type_byte ):
var param = 0
if ( event_type_byte & 0x80 ) == 0:
# running status
param = event_type_byte
event_type_byte = self.last_event_type
else:
param = stream.get_u8( )
self.last_event_type = event_type_byte
var event_type = event_type_byte & 0xf0
match event_type:
0x80:
return {
"type": MIDIEventType.note_off,
"note": param,
"velocity": stream.get_u8( ),
}
0x90:
var velocity = stream.get_u8( )
if velocity == 0:
# velocity0のnote_onはnote_off扱いにする
return {
"type": MIDIEventType.note_off,
"note": param,
"velocity": velocity,
}
else:
return {
"type": MIDIEventType.note_on,
"note": param,
"velocity": velocity,
}
0xA0:
return {
"type": MIDIEventType.polyphonic_key_pressure,
"note": param,
"value": stream.get_u8( ),
}
0xB0:
return {
"type": MIDIEventType.control_change,
"number": param,
"value": stream.get_u8( ),
}
0xC0:
return {
"type": MIDIEventType.program_change,
"number": param,
}
0xD0:
return {
"type": MIDIEventType.channel_pressure,
"value": param,
}
0xE0:
return {
"type": MIDIEventType.pitch_bend,
"value": param | ( stream.get_u8( ) << 7 ),
}
print( "unknown event type: %d" % event_type_byte )
return null
"""
可変長数値の読み込み
@param stream
@return 数値
"""
func _read_variable_int( stream ):
var result = 0
while true:
var c = stream.get_u8( )
if ( c & 0x80 ) != 0:
result |= c & 0x7f
result <<= 7
else:
result |= c
break
return result
"""
チャンクデータの読み込み
@param stream Stream
@return chunk data
"""
func _read_chunk_data( stream ):
var id = self._read_string( stream, 4 )
var size = stream.get_32( )
var new_stream = StreamPeerBuffer.new( )
new_stream.set_data_array( stream.get_partial_data( size )[1] )
new_stream.big_endian = true
return {
"id": id,
"size": size,
"stream": new_stream
}
"""
文字列の読み込み
@param stream Stream
@param size string size
@return string
"""
func _read_string( stream, size ):
return stream.get_partial_data( size )[1].get_string_from_ascii( )
# -----------------------------------------------------------------------------
# 書き込み: Writer
"""
書き込む
@param smf SMF structure
@return PoolByteArray
"""
func write( smf ):
var stream = StreamPeerBuffer.new( )
stream.big_endian = true
stream.put_utf8_string( "MThd".to_ascii( ) )
stream.put_u32( 6 )
stream.put_u16( smf.format_type )
stream.put_u16( len( smf.tracks ) )
stream.put_u16( smf.timebase )
for t in smf.tracks:
self._write_track( stream, t )
return stream.get_partial_data( stream.get_available_bytes( ) )[1]
"""
トラックデータソート用
"""
class TrackEventSorter:
static func sort( a, b ):
if a.time < b.time:
return true
return false
"""
可変長数字を書き込む
@param stream
@param i
"""
func _write_variable_int( stream, i ):
while true:
var v = i & 0x7f
i >>= 7
if i != 0:
stream.put_u8( v | 0x80 )
else:
stream.put_u8( v )
break
"""
トラックデータを書き込む
@param stream
@param track
"""
func _write_track( stream, track ):
var events = track.events.duplicate( )
events.sort_custom( TrackEventSorter, "sort" )
var buf = StreamPeerBuffer.new( )
buf.big_endian = true
var time = 0
for e in events:
self._write_variable_int( buf, e.time - time )
time = e.time
match e.type:
MIDIEventType.note_off:
buf.put_u8( 0x80 | e.channel_number )
buf.put_u8( e.note )
buf.put_u8( e.velocity )
MIDIEventType.note_on:
buf.put_u8( 0x90 | e.channel_number )
buf.put_u8( e.note )
buf.put_u8( e.velocity )
MIDIEventType.polyphonic_key_pressure:
buf.put_u8( 0xA0 | e.channel_number )
buf.put_u8( e.note )
buf.put_u8( e.value )
MIDIEventType.control_change:
buf.put_u8( 0xB0 | e.channel_number )
buf.put_u8( e.number )
buf.put_u8( e.value )
MIDIEventType.program_change:
buf.put_u8( 0xC0 | e.channel_number )
buf.put_u8( e.number )
MIDIEventType.channel_pressure:
buf.put_u8( 0xD0 | e.channel_number )
buf.put_u8( e.value )
MIDIEventType.pitch_bend:
buf.put_u8( 0xE0 | e.channel_number )
buf.put_u8( e.value & 0x7f )
buf.put_u8( ( e.value >> 7 ) & 0x7f )
MIDIEventType.system_event:
self._write_system_event( buf, e )
var track_size = buf.get_available_bytes( )
stream.put_utf8_string( "MTrk".to_ascii( ) )
stream.put_u32( track_size )
stream.put_data( buf.get_partial_data( track_size )[1] )
"""
システムイベント書き込み
@param stream
@param event
"""
func _write_system_event( stream, event ):
match event.type:
MIDISystemEventType.sys_ex:
stream.put_u8( 0xF0 )
self._write_variable_int( stream, len( event.data ) )
stream.put_data( event.data )
MIDISystemEventType.divided_sys_ex:
stream.put_u8( 0xF7 )
self._write_variable_int( stream, len( event.data ) )
stream.put_data( event.data )
MIDISystemEventType.text_event:
stream.put_u8( 0xFF )
stream.put_u8( 0x01 )
self._write_variable_int( stream, len( event.text ) )
stream.put_data( event.text.to_ascii( ) )
MIDISystemEventType.copyright:
stream.put_u8( 0xFF )
stream.put_u8( 0x02 )
self._write_variable_int( stream, len( event.text ) )
stream.put_data( event.text.to_ascii( ) )
MIDISystemEventType.track_name:
stream.put_u8( 0xFF )
stream.put_u8( 0x03 )
self._write_variable_int( stream, len( event.text ) )
stream.put_data( event.text.to_ascii( ) )
MIDISystemEventType.instrument_name:
stream.put_u8( 0xFF )
stream.put_u8( 0x04 )
self._write_variable_int( stream, len( event.text ) )
stream.put_data( event.text.to_ascii( ) )
MIDISystemEventType.lyric:
stream.put_u8( 0xFF )
stream.put_u8( 0x05 )
self._write_variable_int( stream, len( event.text ) )
stream.put_data( event.text.to_ascii( ) )
MIDISystemEventType.marker:
stream.put_u8( 0xFF )
stream.put_u8( 0x06 )
self._write_variable_int( stream, len( event.text ) )
stream.put_data( event.text.to_ascii( ) )
MIDISystemEventType.cue_point:
stream.put_u8( 0xFF )
stream.put_u8( 0x07 )
self._write_variable_int( stream, len( event.text ) )
stream.put_data( event.text.to_ascii( ) )
MIDISystemEventType.midi_channel_prefix:
stream.put_u8( 0xFF )
stream.put_u8( 0x20 )
stream.put_u8( 0x01 )
stream.put_u8( event.prefix )
MIDISystemEventType.end_of_track:
stream.put_u8( 0xFF )
stream.put_u8( 0x2F )
stream.put_u8( 0x00 )
MIDISystemEventType.set_tempo:
stream.put_u8( 0xFF )
stream.put_u8( 0x51 )
stream.put_u8( 0x03 )
stream.put_u8( ( event.bpm >> 16 ) & 0xFF )
stream.put_u8( ( event.bpm >> 8 ) & 0xFF )
stream.put_u8( event.bpm & 0xFF )
MIDISystemEventType.smpte_offset:
stream.put_u8( 0xFF )
stream.put_u8( 0x54 )
stream.put_u8( 0x05 )
stream.put_u8( event.hr )
stream.put_u8( event.mm )
stream.put_u8( event.se )
stream.put_u8( event.fr )
stream.put_u8( event.ff )
MIDISystemEventType.beat:
stream.put_u8( 0xFF )
stream.put_u8( 0x58 )
stream.put_u8( 0x04 )
stream.put_u8( event.numerator )
stream.put_u8( event.denominator )
stream.put_u8( event.clock )
stream.put_u8( event.beat32 )
MIDISystemEventType.key:
stream.put_u8( 0xFF )
stream.put_u8( 0x59 )
stream.put_u8( 0x02 )
stream.put_u8( event.sf )
stream.put_u8( 1 if event.minor else 0 )
MIDISystemEventType.unknown:
stream.put_u8( 0xFF )
stream.put_u8( event.meta_type )
stream.put_u8( len( event.data ) )
stream.put_data( event.data )

493
source/midi/SoundFont.gd Normal file
View File

@@ -0,0 +1,493 @@
"""
SoundFont Reader by Yui Kinomoto @arlez80
"""
"""
SampleLink
"""
const sample_link_mono_sample = 1
const sample_link_right_sample = 2
const sample_link_left_sample = 4
const sample_link_linked_sample = 8
const sample_link_rom_mono_sample = 0x8001
const sample_link_rom_right_sample = 0x8002
const sample_link_rom_left_sample = 0x8004
const sample_link_rom_linked_sample = 0x8008
"""
GenerateOperator
"""
const gen_oper_start_addrs_offset = 0
const gen_oper_end_addrs_offset = 1
const gen_oper_startloop_addrs_offset = 2
const gen_oper_endloop_addrs_offset = 3
const gen_oper_start_addrs_coarse_offset = 4
const gen_oper_mod_lfo_to_pitch = 5
const gen_oper_vib_lfo_to_pitch = 6
const gen_oper_mod_env_to_pitch = 7
const gen_oper_initial_filter_fc = 8
const gen_oper_initial_filter_q = 9
const gen_oper_mod_lfo_to_filter_fc = 10
const gen_oper_mod_env_to_filter_fc = 11
const gen_oper_end_addrs_coarse_offset = 12
const gen_oper_mod_lfo_to_volume = 13
const gen_oper_unused1 = 14
const gen_oper_chorus_effects_send = 15
const gen_oper_reverb_effects_send = 16
const gen_oper_pan = 17
const gen_oper_unused2 = 18
const gen_oper_unused3 = 19
const gen_oper_unused4 = 20
const gen_oper_delay_mod_lfo = 21
const gen_oper_freq_mod_lfo = 22
const gen_oper_delay_vib_lfo = 23
const gen_oper_freq_vib_lfo = 24
const gen_oper_delay_mod_env = 25
const gen_oper_attack_mod_env = 26
const gen_oper_hold_mod_env = 27
const gen_oper_decay_mod_env = 28
const gen_oper_sustain_mod_env = 29
const gen_oper_release_mod_env = 30
const gen_oper_keynum_to_mod_env_hold = 31
const gen_oper_keynum_to_mod_env_decay = 32
const gen_oper_delay_vol_env = 33
const gen_oper_attack_vol_env = 34
const gen_oper_hold_vol_env = 35
const gen_oper_decay_vol_env = 36
const gen_oper_sustain_vol_env = 37
const gen_oper_release_vol_env = 38
const gen_oper_keynum_to_vol_env_hold = 39
const gen_oper_keynum_to_vol_env_decay = 40
const gen_oper_instrument = 41
const gen_oper_reserved1 = 42
const gen_oper_key_range = 43
const gen_oper_vel_range = 44
const gen_oper_startloop_addrs_coarse_offset = 45
const gen_oper_keynum = 46
const gen_oper_velocity = 47
const gen_oper_initial_attenuation = 48
const gen_oper_reserved2 = 49
const gen_oper_endloop_addrs_coarse_offset = 50
const gen_oper_coarse_tune = 51
const gen_oper_fine_tune = 52
const gen_oper_sample_id = 53
const gen_oper_sample_modes = 54
const gen_oper_reserved3 = 55
const gen_oper_scale_tuning = 56
const gen_oper_exclusive_class = 57
const gen_oper_overriding_root_key = 58
const gen_oper_unused5 = 59
const gen_oper_end_oper = 60
"""
SampleMode
"""
const sample_mode_no_loop = 0
const sample_mode_loop_continuously = 1
const sample_mode_unused_no_loop = 2
const sample_mode_loop_ends_by_key_depression = 3
"""
ファイルから読み込み
@param path File path
@return smf
"""
func read_file( path ):
var f = File.new( )
if not f.file_exists( path ):
print( "file %s is not found" % path )
breakpoint
f.open( path, f.READ )
var stream = StreamPeerBuffer.new( )
stream.set_data_array( f.get_buffer( f.get_len( ) ) )
stream.big_endian = false
f.close( )
return self._read( stream )
"""
配列から読み込み
@param data PoolByteArray
@return smf
"""
func read_data( data ):
var stream = StreamPeerBuffer.new( )
stream.set_data_array( data )
stream.big_endian = false
return self._read( stream )
"""
読み込み
@param input
@return SoundFont
"""
func _read( input ):
self._check_chunk( input, "RIFF" )
self._check_header( input, "sfbk" )
var info = self._read_info( input )
var sdta = self._read_sdta( input )
var pdta = self._read_pdta( input )
return {
"info": info,
"sdta": sdta,
"pdta": pdta,
}
"""
チャンクチェック
@param input
@param hdr
"""
func _check_chunk( input, hdr ):
self._check_header( input, hdr )
input.get_32( )
"""
ヘッダーチェック
@param input
@param hdr
"""
func _check_header( input, hdr ):
var chk = input.get_string( 4 )
if hdr != chk:
print( "Doesn't exist " + hdr + " header" )
breakpoint
"""
チャンク読み込み
@param input
@param needs_header
@param chunk
"""
func _read_chunk( stream, needs_header = null ):
var header = stream.get_string( 4 )
if needs_header != null:
if needs_header != header:
print( "Doesn't exist " + needs_header + " header" )
breakpoint
var size = stream.get_u32( )
var new_stream = StreamPeerBuffer.new( )
new_stream.set_data_array( stream.get_partial_data( size )[1] )
new_stream.big_endian = false
return {
"header": header,
"size": size,
"stream": new_stream,
}
"""
INFOチャンクを読み込む
@param stream
@param chunk
"""
func _read_info( stream ):
var chunk = self._read_chunk( stream, "LIST" )
self._check_header( chunk.stream, "INFO" )
var info = {
"ifil":null,
"isng":null,
"inam":null,
"irom":null,
"iver":null,
"icrd":null,
"ieng":null,
"iprd":null,
"icop":null,
"icmt":null,
"isft":null,
}
while 0 < chunk.stream.get_available_bytes( ):
var sub_chunk = self._read_chunk( chunk.stream )
match sub_chunk.header.to_lower( ):
"ifil":
info.ifil = self._read_version_tag( sub_chunk.stream )
"isng":
info.isng = sub_chunk.stream.get_string( sub_chunk.size )
"inam":
info.inam = sub_chunk.stream.get_string( sub_chunk.size )
"irom":
info.irom = sub_chunk.stream.get_string( sub_chunk.size )
"iver":
info.iver = self._read_version_tag( sub_chunk.stream )
"icrd":
info.icrd = sub_chunk.stream.get_string( sub_chunk.size )
"ieng":
info.ieng = sub_chunk.stream.get_string( sub_chunk.size )
"iprd":
info.iprd = sub_chunk.stream.get_string( sub_chunk.size )
"icop":
info.icop = sub_chunk.stream.get_string( sub_chunk.size )
"icmt":
info.icmt = sub_chunk.stream.get_string( sub_chunk.size )
"isft":
info.isft = sub_chunk.stream.get_string( sub_chunk.size )
_:
print( "unknown header" )
breakpoint
return info
"""
バージョンタグを読み込む
@param stream
@param chunk
"""
func _read_version_tag( stream ):
var major = stream.get_u16( )
var minor = stream.get_u16( )
return {
"major": major,
"minor": minor,
}
"""
SDTAを読み込む
@param stream
@param chunk
"""
func _read_sdta( stream ):
var chunk = self._read_chunk( stream, "LIST" )
self._check_header( chunk.stream, "sdta" )
var smpl = self._read_chunk( chunk.stream, "smpl" )
var smpl_bytes = smpl.stream.get_partial_data( smpl.size )[1]
var sm24_bytes = null
if 0 < chunk.stream.get_available_bytes( ):
var sm24_chunk = self._read_chunk( chunk.stream, "sm24" )
sm24_bytes = sm24_chunk.stream.get_partial_data( sm24_chunk.size )[1]
return {
"smpl": smpl_bytes,
"sm24": sm24_bytes,
}
"""
PDTAを読み込む
@param stream
@param chunk
"""
func _read_pdta( stream ):
var chunk = self._read_chunk( stream, "LIST" )
self._check_header( chunk.stream, "pdta" )
var phdr = self._read_pdta_phdr( chunk.stream )
var pbag = self._read_pdta_bag( chunk.stream )
var pmod = self._read_pdta_mod( chunk.stream )
var pgen = self._read_pdta_gen( chunk.stream )
var inst = self._read_pdta_inst( chunk.stream )
var ibag = self._read_pdta_bag( chunk.stream )
var imod = self._read_pdta_mod( chunk.stream )
var igen = self._read_pdta_gen( chunk.stream )
var shdr = self._read_pdta_shdr( chunk.stream )
return {
"phdr": phdr,
"pbag": pbag,
"pmod": pmod,
"pgen": pgen,
"inst": inst,
"ibag": ibag,
"imod": imod,
"igen": igen,
"shdr": shdr,
}
"""
phdr 読み込み
@param stream
@param chunk
"""
func _read_pdta_phdr( stream ):
var chunk = self._read_chunk( stream, "phdr" )
var phdrs = []
while 0 < chunk.stream.get_available_bytes( ):
var phdr = {
"name": "",
"preset": 0,
"bank": 0,
"preset_bag_index": 0,
"library": 0,
"genre": 0,
"morphology": 0,
}
phdr.name = chunk.stream.get_string( 20 )
phdr.preset = chunk.stream.get_u16( )
phdr.bank = chunk.stream.get_u16( )
phdr.preset_bag_index = chunk.stream.get_u16( )
phdr.library = chunk.stream.get_32( )
phdr.genre = chunk.stream.get_32( )
phdr.morphology = chunk.stream.get_32( )
phdrs.append( phdr )
return phdrs
"""
*bag読み込み
@param stream
@param chunk
"""
func _read_pdta_bag( stream ):
var chunk = self._read_chunk( stream )
var bags = []
if chunk.header.substr( 1, 3 ) != "bag":
print( "Doesn't exist *bag header." )
breakpoint
while 0 < chunk.stream.get_available_bytes( ):
var bag = {
"gen_ndx": 0,
"mod_ndx": 0,
}
bag.gen_ndx = chunk.stream.get_u16( )
bag.mod_ndx = chunk.stream.get_u16( )
bags.append( bag )
return bags
"""
*mod読み込み
@param stream
@param chunk
"""
func _read_pdta_mod( stream ):
var chunk = self._read_chunk( stream )
var mods = []
if chunk.header.substr( 1, 3 ) != "mod":
print( "Doesn't exist *mod header." )
breakpoint
while 0 < chunk.stream.get_available_bytes( ):
var mod = {
"src_oper": null,
"dest_oper": 0,
"amount": 0,
"amt_src_oper": null,
"trans_oper": 0,
}
mod.src_oper = self._read_pdta_modulator( chunk.stream.get_u16( ) )
mod.dest_oper = chunk.stream.get_u16( )
mod.amount = chunk.stream.get_u16( )
mod.amt_src_oper = self._read_pdta_modulator( chunk.stream.get_u16( ) )
mod.trans_oper = chunk.stream.get_u16( )
mods.append( mod )
return mods
"""
PDTA-Modulator 読み込み
@param stream
@param chunk
"""
func _read_pdta_modulator( u ):
return {
"type": ( u >> 10 ) & 0x3f,
"direction": ( u >> 8 ) & 0x01,
"polarity": ( u >> 9 ) & 0x01,
"controller": u & 0x7f,
"controllerPallete": ( u >> 7 ) & 0x01,
}
"""
gen 読み込み
@param stream
@param chunk
"""
func _read_pdta_gen( stream ):
var chunk = self._read_chunk( stream )
var gens = []
if chunk.header.substr( 1, 3 ) != "gen":
print( "Doesn't exist *gen header." )
breakpoint
while 0 < chunk.stream.get_available_bytes( ):
var gen = {
"gen_oper": 0,
"amount": 0,
"uamount": 0,
}
gen.gen_oper = chunk.stream.get_u16( )
var u = chunk.stream.get_u16( )
gen.uamount = u
gen.amount = u
if 32767 < u:
gen.amount = - ( 65536 - u )
gens.append( gen )
return gens
"""
inst読み込み
@param stream
@param chunk
"""
func _read_pdta_inst( stream ):
var chunk = self._read_chunk( stream, "inst" )
var insts = []
while 0 < chunk.stream.get_available_bytes( ):
var inst = {
"name": "",
"inst_bag_ndx": 0,
}
inst.name = chunk.stream.get_string( 20 )
inst.inst_bag_ndx = chunk.stream.get_u16( )
insts.append( inst )
return insts
"""
shdr 読み込み
@param stream
@param chunk
"""
func _read_pdta_shdr( stream ):
var chunk = self._read_chunk( stream, "shdr" )
var shdrs = []
while 0 < chunk.stream.get_available_bytes( ):
var shdr = {
"name": "",
"start": 0,
"end": 0,
"start_loop": 0,
"end_loop": 0,
"sample_rate": 0,
"original_key": 0,
"pitch_correction": 0,
"sample_link": 0,
"sample_type": 0,
}
shdr.name = chunk.stream.get_string( 20 )
shdr.start = chunk.stream.get_u32( )
shdr.end = chunk.stream.get_u32( )
shdr.start_loop = chunk.stream.get_u32( )
shdr.end_loop = chunk.stream.get_u32( )
shdr.sample_rate = chunk.stream.get_u32( )
shdr.original_key = chunk.stream.get_u8( )
shdr.pitch_correction = chunk.stream.get_8( )
shdr.sample_link = chunk.stream.get_u16( )
shdr.sample_type = chunk.stream.get_u16( )
shdrs.append( shdr )
return shdrs

Binary file not shown.

BIN
source/midi/TimGM6mb.sf2 Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 587 KiB

54
source/project.godot Normal file
View File

@@ -0,0 +1,54 @@
; 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=3
[application]
config/name="TETRIS 3000"
run/main_scene="res://Main.tscn"
boot_splash/image="res://splash.png"
config/icon="res://icons/48.png"
[display]
window/size/width=500
window/size/height=500
window/vsync/use_vsync=false
window/stretch/mode="2d"
window/stretch/aspect="expand"
window/stretch/shrink="1"
[gui]
theme/use_hidpi=true
[input]
hold=[ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777237,"unicode":0,"echo":false,"script":null)
]
pause=[ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777217,"unicode":0,"echo":false,"script":null)
]
hard_drop=[ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":32,"unicode":0,"echo":false,"script":null)
]
move_left=[ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777231,"unicode":0,"echo":false,"script":null)
]
soft_drop=[ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777234,"unicode":0,"echo":false,"script":null)
]
move_right=[ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777233,"unicode":0,"echo":false,"script":null)
]
rotate_clockwise=[ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777232,"unicode":0,"echo":false,"script":null)
]
rotate_counterclockwise=[ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777238,"unicode":0,"echo":false,"script":null)
]
toggle_fullscreen=[ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":true,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777221,"unicode":0,"echo":false,"script":null)
]
[rendering]
environment/default_clear_color=Color( 0, 0, 0, 1 )

BIN
source/splash.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB