use core::cell::RefCell;
use core::marker::PhantomData;
use core::num::NonZero;
use alloc::boxed::Box;
use alloc::vec::Vec;
use critical_section::{CriticalSection, Mutex};
use super::hw::LeftOrRight;
use super::{Frequency, hw};
use super::{SoundChannel, SoundPriority};
use crate::{
InternalAllocator,
dma::dma_copy16,
fixnum::{Num, num},
interrupt::{InterruptHandler, add_interrupt_handler},
timer::Divider,
timer::Timer,
};
macro_rules! add_mono_fn {
($name:ident) => {
fn $name(
sample_data: *const u8,
sample_buffer: *mut i32,
buffer_size: usize,
restart_amount: Num<u32, 8>,
channel_length: usize,
current_pos: Num<u32, 8>,
playback_speed: Num<u32, 8>,
mul_amount: i32,
) -> Num<u32, 8>;
};
}
unsafe extern "C" {
fn agb_rs__mixer_add_stereo(
sound_data: *const u8,
sound_buffer: *mut Num<i16, 4>,
volume: Num<i16, 4>,
buffer_size: usize,
);
fn agb_rs__mixer_add_stereo_first(
sound_data: *const u8,
sound_buffer: *mut Num<i16, 4>,
volume: Num<i16, 4>,
buffer_size: usize,
);
fn agb_rs__mixer_collapse(
sound_buffer: *mut i8,
input_buffer: *const Num<i16, 4>,
num_samples: usize,
);
add_mono_fn!(agb_rs__mixer_add_mono_loop_first);
add_mono_fn!(agb_rs__mixer_add_mono_loop);
add_mono_fn!(agb_rs__mixer_add_mono_first);
add_mono_fn!(agb_rs__mixer_add_mono);
}
pub struct Mixer<'gba> {
interrupt_timer: Timer,
_interrupt_handler: InterruptHandler,
buffer: raw_box::RawBoxDrop<MixerBuffer, InternalAllocator>,
channels: [Option<SoundChannel>; 8],
indices: [u32; 8],
frequency: Frequency,
working_buffer: Box<[Num<i16, 4>], InternalAllocator>,
temp_storage: Box<[u8], InternalAllocator>,
fifo_timer: Timer,
phantom: PhantomData<&'gba ()>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ChannelId(usize, NonZero<u32>);
impl Mixer<'_> {
pub(super) fn new(frequency: Frequency) -> Self {
let buffer =
raw_box::RawBoxDrop::new(Box::new_in(MixerBuffer::new(frequency), InternalAllocator));
let fifo_timer = unsafe { Timer::new(0) };
let mut interrupt_timer = unsafe { Timer::new(1) };
interrupt_timer
.set_cascade(true)
.set_divider(Divider::Divider1)
.set_interrupt(true)
.set_overflow_amount(frequency.buffer_size() as u16);
struct SendPtr<T>(*const T);
unsafe impl<T> Send for SendPtr<T> {}
unsafe impl<T> Sync for SendPtr<T> {}
let ptr_for_interrupt_handler = SendPtr(&*buffer);
let interrupt_handler = unsafe {
add_interrupt_handler(interrupt_timer.interrupt(), move |cs| {
let _ = &ptr_for_interrupt_handler;
(*ptr_for_interrupt_handler.0).swap(cs);
})
};
let mut working_buffer =
Vec::with_capacity_in(frequency.buffer_size() * 2, InternalAllocator);
working_buffer.resize(frequency.buffer_size() * 2, 0.into());
let mut temp_storage =
Vec::with_capacity_in(frequency.buffer_size() * 3 / 2 + 1, InternalAllocator);
temp_storage.resize(temp_storage.capacity(), 0);
let mut result = Self {
frequency,
buffer,
channels: Default::default(),
indices: Default::default(),
interrupt_timer,
_interrupt_handler: interrupt_handler,
working_buffer: working_buffer.into_boxed_slice(),
temp_storage: temp_storage.into_boxed_slice(),
fifo_timer,
phantom: PhantomData,
};
result.enable();
result
}
fn enable(&mut self) {
hw::set_timer_counter_for_frequency_and_enable(
&mut self.fifo_timer,
self.frequency.frequency(),
);
hw::set_sound_control_register_for_mixer();
self.interrupt_timer.set_enabled(true);
}
pub fn frame(&mut self) {
if !self.buffer.should_calculate() {
return;
}
self.buffer.write_channels(
&mut self.working_buffer,
&mut self.temp_storage,
self.channels.iter_mut().flatten(),
);
}
pub fn play_sound(&mut self, new_channel: SoundChannel) -> Option<ChannelId> {
for (i, channel) in self.channels.iter_mut().enumerate() {
if let Some(some_channel) = channel
&& !some_channel.is_done
{
continue;
}
channel.replace(new_channel);
let generation = self.next_index(i);
return Some(ChannelId(i, generation));
}
if new_channel.priority == SoundPriority::Low {
return None; }
for (i, channel) in self.channels.iter_mut().enumerate() {
if channel.as_ref().unwrap().priority == SoundPriority::High {
continue;
}
channel.replace(new_channel);
let generation = self.next_index(i);
return Some(ChannelId(i, generation));
}
panic!("Cannot play more than 8 sounds at once");
}
fn next_index(&mut self, i: usize) -> NonZero<u32> {
self.indices[i] = self.indices[i].wrapping_add(1);
if self.indices[i] == 0 {
self.indices[i] += 1;
}
NonZero::new(self.indices[i]).expect("Should be bigger than 0")
}
pub fn channel(&mut self, id: &ChannelId) -> Option<&'_ mut SoundChannel> {
if let Some(channel) = &mut self.channels[id.0]
&& self.indices[id.0] == id.1.into()
&& !channel.is_done
{
return Some(channel);
}
None
}
}
struct SoundBuffer(Box<[i8], InternalAllocator>);
impl SoundBuffer {
fn new(frequency: Frequency) -> Self {
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 {
frequency: Frequency,
state: Mutex<RefCell<MixerBufferState>>,
}
struct MixerBufferState {
active_buffer: usize,
playing_buffer: usize,
buffers: [SoundBuffer; 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) != self.playing_buffer
}
fn playing_advanced(&mut self) -> *const i8 {
self.playing_buffer = mod3_estimate(self.playing_buffer + 1);
self.buffers[self.playing_buffer].0.as_ptr()
}
fn active_advanced(&mut self) -> *mut i8 {
self.active_buffer = mod3_estimate(self.active_buffer + 1);
self.buffers[self.active_buffer].0.as_mut_ptr()
}
}
impl MixerBuffer {
fn new(frequency: Frequency) -> Self {
MixerBuffer {
state: Mutex::new(RefCell::new(MixerBufferState {
active_buffer: 0,
playing_buffer: 0,
buffers: [
SoundBuffer::new(frequency),
SoundBuffer::new(frequency),
SoundBuffer::new(frequency),
],
})),
frequency,
}
}
fn should_calculate(&self) -> bool {
critical_section::with(|cs| self.state.borrow_ref_mut(cs).should_calculate())
}
fn swap(&self, cs: CriticalSection) {
let buffer = self.state.borrow_ref_mut(cs).playing_advanced();
let left_buffer = buffer;
let right_buffer = unsafe { buffer.add(self.frequency.buffer_size()) };
hw::enable_dma_for_sound(left_buffer, LeftOrRight::Left);
hw::enable_dma_for_sound(right_buffer, LeftOrRight::Right);
}
fn write_channels<'a>(
&self,
working_buffer: &mut [Num<i16, 4>],
temp_storage: &mut [u8],
channels: impl Iterator<Item = &'a mut SoundChannel>,
) {
let mut channels = channels
.filter(|channel| !channel.is_done && channel.volume != 0.into() && channel.is_playing);
if let Some(channel) = channels.next() {
if channel.is_stereo {
self.write_stereo(channel, working_buffer, true);
} else {
self.write_mono(channel, working_buffer, temp_storage, true);
}
} else {
working_buffer.fill(0.into());
}
for channel in channels {
if channel.is_stereo {
self.write_stereo(channel, working_buffer, false);
} else {
self.write_mono(channel, working_buffer, temp_storage, false);
}
}
let write_buffer =
critical_section::with(|cs| self.state.borrow_ref_mut(cs).active_advanced());
unsafe {
agb_rs__mixer_collapse(
write_buffer,
working_buffer.as_ptr(),
self.frequency.buffer_size(),
);
}
}
fn write_stereo(
&self,
channel: &mut SoundChannel,
working_buffer: &mut [Num<i16, 4>],
is_first: bool,
) {
if (channel.pos + 2 * self.frequency.buffer_size() as u32).floor()
>= channel.data.len() as u32
{
if channel.should_loop {
channel.pos = channel.restart_point * 2;
} else {
channel.is_done = true;
if is_first {
working_buffer.fill(0.into());
}
return;
}
}
unsafe {
if is_first {
agb_rs__mixer_add_stereo_first(
channel.data.as_ptr().add(channel.pos.floor() as usize),
working_buffer.as_mut_ptr(),
channel.volume.change_base(),
self.frequency.buffer_size(),
);
} else {
agb_rs__mixer_add_stereo(
channel.data.as_ptr().add(channel.pos.floor() as usize),
working_buffer.as_mut_ptr(),
channel.volume.change_base(),
self.frequency.buffer_size(),
);
}
}
channel.pos += 2 * self.frequency.buffer_size() as u32;
}
fn write_mono(
&self,
channel: &mut SoundChannel,
working_buffer: &mut [Num<i16, 4>],
temp_storage: &mut [u8],
is_first: bool,
) {
let right_amount = ((channel.panning + 1) / 2) * channel.volume;
let left_amount = ((-channel.panning + 1) / 2) * channel.volume;
let right_amount: Num<i16, 4> = right_amount.change_base();
let left_amount: Num<i16, 4> = left_amount.change_base();
let channel_len = Num::<u32, 8>::new(channel.data.len() as u32);
let working_buffer_i32: &mut [i32] = unsafe {
core::slice::from_raw_parts_mut(
working_buffer.as_mut_ptr().cast(),
working_buffer.len() / 2,
)
};
let mul_amount =
((left_amount.to_raw() as i32) << 16) | (right_amount.to_raw() as i32 & 0x0000ffff);
let playback_buffer =
playback_buffer::PlaybackBuffer::new(channel, self.frequency, temp_storage);
macro_rules! call_mono_fn {
($fn_name:ident) => {
channel.pos = unsafe {
$fn_name(
playback_buffer.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,
)
}
};
}
match (is_first, channel.should_loop) {
(true, true) => call_mono_fn!(agb_rs__mixer_add_mono_loop_first),
(false, true) => call_mono_fn!(agb_rs__mixer_add_mono_loop),
(true, false) => {
call_mono_fn!(agb_rs__mixer_add_mono_first);
channel.is_done = channel.pos >= channel_len;
}
(false, false) => {
call_mono_fn!(agb_rs__mixer_add_mono);
channel.is_done = channel.pos >= channel_len;
}
}
}
}
mod playback_buffer {
use super::*;
pub(super) enum PlaybackBuffer<'a> {
TempStorage(&'a [u8], usize),
Rom(&'static [u8]),
}
impl<'a> PlaybackBuffer<'a> {
pub(super) fn new(
channel: &SoundChannel,
frequency: Frequency,
temp_storage: &'a mut [u8],
) -> Self {
let channel_len = Num::new(channel.data.len() as u32);
if channel.playback_speed > num!(1.5) && channel.data.len() > temp_storage.len() {
return PlaybackBuffer::Rom(channel.data);
}
let total_to_play =
(channel.playback_speed * frequency.buffer_size() as u32).floor() + 1;
if channel_len <= total_to_play.into() {
assert!((channel_len.floor() as usize / 2) * 2 <= temp_storage.len());
unsafe {
dma_copy16(
channel.data.as_ptr().cast(),
temp_storage.as_mut_ptr().cast(),
channel_len.floor() as usize / 2,
);
}
PlaybackBuffer::TempStorage(temp_storage, 0)
} else if channel.pos + total_to_play > channel_len {
PlaybackBuffer::Rom(channel.data)
} else {
assert!((total_to_play as usize / 2 + 1) * 2 <= temp_storage.len());
unsafe {
dma_copy16(
channel.data[channel.pos.floor() as usize..].as_ptr().cast(),
temp_storage.as_mut_ptr().cast(),
total_to_play as usize / 2 + 1,
);
}
PlaybackBuffer::TempStorage(temp_storage, channel.pos.floor() as usize)
}
}
pub(super) fn as_ptr(&self) -> *const u8 {
match self {
PlaybackBuffer::TempStorage(items, offset) => {
items.as_ptr().wrapping_byte_sub(*offset)
}
PlaybackBuffer::Rom(items) => items.as_ptr(),
}
}
}
}
mod raw_box {
use core::ops::Deref;
use alloc::boxed::Box;
pub struct RawBoxDrop<T, A: Clone + alloc::alloc::Allocator>(*mut T, A);
impl<T, A: Clone + alloc::alloc::Allocator> RawBoxDrop<T, A> {
pub fn new(inner: Box<T, A>) -> Self {
let (ptr, allocator) = Box::into_raw_with_allocator(inner);
Self(ptr, allocator)
}
}
impl<T, A: Clone + alloc::alloc::Allocator> Deref for RawBoxDrop<T, A> {
type Target = T;
fn deref(&self) -> &Self::Target {
unsafe { &*self.0 }
}
}
impl<T, A: Clone + alloc::alloc::Allocator> Drop for RawBoxDrop<T, A> {
fn drop(&mut self) {
unsafe { Box::from_raw_in(self.0, self.1.clone()) };
}
}
}
#[cfg(test)]
mod test {
use crate::fixnum::num;
use alloc::vec;
use super::*;
#[test_case]
fn collapse_should_correctly_reduce_size_of_input(_: &mut crate::Gba) {
#[repr(align(4))]
struct AlignedNumbers<const N: usize>([Num<i16, 4>; N]);
let input = &AlignedNumbers([
num!(10.0),
num!(10.0),
num!(5.0),
num!(5.0),
num!(-10.0),
num!(-10.5),
num!(-5.9),
num!(-5.2),
num!(0.0),
num!(1.1),
num!(2.2),
num!(3.3),
num!(155.4),
num!(-230.5),
num!(400.6),
num!(-700.7),
num!(10.0),
num!(10.0),
num!(5.0),
num!(5.0),
num!(-10.0),
num!(-10.5),
num!(-5.9),
num!(-5.2),
num!(0.0),
num!(1.1),
num!(2.2),
num!(3.3),
num!(155.4),
num!(-230.5),
num!(400.6),
num!(-700.7),
]);
let input = &input.0;
let mut output_buffer = vec![0i32; input.len() / 4];
unsafe {
agb_rs__mixer_collapse(
output_buffer.as_mut_ptr().cast(),
input.as_ptr(),
input.len() / 2,
);
}
assert_eq!(
output_buffer
.iter()
.flat_map(|x| x.to_le_bytes())
.map(|x| x as i8)
.collect::<alloc::vec::Vec<_>>(),
&[
10, 5, -10, -6, 0, 2, 127, 127, 10, 5, -10, -6, 0, 2, 127, 127, 10, 5, -11, -6, 1,
3, -128, -128, 10, 5, -11, -6, 1, 3, -128, -128
]
);
}
#[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));
}
#[test_case]
fn channel_id_none_niche(_: &mut crate::Gba) {
assert_eq!(size_of::<Option<ChannelId>>(), size_of::<ChannelId>());
}
}