[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.
This commit is contained in:
Luke Hubmayer-Werner 2024-07-28 22:43:44 +09:30
parent f8e3def5d8
commit 8990a6637e
2 changed files with 78 additions and 50 deletions

View File

@ -12,12 +12,9 @@ class NoteEvent:
var p_end: int
var instrument: int
var pitch: int = 0
var pitch_slide: float
var pitch_slide_end: float
var velocity: float = 0.0
var velocity_end: float = 0.0
var pan: float
var pan_end: float
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
@ -72,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()
@ -231,8 +247,7 @@ static func render_channels(tracks: Array, inst_map: Array, _debug_name := 'none
note_event.p_note_start = p
note_event.p_end = p + duration
note_event.instrument = current_instrument
note_event.pan = curve_pan.get_pulse(p)
note_event.pan_end = curve_pan.get_pulse(curve_p_end)
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
@ -243,17 +258,14 @@ static func render_channels(tracks: Array, inst_map: Array, _debug_name := 'none
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_end = curve_velocity.get_pulse(curve_p_end)
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.pitch_slide = curve_pitch_slide.get_pulse(p)
note_event.pitch_slide_end = curve_pitch_slide.get_pulse(curve_p_end)
note_event.velocity = curve_velocity.get_pulse(p)
note_event.velocity_end = curve_velocity.get_pulse(curve_p_end)
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)
@ -431,11 +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_bytes2 := StreamPeerBuffer.new()
var midi_events_bytes_adsr := StreamPeerBuffer.new()
var midi_events_bytes4 := 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
@ -451,21 +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))
midi_events_bytes2.put_u8(int(event.velocity_end * curve_master_volume.get_pulse(p) * 255.0))
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_bytes4.put_u8(int((event.pan+1.0) * 127.5))
midi_events_bytes4.put_u8(int((event.pan_end+1.0) * 127.5))
midi_events_bytes4.put_u8(int((event.pitch_slide*5))+128)
midi_events_bytes4.put_u8(int((event.pitch_slide_end*5))+128)
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
@ -474,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_bytes4.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_bytes4.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,27 +237,33 @@ 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 next_tex0 = get_midi_texel(tex, event_idx+1.0, row);
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));
highp int next_smp_event_start = retime_smp(unpack_int32(next_tex0));
highp int next_smp_note_start = retime_smp(unpack_int32(next_tex1));
// For now, just branch this
if (smp_note_start < smp) { // First sample may not start at zero!
highp int smp_release_overrun = (smp_note_start == next_smp_note_start) ? 0 : max(smp - next_smp_note_start + 256, 0); // 256 samples of linear decay to 0 before next non-tie event
if (smp_release_overrun < 256) {
highp float event_progress = float(smp - smp_event_start)/float(next_smp_event_start - smp_event_start);
highp float instrument_idx = trunc(tex2.x * 255.0);
highp float pitch_idx = tex2.y * 255.0;
highp float velocity = mix(tex2.z, tex2.w, event_progress);
highp float pan = mix(tex4.x, tex4.y, event_progress);
highp float pitchslide = ((mix(tex4.z, tex4.w, event_progress)*255.0) - 128.0)/5.0;
// 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);