From 991849b32acf83dd14a5096540bb053d2572502a Mon Sep 17 00:00:00 2001 From: Wavy Harp Date: Sun, 7 May 2023 23:04:53 -0600 Subject: initial import currently everything is very tied to alsa and my system, for the moment you'll need to manually change the device names and maybe channels/period_size in src/main.rs, src/bin/smolguitar.rs and src/bin/loopbacker.rs, i'll fix that and add runtime period size/updater allocation soon, (and probably make a cpal backend as well so it can work on other platforms), but doing this initial commit to play around with stereo for now~ --- src/buf.rs | 244 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 244 insertions(+) create mode 100644 src/buf.rs (limited to 'src/buf.rs') diff --git a/src/buf.rs b/src/buf.rs new file mode 100644 index 0000000..fb4a6dd --- /dev/null +++ b/src/buf.rs @@ -0,0 +1,244 @@ +use crate::proc::{Procer, ProcerData, Update}; +use crate::notes::{Note, NoteValue, ProcerNote}; +use crate::outputers::{Outputer, Outputers, SimpleOutputer}; +use crate::rfft::RFftProcer; +use realfft::FftNum; +use rustfft::num_traits::float::Float; +use alsa::{Direction, ValueOr}; +use alsa::pcm::{PCM, HwParams, Format, Access, State}; +use derive_more::Display; +use std::fmt::Debug; +use std::io::Write; +//use std::collections::VecDeque; + +pub enum Procers { + Rfft(RFftProcer), + // XXX: if i add a fir filter that doesn't need a buffer i think + // i can just spec it as Fir12Tet(Fir12TetProcer) or similar + // here +} + +#[derive(Display)] +#[display(fmt="StaticBuffer(rate={}, psize={}, channels={}, opsize={}, adev=(name={}), outdev=(name={:?}), cperiods={}, operiods={})", rate, period_size, channels, out_period_size, adev_name, outdev_name, capture_periods, output_periods)] +pub struct StaticBuffer<'nl, I: Default + Debug + Clone + FftNum + Float + NoteValue, const US: usize, const BS: usize> { + pub rate: u32, + pub period_size: usize, + pub channels: u32, + pub out_period_size: usize, + pub procers: Vec<(Procers, ProcerData<'nl, I>)>, + pub adev_name: String, + pub adev: PCM, + pub outdev_name: Option, + pub outdev: Option, + pub outputer: Outputers, + pub capture_periods: u32, + pub output_periods: u32, +} + +impl<'nl, I: Default + Debug + Clone + FftNum + Float + From + NoteValue, const US: usize, const BS: usize> StaticBuffer<'nl, I, US, BS> { + pub fn new(rate: u32, channels: u32, procers: Vec<(Procers, ProcerData<'nl, I>)>, adev_name: String, outdev_name: Option, outputer: Outputers) -> Self { + let mut adev = PCM::new(&adev_name, Direction::Capture, true).unwrap(); + let mut capture_periods = 0; + { + let hwp = HwParams::any(&adev).unwrap(); + hwp.set_channels(channels).unwrap(); + //hwp.set_rate_resample(false); + hwp.set_rate(rate, ValueOr::Nearest).unwrap(); + hwp.set_format(Format::s16()).unwrap(); + hwp.set_access(Access::MMapInterleaved).unwrap(); + hwp.set_period_size(US as i64, ValueOr::Nearest).unwrap(); + hwp.set_periods(8, ValueOr::Nearest).unwrap(); + // TODO: directly using the ALSA ring buffer and copying directly from it instead of + // going through the Update/VecDeque might be interesting in the future, i think it'd + // require changing the const sizes to runtime determined sizes though, so future + // changes to try~ + adev.hw_params(&hwp).unwrap(); + capture_periods = hwp.get_periods().unwrap(); + println!("capture periods: {}", capture_periods); + } + let (outdev, out_period_size, output_periods) = match &outdev_name { + None => (None, 0, 0), + Some(od_name) => { + let mut outdev = PCM::new(&od_name, Direction::Playback, true).unwrap(); + let mut output_periods = 0; + let mut out_period_size = 0; + { + let hwp = HwParams::any(&outdev).unwrap(); + hwp.set_channels(channels).unwrap(); + hwp.set_rate(rate, ValueOr::Nearest).unwrap(); + hwp.set_format(Format::s16()).unwrap(); + hwp.set_access(Access::RWInterleaved).unwrap(); + //hwp.set_period_size(US as i64, ValueOr::Nearest).unwrap(); + //hwp.set_periods(8, ValueOr::Nearest).unwrap(); + outdev.hw_params(&hwp).unwrap(); + output_periods = hwp.get_periods().unwrap(); + out_period_size = hwp.get_period_size().unwrap() as usize; + println!("output periods: {}", output_periods); + } + (Some(outdev), out_period_size, output_periods) + } + }; + return Self { + rate, procers, adev_name, adev, outdev_name, outdev, + period_size: US, out_period_size: out_period_size, + channels: channels, + outputer, + capture_periods, output_periods, + } + } + + pub fn capture_loop(&mut self) { + let empty: Vec = vec![0i16; US * self.channels as usize]; + //let mut line_str: String = format!("\r{:80}", ""); + let in_io = self.adev.io_i16().unwrap(); + let out_io_opt = { + match &self.outdev { + None => (None), + Some(outdev) => { + //self.adev.link(&outdev).unwrap(); + let out_io = outdev.io_i16().unwrap(); + // queue up an empty period in the outdev so we can sync between in and out devices + // with a 4 period latency + for _i in 0..std::cmp::min(4, self.output_periods/2) { + out_io.writei(&empty).unwrap(); + } + outdev.start();//.unwrap(); + println!("output available: {}", outdev.avail().unwrap()); + Some(out_io) + } + } + }; + self.adev.start();//.unwrap(); + //let vt: I = I::from(112); + let conv_scale: I = >::from(i16::MAX); + println!("input available: {}", self.adev.avail_update().unwrap()); + let mut latentframes = 0; + let mut unstarted = false; + loop { + /*println!("output available: {}", self.outdev.avail().unwrap()); + println!("input available: {}", self.adev.avail_update().unwrap());*/ + self.adev.wait(None).unwrap(); + //println!("a"); + // should be called right before mmap_begin according to: + // https://www.alsa-project.org/alsa-doc/alsa-lib/group___p_c_m___direct.html#ga6d4acf42de554d4d1177fb035d484ea4 + let avail_frames = self.adev.avail_update().unwrap(); + in_io.mmap(US, |in_slice| { + let size = in_slice.len(); + //println!("slice length: {}", size); + if size == 0 { + println!("warning: passed a 0-sized buffer from mmap\n"); + return 0; + } + let stride = self.channels as usize; + if size != (US * stride) { + println!("warning: passed a {} sized buffer from mmap\n\n", size); + return 0; + } + assert_eq!(size, US * stride); + let (written, oa) = match &out_io_opt { + Some(out_io) => { + let wri = out_io.writei(&in_slice).unwrap(); + assert_eq!(wri, (size/stride)); + let oaa = self.outdev.as_ref().unwrap().avail().unwrap(); + (wri, oaa) + } + None => (size/stride, 0i64), + }; + let ia = self.adev.avail_update().unwrap(); + let total_outbuf: i64 = oa - ia; + /*print!("\rtotal: {}, out: {}, in: {}", total_outbuf, oa, ia); + std::io::stdout().flush().unwrap();*/ + //return written; + //let constrained_slice = in_slice[0..US*stride]; + let mut update: Update = [I::default(); US]; + // copy over the left channel data (even indexes) + // TODO/XXX: see if this uses SIMD or needs optimizing :o + // using iter and map/function that does strides would prob also work well :o + // gotta scale this from i16::MAX to float, alsa + // seems to divide by i16::MAX, not sure if there's a better way to do this + for i in 0..US { + update[i] = >::from(in_slice[i*stride]) / conv_scale; + }; + // hmm, i only need to use 1 procer here for displaying, not sure what to do with + // the rest :o + //for (procer, pdata) in self.procers { } + let (ref mut procer, ref mut pdata) = self.procers.first_mut().unwrap(); + let mut processed = false; + { + let mut pnotes = pdata.pnotes.0.as_mut_slice(); + //let processed = true; + processed = procer.process_data(&update, pnotes); + } + if processed { + self.outputer.handle_pnotes(&mut pdata.pnotes); + //let mut position = 1; + // TODO: maybe copy, inplace probably works for now though + /*pnotes.sort_unstable_by(|pn1, pn2| (-pn1.2).partial_cmp(&-pn2.2).unwrap()); + print!("\r{:-80}", pdata.pnotes); + std::io::stdout().flush();*/ + } + //let mut position = 0; + //let mut remaining = in_slice.len(); + //let mut remain_frames = remaining/(self.channels as usize); + //let mut handled = false; + //self.outdev.wait(None).unwrap(); + //println!("wrote {} frames", written); + /*while handled == false { + self.outdev.wait(None).unwrap(); + out_io.mmap(remain_frames, |out_slice| { + let outlen = out_slice.len(); + println!("out slice len: {}", out_slice.len()); + if outlen == 0 { + return 0; + } + out_slice.clone_from_slice(&in_slice); + handled = true; + return out_slice.len()/(self.channels as usize); + }).unwrap(); + }*/ + /*if latentframes > 0 { + latentframes -= 1; + } else if unstarted { + unstarted = false; + println!("starting outdev"); + self.outdev.start().unwrap(); + }*/ + return in_slice.len()/(self.channels as usize); + }).unwrap(); + } + } + //fn new( +} + +/*impl StaticBuffer<'nl, B> { +}*/ + +//impl<'nl, B> StaticBuffer<'nl, A> { + //pub fn fill_initial(&self, + //pub fn new(rate: usize, period_size: usize, out_period_size: usize, procers: Vec<(Procers, ProcerData<'nl>)>, +//} + + +impl Procer for Procers { + fn get_size(&self) -> usize { + match self { + Procers::Rfft(rfp) => rfp.get_size(), + } + } + fn get_frequency(&self) -> usize { + match self { + Procers::Rfft(rfp) => rfp.get_frequency(), + } + } + fn make_pnotes<'nl>(&self, notes: &'nl [Note]) -> Vec> { + match self { + Procers::Rfft(rfp) => rfp.make_pnotes(notes), + } + } + fn process_data(&mut self, input: &Update, notes: &mut [ProcerNote]) -> bool { + match self { + Procers::Rfft(rfp) => rfp.process_data(input, notes), + } + } +} + -- cgit v1.1