Another pointless stuff
Some checks failed
ci/woodpecker/push/code_tests Pipeline failed
ci/woodpecker/push/pre_commit_test Pipeline was successful

This commit is contained in:
2025-12-27 20:54:30 +01:00
parent 2ebf5d1ea2
commit 824939672b
3 changed files with 202 additions and 43 deletions

7
Cargo.lock generated
View File

@@ -86,6 +86,12 @@ dependencies = [
"windows-sys 0.61.2", "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]] [[package]]
name = "arrayvec" name = "arrayvec"
version = "0.7.6" version = "0.7.6"
@@ -1028,6 +1034,7 @@ name = "termix"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"alsa 0.10.0", "alsa 0.10.0",
"anyhow",
"clap", "clap",
"coreaudio-rs", "coreaudio-rs",
"cpal", "cpal",

View File

@@ -14,6 +14,7 @@ cpal = { version = "0.16.0", optional = true }
ringbuf = "0.4.8" ringbuf = "0.4.8"
crossbeam = { version = "0.8.4", features = ["crossbeam-channel"] } crossbeam = { version = "0.8.4", features = ["crossbeam-channel"] }
crossbeam-channel = "0.5.15" crossbeam-channel = "0.5.15"
anyhow = "1.0.100"
[features] [features]
pulseaudio = ["dep:pulseaudio"] pulseaudio = ["dep:pulseaudio"]

View File

@@ -1,49 +1,94 @@
use std::{io, str::FromStr}; //! Sine wave generator with frequency configuration exposed through standard
//! input.
use ringbuf::{ use crossbeam_channel::bounded;
traits::{Consumer, Producer, Split}, use jack::{AsyncClient, AudioOut, Client, ProcessHandler, ProcessScope};
HeapRb, 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<Item = f32> + 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<C>
where
C: Consumer<Item = f32>,
{
out: jack::Port<jack::AudioOut>,
consumer: C,
}
impl<C> ProcessHandler for JackHandler<C>
where
C: Consumer<Item = f32> + 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() { fn main() {
/* let path = env::args()
* Start the JACK backend and make it ready to play sounds .nth(1)
* .expect("Usage: cargo run -- <audio_file>");
*/ let mut looper = AudioFileLooper::open(&path).expect("Brah");
// Ring buffer
let rb = HeapRb::<f32>::new(RB_SIZE);
let (producer, consumer) = rb.split();
// Start decoder thread
start_decoder_thread(looper, producer);
// 1. open a client
let (client, _status) = let (client, _status) =
jack::Client::new("rust_jack_sine", jack::ClientOptions::default()).unwrap(); 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()) .register_port("sine_out", jack::AudioOut::default())
.unwrap(); .unwrap();
let rb = HeapRb::<f32>::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(); let handler = JackHandler { out, consumer };
let active_client = {
active_client let notification_handler = ();
.as_client() AsyncClient::new(client, notification_handler, handler)
.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);
} }
.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 // 6. Optional deactivate. Not required since active_client will deactivate on
// drop, though explicit deactivate may help you identify errors in // drop, though explicit deactivate may help you identify errors in
// deactivate. // deactivate.
@@ -52,11 +97,117 @@ fn main() {
}; };
} }
fn read_freq() -> Option<f32> { /// Attempt to read a frequency from standard in. Will block until there is
Some(10000.0) /// user input. `None` is returned if there was an error reading from standard
//let mut user_input = String::new(); /// in, or the retrieved string wasn't a compatible u16 integer.
//match io::stdin().read_line(&mut user_input) { fn read_freq() -> Option<f64> {
// Ok(_) => u16::from_str(user_input.trim()).ok().map(|n| n as f32), let mut user_input = String::new();
// Err(_) => None, 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<dyn symphonia::core::formats::FormatReader>,
decoder: Box<dyn symphonia::core::codecs::Decoder>,
track_id: u32,
sample_buf: Vec<f32>,
sample_pos: usize,
}
impl AudioFileLooper {
pub fn open(path: &str) -> Result<Self, symphonia::core::errors::Error> {
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::<f32>::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
}
} }