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.

Lift freeq's AV media plane out of sleek 90f8b89 · on 2ab40bf193b6a39bc8d1397cc36d17572bbf59cd · nandi · 19d ago
mod.rs · 199 lines · 7.4 KBRust Blame HistoryRaw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
//! JACK backend implementation.
//!
//! Available on all platforms with the `jack` feature. Requires JACK server and client libraries.

extern crate jack;

use crate::traits::HostTrait;
use crate::{DevicesError, SampleFormat};

mod device;
mod stream;

#[allow(unused_imports)] // Re-exported for public API via platform module
pub use self::{
    device::{Device, SupportedInputConfigs, SupportedOutputConfigs},
    stream::Stream,
};

const JACK_SAMPLE_FORMAT: SampleFormat = SampleFormat::F32;

pub type Devices = std::vec::IntoIter<Device>;

/// The JACK host, providing access to JACK audio devices.
///
/// # JACK-Specific Configuration
///
/// Unlike other backends, JACK provides configuration options to control connection and server behavior:
/// - Port auto-connection via [`set_connect_automatically`](Host::set_connect_automatically)
/// - Server auto-start via [`set_start_server_automatically`](Host::set_start_server_automatically)
#[derive(Debug)]
pub struct Host {
    /// The name that the client will have in JACK.
    /// Until we have duplex streams two clients will be created adding "out" or "in" to the name
    /// since names have to be unique.
    name: String,
    /// If ports are to be connected to the system (soundcard) ports automatically (default is true).
    connect_ports_automatically: bool,
    /// If the JACK server should be started automatically if it isn't already when creating a Client (default is false).
    start_server_automatically: bool,
    /// A list of the devices that have been created from this Host.
    devices_created: Vec<Device>,
}

impl Host {
    pub fn new() -> Result<Self, crate::HostUnavailable> {
        let mut host = Host {
            name: "cpal_client".to_owned(),
            connect_ports_automatically: true,
            start_server_automatically: false,
            devices_created: vec![],
        };
        // Devices don't exist for JACK, they have to be created
        host.initialize_default_devices();
        Ok(host)
    }
    /// Configures whether created ports should automatically connect to system playback/capture ports.
    ///
    /// When enabled (default), output streams connect to system playback ports and input streams
    /// connect to system capture ports automatically. When disabled, applications must manually
    /// connect ports using JACK tools or APIs.
    ///
    /// Default: `true`
    pub fn set_connect_automatically(&mut self, do_connect: bool) {
        self.connect_ports_automatically = do_connect;
    }

    /// Configures whether the JACK server should automatically start if not already running.
    ///
    /// When enabled, attempting to create a JACK client will start the JACK server if it's not
    /// running. When disabled (default), client creation fails if the server is not running.
    ///
    /// Default: `false`
    pub fn set_start_server_automatically(&mut self, do_start_server: bool) {
        self.start_server_automatically = do_start_server;
    }

    pub fn input_device_with_name(&mut self, name: &str) -> Option<Device> {
        self.name = name.to_owned();
        self.default_input_device()
    }

    pub fn output_device_with_name(&mut self, name: &str) -> Option<Device> {
        self.name = name.to_owned();
        self.default_output_device()
    }

    fn initialize_default_devices(&mut self) {
        let in_device_res = Device::default_input_device(
            &self.name,
            self.connect_ports_automatically,
            self.start_server_automatically,
        );

        match in_device_res {
            Ok(device) => self.devices_created.push(device),
            Err(err) => {
                println!("{}", err);
            }
        }

        let out_device_res = Device::default_output_device(
            &self.name,
            self.connect_ports_automatically,
            self.start_server_automatically,
        );
        match out_device_res {
            Ok(device) => self.devices_created.push(device),
            Err(err) => {
                println!("{}", err);
            }
        }
    }
}

impl HostTrait for Host {
    type Devices = Devices;
    type Device = Device;

    /// JACK is available if
    /// - the jack feature flag is set
    /// - libjack is installed (wouldn't compile without it)
    /// - the JACK server can be started
    ///
    /// If the code compiles the necessary jack libraries are installed.
    /// There is no way to know if the user has set up a correct JACK configuration e.g. with qjackctl.
    /// Users can choose to automatically start the server if it isn't already started when creating a client
    /// so checking if the server is running could give a false negative in some use cases.
    /// For these reasons this function should always return true.
    fn is_available() -> bool {
        true
    }

    fn devices(&self) -> Result<Self::Devices, DevicesError> {
        Ok(self.devices_created.clone().into_iter())
    }

    fn default_input_device(&self) -> Option<Self::Device> {
        for device in &self.devices_created {
            if device.is_input() {
                return Some(device.clone());
            }
        }
        None
    }

    fn default_output_device(&self) -> Option<Self::Device> {
        for device in &self.devices_created {
            if device.is_output() {
                return Some(device.clone());
            }
        }
        None
    }
}

fn get_client_options(start_server_automatically: bool) -> jack::ClientOptions {
    let mut client_options = jack::ClientOptions::empty();
    client_options.set(
        jack::ClientOptions::NO_START_SERVER,
        !start_server_automatically,
    );
    client_options
}

fn get_client(name: &str, client_options: jack::ClientOptions) -> Result<jack::Client, String> {
    let c_res = jack::Client::new(name, client_options);
    match c_res {
        Ok((client, status)) => {
            // The ClientStatus can tell us many things
            if status.intersects(jack::ClientStatus::SERVER_ERROR) {
                return Err(String::from(
                    "There was an error communicating with the JACK server!",
                ));
            } else if status.intersects(jack::ClientStatus::SERVER_FAILED) {
                return Err(String::from("Could not connect to the JACK server!"));
            } else if status.intersects(jack::ClientStatus::VERSION_ERROR) {
                return Err(String::from(
                    "Error connecting to JACK server: Client's protocol version does not match!",
                ));
            } else if status.intersects(jack::ClientStatus::INIT_FAILURE) {
                return Err(String::from(
                    "Error connecting to JACK server: Unable to initialize client!",
                ));
            } else if status.intersects(jack::ClientStatus::SHM_FAILURE) {
                return Err(String::from(
                    "Error connecting to JACK server: Unable to access shared memory!",
                ));
            } else if status.intersects(jack::ClientStatus::NO_SUCH_CLIENT) {
                return Err(String::from(
                    "Error connecting to JACK server: Requested client does not exist!",
                ));
            } else if status.intersects(jack::ClientStatus::INVALID_OPTION) {
                return Err(String::from("Error connecting to JACK server: The operation contained an invalid or unsupported option!"));
            }
            Ok(client)
        }
        Err(e) => Err(format!("Failed to open client because of error: {:?}", e)),
    }
}