summaryrefslogtreecommitdiff
path: root/src/buf.rs
diff options
context:
space:
mode:
authorWavy Harp <wavyharp@gmail.com>2023-05-07 23:04:53 -0600
committerKyle McFarland <tfkyle@gmail.com>2023-05-07 23:04:53 -0600
commit991849b32acf83dd14a5096540bb053d2572502a (patch)
tree279b59d75d4ad6081f5242cf77d843ae6b37fc3d /src/buf.rs
downloadrustynotes-991849b32acf83dd14a5096540bb053d2572502a.zip
rustynotes-991849b32acf83dd14a5096540bb053d2572502a.tar.gz
rustynotes-991849b32acf83dd14a5096540bb053d2572502a.tar.bz2
initial importHEADmaster
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~
Diffstat (limited to 'src/buf.rs')
-rw-r--r--src/buf.rs244
1 files changed, 244 insertions, 0 deletions
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<I: Default + Clone + FftNum + Float, const US: usize, const BS: usize> {
+ Rfft(RFftProcer<I, US, BS>),
+ // XXX: if i add a fir filter that doesn't need a buffer i think
+ // i can just spec it as Fir12Tet(Fir12TetProcer<I, US>) 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<I, US, BS>, ProcerData<'nl, I>)>,
+ pub adev_name: String,
+ pub adev: PCM,
+ pub outdev_name: Option<String>,
+ pub outdev: Option<PCM>,
+ pub outputer: Outputers,
+ pub capture_periods: u32,
+ pub output_periods: u32,
+}
+
+impl<'nl, I: Default + Debug + Clone + FftNum + Float + From<i16> + NoteValue, const US: usize, const BS: usize> StaticBuffer<'nl, I, US, BS> {
+ pub fn new(rate: u32, channels: u32, procers: Vec<(Procers<I, US, BS>, ProcerData<'nl, I>)>, adev_name: String, outdev_name: Option<String>, 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<i16> = 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 = <I as From<i16>>::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, US> = [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] = <I as From<i16>>::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<f64>, ProcerData<'nl>)>,
+//}
+
+
+impl<I: Default + Clone + FftNum + Float + NoteValue, const US: usize, const BS: usize> Procer<I, US> for Procers<I, US, BS> {
+ 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<ProcerNote<'nl, I>> {
+ match self {
+ Procers::Rfft(rfp) => rfp.make_pnotes(notes),
+ }
+ }
+ fn process_data(&mut self, input: &Update<I, US>, notes: &mut [ProcerNote<I>]) -> bool {
+ match self {
+ Procers::Rfft(rfp) => rfp.process_data(input, notes),
+ }
+ }
+}
+