From 603c84cbc10effcd1d0b6a007e8fa1f765f610c9 Mon Sep 17 00:00:00 2001 From: Luke Hubmayer-Werner Date: Fri, 26 Jul 2024 19:08:08 +0930 Subject: [PATCH] [BGM] Refactor playback and rendering to a Manager singleton --- README.md | 2 +- project.godot | 3 +- scripts/MusicRenderer.gd | 8 +- scripts/audio_renderer.gd | 34 +++-- scripts/loaders/RomLoader.gd | 2 + scripts/managers/MusicManager.gd | 212 +++++++++++++++++++++++++++++++ test/audio_system.gd | 176 +------------------------ test/audio_system.tscn | 2 - 8 files changed, 250 insertions(+), 189 deletions(-) create mode 100644 scripts/managers/MusicManager.gd diff --git a/README.md b/README.md index 6dd1e2c..25c8576 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ This will be an interpreter, I am not hardcoding the thousands of scripts. ### Sound System - [x] Instrument samples -- [x] Basic Music playing (currently loops correctly, but tuning is a bit off on looped samples, possible Godot bug to investigate) +- [x] Basic Music playing (currently loops correctly for most songs) - [ ] DSP stuff including ADSR envelopes # What's different? diff --git a/project.godot b/project.godot index 413d079..6b0bdeb 100644 --- a/project.godot +++ b/project.godot @@ -33,6 +33,7 @@ SaveLoader="*res://scripts/loaders/SaveLoader.gd" ThemeManager="*res://scripts/managers/ThemeManager.gd" StringLoader="*res://scripts/loaders/StringLoader.gd" HTML5="*res://scripts/managers/HTML5.gd" +MusicManager="*res://scripts/managers/MusicManager.gd" [debug] @@ -65,7 +66,7 @@ texture={ [rendering] -quality/driver/driver_name="GLES2" +quality/driver/fallback_to_gles2=true 2d/snapping/use_gpu_pixel_snap=true vram_compression/import_s3tc=false vram_compression/import_etc=true diff --git a/scripts/MusicRenderer.gd b/scripts/MusicRenderer.gd index 82bc254..0d9a0ce 100644 --- a/scripts/MusicRenderer.gd +++ b/scripts/MusicRenderer.gd @@ -459,12 +459,10 @@ static func render_channels(tracks: Array, inst_map: Array, _debug_name := 'none midi_events_bytes3.put_32(0) midi_events_bytes_adsr.put_32(0) data += midi_events_bytes_t_event_start.data_array + midi_events_bytes_t_end.data_array + midi_events_bytes3.data_array + midi_events_bytes_adsr.data_array + midi_events_bytes_t_note_start.data_array - var smp_loop_start = -1 - var smp_loop_end = -1 + var t_loop_endpoints := Vector2(-1, -1) if highest_channel_p_return >= 0: - smp_loop_start = curve_master_tempo.get_integral(highest_channel_p_return + 100) * 32000 - smp_loop_end = curve_master_tempo.get_integral(longest_channel_p_end + 100) * 32000 - return [data, target_time_length, [smp_loop_start, smp_loop_end]] + t_loop_endpoints = Vector2(curve_master_tempo.get_integral(highest_channel_p_return + 100), curve_master_tempo.get_integral(longest_channel_p_end + 100)) + return [data, target_time_length, t_loop_endpoints] static func disassemble_channel_events(channel_events: Array, inst_map: Array) -> PoolStringArray: diff --git a/scripts/audio_renderer.gd b/scripts/audio_renderer.gd index 373bee5..819f826 100644 --- a/scripts/audio_renderer.gd +++ b/scripts/audio_renderer.gd @@ -21,6 +21,8 @@ onready var cached_renders: Dictionary = {} # key: [remaining_samples, PoolByte onready var current_textures: Array = [] # of ImageTextures - Needed to prevent GC before draw onready var waiting_for_viewport: Array = [] onready var done_first_draw: bool = false +var initialized_instrument_texture := false + func _ready() -> void: self.material = ShaderMaterial.new() @@ -65,14 +67,23 @@ func _update_viewport(width: int, height: int) -> void: self.material.set_shader_param('INT_OUTPUT_WIDTH', OUTPUT_WIDTH) +func initialize_instrument_texture(reinitialize := false) -> void: + if not reinitialize and self.initialized_instrument_texture: + return + SoundLoader.samples_to_texture() + self.material.set_shader_param('instrument_samples', SoundLoader.samples_tex) + self.material.set_shader_param('instrument_samples_size', SoundLoader.samples_tex.get_size()) + self.initialized_instrument_texture = true + + func _get_cached_midi(key: String) -> Array: # [target_samples: int, tex: ImageTexture, tempo_scale_thousandths: int] if not('-' in key): return self.cached_midis[key] + [1000] var split := key.split('-') var tempo_scale_thousandths := int(split[1]) - var target_samples_and_tex = self.cached_midis[split[0]] - var new_target_samples: int = (target_samples_and_tex[0]*1000)/tempo_scale_thousandths - return [new_target_samples, target_samples_and_tex[1], tempo_scale_thousandths] + var target_time_and_tex = self.cached_midis[split[0]] + var new_target_samples: int = (target_time_and_tex[0]*32000*1000)/tempo_scale_thousandths + return [new_target_samples, target_time_and_tex[1], tempo_scale_thousandths] func _render_midi(key: String, output_rows_drawn_including_this: int, rows_to_draw: int) -> void: @@ -153,26 +164,29 @@ func _render_just_ahead_of_time() -> void: # Optimized for latency self.waiting_for_viewport.append([rows_to_draw, key]) # Grab the result next draw -func push_image(image: Image, target_samples: int, key: String, enqueue: bool = true) -> void: +func push_image(image: Image, target_time: int, key: String, enqueue: bool = true) -> void: + if not self.initialized_instrument_texture: + self.initialize_instrument_texture() + var tex := ImageTexture.new() tex.create_from_image(image, 0) - self.cached_midis[key] = [target_samples, tex] + self.cached_midis[key] = [target_time, tex] self.material.set_shader_param('midi_events_size', tex.get_size()) # Should all be the same size for now, revisit if we need mixed sizes. if enqueue: - self.render_queue.append([key, target_samples]) + self.render_queue.append([key, target_time]) -func push_bytes(data: PoolByteArray, target_samples: int, key: String, enqueue: bool = true) -> void: +func push_bytes(data: PoolByteArray, target_time: int, key: String, enqueue: bool = true) -> void: var rows = int(pow(2, ceil(log((len(data)/INPUT_BYTES_PER_TEXEL) / INPUT_TEX_WIDTH)/log(2)))) var target_length = rows * INPUT_BYTES_PER_TEXEL * INPUT_TEX_WIDTH while len(data) < target_length: # This is inefficient, but this function should be called with pre-padded data anyway data.append(0) var image := Image.new() image.create_from_data(INPUT_TEX_WIDTH, rows, false, INPUT_FORMAT, data) - self.push_image(image, target_samples, key, enqueue) + self.push_image(image, target_time, key, enqueue) func queue_cached_bgm(key: String) -> void: - var new_target_samples_etc := self._get_cached_midi(key) - self.render_queue.append([key, new_target_samples_etc[0]]) + var new_target_time_etc := self._get_cached_midi(key) + self.render_queue.append([key, new_target_time_etc[0]]) func get_result() -> void: diff --git a/scripts/loaders/RomLoader.gd b/scripts/loaders/RomLoader.gd index bfb965e..f1b80ad 100644 --- a/scripts/loaders/RomLoader.gd +++ b/scripts/loaders/RomLoader.gd @@ -105,6 +105,8 @@ func load_snes_rom_from_bytes(bytes: PoolByteArray) -> void: SpriteLoader.load_battle_bgs(self.snes_data, self.snes_buffer) yield(_on_loader_loading_stage_updated('Loading map data', 'MapLoader'), 'completed') MapLoader.load_snes_rom(self.snes_data, self.snes_buffer) + yield(_on_loader_loading_stage_updated('Parsing music data', 'MusicManager'), 'completed') + MusicManager.load_snes_rom(self.snes_buffer) yield(_on_loader_loading_stage_updated('Finished loading!', 'RomLoader'), 'completed') emit_signal('rom_loaded') var music = load('res://scripts/loaders/snes/music_ff5.gd').new() diff --git a/scripts/managers/MusicManager.gd b/scripts/managers/MusicManager.gd new file mode 100644 index 0000000..1bafe8f --- /dev/null +++ b/scripts/managers/MusicManager.gd @@ -0,0 +1,212 @@ +extends Node2D +const MusicRenderer := preload('res://scripts/MusicRenderer.gd') +var MusicLoader := preload('res://scripts/loaders/snes/music_ff5.gd').new() +onready var audio_renderer: Node = SoundLoader.audio_renderer +# Have two music players to crossfade between +onready var music_player_1 := AudioStreamPlayer.new() +onready var music_player_2 := AudioStreamPlayer.new() +var current_bgm := '' +var queued_awaiting_render_bgm := '' +# SFX don't currently overlap, so just one for now +onready var sfx_player := AudioStreamPlayer.new() + +var max_cached_bgms := 6 +var cached_bgms := {} +var cached_bgms_lru := [] # For cache eviction purposes +var bgm_tex_bytes := {} # dict - used to create texture to render +var bgm_target_times := {} # dict +var bgm_loop_endpoints := {} # dict +var bgm_tracksets := {} # dict> +const NUM_CHANNELS := 8 + +func _evict_pcm_cache() -> void: + # Remove least-recently-used PCM prerenders + var entries_to_evict := len(self.cached_bgms_lru) - self.max_cached_bgms + if entries_to_evict < 1: + return + for i in entries_to_evict: + var key: String = self.cached_bgms_lru.pop_front() + self.cached_bgms.erase(key) + +func _ready() -> void: + music_player_1.name = 'music_player_1' + music_player_2.name = 'music_player_2' + sfx_player.name = 'sfx_player' + add_child(music_player_1) + add_child(music_player_2) + add_child(sfx_player) + audio_renderer.connect('render_initial_ready', self, '_on_render_initial_ready') + audio_renderer.connect('render_complete', self, '_on_render_complete') + +func stop() -> void: + self.music_player_1.stop() + self.music_player_2.stop() + self.sfx_player.stop() + self.queued_awaiting_render_bgm = '' + self.current_bgm = '' + +func play_bgm(key: String) -> void: + print('@%dms - Playing %s' % [get_ms(), key]) + var bgm_id := int(key.substr(3, 2)) + if not (bgm_id in audio_renderer.cached_midis): + self.queue_prerender_bgm(bgm_id) + # TODO: crossfade + var target_time := 0.0 + if self.music_player_1.playing and self.current_bgm.substr(0, 5) == key.substr(0, 5): + # Try live transition + var tempo_thou := int(key.substr(6)) + var old_tempo_thou := int(self.current_bgm.substr(6)) + var old_playback_pos: float = self.music_player_1.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]) + if key in self.cached_bgms: + self.music_player_1.stream = self.cached_bgms[key] + self.music_player_1.play(target_time) + self.cached_bgms_lru.erase(key) # Move to end of LRU + self.cached_bgms_lru.append(key) + self.queued_awaiting_render_bgm = '' + self.current_bgm = key + else: + self.queued_awaiting_render_bgm = key + audio_renderer.queue_cached_bgm(key) + +func update_tempo(new_tempo_thou: int) -> void: + if not self.music_player_1.playing: + return + self.play_bgm('%s-%04d'%[self.current_bgm.substr(0, 5), new_tempo_thou]) + + +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 prepare_bgm_for_rendering(bgm_id: int) -> void: + var data_and_target_time_and_loops = MusicRenderer.render_channels(self.bgm_tracksets[bgm_id], RomLoader.snes_data.bgm_instrument_indices[bgm_id], 'BGM%02d'%bgm_id) + self.bgm_tex_bytes[bgm_id] = data_and_target_time_and_loops[0] + self.bgm_target_times[bgm_id] = data_and_target_time_and_loops[1] + self.bgm_loop_endpoints[bgm_id] = data_and_target_time_and_loops[2] + +func get_bgm_loop_endpoints(bgm_id: int, tempo_thou: int = 1000) -> Vector2: + if not (bgm_id in bgm_loop_endpoints): + self.prepare_bgm_for_rendering(bgm_id) + var endpoints_1000: Vector2 = bgm_loop_endpoints[bgm_id] + return endpoints_1000 / tempo_thou + +func queue_prerender_bgm(bgm_id: int) -> void: + if not audio_renderer.initialized_instrument_texture: + audio_renderer.initialize_instrument_texture() + if not (bgm_id in self.bgm_tex_bytes): + self.prepare_bgm_for_rendering(bgm_id) + var bgm_key := 'BGM%02d'%bgm_id + audio_renderer.push_bytes(self.bgm_tex_bytes[bgm_id], self.bgm_target_times[bgm_id], bgm_key, false) + + +func render_all_bgm(bgms_to_render: int = 70) -> void: + audio_renderer.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 _cache_bgm(cache_key: String, data: PoolByteArray, is_complete: bool = false) -> void: + var rendered_audio: AudioStreamSample + # var cache_key := 'BGM%02d-%04d' % [bgm_id, tempo_thou] + var bgm_id: int + var tempo_thou: int + if cache_key.substr(0, 3) == 'BGM': + bgm_id = int(cache_key.substr(3, 2)) + tempo_thou = int(cache_key.substr(6)) + if cache_key in self.cached_bgms: + rendered_audio = self.cached_bgms[cache_key] + if is_complete: + rendered_audio.data = data # Don't overwrite if it's incomplete + else: + rendered_audio = AudioStreamSample.new() + rendered_audio.data = data + rendered_audio.stereo = true + rendered_audio.mix_rate = 32000 + rendered_audio.format = AudioStreamSample.FORMAT_16_BITS + + var t_endpoints: Vector2 = bgm_loop_endpoints[bgm_id] * 1000 / tempo_thou # Floating point, safe to divide here + var smp_endpoints: Vector2 = (t_endpoints * 32000).round() + if smp_endpoints.x >= 0: + rendered_audio.loop_begin = int(smp_endpoints.x) + rendered_audio.loop_end = int(smp_endpoints.y) + rendered_audio.loop_mode = AudioStreamSample.LOOP_FORWARD + print('Should loop from %.2fs to %.2fs' % [t_endpoints.x, t_endpoints.y]) + + self.cached_bgms[cache_key] = rendered_audio + self.cached_bgms_lru.erase(cache_key) # Move to end of LRU + self.cached_bgms_lru.append(cache_key) + + if self.queued_awaiting_render_bgm == cache_key: + print('@%dms - Rendered initial chunk of %s for immediate playback (%d samples at 32kHz = %.2fs)' % [get_ms(), cache_key, len(data)/4, rendered_audio.get_length()]) + self.play_bgm(cache_key) + if save_prerendered_audio: + var error = self.cached_bgms[cache_key].save_to_wav('output/rendered_%s.wav'%cache_key) + print('@%dms - Saved render of %s (error code %s)' % [get_ms(), cache_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, cache_key, self.cached_bgms[cache_key].get_length(), len(data)/0x100000] + +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 data: PoolByteArray = remaining_samples_and_data[1] + self._cache_bgm(key, data, false) + +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) # Purge from audio_renderer's output cache + 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._cache_bgm(key, data, true) + +func load_snes_rom(snes_buffer: StreamPeerBuffer) -> void: + var buffer: StreamPeerBuffer = snes_buffer.duplicate() + for i in SoundLoader.BGM_NUM: + self._evaluate_bgm(buffer, i) + +func _evaluate_bgm(buffer: StreamPeerBuffer, id: int): + 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 + self.bgm_tracksets[id] = MusicLoader.unroll_bgm(buffer.duplicate(), bgm_song_ptr, track_ptrs, end_ptr, '%02d'%id) + +func _process(_delta): + update() + +func _draw() -> void: + if audio_renderer.waiting_for_viewport: + self._get_prerendered_audio() diff --git a/test/audio_system.gd b/test/audio_system.gd index c953064..035de39 100644 --- a/test/audio_system.gd +++ b/test/audio_system.gd @@ -6,19 +6,12 @@ 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: Node = SoundLoader.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 @@ -69,45 +62,6 @@ func _enable_inst_button(id: int): 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 - bgm_tracksets[id] = MusicLoader.unroll_bgm(buffer.duplicate(), bgm_song_ptr, track_ptrs, end_ptr, '%02d'%id) - -func _play_bgm_jaot(key: String) -> void: - self.queued_bgm_playback = '' - 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 key == self.current_bgm_playback: - self.audio_player.play(0) - return - 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 - func play_bgm(id: int, live: bool) -> void: self._stop_all() @@ -122,22 +76,14 @@ func play_bgm(id: int, live: bool) -> void: 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) + self.music_player = MusicPlayer.new(MusicManager.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 + var key = 'BGM%02d-%04d' % [id, tempo_thou] + MusicManager.play_bgm(key) func _play_bgm_live() -> void: self.play_bgm($sb_bgm.value, true) @@ -155,8 +101,6 @@ func _create_bgm_playback() -> void: $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: @@ -164,7 +108,7 @@ func _stop_all() -> void: self.music_player.queue_free() self.music_player = null SoundLoader.player.stop() - self.audio_player.stop() + MusicManager.stop() func _update_bgm_label(id = 0) -> void: @@ -177,11 +121,7 @@ func _update_bgm_label(id = 0) -> void: 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 + MusicManager.update_tempo(tempo_thou) func _reset_tempo() -> void: self._update_tempo() @@ -198,11 +138,6 @@ func _ready() -> void: $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 @@ -213,19 +148,10 @@ func get_ms(restart: bool = false) -> int: 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 disassembly: PoolStringArray = MusicRenderer.disassemble_bgm(MusicManager.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: @@ -233,88 +159,6 @@ func save_bgm_disassembly(bgm_id: int, filename: String = '') -> void: 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 - print('Should loop from %.2fs to %.2fs' % [rendered_audio.loop_begin/32000.0, rendered_audio.loop_end/32000.0]) - 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 - print('Should loop from %.2fs to %.2fs' % [self.prerendered_bgms[key].loop_begin/32000.0, self.prerendered_bgms[key].loop_end/32000.0]) - 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() @@ -332,11 +176,3 @@ func get_shader_test_pattern() -> PoolByteArray: # 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() diff --git a/test/audio_system.tscn b/test/audio_system.tscn index 683f03f..58a5cac 100644 --- a/test/audio_system.tscn +++ b/test/audio_system.tscn @@ -62,8 +62,6 @@ margin_right = 84.0 margin_bottom = 137.0 text = "BGM Playback" -[node name="audio_player" type="AudioStreamPlayer" parent="."] - [node name="btn_exit" type="Button" parent="."] margin_left = 245.0 margin_top = 218.0