extends Node const BGM_NUM := 70 const BGM_LOOKUP := 0x043B97 const INST_NUM := 35 const INST_BRR_LOOKUP := 0x043C6F const INST_LOOP := 0x043CD8 const INST_SR := 0x043D1E const INST_ADSR := 0x043D64 const SFX_NUM := 8 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 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) const MAX_15B = 1 << 15 const MAX_16B = 1 << 16 func unsigned16_to_signed(unsigned): return (unsigned + MAX_15B) % MAX_16B - MAX_15B 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 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 if exponent > 12: 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, size: int, sample_rate: int) -> AudioStreamSample: 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 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: # print('End flag on packet') break var audio_data = PoolByteArray() for i in range(2, samples.size()): var b = samples[i] audio_data.append(b & 0xFF) audio_data.append(b >> 8) audio.data = audio_data return audio func get_inst_sample_data(rom: File, id: int) -> AudioStreamSample: rom.seek(INST_SR + (id*2)) 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. var lookup_offset := INST_BRR_LOOKUP + (id*3) rom.seek(lookup_offset) var brr_offset := read_rom_address(rom) rom.seek(brr_offset) var size := rom.get_16() 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 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 func load_sfx_samples_data(rom: File): var sample_rates = [] rom.seek(SFX_SR) for i in SFX_NUM: var tuning1 := rom.get_8() var tuning2 := rom.get_8() sample_rates.append(get_reference_pitch_samplerate(tuning1)) 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] for i in SFX_NUM: brr_spc_addrs[i] += SFX_BRR_START - brr_spc_start for i in SFX_NUM: rom.seek(brr_spc_addrs[i]) # print('Loading sfx sample #%X with BRR data offset $%06X' % [i, rom.get_position()]) 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 # 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): load_sfx_samples_data(rom) for i in INST_NUM: instrument_samples.append(get_inst_sample_data(rom, i)) 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)) 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 parse_rom(rom: File): load_samples(rom) load_bgms(rom) func _ready() -> void: add_child(player) # Called every frame. 'delta' is the elapsed time since the previous frame. #func _process(delta: float) -> void: # pass