mirror of
https://github.com/italicsjenga/agb.git
synced 2024-12-24 00:31:34 +11:00
Merge branch 'master' into affine
This commit is contained in:
commit
f5f73e89f5
|
@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
- HashMap iterators now implement `size_hint` which should result in slightly better generation of code using those iterators.
|
- HashMap iterators now implement `size_hint` which should result in slightly better generation of code using those iterators.
|
||||||
- Transparency of backgrounds is now set once in the toml file rather than once for every image.
|
- Transparency of backgrounds is now set once in the toml file rather than once for every image.
|
||||||
- Palette generation now takes into account every single background a toml definition rather than one at a time, you can now find it in the PALETTES constant rather than in every individual image.
|
- Palette generation now takes into account every single background a toml definition rather than one at a time, you can now find it in the PALETTES constant rather than in every individual image.
|
||||||
|
- Sound frequency is no longer a crate feature, instead set when initialising the sound mixer.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Fixed the fast magnitude function in agb_fixnum. This is also used in fast_normalise. Previously only worked for positive (x, y).
|
- Fixed the fast magnitude function in agb_fixnum. This is also used in fast_normalise. Previously only worked for positive (x, y).
|
||||||
|
|
|
@ -18,10 +18,6 @@ debug = true
|
||||||
[lib]
|
[lib]
|
||||||
proc-macro = true
|
proc-macro = true
|
||||||
|
|
||||||
[features]
|
|
||||||
freq18157 = []
|
|
||||||
freq32768 = []
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
hound = "3.5"
|
hound = "3.5"
|
||||||
syn = "1"
|
syn = "1"
|
||||||
|
|
|
@ -6,15 +6,6 @@ use quote::{quote, ToTokens};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use syn::parse_macro_input;
|
use syn::parse_macro_input;
|
||||||
|
|
||||||
#[cfg(all(not(feature = "freq18157"), not(feature = "freq32768")))]
|
|
||||||
const FREQUENCY: u32 = 10512;
|
|
||||||
#[cfg(feature = "freq18157")]
|
|
||||||
const FREQUENCY: u32 = 18157;
|
|
||||||
#[cfg(feature = "freq32768")]
|
|
||||||
const FREQUENCY: u32 = 32768;
|
|
||||||
#[cfg(all(feature = "freq18157", feature = "freq32768"))]
|
|
||||||
compile_error!("Must have at most one of freq18157 or freq32768 features enabled");
|
|
||||||
|
|
||||||
use quote::TokenStreamExt;
|
use quote::TokenStreamExt;
|
||||||
struct ByteString<'a>(&'a [u8]);
|
struct ByteString<'a>(&'a [u8]);
|
||||||
impl ToTokens for ByteString<'_> {
|
impl ToTokens for ByteString<'_> {
|
||||||
|
@ -37,13 +28,6 @@ pub fn include_wav(input: TokenStream) -> TokenStream {
|
||||||
let wav_reader = hound::WavReader::open(&path)
|
let wav_reader = hound::WavReader::open(&path)
|
||||||
.unwrap_or_else(|_| panic!("Failed to load file {}", include_path));
|
.unwrap_or_else(|_| panic!("Failed to load file {}", include_path));
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
wav_reader.spec().sample_rate,
|
|
||||||
FREQUENCY,
|
|
||||||
"agb currently only supports sample rate of {}Hz",
|
|
||||||
FREQUENCY
|
|
||||||
);
|
|
||||||
|
|
||||||
let samples: Vec<u8> = samples_from_reader(wav_reader).collect();
|
let samples: Vec<u8> = samples_from_reader(wav_reader).collect();
|
||||||
let samples = ByteString(&samples);
|
let samples = ByteString(&samples);
|
||||||
|
|
||||||
|
|
|
@ -17,8 +17,6 @@ debug = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["testing"]
|
default = ["testing"]
|
||||||
freq18157 = ["agb_sound_converter/freq18157"]
|
|
||||||
freq32768 = ["agb_sound_converter/freq32768"]
|
|
||||||
testing = []
|
testing = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
@ -34,7 +32,3 @@ rustc-hash = { version = "1", default-features = false }
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
default-target = "thumbv6m-none-eabi"
|
default-target = "thumbv6m-none-eabi"
|
||||||
targets = []
|
targets = []
|
||||||
|
|
||||||
[[example]]
|
|
||||||
name = "mixer_32768"
|
|
||||||
required-features = ["freq32768"]
|
|
|
@ -7,7 +7,7 @@ use agb::{
|
||||||
Font, Priority,
|
Font, Priority,
|
||||||
},
|
},
|
||||||
include_font, include_wav,
|
include_font, include_wav,
|
||||||
sound::mixer::SoundChannel,
|
sound::mixer::{Frequency, SoundChannel},
|
||||||
Gba,
|
Gba,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ fn main(mut gba: Gba) -> ! {
|
||||||
let mut timer = timer_controller.timer2;
|
let mut timer = timer_controller.timer2;
|
||||||
timer.set_enabled(true);
|
timer.set_enabled(true);
|
||||||
|
|
||||||
let mut mixer = gba.mixer.mixer();
|
let mut mixer = gba.mixer.mixer(Frequency::Hz32768);
|
||||||
mixer.enable();
|
mixer.enable();
|
||||||
let _interrupt = mixer.setup_interrupt_handler();
|
let _interrupt = mixer.setup_interrupt_handler();
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
use agb::fixnum::Num;
|
use agb::fixnum::Num;
|
||||||
use agb::input::{Button, ButtonController, Tri};
|
use agb::input::{Button, ButtonController, Tri};
|
||||||
use agb::sound::mixer::SoundChannel;
|
use agb::sound::mixer::{Frequency, SoundChannel};
|
||||||
use agb::{fixnum::num, include_wav, Gba};
|
use agb::{fixnum::num, include_wav, Gba};
|
||||||
|
|
||||||
// Music - "Dead Code" by Josh Woodward, free download at http://joshwoodward.com
|
// Music - "Dead Code" by Josh Woodward, free download at http://joshwoodward.com
|
||||||
|
@ -14,7 +14,7 @@ fn main(mut gba: Gba) -> ! {
|
||||||
let mut input = ButtonController::new();
|
let mut input = ButtonController::new();
|
||||||
let vblank_provider = agb::interrupt::VBlank::get();
|
let vblank_provider = agb::interrupt::VBlank::get();
|
||||||
|
|
||||||
let mut mixer = gba.mixer.mixer();
|
let mut mixer = gba.mixer.mixer(Frequency::Hz10512);
|
||||||
mixer.enable();
|
mixer.enable();
|
||||||
|
|
||||||
let channel = SoundChannel::new(DEAD_CODE);
|
let channel = SoundChannel::new(DEAD_CODE);
|
||||||
|
|
|
@ -7,7 +7,7 @@ use agb::{
|
||||||
Font, Priority,
|
Font, Priority,
|
||||||
},
|
},
|
||||||
include_font, include_wav,
|
include_font, include_wav,
|
||||||
sound::mixer::SoundChannel,
|
sound::mixer::{Frequency, SoundChannel},
|
||||||
Gba,
|
Gba,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ fn main(mut gba: Gba) -> ! {
|
||||||
let mut timer = timer_controller.timer2;
|
let mut timer = timer_controller.timer2;
|
||||||
timer.set_enabled(true);
|
timer.set_enabled(true);
|
||||||
|
|
||||||
let mut mixer = gba.mixer.mixer();
|
let mut mixer = gba.mixer.mixer(Frequency::Hz10512);
|
||||||
mixer.enable();
|
mixer.enable();
|
||||||
|
|
||||||
let mut channel = SoundChannel::new(LET_IT_IN);
|
let mut channel = SoundChannel::new(LET_IT_IN);
|
||||||
|
|
|
@ -160,7 +160,7 @@ impl<'a> InfiniteScrolledMap<'a> {
|
||||||
/// # );
|
/// # );
|
||||||
/// #
|
/// #
|
||||||
/// # let vblank = agb::interrupt::VBlank::get();
|
/// # let vblank = agb::interrupt::VBlank::get();
|
||||||
/// # let mut mixer = gba.mixer.mixer();
|
/// # let mut mixer = gba.mixer.mixer(agb::sound::mixer::Frequency::Hz10512);
|
||||||
/// let start_position = agb::fixnum::Vector2D::new(10, 10);
|
/// let start_position = agb::fixnum::Vector2D::new(10, 10);
|
||||||
/// backdrop.init(&mut vram, start_position, &mut || {
|
/// backdrop.init(&mut vram, start_position, &mut || {
|
||||||
/// vblank.wait_for_vblank();
|
/// vblank.wait_for_vblank();
|
||||||
|
@ -235,7 +235,7 @@ impl<'a> InfiniteScrolledMap<'a> {
|
||||||
/// # );
|
/// # );
|
||||||
/// #
|
/// #
|
||||||
/// # let vblank = agb::interrupt::VBlank::get();
|
/// # let vblank = agb::interrupt::VBlank::get();
|
||||||
/// # let mut mixer = gba.mixer.mixer();
|
/// # let mut mixer = gba.mixer.mixer(agb::sound::mixer::Frequency::Hz10512);
|
||||||
/// let start_position = agb::fixnum::Vector2D::new(10, 10);
|
/// let start_position = agb::fixnum::Vector2D::new(10, 10);
|
||||||
/// while backdrop.init_partial(&mut vram, start_position) == PartialUpdateStatus::Continue {
|
/// while backdrop.init_partial(&mut vram, start_position) == PartialUpdateStatus::Continue {
|
||||||
/// vblank.wait_for_vblank();
|
/// vblank.wait_for_vblank();
|
||||||
|
|
|
@ -14,17 +14,11 @@
|
||||||
//!
|
//!
|
||||||
//! # Concepts
|
//! # Concepts
|
||||||
//!
|
//!
|
||||||
//! The mixer runs at a fixed frequency which is determined at compile time by enabling
|
//! The mixer runs at a fixed frequency which is determined at initialisation time by
|
||||||
//! certain features within the crate. The following features are currently available:
|
//! passing certain [`Frequency`] options.
|
||||||
//!
|
|
||||||
//! | Feature | Frequency |
|
|
||||||
//! |---------|-----------|
|
|
||||||
//! | none | 10512Hz |
|
|
||||||
//! | freq18157 | 18157Hz |
|
|
||||||
//! | freq32768[^32768Hz] | 32768Hz |
|
|
||||||
//!
|
//!
|
||||||
//! All wav files you use within your application / game must use this _exact_ frequency.
|
//! All wav files you use within your application / game must use this _exact_ frequency.
|
||||||
//! You will get a compile error if you use the incorrect frequency for your file.
|
//! If you don't use this frequency, the sound will play either too slowly or too quickly.
|
||||||
//!
|
//!
|
||||||
//! The mixer can play both mono and stereo sounds, but only mono sound effects can have
|
//! The mixer can play both mono and stereo sounds, but only mono sound effects can have
|
||||||
//! effects applied to them (such as changing the speed at which they play or the panning).
|
//! effects applied to them (such as changing the speed at which they play or the panning).
|
||||||
|
@ -38,12 +32,17 @@
|
||||||
//! ```rust,no_run
|
//! ```rust,no_run
|
||||||
//! # #![no_std]
|
//! # #![no_std]
|
||||||
//! # #![no_main]
|
//! # #![no_main]
|
||||||
|
//! use agb::sound::mixer::Frequency;
|
||||||
//! # fn foo(gba: &mut agb::Gba) {
|
//! # fn foo(gba: &mut agb::Gba) {
|
||||||
//! let mut mixer = gba.mixer.mixer();
|
//! let mut mixer = gba.mixer.mixer(Frequency::Hz10512);
|
||||||
//! mixer.enable();
|
//! mixer.enable();
|
||||||
//! # }
|
//! # }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
|
//! Pass a frequency option. This option must be used for the entire lifetime of the `mixer`
|
||||||
|
//! variable. If you want to change frequency, you will need to drop this one and create a new
|
||||||
|
//! one.
|
||||||
|
//!
|
||||||
//! ## Doing the per-frame work
|
//! ## Doing the per-frame work
|
||||||
//!
|
//!
|
||||||
//! Then, you have a choice of whether you want to use interrupts or do the buffer swapping
|
//! Then, you have a choice of whether you want to use interrupts or do the buffer swapping
|
||||||
|
@ -55,9 +54,10 @@
|
||||||
//! ```rust,no_run
|
//! ```rust,no_run
|
||||||
//! # #![no_std]
|
//! # #![no_std]
|
||||||
//! # #![no_main]
|
//! # #![no_main]
|
||||||
|
//! use agb::sound::mixer::Frequency;
|
||||||
//! # fn foo(gba: &mut agb::Gba) {
|
//! # fn foo(gba: &mut agb::Gba) {
|
||||||
//! # let mut mixer = gba.mixer.mixer();
|
//! let mut mixer = gba.mixer.mixer(Frequency::Hz10512);
|
||||||
//! # let vblank = agb::interrupt::VBlank::get();
|
//! let vblank = agb::interrupt::VBlank::get();
|
||||||
//! // Somewhere in your main loop:
|
//! // Somewhere in your main loop:
|
||||||
//! mixer.frame();
|
//! mixer.frame();
|
||||||
//! vblank.wait_for_vblank();
|
//! vblank.wait_for_vblank();
|
||||||
|
@ -70,10 +70,13 @@
|
||||||
//! ```rust,no_run
|
//! ```rust,no_run
|
||||||
//! # #![no_std]
|
//! # #![no_std]
|
||||||
//! # #![no_main]
|
//! # #![no_main]
|
||||||
|
//! use agb::sound::mixer::Frequency;
|
||||||
//! # fn foo(gba: &mut agb::Gba) {
|
//! # fn foo(gba: &mut agb::Gba) {
|
||||||
//! # let mut mixer = gba.mixer.mixer();
|
//! let mut mixer = gba.mixer.mixer(agb::sound::mixer::Frequency::Hz32768);
|
||||||
//! # let vblank = agb::interrupt::VBlank::get();
|
//! let vblank = agb::interrupt::VBlank::get();
|
||||||
//! // outside your main loop, close to initialisation
|
//! // outside your main loop, close to initialisation
|
||||||
|
//! // you must assign this to a variable (not to _ or ignored) or rust will immediately drop it
|
||||||
|
//! // and prevent the interrupt handler from firing.
|
||||||
//! let _mixer_interrupt = mixer.setup_interrupt_handler();
|
//! let _mixer_interrupt = mixer.setup_interrupt_handler();
|
||||||
//!
|
//!
|
||||||
//! // inside your main loop
|
//! // inside your main loop
|
||||||
|
@ -99,7 +102,7 @@
|
||||||
//! # #![no_std]
|
//! # #![no_std]
|
||||||
//! # #![no_main]
|
//! # #![no_main]
|
||||||
//! # fn foo(gba: &mut agb::Gba) {
|
//! # fn foo(gba: &mut agb::Gba) {
|
||||||
//! # let mut mixer = gba.mixer.mixer();
|
//! # let mut mixer = gba.mixer.mixer(agb::sound::mixer::Frequency::Hz10512);
|
||||||
//! # let vblank = agb::interrupt::VBlank::get();
|
//! # let vblank = agb::interrupt::VBlank::get();
|
||||||
//! # use agb::{*, sound::mixer::*};
|
//! # use agb::{*, sound::mixer::*};
|
||||||
//! // Outside your main function in global scope:
|
//! // Outside your main function in global scope:
|
||||||
|
@ -116,9 +119,6 @@
|
||||||
//!
|
//!
|
||||||
//! Once you have run [`play_sound`](Mixer::play_sound), the mixer will play that sound until
|
//! Once you have run [`play_sound`](Mixer::play_sound), the mixer will play that sound until
|
||||||
//! it has finished.
|
//! it has finished.
|
||||||
//!
|
|
||||||
//! [^32768Hz]: You must use interrupts when using 32768Hz
|
|
||||||
|
|
||||||
mod hw;
|
mod hw;
|
||||||
mod sw_mixer;
|
mod sw_mixer;
|
||||||
|
|
||||||
|
@ -138,8 +138,8 @@ impl MixerController {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a [`Mixer`] in order to start producing sounds.
|
/// Get a [`Mixer`] in order to start producing sounds.
|
||||||
pub fn mixer(&mut self) -> Mixer {
|
pub fn mixer(&mut self, frequency: Frequency) -> Mixer {
|
||||||
Mixer::new()
|
Mixer::new(frequency)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,6 +149,43 @@ enum SoundPriority {
|
||||||
Low,
|
Low,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The supported frequencies within AGB. These are chosen to work well with
|
||||||
|
/// the hardware. Note that the higher the frequency, the better the quality of
|
||||||
|
/// the sound but the more CPU time sound mixing will take.
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum Frequency {
|
||||||
|
/// 10512Hz
|
||||||
|
Hz10512,
|
||||||
|
/// 18157Hz
|
||||||
|
Hz18157,
|
||||||
|
/// 32768Hz - note that this option requires interrupts for buffer swapping
|
||||||
|
Hz32768,
|
||||||
|
}
|
||||||
|
|
||||||
|
// list here: http://deku.gbadev.org/program/sound1.html
|
||||||
|
impl Frequency {
|
||||||
|
pub(crate) fn frequency(self) -> i32 {
|
||||||
|
use Frequency::*;
|
||||||
|
|
||||||
|
match self {
|
||||||
|
Hz10512 => 10512,
|
||||||
|
Hz18157 => 18157,
|
||||||
|
Hz32768 => 32768,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn buffer_size(self) -> usize {
|
||||||
|
use Frequency::*;
|
||||||
|
|
||||||
|
match self {
|
||||||
|
Hz10512 => 176,
|
||||||
|
Hz18157 => 304,
|
||||||
|
Hz32768 => 560,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Describes one sound which should be playing. This could be a sound effect or
|
/// Describes one sound which should be playing. This could be a sound effect or
|
||||||
/// the background music. Use the factory methods on this to modify how it is played.
|
/// the background music. Use the factory methods on this to modify how it is played.
|
||||||
///
|
///
|
||||||
|
@ -185,7 +222,7 @@ enum SoundPriority {
|
||||||
///
|
///
|
||||||
/// // somewhere in code
|
/// // somewhere in code
|
||||||
/// # fn foo(gba: &mut Gba) {
|
/// # fn foo(gba: &mut Gba) {
|
||||||
/// # let mut mixer = gba.mixer.mixer();
|
/// # let mut mixer = gba.mixer.mixer(agb::sound::mixer::Frequency::Hz10512);
|
||||||
/// let mut bgm = SoundChannel::new_high_priority(MY_BGM);
|
/// let mut bgm = SoundChannel::new_high_priority(MY_BGM);
|
||||||
/// bgm.stereo().should_loop();
|
/// bgm.stereo().should_loop();
|
||||||
/// let _ = mixer.play_sound(bgm);
|
/// let _ = mixer.play_sound(bgm);
|
||||||
|
@ -204,7 +241,7 @@ enum SoundPriority {
|
||||||
///
|
///
|
||||||
/// // somewhere in code
|
/// // somewhere in code
|
||||||
/// # fn foo(gba: &mut Gba) {
|
/// # fn foo(gba: &mut Gba) {
|
||||||
/// # let mut mixer = gba.mixer.mixer();
|
/// # let mut mixer = gba.mixer.mixer(agb::sound::mixer::Frequency::Hz10512);
|
||||||
/// let jump_sound = SoundChannel::new(JUMP_SOUND);
|
/// let jump_sound = SoundChannel::new(JUMP_SOUND);
|
||||||
/// let _ = mixer.play_sound(jump_sound);
|
/// let _ = mixer.play_sound(jump_sound);
|
||||||
/// # }
|
/// # }
|
||||||
|
@ -241,7 +278,7 @@ impl SoundChannel {
|
||||||
/// # use agb::sound::mixer::*;
|
/// # use agb::sound::mixer::*;
|
||||||
/// # use agb::*;
|
/// # use agb::*;
|
||||||
/// # fn foo(gba: &mut Gba) {
|
/// # fn foo(gba: &mut Gba) {
|
||||||
/// # let mut mixer = gba.mixer.mixer();
|
/// # let mut mixer = gba.mixer.mixer(agb::sound::mixer::Frequency::Hz10512);
|
||||||
/// // in global scope:
|
/// // in global scope:
|
||||||
/// const JUMP_SOUND: &[u8] = include_wav!("examples/sfx/jump.wav");
|
/// const JUMP_SOUND: &[u8] = include_wav!("examples/sfx/jump.wav");
|
||||||
///
|
///
|
||||||
|
@ -283,7 +320,7 @@ impl SoundChannel {
|
||||||
/// # use agb::sound::mixer::*;
|
/// # use agb::sound::mixer::*;
|
||||||
/// # use agb::*;
|
/// # use agb::*;
|
||||||
/// # fn foo(gba: &mut Gba) {
|
/// # fn foo(gba: &mut Gba) {
|
||||||
/// # let mut mixer = gba.mixer.mixer();
|
/// # let mut mixer = gba.mixer.mixer(agb::sound::mixer::Frequency::Hz10512);
|
||||||
/// // in global scope:
|
/// // in global scope:
|
||||||
/// const MY_BGM: &[u8] = include_wav!("examples/sfx/my_bgm.wav");
|
/// const MY_BGM: &[u8] = include_wav!("examples/sfx/my_bgm.wav");
|
||||||
///
|
///
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
use core::cell::RefCell;
|
use core::cell::RefCell;
|
||||||
use core::intrinsics::transmute;
|
|
||||||
|
|
||||||
|
use alloc::boxed::Box;
|
||||||
|
use alloc::vec::Vec;
|
||||||
use bare_metal::{CriticalSection, Mutex};
|
use bare_metal::{CriticalSection, Mutex};
|
||||||
|
|
||||||
use super::hw;
|
|
||||||
use super::hw::LeftOrRight;
|
use super::hw::LeftOrRight;
|
||||||
|
use super::{hw, Frequency};
|
||||||
use super::{SoundChannel, SoundPriority};
|
use super::{SoundChannel, SoundPriority};
|
||||||
|
|
||||||
|
use crate::InternalAllocator;
|
||||||
use crate::{
|
use crate::{
|
||||||
fixnum::Num,
|
fixnum::Num,
|
||||||
interrupt::free,
|
interrupt::free,
|
||||||
|
@ -46,7 +48,7 @@ extern "C" {
|
||||||
/// # use agb::sound::mixer::*;
|
/// # use agb::sound::mixer::*;
|
||||||
/// # use agb::*;
|
/// # use agb::*;
|
||||||
/// # fn foo(gba: &mut Gba) {
|
/// # fn foo(gba: &mut Gba) {
|
||||||
/// let mut mixer = gba.mixer.mixer();
|
/// let mut mixer = gba.mixer.mixer(Frequency::Hz10512);
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
|
@ -58,13 +60,13 @@ extern "C" {
|
||||||
/// # use agb::sound::mixer::*;
|
/// # use agb::sound::mixer::*;
|
||||||
/// # use agb::*;
|
/// # use agb::*;
|
||||||
/// # fn foo(gba: &mut Gba) {
|
/// # fn foo(gba: &mut Gba) {
|
||||||
/// # let mut mixer = gba.mixer.mixer();
|
/// # let mut mixer = gba.mixer.mixer(agb::sound::mixer::Frequency::Hz10512);
|
||||||
/// # let vblank = agb::interrupt::VBlank::get();
|
/// # let vblank = agb::interrupt::VBlank::get();
|
||||||
/// // Outside your main function in global scope:
|
/// // Outside your main function in global scope:
|
||||||
/// const MY_CRAZY_SOUND: &[u8] = include_wav!("examples/sfx/jump.wav");
|
/// const MY_CRAZY_SOUND: &[u8] = include_wav!("examples/sfx/jump.wav");
|
||||||
///
|
///
|
||||||
/// // in your main function:
|
/// // in your main function:
|
||||||
/// let mut mixer = gba.mixer.mixer();
|
/// let mut mixer = gba.mixer.mixer(Frequency::Hz10512);
|
||||||
/// let mut channel = SoundChannel::new(MY_CRAZY_SOUND);
|
/// let mut channel = SoundChannel::new(MY_CRAZY_SOUND);
|
||||||
/// channel.stereo();
|
/// channel.stereo();
|
||||||
/// let _ = mixer.play_sound(channel);
|
/// let _ = mixer.play_sound(channel);
|
||||||
|
@ -80,6 +82,7 @@ pub struct Mixer {
|
||||||
buffer: MixerBuffer,
|
buffer: MixerBuffer,
|
||||||
channels: [Option<SoundChannel>; 8],
|
channels: [Option<SoundChannel>; 8],
|
||||||
indices: [i32; 8],
|
indices: [i32; 8],
|
||||||
|
frequency: Frequency,
|
||||||
|
|
||||||
timer: Timer,
|
timer: Timer,
|
||||||
}
|
}
|
||||||
|
@ -96,7 +99,7 @@ pub struct Mixer {
|
||||||
/// # use agb::sound::mixer::*;
|
/// # use agb::sound::mixer::*;
|
||||||
/// # use agb::*;
|
/// # use agb::*;
|
||||||
/// # fn foo(gba: &mut Gba) {
|
/// # fn foo(gba: &mut Gba) {
|
||||||
/// # let mut mixer = gba.mixer.mixer();
|
/// # let mut mixer = gba.mixer.mixer(agb::sound::mixer::Frequency::Hz10512);
|
||||||
/// # const MY_BGM: &[u8] = include_wav!("examples/sfx/my_bgm.wav");
|
/// # const MY_BGM: &[u8] = include_wav!("examples/sfx/my_bgm.wav");
|
||||||
/// let mut channel = SoundChannel::new_high_priority(MY_BGM);
|
/// 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
|
/// let bgm_channel_id = mixer.play_sound(channel).unwrap(); // will always be Some if high priority
|
||||||
|
@ -108,9 +111,10 @@ pub struct Mixer {
|
||||||
pub struct ChannelId(usize, i32);
|
pub struct ChannelId(usize, i32);
|
||||||
|
|
||||||
impl Mixer {
|
impl Mixer {
|
||||||
pub(super) fn new() -> Self {
|
pub(super) fn new(frequency: Frequency) -> Self {
|
||||||
Self {
|
Self {
|
||||||
buffer: MixerBuffer::new(),
|
frequency,
|
||||||
|
buffer: MixerBuffer::new(frequency),
|
||||||
channels: Default::default(),
|
channels: Default::default(),
|
||||||
indices: Default::default(),
|
indices: Default::default(),
|
||||||
|
|
||||||
|
@ -123,7 +127,7 @@ impl Mixer {
|
||||||
/// You must call this method in order to start playing sound. You can do as much set up before
|
/// You must call this method in order to start playing sound. You can do as much set up before
|
||||||
/// this as you like, but you will not get any sound out of the console until this method is called.
|
/// this as you like, but you will not get any sound out of the console until this method is called.
|
||||||
pub fn enable(&mut self) {
|
pub fn enable(&mut self) {
|
||||||
hw::set_timer_counter_for_frequency_and_enable(&mut self.timer, constants::SOUND_FREQUENCY);
|
hw::set_timer_counter_for_frequency_and_enable(&mut self.timer, self.frequency.frequency());
|
||||||
hw::set_sound_control_register_for_mixer();
|
hw::set_sound_control_register_for_mixer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,7 +142,7 @@ impl Mixer {
|
||||||
/// # use agb::sound::mixer::*;
|
/// # use agb::sound::mixer::*;
|
||||||
/// # use agb::*;
|
/// # use agb::*;
|
||||||
/// # fn foo(gba: &mut Gba) {
|
/// # fn foo(gba: &mut Gba) {
|
||||||
/// # let mut mixer = gba.mixer.mixer();
|
/// # let mut mixer = gba.mixer.mixer(agb::sound::mixer::Frequency::Hz10512);
|
||||||
/// # let vblank = agb::interrupt::VBlank::get();
|
/// # let vblank = agb::interrupt::VBlank::get();
|
||||||
/// loop {
|
/// loop {
|
||||||
/// mixer.frame();
|
/// mixer.frame();
|
||||||
|
@ -167,7 +171,7 @@ impl Mixer {
|
||||||
/// # use agb::sound::mixer::*;
|
/// # use agb::sound::mixer::*;
|
||||||
/// # use agb::*;
|
/// # use agb::*;
|
||||||
/// # fn foo(gba: &mut Gba) {
|
/// # fn foo(gba: &mut Gba) {
|
||||||
/// # let mut mixer = gba.mixer.mixer();
|
/// # let mut mixer = gba.mixer.mixer(agb::sound::mixer::Frequency::Hz10512);
|
||||||
/// # let vblank = agb::interrupt::VBlank::get();
|
/// # let vblank = agb::interrupt::VBlank::get();
|
||||||
/// // you must set this to a named variable to ensure that the scope is long enough
|
/// // you must set this to a named variable to ensure that the scope is long enough
|
||||||
/// let _mixer_interrupt = mixer.setup_interrupt_handler();
|
/// let _mixer_interrupt = mixer.setup_interrupt_handler();
|
||||||
|
@ -184,7 +188,7 @@ impl Mixer {
|
||||||
.set_cascade(true)
|
.set_cascade(true)
|
||||||
.set_divider(Divider::Divider1)
|
.set_divider(Divider::Divider1)
|
||||||
.set_interrupt(true)
|
.set_interrupt(true)
|
||||||
.set_overflow_amount(constants::SOUND_BUFFER_SIZE as u16)
|
.set_overflow_amount(self.frequency.buffer_size() as u16)
|
||||||
.set_enabled(true);
|
.set_enabled(true);
|
||||||
|
|
||||||
add_interrupt_handler(timer1.interrupt(), move |cs| self.buffer.swap(cs))
|
add_interrupt_handler(timer1.interrupt(), move |cs| self.buffer.swap(cs))
|
||||||
|
@ -205,7 +209,7 @@ impl Mixer {
|
||||||
/// # use agb::sound::mixer::*;
|
/// # use agb::sound::mixer::*;
|
||||||
/// # use agb::*;
|
/// # use agb::*;
|
||||||
/// # fn foo(gba: &mut Gba) {
|
/// # fn foo(gba: &mut Gba) {
|
||||||
/// # let mut mixer = gba.mixer.mixer();
|
/// # let mut mixer = gba.mixer.mixer(agb::sound::mixer::Frequency::Hz10512);
|
||||||
/// # let vblank = agb::interrupt::VBlank::get();
|
/// # let vblank = agb::interrupt::VBlank::get();
|
||||||
/// loop {
|
/// loop {
|
||||||
/// mixer.frame();
|
/// mixer.frame();
|
||||||
|
@ -244,7 +248,7 @@ impl Mixer {
|
||||||
/// # use agb::sound::mixer::*;
|
/// # use agb::sound::mixer::*;
|
||||||
/// # use agb::*;
|
/// # use agb::*;
|
||||||
/// # fn foo(gba: &mut Gba) {
|
/// # fn foo(gba: &mut Gba) {
|
||||||
/// # let mut mixer = gba.mixer.mixer();
|
/// # let mut mixer = gba.mixer.mixer(agb::sound::mixer::Frequency::Hz10512);
|
||||||
/// # const MY_BGM: &[u8] = include_wav!("examples/sfx/my_bgm.wav");
|
/// # const MY_BGM: &[u8] = include_wav!("examples/sfx/my_bgm.wav");
|
||||||
/// let mut channel = SoundChannel::new_high_priority(MY_BGM);
|
/// 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
|
/// let bgm_channel_id = mixer.play_sound(channel).unwrap(); // will always be Some if high priority
|
||||||
|
@ -293,7 +297,7 @@ impl Mixer {
|
||||||
/// # use agb::sound::mixer::*;
|
/// # use agb::sound::mixer::*;
|
||||||
/// # use agb::*;
|
/// # use agb::*;
|
||||||
/// # fn foo(gba: &mut Gba) {
|
/// # fn foo(gba: &mut Gba) {
|
||||||
/// # let mut mixer = gba.mixer.mixer();
|
/// # let mut mixer = gba.mixer.mixer(agb::sound::mixer::Frequency::Hz10512);
|
||||||
/// # const MY_BGM: &[u8] = include_wav!("examples/sfx/my_bgm.wav");
|
/// # const MY_BGM: &[u8] = include_wav!("examples/sfx/my_bgm.wav");
|
||||||
/// let mut channel = SoundChannel::new_high_priority(MY_BGM);
|
/// 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
|
/// let bgm_channel_id = mixer.play_sound(channel).unwrap(); // will always be Some if high priority
|
||||||
|
@ -313,47 +317,32 @@ impl Mixer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// These work perfectly with swapping the buffers every vblank
|
fn set_asm_buffer_size(frequency: Frequency) {
|
||||||
// list here: http://deku.gbadev.org/program/sound1.html
|
|
||||||
#[cfg(all(not(feature = "freq18157"), not(feature = "freq32768")))]
|
|
||||||
mod constants {
|
|
||||||
pub const SOUND_FREQUENCY: i32 = 10512;
|
|
||||||
pub const SOUND_BUFFER_SIZE: usize = 176;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "freq18157")]
|
|
||||||
mod constants {
|
|
||||||
pub const SOUND_FREQUENCY: i32 = 18157;
|
|
||||||
pub const SOUND_BUFFER_SIZE: usize = 304;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "freq32768")]
|
|
||||||
mod constants {
|
|
||||||
pub const SOUND_FREQUENCY: i32 = 32768;
|
|
||||||
pub const SOUND_BUFFER_SIZE: usize = 560;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_asm_buffer_size() {
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
static mut agb_rs__buffer_size: usize;
|
static mut agb_rs__buffer_size: usize;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
agb_rs__buffer_size = constants::SOUND_BUFFER_SIZE;
|
agb_rs__buffer_size = frequency.buffer_size();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C, align(4))]
|
struct SoundBuffer(Box<[i8], InternalAllocator>);
|
||||||
struct SoundBuffer([i8; constants::SOUND_BUFFER_SIZE * 2]);
|
|
||||||
|
|
||||||
impl Default for SoundBuffer {
|
impl SoundBuffer {
|
||||||
fn default() -> Self {
|
fn new(frequency: Frequency) -> Self {
|
||||||
Self([0; constants::SOUND_BUFFER_SIZE * 2])
|
let my_size = frequency.buffer_size() * 2;
|
||||||
|
let mut v = Vec::with_capacity_in(my_size, InternalAllocator);
|
||||||
|
v.resize(my_size, 0);
|
||||||
|
|
||||||
|
SoundBuffer(v.into_boxed_slice())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct MixerBuffer {
|
struct MixerBuffer {
|
||||||
buffers: [SoundBuffer; 3],
|
buffers: [SoundBuffer; 3],
|
||||||
|
working_buffer: Box<[Num<i16, 4>], InternalAllocator>,
|
||||||
|
frequency: Frequency,
|
||||||
|
|
||||||
state: Mutex<RefCell<MixerBufferState>>,
|
state: Mutex<RefCell<MixerBufferState>>,
|
||||||
}
|
}
|
||||||
|
@ -391,16 +380,26 @@ impl MixerBufferState {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MixerBuffer {
|
impl MixerBuffer {
|
||||||
fn new() -> Self {
|
fn new(frequency: Frequency) -> Self {
|
||||||
set_asm_buffer_size();
|
let mut working_buffer =
|
||||||
|
Vec::with_capacity_in(frequency.buffer_size() * 2, InternalAllocator);
|
||||||
|
working_buffer.resize(frequency.buffer_size() * 2, 0.into());
|
||||||
|
|
||||||
MixerBuffer {
|
MixerBuffer {
|
||||||
buffers: Default::default(),
|
buffers: [
|
||||||
|
SoundBuffer::new(frequency),
|
||||||
|
SoundBuffer::new(frequency),
|
||||||
|
SoundBuffer::new(frequency),
|
||||||
|
],
|
||||||
|
|
||||||
|
working_buffer: working_buffer.into_boxed_slice(),
|
||||||
|
|
||||||
state: Mutex::new(RefCell::new(MixerBufferState {
|
state: Mutex::new(RefCell::new(MixerBufferState {
|
||||||
active_buffer: 0,
|
active_buffer: 0,
|
||||||
playing_buffer: 0,
|
playing_buffer: 0,
|
||||||
})),
|
})),
|
||||||
|
|
||||||
|
frequency,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -413,15 +412,16 @@ impl MixerBuffer {
|
||||||
|
|
||||||
let (left_buffer, right_buffer) = self.buffers[buffer]
|
let (left_buffer, right_buffer) = self.buffers[buffer]
|
||||||
.0
|
.0
|
||||||
.split_at(constants::SOUND_BUFFER_SIZE);
|
.split_at(self.frequency.buffer_size());
|
||||||
|
|
||||||
hw::enable_dma_for_sound(left_buffer, LeftOrRight::Left);
|
hw::enable_dma_for_sound(left_buffer, LeftOrRight::Left);
|
||||||
hw::enable_dma_for_sound(right_buffer, LeftOrRight::Right);
|
hw::enable_dma_for_sound(right_buffer, LeftOrRight::Right);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_channels<'a>(&mut self, channels: impl Iterator<Item = &'a mut SoundChannel>) {
|
fn write_channels<'a>(&mut self, channels: impl Iterator<Item = &'a mut SoundChannel>) {
|
||||||
let mut buffer: [Num<i16, 4>; constants::SOUND_BUFFER_SIZE * 2] =
|
set_asm_buffer_size(self.frequency);
|
||||||
unsafe { transmute([0i16; constants::SOUND_BUFFER_SIZE * 2]) };
|
|
||||||
|
self.working_buffer.fill(0.into());
|
||||||
|
|
||||||
for channel in channels {
|
for channel in channels {
|
||||||
if channel.is_done {
|
if channel.is_done {
|
||||||
|
@ -434,7 +434,7 @@ impl MixerBuffer {
|
||||||
channel.playback_speed
|
channel.playback_speed
|
||||||
};
|
};
|
||||||
|
|
||||||
if (channel.pos + playback_speed * constants::SOUND_BUFFER_SIZE).floor()
|
if (channel.pos + playback_speed * self.frequency.buffer_size()).floor()
|
||||||
>= channel.data.len()
|
>= channel.data.len()
|
||||||
{
|
{
|
||||||
// TODO: This should probably play what's left rather than skip the last bit
|
// TODO: This should probably play what's left rather than skip the last bit
|
||||||
|
@ -450,7 +450,7 @@ impl MixerBuffer {
|
||||||
unsafe {
|
unsafe {
|
||||||
agb_rs__mixer_add_stereo(
|
agb_rs__mixer_add_stereo(
|
||||||
channel.data.as_ptr().add(channel.pos.floor()),
|
channel.data.as_ptr().add(channel.pos.floor()),
|
||||||
buffer.as_mut_ptr(),
|
self.working_buffer.as_mut_ptr(),
|
||||||
channel.volume,
|
channel.volume,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -461,7 +461,7 @@ impl MixerBuffer {
|
||||||
unsafe {
|
unsafe {
|
||||||
agb_rs__mixer_add(
|
agb_rs__mixer_add(
|
||||||
channel.data.as_ptr().add(channel.pos.floor()),
|
channel.data.as_ptr().add(channel.pos.floor()),
|
||||||
buffer.as_mut_ptr(),
|
self.working_buffer.as_mut_ptr(),
|
||||||
playback_speed,
|
playback_speed,
|
||||||
left_amount,
|
left_amount,
|
||||||
right_amount,
|
right_amount,
|
||||||
|
@ -469,7 +469,7 @@ impl MixerBuffer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
channel.pos += playback_speed * constants::SOUND_BUFFER_SIZE;
|
channel.pos += playback_speed * self.frequency.buffer_size();
|
||||||
}
|
}
|
||||||
|
|
||||||
let write_buffer_index = free(|cs| self.state.borrow(cs).borrow_mut().active_advanced());
|
let write_buffer_index = free(|cs| self.state.borrow(cs).borrow_mut().active_advanced());
|
||||||
|
@ -477,7 +477,7 @@ impl MixerBuffer {
|
||||||
let write_buffer = &mut self.buffers[write_buffer_index].0;
|
let write_buffer = &mut self.buffers[write_buffer_index].0;
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
agb_rs__mixer_collapse(write_buffer.as_mut_ptr(), buffer.as_ptr());
|
agb_rs__mixer_collapse(write_buffer.as_mut_ptr(), self.working_buffer.as_ptr());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ edition = "2018"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
agb = { version = "0.11.1", path = "../../agb", features = ["freq32768"] }
|
agb = { version = "0.11.1", path = "../../agb" }
|
||||||
bare-metal = "1"
|
bare-metal = "1"
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
|
|
|
@ -10,11 +10,11 @@
|
||||||
// which won't be a particularly clear error message.
|
// which won't be a particularly clear error message.
|
||||||
#![no_main]
|
#![no_main]
|
||||||
|
|
||||||
use agb::display;
|
|
||||||
use agb::display::object::ObjectController;
|
use agb::display::object::ObjectController;
|
||||||
use agb::display::tiled::{TiledMap, VRamManager};
|
use agb::display::tiled::{TiledMap, VRamManager};
|
||||||
use agb::display::Priority;
|
use agb::display::Priority;
|
||||||
use agb::interrupt::VBlank;
|
use agb::interrupt::VBlank;
|
||||||
|
use agb::{display, sound::mixer::Frequency};
|
||||||
|
|
||||||
extern crate alloc;
|
extern crate alloc;
|
||||||
use alloc::vec;
|
use alloc::vec;
|
||||||
|
@ -138,7 +138,7 @@ fn main(mut gba: agb::Gba) -> ! {
|
||||||
let mut star_background = StarBackground::new(&mut background0, &mut background1, &mut vram);
|
let mut star_background = StarBackground::new(&mut background0, &mut background1, &mut vram);
|
||||||
star_background.commit(&mut vram);
|
star_background.commit(&mut vram);
|
||||||
|
|
||||||
let mut mixer = gba.mixer.mixer();
|
let mut mixer = gba.mixer.mixer(Frequency::Hz32768);
|
||||||
mixer.enable();
|
mixer.enable();
|
||||||
let _interrupt_handler = mixer.setup_interrupt_handler();
|
let _interrupt_handler = mixer.setup_interrupt_handler();
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ use agb::{
|
||||||
},
|
},
|
||||||
fixnum::{FixedNum, Vector2D},
|
fixnum::{FixedNum, Vector2D},
|
||||||
input::{self, Button, ButtonController},
|
input::{self, Button, ButtonController},
|
||||||
|
sound::mixer::Frequency,
|
||||||
};
|
};
|
||||||
use alloc::boxed::Box;
|
use alloc::boxed::Box;
|
||||||
|
|
||||||
|
@ -820,7 +821,7 @@ pub fn main(mut agb: agb::Gba) -> ! {
|
||||||
vram.set_background_palettes(tile_sheet::PALETTES);
|
vram.set_background_palettes(tile_sheet::PALETTES);
|
||||||
|
|
||||||
let object = agb.display.object.get();
|
let object = agb.display.object.get();
|
||||||
let mut mixer = agb.mixer.mixer();
|
let mut mixer = agb.mixer.mixer(Frequency::Hz10512);
|
||||||
|
|
||||||
mixer.enable();
|
mixer.enable();
|
||||||
let mut music_box = sfx::MusicBox::new();
|
let mut music_box = sfx::MusicBox::new();
|
||||||
|
|
|
@ -7,7 +7,7 @@ edition = "2018"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
agb = { path = "../../agb", version = "0.11.1", features = ["freq18157"]}
|
agb = { path = "../../agb", version = "0.11.1" }
|
||||||
generational-arena = { version = "0.2", default-features = false }
|
generational-arena = { version = "0.2", default-features = false }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
|
|
|
@ -22,6 +22,7 @@ use agb::{
|
||||||
input::{Button, ButtonController, Tri},
|
input::{Button, ButtonController, Tri},
|
||||||
interrupt::VBlank,
|
interrupt::VBlank,
|
||||||
rng,
|
rng,
|
||||||
|
sound::mixer::Frequency,
|
||||||
};
|
};
|
||||||
use generational_arena::Arena;
|
use generational_arena::Arena;
|
||||||
use sfx::Sfx;
|
use sfx::Sfx;
|
||||||
|
@ -2205,7 +2206,7 @@ fn game_with_level(gba: &mut agb::Gba) {
|
||||||
let vblank = agb::interrupt::VBlank::get();
|
let vblank = agb::interrupt::VBlank::get();
|
||||||
vblank.wait_for_vblank();
|
vblank.wait_for_vblank();
|
||||||
|
|
||||||
let mut mixer = gba.mixer.mixer();
|
let mut mixer = gba.mixer.mixer(Frequency::Hz18157);
|
||||||
mixer.enable();
|
mixer.enable();
|
||||||
|
|
||||||
let mut sfx = sfx::Sfx::new(&mut mixer);
|
let mut sfx = sfx::Sfx::new(&mut mixer);
|
||||||
|
|
Loading…
Reference in a new issue