mirror of
https://github.com/italicsjenga/agb.git
synced 2025-01-22 15:16:40 +11:00
Core loop for the tracker in assembly
This commit is contained in:
parent
df75d1ce8c
commit
de666a54f9
2 changed files with 151 additions and 28 deletions
|
@ -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
|
||||||
|
|
|
@ -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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue