ChocolateBird/scripts/loaders/sound_loader.gd

209 lines
6.6 KiB
GDScript3
Raw Normal View History

2023-07-25 14:21:10 +09:30
extends Node
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
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)
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)
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
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')
2023-07-25 14:21:10 +09:30
break
var audio_data = PoolByteArray()
for i in range(2, samples.size()):
var b = samples[i]
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))
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()
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
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:
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])
# 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
# 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-25 14:21:10 +09:30
for i in INST_NUM:
instrument_samples.append(get_inst_sample_data(rom, i))
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):
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()
2023-07-25 16:33:45 +09:30
func parse_rom(rom: File):
load_samples(rom)
load_bgms(rom)
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