Haphazard progress
This commit is contained in:
commit
b6b55bd2a5
|
@ -0,0 +1,3 @@
|
|||
# Do not include any ROMs
|
||||
*.sfc
|
||||
*.gba
|
|
@ -0,0 +1,54 @@
|
|||
extends Node2D
|
||||
|
||||
var PC = load('PC.tscn')
|
||||
var PCs = []
|
||||
var sfx_buttons = []
|
||||
|
||||
func _ready():
|
||||
Engine.set_target_fps(60)
|
||||
var strips = len(load_sprites.strip_textures) # * 4 / 5
|
||||
#var strip_divide = strips * 2 / 5
|
||||
var strip_divide = strips * 1 / 5
|
||||
for i in strips:
|
||||
PCs.append(PC.instance())
|
||||
#PCs[-1].set_position(Vector2((i%strip_divide)*16, (i/strip_divide)*24*11))
|
||||
PCs[-1].set_position(Vector2((i%strip_divide)*24, (i/strip_divide)*32))
|
||||
PCs[-1].material.set_shader_param('palette', load_sprites.character_battle_sprite_palette_textures[i])
|
||||
PCs[-1].texture = load_sprites.strip_textures[i]
|
||||
|
||||
add_child(PCs[-1])
|
||||
# PCs.append(PC.instance())
|
||||
# PCs[-1].set_position(Vector2(0, 2*24*11))
|
||||
# PCs[-1].material.set_shader_param('palette', load_sprites.character_battle_sprite_palette_textures[45])
|
||||
# PCs[-1].texture = load_sprites.weapon_textures['Fist']
|
||||
# add_child(PCs[-1])
|
||||
|
||||
for i in SoundLoader.INST_NUM:
|
||||
var btn = Button.new()
|
||||
btn.text = 'Play #%02X' % i
|
||||
btn.align = Button.ALIGN_CENTER
|
||||
btn.set_position(Vector2((i%8)*40, 200 + (i/8)*16))
|
||||
btn.set_scale(Vector2(0.5, 0.5))
|
||||
add_child(btn)
|
||||
btn.connect('pressed', SoundLoader, 'play_sample', [i])
|
||||
sfx_buttons.append(btn)
|
||||
for i in SoundLoader.SFX_NUM:
|
||||
var btn = Button.new()
|
||||
btn.text = 'SFX #%02X' % i
|
||||
btn.align = Button.ALIGN_CENTER
|
||||
btn.set_position(Vector2((i%8)*40, 290 + (i/8)*16))
|
||||
btn.set_scale(Vector2(0.5, 0.5))
|
||||
add_child(btn)
|
||||
btn.connect('pressed', SoundLoader, 'play_sfx', [i])
|
||||
sfx_buttons.append(btn)
|
||||
|
||||
|
||||
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||
#func _process(delta):
|
||||
# update()
|
||||
# pass
|
||||
|
||||
|
||||
func _on_OptionButton_item_selected(ID):
|
||||
for pc in PCs:
|
||||
pc.animation = ID
|
|
@ -0,0 +1,47 @@
|
|||
[gd_scene load_steps=4 format=2]
|
||||
|
||||
[ext_resource path="res://Node2D.gd" type="Script" id=2]
|
||||
|
||||
[sub_resource type="Shader" id=1]
|
||||
code = "shader_type canvas_item;
|
||||
//uniform usampler2D tex;
|
||||
uniform sampler2D palette;
|
||||
|
||||
void fragment() {
|
||||
//uint color_idx = textureLod(tex, UV, 0.0).r;
|
||||
uint color_idx = uint(textureLod(TEXTURE, UV, 0.0).r * 256.0);
|
||||
COLOR = texelFetch(palette, ivec2(int(color_idx), 0), 0);
|
||||
if (color_idx == uint(0))
|
||||
COLOR.a = 0.0;
|
||||
}
|
||||
"
|
||||
|
||||
[sub_resource type="ShaderMaterial" id=2]
|
||||
shader = SubResource( 1 )
|
||||
|
||||
[node name="Control" type="Control"]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
|
||||
[node name="Node2D" type="Node2D" parent="."]
|
||||
material = SubResource( 2 )
|
||||
position = Vector2( 16, 16 )
|
||||
scale = Vector2( 2, 2 )
|
||||
script = ExtResource( 2 )
|
||||
|
||||
[node name="OptionButton" type="OptionButton" parent="Node2D"]
|
||||
anchor_bottom = 0.67
|
||||
margin_top = 180.0
|
||||
margin_right = 100.0
|
||||
margin_bottom = 170.0
|
||||
rect_scale = Vector2( 0.5, 0.5 )
|
||||
text = "Stand"
|
||||
items = [ "Stand", null, false, 0, null, "Guard", null, false, 1, null, "Walk", null, false, 2, null, "Down", null, false, 3, null, "R_Swing", null, false, 4, null, "L_Swing", null, false, 5, null, "Cheer", null, false, 6, null, "Recoil", null, false, 7, null, "Chant", null, false, 8, null ]
|
||||
selected = 0
|
||||
|
||||
[node name="ColorRect" type="ColorRect" parent="."]
|
||||
visible = false
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
|
||||
[connection signal="item_selected" from="Node2D/OptionButton" to="Node2D" method="_on_OptionButton_item_selected"]
|
|
@ -0,0 +1,59 @@
|
|||
extends Node2D
|
||||
|
||||
enum Battle_Frame {
|
||||
STAND,
|
||||
GUARD,
|
||||
WALK,
|
||||
DOWN,
|
||||
R_SWING,
|
||||
L_SWING2,
|
||||
L_SWING,
|
||||
CHEER,
|
||||
RECOIL,
|
||||
CHANT1,
|
||||
CHANT2,
|
||||
}
|
||||
|
||||
enum Battle_Anim {
|
||||
STAND,
|
||||
GUARD,
|
||||
WALK,
|
||||
DOWN,
|
||||
R_SWING,
|
||||
L_SWING,
|
||||
CHEER,
|
||||
RECOIL,
|
||||
CHANT
|
||||
}
|
||||
|
||||
var texture: Texture
|
||||
var animation = Battle_Anim.STAND # Battle_Anim enums aren't real types in GDscript :/
|
||||
|
||||
const Animation_Frames = {
|
||||
Battle_Anim.STAND: [Battle_Frame.STAND],
|
||||
Battle_Anim.GUARD: [Battle_Frame.GUARD],
|
||||
Battle_Anim.WALK: [Battle_Frame.WALK, Battle_Frame.STAND],
|
||||
Battle_Anim.DOWN: [Battle_Frame.DOWN],
|
||||
Battle_Anim.R_SWING: [Battle_Frame.R_SWING, Battle_Frame.WALK],
|
||||
Battle_Anim.L_SWING: [Battle_Frame.L_SWING, Battle_Frame.L_SWING2],
|
||||
Battle_Anim.CHEER: [Battle_Frame.CHEER, Battle_Frame.STAND],
|
||||
Battle_Anim.RECOIL: [Battle_Frame.RECOIL],
|
||||
Battle_Anim.CHANT: [Battle_Frame.CHANT1, Battle_Frame.CHANT2]
|
||||
}
|
||||
|
||||
func _init():
|
||||
material = load_sprites.shader_material.duplicate()
|
||||
|
||||
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||
func _process(_delta):
|
||||
update()
|
||||
# pass
|
||||
|
||||
func _draw():
|
||||
var frames = Animation_Frames[animation]
|
||||
var frame = frames[int(globals.time*4) % len(frames)]
|
||||
var y = 0
|
||||
if frame == Battle_Frame.CHEER:
|
||||
y -= 1
|
||||
#draw_texture(texture, Vector2(0, 0))
|
||||
draw_texture_rect_region(texture, Rect2(0, y, 16, 24), Rect2(0, 24*frame, 16, 24))
|
|
@ -0,0 +1,7 @@
|
|||
[gd_scene load_steps=2 format=2]
|
||||
|
||||
[ext_resource path="res://PC.gd" type="Script" id=1]
|
||||
|
||||
[node name="Node2D" type="Node2D"]
|
||||
script = ExtResource( 1 )
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
[gd_resource type="Environment" load_steps=2 format=2]
|
||||
|
||||
[sub_resource type="ProceduralSky" id=1]
|
||||
|
||||
[resource]
|
||||
background_mode = 2
|
||||
background_sky = SubResource( 1 )
|
|
@ -0,0 +1,10 @@
|
|||
extends Node
|
||||
|
||||
var time = 0.0
|
||||
var time_mult = 1.0
|
||||
|
||||
func _ready():
|
||||
set_process(true)
|
||||
|
||||
func _process(delta):
|
||||
time += delta * time_mult
|
|
@ -0,0 +1,264 @@
|
|||
extends Node
|
||||
|
||||
var shader_material = load('res://palette_mat.tres')
|
||||
|
||||
var ROM_filename := 'FF5_SCC_WepTweaks_Inus_Dash.sfc' # 'Final Fantasy V (Japan).sfc'
|
||||
const offset_Character_Battle_Sprite_Tiles: int = 0x120000
|
||||
const offset_Character_Battle_Sprite_Palettes: int = 0x14A3C0
|
||||
const offset_Character_Battle_Sprite_Layouts: int = 0x14B997
|
||||
const num_Character_Battle_Sprite_Layouts: int = 11
|
||||
|
||||
const offset_Character_Battle_Sprite_Disabled_Palette: int = 0x00F867
|
||||
const offset_Character_Battle_Sprite_Stone_Palette: int = 0x00F807
|
||||
|
||||
const offset_Tiles_Fist: int = 0x11D710 # 3bpp tile
|
||||
|
||||
#var character_battle_sprite_tiles = []
|
||||
#var character_battle_sprite_palette_imgs = []
|
||||
var character_battle_sprite_palette_textures = []
|
||||
var character_battle_sprite_palette_disabled_texture: ImageTexture
|
||||
var character_battle_sprite_palette_stone_texture: ImageTexture
|
||||
var weapon_textures = {}
|
||||
|
||||
func texture_from_image(image: Image, flags: int = 0) -> ImageTexture:
|
||||
var tex = ImageTexture.new()
|
||||
tex.create_from_image(image, flags)
|
||||
tex.flags = Texture.FLAG_CONVERT_TO_LINEAR
|
||||
return tex
|
||||
|
||||
func bgr555_to_color(short: int) -> Color:
|
||||
var color = Color()
|
||||
color.a = 1
|
||||
color.r = ((short & 0x1F) / 31.0)
|
||||
color.g = (((short >> 5) & 0x1F) / 31.0)
|
||||
color.b = (((short >> 10) & 0x1F) / 31.0)
|
||||
return color
|
||||
|
||||
func generate_palette_rgbf(rom: File, offset: int, length: int = 16) -> Image:
|
||||
rom.seek(offset)
|
||||
var img := Image.new()
|
||||
img.create(length, 1, false, Image.FORMAT_RGBF)
|
||||
img.lock()
|
||||
for i in range(length):
|
||||
var color = bgr555_to_color(rom.get_16())
|
||||
img.set_pixel(i, 0, color)
|
||||
img.unlock()
|
||||
return img
|
||||
|
||||
func generate_palette_rgb8(rom: File, offset: int, length: int = 16) -> Image:
|
||||
# Implicit sRGB -> linear conversion on ImageTexture creation from RGB8 Image ruins this
|
||||
rom.seek(offset)
|
||||
var data = ByteArray(length*3)
|
||||
# img.lock()
|
||||
for i in length:
|
||||
var color := bgr555_to_color(rom.get_16())
|
||||
var j: int = i*3
|
||||
data[j] = color.r8
|
||||
data[j+1] = color.g8
|
||||
data[j+2] = color.b8
|
||||
var img := Image.new()
|
||||
img.create_from_data(length, 1, false, Image.FORMAT_RGB8, data)
|
||||
return img
|
||||
|
||||
func generate_palette_rgb5_a1(rom: File, offset: int, length: int = 16) -> Image:
|
||||
var data = ByteArray(length*2)
|
||||
rom.seek(offset)
|
||||
for i in range(length):
|
||||
var bgr555 := rom.get_16()
|
||||
var r := bgr555 & 0x1F
|
||||
var g := (bgr555 >> 5) & 0x1F
|
||||
var b := (bgr555 >> 10) & 0x1F
|
||||
var rgb5_a1 := (r << 11) | (g << 6) | (b << 1) | 1
|
||||
data[i*2] = rgb5_a1 & 0xFF
|
||||
data[(i*2)+1] = rgb5_a1 >> 8
|
||||
var img := Image.new()
|
||||
img.create_from_data(length, 1, false, Image.FORMAT_RGBA5551, data)
|
||||
return img
|
||||
|
||||
func generate_palette(rom: File, offset: int, length: int = 16) -> Image:
|
||||
return generate_palette_rgb5_a1(rom, offset, length)
|
||||
|
||||
func ByteArray(size: int) -> PoolByteArray:
|
||||
var arr = PoolByteArray()
|
||||
arr.resize(size)
|
||||
return arr
|
||||
|
||||
func gba_4bpp_to_tile(data: PoolByteArray) -> Image:
|
||||
var tdata := ByteArray(64)
|
||||
for i in range(32):
|
||||
tdata[i*2] = data[i] % 16
|
||||
tdata[i*2+1] = data[i] / 16
|
||||
var tile := Image.new()
|
||||
tile.create_from_data(8, 8, false, Image.FORMAT_R8, tdata)
|
||||
return tile
|
||||
|
||||
func snes_4plane_to_tile(data: PoolByteArray) -> Image:
|
||||
var tdata := ByteArray(64)
|
||||
for i in range(64):
|
||||
var j = (i/8)*2
|
||||
var x = 7 - (i%8)
|
||||
tdata[i] = (data[j] >> x & 1) | ((data[j+1] >> x & 1)<<1) | ((data[j+16] >> x & 1)<<2) | ((data[j+17] >> x & 1)<<3)
|
||||
var tile := Image.new()
|
||||
tile.create_from_data(8, 8, false, Image.FORMAT_R8, tdata)
|
||||
return tile
|
||||
|
||||
func snes_3plane_to_tile(data: PoolByteArray) -> Image:
|
||||
var tdata := ByteArray(64)
|
||||
for i in range(64):
|
||||
var j = (i/8)*2
|
||||
var x = 7 - (i%8)
|
||||
tdata[i] = (data[j] >> x & 1) | ((data[j+1] >> x & 1)<<1) | ((data[(i/8)+16] >> x & 1)<<2)
|
||||
var tile := Image.new()
|
||||
tile.create_from_data(8, 8, false, Image.FORMAT_R8, tdata)
|
||||
return tile
|
||||
|
||||
func snes_2plane_to_tile(data: PoolByteArray) -> Image:
|
||||
var tdata := ByteArray(64)
|
||||
for i in range(64):
|
||||
var j = (i/8)*2
|
||||
var x = 7 - (i%8)
|
||||
tdata[i] = (data[j] >> x & 1) | ((data[j+1] >> x & 1)<<1)
|
||||
var tile := Image.new()
|
||||
tile.create_from_data(8, 8, false, Image.FORMAT_R8, tdata)
|
||||
return tile
|
||||
|
||||
func snes_1plane_to_tile(data: PoolByteArray) -> Image:
|
||||
var tdata := ByteArray(64)
|
||||
for i in range(64):
|
||||
var x = 7 - (i%8)
|
||||
tdata[i] = (data[i/8] >> x & 1)
|
||||
var tile := Image.new()
|
||||
tile.create_from_data(8, 8, false, Image.FORMAT_R8, tdata)
|
||||
return tile
|
||||
|
||||
func snes_get_tile(rom: File, offset: int, length: int) -> Image:
|
||||
rom.seek(offset)
|
||||
var data := rom.get_buffer(length)
|
||||
var planes := length / 8
|
||||
match planes:
|
||||
4:
|
||||
return snes_4plane_to_tile(data)
|
||||
3:
|
||||
return snes_3plane_to_tile(data)
|
||||
2:
|
||||
return snes_2plane_to_tile(data)
|
||||
_:
|
||||
return snes_1plane_to_tile(data)
|
||||
|
||||
|
||||
func load_snes_rom(filename: String):
|
||||
var rom := File.new()
|
||||
var _error = rom.open(filename, File.READ)
|
||||
|
||||
rom.seek(offset_Character_Battle_Sprite_Layouts)
|
||||
var battle_strip_layouts = rom.get_buffer(num_Character_Battle_Sprite_Layouts * 6)
|
||||
# Character Battle Sprite Tiles
|
||||
for strip in range(0, 22*5):
|
||||
var tiles = []
|
||||
for i in range(0, 32*48, 32):
|
||||
tiles.append(snes_get_tile(rom, offset_Character_Battle_Sprite_Tiles + (strip*32*48) + i, 32))
|
||||
var strip_image = Image.new()
|
||||
strip_image.create(16, 24 * num_Character_Battle_Sprite_Layouts, false, Image.FORMAT_R8)
|
||||
for i in range(6 * num_Character_Battle_Sprite_Layouts):
|
||||
strip_image.blit_rect(tiles[battle_strip_layouts[i]], Rect2(0, 0, 8, 8), Vector2((i%2) * 8, (i/2) * 8))
|
||||
strip_images.append(strip_image)
|
||||
strip_textures.append(texture_from_image(strip_image))
|
||||
|
||||
# Character Battle Sprite Palettes
|
||||
for palette in range(0, 22*5):
|
||||
character_battle_sprite_palette_textures.append(texture_from_image(generate_palette(rom, offset_Character_Battle_Sprite_Palettes + (palette*32))))
|
||||
character_battle_sprite_palette_disabled_texture = texture_from_image(generate_palette(rom, offset_Character_Battle_Sprite_Disabled_Palette))
|
||||
character_battle_sprite_palette_stone_texture = texture_from_image(generate_palette(rom, offset_Character_Battle_Sprite_Stone_Palette))
|
||||
|
||||
weapon_textures['Fist'] = texture_from_image(snes_get_tile(rom, offset_Tiles_Fist, 24))
|
||||
|
||||
SoundLoader.load_samples(rom)
|
||||
|
||||
|
||||
func gba_LZ77_decompress(rom: File, address: int) -> PoolByteArray:
|
||||
rom.seek(address)
|
||||
var header := rom.get_32()
|
||||
assert (header & 0x10 == 0x10)
|
||||
var length := header >> 8
|
||||
var output := ByteArray(length)
|
||||
var ptr := 0
|
||||
while ptr < length:
|
||||
var bitmap := rom.get_8()
|
||||
for i in range(8):
|
||||
if (bitmap >> (7-i)) & 1:
|
||||
# Buffer substitution
|
||||
var h1 := rom.get_8()
|
||||
var h2 := rom.get_8()
|
||||
var copy_len := 3 + (h1 >> 4)
|
||||
var copy_ptr := ptr - 1 - (((h1 & 0x0F)<<8) + h2)
|
||||
for j in range(copy_len):
|
||||
output[ptr] = output[copy_ptr]
|
||||
copy_ptr += 1
|
||||
ptr += 1
|
||||
if ptr >= length:
|
||||
return output
|
||||
else:
|
||||
# Literal byte
|
||||
output[ptr] = rom.get_8()
|
||||
ptr += 1
|
||||
if ptr >= length:
|
||||
return output
|
||||
return output
|
||||
|
||||
|
||||
const gba_marker := 'FINAL FANTASY V ADVANCE SYGMAB'
|
||||
const gba_marker_pos_US := 0x12FE10
|
||||
const gba_marker_pos_EU := 0x131294
|
||||
const gba_offset_battle_sprite_tiles := 504
|
||||
const gba_offset_battle_sprite_palettes := 1064
|
||||
const gba_offset_battle_sprite_layouts := 9435
|
||||
func load_gba_rom(filename: String):
|
||||
var rom := File.new()
|
||||
var offset_marker: int
|
||||
var result = rom.open(filename, File.READ)
|
||||
if result != 0:
|
||||
print_debug('ROM failed to open: ', result)
|
||||
return
|
||||
|
||||
rom.seek(gba_marker_pos_US)
|
||||
if rom.get_buffer(len(gba_marker)).get_string_from_ascii() == gba_marker:
|
||||
offset_marker = gba_marker_pos_US
|
||||
else:
|
||||
rom.seek(gba_marker_pos_EU)
|
||||
if rom.get_buffer(len(gba_marker)).get_string_from_ascii() == gba_marker:
|
||||
offset_marker = gba_marker_pos_EU
|
||||
else:
|
||||
print_debug('ROM does not match known US/EU ROMs, aborting')
|
||||
return
|
||||
|
||||
# Battle Sprite Tiles
|
||||
rom.seek(offset_marker + gba_offset_battle_sprite_layouts)
|
||||
var battle_strip_layouts = rom.get_buffer(num_Character_Battle_Sprite_Layouts * 6)
|
||||
for strip in range(0, 26*5):
|
||||
var tiles = []
|
||||
rom.seek(offset_marker + gba_offset_battle_sprite_tiles + strip*4)
|
||||
var address = rom.get_32() - 0x08000000
|
||||
var tiledata = gba_LZ77_decompress(rom, address)
|
||||
tiledata.append(0) # Slicing to end of array causes an engine crash. For shame!
|
||||
for i in range(8, 57*32+8, 32):
|
||||
tiles.append(gba_4bpp_to_tile(tiledata.subarray(i, i+32)))
|
||||
var strip_image = Image.new()
|
||||
strip_image.create(16, 24 * num_Character_Battle_Sprite_Layouts, false, Image.FORMAT_R8)
|
||||
for i in range(6 * num_Character_Battle_Sprite_Layouts):
|
||||
strip_image.blit_rect(tiles[battle_strip_layouts[i]], Rect2(0, 0, 8, 8), Vector2((i%2) * 8, (i/2) * 8))
|
||||
strip_images.append(strip_image)
|
||||
strip_textures.append(texture_from_image(strip_image))
|
||||
|
||||
# Character Battle Sprite Palettes
|
||||
for palette in range(0, 26*5):
|
||||
rom.seek(offset_marker + gba_offset_battle_sprite_palettes + palette*4)
|
||||
character_battle_sprite_palette_textures.append(texture_from_image(generate_palette(rom, rom.get_32() - 0x08000000 + 8)))
|
||||
# character_battle_sprite_palette_disabled_texture = texture_from_image(generate_palette(rom, offset_Character_Battle_Sprite_Disabled_Palette))
|
||||
# character_battle_sprite_palette_stone_texture = texture_from_image(generate_palette(rom, offset_Character_Battle_Sprite_Stone_Palette))
|
||||
|
||||
|
||||
var strip_images = []
|
||||
var strip_textures = []
|
||||
func _ready():
|
||||
load_snes_rom(ROM_filename)
|
||||
#load_gba_rom('2564 - Final Fantasy V Advance (U)(Independent).gba')
|
|
@ -0,0 +1,6 @@
|
|||
[gd_resource type="ShaderMaterial" load_steps=2 format=2]
|
||||
|
||||
[ext_resource path="res://shaders/palette_shader.tres" type="Shader" id=1]
|
||||
|
||||
[resource]
|
||||
shader = ExtResource( 1 )
|
|
@ -0,0 +1,39 @@
|
|||
; 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=4
|
||||
|
||||
_global_script_classes=[ ]
|
||||
_global_script_class_icons={
|
||||
}
|
||||
|
||||
[application]
|
||||
|
||||
config/name="FF"
|
||||
run/main_scene="res://Node2D.tscn"
|
||||
config/icon="res://icon.png"
|
||||
|
||||
[autoload]
|
||||
|
||||
globals="*res://globals.gd"
|
||||
SoundLoader="*res://scripts/sound_loader.gd"
|
||||
load_sprites="*res://load_sprites.gd"
|
||||
|
||||
[debug]
|
||||
|
||||
gdscript/warnings/unused_variable=false
|
||||
gdscript/warnings/integer_division=false
|
||||
|
||||
[global]
|
||||
|
||||
color=false
|
||||
|
||||
[rendering]
|
||||
|
||||
environment/default_clear_color=Color( 0, 0, 0.517647, 1 )
|
||||
environment/default_environment="res://default_env.tres"
|
|
@ -0,0 +1,131 @@
|
|||
extends Node
|
||||
|
||||
const MAX_15B = 1 << 15
|
||||
const MAX_16B = 1 << 16
|
||||
|
||||
func unsigned16_to_signed(unsigned):
|
||||
return (unsigned + MAX_15B) % MAX_16B - MAX_15B
|
||||
|
||||
func process_sample(mantissa: int, exponent: int) -> int:
|
||||
# For filter arithmetic the samples need to be in signed form.
|
||||
# Sign-extend
|
||||
if mantissa >= 8:
|
||||
mantissa |= 0xFFF0
|
||||
exponent = min(exponent, 12)
|
||||
var unsigned = (mantissa << exponent) & 0xFFFF
|
||||
return unsigned16_to_signed(unsigned)
|
||||
|
||||
func decode_brr(data: PoolByteArray):
|
||||
# Decodes a single 9byte BRR packet
|
||||
var exponent := data[0] >> 4
|
||||
var filter_designation := (data[0] >> 2) & 0x03
|
||||
var loop := bool(data[0] & 0x02)
|
||||
var end := bool(data[0] & 0x01)
|
||||
var samples := []
|
||||
for i in range(1, 9):
|
||||
var b := data[i]
|
||||
samples.append(process_sample(b >> 4, exponent))
|
||||
samples.append(process_sample(b & 0x0F, exponent))
|
||||
return [samples, loop, end, filter_designation]
|
||||
|
||||
|
||||
func clamp_short(i: int):
|
||||
return min(max(i, -0x8000), 0x7FFF)
|
||||
|
||||
func make_sample(rom: File, sample_rate: int) -> AudioStreamSample:
|
||||
var audio := AudioStreamSample.new()
|
||||
audio.mix_rate = sample_rate
|
||||
audio.stereo = false
|
||||
audio.set_format(AudioStreamSample.FORMAT_16_BITS)
|
||||
|
||||
var size := rom.get_16()
|
||||
if (size % 9) != 0:
|
||||
print_debug('Oh no! An instrument sample has an invalid size of %d! at $%06X' % [size, rom.get_position()-2])
|
||||
return audio
|
||||
|
||||
var samples = [0, 0] # Two zero samples for filter purposes, strip them from the actual output
|
||||
for pkt in (size / 9):
|
||||
var data = decode_brr(rom.get_buffer(9))
|
||||
samples.append_array(data[0])
|
||||
#var loop = data[1]
|
||||
var end = data[2]
|
||||
var filter = data[3]
|
||||
match filter:
|
||||
1:
|
||||
for i in range(samples.size()-8, samples.size()):
|
||||
samples[i] = clamp_short(samples[i] + (samples[i-1]*15)/16)
|
||||
2:
|
||||
for i in range(samples.size()-8, samples.size()):
|
||||
samples[i] = clamp_short(samples[i] + (samples[i-1]*61)/32 - (samples[i-2]*15)/16)
|
||||
3:
|
||||
for i in range(samples.size()-8, samples.size()):
|
||||
samples[i] = clamp_short(samples[i] + (samples[i-1]*115)/64 - (samples[i-2]*13)/16)
|
||||
if end:
|
||||
break
|
||||
var audio_data = PoolByteArray()
|
||||
for i in range(2, samples.size()):
|
||||
var b = samples[i]
|
||||
audio_data.push_back(b & 0xFF)
|
||||
audio_data.push_back(b >> 8)
|
||||
audio.data = audio_data
|
||||
return audio
|
||||
|
||||
const INST_NUM := 35
|
||||
const INST_BRR_LOOKUP := 0x043C6F
|
||||
const INST_SR := 0x043CD8
|
||||
const INST_LOOP := 0x043D1E
|
||||
const INST_ADSR := 0x043D64
|
||||
const SFX_NUM := 8
|
||||
const SFX_BRR_START := 0x041E3F
|
||||
const SFX_ADSR := 0x041F71
|
||||
const SFX_SR := 0x041F83
|
||||
var instrument_samples = []
|
||||
var sfx_samples = []
|
||||
|
||||
func get_inst_sample_data(rom: File, id: int) -> AudioStreamSample:
|
||||
rom.seek(INST_SR + (id*2))
|
||||
var sample_rate := (rom.get_16() * 32000)/4096
|
||||
|
||||
var lookup_offset := INST_BRR_LOOKUP + (id*3)
|
||||
rom.seek(lookup_offset)
|
||||
var brr_offset := rom.get_16() + ((rom.get_8() & 0x3F) << 16)
|
||||
rom.seek(brr_offset)
|
||||
var sample := make_sample(rom, sample_rate)
|
||||
#print_debug('Loaded sample instrument #%02X with lookup offset $%06X, BRR data offset $%06X and length $%04X (%f packets)' % [id, lookup_offset, brr_offset, size, size/9.0])
|
||||
return sample
|
||||
|
||||
func load_sfx_samples_data(rom: File):
|
||||
var sample_rates = []
|
||||
rom.seek(SFX_SR)
|
||||
for i in SFX_NUM:
|
||||
sample_rates.append((rom.get_16() * 32000)/4096)
|
||||
rom.seek(SFX_BRR_START)
|
||||
for i in SFX_NUM:
|
||||
print('Loading sfx sample #%X with BRR data offset $%06X' % [i, rom.get_position()])
|
||||
sfx_samples.append(make_sample(rom, sample_rates[i]))
|
||||
print('size of %d samples' % sfx_samples[i].data.size())
|
||||
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func load_samples(rom: File):
|
||||
for i in INST_NUM:
|
||||
instrument_samples.append(get_inst_sample_data(rom, i))
|
||||
load_sfx_samples_data(rom)
|
||||
|
||||
var player := AudioStreamPlayer.new() # Make one for each channel, later
|
||||
func play_sample(id: int):
|
||||
print('Playing sample #%02X' % id)
|
||||
player.stream = instrument_samples[id]
|
||||
player.play()
|
||||
|
||||
func play_sfx(id: int):
|
||||
print('Playing sample #%02X' % id)
|
||||
player.stream = sfx_samples[id]
|
||||
player.play()
|
||||
|
||||
func _ready() -> void:
|
||||
add_child(player)
|
||||
|
||||
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||
#func _process(delta: float) -> void:
|
||||
# pass
|
|
@ -0,0 +1,15 @@
|
|||
[gd_resource type="Shader" format=2]
|
||||
|
||||
[resource]
|
||||
code = "shader_type canvas_item;
|
||||
//uniform usampler2D tex;
|
||||
uniform sampler2D palette: hint_normal;
|
||||
|
||||
void fragment() {
|
||||
//uint color_idx = textureLod(tex, UV, 0.0).r;
|
||||
uint color_idx = uint(textureLod(TEXTURE, UV, 0.0).r * 255.0);
|
||||
COLOR = texelFetch(palette, ivec2(int(color_idx), 0), 0);
|
||||
if (color_idx == uint(0))
|
||||
COLOR.a = 0.0;
|
||||
}
|
||||
"
|
Loading…
Reference in New Issue