Another pointless stuff
This commit is contained in:
7
Cargo.lock
generated
7
Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -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"]
|
||||
|
||||
249
src/main.rs
249
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<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)
|
||||
}
|
||||
});
|
||||
}
|
||||
//
|
||||
//
|
||||
|
||||
fn main() {
|
||||
/*
|
||||
* Start the JACK backend and make it ready to play sounds
|
||||
*
|
||||
*/
|
||||
let (client, _status) =
|
||||
jack::Client::new("rust_jack_sine", jack::ClientOptions::default()).unwrap();
|
||||
let mut out_a = client
|
||||
.register_port("sine_out", jack::AudioOut::default())
|
||||
.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();
|
||||
|
||||
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);
|
||||
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() {
|
||||
let path = env::args()
|
||||
.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) =
|
||||
jack::Client::new("rust_jack_sine", jack::ClientOptions::default()).unwrap();
|
||||
|
||||
// 2. register port
|
||||
let out = client
|
||||
.register_port("sine_out", jack::AudioOut::default())
|
||||
.unwrap();
|
||||
|
||||
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<f32> {
|
||||
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<f64> {
|
||||
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<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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user