[BGM] Refactor playback and rendering to a Manager singleton

This commit is contained in:
Luke Hubmayer-Werner 2024-07-26 19:08:08 +09:30
parent 9abf1b49c8
commit 603c84cbc1
8 changed files with 250 additions and 189 deletions

View File

@ -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?

View File

@ -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

View File

@ -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:

View File

@ -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:

View File

@ -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()

View File

@ -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<bgm_id: int, PoolByteArray> - used to create texture to render
var bgm_target_times := {} # dict<bgm_id: int, float>
var bgm_loop_endpoints := {} # dict<bgm_id: int, Vector2>
var bgm_tracksets := {} # dict<bgm_id: int, Array<unrolled events>>
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()

View File

@ -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()

View File

@ -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