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