8 Commits

Author SHA1 Message Date
824939672b Another pointless stuff
Some checks failed
ci/woodpecker/push/code_tests Pipeline failed
ci/woodpecker/push/pre_commit_test Pipeline was successful
2025-12-27 20:54:30 +01:00
2ebf5d1ea2 Some sounds are already produced
Some checks failed
ci/woodpecker/push/code_tests Pipeline failed
ci/woodpecker/push/pre_commit_test Pipeline was successful
2025-12-24 14:02:36 +01:00
a827afd872 Trying to get into jack
Some checks failed
ci/woodpecker/push/code_tests Pipeline failed
ci/woodpecker/push/pre_commit_test Pipeline was successful
2025-12-24 13:57:56 +01:00
535727268d Trying to get into jack
Some checks failed
ci/woodpecker/push/code_tests Pipeline failed
ci/woodpecker/push/pre_commit_test Pipeline was successful
2025-12-24 13:30:15 +01:00
29181f9c76 Update plugins
Some checks failed
ci/woodpecker/push/code_tests Pipeline failed
ci/woodpecker/push/pre_commit_test Pipeline was successful
2025-12-23 19:26:36 +01:00
0a043e10f1 Nothig
Some checks failed
ci/woodpecker/push/code_tests Pipeline failed
ci/woodpecker/push/pre_commit_test Pipeline was successful
2025-12-22 09:45:40 +01:00
6bdb38bcee Something stupid
Some checks failed
ci/woodpecker/push/pre_commit_test Pipeline was successful
ci/woodpecker/push/code_tests Pipeline failed
2025-12-18 17:33:00 +01:00
Nikolai Rodionov
49e4f05b5e Try starting monolith
Some checks failed
ci/woodpecker/push/code_tests Pipeline failed
ci/woodpecker/push/pre_commit_test Pipeline was successful
2025-12-18 16:03:54 +01:00
3 changed files with 1629 additions and 2 deletions

1400
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,3 +4,20 @@ version = "0.1.0"
edition = "2024"
[dependencies]
clap = { version = "4.5.53", features = ["derive"] }
jack = { version = "0.13.3" }
pulseaudio = { version = "0.3.1", optional = true}
alsa = { version = "0.10.0", optional = true}
symphonia = { version = "0.5.5", features = ["mp3"] }
coreaudio-rs = { version = "0.13.0", optional = true }
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"]
alsa = ["dep:alsa"]
coreaudio = ["dep:coreaudio-rs"]
cpal = ["dep:cpal"]

View File

@@ -1,3 +1,213 @@
fn main() {
println!("Hello, world!");
//! Sine wave generator with frequency configuration exposed through standard
//! input.
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)
}
});
}
//
//
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.
if let Err(err) = active_client.deactivate() {
eprintln!("JACK exited with error: {err}");
};
}
/// 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
}
}