use crossterm::event::{KeyCode, KeyEvent}; 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}; pub(crate) fn cycle_engine_setting(ctx: &mut InputContext, right: bool) { let sign = if right { 1 } else { -1 }; match ctx.app.audio.setting_kind { SettingKind::Channels => ctx.dispatch(AppCommand::AdjustAudioSetting { setting: SettingKind::Channels, delta: sign, }), SettingKind::BufferSize => ctx.dispatch(AppCommand::AdjustAudioSetting { setting: SettingKind::BufferSize, delta: sign * 64, }), SettingKind::Polyphony => ctx.dispatch(AppCommand::AdjustAudioSetting { setting: SettingKind::Polyphony, delta: sign, }), SettingKind::Nudge => { let prev = ctx.nudge_us.load(Ordering::Relaxed); let new_val = prev + sign as i64 * 1000; ctx.nudge_us .store(new_val.clamp(-100_000, 100_000), Ordering::Relaxed); } } ctx.app.save_settings(ctx.link); } pub(crate) fn cycle_link_setting(ctx: &mut InputContext, right: bool) { match ctx.app.audio.link_setting { LinkSetting::Enabled => ctx.link.set_enabled(!ctx.link.is_enabled()), LinkSetting::StartStopSync => ctx .link .set_start_stop_sync_enabled(!ctx.link.is_start_stop_sync_enabled()), LinkSetting::Quantum => { let delta = if right { 1.0 } else { -1.0 }; ctx.link.set_quantum(ctx.link.quantum() + delta); } } ctx.app.save_settings(ctx.link); } pub(crate) fn cycle_midi_output(ctx: &mut InputContext, right: bool) { let slot = ctx.app.audio.midi_output_slot; let all_devices = crate::midi::list_midi_outputs(); let selected = ctx.app.midi.selected_outputs(); let available: Vec<(usize, &crate::midi::MidiDeviceInfo)> = all_devices .iter() .enumerate() .filter(|(idx, _)| { selected[slot] == Some(*idx) || !selected .iter() .enumerate() .any(|(s, sel)| s != slot && *sel == Some(*idx)) }) .collect(); let total_options = available.len() + 1; let current_pos = selected[slot] .and_then(|idx| available.iter().position(|(i, _)| *i == idx)) .map(|p| p + 1) .unwrap_or(0); let new_pos = if right { (current_pos + 1) % total_options } else if current_pos == 0 { total_options - 1 } else { current_pos - 1 }; if new_pos == 0 { ctx.app.midi.disconnect_output(slot); ctx.dispatch(AppCommand::SetStatus(format!( "MIDI output {slot}: disconnected" ))); } else { let (device_idx, device) = available[new_pos - 1]; if ctx.app.midi.connect_output(slot, device_idx).is_ok() { ctx.dispatch(AppCommand::SetStatus(format!( "MIDI output {}: {}", slot, device.name ))); } } ctx.app.save_settings(ctx.link); } pub(crate) fn cycle_midi_input(ctx: &mut InputContext, right: bool) { let slot = ctx.app.audio.midi_input_slot; let all_devices = crate::midi::list_midi_inputs(); let available: Vec<(usize, &crate::midi::MidiDeviceInfo)> = all_devices .iter() .enumerate() .filter(|(idx, _)| { ctx.app.midi.selected_inputs[slot] == Some(*idx) || !ctx .app .midi .selected_inputs .iter() .enumerate() .any(|(s, sel)| s != slot && *sel == Some(*idx)) }) .collect(); let total_options = available.len() + 1; let current_pos = ctx.app.midi.selected_inputs[slot] .and_then(|idx| available.iter().position(|(i, _)| *i == idx)) .map(|p| p + 1) .unwrap_or(0); let new_pos = if right { (current_pos + 1) % total_options } else if current_pos == 0 { total_options - 1 } else { current_pos - 1 }; if new_pos == 0 { ctx.app.midi.disconnect_input(slot); ctx.dispatch(AppCommand::SetStatus(format!( "MIDI input {slot}: disconnected" ))); } else { let (device_idx, device) = available[new_pos - 1]; if ctx.app.midi.connect_input(slot, device_idx).is_ok() { ctx.dispatch(AppCommand::SetStatus(format!( "MIDI input {}: {}", slot, device.name ))); } } ctx.app.save_settings(ctx.link); } pub(super) fn handle_engine_page(ctx: &mut InputContext, key: KeyEvent) -> InputResult { match key.code { KeyCode::Char('q') if !ctx.app.plugin_mode => { ctx.dispatch(AppCommand::OpenModal(Modal::Confirm { action: ConfirmAction::Quit, selected: false, })); } 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::Settings => { ctx.dispatch(AppCommand::AudioSettingPrev); } EngineSection::Link => { ctx.app.audio.prev_link_setting(); } EngineSection::MidiOutput => { ctx.app.audio.prev_midi_output_slot(); } EngineSection::MidiInput => { ctx.app.audio.prev_midi_input_slot(); } EngineSection::Samples => { ctx.app.audio.sample_list.move_up(); } _ => {} }, KeyCode::Down => match ctx.app.audio.section { EngineSection::Devices if !ctx.app.plugin_mode => match ctx.app.audio.device_kind { DeviceKind::Output => { let count = ctx.app.audio.output_devices.len(); ctx.dispatch(AppCommand::AudioOutputListDown(count)); } DeviceKind::Input => { let count = ctx.app.audio.input_devices.len(); ctx.dispatch(AppCommand::AudioInputListDown(count)); } }, EngineSection::Settings => { ctx.dispatch(AppCommand::AudioSettingNext); } EngineSection::Link => { ctx.app.audio.next_link_setting(); } EngineSection::MidiOutput => { ctx.app.audio.next_midi_output_slot(); } EngineSection::MidiInput => { ctx.app.audio.next_midi_input_slot(); } EngineSection::Samples => { let count = ctx.app.audio.config.sample_paths.len(); ctx.app.audio.sample_list.move_down(count); } _ => {} }, 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(), } } } KeyCode::PageDown => { if !ctx.app.plugin_mode && ctx.app.audio.section == EngineSection::Devices { match ctx.app.audio.device_kind { DeviceKind::Output => { let count = ctx.app.audio.output_devices.len(); ctx.dispatch(AppCommand::AudioOutputPageDown(count)); } DeviceKind::Input => { let count = ctx.app.audio.input_devices.len(); ctx.dispatch(AppCommand::AudioInputPageDown(count)); } } } } KeyCode::Enter => { if !ctx.app.plugin_mode && ctx.app.audio.section == EngineSection::Devices { match ctx.app.audio.device_kind { DeviceKind::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; ctx.dispatch(AppCommand::SetOutputDevice(index.to_string())); ctx.app.save_settings(ctx.link); } } DeviceKind::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; ctx.dispatch(AppCommand::SetInputDevice(index.to_string())); ctx.app.save_settings(ctx.link); } } } } } KeyCode::Left => match ctx.app.audio.section { EngineSection::Devices if !ctx.app.plugin_mode => { ctx.dispatch(AppCommand::SetDeviceKind(DeviceKind::Output)); } EngineSection::Settings => cycle_engine_setting(ctx, false), EngineSection::Link => cycle_link_setting(ctx, false), EngineSection::MidiOutput => cycle_midi_output(ctx, false), EngineSection::MidiInput => cycle_midi_input(ctx, false), _ => {} }, KeyCode::Right => match ctx.app.audio.section { EngineSection::Devices if !ctx.app.plugin_mode => { ctx.dispatch(AppCommand::SetDeviceKind(DeviceKind::Input)); } EngineSection::Settings => cycle_engine_setting(ctx, true), EngineSection::Link => cycle_link_setting(ctx, true), EngineSection::MidiOutput => cycle_midi_output(ctx, true), EngineSection::MidiInput => cycle_midi_input(ctx, true), _ => {} }, KeyCode::Char('R') if !ctx.app.plugin_mode => { ctx.dispatch(AppCommand::AudioTriggerRestart); } KeyCode::Char('A') if ctx.app.audio.section == EngineSection::Samples => { use crate::state::file_browser::FileBrowserState; let mut state = FileBrowserState::new_load(String::new()); state.compute_audio_counts(); ctx.dispatch(AppCommand::OpenModal(Modal::AddSamplePath(Box::new(state)))); } KeyCode::Char('D') => { if ctx.app.audio.section == EngineSection::Samples { let cursor = ctx.app.audio.sample_list.cursor; ctx.dispatch(AppCommand::RemoveSamplePath(cursor)); ctx.app.save_settings(ctx.link); } else if !ctx.app.plugin_mode { ctx.dispatch(AppCommand::AudioRefreshDevices); let out_count = ctx.app.audio.output_devices.len(); let in_count = ctx.app.audio.input_devices.len(); ctx.dispatch(AppCommand::SetStatus(format!( "Found {out_count} output, {in_count} input devices" ))); } } KeyCode::Char('h') => { if !ctx.app.plugin_mode { let _ = ctx.audio_tx.load().send(AudioCommand::Hush); } } KeyCode::Char('p') => { if !ctx.app.plugin_mode { let _ = ctx.audio_tx.load().send(AudioCommand::Panic); } let _ = ctx.seq_cmd_tx.send(SeqCommand::StopAll); } KeyCode::Char('r') => ctx.dispatch(AppCommand::ResetPeakVoices), KeyCode::Char('t') if !ctx.app.plugin_mode => { let _ = ctx.audio_tx.load().send(AudioCommand::Evaluate { cmd: "/sound/sine/gate/0.5/decay/0.2".into(), tick: None, }); } KeyCode::Char('s') => super::open_save(ctx), KeyCode::Char('l') => super::open_load(ctx), KeyCode::Char('?') => { ctx.dispatch(AppCommand::OpenModal(Modal::KeybindingsHelp { scroll: 0 })); } KeyCode::Char(' ') if !ctx.app.plugin_mode => { ctx.dispatch(AppCommand::TogglePlaying); ctx.playing .store(ctx.app.playback.playing, Ordering::Relaxed); } _ => {} } InputResult::Continue }