Organize codebase
This commit is contained in:
parent
fe14cef7b8
commit
3cc46118ca
@ -3,8 +3,6 @@ name = "audio-logger"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
clap = {url = "https://github.com/clap-rs/clap", features = ["derive"]}
|
||||
cpal= { version = "0.13.5", features = ["jack"] }
|
||||
|
104
audio-logger/src/getters.rs
Normal file
104
audio-logger/src/getters.rs
Normal file
@ -0,0 +1,104 @@
|
||||
use anyhow::anyhow;
|
||||
use cpal::traits::{DeviceTrait, HostTrait};
|
||||
use cpal::*;
|
||||
use std::path::PathBuf;
|
||||
use chrono::prelude::*;
|
||||
use super::*;
|
||||
/// # Get Host
|
||||
///
|
||||
/// Returns the host with the given id if it's available.
|
||||
pub fn get_host(host: HostId) -> Result<Host, anyhow::Error> {
|
||||
Ok(cpal::host_from_id(cpal::available_hosts()
|
||||
.into_iter()
|
||||
.find(|id| *id == host)
|
||||
.ok_or(anyhow!("Requested host device not found"))?
|
||||
)?)
|
||||
}
|
||||
|
||||
/// # Get Device
|
||||
///
|
||||
/// Returns the default input device for the host if it's available.
|
||||
pub fn get_device(host: Host) -> Result<Device, anyhow::Error> {
|
||||
Ok(host.default_input_device()
|
||||
.ok_or(anyhow!("No input device available. Try running `jackd -R -d alsa -d hw:0`",
|
||||
))?)
|
||||
}
|
||||
|
||||
/// # Get Default Config
|
||||
///
|
||||
/// Get the default config for the given device.
|
||||
pub fn get_default_config(device: &Device) -> Result<SupportedStreamConfig, anyhow::Error> {
|
||||
Ok(device.default_input_config()?)
|
||||
}
|
||||
|
||||
/// # Get User Config
|
||||
///
|
||||
/// Overrides certain fields of the default stream config with the user's config.
|
||||
///
|
||||
/// sample_rate: The user's sample rate if it is supported by the device, otherwise the default sample rate.
|
||||
/// channels: The user's number of channels if it is supported by the device, otherwise the default number of channels.
|
||||
/// buffer_size: The user's buffer size if it is supported by the device, otherwise the default buffer size.
|
||||
pub fn get_user_config(sample_rate: u32, channels: u16, buffer_size: u32) -> Result<StreamConfig, anyhow::Error> {
|
||||
if !ALLOWED_SAMPLE_RATES.contains(&sample_rate) {
|
||||
return Err(anyhow!(
|
||||
"Sample rate {} is not supported. Allowed sample rates: {:?}",
|
||||
sample_rate,
|
||||
ALLOWED_SAMPLE_RATES
|
||||
));
|
||||
}
|
||||
if !(channels >= 1 && channels <= MAX_CHANNEL_COUNT) {
|
||||
return Err(anyhow!(
|
||||
"Channel count {} is not supported. Allowed channel counts: 1-{}",
|
||||
channels,
|
||||
MAX_CHANNEL_COUNT
|
||||
));
|
||||
}
|
||||
if !(buffer_size >= MIN_BUFFER_SIZE as u32 && buffer_size <= MAX_BUFFER_SIZE as u32) {
|
||||
return Err(anyhow!(
|
||||
"Buffer size {} is not supported. Allowed buffer sizes: {}-{}",
|
||||
buffer_size,
|
||||
MIN_BUFFER_SIZE,
|
||||
MAX_BUFFER_SIZE
|
||||
));
|
||||
}
|
||||
Ok(StreamConfig {
|
||||
channels,
|
||||
sample_rate: SampleRate(sample_rate),
|
||||
buffer_size: BufferSize::Fixed(buffer_size),
|
||||
})
|
||||
}
|
||||
|
||||
/// # Get WAV Spec
|
||||
///
|
||||
/// Get the WAV spec for the given stream config.
|
||||
pub fn get_wav_spec(default_config: &SupportedStreamConfig, user_config: &StreamConfig) -> Result<hound::WavSpec, anyhow::Error> {
|
||||
Ok(hound::WavSpec {
|
||||
channels: user_config.channels,
|
||||
sample_rate: user_config.sample_rate.0,
|
||||
bits_per_sample: (default_config.sample_format().sample_size() * 8) as u16,
|
||||
sample_format: match default_config.sample_format() {
|
||||
cpal::SampleFormat::U16 => hound::SampleFormat::Int,
|
||||
cpal::SampleFormat::I16 => hound::SampleFormat::Int,
|
||||
cpal::SampleFormat::F32 => hound::SampleFormat::Float,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/// # Get Filename
|
||||
///
|
||||
/// Get the filename for the current recording according to the given format,
|
||||
/// the current date and time, and the name prefix.
|
||||
pub fn get_filename(name: &str, path: &PathBuf) -> Result<String, anyhow::Error> {
|
||||
let now: DateTime<Local> = Local::now();
|
||||
let filename = format!(
|
||||
"{}-{}-{}-{}-{}:{}:{}.wav",
|
||||
name,
|
||||
now.year(),
|
||||
now.month(),
|
||||
now.day(),
|
||||
now.hour(),
|
||||
now.minute(),
|
||||
now.second(),
|
||||
);
|
||||
Ok(path.join(filename).to_str().unwrap().to_string())
|
||||
}
|
54
audio-logger/src/input_handling.rs
Normal file
54
audio-logger/src/input_handling.rs
Normal file
@ -0,0 +1,54 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
use super::*;
|
||||
|
||||
type StreamInterrupt = Arc<(Mutex<bool>, Condvar)>;
|
||||
type BatchInterrupt= Arc<AtomicBool>;
|
||||
|
||||
/// # Interrupts Handling
|
||||
///
|
||||
/// The `Recorder` struct has two interrupt mechanisms:
|
||||
///
|
||||
/// 1. `stream_interrupt` is used to interrupt the stream when the user presses `ctrl+c`.
|
||||
/// 2. `batch_interrupt` is used to interrupt the batch recording when the user presses `ctrl+c`.
|
||||
#[derive(Clone)]
|
||||
pub struct InterruptHandles {
|
||||
batch_interrupt: BatchInterrupt,
|
||||
stream_interrupt: StreamInterrupt,
|
||||
}
|
||||
|
||||
impl InterruptHandles {
|
||||
pub fn new() -> Result<Self, anyhow::Error> {
|
||||
let stream_interrupt = Arc::new((Mutex::new(false), Condvar::new()));
|
||||
let stream_interrupt_cloned = stream_interrupt.clone();
|
||||
|
||||
let batch_interrupt = Arc::new(AtomicBool::new(false));
|
||||
let batch_interrupt_cloned = batch_interrupt.clone();
|
||||
|
||||
ctrlc::set_handler(move || {
|
||||
// Set batch interrupt to true
|
||||
batch_interrupt_cloned.store(true, Ordering::SeqCst);
|
||||
|
||||
// Release the stream
|
||||
let &(ref lock, ref cvar) = &*stream_interrupt_cloned;
|
||||
let mut started = lock.lock().unwrap();
|
||||
*started = true;
|
||||
cvar.notify_one();
|
||||
})?;
|
||||
Ok(Self {
|
||||
batch_interrupt,
|
||||
stream_interrupt,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn stream_wait(&self) {
|
||||
let &(ref lock, ref cvar) = &*self.stream_interrupt;
|
||||
let mut started = lock.lock().unwrap();
|
||||
while !*started {
|
||||
started = cvar.wait(started).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn batch_is_running(&self) -> bool {
|
||||
!self.batch_interrupt.load(Ordering::SeqCst)
|
||||
}
|
||||
}
|
@ -4,6 +4,8 @@
|
||||
mod cli;
|
||||
mod recorder;
|
||||
mod print_configs;
|
||||
mod getters;
|
||||
mod input_handling;
|
||||
|
||||
use clap::Parser;
|
||||
use recorder::{batch_recording, contiguous_recording};
|
||||
|
@ -1,12 +1,11 @@
|
||||
use anyhow::anyhow;
|
||||
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
||||
use cpal::traits::{DeviceTrait, StreamTrait};
|
||||
use cpal::*;
|
||||
use std::fs::File;
|
||||
use std::io::BufWriter;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use chrono::prelude::*;
|
||||
use super::*;
|
||||
use crate::getters::*;
|
||||
use crate::input_handling::*;
|
||||
|
||||
type WriteHandle = Arc<Mutex<Option<hound::WavWriter<BufWriter<File>>>>>;
|
||||
|
||||
@ -22,95 +21,20 @@ pub struct Recorder {
|
||||
current_file: String,
|
||||
}
|
||||
|
||||
fn get_host(host: HostId) -> Result<Host, anyhow::Error> {
|
||||
Ok(cpal::host_from_id(cpal::available_hosts()
|
||||
.into_iter()
|
||||
.find(|id| *id == host)
|
||||
.ok_or(anyhow!("Requested host device not found"))?
|
||||
)?)
|
||||
}
|
||||
|
||||
fn get_device(host: Host) -> Result<Device, anyhow::Error> {
|
||||
Ok(host.default_input_device()
|
||||
.ok_or(anyhow!("No input device available. Try running `jackd -R -d alsa -d hw:0`",
|
||||
))?)
|
||||
}
|
||||
|
||||
fn get_default_config(device: &Device) -> Result<SupportedStreamConfig, anyhow::Error> {
|
||||
Ok(device.default_input_config()?)
|
||||
}
|
||||
|
||||
/// # Get User Config
|
||||
///
|
||||
/// Overrides certain fields of the default stream config with the user's config.
|
||||
///
|
||||
/// sample_rate: The user's sample rate if it is supported by the device, otherwise the default sample rate.
|
||||
/// channels: The user's number of channels if it is supported by the device, otherwise the default number of channels.
|
||||
/// buffer_size: The user's buffer size if it is supported by the device, otherwise the default buffer size.
|
||||
fn get_user_config(sample_rate: u32, channels: u16, buffer_size: u32) -> Result<StreamConfig, anyhow::Error> {
|
||||
if !ALLOWED_SAMPLE_RATES.contains(&sample_rate) {
|
||||
return Err(anyhow!(
|
||||
"Sample rate {} is not supported. Allowed sample rates: {:?}",
|
||||
sample_rate,
|
||||
ALLOWED_SAMPLE_RATES
|
||||
));
|
||||
}
|
||||
if !(channels >= 1 && channels <= MAX_CHANNEL_COUNT) {
|
||||
return Err(anyhow!(
|
||||
"Channel count {} is not supported. Allowed channel counts: 1-{}",
|
||||
channels,
|
||||
MAX_CHANNEL_COUNT
|
||||
));
|
||||
}
|
||||
if !(buffer_size >= MIN_BUFFER_SIZE as u32 && buffer_size <= MAX_BUFFER_SIZE as u32) {
|
||||
return Err(anyhow!(
|
||||
"Buffer size {} is not supported. Allowed buffer sizes: {}-{}",
|
||||
buffer_size,
|
||||
MIN_BUFFER_SIZE,
|
||||
MAX_BUFFER_SIZE
|
||||
));
|
||||
}
|
||||
Ok(StreamConfig {
|
||||
channels,
|
||||
sample_rate: SampleRate(sample_rate),
|
||||
buffer_size: BufferSize::Fixed(buffer_size),
|
||||
})
|
||||
}
|
||||
|
||||
fn get_spec(default_config: &SupportedStreamConfig, user_config: &StreamConfig) -> Result<hound::WavSpec, anyhow::Error> {
|
||||
Ok(hound::WavSpec {
|
||||
channels: user_config.channels,
|
||||
sample_rate: user_config.sample_rate.0,
|
||||
bits_per_sample: (default_config.sample_format().sample_size() * 8) as u16,
|
||||
sample_format: match default_config.sample_format() {
|
||||
cpal::SampleFormat::U16 => hound::SampleFormat::Int,
|
||||
cpal::SampleFormat::I16 => hound::SampleFormat::Int,
|
||||
cpal::SampleFormat::F32 => hound::SampleFormat::Float,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn get_filename(name: &str, path: &PathBuf) -> Result<String, anyhow::Error> {
|
||||
let now: DateTime<Local> = Local::now();
|
||||
let filename = format!(
|
||||
"{}-{}-{}-{}-{}:{}:{}.wav",
|
||||
name,
|
||||
now.year(),
|
||||
now.month(),
|
||||
now.day(),
|
||||
now.hour(),
|
||||
now.minute(),
|
||||
now.second(),
|
||||
);
|
||||
Ok(path.join(filename).to_str().unwrap().to_string())
|
||||
}
|
||||
|
||||
/// # Recorder
|
||||
///
|
||||
/// The `Recorder` struct is used to record audio.
|
||||
///
|
||||
/// Use `init()` to initialize the recorder, `record()` to start a continuous recording,
|
||||
/// and `rec_secs()` to record for a given number of seconds. The Recorder does not
|
||||
/// need to be reinitialized after a recording is stopped. Calling `record()` or
|
||||
/// `rec_secs()` again will start a new recording with a new filename according to
|
||||
/// the time and date.
|
||||
impl Recorder {
|
||||
|
||||
/// Initializes a new recorder.
|
||||
/// # Init
|
||||
///
|
||||
/// Initializes the recorder with the given host, sample rate, channel count, and buffer size.
|
||||
pub fn init(
|
||||
name: String,
|
||||
path: PathBuf,
|
||||
@ -136,7 +60,7 @@ impl Recorder {
|
||||
let user_config = get_user_config(sample_rate, channels, buffer_size)?;
|
||||
|
||||
// Get the hound WAV spec for the user's config.
|
||||
let spec = get_spec(&default_config, &user_config)?;
|
||||
let spec = get_wav_spec(&default_config, &user_config)?;
|
||||
|
||||
Ok(Self {
|
||||
writer: Arc::new(Mutex::new(None)),
|
||||
@ -183,6 +107,10 @@ impl Recorder {
|
||||
Ok(stream)
|
||||
}
|
||||
|
||||
/// # Record
|
||||
///
|
||||
/// Start a continuous recording. The recording will be stopped when the
|
||||
/// user presses `Ctrl+C`.
|
||||
pub fn record(&mut self) -> Result<(), anyhow::Error> {
|
||||
self.init_writer()?;
|
||||
let stream = self.create_stream()?;
|
||||
@ -195,6 +123,10 @@ impl Recorder {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// # Record Seconds
|
||||
///
|
||||
/// Record for a given number of seconds or until the user presses `Ctrl+C`.
|
||||
/// Current batch is finished before stopping.
|
||||
pub fn record_secs(&mut self, secs: u64) -> Result<(), anyhow::Error> {
|
||||
self.init_writer()?;
|
||||
let stream = self.create_stream()?;
|
||||
@ -240,55 +172,3 @@ pub fn contiguous_recording(rec: &mut Recorder) -> Result<(), anyhow::Error> {
|
||||
rec.record()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
type StreamInterrupt = Arc<(Mutex<bool>, Condvar)>;
|
||||
type BatchInterrupt= Arc<AtomicBool>;
|
||||
|
||||
/// # Interrupts Handling
|
||||
///
|
||||
/// The `Recorder` struct has two interrupt mechanisms:
|
||||
///
|
||||
/// 1. `stream_interrupt` is used to interrupt the stream when the user presses `ctrl+c`.
|
||||
/// 2. `batch_interrupt` is used to interrupt the batch recording when the user presses `ctrl+c`.
|
||||
#[derive(Clone)]
|
||||
pub struct InterruptHandles {
|
||||
batch_interrupt: BatchInterrupt,
|
||||
stream_interrupt: StreamInterrupt,
|
||||
}
|
||||
|
||||
impl InterruptHandles {
|
||||
pub fn new() -> Result<Self, anyhow::Error> {
|
||||
let stream_interrupt = Arc::new((Mutex::new(false), Condvar::new()));
|
||||
let stream_interrupt_cloned = stream_interrupt.clone();
|
||||
|
||||
let batch_interrupt = Arc::new(AtomicBool::new(false));
|
||||
let batch_interrupt_cloned = batch_interrupt.clone();
|
||||
|
||||
ctrlc::set_handler(move || {
|
||||
// Set batch interrupt to true
|
||||
batch_interrupt_cloned.store(true, Ordering::SeqCst);
|
||||
|
||||
// Release the stream
|
||||
let &(ref lock, ref cvar) = &*stream_interrupt_cloned;
|
||||
let mut started = lock.lock().unwrap();
|
||||
*started = true;
|
||||
cvar.notify_one();
|
||||
})?;
|
||||
Ok(Self {
|
||||
batch_interrupt,
|
||||
stream_interrupt,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn stream_wait(&self) {
|
||||
let &(ref lock, ref cvar) = &*self.stream_interrupt;
|
||||
let mut started = lock.lock().unwrap();
|
||||
while !*started {
|
||||
started = cvar.wait(started).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn batch_is_running(&self) -> bool {
|
||||
!self.batch_interrupt.load(Ordering::SeqCst)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user