Skip to main content

cpal/host/jack/
mod.rs

1//! JACK backend implementation.
2//!
3//! Available on all platforms with the `jack` feature. Requires JACK server and client libraries.
4
5extern crate jack;
6
7use crate::{traits::HostTrait, Error, ErrorKind, SampleFormat};
8
9mod device;
10mod stream;
11
12#[allow(unused_imports)] // Re-exported for public API via platform module
13pub use self::{
14    device::{Device, SupportedInputConfigs, SupportedOutputConfigs},
15    stream::Stream,
16};
17
18const JACK_SAMPLE_FORMAT: SampleFormat = SampleFormat::F32;
19
20pub type Devices = std::vec::IntoIter<Device>;
21
22/// The JACK host, providing access to JACK audio devices.
23///
24/// # JACK-Specific Configuration
25///
26/// JACK provides configuration options to control connection and server behavior:
27/// - Port auto-connection via [`set_connect_automatically`](Host::set_connect_automatically)
28/// - Server auto-start via [`set_start_server_automatically`](Host::set_start_server_automatically)
29#[derive(Debug)]
30pub struct Host {
31    /// The name that the client will have in JACK.
32    /// Until we have duplex streams two clients will be created adding "out" or "in" to the name
33    /// since names have to be unique.
34    name: String,
35    /// If ports are to be connected to the system (soundcard) ports automatically (default is true).
36    connect_ports_automatically: bool,
37    /// If the JACK server should be started automatically if it isn't already when creating a Client (default is false).
38    start_server_automatically: bool,
39    /// A list of the devices that have been created from this Host.
40    devices_created: Vec<Device>,
41}
42
43impl Host {
44    pub fn new() -> Result<Self, Error> {
45        let mut host = Host {
46            name: format!("cpal_client_{}", std::process::id()),
47            connect_ports_automatically: true,
48            start_server_automatically: false,
49            devices_created: vec![],
50        };
51        // Devices don't exist for JACK, they have to be created
52        host.initialize_default_devices();
53        Ok(host)
54    }
55    /// Configures whether created ports should automatically connect to system playback/capture
56    /// ports.
57    ///
58    /// When enabled (default), output streams connect to system playback ports and input streams
59    /// connect to system capture ports automatically. When disabled, users must manually connect
60    /// ports using JACK tools or APIs.
61    ///
62    /// Default: `true`
63    pub fn set_connect_automatically(&mut self, do_connect: bool) {
64        self.connect_ports_automatically = do_connect;
65    }
66
67    /// Configures whether the JACK server should automatically start if not already running.
68    ///
69    /// When enabled, attempting to create a JACK client will start the JACK server if it's not
70    /// running. When disabled (default), client creation fails if the server is not running.
71    ///
72    /// Default: `false`
73    pub fn set_start_server_automatically(&mut self, do_start_server: bool) {
74        self.start_server_automatically = do_start_server;
75    }
76
77    pub fn input_device_with_name(&mut self, name: &str) -> Option<Device> {
78        self.name = name.to_owned();
79        self.default_input_device()
80    }
81
82    pub fn output_device_with_name(&mut self, name: &str) -> Option<Device> {
83        self.name = name.to_owned();
84        self.default_output_device()
85    }
86
87    fn initialize_default_devices(&mut self) {
88        let in_device_res = Device::default_input_device(
89            &self.name,
90            self.connect_ports_automatically,
91            self.start_server_automatically,
92        );
93
94        if let Ok(device) = in_device_res {
95            self.devices_created.push(device);
96        }
97
98        let out_device_res = Device::default_output_device(
99            &self.name,
100            self.connect_ports_automatically,
101            self.start_server_automatically,
102        );
103        if let Ok(device) = out_device_res {
104            self.devices_created.push(device);
105        }
106    }
107}
108
109impl HostTrait for Host {
110    type Devices = Devices;
111    type Device = Device;
112
113    /// JACK is available if
114    /// - the jack feature flag is set
115    /// - libjack is installed (wouldn't compile without it)
116    /// - the JACK server can be started
117    ///
118    /// If the code compiles the necessary jack libraries are installed.
119    /// There is no way to know if the user has set up a correct JACK configuration e.g. with
120    /// qjackctl.
121    /// Users can choose to automatically start the server if it isn't already started when
122    /// creating a client so checking if the server is running could give a false negative in some
123    /// use cases. For these reasons this function should always return true.
124    fn is_available() -> bool {
125        true
126    }
127
128    fn devices(&self) -> Result<Self::Devices, Error> {
129        Ok(self.devices_created.clone().into_iter())
130    }
131
132    fn default_input_device(&self) -> Option<Self::Device> {
133        for device in &self.devices_created {
134            if device.is_input() {
135                return Some(device.clone());
136            }
137        }
138        None
139    }
140
141    fn default_output_device(&self) -> Option<Self::Device> {
142        for device in &self.devices_created {
143            if device.is_output() {
144                return Some(device.clone());
145            }
146        }
147        None
148    }
149}
150
151fn get_client_options(start_server_automatically: bool) -> jack::ClientOptions {
152    let mut client_options = jack::ClientOptions::empty();
153    client_options.set(
154        jack::ClientOptions::NO_START_SERVER,
155        !start_server_automatically,
156    );
157    client_options
158}
159
160impl From<jack::Error> for Error {
161    fn from(err: jack::Error) -> Self {
162        let msg = format!("{err}");
163        match err {
164            jack::Error::ClientError(_)
165            | jack::Error::ClientActivationError
166            | jack::Error::ClientDeactivationError
167            | jack::Error::LibraryError(_)
168            | jack::Error::WeakFunctionNotFound(_)
169            | jack::Error::RingbufferCreateFailed => {
170                Error::with_message(ErrorKind::DeviceNotAvailable, msg)
171            }
172
173            jack::Error::ClientIsNoLongerAlive | jack::Error::ClientPanicked => {
174                Error::with_message(ErrorKind::StreamInvalidated, msg)
175            }
176
177            jack::Error::SetBufferSizeError | jack::Error::NotEnoughSpace => {
178                Error::with_message(ErrorKind::UnsupportedConfig, msg)
179            }
180
181            jack::Error::InvalidDeactivation | jack::Error::FreewheelError => {
182                Error::with_message(ErrorKind::UnsupportedOperation, msg)
183            }
184
185            jack::Error::PortNamingError | jack::Error::PortAliasError => {
186                Error::with_message(ErrorKind::InvalidInput, msg)
187            }
188
189            _ => Error::with_message(ErrorKind::BackendError, msg),
190        }
191    }
192}
193
194fn get_client(name: &str, client_options: jack::ClientOptions) -> Result<jack::Client, Error> {
195    let (client, status) = jack::Client::new(name, client_options)?;
196    if status.intersects(jack::ClientStatus::VERSION_ERROR) {
197        Err(Error::with_message(
198            ErrorKind::UnsupportedOperation,
199            "Client protocol version does not match the JACK server",
200        ))
201    } else if status.intersects(jack::ClientStatus::INVALID_OPTION) {
202        Err(Error::with_message(
203            ErrorKind::UnsupportedOperation,
204            "JACK client operation contained an invalid or unsupported option",
205        ))
206    } else if status.intersects(jack::ClientStatus::SERVER_ERROR) {
207        Err(Error::with_message(
208            ErrorKind::DeviceNotAvailable,
209            "Error communicating with the JACK server",
210        ))
211    } else if status.intersects(jack::ClientStatus::SERVER_FAILED) {
212        Err(Error::with_message(
213            ErrorKind::DeviceNotAvailable,
214            "Could not connect to the JACK server",
215        ))
216    } else if status.intersects(jack::ClientStatus::INIT_FAILURE) {
217        Err(Error::with_message(
218            ErrorKind::DeviceNotAvailable,
219            "Unable to initialize JACK client",
220        ))
221    } else if status.intersects(jack::ClientStatus::SHM_FAILURE) {
222        Err(Error::with_message(
223            ErrorKind::DeviceNotAvailable,
224            "Unable to access JACK shared memory",
225        ))
226    } else if status.intersects(jack::ClientStatus::NO_SUCH_CLIENT) {
227        Err(Error::with_message(
228            ErrorKind::DeviceNotAvailable,
229            "Requested JACK client does not exist",
230        ))
231    } else {
232        Ok(client)
233    }
234}