diff --git a/agb-sound-converter/src/lib.rs b/agb-sound-converter/src/lib.rs index 4b6ddebf..c38efb79 100644 --- a/agb-sound-converter/src/lib.rs +++ b/agb-sound-converter/src/lib.rs @@ -66,9 +66,12 @@ pub fn include_wav(input: TokenStream) -> TokenStream { let result = quote! { { + #[repr(align(4))] + struct AlignmentWrapper([u8; N]); + const _: &[u8] = include_bytes!(#include_path); - include_bytes!(#out_file_path_include) + &AlignmentWrapper(*include_bytes!(#out_file_path_include)).0 } }; diff --git a/agb/examples/JoshWoodward-LetItIn.wav b/agb/examples/JoshWoodward-LetItIn.wav new file mode 100644 index 00000000..cb5417bd Binary files /dev/null and b/agb/examples/JoshWoodward-LetItIn.wav differ diff --git a/agb/examples/stereo_sound.rs b/agb/examples/stereo_sound.rs new file mode 100644 index 00000000..b7dca810 --- /dev/null +++ b/agb/examples/stereo_sound.rs @@ -0,0 +1,28 @@ +#![no_std] +#![no_main] + +extern crate agb; + +use agb::sound::mixer::SoundChannel; +use agb::{include_wav, Gba}; + +// Music - "Let it in" by Josh Woodward, free download at http://joshwoodward.com +const LET_IT_IN: &[u8] = include_wav!("examples/JoshWoodward-LetItIn.wav"); + +#[agb::entry] +fn main() -> ! { + let mut gba = Gba::new(); + let vblank_provider = agb::interrupt::VBlank::get(); + + let mut mixer = gba.mixer.mixer(); + mixer.enable(); + + let mut channel = SoundChannel::new(LET_IT_IN); + channel.stereo(); + mixer.play_sound(channel).unwrap(); + + loop { + vblank_provider.wait_for_vblank(); + mixer.vblank(); + } +} diff --git a/agb/src/sound/mixer/mixer.s b/agb/src/sound/mixer/mixer.s index 151c883e..c6abf36e 100644 --- a/agb/src/sound/mixer/mixer.s +++ b/agb/src/sound/mixer/mixer.s @@ -93,6 +93,60 @@ same_modification: agb_arm_end agb_rs__mixer_add +agb_arm_func agb_rs__mixer_add_stereo + @ Arguments + @ r0 - pointer to the data to be copied (u8 array) + @ r1 - pointer to the sound buffer (i16 array which will alternate left and right channels, 32-bit aligned) + @ + @ The sound buffer must be SOUND_BUFFER_SIZE * 2 in size = 176 * 2 + push {r4-r8} + + ldr r5, =0x00000FFF + +.macro mixer_add_loop_simple_stereo + ldrsh r6, [r0], #2 @ load the current sound sample to r6 + + ldr r4, [r1] @ read the current value + + @ This is slightly convoluted, but is mainly done for performance reasons. It is better + @ to hit ROM just once and then do 3 really simple instructions then do 2 ldrsbs however annoying + @ this is. Also, since all this code is in IWRAM and we never hit ROM otherwise, all accesses + @ are sequential and exactly the size of the bus to ROM (16 bits), so hopefully this will be super fast. + @ + @ The next 3 instructions set up the current value in r6 to be in the expected format + @ 1 = 2s complement marks (so if negative, these are all 1s, if positive these are 0s) + @ L = the left sample + @ R = the right sample + @ 0 = all zeros + @ Split into bytes + @ + @ At this point + @ r6 = | 1 | 1 | L | R | where the upper bytes are 1s if L is negative. No care about R + @ asr #8 | 1 | 1 | 1 | L | drop R off the right hand side + and r7, r5, r6, asr #8 @ r7 = | 0 | 0 | 1 | L | exactly what we want this to be. The mask puts the 1 as 00001111 ready for the shift later + lsl r6, r6, #24 @ r6 = | R | 0 | 0 | 0 | drop everything except the right sample + orr r6, r7, r6, asr #8 @ r6 = | 1 | R | 1 | L | now we have it perfectly set up + + add r4, r4, r6, lsl #4 @ r4 += r6 << 4 (calculating both the left and right samples together) + + str r4, [r1], #4 @ store the new value, and increment the pointer +.endm + + mov r8, #SOUND_BUFFER_SIZE +1: + mixer_add_loop_simple_stereo + mixer_add_loop_simple_stereo + mixer_add_loop_simple_stereo + mixer_add_loop_simple_stereo + + subs r8, r8, #4 @ loop counter + bne 1b @ jump back if we're done with the loop + + pop {r4-r8} + bx lr + +agb_arm_end agb_rs__mixer_add_stereo + .macro clamp_s8 reg:req cmn \reg, #127 mvnlt \reg, #127 diff --git a/agb/src/sound/mixer/mod.rs b/agb/src/sound/mixer/mod.rs index 04b0dfec..2310ff7e 100644 --- a/agb/src/sound/mixer/mod.rs +++ b/agb/src/sound/mixer/mod.rs @@ -35,6 +35,8 @@ pub struct SoundChannel { panning: Num, // between -1 and 1 is_done: bool, + is_stereo: bool, + priority: SoundPriority, } @@ -49,6 +51,7 @@ impl SoundChannel { is_done: false, priority: SoundPriority::Low, volume: 1.into(), + is_stereo: false, } } @@ -62,6 +65,7 @@ impl SoundChannel { is_done: false, priority: SoundPriority::High, volume: 1.into(), + is_stereo: false, } } @@ -91,6 +95,12 @@ impl SoundChannel { self } + pub fn stereo(&mut self) -> &mut Self { + self.is_stereo = true; + + self + } + pub fn stop(&mut self) { self.is_done = true } diff --git a/agb/src/sound/mixer/sw_mixer.rs b/agb/src/sound/mixer/sw_mixer.rs index 408eb230..00a1f56b 100644 --- a/agb/src/sound/mixer/sw_mixer.rs +++ b/agb/src/sound/mixer/sw_mixer.rs @@ -13,6 +13,8 @@ extern "C" { right_amount: Num, ); + fn agb_rs__mixer_add_stereo(sound_data: *const u8, sound_buffer: *mut Num); + fn agb_rs__mixer_collapse(sound_buffer: *mut i8, input_buffer: *const Num); } @@ -133,12 +135,16 @@ impl MixerBuffer { continue; } + let playback_speed = if channel.is_stereo { + 2.into() + } else { + channel.playback_speed + }; + let right_amount = ((channel.panning + 1) / 2) * channel.volume; let left_amount = ((-channel.panning + 1) / 2) * channel.volume; - if (channel.pos + channel.playback_speed * SOUND_BUFFER_SIZE).floor() - >= channel.data.len() - { + if (channel.pos + playback_speed * SOUND_BUFFER_SIZE).floor() >= channel.data.len() { // TODO: This should probably play what's left rather than skip the last bit if channel.should_loop { channel.pos -= channel.data.len(); @@ -148,16 +154,26 @@ impl MixerBuffer { } } - unsafe { - agb_rs__mixer_add( - channel.data.as_ptr().add(channel.pos.floor()), - buffer.as_mut_ptr(), - channel.playback_speed, - left_amount, - right_amount, - ); + if channel.is_stereo { + unsafe { + agb_rs__mixer_add_stereo( + channel.data.as_ptr().add(channel.pos.floor()), + buffer.as_mut_ptr(), + ); + } + } else { + unsafe { + agb_rs__mixer_add( + channel.data.as_ptr().add(channel.pos.floor()), + buffer.as_mut_ptr(), + playback_speed, + left_amount, + right_amount, + ); + } } - channel.pos += channel.playback_speed * SOUND_BUFFER_SIZE; + + channel.pos += playback_speed * SOUND_BUFFER_SIZE; } let write_buffer = self.get_write_buffer();