Add loop extension hack for audio samples
This commit is contained in:
parent
334545fcc4
commit
f1fb01501c
|
@ -17,10 +17,32 @@ const SFX_BRR_SPC_TABLE := 0x041F4F + 2 # (first two bytes are the length of 0x0
|
||||||
const SFX_BRR_START := 0x041E3F + 2 # First two bytes are the length of the block, 0x010E = 270 bytes = 16 BRR packets = 480 samples
|
const SFX_BRR_START := 0x041E3F + 2 # First two bytes are the length of the block, 0x010E = 270 bytes = 16 BRR packets = 480 samples
|
||||||
const SFX_ADSR := 0x041F71
|
const SFX_ADSR := 0x041F71
|
||||||
const SFX_SR := 0x041F83
|
const SFX_SR := 0x041F83
|
||||||
const PREPEND_MS := 50 # Prepend 20ms of silence to each sample for preplay purposes
|
const PREPEND_MS := 20 # Prepend 20ms of silence to each sample for preplay purposes
|
||||||
const PLAY_START := PREPEND_MS / 1000.0
|
const PLAY_START := PREPEND_MS / 1000.0
|
||||||
|
const BYTES_PER_SAMPLE := 2 # 16bit samples
|
||||||
|
|
||||||
|
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
# !!! Testing a workaround for a Godot 3.x AudioStreamSample playback: !!!
|
||||||
|
# !!! Copy the looped samples one or more times to avoid the break in interpolation at buffer end. !!!
|
||||||
|
# !!! Adding a few ms to the loops removes harshness. !!!
|
||||||
|
const HACK_EXTEND_LOOP_SAMPLE_EXTRA_MS := 2 # !!!
|
||||||
|
func HACK_EXTEND_LOOP_SAMPLE(audio: AudioStreamSample) -> AudioStreamSample: # !!!
|
||||||
|
if audio.loop_begin >= audio.loop_end: # !!!
|
||||||
|
return audio # !!!
|
||||||
|
var looped_samples = audio.data.subarray(audio.loop_begin * BYTES_PER_SAMPLE, -1) # !!!
|
||||||
|
var loop_len = len(looped_samples) # !!!
|
||||||
|
var target_len = (audio.mix_rate * HACK_EXTEND_LOOP_SAMPLE_EXTRA_MS / 1000) * BYTES_PER_SAMPLE # !!!
|
||||||
|
while loop_len < target_len: # Keep doubling in length until it's long enough !!!
|
||||||
|
looped_samples += looped_samples # !!!
|
||||||
|
loop_len = len(looped_samples) # !!!
|
||||||
|
var output = audio.duplicate(true) # !!!
|
||||||
|
output.data = audio.data + looped_samples # !!!
|
||||||
|
return output # !!!
|
||||||
|
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
|
||||||
var bgm_tracks = []
|
var bgm_tracks = []
|
||||||
var instrument_samples = []
|
var instrument_samples = []
|
||||||
|
var instrument_samples_HACK_EXTENDED_LOOPS = []
|
||||||
var sfx_samples = []
|
var sfx_samples = []
|
||||||
|
|
||||||
func read_rom_address(buffer: StreamPeerBuffer) -> int:
|
func read_rom_address(buffer: StreamPeerBuffer) -> int:
|
||||||
|
@ -119,7 +141,7 @@ func get_inst_sample_data(snes_data: Dictionary, buffer: StreamPeerBuffer, id: i
|
||||||
var audio := make_sample(buffer, size, sample_rate)
|
var audio := make_sample(buffer, size, sample_rate)
|
||||||
audio.loop_mode = AudioStreamSample.LOOP_FORWARD
|
audio.loop_mode = AudioStreamSample.LOOP_FORWARD
|
||||||
audio.loop_begin = (loop_start_packet * 16) + silent_samples # Each 9byte packet is 16 samples
|
audio.loop_begin = (loop_start_packet * 16) + silent_samples # Each 9byte packet is 16 samples
|
||||||
audio.loop_end = silent_samples + num_samples - 1
|
audio.loop_end = silent_samples + num_samples
|
||||||
# print_debug('Loaded instrument #%02X with lookup offset $%06X, BRR data offset $%06X, length $%04X (%f packets, %d samples) and loop point %d samples' % [id, lookup_offset, brr_offset, size, size/9.0, num_samples, audio.loop_begin])
|
# print_debug('Loaded instrument #%02X with lookup offset $%06X, BRR data offset $%06X, length $%04X (%f packets, %d samples) and loop point %d samples' % [id, lookup_offset, brr_offset, size, size/9.0, num_samples, audio.loop_begin])
|
||||||
return audio
|
return audio
|
||||||
|
|
||||||
|
@ -139,7 +161,7 @@ func load_sfx_samples_data(snes_data: Dictionary, buffer: StreamPeerBuffer):
|
||||||
var loop_start_packet: int = brr_spc_loop_addrs[i] - brr_spc_addrs[i]
|
var loop_start_packet: int = brr_spc_loop_addrs[i] - brr_spc_addrs[i]
|
||||||
audio.loop_mode = AudioStreamSample.LOOP_FORWARD
|
audio.loop_mode = AudioStreamSample.LOOP_FORWARD
|
||||||
audio.loop_begin = (loop_start_packet * 16) + silent_samples # Each 9byte packet is 16 samples
|
audio.loop_begin = (loop_start_packet * 16) + silent_samples # Each 9byte packet is 16 samples
|
||||||
audio.loop_end = (len(audio.data)/2) - 1
|
audio.loop_end = (len(audio.data)/2)
|
||||||
sfx_samples.append(audio) # Use 900 as a limit, it won't be hit, parser stops after End packet anyway
|
sfx_samples.append(audio) # Use 900 as a limit, it won't be hit, parser stops after End packet anyway
|
||||||
emit_signal('audio_sfx_sample_loaded', i)
|
emit_signal('audio_sfx_sample_loaded', i)
|
||||||
# print('size of %d samples' % sfx_samples[i].data.size())
|
# print('size of %d samples' % sfx_samples[i].data.size())
|
||||||
|
@ -148,9 +170,12 @@ func load_sfx_samples_data(snes_data: Dictionary, buffer: StreamPeerBuffer):
|
||||||
# Called when the node enters the scene tree for the first time.
|
# Called when the node enters the scene tree for the first time.
|
||||||
func load_samples(snes_data: Dictionary, buffer: StreamPeerBuffer):
|
func load_samples(snes_data: Dictionary, buffer: StreamPeerBuffer):
|
||||||
load_sfx_samples_data(snes_data, buffer)
|
load_sfx_samples_data(snes_data, buffer)
|
||||||
# For some reason, this is a bit slow currently. Might optimize later.
|
# For some reason, this is a bit slow currently under certain editor conditions. Might optimize later.
|
||||||
for i in INST_NUM:
|
for i in INST_NUM:
|
||||||
instrument_samples.append(get_inst_sample_data(snes_data, buffer, i))
|
instrument_samples.append(get_inst_sample_data(snes_data, buffer, i))
|
||||||
|
# Workaround for Godot 3.x quirk where looping samples are interpolated as if they go to nothing instead of looping
|
||||||
|
instrument_samples_HACK_EXTENDED_LOOPS.append(HACK_EXTEND_LOOP_SAMPLE(instrument_samples[i]))
|
||||||
|
print('Instrument %02X has mix_rate %d Hz'%[i, instrument_samples[i].mix_rate])
|
||||||
emit_signal('audio_inst_sample_loaded', i)
|
emit_signal('audio_inst_sample_loaded', i)
|
||||||
|
|
||||||
|
|
||||||
|
@ -189,9 +214,13 @@ func load_bgms(buffer: StreamPeerBuffer):
|
||||||
|
|
||||||
|
|
||||||
var player := AudioStreamPlayer.new() # Make one for each channel, later
|
var player := AudioStreamPlayer.new() # Make one for each channel, later
|
||||||
|
var HACK_EXTEND_LOOP_SAMPLE_playback: bool = false
|
||||||
func play_sample(id: int):
|
func play_sample(id: int):
|
||||||
print('Playing inst sample #%02X' % id)
|
print('Playing inst sample #%02X' % id)
|
||||||
player.stream = instrument_samples[id]
|
if HACK_EXTEND_LOOP_SAMPLE_playback:
|
||||||
|
player.stream = instrument_samples_HACK_EXTENDED_LOOPS[id]
|
||||||
|
else:
|
||||||
|
player.stream = instrument_samples[id]
|
||||||
player.play(PLAY_START)
|
player.play(PLAY_START)
|
||||||
|
|
||||||
func play_sfx(id: int):
|
func play_sfx(id: int):
|
||||||
|
|
|
@ -84,7 +84,10 @@ func play_bgm(id: int) -> void:
|
||||||
if inst_idx < 0:
|
if inst_idx < 0:
|
||||||
self.inst_sample_map[i + 0x20] = null
|
self.inst_sample_map[i + 0x20] = null
|
||||||
else:
|
else:
|
||||||
self.inst_sample_map[i + 0x20] = SoundLoader.instrument_samples[inst_idx]
|
if SoundLoader.HACK_EXTEND_LOOP_SAMPLE_playback:
|
||||||
|
self.inst_sample_map[i + 0x20] = SoundLoader.instrument_samples_HACK_EXTENDED_LOOPS[inst_idx]
|
||||||
|
else:
|
||||||
|
self.inst_sample_map[i + 0x20] = SoundLoader.instrument_samples[inst_idx]
|
||||||
if self.music_player:
|
if self.music_player:
|
||||||
remove_child(music_player)
|
remove_child(music_player)
|
||||||
self.music_player = MusicPlayer.new(bgm_tracksets[id], self.inst_sample_map)
|
self.music_player = MusicPlayer.new(bgm_tracksets[id], self.inst_sample_map)
|
||||||
|
@ -109,11 +112,16 @@ func _stop_all() -> void:
|
||||||
self.music_player = null
|
self.music_player = null
|
||||||
SoundLoader.player.stop()
|
SoundLoader.player.stop()
|
||||||
|
|
||||||
|
func _update_loop_hack_status(enabled: bool) -> void:
|
||||||
|
SoundLoader.HACK_EXTEND_LOOP_SAMPLE_playback = enabled
|
||||||
|
|
||||||
# Called when the node enters the scene tree for the first time.
|
# Called when the node enters the scene tree for the first time.
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
self._create_sfx_buttons()
|
self._create_sfx_buttons()
|
||||||
self._create_bgm_playback()
|
self._create_bgm_playback()
|
||||||
$btn_stop.connect('pressed', self, '_stop_all')
|
$btn_stop.connect('pressed', self, '_stop_all')
|
||||||
|
$btn_hack_loop_extension.connect('toggled', self, '_update_loop_hack_status')
|
||||||
|
$btn_hack_loop_extension.text += ' (%dms)'%SoundLoader.HACK_EXTEND_LOOP_SAMPLE_EXTRA_MS
|
||||||
for i in len(RomLoader.snes_data.bgm_song_pointers):
|
for i in len(RomLoader.snes_data.bgm_song_pointers):
|
||||||
var pointer = RomLoader.snes_data.bgm_song_pointers[i]
|
var pointer = RomLoader.snes_data.bgm_song_pointers[i]
|
||||||
print('BGM 0x%02X (%02d) at 0x%06X' % [i, i, pointer])
|
print('BGM 0x%02X (%02d) at 0x%06X' % [i, i, pointer])
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
[gd_scene load_steps=2 format=2]
|
[gd_scene load_steps=3 format=2]
|
||||||
|
|
||||||
[ext_resource path="res://test/audio_system.gd" type="Script" id=1]
|
[ext_resource path="res://test/audio_system.gd" type="Script" id=1]
|
||||||
|
[ext_resource path="res://theme/menu_theme.tres" type="Theme" id=2]
|
||||||
|
|
||||||
[node name="audio_system" type="Node2D"]
|
[node name="audio_system" type="Node2D"]
|
||||||
script = ExtResource( 1 )
|
script = ExtResource( 1 )
|
||||||
|
@ -16,8 +17,8 @@ margin_bottom = 176.0
|
||||||
|
|
||||||
[node name="sb_bgm" type="SpinBox" parent="."]
|
[node name="sb_bgm" type="SpinBox" parent="."]
|
||||||
margin_top = 192.0
|
margin_top = 192.0
|
||||||
margin_right = 23.0
|
margin_right = 38.0
|
||||||
margin_bottom = 214.0
|
margin_bottom = 216.0
|
||||||
rect_min_size = Vector2( 38, 0 )
|
rect_min_size = Vector2( 38, 0 )
|
||||||
align = 2
|
align = 2
|
||||||
|
|
||||||
|
@ -25,12 +26,20 @@ align = 2
|
||||||
margin_left = 40.0
|
margin_left = 40.0
|
||||||
margin_top = 192.0
|
margin_top = 192.0
|
||||||
margin_right = 102.0
|
margin_right = 102.0
|
||||||
margin_bottom = 214.0
|
margin_bottom = 216.0
|
||||||
text = "Play BGM"
|
text = "Play BGM"
|
||||||
|
|
||||||
[node name="btn_stop" type="Button" parent="."]
|
[node name="btn_stop" type="Button" parent="."]
|
||||||
margin_left = 312.0
|
margin_left = 320.0
|
||||||
margin_top = 192.0
|
margin_top = 192.0
|
||||||
margin_right = 374.0
|
margin_right = 374.0
|
||||||
margin_bottom = 214.0
|
margin_bottom = 216.0
|
||||||
text = "Stop All"
|
text = "Stop All"
|
||||||
|
|
||||||
|
[node name="btn_hack_loop_extension" type="CheckBox" parent="."]
|
||||||
|
margin_left = 105.0
|
||||||
|
margin_top = 192.0
|
||||||
|
margin_right = 315.0
|
||||||
|
margin_bottom = 216.0
|
||||||
|
theme = ExtResource( 2 )
|
||||||
|
text = "HACK: Loop Extension"
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 155 B |
|
@ -0,0 +1,35 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="StreamTexture"
|
||||||
|
path="res://.import/checkbox_filled.png-3781a04311202b1a451f80288a9e4cf3.stex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://theme/checkbox_filled.png"
|
||||||
|
dest_files=[ "res://.import/checkbox_filled.png-3781a04311202b1a451f80288a9e4cf3.stex" ]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/hdr_mode=0
|
||||||
|
compress/bptc_ldr=0
|
||||||
|
compress/normal_map=0
|
||||||
|
flags/repeat=0
|
||||||
|
flags/filter=false
|
||||||
|
flags/mipmaps=false
|
||||||
|
flags/anisotropic=false
|
||||||
|
flags/srgb=0
|
||||||
|
process/fix_alpha_border=false
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/HDR_as_SRGB=false
|
||||||
|
process/invert_color=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
stream=false
|
||||||
|
size_limit=0
|
||||||
|
detect_3d=false
|
||||||
|
svg/scale=1.0
|
|
@ -1,4 +1,4 @@
|
||||||
[gd_resource type="Theme" load_steps=22 format=2]
|
[gd_resource type="Theme" load_steps=25 format=2]
|
||||||
|
|
||||||
[ext_resource path="res://3rd_party/sysfont/dynamicfont.tres" type="DynamicFont" id=1]
|
[ext_resource path="res://3rd_party/sysfont/dynamicfont.tres" type="DynamicFont" id=1]
|
||||||
[ext_resource path="res://theme/border_stylebox.tres" type="StyleBox" id=2]
|
[ext_resource path="res://theme/border_stylebox.tres" type="StyleBox" id=2]
|
||||||
|
@ -9,6 +9,9 @@
|
||||||
[ext_resource path="res://theme/vslider10px_inner_stylebox.tres" type="StyleBox" id=7]
|
[ext_resource path="res://theme/vslider10px_inner_stylebox.tres" type="StyleBox" id=7]
|
||||||
[ext_resource path="res://theme/vslider10px_stylebox.tres" type="StyleBox" id=8]
|
[ext_resource path="res://theme/vslider10px_stylebox.tres" type="StyleBox" id=8]
|
||||||
[ext_resource path="res://theme/ThemeElements.png" type="Texture" id=9]
|
[ext_resource path="res://theme/ThemeElements.png" type="Texture" id=9]
|
||||||
|
[ext_resource path="res://theme/border_imagetexture.tres" type="Texture" id=10]
|
||||||
|
[ext_resource path="res://theme/checkbox_filled.png" type="Texture" id=11]
|
||||||
|
[ext_resource path="res://theme/border_imagetexture_black.tres" type="Texture" id=12]
|
||||||
|
|
||||||
[sub_resource type="StyleBoxTexture" id=10]
|
[sub_resource type="StyleBoxTexture" id=10]
|
||||||
texture = ExtResource( 9 )
|
texture = ExtResource( 9 )
|
||||||
|
@ -58,6 +61,15 @@ Button/styles/disabled = ExtResource( 2 )
|
||||||
Button/styles/hover = ExtResource( 2 )
|
Button/styles/hover = ExtResource( 2 )
|
||||||
Button/styles/normal = ExtResource( 2 )
|
Button/styles/normal = ExtResource( 2 )
|
||||||
Button/styles/pressed = ExtResource( 2 )
|
Button/styles/pressed = ExtResource( 2 )
|
||||||
|
CheckBox/icons/checked = ExtResource( 11 )
|
||||||
|
CheckBox/icons/checked_disabled = ExtResource( 11 )
|
||||||
|
CheckBox/icons/radio_checked = ExtResource( 11 )
|
||||||
|
CheckBox/icons/radio_checked_disabled = ExtResource( 11 )
|
||||||
|
CheckBox/icons/radio_unchecked = ExtResource( 10 )
|
||||||
|
CheckBox/icons/radio_unchecked_disabled = ExtResource( 12 )
|
||||||
|
CheckBox/icons/unchecked = ExtResource( 10 )
|
||||||
|
CheckBox/icons/unchecked_disabled = ExtResource( 12 )
|
||||||
|
CheckBox/styles/hover_pressed = ExtResource( 2 )
|
||||||
HScrollBar/styles/grabber = SubResource( 10 )
|
HScrollBar/styles/grabber = SubResource( 10 )
|
||||||
HScrollBar/styles/grabber_highlight = SubResource( 10 )
|
HScrollBar/styles/grabber_highlight = SubResource( 10 )
|
||||||
HScrollBar/styles/grabber_pressed = SubResource( 10 )
|
HScrollBar/styles/grabber_pressed = SubResource( 10 )
|
||||||
|
|
Loading…
Reference in New Issue