344 lines
13 KiB
GDScript
344 lines
13 KiB
GDScript
extends Node2D
|
|
#warning-ignore-all:return_value_discarded
|
|
signal exit
|
|
const MusicPlayer := preload('res://scripts/MusicPlayer.gd')
|
|
const MusicRenderer := preload('res://scripts/MusicRenderer.gd')
|
|
var MusicLoader := preload('res://scripts/loaders/snes/music_ff5.gd').new()
|
|
onready var bgm_titles := Common.load_glyph_table('res://data/5/bgm_titles.txt')
|
|
onready var audio_renderer := $'%audio_renderer'
|
|
onready var audio_player := $audio_player
|
|
var prerendered_bgms := {}
|
|
var prerendered_bgm_start_and_end_loops := {}
|
|
var inst_buttons = []
|
|
var sfx_buttons = []
|
|
var initialized_instrument_texture := false
|
|
var queued_bgm_playback := ''
|
|
var current_bgm_playback := ''
|
|
|
|
const NUM_CHANNELS := 8
|
|
var music_player = null
|
|
var inst_sample_map := {}
|
|
var bgm_tracksets := {}
|
|
|
|
func _create_sfx_buttons():
|
|
var disable_btn := !SoundLoader.has_loaded_audio_samples
|
|
var btn_width := 48
|
|
var btn_height := 22
|
|
var btns_per_row := 8
|
|
var inst_pos_offset: Vector2 = $inst_buttons.rect_position
|
|
for i in SoundLoader.INST_NUM:
|
|
var btn = Button.new()
|
|
btn.text = 'Inst #%02d' % i
|
|
btn.align = Button.ALIGN_CENTER
|
|
btn.set_position(Vector2((i%btns_per_row)*btn_width, (i/btns_per_row)*btn_height) + inst_pos_offset)
|
|
btn.rect_min_size.x = btn_width
|
|
add_child(btn)
|
|
btn.connect('pressed', SoundLoader, 'play_sample', [i])
|
|
inst_buttons.append(btn)
|
|
btn.disabled = disable_btn
|
|
btns_per_row = int($sfx_buttons.rect_size.x/btn_width)
|
|
var sfx_pos_offset: Vector2 = $sfx_buttons.rect_position
|
|
for i in SoundLoader.SFX_NUM:
|
|
var btn = Button.new()
|
|
btn.text = 'SFX #%02d' % i
|
|
btn.align = Button.ALIGN_CENTER
|
|
btn.set_position(Vector2((i%btns_per_row)*btn_width, (i/btns_per_row)*btn_height) + sfx_pos_offset)
|
|
btn.rect_min_size.x = btn_width
|
|
add_child(btn)
|
|
btn.connect('pressed', SoundLoader, 'play_sfx', [i])
|
|
sfx_buttons.append(btn)
|
|
btn.disabled = disable_btn
|
|
if disable_btn:
|
|
SoundLoader.connect('audio_samples_loaded', self, '_enable_sfx_buttons')
|
|
SoundLoader.connect('audio_inst_sample_loaded', self, '_enable_inst_button')
|
|
SoundLoader.connect('audio_sfx_sample_loaded', self, '_enable_sfx_button')
|
|
|
|
func _enable_sfx_buttons():
|
|
for btn in sfx_buttons:
|
|
btn.disabled = false
|
|
for btn in inst_buttons:
|
|
btn.disabled = false
|
|
|
|
func _enable_sfx_button(id: int):
|
|
# NB: This assumes sequential loading (may change, probably won't)
|
|
for i in id+1:
|
|
sfx_buttons[i].disabled = false
|
|
|
|
func _enable_inst_button(id: int):
|
|
# NB: This assumes sequential loading (may change, probably won't)
|
|
for i in id+1:
|
|
inst_buttons[i].disabled = false
|
|
|
|
func evaluate_bgm(id: int):
|
|
var buffer: StreamPeerBuffer = RomLoader.snes_buffer.duplicate()
|
|
var bgm_song_ptr: int = RomLoader.snes_data.bgm_song_pointers[id] & 0x3FFFFF
|
|
var bank_offset: int = bgm_song_ptr & 0x3F0000
|
|
buffer.seek(bgm_song_ptr)
|
|
var length := buffer.get_u16()
|
|
var rom_address_base := buffer.get_u16()
|
|
var track_ptrs := PoolIntArray()
|
|
for i in NUM_CHANNELS:
|
|
var track_ptr := buffer.get_u16() + bank_offset
|
|
if track_ptr < bgm_song_ptr:
|
|
track_ptr += 0x010000 # next bank
|
|
track_ptrs.append(track_ptr)
|
|
var end_ptr := buffer.get_u16() + bank_offset
|
|
if end_ptr < bgm_song_ptr:
|
|
end_ptr += 0x010000 # next bank
|
|
var tracks := []
|
|
for i in NUM_CHANNELS:
|
|
var track_ptr := track_ptrs[i]
|
|
# var channel: MusicChannel = self.channels[i]
|
|
# print('Unrolling BGM track %02d:%02d at 0x%06X:0x%06X:0x%06X' % [id, i, bgm_song_ptr, track_ptr, end_ptr])
|
|
tracks.append(MusicLoader.unroll_track(buffer.duplicate(), bgm_song_ptr, track_ptr, end_ptr, '%02d:%02d'%[id, i]))
|
|
bgm_tracksets[id] = tracks
|
|
|
|
func _play_bgm_jaot(key: String) -> void:
|
|
var target_time := 0.0
|
|
var bgm_key_and_tempo_thou := key.split('-')
|
|
var bgm_key := bgm_key_and_tempo_thou[0]
|
|
var tempo_thou := int(bgm_key_and_tempo_thou[1])
|
|
if audio_player.playing and self.current_bgm_playback.begins_with(bgm_key):
|
|
var old_tempo_thou := int(self.current_bgm_playback.split('-')[1])
|
|
var old_playback_pos: float = audio_player.get_playback_position()
|
|
target_time = old_playback_pos * old_tempo_thou / tempo_thou
|
|
print('Old temposcale %d, New temposcale %d, Old pos %.2f, New pos %.2f' % [old_tempo_thou, tempo_thou, old_playback_pos, target_time])
|
|
else:
|
|
print('audioplayer was not playing same bgm')
|
|
self.audio_player.stream = self.prerendered_bgms[key]
|
|
self.audio_player.play(target_time)
|
|
self.prerendered_bgms.erase(self.current_bgm_playback) # purge previous stream from cache
|
|
self.current_bgm_playback = key
|
|
self.queued_bgm_playback = ''
|
|
|
|
|
|
func play_bgm(id: int, live: bool) -> void:
|
|
self._stop_all()
|
|
if live:
|
|
print('@%dms - Playing BGM%02d' % [get_ms(), id])
|
|
var inst_indices = RomLoader.snes_data.bgm_instrument_indices[id]
|
|
for i in 16:
|
|
var inst_idx: int = inst_indices[i]-1
|
|
if inst_idx < 0:
|
|
self.inst_sample_map[i + 0x20] = null
|
|
else:
|
|
self.inst_sample_map[i + 0x20] = SoundLoader.instrument_samples_HACK_EXTENDED_LOOPS[inst_idx]
|
|
if self.music_player:
|
|
remove_child(music_player)
|
|
self.music_player = MusicPlayer.new(bgm_tracksets[id], self.inst_sample_map)
|
|
add_child(self.music_player)
|
|
self.music_player.is_playing = true
|
|
else:
|
|
# Play prerendered
|
|
var bgm_key = 'BGM%02d'%id
|
|
var tempo_thou = $slider_tempo.value * 10
|
|
var new_key = '%s-%04d' % [bgm_key, tempo_thou]
|
|
print('@%dms - Playing %s' % [get_ms(), new_key])
|
|
if not (bgm_key in self.prerendered_bgms):
|
|
self.queue_prerender_bgm(id)
|
|
if new_key in self.prerendered_bgms:
|
|
self._play_bgm_jaot(new_key)
|
|
else:
|
|
audio_renderer.queue_cached_bgm(new_key)
|
|
self.queued_bgm_playback = new_key
|
|
|
|
func _play_bgm_live() -> void:
|
|
self.play_bgm($sb_bgm.value, true)
|
|
|
|
func _play_bgm_prerendered() -> void:
|
|
self.play_bgm($sb_bgm.value, false)
|
|
|
|
|
|
func _create_bgm_playback() -> void:
|
|
$sb_bgm.max_value = SoundLoader.BGM_NUM
|
|
$sb_bgm.connect('value_changed', self, '_update_bgm_label')
|
|
self._update_bgm_label()
|
|
$btn_bgm_live.connect('pressed', self, '_play_bgm_live')
|
|
$btn_bgm_prerendered.connect('pressed', self, '_play_bgm_prerendered')
|
|
$btn_render.connect('pressed', self, 'render_all_bgm')
|
|
for i in SoundLoader.SFX_NUM:
|
|
self.inst_sample_map[i] = SoundLoader.sfx_samples[i]
|
|
for i in SoundLoader.BGM_NUM:
|
|
evaluate_bgm(i)
|
|
|
|
|
|
func _stop_all() -> void:
|
|
if self.music_player:
|
|
self.music_player.queue_free()
|
|
self.music_player = null
|
|
SoundLoader.player.stop()
|
|
self.audio_player.stop()
|
|
|
|
|
|
func _update_bgm_label(id = 0) -> void:
|
|
if id < len(bgm_titles):
|
|
$lbl_bgm_title.text = bgm_titles[id]
|
|
else:
|
|
$lbl_bgm_title.text = ''
|
|
|
|
|
|
func _update_tempo(tempo := 100) -> void:
|
|
$lbl_tempo.text = 'Tempo scale: %d' % tempo
|
|
var tempo_thou = tempo * 10
|
|
if audio_player.is_playing():
|
|
var bgm_key = 'BGM%02d-%04d' % [$sb_bgm.value, tempo_thou]
|
|
# TODO: render a tempo-scaled version
|
|
audio_renderer.queue_cached_bgm(bgm_key)
|
|
self.queued_bgm_playback = bgm_key
|
|
|
|
func _reset_tempo() -> void:
|
|
self._update_tempo()
|
|
$slider_tempo.value = 100
|
|
|
|
|
|
func _exit() -> void:
|
|
self.emit_signal('exit')
|
|
|
|
func _ready() -> void:
|
|
self._create_sfx_buttons()
|
|
self._create_bgm_playback()
|
|
$btn_stop.connect('pressed', self, '_stop_all')
|
|
$slider_tempo.connect('value_changed', self, '_update_tempo')
|
|
$btn_reset_tempo.connect('pressed', self, '_reset_tempo')
|
|
$btn_exit.connect('pressed', self, '_exit')
|
|
audio_renderer.connect('render_initial_ready', self, '_on_render_initial_ready')
|
|
audio_renderer.connect('render_complete', self, '_on_render_complete')
|
|
# for i in len(RomLoader.snes_data.bgm_song_pointers):
|
|
# var pointer = RomLoader.snes_data.bgm_song_pointers[i]
|
|
# print('BGM 0x%02X (%02d) at 0x%06X' % [i, i, pointer])
|
|
|
|
|
|
var load_start_tick: int
|
|
func get_ms(restart: bool = false) -> int:
|
|
if restart or not self.load_start_tick:
|
|
self.load_start_tick = Time.get_ticks_msec()
|
|
return 0
|
|
return Time.get_ticks_msec() - self.load_start_tick
|
|
|
|
|
|
func initialize_instrument_texture() -> void:
|
|
get_ms(true)
|
|
SoundLoader.samples_to_texture()
|
|
audio_renderer.material.set_shader_param('instrument_samples', SoundLoader.samples_tex)
|
|
audio_renderer.material.set_shader_param('instrument_samples_size', SoundLoader.samples_tex.get_size())
|
|
self.initialized_instrument_texture = true
|
|
print('@%dms - Initialized instrument samples texture' % get_ms())
|
|
|
|
|
|
func save_bgm_disassembly(bgm_id: int, filename: String = '') -> void:
|
|
if not filename:
|
|
filename = 'output/BGM%02d disassembly.txt' % bgm_id
|
|
var disassembly: PoolStringArray = MusicRenderer.disassemble_bgm(bgm_tracksets[bgm_id], RomLoader.snes_data.bgm_instrument_indices[bgm_id])
|
|
var file := File.new()
|
|
file.open(filename, File.WRITE)
|
|
for line in disassembly:
|
|
file.store_line(line)
|
|
file.close()
|
|
|
|
|
|
func queue_prerender_bgm(bgm_id: int) -> void:
|
|
if not self.initialized_instrument_texture:
|
|
self.initialize_instrument_texture()
|
|
var data_and_target_time_and_loops = MusicRenderer.render_channels(bgm_tracksets[bgm_id], RomLoader.snes_data.bgm_instrument_indices[bgm_id], 'BGM%02d'%bgm_id)
|
|
var data = data_and_target_time_and_loops[0]
|
|
var target_time = data_and_target_time_and_loops[1]
|
|
var target_samples = target_time * 32000
|
|
var bgm_key := 'BGM%02d'%bgm_id
|
|
audio_renderer.push_bytes(data, target_samples, bgm_key, false)
|
|
self.prerendered_bgm_start_and_end_loops[bgm_key] = data_and_target_time_and_loops[2]
|
|
|
|
|
|
func render_all_bgm(bgms_to_render: int = 70) -> void:
|
|
$btn_render.set_disabled(true)
|
|
self.initialize_instrument_texture()
|
|
for bgm_id in bgms_to_render:
|
|
self.queue_prerender_bgm(bgm_id)
|
|
# self.save_bgm_disassembly(bgm_id)
|
|
if bgm_id % 10 == 9 and bgm_id < bgms_to_render-1:
|
|
print('@%dms - Processed %d/%d bgm tracks' % [get_ms(), bgm_id+1, bgms_to_render])
|
|
print('@%dms - Processed %d bgm tracks and sent to gpu for rendering' % [get_ms(), bgms_to_render])
|
|
|
|
|
|
const save_prerendered_audio := false
|
|
var print_batch_results := ''
|
|
func _get_prerendered_audio():
|
|
self.print_batch_results = ''
|
|
audio_renderer.get_result()
|
|
if self.print_batch_results:
|
|
print('@%dms - Rendered %s without saving' % [get_ms(), self.print_batch_results.right(2)])
|
|
|
|
func _on_render_initial_ready(key: String):
|
|
print('@%dms - _on_render_initial_ready("%s")' % [get_ms(), key])
|
|
# Used for JAOT playback
|
|
var remaining_samples_and_data = audio_renderer.cached_renders[key]
|
|
|
|
var rendered_audio := AudioStreamSample.new()
|
|
rendered_audio.data = remaining_samples_and_data[1]
|
|
rendered_audio.stereo = true
|
|
rendered_audio.mix_rate = 32000
|
|
rendered_audio.format = AudioStreamSample.FORMAT_16_BITS
|
|
var tempo_thou := 1000
|
|
if '-' in key:
|
|
var bgm_key_and_tempo := key.split('-')
|
|
tempo_thou = int(bgm_key_and_tempo[1])
|
|
var bgm_key = bgm_key_and_tempo[0]
|
|
if prerendered_bgm_start_and_end_loops[bgm_key][0] >= 0:
|
|
rendered_audio.loop_begin = int(round(prerendered_bgm_start_and_end_loops[bgm_key][0] * 1000 / tempo_thou))
|
|
rendered_audio.loop_end = int(round(prerendered_bgm_start_and_end_loops[bgm_key][1] * 1000 / tempo_thou))
|
|
rendered_audio.loop_mode = AudioStreamSample.LOOP_FORWARD
|
|
else:
|
|
if prerendered_bgm_start_and_end_loops[key][0] >= 0:
|
|
rendered_audio.loop_begin = int(round(prerendered_bgm_start_and_end_loops[key][0]))
|
|
rendered_audio.loop_end = int(round(prerendered_bgm_start_and_end_loops[key][1]))
|
|
rendered_audio.loop_mode = AudioStreamSample.LOOP_FORWARD
|
|
self.prerendered_bgms[key] = rendered_audio
|
|
|
|
if self.queued_bgm_playback == key:
|
|
print('@%dms - Rendered initial chunk of %s for immediate playback (%d samples at 32kHz = %.2fs)' % [get_ms(), key, len(remaining_samples_and_data[1])/4, rendered_audio.get_length()])
|
|
self._play_bgm_jaot(key)
|
|
|
|
func _on_render_complete(key: String):
|
|
print('@%dms - _on_render_complete("%s")' % [get_ms(), key])
|
|
# Used for JAOT playback
|
|
var remaining_samples_and_data = audio_renderer.cached_renders[key]
|
|
audio_renderer.cached_renders.erase(key)
|
|
var data: PoolByteArray = remaining_samples_and_data[1]
|
|
|
|
if remaining_samples_and_data[0] != 0: # Should be 0
|
|
print_debug('render_completed signal for incomplete render! %s has %d remaining samples, should be 0'%[key, remaining_samples_and_data[0]])
|
|
# Assume _on_render_initial_ready was already called and AudioStreamSample has already been created
|
|
self.prerendered_bgms[key].data = data
|
|
if save_prerendered_audio:
|
|
var error = self.prerendered_bgms[key].save_to_wav('output/rendered_%s.wav'%key)
|
|
print('@%dms - Saved render of %s (error code %s)' % [get_ms(), key, globals.ERROR_CODE_STRINGS[error]])
|
|
else:
|
|
# print('@%dms - Rendered %s without saving' % [get_ms(), key])
|
|
self.print_batch_results = '%s, %s (%.2fs %.2fMiB)'%[self.print_batch_results, key, self.prerendered_bgms[key].get_length(), len(data)/0x100000]
|
|
|
|
|
|
func get_shader_test_pattern() -> PoolByteArray:
|
|
var midi_events_bytes := StreamPeerBuffer.new()
|
|
var midi_events_bytes2 := StreamPeerBuffer.new()
|
|
var midi_events_bytes3 := StreamPeerBuffer.new()
|
|
var midi_events_bytes4 := StreamPeerBuffer.new()
|
|
for i in 2048:
|
|
var t = i * 2.0
|
|
midi_events_bytes.put_32(t*32000) # t_start
|
|
midi_events_bytes2.put_32((t+1.75)*32000) # t_end
|
|
midi_events_bytes3.put_u8(i%35) # instrument
|
|
midi_events_bytes3.put_u8(71) # pitch_idx
|
|
# midi_events_bytes.put_float((35 + (i%40))) # pitch_idx
|
|
midi_events_bytes3.put_u8(255) # velocity
|
|
midi_events_bytes3.put_u8(0) # pan
|
|
# midi_events_bytes3.put_u8(i%256) # pan
|
|
midi_events_bytes4.put_32(0) # ADSR
|
|
return midi_events_bytes.data_array + midi_events_bytes2.data_array + midi_events_bytes3.data_array + midi_events_bytes4.data_array
|
|
|
|
|
|
func _process(_delta):
|
|
update()
|
|
|
|
func _draw() -> void:
|
|
if audio_renderer.waiting_for_viewport:
|
|
self._get_prerendered_audio()
|