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",
|
"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",
|
||||||
|
|||||||
@@ -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"]
|
||||||
|
|||||||
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::{
|
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)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
fn main() {
|
struct JackHandler<C>
|
||||||
/*
|
where
|
||||||
* Start the JACK backend and make it ready to play sounds
|
C: Consumer<Item = f32>,
|
||||||
*
|
{
|
||||||
*/
|
out: jack::Port<jack::AudioOut>,
|
||||||
let (client, _status) =
|
consumer: C,
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
// 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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user