Core loop for the tracker in assembly

This commit is contained in:
Gwilym Inzani 2023-07-24 23:59:54 +01:00
parent df75d1ce8c
commit de666a54f9
2 changed files with 151 additions and 28 deletions

View file

@ -1,3 +1,59 @@
.macro mono_add_fn_loop fn_name:req is_first:req
agb_arm_func \fn_name
@ Arguments
@ r0 - pointer to the sample data from the beginning
@ r1 - pointer to the target sample buffer &[i32; BUFFER_SIZE]
@ r2 - BUFFER_SIZE - the length of the array in r1. Must be a multiple of 4
@ r3 - (length - restart point) (how much to rewind by)
@ Stack position 1 - channel length
@ Stack position 2 - current channel position
@ Stack position 3 - the playback speed
@ Stack position 4 - the amount to multiply by
@
@ Returns the new channel position
push {{r4-r11,lr}}
ldr r4, [sp, #(9*4)] @ load the channel length into r4
ldr r5, [sp, #(10*4)] @ load the current channel position into r5
ldr r6, [sp, #(11*4)] @ load the playback speed into r6
ldr r12, [sp, #(12*4)] @ load the amount to multiply by into r12
@ The core loop
1:
.ifc \is_first,false
ldm r1, {{r7-r10}}
.endif
.irp reg, r7,r8,r9,r10
cmp r4, r5, lsr #8 @ check if we're overflowing
suble r5, r5, r3 @ if we are, subtract the overflow amount
mov r11, r5, lsr #8 @ calculate the next location to get a value from
ldrsb r11, [r0, r11] @ load a single value
.ifc \is_first,true @ multiply the sample value, but only add if not the first call
mul \reg, r11, r12
.else
mla \reg, r11, r12, \reg
.endif
add r5, r5, r6 @ calculate the next sample read location
.endr
stmia r1!, {{r7-r10}}
subs r2, r2, #4
bne 1b
mov r0, r5 @ return the playback position
pop {{r4-r11,lr}}
bx lr
agb_arm_end \fn_name
.endm
mono_add_fn_loop agb_rs__mixer_add_mono_loop_first true
mono_add_fn_loop agb_rs__mixer_add_mono_loop false
.macro stereo_add_fn fn_name:req is_first:req .macro stereo_add_fn fn_name:req is_first:req
agb_arm_func \fn_name agb_arm_func \fn_name
@ Arguments @ Arguments

View file

@ -40,6 +40,28 @@ extern "C" {
input_buffer: *const Num<i16, 4>, input_buffer: *const Num<i16, 4>,
num_samples: usize, num_samples: usize,
); );
fn agb_rs__mixer_add_mono_loop_first(
sample_data: *const u8,
sample_buffer: *mut i32,
buffer_size: usize,
restart_amount: Num<u32, 8>,
channel_length: usize,
current_pos: Num<u32, 8>,
playback_speed: Num<u32, 8>,
mul_amount: i32,
) -> Num<u32, 8>;
fn agb_rs__mixer_add_mono_loop(
sample_data: *const u8,
sample_buffer: *mut i32,
buffer_size: usize,
restart_amount: Num<u32, 8>,
channel_length: usize,
current_pos: Num<u32, 8>,
playback_speed: Num<u32, 8>,
mul_amount: i32,
) -> Num<u32, 8>;
} }
/// The main software mixer struct. /// The main software mixer struct.
@ -496,29 +518,43 @@ impl MixerBuffer {
let mul_amount = let mul_amount =
((left_amount.to_raw() as i32) << 16) | (right_amount.to_raw() as i32 & 0x0000ffff); ((left_amount.to_raw() as i32) << 16) | (right_amount.to_raw() as i32 & 0x0000ffff);
if IS_FIRST && channel.should_loop {
channel.pos = unsafe {
agb_rs__mixer_add_mono_loop_first(
channel.data.as_ptr(),
working_buffer_i32.as_mut_ptr(),
working_buffer_i32.len(),
channel_len - channel.restart_point,
channel.data.len(),
channel.pos,
channel.playback_speed,
mul_amount,
)
};
} else if !IS_FIRST && channel.should_loop {
channel.pos = unsafe {
agb_rs__mixer_add_mono_loop(
channel.data.as_ptr(),
working_buffer_i32.as_mut_ptr(),
working_buffer_i32.len(),
channel_len - channel.restart_point,
channel.data.len(),
channel.pos,
channel.playback_speed,
mul_amount,
)
};
} else {
for i in 0..self.frequency.buffer_size() { for i in 0..self.frequency.buffer_size() {
if channel.pos >= channel_len { if channel.pos >= channel_len {
if channel.should_loop {
channel.pos -= channel_len - channel.restart_point;
} else {
channel.is_done = true; channel.is_done = true;
if IS_FIRST {
for j in i..self.frequency.buffer_size() {
// SAFETY: working buffer length = self.frequency.buffer_size()
unsafe {
*working_buffer_i32.get_unchecked_mut(j) = 0;
}
}
}
break; break;
} }
}
// SAFETY: channel.pos < channel_len by the above if statement and the fact we reduce the playback speed // SAFETY: channel.pos < channel_len by the above if statement and the fact we reduce the playback speed
let value = let value = unsafe { *channel.data.get_unchecked(channel.pos.floor() as usize) }
unsafe { *channel.data.get_unchecked(channel.pos.floor() as usize) } as i8 as i32; as i8 as i32;
// SAFETY: working buffer length = self.frequency.buffer_size() // SAFETY: working buffer length = self.frequency.buffer_size()
if IS_FIRST { if IS_FIRST {
@ -534,6 +570,7 @@ impl MixerBuffer {
channel.pos += playback_speed; channel.pos += playback_speed;
} }
} }
}
} }
#[cfg(test)] #[cfg(test)]
@ -608,4 +645,34 @@ mod test {
] ]
); );
} }
#[test_case]
fn mono_add_loop_first_should_work(_: &mut crate::Gba) {
let mut buffer = vec![0i32; 16];
let sample_data: [i8; 9] = [5, 10, 0, 100, -18, 55, 8, -120, 19];
let restart_amount = num!(9.0);
let current_pos = num!(0.0);
let playback_speed = num!(1.0);
let mul_amount = 10;
let result = unsafe {
agb_rs__mixer_add_mono_loop_first(
sample_data.as_ptr().cast(),
buffer.as_mut_ptr(),
buffer.len(),
restart_amount,
sample_data.len(),
current_pos,
playback_speed,
mul_amount,
)
};
assert_eq!(
buffer,
&[50, 100, 0, 1000, -180, 550, 80, -1200, 190, 50, 100, 0, 1000, -180, 550, 80]
);
assert_eq!(result, num!(7.0));
}
} }