crabshell_services/audio/
peaks.rs1use std::panic::{self, AssertUnwindSafe, Location};
6use std::rc::Rc;
7use std::sync::Arc;
8
9use calloop::channel::Sender;
10use crabshell_reactive::{ReadSignal, Signal, untrack};
11use crabshell_ui::{IntoProp, Prop};
12use libpulse_binding::channelmap::Map;
13use libpulse_binding::context::{self, Context};
14use libpulse_binding::def::BufferAttr;
15use libpulse_binding::sample::{Format, Spec};
16use libpulse_binding::stream::{FlagSet, PeekResult, State, Stream};
17
18use super::{Audio, AudioService, Event, Locked, Message, Nodes, Pulse, on_pa_thread};
19use crate::dbus::set_changed;
20use crate::lines::panic_message;
21
22const RATE: u32 = 25;
25
26#[derive(Clone, Debug, PartialEq, Eq)]
28pub enum PeakTarget {
29 Sink(String),
31 Source(String),
33 Stream(u32),
35}
36
37impl IntoProp<Option<PeakTarget>> for PeakTarget {
39 fn into_prop(self) -> Prop<Option<PeakTarget>> {
40 (move || Some(self.clone())).into_prop()
42 }
43}
44
45impl Audio {
46 #[track_caller]
71 pub fn peaks(&self, target: impl IntoProp<Option<PeakTarget>>) -> ReadSignal<Vec<f32>> {
72 let at = Location::caller();
73 let Some(service) = self.service.clone().filter(|_| !self.owner.is_disposed()) else {
74 return self.no_peaks;
75 };
76 let peaks = self.owner.signal(Vec::new());
77 let target = target.into_prop().shared();
78 let target = self.owner.memo(move || {
81 let chosen = panic::catch_unwind(AssertUnwindSafe(|| target()));
82 chosen.unwrap_or_else(|payload| {
83 let message = panic_message(payload.as_ref());
84 tracing::error!(
85 "cx.audio().peaks (called at {at}) panicked choosing its target: {message}"
86 );
87 None
88 })
89 });
90 self.owner.effect_scoped(move |scope| {
93 if let Some(target) = target.get() {
94 let id = service.meter(target, peaks);
95 scope.signal(Unmeter(Rc::clone(&service), id));
96 }
97 });
98 peaks.read_only()
99 }
100}
101
102pub(super) struct Wanted {
104 id: u64,
105 target: PeakTarget,
106 pub(super) peaks: Signal<Vec<f32>>,
107}
108
109struct Unmeter(Rc<AudioService>, u64);
111
112impl Drop for Unmeter {
113 fn drop(&mut self) {
114 self.0.unmeter(self.1);
115 }
116}
117
118impl AudioService {
119 fn meter(&self, target: PeakTarget, peaks: Signal<Vec<f32>>) -> u64 {
121 let id = self.next_meter.get();
122 self.next_meter.set(id + 1);
123 self.meters.borrow_mut().push(Wanted { id, target, peaks });
124 self.sync_meters();
125 id
126 }
127
128 fn unmeter(&self, id: u64) {
129 self.meters.borrow_mut().retain(|meter| meter.id != id);
130 self.sync_meters();
131 }
132
133 fn sync_meters(&self) {
137 untrack(|| {
138 let wanted = self.meters.borrow();
139 if let Some(pulse) = self.pulse.borrow_mut().as_mut() {
140 pulse.sync_meters(&wanted);
141 }
142 });
143 }
144}
145
146#[derive(Clone, PartialEq)]
148pub(super) struct Source {
149 name: String,
151 map: Map,
152 stream: Option<u32>,
154 passive: bool,
158}
159
160impl Nodes {
161 fn source_of(&self, target: &PeakTarget) -> Option<(Source, bool)> {
166 let (name, map, stream, quiet) = match target {
167 PeakTarget::Sink(name) => {
168 let sink = self.sinks.iter().find(|sink| sink.name == *name)?;
169 (sink.monitor_source.clone()?, sink.map, None, !sink.running)
170 }
171 PeakTarget::Source(name) => {
172 let source = self.sources.iter().find(|source| source.name == *name)?;
173 (source.name.clone(), source.map, None, false)
174 }
175 PeakTarget::Stream(index) => {
176 let stream = self.streams.iter().find(|stream| stream.index == *index)?;
177 let sink = self.sinks.iter().find(|sink| sink.index == stream.sink)?;
178 let quiet = stream.corked || !sink.running;
179 let name = sink.monitor_source.clone()?;
180 (name, stream.map, Some(*index), quiet)
181 }
182 };
183 let passive = !matches!(target, PeakTarget::Source(_));
184 let source = Source {
185 name,
186 map,
187 stream,
188 passive,
189 };
190 Some((source, quiet))
191 }
192}
193
194pub(super) struct Meter {
196 id: u64,
197 peaks: Signal<Vec<f32>>,
198 source: Source,
199 stream: Option<Recording>,
202 quiet: bool,
205 corked: bool,
207}
208
209impl Meter {
210 fn cork(&mut self) {
214 let Some(Recording(stream)) = self.stream.as_mut() else {
215 return;
216 };
217 if self.corked != self.quiet && stream.get_state() == State::Ready {
220 drop(stream.set_corked_state(self.quiet, None));
221 self.corked = self.quiet;
222 }
223 }
224}
225
226pub(super) struct Recording(Stream);
231
232impl Drop for Recording {
233 fn drop(&mut self) {
234 self.0.set_read_callback(None);
235 self.0.set_state_callback(None);
236 }
237}
238
239fn end(
244 mut recording: Recording,
245 closing: &mut Vec<Recording>,
246 sender: &Arc<Sender<Message>>,
247 generation: u32,
248) {
249 if recording.0.get_state() != State::Creating {
250 return;
251 }
252 recording.0.set_read_callback(None);
253 let sender = Arc::clone(sender);
254 let on_state = on_pa_thread(move || {
255 let _ = sender.send((generation, Event::Closing));
256 });
257 recording.0.set_state_callback(Some(Box::new(on_state)));
258 closing.push(recording);
259}
260
261impl Pulse {
262 pub(super) fn sync_meters(&mut self, wanted: &[Wanted]) {
265 let next: Vec<_> = wanted
266 .iter()
267 .map(|meter| (meter.id, meter.peaks, self.nodes.source_of(&meter.target)))
268 .collect();
269 let _lock = Locked::new(&mut self.mainloop);
270 let (closing, sender, generation) = (&mut self.closing, &self.sender, self.generation);
271 self.meters.retain_mut(|meter| {
272 let same = |(id, _, found): &(u64, _, Option<(Source, bool)>)| {
273 *id == meter.id
274 && found
275 .as_ref()
276 .is_some_and(|(source, _)| *source == meter.source)
277 };
278 let kept = next.iter().any(same);
279 if !kept {
280 if let Some(recording) = meter.stream.take() {
281 end(recording, closing, sender, generation);
282 }
283 set_changed(meter.peaks, Vec::new());
284 }
285 kept
286 });
287 let mut context = self.context.as_mut();
288 let ready = context
289 .as_ref()
290 .is_some_and(|c| c.get_state() == context::State::Ready);
291 for (id, peaks, found) in next {
292 let Some((source, quiet)) = found else {
293 continue;
294 };
295 let meter = match self.meters.iter_mut().find(|meter| meter.id == id) {
296 Some(meter) => meter,
297 None => match context.as_mut().filter(|_| ready) {
298 Some(context) => {
299 let stream = open(context, &source, id, &self.sender, self.generation, quiet);
300 self.meters.push(Meter {
301 id,
302 peaks,
303 source,
304 stream,
305 quiet,
306 corked: quiet,
307 });
308 self.meters.last_mut().expect("the meter just pushed")
309 }
310 None => continue,
311 },
312 };
313 meter.quiet = quiet;
314 meter.cork();
315 if quiet && meter.stream.is_some() {
316 let channels = usize::from(meter.source.map.len());
317 set_changed(meter.peaks, vec![0.0; channels]);
318 }
319 }
320 }
321
322 pub(super) fn read_peaks(&mut self, id: u64) {
325 let _lock = Locked::new(&mut self.mainloop);
326 let Some(meter) = self.meters.iter_mut().find(|meter| meter.id == id) else {
327 return;
328 };
329 let channels = usize::from(meter.source.map.len());
330 let Some(Recording(stream)) = meter.stream.as_mut() else {
331 return;
332 };
333 let mut peaks = None;
334 loop {
335 match stream.peek() {
336 Ok(PeekResult::Data(bytes)) => louder(peaks.get_or_insert(vec![0.0; channels]), bytes),
337 Ok(PeekResult::Hole(_)) => {}
338 Ok(PeekResult::Empty) | Err(_) => break,
339 }
340 if stream.discard().is_err() {
341 break;
342 }
343 }
344 if let Some(peaks) = peaks.filter(|_| !meter.quiet) {
346 set_changed(meter.peaks, peaks);
347 }
348 }
349
350 pub(super) fn on_meter_state(&mut self, id: u64) {
353 let _lock = Locked::new(&mut self.mainloop);
354 let Some(meter) = self.meters.iter_mut().find(|meter| meter.id == id) else {
355 return;
356 };
357 let state = meter
358 .stream
359 .as_ref()
360 .map(|recording| recording.0.get_state());
361 if matches!(state, Some(State::Failed | State::Terminated)) {
362 let source = &meter.source.name;
363 tracing::info!(source, "the audio server ended a peak meter");
364 meter.stream = None;
365 set_changed(meter.peaks, Vec::new());
366 }
367 meter.cork();
368 }
369
370 pub(super) fn on_closing(&mut self) {
372 let _lock = Locked::new(&mut self.mainloop);
373 let made = |recording: &Recording| recording.0.get_state() != State::Creating;
374 self.closing.retain(|recording| !made(recording));
375 }
376}
377
378pub(super) fn close_all(meters: &mut Vec<Meter>, closing: &mut Vec<Recording>) {
382 closing.clear();
383 for meter in meters.drain(..) {
384 set_changed(meter.peaks, Vec::new());
385 }
386}
387
388fn open(
391 context: &mut Context,
392 source: &Source,
393 id: u64,
394 sender: &Arc<Sender<Message>>,
395 generation: u32,
396 corked: bool,
397) -> Option<Recording> {
398 let spec = Spec {
399 format: Format::FLOAT32NE,
400 rate: RATE,
401 channels: source.map.len(),
402 };
403 let name = &source.name;
404 let Some(mut stream) = Stream::new(context, "Peak meter", &spec, Some(&source.map)) else {
405 tracing::warn!(name, "libpulse made no stream for a peak meter");
406 return None;
407 };
408 if let Some(index) = source.stream
409 && let Err(error) = stream.set_monitor_stream(index)
410 {
411 tracing::warn!(name, index, %error, "a peak meter couldn't keep to its stream");
412 return None;
413 }
414 let frame = u32::from(spec.channels) * 4;
416 let attr = BufferAttr {
417 maxlength: u32::MAX,
418 tlength: u32::MAX,
419 prebuf: u32::MAX,
420 minreq: u32::MAX,
421 fragsize: frame,
422 };
423 let mut flags = FlagSet::PEAK_DETECT | FlagSet::ADJUST_LATENCY | FlagSet::DONT_MOVE;
424 if corked {
425 flags |= FlagSet::START_CORKED;
426 }
427 if source.passive {
428 flags |= FlagSet::DONT_INHIBIT_AUTO_SUSPEND;
429 }
430 if let Err(error) = stream.connect_record(Some(name), Some(&attr), flags) {
431 tracing::warn!(name, %error, "the audio server refused a peak meter");
432 return None;
433 }
434 let read = Arc::clone(sender);
436 let on_read = on_pa_thread(move |_| {
437 let _ = read.send((generation, Event::Peaks(id)));
438 });
439 stream.set_read_callback(Some(Box::new(on_read)));
440 let state = Arc::clone(sender);
441 let on_state = on_pa_thread(move || {
442 let _ = state.send((generation, Event::MeterState(id)));
443 });
444 stream.set_state_callback(Some(Box::new(on_state)));
445 Some(Recording(stream))
446}
447
448pub(super) fn louder(peaks: &mut [f32], bytes: &[u8]) {
452 if peaks.is_empty() {
453 return;
454 }
455 for frame in bytes.chunks_exact(4 * peaks.len()) {
456 for (peak, sample) in peaks.iter_mut().zip(frame.as_chunks::<4>().0) {
457 *peak = peak.max(f32::from_ne_bytes(*sample).abs()).min(1.0);
459 }
460 }
461}