beginnings of prog architecture

This commit is contained in:
TerraMaster85 2024-12-14 11:03:28 -05:00
commit c47228e4d9
9 changed files with 1492 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

2
.rustfmt.toml Normal file
View file

@ -0,0 +1,2 @@
max_width = 80
edition = "2024"

1265
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

13
Cargo.toml Normal file
View file

@ -0,0 +1,13 @@
[package]
name = "sang"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "1.0.94"
cpal = "0.15.3"
log = "0.4.22"
ringbuf = "0.4.7"
simple_logger = { version = "^5.0.0", features = ["colors", "threads", "timestamps"] }
thiserror = "2.0.6"
tokio = { version = '^1', features = ["full"] }

110
src/audio_receiver/mod.rs Normal file
View file

@ -0,0 +1,110 @@
use anyhow::{Context, Result};
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use cpal::{FromSample, Sample, SampleFormat};
use log::{error, info, trace, warn};
use tokio::sync::mpsc::Sender;
use crate::msg::PcmFrameMessage;
pub fn audio_receiver_main(
tx_for_demod: Sender<PcmFrameMessage>,
cpal_input: cpal::Device,
) -> Result<()> {
trace!("audio_receiver started");
let cpal_input_config = cpal_input.supported_input_configs()?.max_by_key(|cfg| {
match cfg.sample_format() {
SampleFormat::U8 => 10,
SampleFormat::I8 => 11,
SampleFormat::U16 => 20,
SampleFormat::I16 => 21,
SampleFormat::U32 => 30,
SampleFormat::I32 => 31,
SampleFormat::F32 => 40,
SampleFormat::F64 => 50,
SampleFormat::U64 => 60,
SampleFormat::I64 => 61,
x => { warn!("Backend offered unknown sample format {x}",); -999 }
}
}).expect("input device has no supported configurations available")
.with_max_sample_rate();
info!(
"input samples are {} @ {}hz",
&cpal_input_config.sample_format(),
&cpal_input_config.sample_rate().0,
);
let failed_pcm_take = move |e| {
warn!("dropped some PCM data on input stream?! {}", e);
};
let input_stream = match cpal_input_config.sample_format() {
cpal::SampleFormat::U8 => cpal_input.build_input_stream(
&cpal_input_config.into(),
move |pcm, _: &_| take_input_pcm::<u8>(pcm, &tx_for_demod),
failed_pcm_take,
None,
)?,
cpal::SampleFormat::I8 => cpal_input.build_input_stream(
&cpal_input_config.into(),
move |pcm, _: &_| take_input_pcm::<i8>(pcm, &tx_for_demod),
failed_pcm_take,
None,
)?,
cpal::SampleFormat::I16 => cpal_input.build_input_stream(
&cpal_input_config.into(),
move |pcm, _: &_| take_input_pcm::<i16>(pcm, &tx_for_demod),
failed_pcm_take,
None,
)?,
cpal::SampleFormat::I32 => cpal_input.build_input_stream(
&cpal_input_config.into(),
move |pcm, _: &_| take_input_pcm::<i32>(pcm, &tx_for_demod),
failed_pcm_take,
None,
)?,
cpal::SampleFormat::F32 => cpal_input.build_input_stream(
&cpal_input_config.into(),
move |pcm, _: &_| take_input_pcm::<f32>(pcm, &tx_for_demod),
failed_pcm_take,
None,
)?,
cpal::SampleFormat::F64 => cpal_input.build_input_stream(
&cpal_input_config.into(),
move |pcm, _: &_| take_input_pcm::<f64>(pcm, &tx_for_demod),
failed_pcm_take,
None,
)?,
cpal::SampleFormat::U64 => cpal_input.build_input_stream(
&cpal_input_config.into(),
move |pcm, _: &_| take_input_pcm::<u64>(pcm, &tx_for_demod),
failed_pcm_take,
None,
)?,
cpal::SampleFormat::I64 => cpal_input.build_input_stream(
&cpal_input_config.into(),
move |pcm, _: &_| take_input_pcm::<i64>(pcm, &tx_for_demod),
failed_pcm_take,
None,
)?,
sample_format => {
Err(anyhow::Error::msg(
format!(
"unsupported sample format '{}'", sample_format)
))?
}
};
input_stream.play()?;
Ok(())
}
fn take_input_pcm<T>(pcm: &[T], channel: &Sender<PcmFrameMessage>) where T: Sample + std::fmt::Debug, i64: FromSample<T> {
if channel.try_send(pcm.iter().map(|&n| n.to_sample::<i64>()).collect()).is_err() {
error!("demodulator PCM data queue is full???");
}
}

16
src/dsp_inb/mod.rs Normal file
View file

@ -0,0 +1,16 @@
use anyhow::{Context, Result};
use log::{error, info, trace, warn};
use tokio::sync::mpsc::{Receiver, UnboundedSender};
use crate::msg::{RawIpDataMessage, PcmFrameMessage};
pub async fn dsp_inb_main(
tx_for_ip_inb: UnboundedSender<RawIpDataMessage>,
rx_for_demod: Receiver<PcmFrameMessage>,
) -> Result<()> {
trace!("pcm_inb task started");
Ok(())
}

76
src/main.rs Normal file
View file

@ -0,0 +1,76 @@
#![cfg_attr(
debug_assertions,
allow(dead_code, unused_imports, unused_variables)
)]
use anyhow::{Context, Result};
use cpal::traits::HostTrait;
use log::{trace};
use tokio::sync::mpsc::{channel, unbounded_channel};
//use ringbuf::
mod msg;
use crate::msg::{PcmFrameMessage, RawIpDataMessage};
mod audio_receiver;
use crate::audio_receiver::audio_receiver_main;
mod dsp_inb;
use crate::dsp_inb::dsp_inb_main;
//#[derive(argh::FromArgs)]
//struct Cli {}
#[tokio::main]
async fn main() -> Result<()> {
simple_logger::init_with_level(log::Level::Trace)
.expect("logger init failure???");
let cpal_host = cpal::default_host();
let cpal_input = cpal_host.default_input_device().context(
"no audio input could be found; is a microphone installed?"
)?;
let cpal_output = cpal_host.default_output_device().context(
"no audio output could be found; is a sound card installed?",
)?;
let (tx_for_demod, rx_for_demod) = channel::<PcmFrameMessage>(1024);
let (tx_for_ip_inb, rx_for_ip_inb) =
unbounded_channel::<RawIpDataMessage>();
let (tx_for_mod, rx_for_mod) = unbounded_channel::<RawIpDataMessage>();
let (tx_for_wire, rx_for_wire) = unbounded_channel::<PcmFrameMessage>();
trace!("starting listener for audio on wire");
audio_receiver_main(tx_for_demod, cpal_input)
.context("listener for audio on wire has failed to start");
let mut tasks = tokio::task::JoinSet::new();
let task_dsp_inb = tasks.spawn(async move {
trace!("starting inbound audio demodulator");
dsp_inb_main(tx_for_ip_inb, rx_for_demod).await
});
//let task_tun_junction = tasks.spawn(async move {
// trace!("Starting tun manager");
// tun_junction_main(tx_for_mod, rx_for_ip_inb).await
//});
//let task_dsp_outb = tasks.spawn(async move {
// trace!("Starting outbound audio modulator");
// dsp_outb_main(tx_for_wire, rx_for_mod).await
//});
//let task_audio_sender = tasks.spawn(async move {
// trace!("Starting audio transmitter");
// audio_sender_main(rx_for_mod, cpal_output).await
//});
while let Some(task_result) = tasks.join_next().await {
task_result??
}
Ok(())
}

2
src/msg.rs Normal file
View file

@ -0,0 +1,2 @@
pub type PcmFrameMessage = Vec<i64>;
pub type RawIpDataMessage = Vec<u8>;

7
src/wire-fmt/mod.rs Normal file
View file

@ -0,0 +1,7 @@
pub enum SpecialCapability {
pub StereoOut = 1,
pub StereoIn = 2,
pub Telephone = 3,
pub Typewriter = 4,
pub IpDce = 5,
}