diff --git a/agb/examples/mixer_basic.rs b/agb/examples/mixer_basic.rs index 50aae56d..1419562b 100644 --- a/agb/examples/mixer_basic.rs +++ b/agb/examples/mixer_basic.rs @@ -4,7 +4,7 @@ use agb::fixnum::Num; use agb::input::{Button, ButtonController, Tri}; use agb::sound::mixer::SoundChannel; -use agb::{include_wav, Gba, fixnum::num}; +use agb::{fixnum::num, include_wav, Gba}; // Music - "Dead Code" by Josh Woodward, free download at http://joshwoodward.com const DEAD_CODE: &[u8] = include_wav!("examples/JoshWoodward-DeadCode.wav"); diff --git a/agb/examples/sfx/jump.wav b/agb/examples/sfx/jump.wav new file mode 100644 index 00000000..e4f078cb Binary files /dev/null and b/agb/examples/sfx/jump.wav differ diff --git a/agb/examples/sfx/my_bgm.wav b/agb/examples/sfx/my_bgm.wav new file mode 100644 index 00000000..80ba543e Binary files /dev/null and b/agb/examples/sfx/my_bgm.wav differ diff --git a/agb/src/display/tiled/infinite_scrolled_map.rs b/agb/src/display/tiled/infinite_scrolled_map.rs index 97dafea1..decdd4db 100644 --- a/agb/src/display/tiled/infinite_scrolled_map.rs +++ b/agb/src/display/tiled/infinite_scrolled_map.rs @@ -20,9 +20,38 @@ use crate::{ /// /// # Example /// -/// ``` -/// let backdrop = InfiniteScrolledMap::new( -/// background.background(Priority::P2, RegularBackgroundSize::Background32x32), +/// ```rust,no_run +/// # #![no_std] +/// # #![no_main] +/// extern crate alloc; +/// +/// use alloc::boxed::Box; +/// +/// use agb::display::tiled::{ +/// InfiniteScrolledMap, +/// TileSetting, +/// RegularBackgroundSize, +/// TileSet, +/// TileFormat, +/// }; +/// use agb::display::Priority; +/// +/// mod tilemap { +/// pub const BACKGROUND_MAP: &[u16] = &[ // Probably load this from a file +/// # 0, 1, 2]; +/// pub const WIDTH: i32 = // set it to some width +/// # 12; +/// pub const MAP_TILES: &[u8] = &[ // probably load this from a file +/// # 0]; +/// } +/// +/// # fn foo(mut gba: agb::Gba) { +/// let (gfx, mut vram) = gba.display.video.tiled0(); +/// +/// let tileset = TileSet::new(&tilemap::MAP_TILES, TileFormat::FourBpp); +/// +/// let mut backdrop = InfiniteScrolledMap::new( +/// gfx.background(Priority::P2, RegularBackgroundSize::Background32x32), /// Box::new(|pos| { /// ( /// &tileset, @@ -40,6 +69,7 @@ use crate::{ /// backdrop.set_pos(&mut vram, (3, 5).into()); /// backdrop.commit(&mut vram); /// backdrop.show(); +/// # } /// ``` pub struct InfiniteScrolledMap<'a> { map: MapLoan<'a, RegularMap>, @@ -83,11 +113,57 @@ impl<'a> InfiniteScrolledMap<'a> { /// /// # Example /// - /// ``` - /// background.init(&mut vram, start_position, || { + /// ```rust,no_run + /// # #![no_std] + /// # #![no_main] + /// # #![no_std] + /// # #![no_main] + /// # extern crate alloc; + /// # + /// # use alloc::boxed::Box; + /// # + /// # use agb::display::tiled::{ + /// # InfiniteScrolledMap, + /// # TileSetting, + /// # RegularBackgroundSize, + /// # TileSet, + /// # TileFormat, + /// # }; + /// # use agb::display::Priority; + /// # + /// # mod tilemap { + /// # pub const BACKGROUND_MAP: &[u16] = &[0, 1, 2]; + /// # pub const WIDTH: i32 = 12; + /// # pub const MAP_TILES: &[u8] = &[0]; + /// # } + /// # + /// # fn foo(mut gba: agb::Gba) { + /// # let (gfx, mut vram) = gba.display.video.tiled0(); + /// # + /// # let tileset = TileSet::new(&tilemap::MAP_TILES, TileFormat::FourBpp); + /// # + /// # let mut backdrop = InfiniteScrolledMap::new( + /// # gfx.background(Priority::P2, RegularBackgroundSize::Background32x32), + /// # Box::new(|pos| { + /// # ( + /// # &tileset, + /// # TileSetting::from_raw( + /// # *tilemap::BACKGROUND_MAP + /// # .get((pos.x + tilemap::WIDTH * pos.y) as usize) + /// # .unwrap_or(&0), + /// # ), + /// # ) + /// # }), + /// # ); + /// # + /// # let vblank = agb::interrupt::VBlank::get(); + /// # let mut mixer = gba.mixer.mixer(); + /// let start_position = agb::fixnum::Vector2D::new(10, 10); + /// backdrop.init(&mut vram, start_position, &mut || { /// vblank.wait_for_vblank(); /// mixer.frame(); /// }); + /// # } /// ``` pub fn init( &mut self, @@ -111,11 +187,58 @@ impl<'a> InfiniteScrolledMap<'a> { /// /// # Example /// - /// ``` - /// while background.init_partial(&mut vram, start_position) == PartialUpdateStatus::Continue { + /// ```rust,no_run + /// # #![no_std] + /// # #![no_main] + /// # #![no_std] + /// # #![no_main] + /// # extern crate alloc; + /// # + /// # use alloc::boxed::Box; + /// # + /// # use agb::display::tiled::{ + /// # InfiniteScrolledMap, + /// # TileSetting, + /// # RegularBackgroundSize, + /// # TileSet, + /// # TileFormat, + /// # PartialUpdateStatus, + /// # }; + /// # use agb::display::Priority; + /// # + /// # mod tilemap { + /// # pub const BACKGROUND_MAP: &[u16] = &[0, 1, 2]; + /// # pub const WIDTH: i32 = 12; + /// # pub const MAP_TILES: &[u8] = &[0]; + /// # } + /// # + /// # fn foo(mut gba: agb::Gba) { + /// # let (gfx, mut vram) = gba.display.video.tiled0(); + /// # + /// # let tileset = TileSet::new(&tilemap::MAP_TILES, TileFormat::FourBpp); + /// # + /// # let mut backdrop = InfiniteScrolledMap::new( + /// # gfx.background(Priority::P2, RegularBackgroundSize::Background32x32), + /// # Box::new(|pos| { + /// # ( + /// # &tileset, + /// # TileSetting::from_raw( + /// # *tilemap::BACKGROUND_MAP + /// # .get((pos.x + tilemap::WIDTH * pos.y) as usize) + /// # .unwrap_or(&0), + /// # ), + /// # ) + /// # }), + /// # ); + /// # + /// # let vblank = agb::interrupt::VBlank::get(); + /// # let mut mixer = gba.mixer.mixer(); + /// let start_position = agb::fixnum::Vector2D::new(10, 10); + /// while backdrop.init_partial(&mut vram, start_position) == PartialUpdateStatus::Continue { /// vblank.wait_for_vblank(); /// mixer.frame(); /// } + /// # } /// ``` pub fn init_partial( &mut self, diff --git a/agb/src/interrupt.rs b/agb/src/interrupt.rs index 3d08d216..c88fb9fd 100644 --- a/agb/src/interrupt.rs +++ b/agb/src/interrupt.rs @@ -235,10 +235,17 @@ fn interrupt_to_root(interrupt: Interrupt) -> &'static InterruptRoot { /// /// # Examples /// -/// ``` -/// let _a = add_interrupt_handler(Interrupt::VBlank, |_: &CriticalSection| { -/// println!("Woah there! There's been a vblank!"); +/// ```rust,no_run +/// # #![no_std] +/// # #![no_main] +/// use bare_metal::CriticalSection; +/// +/// # fn foo() { +/// # use agb::interrupt::{add_interrupt_handler, Interrupt}; +/// let _a = add_interrupt_handler(Interrupt::VBlank, |_: CriticalSection| { +/// agb::println!("Woah there! There's been a vblank!"); /// }); +/// # } /// ``` pub fn add_interrupt_handler<'a>( interrupt: Interrupt, diff --git a/agb/src/lib.rs b/agb/src/lib.rs index a45d570c..97f1159c 100644 --- a/agb/src/lib.rs +++ b/agb/src/lib.rs @@ -50,7 +50,7 @@ /// /// This will generate something along the lines of the following: /// -/// ``` +/// ```rust,ignore /// // module name comes from the name of the toml file, so `sprites` in this case because it is /// // called `sprites.toml` /// mod sprites { @@ -75,7 +75,7 @@ /// ``` /// /// In `src/main.rs` -/// ``` +/// ```rust,ignore /// mod gfx { /// use agb::display::object::ObjectControl; /// @@ -105,7 +105,7 @@ /// ``` /// /// In `src/main.rs`: -/// ``` +/// ```rust,ignore /// mod gfx { /// use agb::display::background::BackgroundDistributor; /// @@ -139,7 +139,7 @@ macro_rules! include_font { /// Doing this will ensure that `agb` can correctly set up the environment to call your rust function on start up. /// /// # Examples -/// ``` +/// ```no_run,rust /// #![no_std] /// #![no_main] /// @@ -206,7 +206,7 @@ static mut GBASINGLE: single::Singleton = single::Singleton::new(unsafe { G /// /// # Examples /// -/// ``` +/// ```no_run,rust /// #![no_std] /// #![no_main] /// diff --git a/agb/src/sound/mixer/mod.rs b/agb/src/sound/mixer/mod.rs index 16ca4cd8..9b72e599 100644 --- a/agb/src/sound/mixer/mod.rs +++ b/agb/src/sound/mixer/mod.rs @@ -35,9 +35,13 @@ //! To create a sound mixer, you will need to get it out of the [`Gba`](crate::Gba) struct //! as follows: //! -//! ``` +//! ```rust,no_run +//! # #![no_std] +//! # #![no_main] +//! # fn foo(gba: &mut agb::Gba) { //! let mut mixer = gba.mixer.mixer(); //! mixer.enable(); +//! # } //! ``` //! //! ## Doing the per-frame work @@ -48,22 +52,34 @@ //! //! Without interrupts: //! -//! ``` +//! ```rust,no_run +//! # #![no_std] +//! # #![no_main] +//! # fn foo(gba: &mut agb::Gba) { +//! # let mut mixer = gba.mixer.mixer(); +//! # let vblank = agb::interrupt::VBlank::get(); //! // Somewhere in your main loop: //! mixer.frame(); //! vblank.wait_for_vblank(); //! mixer.after_vblank(); +//! # } //! ``` //! //! Or with interrupts: //! -//! ``` +//! ```rust,no_run +//! # #![no_std] +//! # #![no_main] +//! # fn foo(gba: &mut agb::Gba) { +//! # let mut mixer = gba.mixer.mixer(); +//! # let vblank = agb::interrupt::VBlank::get(); //! // outside your main loop, close to initialisation //! let _mixer_interrupt = mixer.setup_interrupt_handler(); //! //! // inside your main loop //! mixer.frame(); //! vblank.wait_for_vblank(); +//! # } //! ``` //! //! Despite being high performance, the mixer still takes a sizable portion of CPU time (6-10% @@ -79,14 +95,21 @@ //! Use the [`include_wav!`](crate::include_wav) macro in order to load the sound. This will produce //! an error if your wav file is of the wrong frequency. //! -//! ``` +//! ```rust,no_run +//! # #![no_std] +//! # #![no_main] +//! # fn foo(gba: &mut agb::Gba) { +//! # let mut mixer = gba.mixer.mixer(); +//! # let vblank = agb::interrupt::VBlank::get(); +//! # use agb::{*, sound::mixer::*}; //! // Outside your main function in global scope: -//! const MY_CRAZY_SOUND: &[u8] = include_wav!("sfx/my_crazy_sound.wav"); +//! const MY_CRAZY_SOUND: &[u8] = include_wav!("examples/sfx/jump.wav"); //! //! // Then to play the sound: //! let mut channel = SoundChannel::new(MY_CRAZY_SOUND); //! channel.stereo(); //! let _ = mixer.play_sound(channel); // we don't mind if this sound doesn't actually play +//! # } //! ``` //! //! See the [`SoundChannel`] struct for more details on how you can configure the sounds to play. @@ -152,25 +175,39 @@ enum SoundPriority { /// play regardless of whether you have lots of sound effects playing. You create a high /// priority sound channel using [`new_high_priority`](SoundChannel::new_high_priority). /// -/// ``` +/// ```rust,no_run +/// # #![no_std] +/// # #![no_main] +/// # use agb::sound::mixer::*; +/// # use agb::*; /// // in global scope: -/// const MY_BGM: [u8] = include_wav!("sfx/my_bgm.wav"); +/// const MY_BGM: &[u8] = include_wav!("examples/sfx/my_bgm.wav"); /// /// // somewhere in code +/// # fn foo(gba: &mut Gba) { +/// # let mut mixer = gba.mixer.mixer(); /// let mut bgm = SoundChannel::new_high_priority(MY_BGM); /// bgm.stereo().should_loop(); /// let _ = mixer.play_sound(bgm); +/// # } /// ``` /// /// ## Playing a sound effect /// -/// ``` +/// ```rust,no_run +/// # #![no_std] +/// # #![no_main] +/// # use agb::sound::mixer::*; +/// # use agb::*; /// // in global scope: -/// const JUMP_SOUND: [u8] = include_wav!("sfx/jump_sound.wav"); +/// const JUMP_SOUND: &[u8] = include_wav!("examples/sfx/jump.wav"); /// /// // somewhere in code -/// let jump_sound = SoundChannel::new(MY_JUMP_SOUND); +/// # fn foo(gba: &mut Gba) { +/// # let mut mixer = gba.mixer.mixer(); +/// let jump_sound = SoundChannel::new(JUMP_SOUND); /// let _ = mixer.play_sound(jump_sound); +/// # } /// ``` pub struct SoundChannel { data: &'static [u8], @@ -198,13 +235,20 @@ impl SoundChannel { /// /// # Example /// - /// ``` + /// ```rust,no_run + /// # #![no_std] + /// # #![no_main] + /// # use agb::sound::mixer::*; + /// # use agb::*; + /// # fn foo(gba: &mut Gba) { + /// # let mut mixer = gba.mixer.mixer(); /// // in global scope: - /// const JUMP_SOUND: [u8] = include_wav!("sfx/jump_sound.wav"); + /// const JUMP_SOUND: &[u8] = include_wav!("examples/sfx/jump.wav"); /// /// // somewhere in code - /// let jump_sound = SoundChannel::new(MY_JUMP_SOUND); + /// let jump_sound = SoundChannel::new(JUMP_SOUND); /// let _ = mixer.play_sound(jump_sound); + /// # } /// ``` #[inline(always)] #[must_use] @@ -233,14 +277,21 @@ impl SoundChannel { /// /// # Example /// - /// ``` + /// ```rust,no_run + /// # #![no_std] + /// # #![no_main] + /// # use agb::sound::mixer::*; + /// # use agb::*; + /// # fn foo(gba: &mut Gba) { + /// # let mut mixer = gba.mixer.mixer(); /// // in global scope: - /// const MY_BGM: [u8] = include_wav!("sfx/my_bgm.wav"); + /// const MY_BGM: &[u8] = include_wav!("examples/sfx/my_bgm.wav"); /// /// // somewhere in code /// let mut bgm = SoundChannel::new_high_priority(MY_BGM); /// bgm.stereo().should_loop(); /// let _ = mixer.play_sound(bgm); + /// # } /// ``` #[inline(always)] #[must_use] diff --git a/agb/src/sound/mixer/sw_mixer.rs b/agb/src/sound/mixer/sw_mixer.rs index f3f3e081..676c0ed8 100644 --- a/agb/src/sound/mixer/sw_mixer.rs +++ b/agb/src/sound/mixer/sw_mixer.rs @@ -36,15 +36,28 @@ extern "C" { /// You should not create this struct directly, instead creating it through the [`Gba`](crate::Gba) /// struct as follows: /// -/// ``` +/// ```rust,no_run +/// # #![no_std] +/// # #![no_main] +/// # use agb::sound::mixer::*; +/// # use agb::*; +/// # fn foo(gba: &mut Gba) { /// let mut mixer = gba.mixer.mixer(); +/// # } /// ``` /// /// # Example /// -/// ``` +/// ```rust,no_run +/// # #![no_std] +/// # #![no_main] +/// # use agb::sound::mixer::*; +/// # use agb::*; +/// # fn foo(gba: &mut Gba) { +/// # let mut mixer = gba.mixer.mixer(); +/// # let vblank = agb::interrupt::VBlank::get(); /// // Outside your main function in global scope: -/// const MY_CRAZY_SOUND: &[u8] = include_wav!("sfx/my_crazy_sound.wav"); +/// const MY_CRAZY_SOUND: &[u8] = include_wav!("examples/sfx/jump.wav"); /// /// // in your main function: /// let mut mixer = gba.mixer.mixer(); @@ -57,6 +70,7 @@ extern "C" { /// vblank.wait_for_vblank(); /// mixer.after_vblank(); /// } +/// # } /// ``` pub struct Mixer { buffer: MixerBuffer, @@ -72,12 +86,20 @@ pub struct Mixer { /// /// # Example /// -/// ``` +/// ```rust,no_run +/// # #![no_std] +/// # #![no_main] +/// # use agb::sound::mixer::*; +/// # use agb::*; +/// # fn foo(gba: &mut Gba) { +/// # let mut mixer = gba.mixer.mixer(); +/// # const MY_BGM: &[u8] = include_wav!("examples/sfx/my_bgm.wav"); /// let mut channel = SoundChannel::new_high_priority(MY_BGM); /// let bgm_channel_id = mixer.play_sound(channel).unwrap(); // will always be Some if high priority /// /// // Later, stop that particular channel -/// mixer.channel(bgm_channel_id).stop(); +/// mixer.channel(&bgm_channel_id).expect("Expected to still be playing").stop(); +/// # } /// ``` pub struct ChannelId(usize, i32); @@ -106,12 +128,20 @@ impl Mixer { /// /// # Example /// - /// ``` + /// ```rust,no_run + /// # #![no_std] + /// # #![no_main] + /// # use agb::sound::mixer::*; + /// # use agb::*; + /// # fn foo(gba: &mut Gba) { + /// # let mut mixer = gba.mixer.mixer(); + /// # let vblank = agb::interrupt::VBlank::get(); /// loop { /// mixer.frame(); /// vblank.wait_for_vblank(); /// mixer.after_vblank(); /// } + /// # } /// ``` #[cfg(not(feature = "freq32768"))] pub fn after_vblank(&mut self) { @@ -127,7 +157,14 @@ impl Mixer { /// /// # Example /// - /// ``` + /// ```rust,no_run + /// # #![no_std] + /// # #![no_main] + /// # use agb::sound::mixer::*; + /// # use agb::*; + /// # fn foo(gba: &mut Gba) { + /// # let mut mixer = gba.mixer.mixer(); + /// # let vblank = agb::interrupt::VBlank::get(); /// // you must set this to a named variable to ensure that the scope is long enough /// let _mixer_interrupt = mixer.setup_interrupt_handler(); /// @@ -135,6 +172,7 @@ impl Mixer { /// mixer.frame(); /// vblank.wait_for_vblank(); /// } + /// # } /// ``` pub fn setup_interrupt_handler(&self) -> InterruptHandler<'_> { let mut timer1 = unsafe { Timer::new(1) }; @@ -157,12 +195,20 @@ impl Mixer { /// /// # Example /// - /// ``` + /// ```rust,no_run + /// # #![no_std] + /// # #![no_main] + /// # use agb::sound::mixer::*; + /// # use agb::*; + /// # fn foo(gba: &mut Gba) { + /// # let mut mixer = gba.mixer.mixer(); + /// # let vblank = agb::interrupt::VBlank::get(); /// loop { /// mixer.frame(); /// vblank.wait_for_vblank(); /// mixer.after_vblank(); // optional, only if not using interrupts /// } + /// # } /// ``` pub fn frame(&mut self) { if !self.buffer.should_calculate() { @@ -188,9 +234,17 @@ impl Mixer { /// /// # Example /// - /// ``` + /// ```rust,no_run + /// # #![no_std] + /// # #![no_main] + /// # use agb::sound::mixer::*; + /// # use agb::*; + /// # fn foo(gba: &mut Gba) { + /// # let mut mixer = gba.mixer.mixer(); + /// # const MY_BGM: &[u8] = include_wav!("examples/sfx/my_bgm.wav"); /// let mut channel = SoundChannel::new_high_priority(MY_BGM); /// let bgm_channel_id = mixer.play_sound(channel).unwrap(); // will always be Some if high priority + /// # } /// ``` pub fn play_sound(&mut self, new_channel: SoundChannel) -> Option { for (i, channel) in self.channels.iter_mut().enumerate() { @@ -229,12 +283,20 @@ impl Mixer { /// /// # Example /// - /// ``` + /// ```rust,no_run + /// # #![no_std] + /// # #![no_main] + /// # use agb::sound::mixer::*; + /// # use agb::*; + /// # fn foo(gba: &mut Gba) { + /// # let mut mixer = gba.mixer.mixer(); + /// # const MY_BGM: &[u8] = include_wav!("examples/sfx/my_bgm.wav"); /// let mut channel = SoundChannel::new_high_priority(MY_BGM); /// let bgm_channel_id = mixer.play_sound(channel).unwrap(); // will always be Some if high priority /// /// // Later, stop that particular channel - /// mixer.channel(bgm_channel_id).stop(); + /// mixer.channel(&bgm_channel_id).expect("Expected still to be playing").stop(); + /// # } /// ``` pub fn channel(&mut self, id: &ChannelId) -> Option<&'_ mut SoundChannel> { if let Some(channel) = &mut self.channels[id.0] { diff --git a/justfile b/justfile index 79d0099e..cdc3097a 100644 --- a/justfile +++ b/justfile @@ -17,6 +17,9 @@ test: test-release: just _test-release agb +doctest-agb: + (cd agb && cargo test --doc -Z doctest-xcompile) + clean: just _all-crates _clean @@ -34,7 +37,7 @@ run-game game: run-game-debug game: (cd "examples/{{game}}" && cargo run) -ci: build-debug clippy test build-release test-release build-roms build-book +ci: build-debug clippy test build-release test-release doctest-agb build-roms build-book build-roms: just _build-rom "examples/the-purple-night" "PURPLENIGHT"