bbx_player/player.rs
1use std::sync::{Arc, atomic::AtomicBool};
2
3use bbx_dsp::{graph::Graph, sample::Sample};
4
5#[cfg(feature = "rodio")]
6use crate::backends::RodioBackend;
7use crate::{
8 backend::{Backend, PlayHandle},
9 error::Result,
10 signal::Signal,
11};
12
13/// Audio player that plays a DSP graph through a configurable backend.
14///
15/// `Player` wraps a DSP [`Graph`] and handles the conversion to an
16/// audio stream that can be played through the system's audio output.
17///
18/// # Examples
19///
20/// Using the default rodio backend (requires `rodio` feature):
21///
22/// ```ignore
23/// use bbx_player::Player;
24///
25/// let player = Player::new(graph)?;
26/// let handle = player.play()?;
27///
28/// std::thread::sleep(Duration::from_secs(5));
29/// handle.stop();
30/// ```
31///
32/// Using a custom backend:
33///
34/// ```ignore
35/// use bbx_player::{Player, backends::CpalBackend};
36///
37/// let backend = CpalBackend::try_default()?;
38/// let player = Player::with_backend(graph, backend);
39/// let handle = player.play()?;
40/// ```
41pub struct Player<S: Sample> {
42 graph: Graph<S>,
43 backend: Box<dyn Backend>,
44}
45
46#[cfg(feature = "rodio")]
47impl<S: Sample> Player<S> {
48 /// Create a new player with the default rodio backend.
49 pub fn new(graph: Graph<S>) -> Result<Self> {
50 let backend = RodioBackend::try_default()?;
51 Ok(Self {
52 graph,
53 backend: Box::new(backend),
54 })
55 }
56}
57
58impl<S: Sample> Player<S> {
59 /// Create a new player with a custom backend.
60 pub fn with_backend<B: Backend>(graph: Graph<S>, backend: B) -> Self {
61 Self {
62 graph,
63 backend: Box::new(backend),
64 }
65 }
66
67 /// Start non-blocking playback.
68 ///
69 /// Returns a [`PlayHandle`] that can be used to stop playback.
70 /// The player is consumed by this method.
71 pub fn play(self) -> Result<PlayHandle> {
72 let stop_flag = Arc::new(AtomicBool::new(false));
73 let handle = PlayHandle::new(Arc::clone(&stop_flag));
74
75 let signal = Signal::new(self.graph, Arc::clone(&stop_flag));
76 let sample_rate = signal.sample_rate();
77 let num_channels = signal.num_channels() as u16;
78
79 let signal_f32: Box<dyn Iterator<Item = f32> + Send> = Box::new(signal.map(|s| s.to_f64() as f32));
80
81 self.backend.play(signal_f32, sample_rate, num_channels, stop_flag)?;
82
83 Ok(handle)
84 }
85}