diff --git a/Cargo.lock b/Cargo.lock index 14b20a9..df292b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -86,6 +86,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + [[package]] name = "arrayvec" version = "0.7.6" @@ -1028,6 +1034,7 @@ name = "termix" version = "0.1.0" dependencies = [ "alsa 0.10.0", + "anyhow", "clap", "coreaudio-rs", "cpal", diff --git a/Cargo.toml b/Cargo.toml index 2784adf..7b44fa7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ cpal = { version = "0.16.0", optional = true } ringbuf = "0.4.8" crossbeam = { version = "0.8.4", features = ["crossbeam-channel"] } crossbeam-channel = "0.5.15" +anyhow = "1.0.100" [features] pulseaudio = ["dep:pulseaudio"] diff --git a/src/main.rs b/src/main.rs index fcf8904..b394395 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,49 +1,94 @@ -use std::{io, str::FromStr}; +//! Sine wave generator with frequency configuration exposed through standard +//! input. -use ringbuf::{ - traits::{Consumer, Producer, Split}, - HeapRb, -}; +use crossbeam_channel::bounded; +use jack::{AsyncClient, AudioOut, Client, ProcessHandler, ProcessScope}; +use ringbuf::traits::{Consumer, Producer, Split}; +use ringbuf::HeapRb; +use std::fs::File; +use std::str::FromStr; +use std::thread::{self, sleep}; +use std::time::Duration; +use std::{env, io}; +use symphonia::core::audio::{AudioBufferRef, SampleBuffer, Signal}; +use symphonia::core::codecs::DecoderOptions; +use symphonia::core::formats::FormatOptions; +use symphonia::core::io::MediaSourceStream; +use symphonia::core::meta::MetadataOptions; +use symphonia::core::probe::Hint; +use symphonia::core::units::Time; +const RB_SIZE: usize = 48000 * 2; // ~1 second mono + // + // +fn start_decoder_thread( + mut looper: AudioFileLooper, + mut producer: impl ringbuf::traits::Producer + std::marker::Send + 'static, +) { + thread::spawn(move || { + loop { + let sample = looper.next_sample(); + let _ = producer.try_push(sample); + // Drop if buffer is full (never block) + } + }); +} +// +// + +struct JackHandler +where + C: Consumer, +{ + out: jack::Port, + consumer: C, +} + +impl ProcessHandler for JackHandler +where + C: Consumer + std::marker::Send, +{ + fn process(&mut self, _: &Client, ps: &ProcessScope) -> jack::Control { + let buffer = self.out.as_mut_slice(ps); + + for sample in buffer.iter_mut() { + *sample = self.consumer.try_pop().unwrap_or(0.0); + } + + jack::Control::Continue + } +} fn main() { - /* - * Start the JACK backend and make it ready to play sounds - * - */ + let path = env::args() + .nth(1) + .expect("Usage: cargo run -- "); + let mut looper = AudioFileLooper::open(&path).expect("Brah"); + // Ring buffer + let rb = HeapRb::::new(RB_SIZE); + let (producer, consumer) = rb.split(); + + // Start decoder thread + start_decoder_thread(looper, producer); + // 1. open a client let (client, _status) = jack::Client::new("rust_jack_sine", jack::ClientOptions::default()).unwrap(); - let mut out_a = client + + // 2. register port + let out = client .register_port("sine_out", jack::AudioOut::default()) .unwrap(); - let rb = HeapRb::::new(128); - let (mut prod, mut cons) = rb.split(); - let process_callback = move |_: &jack::Client, ps: &jack::ProcessScope| -> jack::Control { - let out = out_a.as_mut_slice(ps); - let available = cons.pop_slice(out); - out[available..].fill(0.0); - jack::Control::Continue - }; - let process = jack::contrib::ClosureProcessHandler::new(process_callback); - let active_client = client.activate_async((), process).unwrap(); - - active_client - .as_client() - .connect_ports_by_name("rust_jack_sine:sine_out", "system:playback_1") - .unwrap(); - active_client - .as_client() - .connect_ports_by_name("rust_jack_sine:sine_out", "system:playback_2") - .unwrap(); - // processing starts here - - // 5. wait or do some processing while your handler is running in real time. - println!("Enter an integer value to change the frequency of the sine wave."); - while let Some(f) = read_freq() { - println!("Pushing to the prod"); - prod.try_push(f); + let handler = JackHandler { out, consumer }; + let active_client = { + let notification_handler = (); + AsyncClient::new(client, notification_handler, handler) } + .expect("bvrah"); + println!("Running… press Enter to quit"); + let mut input = String::new(); + std::io::stdin().read_line(&mut input).unwrap(); + // 4. Activate the client. Also connect the ports to the system audio. // 6. Optional deactivate. Not required since active_client will deactivate on // drop, though explicit deactivate may help you identify errors in // deactivate. @@ -52,11 +97,117 @@ fn main() { }; } -fn read_freq() -> Option { - Some(10000.0) - //let mut user_input = String::new(); - //match io::stdin().read_line(&mut user_input) { - // Ok(_) => u16::from_str(user_input.trim()).ok().map(|n| n as f32), - // Err(_) => None, - //} +/// Attempt to read a frequency from standard in. Will block until there is +/// user input. `None` is returned if there was an error reading from standard +/// in, or the retrieved string wasn't a compatible u16 integer. +fn read_freq() -> Option { + let mut user_input = String::new(); + match io::stdin().read_line(&mut user_input) { + Ok(_) => u16::from_str(user_input.trim()).ok().map(|n| n as f64), + Err(_) => None, + } +} + +pub struct AudioFileLooper { + format: Box, + decoder: Box, + track_id: u32, + + sample_buf: Vec, + sample_pos: usize, +} +impl AudioFileLooper { + pub fn open(path: &str) -> Result { + let file = File::open(path)?; + let mss = MediaSourceStream::new(Box::new(file), Default::default()); + + let mut hint = Hint::new(); + if let Some(ext) = path.split('.').last() { + hint.with_extension(ext); + } + + let format = symphonia::default::get_probe() + .format( + &hint, + mss, + &FormatOptions::default(), + &MetadataOptions::default(), + )? + .format; + + let track = format + .tracks() + .iter() + .find(|t| t.codec_params.sample_rate.is_some()) + .unwrap(); + let track_id = track.id; + + let decoder = symphonia::default::get_codecs() + .make(&track.codec_params, &DecoderOptions::default())?; + + Ok(Self { + format, + decoder, + track_id: track_id, + sample_buf: Vec::new(), + sample_pos: 0, + }) + } + fn refill_samples(&mut self) -> Result<(), symphonia::core::errors::Error> { + self.sample_buf.clear(); + self.sample_pos = 0; + + loop { + match self.format.next_packet() { + Ok(packet) => { + if packet.track_id() != self.track_id { + continue; + } + + let decoded = self.decoder.decode(&packet)?; + + match decoded { + AudioBufferRef::F32(buf) => { + self.sample_buf.extend_from_slice(buf.chan(0)); + } + + _ => { + let mut sample_buffer = SampleBuffer::::new( + decoded.capacity() as u64, + *decoded.spec(), + ); + sample_buffer.copy_interleaved_ref(decoded); + self.sample_buf.extend_from_slice(sample_buffer.samples()); + } + } + + return Ok(()); + } + + Err(symphonia::core::errors::Error::IoError(_)) => { + // EOF → loop + self.format.seek( + symphonia::core::formats::SeekMode::Accurate, + symphonia::core::formats::SeekTo::Time { + time: Time::new(0, 0.0), + track_id: Some(self.track_id), + }, + )?; + } + + Err(e) => return Err(e), + } + } + } + pub fn next_sample(&mut self) -> f32 { + if self.sample_pos >= self.sample_buf.len() { + if self.refill_samples().is_err() { + return 0.0; + } + } + + let sample = self.sample_buf[self.sample_pos]; + self.sample_pos += 1; + sample + } }