[BGM] Fix default volume and TrackCurve edge cases

Also clean up some other stuff
This commit is contained in:
Luke Hubmayer-Werner 2024-07-19 21:44:38 +09:30
parent 24eab16356
commit 26f83ef224
3 changed files with 79 additions and 38 deletions

View File

@ -34,16 +34,18 @@ class TrackCurve: # built-in Curve class is too restrictive for this
self.entries.append(entry)
else: # Find the first entry bigger than pulse, and insert before
for i in l:
if self.entries[i].x > pulse:
if self.entries[i].x == pulse:
self.entries[i] = entry # Replace existing, this makes sense for the VOLUME -> VOLUME_SLIDE pattern
elif self.entries[i].x > pulse:
self.entries.insert(i, entry)
break
var last_pulse_block_get: int = -1 # Cache previous position for sequential lookups
func get_pulse(pulse: float) -> float:
var l := len(self.entries)
if l == 0 or pulse < self.entries[-1].x:
if l == 0 or pulse < self.entries[0].x:
return self.default
if pulse > self.entries[-1].x:
if pulse >= self.entries[-1].x:
return self.entries[-1].y
for i in l-2:
# Find first entry beyond
@ -94,7 +96,25 @@ class TrackCurve: # built-in Curve class is too restrictive for this
return integral
return 0.0
static func get_gcd(a: int, b: int) -> int: # Greatest Common Divisor of two numbers
# Euclidian reduction
var c
while b:
c = b
b = a % b
a = c
return a
static func get_lcm(a: int, b: int) -> int: # Least Common Multiple of two numbers
return (a * b) / get_gcd(a, b)
static func get_lcm_n(values: Array) -> int: # Least Common Multiple of an array of numbers
var lcm: int = values.pop_back()
for value in values:
lcm = get_lcm(lcm, value)
return lcm
const LOOP_OVERSHOOT := 768
static func render_channels(tracks: Array, inst_map: Array, _debug_name := 'none') -> Array: # [data: PoolByteArray, target_time_length: float in seconds]
# Since some channels contain global events (tempo and global volume for now),
# the strategy will be to preprocess each channel in a global-state-agnostic way,
@ -104,13 +124,13 @@ static func render_channels(tracks: Array, inst_map: Array, _debug_name := 'none
var sample_default_adsrs = RomLoader.snes_data.sfx_adsrs + RomLoader.snes_data.bgm_instrument_adsrs # TODO: UNHARDCODE THIS
var all_note_events = []
var curve_master_volume := TrackCurve.new(100.0/255.0) # [0.0, 1.0] for now
var curve_master_volume := TrackCurve.new(1.0) # [0.0, 1.0] for now
var curve_master_tempo := TrackCurve.new(120.0) # bpm is too big, need pulses per second
var curve_channel_pans := []
for channel in NUM_TRACKS:
var curve_velocity := TrackCurve.new(100.0/255.0) # [0.0, 1.0] for now
var curve_velocity := TrackCurve.new(1.0) # [0.0, 1.0] for now
var curve_pan := TrackCurve.new() # [-1.0, 1.0] for now
# Stored and unused for now
var curve_fine_tuning := TrackCurve.new() # [0.0, 1.0] for now
@ -145,7 +165,7 @@ static func render_channels(tracks: Array, inst_map: Array, _debug_name := 'none
var current_instrument := 0
var current_octave := 5
var current_transpose := 0
# var current_velocity := 100
# var current_velocity := 255
var current_adsr_attack_rate := 0
var current_adsr_decay_rate := 0
var current_adsr_sustain_level := 0
@ -342,14 +362,17 @@ static func render_channels(tracks: Array, inst_map: Array, _debug_name := 'none
# Integrate tempo so we can get a pulse->time mapping
curve_master_tempo.bake_integrals()
# Find the longest channel
var channel_loop_p_returns = PoolIntArray()
var channel_loop_p_lengths = PoolIntArray()
var channel_loop_p_returns := PoolIntArray()
var channel_loop_p_lengths := PoolIntArray()
var channel_p_ends := PoolIntArray()
var longest_channel_idx = 0
var longest_channel_p_end = 0
var highest_channel_p_return = -1
for channel in NUM_TRACKS:
if all_note_events[channel].empty():
channel_loop_p_returns.append(-1)
channel_loop_p_lengths.append(0)
channel_p_ends.append(0)
continue
var note_event: NoteEvent = all_note_events[channel][-1]
var p_end = note_event.p_end
@ -364,13 +387,28 @@ static func render_channels(tracks: Array, inst_map: Array, _debug_name := 'none
channel_loop_p_returns.append(-1)
channel_loop_p_lengths.append(0)
channel_p_ends.append(p_end)
if p_end > longest_channel_p_end:
longest_channel_p_end = p_end
longest_channel_idx = channel
var target_pulse_length = longest_channel_p_end + 200
var target_pulse_length = longest_channel_p_end + LOOP_OVERSHOOT
var target_time_length = curve_master_tempo.get_integral(target_pulse_length)
# # DEBUG: calculate LCM of the loop lengths to determine required length for a true loop point
# var unique_loop_lengths := []
# # TODO: find common loop pulses
# for loop_length in channel_loop_p_lengths:
# if loop_length > 0 and not (loop_length in unique_loop_lengths):
# unique_loop_lengths.append(loop_length)
# if unique_loop_lengths:
# var loop_length_lcm = get_lcm_n(unique_loop_lengths)
# var p_overall_loop_start = highest_channel_p_return #+ LOOP_OVERSHOOT
# var p_overall_loop_end = p_overall_loop_start + loop_length_lcm
# print('%s has lcm loop length %d pulses from loop lengths %s, from %d to %d.' % [_debug_name, loop_length_lcm, channel_loop_p_lengths, p_overall_loop_start, p_overall_loop_end])
# else:
# print('%s does not loop' % _debug_name)
# Second pass - encode the notes with the now-known global tempo and volume curves
var data := PoolByteArray()
for channel in NUM_TRACKS:

View File

@ -309,7 +309,7 @@ func unroll_track(buffer: StreamPeerBuffer, bgm_start_pos: int, track_start_pos:
print_info('Infinite GOTO found in track %s at 0x%06X:0x%06X:0x%06X: 0x%06X to 0x%06X (event number %d)' % [bgm_id, bgm_start_pos, track_start_pos, bgm_end_pos, pos, target_pos, event_num])
events.append([EventType.GOTO, rom_address_to_event_index[target_pos]])
break
else:
else: # The GOTO address isn't in our index yet, proceed to it
print_debug('forward GOTO found in track %s at 0x%06X:0x%06X:0x%06X: 0x%06X to 0x%06X' % [bgm_id, bgm_start_pos, track_start_pos, bgm_end_pos, pos, target_pos])
buffer.seek(target_pos)
_:
@ -318,25 +318,32 @@ func unroll_track(buffer: StreamPeerBuffer, bgm_start_pos: int, track_start_pos:
# DEBUG: Report if track doesn't end all the contained loops
if loop_level >= 0:
print_info('track %s ended on loop_level %d (0x%06X)' % [bgm_id, loop_level, loop_positions[loop_level]])
# DEBUG: Report total note duration and repeat note duration
if events:
print_info('%d events' % len(events))
var total_note_dur := 0
var total_notes := 0
var total_rests := 0
for e in events:
if e[0] == EventType.NOTE:
total_note_dur += e[2]
if e[1] >= 0:
total_notes += 1
else:
total_rests += 1
if events[-1][0] == EventType.GOTO:
var repeat_note_dur := 0
for e in events.slice(events[-1][1], -1):
if e[0] == EventType.NOTE:
repeat_note_dur += e[2]
print_info('track %s has duration %d and repeat duration %d (intro %d) - %d notes %d rests' % [bgm_id, total_note_dur, repeat_note_dur, total_note_dur-repeat_note_dur, total_notes, total_rests])
else:
print_info('track %s has duration %d - %d notes %d rests' % [bgm_id, total_note_dur, total_notes, total_rests])
# # DEBUG: Report total note duration and repeat note duration
# if events:
# print_info('%d events' % len(events))
# var total_note_dur := 0
# var total_notes := 0
# var total_rests := 0
# for e in events:
# if e[0] == EventType.NOTE:
# total_note_dur += e[2]
# if e[1] >= 0:
# total_notes += 1
# else:
# total_rests += 1
# if events[-1][0] == EventType.GOTO:
# var repeat_note_dur := 0
# for e in events.slice(events[-1][1], -1):
# if e[0] == EventType.NOTE:
# repeat_note_dur += e[2]
# print_info('track %s has duration %d and repeat duration %d (intro %d) - %d notes %d rests' % [bgm_id, total_note_dur, repeat_note_dur, total_note_dur-repeat_note_dur, total_notes, total_rests])
# events[-1].append_array([total_note_dur-repeat_note_dur, total_note_dur])
# else:
# print_info('track %s has duration %d - %d notes %d rests' % [bgm_id, total_note_dur, total_notes, total_rests])
return events
func unroll_bgm(buffer: StreamPeerBuffer, bgm_start_address: int, track_start_addresses: PoolIntArray, bgm_end_address: int, bgm_id='N/A') -> Array:
var tracks := []
for track_start_address in track_start_addresses:
tracks.append(self.unroll_track(buffer, bgm_start_address, track_start_address, bgm_end_address, bgm_id))
return tracks

View File

@ -85,13 +85,7 @@ func evaluate_bgm(id: int):
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
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 = ''
@ -294,6 +288,7 @@ func _on_render_initial_ready(key: String):
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:
@ -311,6 +306,7 @@ func _on_render_complete(key: String):
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]])