use std::panic::{self, AssertUnwindSafe, Location};
use std::rc::Rc;
use std::sync::Arc;
use calloop::channel::Sender;
use crabshell_reactive::{ReadSignal, Signal, untrack};
use crabshell_ui::{IntoProp, Prop};
use libpulse_binding::channelmap::Map;
use libpulse_binding::context::{self, Context};
use libpulse_binding::def::BufferAttr;
use libpulse_binding::sample::{Format, Spec};
use libpulse_binding::stream::{FlagSet, PeekResult, State, Stream};
use super::{Audio, AudioService, Event, Locked, Message, Nodes, Pulse, on_pa_thread};
use crate::dbus::set_changed;
use crate::lines::panic_message;
const RATE: u32 = 25;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum PeakTarget {
Sink(String),
Source(String),
Stream(u32),
}
impl IntoProp<Option<PeakTarget>> for PeakTarget {
fn into_prop(self) -> Prop<Option<PeakTarget>> {
(move || Some(self.clone())).into_prop()
}
}
impl Audio {
#[track_caller]
pub fn peaks(&self, target: impl IntoProp<Option<PeakTarget>>) -> ReadSignal<Vec<f32>> {
let at = Location::caller();
let Some(service) = self.service.clone().filter(|_| !self.owner.is_disposed()) else {
return self.no_peaks;
};
let peaks = self.owner.signal(Vec::new());
let target = target.into_prop().shared();
let target = self.owner.memo(move || {
let chosen = panic::catch_unwind(AssertUnwindSafe(|| target()));
chosen.unwrap_or_else(|payload| {
let message = panic_message(payload.as_ref());
tracing::error!(
"cx.audio().peaks (called at {at}) panicked choosing its target: {message}"
);
None
})
});
self.owner.effect_scoped(move |scope| {
if let Some(target) = target.get() {
let id = service.meter(target, peaks);
scope.signal(Unmeter(Rc::clone(&service), id));
}
});
peaks.read_only()
}
}
pub(super) struct Wanted {
id: u64,
target: PeakTarget,
pub(super) peaks: Signal<Vec<f32>>,
}
struct Unmeter(Rc<AudioService>, u64);
impl Drop for Unmeter {
fn drop(&mut self) {
self.0.unmeter(self.1);
}
}
impl AudioService {
fn meter(&self, target: PeakTarget, peaks: Signal<Vec<f32>>) -> u64 {
let id = self.next_meter.get();
self.next_meter.set(id + 1);
self.meters.borrow_mut().push(Wanted { id, target, peaks });
self.sync_meters();
id
}
fn unmeter(&self, id: u64) {
self.meters.borrow_mut().retain(|meter| meter.id != id);
self.sync_meters();
}
fn sync_meters(&self) {
untrack(|| {
let wanted = self.meters.borrow();
if let Some(pulse) = self.pulse.borrow_mut().as_mut() {
pulse.sync_meters(&wanted);
}
});
}
}
#[derive(Clone, PartialEq)]
pub(super) struct Source {
name: String,
map: Map,
stream: Option<u32>,
passive: bool,
}
impl Nodes {
fn source_of(&self, target: &PeakTarget) -> Option<(Source, bool)> {
let (name, map, stream, quiet) = match target {
PeakTarget::Sink(name) => {
let sink = self.sinks.iter().find(|sink| sink.name == *name)?;
(sink.monitor_source.clone()?, sink.map, None, !sink.running)
}
PeakTarget::Source(name) => {
let source = self.sources.iter().find(|source| source.name == *name)?;
(source.name.clone(), source.map, None, false)
}
PeakTarget::Stream(index) => {
let stream = self.streams.iter().find(|stream| stream.index == *index)?;
let sink = self.sinks.iter().find(|sink| sink.index == stream.sink)?;
let quiet = stream.corked || !sink.running;
let name = sink.monitor_source.clone()?;
(name, stream.map, Some(*index), quiet)
}
};
let passive = !matches!(target, PeakTarget::Source(_));
let source = Source {
name,
map,
stream,
passive,
};
Some((source, quiet))
}
}
pub(super) struct Meter {
id: u64,
peaks: Signal<Vec<f32>>,
source: Source,
stream: Option<Recording>,
quiet: bool,
corked: bool,
}
impl Meter {
fn cork(&mut self) {
let Some(Recording(stream)) = self.stream.as_mut() else {
return;
};
if self.corked != self.quiet && stream.get_state() == State::Ready {
drop(stream.set_corked_state(self.quiet, None));
self.corked = self.quiet;
}
}
}
pub(super) struct Recording(Stream);
impl Drop for Recording {
fn drop(&mut self) {
self.0.set_read_callback(None);
self.0.set_state_callback(None);
}
}
fn end(
mut recording: Recording,
closing: &mut Vec<Recording>,
sender: &Arc<Sender<Message>>,
generation: u32,
) {
if recording.0.get_state() != State::Creating {
return;
}
recording.0.set_read_callback(None);
let sender = Arc::clone(sender);
let on_state = on_pa_thread(move || {
let _ = sender.send((generation, Event::Closing));
});
recording.0.set_state_callback(Some(Box::new(on_state)));
closing.push(recording);
}
impl Pulse {
pub(super) fn sync_meters(&mut self, wanted: &[Wanted]) {
let next: Vec<_> = wanted
.iter()
.map(|meter| (meter.id, meter.peaks, self.nodes.source_of(&meter.target)))
.collect();
let _lock = Locked::new(&mut self.mainloop);
let (closing, sender, generation) = (&mut self.closing, &self.sender, self.generation);
self.meters.retain_mut(|meter| {
let same = |(id, _, found): &(u64, _, Option<(Source, bool)>)| {
*id == meter.id
&& found
.as_ref()
.is_some_and(|(source, _)| *source == meter.source)
};
let kept = next.iter().any(same);
if !kept {
if let Some(recording) = meter.stream.take() {
end(recording, closing, sender, generation);
}
set_changed(meter.peaks, Vec::new());
}
kept
});
let mut context = self.context.as_mut();
let ready = context
.as_ref()
.is_some_and(|c| c.get_state() == context::State::Ready);
for (id, peaks, found) in next {
let Some((source, quiet)) = found else {
continue;
};
let meter = match self.meters.iter_mut().find(|meter| meter.id == id) {
Some(meter) => meter,
None => match context.as_mut().filter(|_| ready) {
Some(context) => {
let stream = open(context, &source, id, &self.sender, self.generation, quiet);
self.meters.push(Meter {
id,
peaks,
source,
stream,
quiet,
corked: quiet,
});
self.meters.last_mut().expect("the meter just pushed")
}
None => continue,
},
};
meter.quiet = quiet;
meter.cork();
if quiet && meter.stream.is_some() {
let channels = usize::from(meter.source.map.len());
set_changed(meter.peaks, vec![0.0; channels]);
}
}
}
pub(super) fn read_peaks(&mut self, id: u64) {
let _lock = Locked::new(&mut self.mainloop);
let Some(meter) = self.meters.iter_mut().find(|meter| meter.id == id) else {
return;
};
let channels = usize::from(meter.source.map.len());
let Some(Recording(stream)) = meter.stream.as_mut() else {
return;
};
let mut peaks = None;
loop {
match stream.peek() {
Ok(PeekResult::Data(bytes)) => louder(peaks.get_or_insert(vec![0.0; channels]), bytes),
Ok(PeekResult::Hole(_)) => {}
Ok(PeekResult::Empty) | Err(_) => break,
}
if stream.discard().is_err() {
break;
}
}
if let Some(peaks) = peaks.filter(|_| !meter.quiet) {
set_changed(meter.peaks, peaks);
}
}
pub(super) fn on_meter_state(&mut self, id: u64) {
let _lock = Locked::new(&mut self.mainloop);
let Some(meter) = self.meters.iter_mut().find(|meter| meter.id == id) else {
return;
};
let state = meter
.stream
.as_ref()
.map(|recording| recording.0.get_state());
if matches!(state, Some(State::Failed | State::Terminated)) {
let source = &meter.source.name;
tracing::info!(source, "the audio server ended a peak meter");
meter.stream = None;
set_changed(meter.peaks, Vec::new());
}
meter.cork();
}
pub(super) fn on_closing(&mut self) {
let _lock = Locked::new(&mut self.mainloop);
let made = |recording: &Recording| recording.0.get_state() != State::Creating;
self.closing.retain(|recording| !made(recording));
}
}
pub(super) fn close_all(meters: &mut Vec<Meter>, closing: &mut Vec<Recording>) {
closing.clear();
for meter in meters.drain(..) {
set_changed(meter.peaks, Vec::new());
}
}
fn open(
context: &mut Context,
source: &Source,
id: u64,
sender: &Arc<Sender<Message>>,
generation: u32,
corked: bool,
) -> Option<Recording> {
let spec = Spec {
format: Format::FLOAT32NE,
rate: RATE,
channels: source.map.len(),
};
let name = &source.name;
let Some(mut stream) = Stream::new(context, "Peak meter", &spec, Some(&source.map)) else {
tracing::warn!(name, "libpulse made no stream for a peak meter");
return None;
};
if let Some(index) = source.stream
&& let Err(error) = stream.set_monitor_stream(index)
{
tracing::warn!(name, index, %error, "a peak meter couldn't keep to its stream");
return None;
}
let frame = u32::from(spec.channels) * 4;
let attr = BufferAttr {
maxlength: u32::MAX,
tlength: u32::MAX,
prebuf: u32::MAX,
minreq: u32::MAX,
fragsize: frame,
};
let mut flags = FlagSet::PEAK_DETECT | FlagSet::ADJUST_LATENCY | FlagSet::DONT_MOVE;
if corked {
flags |= FlagSet::START_CORKED;
}
if source.passive {
flags |= FlagSet::DONT_INHIBIT_AUTO_SUSPEND;
}
if let Err(error) = stream.connect_record(Some(name), Some(&attr), flags) {
tracing::warn!(name, %error, "the audio server refused a peak meter");
return None;
}
let read = Arc::clone(sender);
let on_read = on_pa_thread(move |_| {
let _ = read.send((generation, Event::Peaks(id)));
});
stream.set_read_callback(Some(Box::new(on_read)));
let state = Arc::clone(sender);
let on_state = on_pa_thread(move || {
let _ = state.send((generation, Event::MeterState(id)));
});
stream.set_state_callback(Some(Box::new(on_state)));
Some(Recording(stream))
}
pub(super) fn louder(peaks: &mut [f32], bytes: &[u8]) {
if peaks.is_empty() {
return;
}
for frame in bytes.chunks_exact(4 * peaks.len()) {
for (peak, sample) in peaks.iter_mut().zip(frame.as_chunks::<4>().0) {
*peak = peak.max(f32::from_ne_bytes(*sample).abs()).min(1.0);
}
}
}