Redo lost work
This commit is contained in:
3
Cargo.lock
generated
3
Cargo.lock
generated
@@ -1824,8 +1824,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "doux"
|
||||
version = "0.0.15"
|
||||
source = "git+https://github.com/sova-org/doux?tag=v0.0.15#29d8f055612f6141d7546d72b91e60026937b0fd"
|
||||
version = "0.0.18"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"clap",
|
||||
|
||||
@@ -51,7 +51,7 @@ cagire-forth = { path = "crates/forth" }
|
||||
cagire-markdown = { path = "crates/markdown" }
|
||||
cagire-project = { path = "crates/project" }
|
||||
cagire-ratatui = { path = "crates/ratatui" }
|
||||
doux = { git = "https://github.com/sova-org/doux", tag = "v0.0.15", features = ["native", "soundfont"] }
|
||||
doux = { git = "https://github.com/sova-org/doux", tag = "v0.0.18", features = ["native", "soundfont"] }
|
||||
rusty_link = "0.4"
|
||||
ratatui = "0.30"
|
||||
crossterm = "0.29"
|
||||
@@ -105,6 +105,9 @@ egui-baseview = { path = "plugins/egui-baseview" }
|
||||
[patch."https://github.com/RustAudio/baseview.git"]
|
||||
baseview = { path = "plugins/baseview" }
|
||||
|
||||
[patch.'https://github.com/sova-org/doux']
|
||||
doux = { path = "/Users/bubo/doux" }
|
||||
|
||||
[package.metadata.bundle.bin.cagire-desktop]
|
||||
name = "Cagire"
|
||||
identifier = "com.sova.cagire"
|
||||
|
||||
@@ -14,7 +14,7 @@ cagire = { path = "../..", default-features = false, features = ["block-renderer
|
||||
cagire-forth = { path = "../../crates/forth" }
|
||||
cagire-project = { path = "../../crates/project" }
|
||||
cagire-ratatui = { path = "../../crates/ratatui" }
|
||||
doux = { git = "https://github.com/sova-org/doux", tag = "v0.0.15", features = ["native", "soundfont"] }
|
||||
doux = { git = "https://github.com/sova-org/doux", tag = "v0.0.18", features = ["native", "soundfont"] }
|
||||
nih_plug = { git = "https://github.com/robbert-vdh/nih-plug", features = ["standalone"] }
|
||||
nih_plug_egui = { git = "https://github.com/robbert-vdh/nih-plug" }
|
||||
egui_ratatui = "2.1"
|
||||
|
||||
@@ -375,7 +375,11 @@ impl App {
|
||||
AppCommand::AudioSettingPrev => self.audio.prev_setting(self.plugin_mode),
|
||||
AppCommand::SetOutputDevice(name) => self.audio.config.output_device = Some(name),
|
||||
AppCommand::SetInputDevice(name) => self.audio.config.input_device = Some(name),
|
||||
AppCommand::SetDeviceKind(kind) => self.audio.device_kind = kind,
|
||||
AppCommand::SetDevicesFocus(focus) => self.audio.devices_focus = focus,
|
||||
AppCommand::CycleHost { right } => {
|
||||
self.audio.cycle_host(right);
|
||||
self.audio.trigger_restart();
|
||||
}
|
||||
AppCommand::AdjustAudioSetting { setting, delta } => {
|
||||
use crate::state::SettingKind;
|
||||
match setting {
|
||||
|
||||
@@ -16,6 +16,7 @@ impl App {
|
||||
pub fn save_settings(&self, link: &LinkState) {
|
||||
let settings = Settings {
|
||||
audio: crate::settings::AudioSettings {
|
||||
host: self.audio.config.selected_host.clone(),
|
||||
output_device: self.audio.config.output_device.clone(),
|
||||
input_device: self.audio.config.input_device.clone(),
|
||||
channels: self.audio.config.channels,
|
||||
|
||||
@@ -131,6 +131,7 @@ impl CagireDesktop {
|
||||
let new_audio_rx = sequencer.swap_audio_channel();
|
||||
|
||||
let new_config = AudioStreamConfig {
|
||||
host: self.app.audio.config.selected_host.clone(),
|
||||
output_device: self.app.audio.config.output_device.clone(),
|
||||
input_device: self.app.audio.config.input_device.clone(),
|
||||
channels: self.app.audio.config.channels,
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::path::PathBuf;
|
||||
|
||||
use crate::model::{FollowUp, LaunchQuantization, PatternSpeed};
|
||||
use crate::page::Page;
|
||||
use crate::state::{ColorScheme, DeviceKind, Modal, OptionsFocus, PatternField, ScriptField, SettingKind};
|
||||
use crate::state::{ColorScheme, DevicesFocus, Modal, OptionsFocus, PatternField, ScriptField, SettingKind};
|
||||
|
||||
pub enum AppCommand {
|
||||
// Undo/Redo
|
||||
@@ -252,7 +252,8 @@ pub enum AppCommand {
|
||||
AudioSettingPrev,
|
||||
SetOutputDevice(String),
|
||||
SetInputDevice(String),
|
||||
SetDeviceKind(DeviceKind),
|
||||
SetDevicesFocus(DevicesFocus),
|
||||
CycleHost { right: bool },
|
||||
AdjustAudioSetting {
|
||||
setting: SettingKind,
|
||||
delta: i32,
|
||||
|
||||
@@ -279,6 +279,7 @@ use super::AudioCommand;
|
||||
|
||||
#[cfg(feature = "cli")]
|
||||
pub struct AudioStreamConfig {
|
||||
pub host: Option<String>,
|
||||
pub output_device: Option<String>,
|
||||
pub input_device: Option<String>,
|
||||
pub channels: u16,
|
||||
@@ -317,10 +318,16 @@ pub fn build_stream(
|
||||
sample_paths: &[std::path::PathBuf],
|
||||
device_lost: Arc<AtomicBool>,
|
||||
) -> Result<BuildStreamResult, String> {
|
||||
let selection = match &config.host {
|
||||
Some(name) => doux::audio::HostSelection::Named(name.to_lowercase()),
|
||||
None => doux::audio::HostSelection::Auto,
|
||||
};
|
||||
let host = doux::audio::get_host(selection).map_err(|e| format!("{e}"))?;
|
||||
|
||||
let device = match &config.output_device {
|
||||
Some(name) => doux::audio::find_output_device(name)
|
||||
Some(name) => doux::audio::find_output_device_for(&host, name)
|
||||
.ok_or_else(|| format!("Device not found: {name}"))?,
|
||||
None => doux::audio::default_output_device().ok_or("No default output device")?,
|
||||
None => doux::audio::default_output_device_for(&host).ok_or("No default output device")?,
|
||||
};
|
||||
|
||||
let default_config = device.default_output_config().map_err(|e| e.to_string())?;
|
||||
@@ -329,10 +336,10 @@ pub fn build_stream(
|
||||
let max_channels = doux::audio::max_output_channels(&device);
|
||||
let channels = config.channels.min(max_channels).max(2);
|
||||
|
||||
let host_name = doux::audio::preferred_host().id().name().to_string();
|
||||
let is_jack = host_name.to_lowercase().contains("jack");
|
||||
let host_name = host.id().name().to_string();
|
||||
let host_managed_buffer = doux::audio::host_controls_buffer_size(&host);
|
||||
|
||||
let buffer_size = if config.buffer_size > 0 && !is_jack {
|
||||
let buffer_size = if config.buffer_size > 0 && !host_managed_buffer {
|
||||
cpal::BufferSize::Fixed(config.buffer_size)
|
||||
} else {
|
||||
cpal::BufferSize::Default
|
||||
@@ -370,7 +377,7 @@ pub fn build_stream(
|
||||
.input_device
|
||||
.as_ref()
|
||||
.and_then(|name| {
|
||||
let dev = doux::audio::find_input_device(name);
|
||||
let dev = doux::audio::find_input_device_for(&host, name);
|
||||
if dev.is_none() {
|
||||
eprintln!("input device not found: {name}");
|
||||
}
|
||||
|
||||
13
src/init.rs
13
src/init.rs
@@ -99,6 +99,18 @@ pub fn init(args: InitArgs) -> Init {
|
||||
});
|
||||
}
|
||||
|
||||
app.audio.config.selected_host = settings.audio.host.clone();
|
||||
if let Some(ref host_name) = app.audio.config.selected_host {
|
||||
if let Some(idx) = app
|
||||
.audio
|
||||
.available_hosts
|
||||
.iter()
|
||||
.position(|h| &h.name == host_name)
|
||||
{
|
||||
app.audio.host_index = idx;
|
||||
app.audio.refresh_devices_for_host();
|
||||
}
|
||||
}
|
||||
app.audio.config.output_device = args.output.or(settings.audio.output_device.clone());
|
||||
app.audio.config.input_device = args.input.or(settings.audio.input_device.clone());
|
||||
app.audio.config.channels = args.channels.unwrap_or(settings.audio.channels);
|
||||
@@ -210,6 +222,7 @@ pub fn init(args: InitArgs) -> Init {
|
||||
let (stream_error_tx, stream_error_rx) = crossbeam_channel::bounded(16);
|
||||
|
||||
let stream_config = AudioStreamConfig {
|
||||
host: app.audio.config.selected_host.clone(),
|
||||
output_device: app.audio.config.output_device.clone(),
|
||||
input_device: app.audio.config.input_device.clone(),
|
||||
channels: app.audio.config.channels,
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::sync::atomic::Ordering;
|
||||
use super::{InputContext, InputResult};
|
||||
use crate::commands::AppCommand;
|
||||
use crate::engine::{AudioCommand, SeqCommand};
|
||||
use crate::state::{ConfirmAction, DeviceKind, EngineSection, LinkSetting, Modal, SettingKind};
|
||||
use crate::state::{ConfirmAction, DevicesFocus, EngineSection, LinkSetting, Modal, SettingKind};
|
||||
|
||||
pub(crate) fn cycle_engine_setting(ctx: &mut InputContext, right: bool) {
|
||||
let sign = if right { 1 } else { -1 };
|
||||
@@ -13,10 +13,14 @@ pub(crate) fn cycle_engine_setting(ctx: &mut InputContext, right: bool) {
|
||||
setting: SettingKind::Channels,
|
||||
delta: sign,
|
||||
}),
|
||||
SettingKind::BufferSize => ctx.dispatch(AppCommand::AdjustAudioSetting {
|
||||
SettingKind::BufferSize => {
|
||||
if !ctx.app.audio.host_controls_buffer() {
|
||||
ctx.dispatch(AppCommand::AdjustAudioSetting {
|
||||
setting: SettingKind::BufferSize,
|
||||
delta: sign * 64,
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
SettingKind::Polyphony => ctx.dispatch(AppCommand::AdjustAudioSetting {
|
||||
setting: SettingKind::Polyphony,
|
||||
delta: sign,
|
||||
@@ -146,9 +150,22 @@ pub(super) fn handle_engine_page(ctx: &mut InputContext, key: KeyEvent) -> Input
|
||||
KeyCode::Tab => ctx.dispatch(AppCommand::AudioNextSection),
|
||||
KeyCode::BackTab => ctx.dispatch(AppCommand::AudioPrevSection),
|
||||
KeyCode::Up => match ctx.app.audio.section {
|
||||
EngineSection::Devices if !ctx.app.plugin_mode => match ctx.app.audio.device_kind {
|
||||
DeviceKind::Output => ctx.dispatch(AppCommand::AudioOutputListUp),
|
||||
DeviceKind::Input => ctx.dispatch(AppCommand::AudioInputListUp),
|
||||
EngineSection::Devices if !ctx.app.plugin_mode => match ctx.app.audio.devices_focus {
|
||||
DevicesFocus::Host => {}
|
||||
DevicesFocus::Output => {
|
||||
if ctx.app.audio.output_list.cursor == 0 {
|
||||
ctx.dispatch(AppCommand::SetDevicesFocus(DevicesFocus::Host));
|
||||
} else {
|
||||
ctx.dispatch(AppCommand::AudioOutputListUp);
|
||||
}
|
||||
}
|
||||
DevicesFocus::Input => {
|
||||
if ctx.app.audio.input_list.cursor == 0 {
|
||||
ctx.dispatch(AppCommand::SetDevicesFocus(DevicesFocus::Host));
|
||||
} else {
|
||||
ctx.dispatch(AppCommand::AudioInputListUp);
|
||||
}
|
||||
}
|
||||
},
|
||||
EngineSection::Settings => {
|
||||
ctx.dispatch(AppCommand::AudioSettingPrev);
|
||||
@@ -168,12 +185,15 @@ pub(super) fn handle_engine_page(ctx: &mut InputContext, key: KeyEvent) -> Input
|
||||
_ => {}
|
||||
},
|
||||
KeyCode::Down => match ctx.app.audio.section {
|
||||
EngineSection::Devices if !ctx.app.plugin_mode => match ctx.app.audio.device_kind {
|
||||
DeviceKind::Output => {
|
||||
EngineSection::Devices if !ctx.app.plugin_mode => match ctx.app.audio.devices_focus {
|
||||
DevicesFocus::Host => {
|
||||
ctx.dispatch(AppCommand::SetDevicesFocus(DevicesFocus::Output));
|
||||
}
|
||||
DevicesFocus::Output => {
|
||||
let count = ctx.app.audio.output_devices.len();
|
||||
ctx.dispatch(AppCommand::AudioOutputListDown(count));
|
||||
}
|
||||
DeviceKind::Input => {
|
||||
DevicesFocus::Input => {
|
||||
let count = ctx.app.audio.input_devices.len();
|
||||
ctx.dispatch(AppCommand::AudioInputListDown(count));
|
||||
}
|
||||
@@ -198,20 +218,22 @@ pub(super) fn handle_engine_page(ctx: &mut InputContext, key: KeyEvent) -> Input
|
||||
},
|
||||
KeyCode::PageUp => {
|
||||
if !ctx.app.plugin_mode && ctx.app.audio.section == EngineSection::Devices {
|
||||
match ctx.app.audio.device_kind {
|
||||
DeviceKind::Output => ctx.dispatch(AppCommand::AudioOutputPageUp),
|
||||
DeviceKind::Input => ctx.app.audio.input_list.page_up(),
|
||||
match ctx.app.audio.devices_focus {
|
||||
DevicesFocus::Host => {}
|
||||
DevicesFocus::Output => ctx.dispatch(AppCommand::AudioOutputPageUp),
|
||||
DevicesFocus::Input => ctx.app.audio.input_list.page_up(),
|
||||
}
|
||||
}
|
||||
}
|
||||
KeyCode::PageDown => {
|
||||
if !ctx.app.plugin_mode && ctx.app.audio.section == EngineSection::Devices {
|
||||
match ctx.app.audio.device_kind {
|
||||
DeviceKind::Output => {
|
||||
match ctx.app.audio.devices_focus {
|
||||
DevicesFocus::Host => {}
|
||||
DevicesFocus::Output => {
|
||||
let count = ctx.app.audio.output_devices.len();
|
||||
ctx.dispatch(AppCommand::AudioOutputPageDown(count));
|
||||
}
|
||||
DeviceKind::Input => {
|
||||
DevicesFocus::Input => {
|
||||
let count = ctx.app.audio.input_devices.len();
|
||||
ctx.dispatch(AppCommand::AudioInputPageDown(count));
|
||||
}
|
||||
@@ -220,8 +242,9 @@ pub(super) fn handle_engine_page(ctx: &mut InputContext, key: KeyEvent) -> Input
|
||||
}
|
||||
KeyCode::Enter => {
|
||||
if !ctx.app.plugin_mode && ctx.app.audio.section == EngineSection::Devices {
|
||||
match ctx.app.audio.device_kind {
|
||||
DeviceKind::Output => {
|
||||
match ctx.app.audio.devices_focus {
|
||||
DevicesFocus::Host => {}
|
||||
DevicesFocus::Output => {
|
||||
let cursor = ctx.app.audio.output_list.cursor;
|
||||
if cursor < ctx.app.audio.output_devices.len() {
|
||||
let index = ctx.app.audio.output_devices[cursor].index;
|
||||
@@ -229,7 +252,7 @@ pub(super) fn handle_engine_page(ctx: &mut InputContext, key: KeyEvent) -> Input
|
||||
ctx.app.save_settings(ctx.link);
|
||||
}
|
||||
}
|
||||
DeviceKind::Input => {
|
||||
DevicesFocus::Input => {
|
||||
let cursor = ctx.app.audio.input_list.cursor;
|
||||
if cursor < ctx.app.audio.input_devices.len() {
|
||||
let index = ctx.app.audio.input_devices[cursor].index;
|
||||
@@ -241,9 +264,16 @@ pub(super) fn handle_engine_page(ctx: &mut InputContext, key: KeyEvent) -> Input
|
||||
}
|
||||
}
|
||||
KeyCode::Left => match ctx.app.audio.section {
|
||||
EngineSection::Devices if !ctx.app.plugin_mode => {
|
||||
ctx.dispatch(AppCommand::SetDeviceKind(DeviceKind::Output));
|
||||
EngineSection::Devices if !ctx.app.plugin_mode => match ctx.app.audio.devices_focus {
|
||||
DevicesFocus::Host => {
|
||||
ctx.dispatch(AppCommand::CycleHost { right: false });
|
||||
ctx.app.save_settings(ctx.link);
|
||||
}
|
||||
DevicesFocus::Output => {}
|
||||
DevicesFocus::Input => {
|
||||
ctx.dispatch(AppCommand::SetDevicesFocus(DevicesFocus::Output));
|
||||
}
|
||||
},
|
||||
EngineSection::Settings => cycle_engine_setting(ctx, false),
|
||||
EngineSection::Link => cycle_link_setting(ctx, false),
|
||||
EngineSection::MidiOutput => cycle_midi_output(ctx, false),
|
||||
@@ -251,9 +281,16 @@ pub(super) fn handle_engine_page(ctx: &mut InputContext, key: KeyEvent) -> Input
|
||||
_ => {}
|
||||
},
|
||||
KeyCode::Right => match ctx.app.audio.section {
|
||||
EngineSection::Devices if !ctx.app.plugin_mode => {
|
||||
ctx.dispatch(AppCommand::SetDeviceKind(DeviceKind::Input));
|
||||
EngineSection::Devices if !ctx.app.plugin_mode => match ctx.app.audio.devices_focus {
|
||||
DevicesFocus::Host => {
|
||||
ctx.dispatch(AppCommand::CycleHost { right: true });
|
||||
ctx.app.save_settings(ctx.link);
|
||||
}
|
||||
DevicesFocus::Output => {
|
||||
ctx.dispatch(AppCommand::SetDevicesFocus(DevicesFocus::Input));
|
||||
}
|
||||
DevicesFocus::Input => {}
|
||||
},
|
||||
EngineSection::Settings => cycle_engine_setting(ctx, true),
|
||||
EngineSection::Link => cycle_link_setting(ctx, true),
|
||||
EngineSection::MidiOutput => cycle_midi_output(ctx, true),
|
||||
|
||||
@@ -126,6 +126,7 @@ fn main() -> io::Result<()> {
|
||||
let new_audio_rx = sequencer.swap_audio_channel();
|
||||
|
||||
let new_config = AudioStreamConfig {
|
||||
host: app.audio.config.selected_host.clone(),
|
||||
output_device: app.audio.config.output_device.clone(),
|
||||
input_device: app.audio.config.input_device.clone(),
|
||||
channels: app.audio.config.channels,
|
||||
|
||||
@@ -26,6 +26,8 @@ pub struct Settings {
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct AudioSettings {
|
||||
#[serde(default)]
|
||||
pub host: Option<String>,
|
||||
pub output_device: Option<String>,
|
||||
pub input_device: Option<String>,
|
||||
pub channels: u16,
|
||||
@@ -96,6 +98,7 @@ pub struct LinkSettings {
|
||||
impl Default for AudioSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
host: None,
|
||||
output_device: None,
|
||||
input_device: None,
|
||||
channels: 2,
|
||||
|
||||
@@ -105,6 +105,7 @@ impl RefreshRate {
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AudioConfig {
|
||||
pub selected_host: Option<String>,
|
||||
pub output_device: Option<String>,
|
||||
pub input_device: Option<String>,
|
||||
pub channels: u16,
|
||||
@@ -133,6 +134,7 @@ pub struct AudioConfig {
|
||||
impl Default for AudioConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
selected_host: None,
|
||||
output_device: None,
|
||||
input_device: None,
|
||||
channels: 2,
|
||||
@@ -234,7 +236,8 @@ impl CyclicEnum for LinkSetting {
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub enum DeviceKind {
|
||||
pub enum DevicesFocus {
|
||||
Host,
|
||||
#[default]
|
||||
Output,
|
||||
Input,
|
||||
@@ -293,11 +296,13 @@ impl Default for Metrics {
|
||||
pub struct AudioSettings {
|
||||
pub config: AudioConfig,
|
||||
pub section: EngineSection,
|
||||
pub device_kind: DeviceKind,
|
||||
pub devices_focus: DevicesFocus,
|
||||
pub setting_kind: SettingKind,
|
||||
pub link_setting: LinkSetting,
|
||||
pub midi_output_slot: usize,
|
||||
pub midi_input_slot: usize,
|
||||
pub available_hosts: Vec<doux::audio::AudioHostInfo>,
|
||||
pub host_index: usize,
|
||||
pub output_devices: Vec<AudioDeviceInfo>,
|
||||
pub input_devices: Vec<AudioDeviceInfo>,
|
||||
pub output_list: ListSelectState,
|
||||
@@ -310,16 +315,25 @@ pub struct AudioSettings {
|
||||
|
||||
impl Default for AudioSettings {
|
||||
fn default() -> Self {
|
||||
let hosts = doux::audio::list_hosts();
|
||||
let preferred = doux::audio::preferred_host();
|
||||
let preferred_name = preferred.id().name().to_string();
|
||||
let host_index = hosts
|
||||
.iter()
|
||||
.position(|h| h.name == preferred_name)
|
||||
.unwrap_or(0);
|
||||
Self {
|
||||
config: AudioConfig::default(),
|
||||
section: EngineSection::default(),
|
||||
device_kind: DeviceKind::default(),
|
||||
devices_focus: DevicesFocus::default(),
|
||||
setting_kind: SettingKind::default(),
|
||||
link_setting: LinkSetting::default(),
|
||||
midi_output_slot: 0,
|
||||
midi_input_slot: 0,
|
||||
output_devices: doux::audio::list_output_devices(),
|
||||
input_devices: doux::audio::list_input_devices(),
|
||||
available_hosts: hosts,
|
||||
host_index,
|
||||
output_devices: doux::audio::list_output_devices_for(&preferred),
|
||||
input_devices: doux::audio::list_input_devices_for(&preferred),
|
||||
output_list: ListSelectState {
|
||||
cursor: 0,
|
||||
scroll_offset: 0,
|
||||
@@ -344,11 +358,13 @@ impl AudioSettings {
|
||||
Self {
|
||||
config: AudioConfig::default(),
|
||||
section: EngineSection::Settings,
|
||||
device_kind: DeviceKind::default(),
|
||||
devices_focus: DevicesFocus::default(),
|
||||
setting_kind: SettingKind::Polyphony,
|
||||
link_setting: LinkSetting::default(),
|
||||
midi_output_slot: 0,
|
||||
midi_input_slot: 0,
|
||||
available_hosts: Vec::new(),
|
||||
host_index: 0,
|
||||
output_devices: Vec::new(),
|
||||
input_devices: Vec::new(),
|
||||
output_list: ListSelectState {
|
||||
@@ -370,8 +386,75 @@ impl AudioSettings {
|
||||
}
|
||||
|
||||
pub fn refresh_devices(&mut self) {
|
||||
self.output_devices = doux::audio::list_output_devices();
|
||||
self.input_devices = doux::audio::list_input_devices();
|
||||
self.refresh_devices_for_host();
|
||||
}
|
||||
|
||||
pub fn refresh_devices_for_host(&mut self) {
|
||||
let host_name = self
|
||||
.available_hosts
|
||||
.get(self.host_index)
|
||||
.map(|h| h.name.clone());
|
||||
let selection = match host_name {
|
||||
Some(name) => doux::audio::HostSelection::Named(name.to_lowercase()),
|
||||
None => doux::audio::HostSelection::Auto,
|
||||
};
|
||||
if let Ok(host) = doux::audio::get_host(selection) {
|
||||
self.output_devices = doux::audio::list_output_devices_for(&host);
|
||||
self.input_devices = doux::audio::list_input_devices_for(&host);
|
||||
} else {
|
||||
self.output_devices = Vec::new();
|
||||
self.input_devices = Vec::new();
|
||||
}
|
||||
self.output_list.cursor = 0;
|
||||
self.output_list.scroll_offset = 0;
|
||||
self.input_list.cursor = 0;
|
||||
self.input_list.scroll_offset = 0;
|
||||
}
|
||||
|
||||
pub fn cycle_host(&mut self, right: bool) {
|
||||
let available: Vec<usize> = self
|
||||
.available_hosts
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, h)| h.available)
|
||||
.map(|(i, _)| i)
|
||||
.collect();
|
||||
if available.len() <= 1 {
|
||||
return;
|
||||
}
|
||||
let current_pos = available
|
||||
.iter()
|
||||
.position(|&i| i == self.host_index)
|
||||
.unwrap_or(0);
|
||||
let new_pos = if right {
|
||||
(current_pos + 1) % available.len()
|
||||
} else if current_pos == 0 {
|
||||
available.len() - 1
|
||||
} else {
|
||||
current_pos - 1
|
||||
};
|
||||
self.host_index = available[new_pos];
|
||||
let name = self.available_hosts[self.host_index].name.clone();
|
||||
self.config.selected_host = Some(name);
|
||||
self.config.output_device = None;
|
||||
self.config.input_device = None;
|
||||
self.refresh_devices_for_host();
|
||||
}
|
||||
|
||||
pub fn host_controls_buffer(&self) -> bool {
|
||||
let host_name = self
|
||||
.available_hosts
|
||||
.get(self.host_index)
|
||||
.map(|h| h.name.clone());
|
||||
let selection = match host_name {
|
||||
Some(name) => doux::audio::HostSelection::Named(name.to_lowercase()),
|
||||
None => doux::audio::HostSelection::Auto,
|
||||
};
|
||||
if let Ok(host) = doux::audio::get_host(selection) {
|
||||
doux::audio::host_controls_buffer_size(&host)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next_section(&mut self, plugin_mode: bool) {
|
||||
|
||||
@@ -28,7 +28,7 @@ pub mod sample_browser;
|
||||
pub mod undo;
|
||||
pub mod ui;
|
||||
|
||||
pub use audio::{AudioSettings, DeviceKind, EngineSection, LinkSetting, MainLayout, Metrics, ScopeMode, SettingKind, SpectrumMode};
|
||||
pub use audio::{AudioSettings, DevicesFocus, EngineSection, LinkSetting, MainLayout, Metrics, ScopeMode, SettingKind, SpectrumMode};
|
||||
pub use color_scheme::ColorScheme;
|
||||
pub use editor::{
|
||||
CopiedStepData, CopiedSteps, EditorContext, EditorTarget, EuclideanField, PatternField,
|
||||
|
||||
@@ -8,7 +8,7 @@ use ratatui::Frame;
|
||||
use crate::app::App;
|
||||
use crate::engine::LinkState;
|
||||
use crate::midi;
|
||||
use crate::state::{DeviceKind, EngineSection, LinkSetting, SettingKind};
|
||||
use crate::state::{DevicesFocus, EngineSection, LinkSetting, SettingKind};
|
||||
use crate::theme;
|
||||
use crate::widgets::{
|
||||
render_scroll_indicators, render_section_header, IndicatorAlign, Scope,
|
||||
@@ -607,18 +607,46 @@ pub fn list_height(item_count: usize) -> u16 {
|
||||
pub fn devices_section_height(app: &App) -> u16 {
|
||||
let output_h = list_height(app.audio.output_devices.len());
|
||||
let input_h = list_height(app.audio.input_devices.len());
|
||||
3 + output_h.max(input_h)
|
||||
4 + output_h.max(input_h)
|
||||
}
|
||||
|
||||
fn render_devices(frame: &mut Frame, app: &App, area: Rect) {
|
||||
let theme = theme::get();
|
||||
let section_focused = app.audio.section == EngineSection::Devices;
|
||||
|
||||
let [header_area, content_area] =
|
||||
Layout::vertical([Constraint::Length(2), Constraint::Min(1)]).areas(area);
|
||||
let [header_area, host_area, content_area] =
|
||||
Layout::vertical([Constraint::Length(2), Constraint::Length(1), Constraint::Min(1)])
|
||||
.areas(area);
|
||||
|
||||
render_section_header(frame, "DEVICES", section_focused, header_area);
|
||||
|
||||
// Host row
|
||||
let host_focused = section_focused && app.audio.devices_focus == DevicesFocus::Host;
|
||||
let host_name = app
|
||||
.audio
|
||||
.available_hosts
|
||||
.get(app.audio.host_index)
|
||||
.map(|h| h.name.as_str())
|
||||
.unwrap_or("-");
|
||||
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 host_line = Line::from(vec![
|
||||
Span::styled(
|
||||
if host_focused {
|
||||
"> Driver "
|
||||
} else {
|
||||
" Driver "
|
||||
},
|
||||
label_style,
|
||||
),
|
||||
render_selector(host_name, host_focused, highlight, normal),
|
||||
]);
|
||||
frame.render_widget(Paragraph::new(host_line), host_area);
|
||||
|
||||
let [output_col, separator, input_col] = Layout::horizontal([
|
||||
Constraint::Percentage(48),
|
||||
Constraint::Length(3),
|
||||
@@ -626,8 +654,8 @@ fn render_devices(frame: &mut Frame, app: &App, area: Rect) {
|
||||
])
|
||||
.areas(content_area);
|
||||
|
||||
let output_focused = section_focused && app.audio.device_kind == DeviceKind::Output;
|
||||
let input_focused = section_focused && app.audio.device_kind == DeviceKind::Input;
|
||||
let output_focused = section_focused && app.audio.devices_focus == DevicesFocus::Output;
|
||||
let input_focused = section_focused && app.audio.devices_focus == DevicesFocus::Input;
|
||||
|
||||
render_device_column(
|
||||
frame,
|
||||
@@ -758,8 +786,8 @@ fn render_settings(frame: &mut Frame, app: &App, area: Rect) {
|
||||
label_style,
|
||||
),
|
||||
render_selector(
|
||||
&if app.audio.config.host_name.to_lowercase().contains("jack") {
|
||||
"JACK managed".to_string()
|
||||
&if app.audio.host_controls_buffer() {
|
||||
"Host managed".to_string()
|
||||
} else {
|
||||
format!("{}", app.audio.config.buffer_size)
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user