2023-07-25 14:21:10 +09:30
extends Node
2023-07-26 21:52:54 +09:30
signal audio_samples_loaded
2023-07-27 19:53:52 +09:30
signal audio_inst_sample_loaded ( id )
signal audio_sfx_sample_loaded ( id )
2023-07-26 21:52:54 +09:30
var has_loaded_audio_samples : = false
2023-07-25 16:33:45 +09:30
const BGM_NUM : = 70
const INST_NUM : = 35
const SFX_NUM : = 8
2024-07-05 16:01:08 +09:30
const PREPEND_MS : = 20 # Prepend 20ms of silence to each sample for preplay purposes
2023-08-24 14:54:32 +09:30
const PLAY_START : = PREPEND_MS / 1000.0
2024-07-05 16:01:08 +09:30
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 : # !!!
2024-07-10 22:13:58 +09:30
var output : AudioStreamSample = audio . duplicate ( true ) # !!!
# Prepend silence # !!!
var silent_samples : = ( audio . mix_rate * PREPEND_MS ) / 1000 # !!!
var silence : = PoolByteArray ( ) # !!!
silence . resize ( silent_samples * 2 ) # 16bit samples in 8bit array # !!!
silence . fill ( 0 ) # !!!
output . data = silence + output . data # !!!
output . loop_begin += silent_samples # !!!
output . loop_end += silent_samples # !!!
# Append looped samples # !!!
if output . loop_begin > = output . loop_end : # !!!
return output # !!!
2024-07-05 16:01:08 +09:30
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 ) # !!!
2024-07-10 22:13:58 +09:30
output . data += looped_samples # !!!
2024-07-05 16:01:08 +09:30
return output # !!!
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
2023-07-25 16:33:45 +09:30
var bgm_tracks = [ ]
var instrument_samples = [ ]
2024-07-05 16:01:08 +09:30
var instrument_samples_HACK_EXTENDED_LOOPS = [ ]
2023-07-25 16:33:45 +09:30
var sfx_samples = [ ]
2023-08-23 19:58:20 +09:30
func read_rom_address ( buffer : StreamPeerBuffer ) - > int :
2023-07-25 16:33:45 +09:30
# Read a 3-byte little-endian address and wrap the bank to ROM space
2023-08-23 19:58:20 +09:30
return buffer . get_u16 ( ) + ( ( buffer . get_u8 ( ) & 0x3F ) << 16 )
2023-07-25 16:33:45 +09:30
2023-07-25 14:21:10 +09:30
const MAX_15B = 1 << 15
const MAX_16B = 1 << 16
func unsigned16_to_signed ( unsigned ) :
return ( unsigned + MAX_15B ) % MAX_16B - MAX_15B
2023-07-26 14:41:05 +09:30
func get_reference_pitch_samplerate ( tuning1 : int , tuning2 : int = 0 ) - > int :
# This is non-trivial and subject to change
2023-09-23 16:04:51 +09:30
var pitch_scale = tuning1 / 255.0 + tuning2 / 65535.0
2023-07-26 14:41:05 +09:30
if tuning1 < 0x80 :
pitch_scale += 1.0
return int ( pitch_scale * 36000 )
# return (unsigned16_to_signed(pitch) + 0x8000) * 32000/4096
2023-07-25 14:21:10 +09:30
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
2023-07-25 16:33:45 +09:30
if exponent > 12 :
exponent = 12
2023-07-25 14:21:10 +09:30
var unsigned = ( mantissa << exponent ) & 0xFFFF
return unsigned16_to_signed ( unsigned )
2023-07-26 22:21:25 +09:30
func clamp_short ( i : int ) - > int :
if i < - 0x8000 :
return - 0x8000
if i > 0x7FFF :
return 0x7FFF
return i
2023-07-25 14:21:10 +09:30
2023-08-23 19:58:20 +09:30
func make_sample ( buffer : StreamPeerBuffer , size : int , sample_rate : int ) - > AudioStreamSample :
2023-07-25 14:21:10 +09:30
var audio : = AudioStreamSample . new ( )
audio . mix_rate = sample_rate
audio . stereo = false
audio . set_format ( AudioStreamSample . FORMAT_16_BITS )
if ( size % 9 ) != 0 :
2023-08-23 19:58:20 +09:30
print_debug ( ' Oh no! An instrument sample has an invalid size of %d ! at $ %06X ' % [ size , buffer . get_position ( ) - 2 ] )
2023-07-25 14:21:10 +09:30
return audio
2023-07-26 22:21:25 +09:30
var num_packets : = size / 9
2023-07-25 14:21:10 +09:30
2024-07-10 22:13:58 +09:30
var samples = PoolIntArray ( [ 0 , 0 ] ) # Start with two zero samples for filter purposes, strip them from the actual output later
2023-07-26 22:21:25 +09:30
var i : = 2
for pkt in num_packets :
2023-08-23 19:38:59 +09:30
# Decode a single 9byte BRR packet
2023-08-23 19:58:20 +09:30
var header_byte : = buffer . get_u8 ( )
2023-08-23 19:38:59 +09:30
var exponent : = header_byte >> 4
var filter : = ( header_byte >> 2 ) & 0x03
2024-07-10 22:13:58 +09:30
# var loop := bool(header_byte & 0x02)
2023-08-23 19:38:59 +09:30
var end : = bool ( header_byte & 0x01 )
for sample in 8 :
2023-08-23 19:58:20 +09:30
var b : = buffer . get_u8 ( )
2023-08-23 19:38:59 +09:30
samples . append ( process_sample ( b >> 4 , exponent ) )
samples . append ( process_sample ( b & 0x0F , exponent ) )
# Apply filter
var l : = len ( samples )
2023-07-25 14:21:10 +09:30
match filter :
1 :
2023-08-23 19:38:59 +09:30
for j in range ( l - 16 , l ) :
2023-07-26 22:21:25 +09:30
samples [ j ] = clamp_short ( samples [ j ] + ( samples [ j - 1 ] * 15 ) / 16 )
2023-07-25 14:21:10 +09:30
2 :
2023-08-23 19:38:59 +09:30
for j in range ( l - 16 , l ) :
2023-07-26 22:21:25 +09:30
samples [ j ] = clamp_short ( samples [ j ] + ( samples [ j - 1 ] * 61 ) / 32 - ( samples [ j - 2 ] * 15 ) / 16 )
2023-07-25 14:21:10 +09:30
3 :
2023-08-23 19:38:59 +09:30
for j in range ( l - 16 , l ) :
2023-07-26 22:21:25 +09:30
samples [ j ] = clamp_short ( samples [ j ] + ( samples [ j - 1 ] * 115 ) / 64 - ( samples [ j - 2 ] * 13 ) / 16 )
2023-07-25 14:21:10 +09:30
if end :
2023-07-26 14:41:05 +09:30
# print('End flag on packet')
2023-07-25 14:21:10 +09:30
break
2024-07-10 22:13:58 +09:30
# Remove first two zero samples
samples . remove ( 0 )
samples . remove ( 0 )
2023-08-24 14:54:32 +09:30
# Pack 16bit samples to 8bit array
2024-07-10 22:13:58 +09:30
var out_buff = StreamPeerBuffer . new ( )
for sample in samples :
out_buff . put_16 ( sample )
audio . data = out_buff . data_array
2023-07-25 14:21:10 +09:30
return audio
2023-08-24 14:54:32 +09:30
func get_inst_sample_data ( snes_data : Dictionary , buffer : StreamPeerBuffer , id : int ) - > AudioStreamSample :
var sample_rate : = get_reference_pitch_samplerate ( snes_data . bgm_instrument_samplerates [ id ] & 0xFF )
var loop_start_packet : int = snes_data . bgm_instrument_loop_starts [ id ] / 9 # Note that Instrument $1F Steel Guitar has a length of $088B but a loop point of $088D which is 243.22... packets. Luckily it doesn't matter.
buffer . seek ( snes_data . bgm_instrument_brr_pointers [ id ] & 0x3FFFFF )
2023-08-23 19:58:20 +09:30
var size : = buffer . get_u16 ( )
2023-07-26 14:41:05 +09:30
var num_samples : = ( size / 9 ) * 16
2023-08-23 19:58:20 +09:30
var audio : = make_sample ( buffer , size , sample_rate )
2023-07-26 14:41:05 +09:30
audio . loop_mode = AudioStreamSample . LOOP_FORWARD
2024-07-10 22:13:58 +09:30
audio . loop_begin = ( loop_start_packet * 16 ) # Each 9byte packet is 16 samples
audio . loop_end = num_samples
2023-07-26 21:52:54 +09:30
# 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])
2023-07-26 14:41:05 +09:30
return audio
2023-07-25 14:21:10 +09:30
2024-07-05 20:59:54 +09:30
# const SFX_BRR_START := 0x041E3F + 2 # First two bytes are the length of the block, 0x010E = 270 bytes = 16 BRR packets = 480 samples
2023-08-24 14:54:32 +09:30
func load_sfx_samples_data ( snes_data : Dictionary , buffer : StreamPeerBuffer ) :
2023-07-25 18:37:55 +09:30
var brr_spc_addrs = [ ]
var brr_spc_loop_addrs = [ ]
2023-08-24 14:54:32 +09:30
for two_of_u16 in snes_data . sfx_brr_pointers :
brr_spc_addrs . append ( two_of_u16 [ 0 ] )
brr_spc_loop_addrs . append ( two_of_u16 [ 1 ] )
2024-07-05 20:59:54 +09:30
var brr_spc_start = Common . SNES_PSX_addresses . sfx_brr_data . SNES - brr_spc_addrs [ 0 ] # Refactor this later
2023-07-25 18:37:55 +09:30
for i in SFX_NUM :
2023-08-24 14:54:32 +09:30
buffer . seek ( brr_spc_addrs [ i ] + brr_spc_start )
2023-08-23 19:58:20 +09:30
# print('Loading sfx sample #%X with BRR data offset $%06X' % [i, buffer.get_position()])
2023-08-24 14:54:32 +09:30
var sample_rate : = get_reference_pitch_samplerate ( snes_data . sfx_samplerates [ i ] & 0xFF )
var audio : = make_sample ( buffer , 900 , sample_rate )
var loop_start_packet : int = brr_spc_loop_addrs [ i ] - brr_spc_addrs [ i ]
audio . loop_mode = AudioStreamSample . LOOP_FORWARD
2024-07-10 22:13:58 +09:30
audio . loop_begin = loop_start_packet * 16 # Each 9byte packet is 16 samples
2024-07-05 16:01:08 +09:30
audio . loop_end = ( len ( audio . data ) / 2 )
2023-08-24 14:54:32 +09:30
sfx_samples . append ( audio ) # Use 900 as a limit, it won't be hit, parser stops after End packet anyway
2023-07-27 19:53:52 +09:30
emit_signal ( ' audio_sfx_sample_loaded ' , i )
2023-07-26 14:41:05 +09:30
# print('size of %d samples' % sfx_samples[i].data.size())
2023-07-25 14:21:10 +09:30
# Called when the node enters the scene tree for the first time.
2023-08-24 14:54:32 +09:30
func load_samples ( snes_data : Dictionary , buffer : StreamPeerBuffer ) :
load_sfx_samples_data ( snes_data , buffer )
2023-12-02 00:05:28 +10:30
# var largest_sample_idx := -1
# var largest_sample_sample_count := 0
# var total_frames := 0
2024-07-05 16:01:08 +09:30
# For some reason, this is a bit slow currently under certain editor conditions. Might optimize later.
2023-07-25 14:21:10 +09:30
for i in INST_NUM :
2023-12-02 00:05:28 +10:30
var samp : = get_inst_sample_data ( snes_data , buffer , i )
instrument_samples . append ( samp )
# total_frames += samp.loop_end
# if largest_sample_sample_count < samp.loop_end:
# largest_sample_sample_count = samp.loop_end
# largest_sample_idx = i
2024-07-05 16:01:08 +09:30
# Workaround for Godot 3.x quirk where looping samples are interpolated as if they go to nothing instead of looping
2023-12-02 00:05:28 +10:30
instrument_samples_HACK_EXTENDED_LOOPS . append ( HACK_EXTEND_LOOP_SAMPLE ( samp ) )
# print('Instrument %02X has mix_rate %d Hz and %d samples'%[i, samp.mix_rate, len(samp.data)/2])
2023-07-27 19:53:52 +09:30
emit_signal ( ' audio_inst_sample_loaded ' , i )
2023-12-02 00:05:28 +10:30
# samp.save_to_wav('output/instrument%02d(%dHz)(loop from %d).wav' % [i, samp.mix_rate, samp.loop_begin])
2024-07-10 22:13:58 +09:30
# print('Largest sample is instrument %d with length %d and mix_rate %d'%[largest_sample_idx, largest_sample_sample_count, instrument_samples[largest_sample_idx].mix_rate])
# print('Total frames: %d'%total_frames)
# We start the texture with a bunch of same-size headers
# uint16 sample_start // The true start, after the prepended 3 frames of silence
# uint16 sample_length // 3 frames after the true end, because of how we loop
# uint16 sample_loop_begin // 3 frames after the true loop point
# uint16 mixrate
# 2*uint8 AD of ADSR ([0.0, 1.0] is fine)
# 2*uint8 SR of ADSR ([0.0, 1.0] is fine)
var samples_tex : ImageTexture
const TEX_WIDTH : = 2048
const FILTER_PAD : = 3
func samples_to_texture ( ) :
var num_samples : = INST_NUM + SFX_NUM
var header_length : = num_samples * 6
# Create header and unwrapped payload separately first
var header_data : = PoolByteArray ( )
var header_buffer : = StreamPeerBuffer . new ( )
header_buffer . data_array = header_data
var payload_data : = PoolByteArray ( )
var payload_buffer : = StreamPeerBuffer . new ( )
payload_buffer . data_array = payload_data
for sample in instrument_samples + sfx_samples :
var loop_end : int = sample . loop_end
var loop_begin : int = sample . loop_begin
var nonlooping : bool = loop_begin > = loop_end
if nonlooping :
loop_begin = loop_end
loop_end += 1
header_buffer . put_u16 ( header_length + ( len ( payload_data ) / 2 ) + FILTER_PAD ) # sample_start
header_buffer . put_u16 ( sample . loop_end + FILTER_PAD ) # sample_length
header_buffer . put_u16 ( sample . loop_begin + FILTER_PAD ) # sample_loop_begin
header_buffer . put_u16 ( sample . mix_rate ) # sample_mixrate
header_buffer . put_u8 ( 0 ) # TODO: attack
header_buffer . put_u8 ( 0 ) # TODO: decay
header_buffer . put_u8 ( 0 ) # TODO: sustain
header_buffer . put_u8 ( 0 ) # TODO: release
for i in FILTER_PAD : # Prepend 3 frames of silence
payload_buffer . put_16 ( 0 )
payload_buffer . put_data ( sample . data ) # Copy entire S16LE audio data
if nonlooping :
for i in FILTER_PAD * 2 :
payload_buffer . put_16 ( 0 ) # 6 frames of trailing silence to loop
else :
# Copy frame by frame in case the loop is shorter than 6 frames
var loop_length = sample . loop_end - sample . loop_begin
for i in FILTER_PAD * 2 :
var pos : = payload_buffer . get_position ( )
payload_buffer . seek ( pos - loop_length )
var frame : = payload_buffer . get_16 ( )
payload_buffer . seek ( pos )
payload_buffer . put_16 ( frame )
# Combine the unwrapped arrays
var data : = header_data + payload_data
# Now calculate wrapping and rowwise padding for the combined array
for row in TEX_WIDTH :
var row_end : int = ( row + 1 ) * TEX_WIDTH * 2 # Remember: 8bit array, 16bit values
if len ( data ) / 2 > row_end :
# [... a b c] + [a b c] + [a b c ...]
data = data . subarray ( 0 , row_end - 1 ) + data . subarray ( row_end - FILTER_PAD * 2 , row_end - 1 ) + data . subarray ( row_end - FILTER_PAD * 2 , - 1 )
else :
break
var needed_rows : = ( len ( data ) / 2 ) / float ( TEX_WIDTH )
var rows : = int ( pow ( 2 , ceil ( log ( needed_rows ) / log ( 2 ) ) ) )
if rows > TEX_WIDTH :
print_debug ( ' Sound Sample Texture rows have exceeded width: %d > %d ' % [ rows , TEX_WIDTH ] )
# Now that the full texture size is known, pad our existing data with zeroes until the end
var final_data_size_bytes = rows * TEX_WIDTH * 2
if final_data_size_bytes > len ( data ) :
var end_padding : = PoolByteArray ( )
end_padding . resize ( final_data_size_bytes - len ( data ) )
end_padding . fill ( 0 )
data = data + end_padding
# data is complete, turn it into an ImageTexture for the shader to use
var samples_img = Image . new ( )
samples_img . create_from_data ( TEX_WIDTH , rows , false , Image . FORMAT_LA8 , data )
self . samples_tex = ImageTexture . new ( )
self . samples_tex . create_from_image ( samples_img , Texture . FLAG_FILTER )
2023-07-25 14:21:10 +09:30
2023-07-25 16:33:45 +09:30
2023-07-25 14:21:10 +09:30
var player : = AudioStreamPlayer . new ( ) # Make one for each channel, later
2024-07-10 00:34:28 +09:30
var HACK_EXTEND_LOOP_SAMPLE_playback : bool = true
2024-07-05 20:59:54 +09:30
func play_sample ( id : int , pitch_scale : float = 1.0 ) :
2023-08-23 19:38:59 +09:30
print ( ' Playing inst sample # %02X ' % id )
2024-07-05 20:59:54 +09:30
player . pitch_scale = pitch_scale
2024-07-05 16:01:08 +09:30
if HACK_EXTEND_LOOP_SAMPLE_playback :
player . stream = instrument_samples_HACK_EXTENDED_LOOPS [ id ]
else :
player . stream = instrument_samples [ id ]
2024-07-05 20:59:54 +09:30
player . play ( PLAY_START / pitch_scale )
2023-07-25 14:21:10 +09:30
func play_sfx ( id : int ) :
2023-08-23 19:38:59 +09:30
print ( ' Playing sfx sample # %02X ' % id )
2024-07-05 20:59:54 +09:30
player . pitch_scale = 1.0
2023-07-25 14:21:10 +09:30
player . stream = sfx_samples [ id ]
2023-08-24 14:54:32 +09:30
player . play ( PLAY_START )
2023-07-25 14:21:10 +09:30
2023-08-24 14:54:32 +09:30
func parse_rom ( snes_data : Dictionary , buffer : StreamPeerBuffer ) :
load_samples ( snes_data , buffer )
2023-08-23 19:58:20 +09:30
#load_bgms(buffer)
2023-07-26 21:52:54 +09:30
has_loaded_audio_samples = true
emit_signal ( ' audio_samples_loaded ' )
2023-07-25 16:33:45 +09:30
2023-07-25 14:21:10 +09:30
func _ready ( ) - > void :
add_child ( player )