Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Timestamps now stay monotonic across device and graph changes.
- **ALSA**: A nonzero but sub-millisecond stream timeout is no longer treated as a non-blocking poll.
- **JACK**: Streams with more channels than physical system ports no longer fail to build.
- **JACK**: Channel enumeration is no longer capped at the physical system port count.
- **PipeWire**: Streams for a specific device no longer auto-reroute if it disappears.
- **visionOS**: The CoreAudio backend now builds.
- **WASAPI**: Default device changes no longer report `DeviceChanged`, which wrongly implied the stream had rerouted automatically.
Expand Down
29 changes: 10 additions & 19 deletions src/host/jack/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ use crate::{

const DEFAULT_NUM_CHANNELS: ChannelCount = 2;

// 64 = AES10 (MADI) maximum; JACK itself imposes no per-client channel limit.
const MAX_CHANNELS: ChannelCount = 64;

#[derive(Clone, Debug)]
pub struct Device {
name: String,
sample_rate: SampleRate,
buffer_size: SupportedBufferSize,
max_channels: ChannelCount,
direction: DeviceDirection,
start_server_automatically: bool,
connect_ports_automatically: bool,
Expand All @@ -40,22 +42,12 @@ impl Device {
// making the stream. This is a hack due to the fact that the Client must be moved to
// create the AsyncClient.
let client = super::get_client(&name, client_options)?;
let port_pattern = match direction {
DeviceDirection::Input => "system:capture_.*",
DeviceDirection::Output => "system:playback_.*",
_ => {
return Err(Error::with_message(
ErrorKind::UnsupportedOperation,
format!("JACK does not support {direction:?} direction"),
))
}
};
let max_channels = client
.ports(Some(port_pattern), None, jack::PortFlags::empty())
.len()
.try_into()
.unwrap_or(DEFAULT_NUM_CHANNELS)
.max(DEFAULT_NUM_CHANNELS);
if !matches!(direction, DeviceDirection::Input | DeviceDirection::Output) {
return Err(Error::with_message(
ErrorKind::UnsupportedOperation,
format!("JACK does not support {direction:?} direction"),
));
}
Ok(Self {
// The name given to the client by JACK, could potentially be different from the name
// supplied e.g. if there is a name collision
Expand All @@ -65,7 +57,6 @@ impl Device {
min: client.buffer_size(),
max: client.buffer_size(),
},
max_channels,
direction,
start_server_automatically,
connect_ports_automatically,
Expand Down Expand Up @@ -126,7 +117,7 @@ impl Device {
Ok(f) => f,
};

(1..=self.max_channels)
(1..=MAX_CHANNELS)
.map(|channels| SupportedStreamConfigRange {
channels,
min_sample_rate: f.sample_rate,
Expand Down
54 changes: 12 additions & 42 deletions src/host/jack/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,32 +158,17 @@ impl Stream {
})
}

/// Connect stream output ports to the standard JACK system playback ports.
/// Must be called after the client is activated.
/// Connects the stream's output ports to as many system playback ports as are available;
/// must be called after the client is activated. A stream may have more output channels
/// than physical ports (e.g. feeding a downstream JACK client); the surplus is simply left
/// unconnected for manual patching.
///
/// # Returns
///
/// - `Ok(())` if every stream channel was connected to a system playback port.
/// - `Err` if there are fewer system playback ports than stream channels, or if any
/// individual port-connection call fails.
///
/// On error, connections that were made before the failure are rolled back on a best-effort
/// basis so the JACK graph is left unchanged.
/// Returns `Err` only if an individual port-connection call fails, rolling back any
/// connections already made so the JACK graph is left unchanged.
pub fn connect_to_system_outputs(&mut self) -> Result<(), Error> {
let client = self.async_client.as_client();
let system_ports = client.ports(Some("system:playback_.*"), None, jack::PortFlags::empty());

let n_our = self.output_port_names.len();
let n_sys = system_ports.len();
if n_sys < n_our {
return Err(Error::with_message(
ErrorKind::UnsupportedConfig,
format!(
"Only {n_sys} system playback port(s) available, but the stream has {n_our} output channel(s)"
),
));
}

// Connect outputs from this client to the system playback inputs.
for (i, (our_port, system_port)) in
self.output_port_names.iter().zip(&system_ports).enumerate()
Expand All @@ -204,32 +189,17 @@ impl Stream {
Ok(())
}

/// Connect stream input ports to the standard JACK system capture ports.
/// Must be called after the client is activated.
/// Connects the stream's input ports to as many system capture ports as are available; must
/// be called after the client is activated. A stream may have more input channels than
/// physical ports (e.g. sourced from an upstream JACK client); the surplus is simply left
/// unconnected for manual patching.
///
/// # Returns
///
/// - `Ok(())` if every stream channel was connected to a system capture port.
/// - `Err` if there are fewer system capture ports than stream channels, or if any individual
/// port-connection call fails.
///
/// On error, connections that were made before the failure are rolled back on a best-effort
/// basis so the JACK graph is left unchanged.
/// Returns `Err` only if an individual port-connection call fails, rolling back any
/// connections already made so the JACK graph is left unchanged.
pub fn connect_to_system_inputs(&mut self) -> Result<(), Error> {
let client = self.async_client.as_client();
let system_ports = client.ports(Some("system:capture_.*"), None, jack::PortFlags::empty());

let n_our = self.input_port_names.len();
let n_sys = system_ports.len();
if n_sys < n_our {
return Err(Error::with_message(
ErrorKind::UnsupportedConfig,
format!(
"Only {n_sys} system capture port(s) available, but the stream has {n_our} input channel(s)"
),
));
}

// Connect inputs from system capture ports to this client.
for (i, (system_port, our_port)) in
system_ports.iter().zip(&self.input_port_names).enumerate()
Expand Down
Loading