Get the desktop player actually playing music

This commit is contained in:
Gwilym Inzani 2024-06-05 15:33:56 +01:00
parent c863b94f49
commit 3a755107aa
5 changed files with 281 additions and 11 deletions

View file

@ -298,12 +298,7 @@ impl<M: Mixer> TrackerChannel<M> {
channel.stop(); channel.stop();
} }
let mut new_channel = M::SoundChannel::new(match sample.data { let mut new_channel = M::SoundChannel::new(&sample.data);
alloc::borrow::Cow::Borrowed(data) => data,
alloc::borrow::Cow::Owned(_) => {
unimplemented!("Must use borrowed COW data for tracker")
}
});
new_channel.volume( new_channel.volume(
(sample.volume.change_base() * global_settings.volume) (sample.volume.change_base() * global_settings.volume)
@ -528,8 +523,13 @@ fn main(gba: agb::Gba) -> ! {
#[cfg(feature = "agb")] #[cfg(feature = "agb")]
impl SoundChannel for agb::sound::mixer::SoundChannel { impl SoundChannel for agb::sound::mixer::SoundChannel {
fn new(data: &'static [u8]) -> Self { fn new(data: &alloc::borrow::Cow<'static, [u8]>) -> Self {
Self::new(data) Self::new(match data {
alloc::borrow::Cow::Borrowed(data) => data,
alloc::borrow::Cow::Owned(_) => {
unimplemented!("Must use borrowed COW data for tracker")
}
})
} }
fn stop(&mut self) { fn stop(&mut self) {

View file

@ -3,7 +3,7 @@
use agb_fixnum::Num; use agb_fixnum::Num;
pub trait SoundChannel { pub trait SoundChannel {
fn new(data: &'static [u8]) -> Self; fn new(data: &alloc::borrow::Cow<'static, [u8]>) -> Self;
fn stop(&mut self); fn stop(&mut self);
fn pause(&mut self) -> &mut Self; fn pause(&mut self) -> &mut Self;

View file

@ -13,3 +13,5 @@ agb_tracker = { version = "0.20.2", path = "../agb-tracker", default-features =
agb_fixnum = { version = "0.20.2", path = "../../agb-fixnum" } agb_fixnum = { version = "0.20.2", path = "../../agb-fixnum" }
xmrs = "0.6" xmrs = "0.6"
cpal = "0.15"

View file

@ -1,3 +1,66 @@
fn main() { use std::{env, error::Error, fs, path::Path, sync::mpsc};
println!("Hello, world!");
use cpal::{
traits::{DeviceTrait, HostTrait, StreamTrait},
SampleFormat, SampleRate,
};
use mixer::Mixer;
use xmrs::{module::Module, xm::xmmodule::XmModule};
mod mixer;
fn main() -> Result<(), Box<dyn Error>> {
let args: Vec<String> = env::args().collect();
let file_path = &args[1];
let module = load_module_from_file(Path::new(file_path))?;
let track = Box::leak::<'static>(Box::new(agb_xm_core::parse_module(&module)));
let mut mixer = Mixer::new();
let mut tracker = agb_tracker::Tracker::new(track);
let host = cpal::default_host();
let device = host
.default_output_device()
.expect("Failed to open output device");
let mut supported_configs = device.supported_output_configs()?;
let config = supported_configs
.find_map(|config| {
if config.channels() == 2 && config.sample_format() == SampleFormat::F32 {
return config.try_with_sample_rate(SampleRate(32768));
}
None
})
.expect("Could not produce valid config");
let (tx, rx) = mpsc::sync_channel(32768 * 3);
let stream = device.build_output_stream(
&config.into(),
move |data: &mut [f32], _| {
for val in data.iter_mut() {
*val = rx.recv().unwrap();
}
},
|err| eprintln!("Error on audio stream {err}"),
None,
)?;
stream.play()?;
loop {
tracker.step(&mut mixer);
for (l, r) in mixer.frame() {
tx.send((l as f32) / 128.0)?;
tx.send((r as f32) / 128.0)?;
}
}
}
fn load_module_from_file(xm_path: &Path) -> Result<Module, Box<dyn Error>> {
let file_content = fs::read(xm_path)?;
Ok(XmModule::load(&file_content)?.to_module())
} }

View file

@ -0,0 +1,205 @@
use agb_fixnum::Num;
use std::{borrow::Cow, num::Wrapping};
const BUFFER_SIZE: usize = 560;
#[derive(Default)]
pub struct Mixer {
channels: [Option<SoundChannel>; 8],
indices: [Wrapping<usize>; 8],
}
impl Mixer {
pub fn new() -> Self {
Self {
channels: Default::default(),
indices: Default::default(),
}
}
pub fn frame(&mut self) -> Vec<(i8, i8)> {
let channels =
self.channels.iter_mut().flatten().filter(|channel| {
!channel.is_done && channel.volume != 0.into() && channel.is_playing
});
let mut buffer = vec![Num::new(0); BUFFER_SIZE * 2];
for channel in channels {
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 mut playback_speed = channel.playback_speed;
while playback_speed >= channel_len - channel.restart_point {
playback_speed -= channel_len;
}
let restart_subtract = channel_len - channel.restart_point;
let mut current_pos = channel.pos;
for i in 0..BUFFER_SIZE {
let val = channel.data[current_pos.floor() as usize] as i8 as i16;
buffer[2 * i] += left_amount * val;
buffer[2 * i + 1] += right_amount * val;
current_pos += playback_speed;
if current_pos >= channel_len {
if channel.should_loop {
current_pos -= restart_subtract;
} else {
channel.is_done = true;
break;
}
}
}
channel.pos = current_pos;
}
let mut ret = Vec::with_capacity(BUFFER_SIZE);
for i in 0..BUFFER_SIZE {
let l = buffer[2 * i].floor();
let r = buffer[2 * i + 1].floor();
ret.push((
l.clamp(i8::MIN as i16, i8::MAX as i16) as i8,
r.clamp(i8::MIN as i16, i8::MAX as i16) as i8,
));
}
ret
}
}
pub struct SoundChannel {
data: Cow<'static, [u8]>,
pos: Num<u32, 8>,
should_loop: bool,
restart_point: Num<u32, 8>,
is_playing: bool,
playback_speed: Num<u32, 8>,
volume: Num<i16, 8>,
panning: Num<i16, 8>, // between -1 and 1
is_done: bool,
}
impl std::fmt::Debug for SoundChannel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SoundChannel")
.field("pos", &self.pos)
.field("should_loop", &self.should_loop)
.field("restart_point", &self.restart_point)
.field("is_playing", &self.is_playing)
.field("playback_speed", &self.playback_speed)
.field("volume", &self.volume)
.field("panning", &self.panning)
.field("is_done", &self.is_done)
.finish()
}
}
impl SoundChannel {
fn new(data: Cow<'static, [u8]>) -> Self {
Self {
data: data.clone(),
pos: 0.into(),
should_loop: false,
playback_speed: 1.into(),
is_playing: true,
panning: 0.into(),
is_done: false,
volume: 1.into(),
restart_point: 0.into(),
}
}
}
pub struct SoundChannelId(usize, Wrapping<usize>);
impl agb_tracker::SoundChannel for SoundChannel {
fn new(data: &Cow<'static, [u8]>) -> Self {
Self::new(data.clone())
}
fn stop(&mut self) {
self.is_done = true;
}
fn pause(&mut self) -> &mut Self {
self.is_playing = false;
self
}
fn resume(&mut self) -> &mut Self {
self.is_playing = true;
self
}
fn should_loop(&mut self) -> &mut Self {
self.should_loop = true;
self
}
fn volume(&mut self, value: impl Into<Num<i16, 8>>) -> &mut Self {
self.volume = value.into();
self
}
fn restart_point(&mut self, value: impl Into<Num<u32, 8>>) -> &mut Self {
self.restart_point = value.into();
self
}
fn playback(&mut self, playback_speed: impl Into<Num<u32, 8>>) -> &mut Self {
self.playback_speed = playback_speed.into();
self
}
fn panning(&mut self, panning: impl Into<Num<i16, 8>>) -> &mut Self {
self.panning = panning.into();
self
}
}
impl agb_tracker::Mixer for Mixer {
type ChannelId = SoundChannelId;
type SoundChannel = SoundChannel;
fn channel(&mut self, channel_id: &Self::ChannelId) -> Option<&mut Self::SoundChannel> {
if let Some(channel) = &mut self.channels[channel_id.0] {
if self.indices[channel_id.0] == channel_id.1 && !channel.is_done {
return Some(channel);
}
}
None
}
fn play_sound(&mut self, new_channel: Self::SoundChannel) -> Option<Self::ChannelId> {
for (i, channel) in self.channels.iter_mut().enumerate() {
if let Some(channel) = channel {
if !channel.is_done {
continue;
}
}
channel.replace(new_channel);
self.indices[i] += 1;
return Some(SoundChannelId(i, self.indices[i]));
}
None
}
}