Merge pull request #232 from gwilymk/add-32khz-frequency-option

Add 32khz frequency option
This commit is contained in:
Gwilym Kuiper 2022-06-11 17:32:05 +01:00 committed by GitHub
commit 46260c085a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 247 additions and 84 deletions

View file

@ -19,6 +19,7 @@ proc-macro = true
[features]
freq18157 = []
freq32768 = []
[dependencies]
hound = "3.4"

View file

@ -12,10 +12,14 @@ use std::{
};
use syn::parse_macro_input;
#[cfg(not(feature = "freq18157"))]
#[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");
#[proc_macro]
pub fn include_wav(input: TokenStream) -> TokenStream {

View file

@ -17,6 +17,7 @@ debug = true
[features]
default = []
freq18157 = ["agb_sound_converter/freq18157"]
freq32768 = ["agb_sound_converter/freq32768"]
[dependencies]
bitflags = "1"
@ -31,3 +32,7 @@ rustc-hash = { version = "1", default-features = false }
[package.metadata.docs.rs]
default-target = "thumbv6m-none-eabi"
targets = []
[[example]]
name = "mixer_32768"
required-features = ["freq32768"]

Binary file not shown.

View file

@ -0,0 +1,47 @@
#![no_std]
#![no_main]
use agb::sound::mixer::SoundChannel;
use agb::{include_wav, Gba};
// Music - "Crazy glue" by Josh Woodward, free download at http://joshwoodward.com
const LET_IT_IN: &[u8] = include_wav!("examples/JoshWoodward-CrazyGlue.wav");
#[agb::entry]
fn main(mut gba: Gba) -> ! {
let vblank_provider = agb::interrupt::VBlank::get();
let timer_controller = gba.timers.timers();
let mut timer = timer_controller.timer2;
timer.set_enabled(true);
let mut mixer = gba.mixer.mixer();
mixer.enable();
let mut channel = SoundChannel::new(LET_IT_IN);
channel.stereo();
mixer.play_sound(channel).unwrap();
let _interrupt = mixer.setup_interrupt_handler();
let mut frame_counter = 0i32;
loop {
vblank_provider.wait_for_vblank();
let before_mixing_cycles = timer.value();
mixer.frame();
let after_mixing_cycles = timer.value();
frame_counter = frame_counter.wrapping_add(1);
if frame_counter % 128 == 0 {
let total_cycles = after_mixing_cycles.wrapping_sub(before_mixing_cycles) as u32;
let percent = (total_cycles * 100) / 280896;
agb::println!(
"Took {} cycles to calculate mixer ~= {}% of total frame",
total_cycles,
percent
);
}
}
}

View file

@ -14,8 +14,7 @@ fn main(mut gba: Gba) -> ! {
let mut input = ButtonController::new();
let vblank_provider = agb::interrupt::VBlank::get();
let mut timers = gba.timers.timers();
let mut mixer = gba.mixer.mixer(&mut timers.timer0);
let mut mixer = gba.mixer.mixer();
mixer.enable();
let channel = SoundChannel::new(DEAD_CODE);

View file

@ -11,11 +11,11 @@ const LET_IT_IN: &[u8] = include_wav!("examples/JoshWoodward-LetItIn.wav");
fn main(mut gba: Gba) -> ! {
let vblank_provider = agb::interrupt::VBlank::get();
let mut timer_controller = gba.timers.timers();
let mut timer = timer_controller.timer1;
let timer_controller = gba.timers.timers();
let mut timer = timer_controller.timer2;
timer.set_enabled(true);
let mut mixer = gba.mixer.mixer(&mut timer_controller.timer0);
let mut mixer = gba.mixer.mixer();
mixer.enable();
let mut channel = SoundChannel::new(LET_IT_IN);

View file

@ -28,6 +28,8 @@ const FIFOB_DEST_ADDR: u32 = 0x0400_00a4;
const SOUND_CONTROL: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_0082) };
const SOUND_CONTROL_X: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_0084) };
const SOUND_BIAS: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_0088) };
const DMA_CONTROL_SETTING_FOR_SOUND: u16 = {
let dest_fixed: u16 = 2 << 5; // dest addr control = fixed
let repeat: u16 = 1 << 9;
@ -89,6 +91,9 @@ pub(super) fn set_sound_control_register_for_mixer() {
// master sound enable
SOUND_CONTROL_X.set(1 << 7);
// Set the sound bias PWM resampling rate to 8bit at 65536Hz (default for most games)
SOUND_BIAS.set(SOUND_BIAS.get() | 1 << 14);
}
pub(super) fn set_timer_counter_for_frequency_and_enable(timer: &mut Timer, frequency: i32) {

View file

@ -192,6 +192,7 @@ SWAP_SIGN .req r11
.endm
1:
.rept 4
load_sample r3, r12
load_sample r5, r6
@ -211,8 +212,9 @@ SWAP_SIGN .req r11
str r3, [r0, r4] @ *(r0 + (r4 = SOUND_BUFFER_SIZE)) = r3
str r12, [r0], #4 @ *r0 = r12; r0 += 4
.endr
subs r2, r2, #4 @ r2 -= 4
subs r2, r2, #16 @ r2 -= 16
bne 1b @ loop if not 0
pop {r4-r11}

View file

@ -5,7 +5,6 @@ pub use sw_mixer::ChannelId;
pub use sw_mixer::Mixer;
use crate::fixnum::Num;
use crate::timer::Timer;
#[non_exhaustive]
pub struct MixerController {}
@ -15,8 +14,8 @@ impl MixerController {
MixerController {}
}
pub fn mixer<'a>(&mut self, timer: &'a mut Timer) -> Mixer<'a> {
Mixer::new(timer)
pub fn mixer(&mut self) -> Mixer {
Mixer::new()
}
}

View file

@ -1,8 +1,18 @@
use core::cell::RefCell;
use bare_metal::{CriticalSection, Mutex};
use super::hw;
use super::hw::LeftOrRight;
use super::{SoundChannel, SoundPriority};
use crate::fixnum::Num;
use crate::timer::Timer;
use crate::syscall::cpu_fast_fill_i8;
use crate::{
fixnum::Num,
interrupt::free,
interrupt::{add_interrupt_handler, InterruptHandler},
timer::Divider,
timer::Timer,
};
// Defined in mixer.s
extern "C" {
@ -19,40 +29,58 @@ extern "C" {
fn agb_rs__mixer_collapse(sound_buffer: *mut i8, input_buffer: *const Num<i16, 4>);
}
pub struct Mixer<'a> {
pub struct Mixer {
buffer: MixerBuffer,
channels: [Option<SoundChannel>; 8],
indices: [i32; 8],
timer: &'a mut Timer,
timer: Timer,
}
pub struct ChannelId(usize, i32);
impl<'a> Mixer<'a> {
pub(super) fn new(timer: &'a mut Timer) -> Self {
Mixer {
impl Mixer {
pub(super) fn new() -> Self {
Self {
buffer: MixerBuffer::new(),
channels: Default::default(),
indices: Default::default(),
timer,
timer: unsafe { Timer::new(0) },
}
}
pub fn enable(&mut self) {
hw::set_timer_counter_for_frequency_and_enable(self.timer, SOUND_FREQUENCY);
hw::set_timer_counter_for_frequency_and_enable(&mut self.timer, constants::SOUND_FREQUENCY);
hw::set_sound_control_register_for_mixer();
}
pub fn frame(&mut self) {
self.buffer.clear();
self.buffer
.write_channels(self.channels.iter_mut().flatten());
#[cfg(not(feature = "freq32768"))]
pub fn after_vblank(&mut self) {
free(|cs| self.buffer.swap(cs));
}
pub fn after_vblank(&mut self) {
self.buffer.swap();
/// Note that if you set up an interrupt handler, you should not call `after_vblank` any more
/// You are still required to call `frame`
pub fn setup_interrupt_handler(&self) -> InterruptHandler<'_> {
let mut timer1 = unsafe { Timer::new(1) };
timer1
.set_cascade(true)
.set_divider(Divider::Divider1)
.set_interrupt(true)
.set_overflow_amount(constants::SOUND_BUFFER_SIZE as u16)
.set_enabled(true);
add_interrupt_handler(timer1.interrupt(), move |cs| self.buffer.swap(cs))
}
pub fn frame(&mut self) {
if !self.buffer.should_calculate() {
return;
}
self.buffer
.write_channels(self.channels.iter_mut().flatten());
}
pub fn play_sound(&mut self, new_channel: SoundChannel) -> Option<ChannelId> {
@ -96,17 +124,25 @@ impl<'a> Mixer<'a> {
}
}
// I've picked one frequency that works nicely. But there are others that work nicely
// which we may want to consider in the future: http://deku.gbadev.org/program/sound1.html
#[cfg(not(feature = "freq18157"))]
const SOUND_FREQUENCY: i32 = 10512;
#[cfg(not(feature = "freq18157"))]
const SOUND_BUFFER_SIZE: usize = 176;
// These work perfectly with swapping the buffers every vblank
// 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")]
const SOUND_FREQUENCY: i32 = 18157;
#[cfg(feature = "freq18157")]
const SOUND_BUFFER_SIZE: usize = 304;
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" {
@ -114,18 +150,55 @@ fn set_asm_buffer_size() {
}
unsafe {
agb_rs__buffer_size = SOUND_BUFFER_SIZE;
agb_rs__buffer_size = constants::SOUND_BUFFER_SIZE;
}
}
#[repr(C, align(4))]
struct SoundBuffer([i8; SOUND_BUFFER_SIZE * 2]);
struct SoundBuffer([i8; constants::SOUND_BUFFER_SIZE * 2]);
impl Default for SoundBuffer {
fn default() -> Self {
Self([0; constants::SOUND_BUFFER_SIZE * 2])
}
}
struct MixerBuffer {
buffer1: SoundBuffer, // alternating bytes left and right channels
buffer2: SoundBuffer,
buffers: [SoundBuffer; 3],
buffer_1_active: bool,
state: Mutex<RefCell<MixerBufferState>>,
}
struct MixerBufferState {
active_buffer: usize,
playing_buffer: usize,
}
/// Only returns a valid result if 0 <= x <= 3
const fn mod3_estimate(x: usize) -> usize {
match x & 0b11 {
0 => 0,
1 => 1,
2 => 2,
3 => 0,
_ => unreachable!(),
}
}
impl MixerBufferState {
fn should_calculate(&self) -> bool {
mod3_estimate(self.active_buffer + 1) != mod3_estimate(self.playing_buffer)
}
fn playing_advanced(&mut self) -> usize {
self.playing_buffer = mod3_estimate(self.playing_buffer + 1);
self.playing_buffer
}
fn active_advanced(&mut self) -> usize {
self.active_buffer = mod3_estimate(self.active_buffer + 1);
self.active_buffer
}
}
impl MixerBuffer {
@ -133,28 +206,33 @@ impl MixerBuffer {
set_asm_buffer_size();
MixerBuffer {
buffer1: SoundBuffer([0; SOUND_BUFFER_SIZE * 2]),
buffer2: SoundBuffer([0; SOUND_BUFFER_SIZE * 2]),
buffers: Default::default(),
buffer_1_active: true,
state: Mutex::new(RefCell::new(MixerBufferState {
active_buffer: 0,
playing_buffer: 0,
})),
}
}
fn swap(&mut self) {
let (left_buffer, right_buffer) = self.write_buffer().split_at(SOUND_BUFFER_SIZE);
fn should_calculate(&self) -> bool {
free(|cs| self.state.borrow(*cs).borrow().should_calculate())
}
fn swap(&self, cs: &CriticalSection) {
let buffer = self.state.borrow(*cs).borrow_mut().playing_advanced();
let (left_buffer, right_buffer) = self.buffers[buffer]
.0
.split_at(constants::SOUND_BUFFER_SIZE);
hw::enable_dma_for_sound(left_buffer, LeftOrRight::Left);
hw::enable_dma_for_sound(right_buffer, LeftOrRight::Right);
self.buffer_1_active = !self.buffer_1_active;
}
fn clear(&mut self) {
self.write_buffer().fill(0);
}
fn write_channels<'a>(&mut self, channels: impl Iterator<Item = &'a mut SoundChannel>) {
let mut buffer: [Num<i16, 4>; SOUND_BUFFER_SIZE * 2] = [Num::new(0); SOUND_BUFFER_SIZE * 2];
let mut buffer: [Num<i16, 4>; constants::SOUND_BUFFER_SIZE * 2] =
[Num::new(0); constants::SOUND_BUFFER_SIZE * 2];
for channel in channels {
if channel.is_done {
@ -170,7 +248,9 @@ impl MixerBuffer {
let right_amount = ((channel.panning + 1) / 2) * channel.volume;
let left_amount = ((-channel.panning + 1) / 2) * channel.volume;
if (channel.pos + playback_speed * SOUND_BUFFER_SIZE).floor() >= channel.data.len() {
if (channel.pos + playback_speed * constants::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 = 0.into();
@ -199,20 +279,16 @@ impl MixerBuffer {
}
}
channel.pos += playback_speed * SOUND_BUFFER_SIZE;
channel.pos += playback_speed * constants::SOUND_BUFFER_SIZE;
}
let write_buffer = self.write_buffer();
let write_buffer_index = free(|cs| self.state.borrow(*cs).borrow_mut().active_advanced());
let write_buffer = &mut self.buffers[write_buffer_index].0;
cpu_fast_fill_i8(write_buffer, 0);
unsafe {
agb_rs__mixer_collapse(write_buffer.as_mut_ptr(), buffer.as_ptr());
}
}
fn write_buffer(&mut self) -> &mut [i8; SOUND_BUFFER_SIZE * 2] {
if self.buffer_1_active {
&mut self.buffer2.0
} else {
&mut self.buffer1.0
}
}
}

View file

@ -2,7 +2,6 @@ use core::arch::asm;
// use crate::display::object::AffineMatrixAttributes;
#[allow(non_snake_case)]
pub fn halt() {
@ -94,6 +93,9 @@ pub fn arc_tan(n: i16) -> i16 {
"swi 0x09",
in("r0") n,
lateout("r0") result,
lateout("r1") _,
lateout("r2") _,
lateout("r3") _
);
}
result
@ -107,12 +109,36 @@ pub fn arc_tan2(x: i16, y: i32) -> i16 {
in("r0") x,
in("r1") y,
lateout("r0") result,
lateout("r2") _,
lateout("r3") _
);
}
result
}
pub(crate) fn cpu_fast_fill_i8(input: &mut [i8], new_content: i32) {
assert_eq!(
input.len() % (4 * 8),
0,
"Input length must be divisible by 32"
);
let input_ptr = [new_content].as_ptr();
let output_ptr = input.as_mut_ptr();
let length_mode = (1 << 24) | // copy
(input.len() / 4);
unsafe {
asm!(
"swi 0x0c",
in("r0") input_ptr,
in("r1") output_ptr,
in("r2") length_mode,
lateout("r3") _,
);
}
}
// pub fn affine_matrix(
// x_scale: Num<i16, 8>,
// y_scale: Num<i16, 8>,

View file

@ -40,8 +40,6 @@ pub struct Timer {
#[non_exhaustive]
pub struct Timers {
pub timer0: Timer,
pub timer1: Timer,
pub timer2: Timer,
pub timer3: Timer,
}
@ -49,8 +47,6 @@ pub struct Timers {
impl Timers {
pub(crate) unsafe fn new() -> Self {
Self {
timer0: Timer::new(0),
timer1: Timer::new(1),
timer2: Timer::new(2),
timer3: Timer::new(3),
}
@ -58,7 +54,7 @@ impl Timers {
}
impl Timer {
unsafe fn new(timer_number: u16) -> Self {
pub(crate) unsafe fn new(timer_number: u16) -> Self {
let new_timer = Self { timer_number };
new_timer.data_register().set(0);
new_timer.control_register().set(0);
@ -66,32 +62,37 @@ impl Timer {
new_timer
}
pub fn set_overflow_amount(&mut self, n: u16) {
pub fn set_overflow_amount(&mut self, n: u16) -> &mut Self {
let count_up_value = 0u16.wrapping_sub(n);
self.data_register().set(count_up_value);
self
}
pub fn value(&self) -> u16 {
self.data_register().get()
}
pub fn set_divider(&mut self, divider: Divider) {
pub fn set_divider(&mut self, divider: Divider) -> &mut Self {
self.control_register().set_bits(divider.as_bits(), 2, 0);
self
}
pub fn set_enabled(&mut self, enabled: bool) {
pub fn set_enabled(&mut self, enabled: bool) -> &mut Self {
let bit = if enabled { 1 } else { 0 };
self.control_register().set_bits(bit, 1, 7);
self
}
pub fn set_cascade(&mut self, cascade: bool) {
pub fn set_cascade(&mut self, cascade: bool) -> &mut Self {
let bit = if cascade { 1 } else { 0 };
self.control_register().set_bits(bit, 1, 2);
self
}
pub fn set_interrupt(&mut self, interrupt: bool) {
pub fn set_interrupt(&mut self, interrupt: bool) -> &mut Self {
let bit = interrupt as u16;
self.control_register().set_bits(bit, 1, 6);
self
}
fn data_register(&self) -> MemoryMapped<u16> {

View file

@ -809,8 +809,7 @@ fn main(mut agb: agb::Gba) -> ! {
vram.set_background_palettes(tile_sheet::background.palettes);
let object = agb.display.object.get();
let mut timer_controller = agb.timers.timers();
let mut mixer = agb.mixer.mixer(&mut timer_controller.timer0);
let mut mixer = agb.mixer.mixer();
mixer.enable();
let mut music_box = sfx::MusicBox::new();

View file

@ -60,13 +60,13 @@ impl MusicBox {
}
}
pub struct SfxPlayer<'a, 'b> {
mixer: &'a mut Mixer<'b>,
pub struct SfxPlayer<'a> {
mixer: &'a mut Mixer,
frame: i32,
}
impl<'a, 'b> SfxPlayer<'a, 'b> {
pub fn new(mixer: &'a mut Mixer<'b>, music_box: &MusicBox) -> Self {
impl<'a> SfxPlayer<'a> {
pub fn new(mixer: &'a mut Mixer, music_box: &MusicBox) -> Self {
SfxPlayer {
mixer,
frame: music_box.frame,

View file

@ -2205,8 +2205,7 @@ fn game_with_level(gba: &mut agb::Gba) {
let vblank = agb::interrupt::VBlank::get();
vblank.wait_for_vblank();
let mut timers = gba.timers.timers();
let mut mixer = gba.mixer.mixer(&mut timers.timer0);
let mut mixer = gba.mixer.mixer();
mixer.enable();
let mut sfx = sfx::Sfx::new(&mut mixer);

View file

@ -27,11 +27,11 @@ const BLUE_SPIRIT: &[u8] = agb::include_wav!("sfx/03 - Blue Spirit (Main Loop).w
pub struct Sfx<'a> {
bgm: Option<ChannelId>,
mixer: &'a mut Mixer<'a>,
mixer: &'a mut Mixer,
}
impl<'a> Sfx<'a> {
pub fn new(mixer: &'a mut Mixer<'a>) -> Self {
pub fn new(mixer: &'a mut Mixer) -> Self {
Self { mixer, bgm: None }
}