use std::slice;
use std::sync::mpsc;
use anyhow::Context;
use log::{debug, error, info};
use crate::common::callback::AudioCallback;
use crate::common::chunk::Chunk;
use crate::common::result::{Error, Result};
use crate::common::source::AudioSource;
use crate::common::stream::{CHANNELS, SAMPLE_RATE, latency};
use crate::platform::platform::link::LinkState;
use crate::platform::windows::link::BLOCKALIGN;
use crate::platform::windows::manager::Command;
const MAX_TIME_MS: u32 = 1000;
#[derive(Debug)]
pub enum Response {
Ready,
Error(Error),
}
pub fn start_stream(
sender: &mpsc::Sender<Response>,
receiver: &mpsc::Receiver<Command>,
sources: &[AudioSource],
callback: &AudioCallback,
frames: usize,
) {
if let Some(mut clock) = link(sender, &sources[0], frames) {
if sources.len() == 1 {
process(sender, receiver, &sources[0], clock.buffer.len(), || {
clock.handle.wait_for_event(MAX_TIME_MS)?;
clock.capture_client
.read_from_device(&mut clock.buffer)
.context("Failed to read audio data")?;
callback(Chunk::from(clock.buffer.as_ref()));
Ok(())
});
} else {
let mut links = Vec::with_capacity(sources.len());
links.push(clock);
for source in &sources[1..] {
if let Some(link) = link(sender, source, frames) {
links.push(link);
} else {
return;
}
}
let mut mixdown = vec![0f32; links[0].buffer.len() / size_of::<f32>()].into_boxed_slice();
process(sender, receiver, &sources[0], links[0].buffer.len(), || {
links[0].handle.wait_for_event(MAX_TIME_MS)?;
for link in &mut links {
link.capture_client
.read_from_device(&mut link.buffer)
.context("Failed to read audio data")?;
}
stereo_mixdown(&links, &mut mixdown);
callback(Chunk::from(mixdown.as_ref()));
Ok(())
});
}
}
}
fn link(
sender: &mpsc::Sender<Response>,
source: &AudioSource,
frames: usize,
) -> Option<LinkState> {
match LinkState::link(source, frames).with_context(|| {
format!("Failed to link to source id={}, name={}", source.id, source.name)
}) {
Ok(link) => Some(link),
Err(e) => {
send_logged!(sender, Response::Error(e));
None
}
}
}
#[inline]
fn process<F>(
sender: &mpsc::Sender<Response>,
receiver: &mpsc::Receiver<Command>,
clock: &AudioSource,
buffer_size: usize,
mut process: F,
) where
F: FnMut() -> Result<()>,
{
send_logged!(sender, Response::Ready);
debug!("using source id={}, name={} as clock", clock.id, clock.name);
info!(
"capturing at rate={}, channels={}, latency={:.2}ms",
SAMPLE_RATE, CHANNELS, latency(buffer_size / BLOCKALIGN)
);
match || -> Result<()> {
loop {
if matches!(receiver.try_recv(), Ok(Command::Unlink)) {
break Ok(());
}
process()?;
}
}() {
Ok(()) => info!("stream stopped"),
Err(e) => error!("stream stopped due to error: {e}"),
}
}
#[inline]
fn stereo_mixdown(links: &[LinkState], mixdown: &mut [f32]) {
for (i, link) in links.iter().enumerate() {
let samples = unsafe {
slice::from_raw_parts(
link.buffer.as_ptr().cast::<f32>(),
link.buffer.len() / size_of::<f32>(),
)
};
if i == 0 {
mixdown.copy_from_slice(samples);
}
else if i == links.len() - 1 {
for (j, sample) in samples.iter().enumerate() {
let sum = mixdown[j] + sample;
mixdown[j] = sum.tanh();
}
}
else {
for (j, sample) in samples.iter().enumerate() {
mixdown[j] += sample;
}
}
}
}