diff --git a/agb/src/sound/mixer/mixer.s b/agb/src/sound/mixer/mixer.s index c1101dc2..e6cbe929 100644 --- a/agb/src/sound/mixer/mixer.s +++ b/agb/src/sound/mixer/mixer.s @@ -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 agb_arm_func \fn_name @ Arguments diff --git a/agb/src/sound/mixer/sw_mixer.rs b/agb/src/sound/mixer/sw_mixer.rs index 2fa951a1..f572fb6b 100644 --- a/agb/src/sound/mixer/sw_mixer.rs +++ b/agb/src/sound/mixer/sw_mixer.rs @@ -40,6 +40,28 @@ extern "C" { input_buffer: *const Num, 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, + channel_length: usize, + current_pos: Num, + playback_speed: Num, + mul_amount: i32, + ) -> Num; + + fn agb_rs__mixer_add_mono_loop( + sample_data: *const u8, + sample_buffer: *mut i32, + buffer_size: usize, + restart_amount: Num, + channel_length: usize, + current_pos: Num, + playback_speed: Num, + mul_amount: i32, + ) -> Num; } /// The main software mixer struct. @@ -496,42 +518,57 @@ impl MixerBuffer { let mul_amount = ((left_amount.to_raw() as i32) << 16) | (right_amount.to_raw() as i32 & 0x0000ffff); - for i in 0..self.frequency.buffer_size() { - if channel.pos >= channel_len { - if channel.should_loop { - channel.pos -= channel_len - channel.restart_point; - } else { + 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() { + if channel.pos >= channel_len { 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; } - } - // SAFETY: channel.pos < channel_len by the above if statement and the fact we reduce the playback speed - let value = - unsafe { *channel.data.get_unchecked(channel.pos.floor() as usize) } as i8 as i32; + // SAFETY: channel.pos < channel_len by the above if statement and the fact we reduce the playback speed + let value = unsafe { *channel.data.get_unchecked(channel.pos.floor() as usize) } + as i8 as i32; - // SAFETY: working buffer length = self.frequency.buffer_size() - if IS_FIRST { - unsafe { - *working_buffer_i32.get_unchecked_mut(i) = value.wrapping_mul(mul_amount); + // SAFETY: working buffer length = self.frequency.buffer_size() + if IS_FIRST { + unsafe { + *working_buffer_i32.get_unchecked_mut(i) = value.wrapping_mul(mul_amount); + } + } else { + unsafe { + let value_ref = working_buffer_i32.get_unchecked_mut(i); + *value_ref = value_ref.wrapping_add(value.wrapping_mul(mul_amount)); + }; } - } else { - unsafe { - let value_ref = working_buffer_i32.get_unchecked_mut(i); - *value_ref = value_ref.wrapping_add(value.wrapping_mul(mul_amount)); - }; + channel.pos += playback_speed; } - channel.pos += playback_speed; } } } @@ -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)); + } }