Compare commits

...

5 Commits

Author SHA1 Message Date
Luke Hubmayer-Werner 8990a6637e [BGM] Fix slide behaviour
Previously it just lerped from the value at the start of the event to the one at the end of the event.
This did not account for slides that ended in the middle of the event.
Note that slides beginning in the middle of the event are impossible in this sequence format.
2024-07-28 22:43:44 +09:30
Luke Hubmayer-Werner f8e3def5d8 [BGM] Fix pitch slide reset logic
Seems like it actually modifies the tie note value, so it should reset whenever the tied note changes
2024-07-28 21:22:33 +09:30
Luke Hubmayer-Werner e5530e1dd1 [BGM] Rudimentary Pitch Slide
Need to dig deeper on the reset conditions, these are wrong for many songs.
Also slides in general need to have the actual end sample for slides that finish mid-note-event.
2024-07-28 20:40:48 +09:30
Luke Hubmayer-Werner c6c4a39d9c [BGM] Render volume and pan slides 2024-07-28 15:31:36 +09:30
Luke Hubmayer-Werner 5fed1672c9 [BGM] Fix off-by-one in curve interpolator 2024-07-28 15:05:28 +09:30
2 changed files with 110 additions and 54 deletions

View File

@ -5,13 +5,16 @@ const EventType := music.EventType
var MUSIC := music.new()
const NUM_TRACKS := 8 # TODO
const MAX_NOTE_EVENTS := 4096
const CURVE_EPSILON := 0.0000001
class NoteEvent:
var p_event_start: int # In pulse space
var p_note_start: int # For tied notes, this will be earlier than p_event_start and is used for envelope calculations
var p_end: int
var instrument: int
var pitch: int = 0
var velocity: float = 0.0
var pitch_slide: Vector3
var velocity: Vector3
var pan: Vector3
var adsr_attack_rate: int
var adsr_decay_rate: int
var adsr_decay_total_periods: int
@ -40,6 +43,16 @@ class TrackCurve: # built-in Curve class is too restrictive for this
self.entries.insert(i, entry)
break
func add_slide(pulse_start: int, duration: int, value: float, zero_is_256: bool = false) -> void:
if duration == 0:
if zero_is_256:
duration = 256
else:
self.add_point(pulse_start, value)
return
self.add_point(pulse_start, self.get_pulse(pulse_start), true)
self.add_point(pulse_start + duration, value)
var last_pulse_block_get: int = -1 # Cache previous position for sequential lookups
func get_pulse(pulse: float) -> float:
var l := len(self.entries)
@ -47,7 +60,7 @@ class TrackCurve: # built-in Curve class is too restrictive for this
return self.default
if pulse >= self.entries[-1].x:
return self.entries[-1].y
for i in l-2:
for i in l-1:
# Find first entry beyond
if pulse < self.entries[i+1].x:
if self.entries[i].z > 0: # ramp_to_next
@ -56,6 +69,25 @@ class TrackCurve: # built-in Curve class is too restrictive for this
return self.entries[i].y
return self.default # Should be unreachable
func get_pulse_and_slide(pulse: float) -> Vector3: # [value, end_value, p_end_value]
var value = self.default
var l := len(self.entries)
if l == 0 or pulse < self.entries[0].x:
return Vector3(value, value, pulse)
if pulse >= self.entries[-1].x:
value = self.entries[-1].y
return Vector3(value, value, pulse)
for i in l-1:
# Find first entry beyond
if pulse < self.entries[i+1].x:
if self.entries[i].z > 0: # ramp_to_next
value = range_lerp(pulse, self.entries[i].x, self.entries[i+1].x, self.entries[i].y, self.entries[i+1].y)
return Vector3(value, self.entries[i+1].y, self.entries[i+1].x)
else:
value = self.entries[i].y
return Vector3(value, value, pulse)
return Vector3(value, value, pulse) # Should be unreachable
func bake_integrals():
# Store the starting integrated value (i.e. time for the tempo curve) of each pulse value
self.baked_integrals.clear()
@ -148,6 +180,7 @@ static func render_channels(tracks: Array, inst_map: Array, _debug_name := 'none
var curve_pan_lfo_depth := TrackCurve.new()
var curve_noise_on := TrackCurve.new() # [0.0, 1.0] for now
var curve_noise_freq := TrackCurve.new()
var curve_pitch_slide := TrackCurve.new()
var curve_pitchmod_on := TrackCurve.new() # [0.0, 1.0] for now
var curve_echo_on := TrackCurve.new() # [0.0, 1.0] for now
var curve_echo_volume := TrackCurve.new()
@ -202,19 +235,19 @@ static func render_channels(tracks: Array, inst_map: Array, _debug_name := 'none
var new_tempo = music.tempo_to_seconds_per_pulse(event[1])
curve_master_tempo.add_point(p, new_tempo, false)
EventType.TEMPO_SLIDE:
var old_tempo = curve_master_tempo.get_pulse(p)
var new_tempo = music.tempo_to_seconds_per_pulse(event[2])
var slide_duration: int = event[1] # TODO: work out how this is scaled
curve_master_tempo.add_point(p, old_tempo, true)
curve_master_tempo.add_point(p + slide_duration, new_tempo, false)
curve_master_tempo.add_slide(p, slide_duration, new_tempo)
EventType.NOTE:
var note = event[1]
var duration = event[2]
var note_event = NoteEvent.new()
var curve_p_end: float = p + duration - CURVE_EPSILON
note_event.p_event_start = p
note_event.p_note_start = p
note_event.p_end = p + duration
note_event.instrument = current_instrument
note_event.pan = curve_pan.get_pulse_and_slide(p)
note_event.adsr_attack_rate = current_adsr_attack_rate
note_event.adsr_decay_rate = current_adsr_decay_rate
note_event.adsr_decay_total_periods = current_adsr_decay_total_periods
@ -222,38 +255,39 @@ static func render_channels(tracks: Array, inst_map: Array, _debug_name := 'none
if note >= 0: # Don't shift or play rests
last_note_pretransform_pitch = note # Ties reuse this
last_untied_note_p_start = p
curve_pitch_slide.add_point(p, 0) # Reset pitch slide
note += (12 * current_octave) + current_transpose
note_event.pitch = note # pitch_idx #* curve_fine_tuning
note_event.velocity = curve_velocity.get_pulse(p) # current_velocity
note_event.velocity = curve_velocity.get_pulse_and_slide(p) # current_velocity
elif note == music.NOTE_IS_TIE:
if last_note_pretransform_pitch >= 0:
note = last_note_pretransform_pitch + (12 * current_octave) + current_transpose
note_event.p_note_start = last_untied_note_p_start
note_event.pitch = note # pitch_idx #* curve_fine_tuning
note_event.velocity = curve_velocity.get_pulse(p) # current_velocity
note_event.pitch_slide = curve_pitch_slide.get_pulse_and_slide(p)
note_event.velocity = curve_velocity.get_pulse_and_slide(p)
#else:
#curve_pitch_slide.add_point(p, 0) # Reset pitch slide on rest
channel_note_events.append(note_event)
p += duration
EventType.VOLUME:
var new_velocity: float = event[1]/255.0
curve_velocity.add_point(p, new_velocity, false)
EventType.VOLUME_SLIDE: # TODO: implement slides
var old_velocity = curve_velocity.get_pulse(p)
curve_velocity.add_point(p, new_velocity)
EventType.VOLUME_SLIDE:
var slide_duration: int = event[1]
var new_velocity: float = event[2]/255.0
curve_velocity.add_point(p, old_velocity, true)
curve_velocity.add_point(p + slide_duration, new_velocity, false)
curve_velocity.add_slide(p, slide_duration, new_velocity)
EventType.PAN:
var new_pan = 1.0 - event[1]/127.5
curve_pan.add_point(p, new_pan, false)
EventType.PAN_SLIDE: # TODO: implement slides
var old_pan = curve_pan.get_pulse(p)
curve_pan.add_point(p, new_pan)
EventType.PAN_SLIDE:
var new_pan = 1.0 - event[2]/127.5
var slide_duration: int = event[1] # TODO: work out how slides are scaled
curve_pan.add_point(p, old_pan, true)
curve_pan.add_point(p + slide_duration, new_pan, false)
EventType.PITCH_SLIDE: # TODO: implement slides
var slide_duration: int = event[1]
var target_pitch: int = event[2] # Signed
curve_pan.add_slide(p, slide_duration, new_pan)
EventType.PITCH_SLIDE:
var slide_duration: int = event[1]
var target_pitch: int = event[2] + curve_pitch_slide.get_pulse(p) # Signed and relative
curve_pitch_slide.add_slide(p, slide_duration, target_pitch)
EventType.OCTAVE:
current_octave = event[1]
EventType.OCTAVE_UP:
@ -273,6 +307,7 @@ static func render_channels(tracks: Array, inst_map: Array, _debug_name := 'none
scale = fine_tune/255.0
curve_fine_tuning.add_point(p, scale)
EventType.PROGCHANGE:
curve_pitch_slide.add_point(p, 0) # Reset pitch slide
current_instrument = event[1]
if current_instrument >= 0x20:
current_instrument = inst_map[current_instrument-0x20] - 1 + SoundLoader.SFX_NUM
@ -334,11 +369,9 @@ static func render_channels(tracks: Array, inst_map: Array, _debug_name := 'none
EventType.ECHO_VOLUME:
curve_echo_volume.add_point(p, event[1])
EventType.ECHO_VOLUME_SLIDE:
var slide_duration: int = event[1] # TODO: work out how slides are scaled
var old_echo_volume = curve_echo_volume.get_pulse(p)
var slide_duration: int = event[1]
var new_echo_volume = event[2]
curve_echo_volume.add_point(p, old_echo_volume, true)
curve_echo_volume.add_point(p + slide_duration, new_echo_volume)
curve_echo_volume.add_slide(p, slide_duration, new_echo_volume)
EventType.ECHO_FEEDBACK_FIR: # TODO
var feedback: int = event[1]
var filterIndex: int = event[2]
@ -410,12 +443,14 @@ static func render_channels(tracks: Array, inst_map: Array, _debug_name := 'none
var loop_return_p = channel_loop_p_returns[channel]
var curve_pan: TrackCurve = curve_channel_pans[channel]
var midi_events_bytes_t_event_start := StreamPeerBuffer.new()
var midi_events_bytes_t_note_start := StreamPeerBuffer.new()
# var midi_events_bytes_t_end := StreamPeerBuffer.new()
var midi_events_bytes2 := StreamPeerBuffer.new()
var midi_events_bytes_adsr := StreamPeerBuffer.new()
var midi_events_bytes_adsr2 := StreamPeerBuffer.new()
var midi_bytes_t_start_event := StreamPeerBuffer.new()
var midi_bytes_t_start_note := StreamPeerBuffer.new()
var midi_bytes2 := StreamPeerBuffer.new()
var midi_bytes_adsr := StreamPeerBuffer.new()
var midi_bytes4 := StreamPeerBuffer.new()
var midi_bytes_t_end_volume := StreamPeerBuffer.new()
var midi_bytes_t_end_pan := StreamPeerBuffer.new()
var midi_bytes_t_end_pitch := StreamPeerBuffer.new()
var num_notes: int = 0
var event_ptr := 0
@ -431,18 +466,27 @@ static func render_channels(tracks: Array, inst_map: Array, _debug_name := 'none
var p = event.p_event_start
if loop_return_note_event_idx < 0 and p >= loop_return_p:
loop_return_note_event_idx = event_ptr
midi_events_bytes_t_event_start.put_32(int(curve_master_tempo.get_integral(p + loop_p_offset) * 32000))
midi_events_bytes_t_note_start.put_32(int(curve_master_tempo.get_integral(event.p_note_start + loop_p_offset) * 32000))
# midi_events_bytes_t_end.put_32(int(curve_master_tempo.get_integral(event.p_end + loop_p_offset) * 32000)) # t_end
midi_events_bytes2.put_u8(event.instrument)
midi_events_bytes2.put_u8(event.pitch)
midi_events_bytes2.put_u8(int(event.velocity * curve_master_volume.get_pulse(p) * 255.0)) # velocity
midi_events_bytes2.put_u8(int((curve_pan.get_pulse(p)+1.0) * 127.5)) # pan
midi_events_bytes_adsr.put_u8(event.adsr_attack_rate)
midi_events_bytes_adsr.put_u8(event.adsr_decay_rate)
midi_events_bytes_adsr.put_u8(event.adsr_decay_total_periods)
midi_events_bytes_adsr.put_u8(event.adsr_sustain_rate)
midi_events_bytes_adsr2.put_32(0)
midi_bytes_t_start_event.put_32(int(curve_master_tempo.get_integral(p + loop_p_offset) * 32000))
midi_bytes_t_start_note.put_32(int(curve_master_tempo.get_integral(event.p_note_start + loop_p_offset) * 32000))
midi_bytes_t_end_volume.put_32(int(curve_master_tempo.get_integral(event.velocity.z + loop_p_offset) * 32000))
midi_bytes_t_end_pan.put_32(int(curve_master_tempo.get_integral(event.pan.z + loop_p_offset) * 32000))
midi_bytes_t_end_pitch.put_32(int(curve_master_tempo.get_integral(event.pitch_slide.z + loop_p_offset) * 32000))
# midi_bytes_t_end.put_32(int(curve_master_tempo.get_integral(event.p_end + loop_p_offset) * 32000)) # t_end
midi_bytes2.put_u8(event.instrument)
midi_bytes2.put_u8(event.pitch)
var vel_u8 := event.velocity * curve_master_volume.get_pulse(p) * 255.0
midi_bytes2.put_u8(int(vel_u8.x))
midi_bytes2.put_u8(int(vel_u8.y))
midi_bytes_adsr.put_u8(event.adsr_attack_rate)
midi_bytes_adsr.put_u8(event.adsr_decay_rate)
midi_bytes_adsr.put_u8(event.adsr_decay_total_periods)
midi_bytes_adsr.put_u8(event.adsr_sustain_rate)
var pan_u8 := (event.pan+Vector3.ONE) * 127.5
midi_bytes4.put_u8(int(pan_u8.x))
midi_bytes4.put_u8(int(pan_u8.y))
var pitchslide_s8 := event.pitch_slide * 2
midi_bytes4.put_u8(int(pitchslide_s8.x) + 128)
midi_bytes4.put_u8(int(pitchslide_s8.y) + 128)
event_ptr += 1
num_notes += 1
@ -451,14 +495,15 @@ static func render_channels(tracks: Array, inst_map: Array, _debug_name := 'none
if events:
last_note_end = int(curve_master_tempo.get_integral(events[-1].p_end + loop_p_offset) * 32000)
for i in range(num_notes, MAX_NOTE_EVENTS):
midi_events_bytes_t_event_start.put_32(last_note_end)
midi_events_bytes_t_note_start.put_32(last_note_end)
# midi_events_bytes_t_end.put_32(0x0FFFFFFF)
midi_events_bytes2.put_32(0)
midi_events_bytes_adsr.put_32(0)
midi_events_bytes_adsr2.put_32(0)
# data += midi_events_bytes_t_event_start.data_array + midi_events_bytes_t_end.data_array + midi_events_bytes2.data_array + midi_events_bytes_adsr.data_array + midi_events_bytes_t_note_start.data_array
data += midi_events_bytes_t_event_start.data_array + midi_events_bytes_t_note_start.data_array + midi_events_bytes2.data_array + midi_events_bytes_adsr.data_array + midi_events_bytes_adsr2.data_array
midi_bytes_t_start_event.put_32(last_note_end)
midi_bytes_t_start_note.put_32(last_note_end)
midi_bytes_t_end_volume.put_32(0x0FFFFFFF)
midi_bytes_t_end_pan.put_32(0x0FFFFFFF)
midi_bytes_t_end_pitch.put_32(0x0FFFFFFF)
midi_bytes2.put_32(0)
midi_bytes_adsr.put_32(0)
midi_bytes4.put_32(0)
data += midi_bytes_t_start_event.data_array + midi_bytes_t_start_note.data_array + midi_bytes2.data_array + midi_bytes_adsr.data_array + midi_bytes4.data_array + midi_bytes_t_end_volume.data_array + midi_bytes_t_end_pan.data_array + midi_bytes_t_end_pitch.data_array
var t_loop_endpoints := Vector2(-1, -1)
if highest_channel_p_return >= 0:
t_loop_endpoints = Vector2(curve_master_tempo.get_integral(highest_channel_p_return + 100), curve_master_tempo.get_integral(longest_channel_p_end + 100))

View File

@ -224,7 +224,7 @@ highp vec4 render_song(highp sampler2D tex, highp int smp) {
// Binary search the channels
for (int channel = 0; channel < NUM_CHANNELS; channel++) {
highp float row = float(channel * 5);
highp float row = float(channel * 8);
highp float event_idx = 0.0;
highp int smp_event_start;
for (int i = 0; i < NUM_CHANNEL_NOTE_PROBES; i++) {
@ -237,6 +237,9 @@ highp vec4 render_song(highp sampler2D tex, highp int smp) {
highp vec4 tex2 = get_midi_texel(tex, event_idx, row+2.0);
highp vec4 tex3 = get_midi_texel(tex, event_idx, row+3.0);
highp vec4 tex4 = get_midi_texel(tex, event_idx, row+4.0);
highp vec4 tex5 = get_midi_texel(tex, event_idx, row+5.0);
highp vec4 tex6 = get_midi_texel(tex, event_idx, row+6.0);
highp vec4 tex7 = get_midi_texel(tex, event_idx, row+7.0);
highp vec4 next_tex1 = get_midi_texel(tex, event_idx+1.0, row+1.0);
smp_event_start = retime_smp(unpack_int32(tex0));
highp int smp_note_start = retime_smp(unpack_int32(tex1));
@ -248,11 +251,19 @@ highp vec4 render_song(highp sampler2D tex, highp int smp) {
if (smp_release_overrun < 256) {
highp float instrument_idx = trunc(tex2.x * 255.0);
highp float pitch_idx = tex2.y * 255.0;
highp float velocity = tex2.z;
highp float pan = tex2.w;
// Calculate slide-based values
highp float smp_event_progress = float(smp - smp_event_start);
highp ivec3 smp_end_vol_pan_pitch = ivec3(retime_smp(unpack_int32(tex5)), retime_smp(unpack_int32(tex6)), retime_smp(unpack_int32(tex7)));
highp vec3 smp_progress_vol_pan_pitch = vec3(smp_end_vol_pan_pitch - smp_event_start);
highp vec3 progress_vol_pan_pitch = clamp(smp_event_progress/smp_progress_vol_pan_pitch, 0.0, 1.0);
highp float velocity = mix(tex2.z, tex2.w, progress_vol_pan_pitch.x);
highp float pan = mix(tex4.x, tex4.y, progress_vol_pan_pitch.y);
highp float pitchslide = ((mix(tex4.z, tex4.w, progress_vol_pan_pitch.z)*255.0) - 128.0)/2.0;
pitch_idx += pitchslide;
highp ivec4 adsr = ivec4(tex3 * 255.0);
// ====================At some point I'll look back into packing floats====================
// TBD = note_event_supplement.zw; - tremolo/vibrato/noise/pan_lfo/pitchbend/echo remain
// TBD = note_event_supplement.zw; - tremolo/vibrato/noise/pan_lfo/echo remain
// ====================At some point I'll look back into packing floats====================
highp int smp_attack = get_attack_time_smps(adsr.x);
highp int adsr_decay_rate = get_rate_period(adsr.y*2 + 16);