[BGM] Fix default volume and TrackCurve edge cases
Also clean up some other stuff
This commit is contained in:
parent
24eab16356
commit
26f83ef224
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]])
|
||||
|
|
Loading…
Reference in New Issue