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 BGM_LOOKUP : = 0x043B97
const INST_NUM : = 35
const INST_BRR_LOOKUP : = 0x043C6F
2023-07-25 18:37:55 +09:30
const INST_LOOP : = 0x043CD8
const INST_SR : = 0x043D1E
2023-07-25 16:33:45 +09:30
const INST_ADSR : = 0x043D64
const SFX_NUM : = 8
2023-07-25 18:37:55 +09:30
const SFX_BRR_SPC_TABLE : = 0x041F4F + 2 # (first two bytes are the length of 0x0020 = 32 bytes = 4*8)
const SFX_BRR_START : = 0x041E3F + 2 # First two bytes are the length of the block, 0x010E = 270 bytes = 16 BRR packets = 480 samples
2023-07-25 16:33:45 +09:30
const SFX_ADSR : = 0x041F71
const SFX_SR : = 0x041F83
var bgm_tracks = [ ]
var instrument_samples = [ ]
var sfx_samples = [ ]
func read_rom_address ( rom : File ) - > int :
# Read a 3-byte little-endian address and wrap the bank to ROM space
return rom . get_16 ( ) + ( ( rom . get_8 ( ) & 0x3F ) << 16 )
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
var pitch_scale = tuning1 / 256.0 + tuning2 / 65536.0
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-07-25 18:37:55 +09:30
func make_sample ( rom : File , 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 :
print_debug ( ' Oh no! An instrument sample has an invalid size of %d ! at $ %06X ' % [ size , rom . get_position ( ) - 2 ] )
return audio
2023-07-26 22:21:25 +09:30
var num_packets : = size / 9
2023-07-25 14:21:10 +09:30
2023-07-26 22:21:25 +09:30
var samples = PoolIntArray ( [ 0 , 0 ] ) # Start with two zero samples for filter purposes, strip them from the actual output
var i : = 2
for pkt in num_packets :
2023-08-23 19:38:59 +09:30
# Decode a single 9byte BRR packet
var header_byte : = rom . get_8 ( )
var exponent : = header_byte >> 4
var filter : = ( header_byte >> 2 ) & 0x03
var loop : = bool ( header_byte & 0x02 )
var end : = bool ( header_byte & 0x01 )
for sample in 8 :
var b : = rom . get_8 ( )
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
2023-07-26 22:21:25 +09:30
# Convert int array to byte array
2023-07-25 14:21:10 +09:30
var audio_data = PoolByteArray ( )
2023-07-26 22:21:25 +09:30
for j in range ( 2 , samples . size ( ) ) :
var b = samples [ j ]
2023-07-25 16:33:45 +09:30
audio_data . append ( b & 0xFF )
audio_data . append ( b >> 8 )
2023-07-25 14:21:10 +09:30
audio . data = audio_data
return audio
func get_inst_sample_data ( rom : File , id : int ) - > AudioStreamSample :
rom . seek ( INST_SR + ( id * 2 ) )
2023-07-26 14:41:05 +09:30
var tuning1 : = rom . get_8 ( )
var tuning2 : = rom . get_8 ( )
var sample_rate : = get_reference_pitch_samplerate ( tuning1 )
rom . seek ( INST_LOOP + ( id * 2 ) )
var loop_start_packet : = rom . get_16 ( ) / 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.
2023-07-25 14:21:10 +09:30
var lookup_offset : = INST_BRR_LOOKUP + ( id * 3 )
rom . seek ( lookup_offset )
2023-07-25 16:33:45 +09:30
var brr_offset : = read_rom_address ( rom )
2023-07-25 14:21:10 +09:30
rom . seek ( brr_offset )
2023-07-25 18:37:55 +09:30
var size : = rom . get_16 ( )
2023-07-26 14:41:05 +09:30
var num_samples : = ( size / 9 ) * 16
var audio : = make_sample ( rom , size , sample_rate )
audio . loop_mode = AudioStreamSample . LOOP_FORWARD
audio . loop_begin = loop_start_packet * 16 # Each 9byte packet is 16 samples
audio . loop_end = num_samples - 1
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
func load_sfx_samples_data ( rom : File ) :
var sample_rates = [ ]
rom . seek ( SFX_SR )
for i in SFX_NUM :
2023-07-26 14:41:05 +09:30
var tuning1 : = rom . get_8 ( )
var tuning2 : = rom . get_8 ( )
sample_rates . append ( get_reference_pitch_samplerate ( tuning1 ) )
2023-07-25 18:37:55 +09:30
var brr_spc_addrs = [ ]
var brr_spc_loop_addrs = [ ]
rom . seek ( SFX_BRR_SPC_TABLE )
for i in SFX_NUM :
brr_spc_addrs . append ( rom . get_16 ( ) )
brr_spc_loop_addrs . append ( rom . get_16 ( ) )
var brr_spc_start = brr_spc_addrs [ 0 ]
2023-07-25 14:21:10 +09:30
for i in SFX_NUM :
2023-07-25 18:37:55 +09:30
brr_spc_addrs [ i ] += SFX_BRR_START - brr_spc_start
for i in SFX_NUM :
rom . seek ( brr_spc_addrs [ i ] )
2023-07-26 14:41:05 +09:30
# print('Loading sfx sample #%X with BRR data offset $%06X' % [i, rom.get_position()])
2023-07-25 18:37:55 +09:30
sfx_samples . append ( make_sample ( rom , 900 , sample_rates [ i ] ) ) # 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.
func load_samples ( rom : File ) :
2023-07-25 18:37:55 +09:30
load_sfx_samples_data ( rom )
2023-07-27 19:53:52 +09:30
# For some reason, this is a bit slow currently. Might optimize later.
2023-07-25 14:21:10 +09:30
for i in INST_NUM :
instrument_samples . append ( get_inst_sample_data ( rom , i ) )
2023-07-27 19:53:52 +09:30
emit_signal ( ' audio_inst_sample_loaded ' , i )
2023-07-25 14:21:10 +09:30
2023-07-25 16:33:45 +09:30
func get_song_data ( rom : File , id : int ) :
var lookup_offset : = BGM_LOOKUP + ( id * 3 )
rom . seek ( lookup_offset )
var offset : = read_rom_address ( rom )
var bank : = offset & 0xFF0000
rom . seek ( offset )
var _block_size : = rom . get_16 ( ) # Unused since we pull the individual tracks
var track_ptrs = [ ]
for i in 10 :
var a : = bank + rom . get_16 ( )
if a < offset :
a += 0x010000 # Bank shift
track_ptrs . append ( a )
if track_ptrs [ 0 ] != track_ptrs [ 1 ] :
print ( ' Master is not channel 1, interesting ' , track_ptrs )
var tracks = [ ]
for i in range ( 1 , track_ptrs . size ( ) - 1 ) :
var length = track_ptrs [ i + 1 ] - track_ptrs [ i ]
tracks . append ( rom . get_buffer ( length ) )
return tracks
func load_bgms ( rom : File ) :
for i in BGM_NUM :
bgm_tracks . append ( get_song_data ( rom , i ) )
2023-07-25 14:21:10 +09:30
var player : = AudioStreamPlayer . new ( ) # Make one for each channel, later
func play_sample ( id : int ) :
2023-08-23 19:38:59 +09:30
print ( ' Playing inst sample # %02X ' % id )
2023-07-25 14:21:10 +09:30
player . stream = instrument_samples [ id ]
player . play ( )
func play_sfx ( id : int ) :
2023-08-23 19:38:59 +09:30
print ( ' Playing sfx sample # %02X ' % id )
2023-07-25 14:21:10 +09:30
player . stream = sfx_samples [ id ]
player . play ( )
2023-07-25 16:33:45 +09:30
func parse_rom ( rom : File ) :
load_samples ( rom )
2023-07-26 21:52:54 +09:30
#load_bgms(rom)
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 )
# Called every frame. 'delta' is the elapsed time since the previous frame.
#func _process(delta: float) -> void:
# pass