nandi/jolt-nativepublic Fork 0
2ab40bf193b6a39bc8d1397cc36d17572bbf59cd
Commits
Clone
git clone https://git.rickub.com/nandi/jolt-native.git
git clone ssh://git@rickub.com/nandi/jolt-native.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

mod.rs · 199 lines · 7.4 KBRust Blame HistoryRaw
Lift freeq's AV media plane out of sleek 90f8b89 nandi 20d ago1//! 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;
8use crate::{DevicesError, SampleFormat};
9
10mod device;
11mod stream;
12
13#[allow(unused_imports)] // Re-exported for public API via platform module
14pub use self::{
15 device::{Device, SupportedInputConfigs, SupportedOutputConfigs},
16 stream::Stream,
17};
18
19const JACK_SAMPLE_FORMAT: SampleFormat = SampleFormat::F32;
20
21pub type Devices = std::vec::IntoIter<Device>;
22
23/// The JACK host, providing access to JACK audio devices.
24///
25/// # JACK-Specific Configuration
26///
27/// Unlike other backends, JACK provides configuration options to control connection and server behavior:
28/// - Port auto-connection via [`set_connect_automatically`](Host::set_connect_automatically)
29/// - Server auto-start via [`set_start_server_automatically`](Host::set_start_server_automatically)
30#[derive(Debug)]
31pub struct Host {
32 /// The name that the client will have in JACK.
33 /// Until we have duplex streams two clients will be created adding "out" or "in" to the name
34 /// since names have to be unique.
35 name: String,
36 /// If ports are to be connected to the system (soundcard) ports automatically (default is true).
37 connect_ports_automatically: bool,
38 /// If the JACK server should be started automatically if it isn't already when creating a Client (default is false).
39 start_server_automatically: bool,
40 /// A list of the devices that have been created from this Host.
41 devices_created: Vec<Device>,
42}
43
44impl Host {
45 pub fn new() -> Result<Self, crate::HostUnavailable> {
46 let mut host = Host {
47 name: "cpal_client".to_owned(),
48 connect_ports_automatically: true,
49 start_server_automatically: false,
50 devices_created: vec![],
51 };
52 // Devices don't exist for JACK, they have to be created
53 host.initialize_default_devices();
54 Ok(host)
55 }
56 /// Configures whether created ports should automatically connect to system playback/capture 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, applications must manually
60 /// connect 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 match in_device_res {
95 Ok(device) => self.devices_created.push(device),
96 Err(err) => {
97 println!("{}", err);
98 }
99 }
100
101 let out_device_res = Device::default_output_device(
102 &self.name,
103 self.connect_ports_automatically,
104 self.start_server_automatically,
105 );
106 match out_device_res {
107 Ok(device) => self.devices_created.push(device),
108 Err(err) => {
109 println!("{}", err);
110 }
111 }
112 }
113}
114
115impl HostTrait for Host {
116 type Devices = Devices;
117 type Device = Device;
118
119 /// JACK is available if
120 /// - the jack feature flag is set
121 /// - libjack is installed (wouldn't compile without it)
122 /// - the JACK server can be started
123 ///
124 /// If the code compiles the necessary jack libraries are installed.
125 /// There is no way to know if the user has set up a correct JACK configuration e.g. with qjackctl.
126 /// Users can choose to automatically start the server if it isn't already started when creating a client
127 /// so checking if the server is running could give a false negative in some use cases.
128 /// For these reasons this function should always return true.
129 fn is_available() -> bool {
130 true
131 }
132
133 fn devices(&self) -> Result<Self::Devices, DevicesError> {
134 Ok(self.devices_created.clone().into_iter())
135 }
136
137 fn default_input_device(&self) -> Option<Self::Device> {
138 for device in &self.devices_created {
139 if device.is_input() {
140 return Some(device.clone());
141 }
142 }
143 None
144 }
145
146 fn default_output_device(&self) -> Option<Self::Device> {
147 for device in &self.devices_created {
148 if device.is_output() {
149 return Some(device.clone());
150 }
151 }
152 None
153 }
154}
155
156fn get_client_options(start_server_automatically: bool) -> jack::ClientOptions {
157 let mut client_options = jack::ClientOptions::empty();
158 client_options.set(
159 jack::ClientOptions::NO_START_SERVER,
160 !start_server_automatically,
161 );
162 client_options
163}
164
165fn get_client(name: &str, client_options: jack::ClientOptions) -> Result<jack::Client, String> {
166 let c_res = jack::Client::new(name, client_options);
167 match c_res {
168 Ok((client, status)) => {
169 // The ClientStatus can tell us many things
170 if status.intersects(jack::ClientStatus::SERVER_ERROR) {
171 return Err(String::from(
172 "There was an error communicating with the JACK server!",
173 ));
174 } else if status.intersects(jack::ClientStatus::SERVER_FAILED) {
175 return Err(String::from("Could not connect to the JACK server!"));
176 } else if status.intersects(jack::ClientStatus::VERSION_ERROR) {
177 return Err(String::from(
178 "Error connecting to JACK server: Client's protocol version does not match!",
179 ));
180 } else if status.intersects(jack::ClientStatus::INIT_FAILURE) {
181 return Err(String::from(
182 "Error connecting to JACK server: Unable to initialize client!",
183 ));
184 } else if status.intersects(jack::ClientStatus::SHM_FAILURE) {
185 return Err(String::from(
186 "Error connecting to JACK server: Unable to access shared memory!",
187 ));
188 } else if status.intersects(jack::ClientStatus::NO_SUCH_CLIENT) {
189 return Err(String::from(
190 "Error connecting to JACK server: Requested client does not exist!",
191 ));
192 } else if status.intersects(jack::ClientStatus::INVALID_OPTION) {
193 return Err(String::from("Error connecting to JACK server: The operation contained an invalid or unsupported option!"));
194 }
195 Ok(client)
196 }
197 Err(e) => Err(format!("Failed to open client because of error: {:?}", e)),
198 }
199}