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 := '' # TODO: Add a tempo slider, a uniform in the shader for tempo scale, and use these to demo JAOT rendering 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(id: int, live: bool) -> void: self._stop_all() if live: 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 if bgm_key in self.prerendered_bgms: self.audio_player.stream = self.prerendered_bgms[bgm_key] self.audio_player.play() else: self.queue_prerender_bgm(id) self.queued_bgm_playback = bgm_key print('Playing BGM%02d' % id) 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 _exit() -> void: self.emit_signal('exit') func _ready() -> void: self._create_sfx_buttons() self._create_bgm_playback() $btn_stop.connect('pressed', self, '_stop_all') $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) 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() print('@%dms - Rendered %s without saving' % [get_ms(), self.print_batch_results.right(2)]) func _on_render_initial_ready(key: String): # 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 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' % [get_ms(), key]) self.audio_player.stream = rendered_audio self.audio_player.play() self.queued_bgm_playback = '' func _on_render_complete(key: String): # Used for JAOT playback var remaining_samples_and_data = audio_renderer.cached_renders[key] audio_renderer.cached_renders.erase(key) 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 = remaining_samples_and_data[1] 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'%[self.print_batch_results, key] 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()