Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
7.5 kB · 198 lines
Rust
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199use core::f32;use core::ops::{AddAssign, IndexMut};
use crate::audio_processing::playback::{PlaybackState, PlaybackStatus};use crate::audio_processing::sample::SamplePlayer;use crate::audio_processing::{Frame, Interpolation};use crate::project::song::Song;use crate::sample::Sample;use crate::{OutputConfig, ToWorkerMsg};use alloc::boxed::Box;use alloc::vec;use simple_left_right::Reader;
pub struct LiveAudio<StreamData> { song: Reader<Song>, playback_state: Option<PlaybackState>, live_note: Option<SamplePlayer>, manager: rtrb::Consumer<ToWorkerMsg>, state_sender: rt_write_lock::Writer<(Option<PlaybackStatus>, StreamData)>, config: OutputConfig,
buffer: Box<[Frame]>,}
impl<S> LiveAudio<S> { /// Not realtime safe. pub(crate) fn new( song: Reader<Song>, manager: rtrb::Consumer<ToWorkerMsg>, state_sender: rt_write_lock::Writer<(Option<PlaybackStatus>, S)>, config: OutputConfig, ) -> Self { Self { song, playback_state: None, live_note: None, manager, state_sender, config, buffer: vec![Frame::default(); usize::try_from(config.buffer_size).unwrap() * 2].into(), } }
#[cfg_attr(feature = "rtsan", sanitize(realtime = "nonblocking"))] fn send_state(&mut self, stream_data: S) { let playback_state = self.playback_state.as_ref().map(|s| s.get_status()); let mut write_guard = self.state_sender.write(); // make this more granular once the state includes AudioData or other allocated data *write_guard = (playback_state, stream_data); }
#[cfg_attr(feature = "rtsan", sanitize(realtime = "nonblocking"))] /// returns true if work was done fn fill_internal_buffer(&mut self, len: usize) -> bool { // the output buffer should be smaller than the internal buffer let buffer = &mut self.buffer[..len];
let song = self.song.lock();
// process manager events while let Ok(event) = self.manager.pop() { match event { ToWorkerMsg::StopPlayback => self.playback_state = None, ToWorkerMsg::Playback(settings) => { self.playback_state = PlaybackState::new(&song, self.config.sample_rate, settings); } ToWorkerMsg::PlayEvent(note) => { if let Some(sample) = &song.samples[usize::from(note.sample_instr)] { let sample_player = SamplePlayer::new( Sample::clone(&sample.1), sample.0, // this at some point was divided by two, if i ever figure out why, maybe put it back self.config.sample_rate, note.note, ); self.live_note = Some(sample_player); } } ToWorkerMsg::StopLiveNote => self.live_note = None, ToWorkerMsg::SetInterpolation(i) => self.config.interpolation = i, } } if self.live_note.is_none() && self.playback_state.is_none() { // no processing todo return false; }
// clear buffer from past run // only happens if there is work todo buffer.fill(Frame::default());
// process live_note if let Some(live_note) = &mut self.live_note { fn process_note<const INTERPOLATION: u8>( buffer: &mut [Frame], note: &mut SamplePlayer, ) { // constant power panning leads to a lower volume. As the live_note isn't channel panned // increase the volume. If i change the pan law i use i need to change this here. // // This is sin(PI/4) or cos(PI/4). // TODO: sin or cos const replace this #[expect(clippy::excessive_precision)] const CENTER_VOL: f32 = 0.707106769084930419921875; buffer .iter_mut() .zip(note.iter::<{ INTERPOLATION }>()) .for_each(|(buf, note)| buf.add_assign(note * CENTER_VOL)); } match self.config.interpolation { Interpolation::Nearest => { process_note::<{ Interpolation::Nearest as u8 }>(buffer, live_note) } Interpolation::Linear => { process_note::<{ Interpolation::Linear as u8 }>(buffer, live_note) } Interpolation::Quadratic => { process_note::<{ Interpolation::Quadratic as u8 }>(buffer, live_note) } }
if live_note.check_position().is_break() { self.live_note = None; } }
// process song playback if let Some(playback) = &mut self.playback_state { fn process_playback<const INTERPOLATION: u8>( buffer: &mut [Frame], playback: &mut PlaybackState, song: &Song, ) { buffer .iter_mut() .zip(playback.iter::<{ INTERPOLATION }>(song)) .for_each(|(buf, note)| buf.add_assign(note)); } match self.config.interpolation { Interpolation::Nearest => { process_playback::<{ Interpolation::Nearest as u8 }>(buffer, playback, &song) } Interpolation::Linear => { process_playback::<{ Interpolation::Linear as u8 }>(buffer, playback, &song) } Interpolation::Quadratic => { process_playback::<{ Interpolation::Quadratic as u8 }>(buffer, playback, &song) } }
if playback.is_done() { self.playback_state = None; } }
true }
pub fn render(&mut self, required_len: usize, stream_data: S) -> Option<&[Frame]> { assert!(required_len <= self.buffer.len()); let worked = self.fill_internal_buffer(required_len); self.send_state(stream_data); if worked { Some(&self.buffer[..required_len]) } else { None } }
// // unsure wether i want to use this or untyped_callback // // also relevant when cpal gets made into a generic that maybe this gets useful // pub fn get_typed(mut self) -> impl FnMut(usize, S) -> Option<&[Frame]> { // move |required_len, stream_data| { // assert!(self.buffer.len() > required_len); // self.send_state(stream_data);
// if self.fill_internal_buffer(required_len) { // return Some(&self.buffer[..required_len]); // } else { // None // } // } // }}
// only used for testing// if not testing is unused#[allow(dead_code)]fn sine(output: &mut [[f32; 2]], sample_rate: f32) { let mut sample_clock = 0f32; for frame in output { sample_clock = (sample_clock + 1.) % sample_rate; let value = (sample_clock * 440. * 2. * f32::consts::PI / sample_rate).sin(); *frame.index_mut(0) = value; *frame.index_mut(1) = value; }}