WIP: improve Linux audio support
This commit is contained in:
@@ -237,6 +237,12 @@ pub struct AudioStreamConfig {
|
||||
pub max_voices: usize,
|
||||
}
|
||||
|
||||
pub struct AudioStreamInfo {
|
||||
pub sample_rate: f32,
|
||||
pub host_name: String,
|
||||
pub channels: u16,
|
||||
}
|
||||
|
||||
pub fn build_stream(
|
||||
config: &AudioStreamConfig,
|
||||
audio_rx: Receiver<AudioCommand>,
|
||||
@@ -245,7 +251,7 @@ pub fn build_stream(
|
||||
metrics: Arc<EngineMetrics>,
|
||||
initial_samples: Vec<doux::sampling::SampleEntry>,
|
||||
audio_sample_pos: Arc<AtomicU64>,
|
||||
) -> Result<(Stream, f32, AnalysisHandle), String> {
|
||||
) -> Result<(Stream, AudioStreamInfo, AnalysisHandle), String> {
|
||||
let device = match &config.output_device {
|
||||
Some(name) => doux::audio::find_output_device(name)
|
||||
.ok_or_else(|| format!("Device not found: {name}"))?,
|
||||
@@ -258,11 +264,8 @@ pub fn build_stream(
|
||||
let max_channels = doux::audio::max_output_channels(&device);
|
||||
let channels = config.channels.min(max_channels);
|
||||
|
||||
let is_jack = doux::audio::preferred_host()
|
||||
.id()
|
||||
.name()
|
||||
.to_lowercase()
|
||||
.contains("jack");
|
||||
let host_name = doux::audio::preferred_host().id().name().to_string();
|
||||
let is_jack = host_name.to_lowercase().contains("jack");
|
||||
|
||||
let buffer_size = if config.buffer_size > 0 && !is_jack {
|
||||
cpal::BufferSize::Fixed(config.buffer_size)
|
||||
@@ -277,6 +280,7 @@ pub fn build_stream(
|
||||
};
|
||||
|
||||
let sr = sample_rate;
|
||||
let effective_channels = channels;
|
||||
let channels = channels as usize;
|
||||
let max_voices = config.max_voices;
|
||||
|
||||
@@ -347,5 +351,10 @@ pub fn build_stream(
|
||||
stream
|
||||
.play()
|
||||
.map_err(|e| format!("Failed to play stream: {e}"))?;
|
||||
Ok((stream, sample_rate, analysis_handle))
|
||||
let info = AudioStreamInfo {
|
||||
sample_rate,
|
||||
host_name,
|
||||
channels: effective_channels,
|
||||
};
|
||||
Ok((stream, info, analysis_handle))
|
||||
}
|
||||
|
||||
19
src/main.rs
19
src/main.rs
@@ -106,7 +106,8 @@ fn main() -> io::Result<()> {
|
||||
app.ui.hue_rotation = settings.display.hue_rotation;
|
||||
app.audio.config.layout = settings.display.layout;
|
||||
let base_theme = settings.display.color_scheme.to_theme();
|
||||
let rotated = cagire_ratatui::theme::transform::rotate_theme(base_theme, settings.display.hue_rotation);
|
||||
let rotated =
|
||||
cagire_ratatui::theme::transform::rotate_theme(base_theme, settings.display.hue_rotation);
|
||||
theme::set(rotated);
|
||||
|
||||
// Load MIDI settings
|
||||
@@ -190,9 +191,11 @@ fn main() -> io::Result<()> {
|
||||
initial_samples,
|
||||
Arc::clone(&audio_sample_pos),
|
||||
) {
|
||||
Ok((s, sample_rate, analysis)) => {
|
||||
app.audio.config.sample_rate = sample_rate;
|
||||
sample_rate_shared.store(sample_rate as u32, Ordering::Relaxed);
|
||||
Ok((s, info, analysis)) => {
|
||||
app.audio.config.sample_rate = info.sample_rate;
|
||||
app.audio.config.host_name = info.host_name;
|
||||
app.audio.config.channels = info.channels;
|
||||
sample_rate_shared.store(info.sample_rate as u32, Ordering::Relaxed);
|
||||
(Some(s), Some(analysis))
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -244,11 +247,13 @@ fn main() -> io::Result<()> {
|
||||
restart_samples,
|
||||
Arc::clone(&audio_sample_pos),
|
||||
) {
|
||||
Ok((new_stream, sr, new_analysis)) => {
|
||||
Ok((new_stream, info, new_analysis)) => {
|
||||
_stream = Some(new_stream);
|
||||
_analysis_handle = Some(new_analysis);
|
||||
app.audio.config.sample_rate = sr;
|
||||
sample_rate_shared.store(sr as u32, Ordering::Relaxed);
|
||||
app.audio.config.sample_rate = info.sample_rate;
|
||||
app.audio.config.host_name = info.host_name;
|
||||
app.audio.config.channels = info.channels;
|
||||
sample_rate_shared.store(info.sample_rate as u32, Ordering::Relaxed);
|
||||
app.audio.error = None;
|
||||
app.ui.set_status("Audio restarted".to_string());
|
||||
}
|
||||
|
||||
@@ -77,6 +77,7 @@ pub struct AudioConfig {
|
||||
pub buffer_size: u32,
|
||||
pub max_voices: usize,
|
||||
pub sample_rate: f32,
|
||||
pub host_name: String,
|
||||
pub sample_paths: Vec<PathBuf>,
|
||||
pub sample_count: usize,
|
||||
pub refresh_rate: RefreshRate,
|
||||
@@ -95,6 +96,7 @@ impl Default for AudioConfig {
|
||||
buffer_size: 512,
|
||||
max_voices: 32,
|
||||
sample_rate: 44100.0,
|
||||
host_name: String::new(),
|
||||
sample_paths: Vec::new(),
|
||||
sample_count: 0,
|
||||
refresh_rate: RefreshRate::default(),
|
||||
|
||||
@@ -135,7 +135,12 @@ fn render_settings_section(frame: &mut Frame, app: &App, area: Rect) {
|
||||
let down_indicator = Paragraph::new("▼").style(indicator_style);
|
||||
frame.render_widget(
|
||||
down_indicator,
|
||||
Rect::new(indicator_x, padded.y + padded.height.saturating_sub(1), 1, 1),
|
||||
Rect::new(
|
||||
indicator_x,
|
||||
padded.y + padded.height.saturating_sub(1),
|
||||
1,
|
||||
1,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -211,9 +216,13 @@ fn render_section_header(frame: &mut Frame, title: &str, focused: bool, area: Re
|
||||
Layout::vertical([Constraint::Length(1), Constraint::Length(1)]).areas(area);
|
||||
|
||||
let header_style = if focused {
|
||||
Style::new().fg(theme.engine.header_focused).add_modifier(Modifier::BOLD)
|
||||
Style::new()
|
||||
.fg(theme.engine.header_focused)
|
||||
.add_modifier(Modifier::BOLD)
|
||||
} else {
|
||||
Style::new().fg(theme.engine.header).add_modifier(Modifier::BOLD)
|
||||
Style::new()
|
||||
.fg(theme.engine.header)
|
||||
.add_modifier(Modifier::BOLD)
|
||||
};
|
||||
|
||||
frame.render_widget(Paragraph::new(title).style(header_style), header_area);
|
||||
@@ -292,7 +301,9 @@ fn render_device_column(
|
||||
Layout::vertical([Constraint::Length(1), Constraint::Min(1)]).areas(area);
|
||||
|
||||
let label_style = if focused {
|
||||
Style::new().fg(theme.engine.focused).add_modifier(Modifier::BOLD)
|
||||
Style::new()
|
||||
.fg(theme.engine.focused)
|
||||
.add_modifier(Modifier::BOLD)
|
||||
} else if section_focused {
|
||||
Style::new().fg(theme.engine.label_focused)
|
||||
} else {
|
||||
@@ -322,7 +333,9 @@ fn render_settings(frame: &mut Frame, app: &App, area: Rect) {
|
||||
|
||||
render_section_header(frame, "SETTINGS", section_focused, header_area);
|
||||
|
||||
let highlight = Style::new().fg(theme.engine.focused).add_modifier(Modifier::BOLD);
|
||||
let highlight = Style::new()
|
||||
.fg(theme.engine.focused)
|
||||
.add_modifier(Modifier::BOLD);
|
||||
let normal = Style::new().fg(theme.engine.normal);
|
||||
let label_style = Style::new().fg(theme.engine.label);
|
||||
let value_style = Style::new().fg(theme.engine.value);
|
||||
@@ -373,7 +386,11 @@ fn render_settings(frame: &mut Frame, app: &App, area: Rect) {
|
||||
label_style,
|
||||
),
|
||||
render_selector(
|
||||
&format!("{}", app.audio.config.buffer_size),
|
||||
&if app.audio.config.host_name.to_lowercase().contains("jack") {
|
||||
"JACK managed".to_string()
|
||||
} else {
|
||||
format!("{}", app.audio.config.buffer_size)
|
||||
},
|
||||
buffer_focused,
|
||||
highlight,
|
||||
normal,
|
||||
@@ -420,6 +437,17 @@ fn render_settings(frame: &mut Frame, app: &App, area: Rect) {
|
||||
value_style,
|
||||
),
|
||||
]),
|
||||
Row::new(vec![
|
||||
Span::styled(" Audio host", label_style),
|
||||
Span::styled(
|
||||
if app.audio.config.host_name.is_empty() {
|
||||
"-".to_string()
|
||||
} else {
|
||||
app.audio.config.host_name.clone()
|
||||
},
|
||||
value_style,
|
||||
),
|
||||
]),
|
||||
];
|
||||
|
||||
let table = Table::new(rows, [Constraint::Length(14), Constraint::Fill(1)]);
|
||||
|
||||
Reference in New Issue
Block a user