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
34 changes: 34 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ cpal = "0.17"
crossbeam-channel = "0.5"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
log = "0.4"
simplelog = "0.12"
2 changes: 1 addition & 1 deletion src/audio/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ pub fn start_audio_stream(
}
},
|err| {
eprintln!("audio stream error: {}", err);
log::error!("audio stream error: {}", err);
},
None,
)
Expand Down
85 changes: 38 additions & 47 deletions src/keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -818,51 +818,38 @@ fn handle_knobs(app: &mut App, key: KeyEvent) {
let has_alt = key.modifiers.contains(KeyModifiers::ALT);

match key.code {
KeyCode::Left if has_shift => {
// Shift+Left: change track up
app.ui.drum_ctrl_track = if t == 0 { NUM_DRUM_TRACKS - 1 } else { t - 1 };
app.ui.drum_cursor_track = app.ui.drum_ctrl_track;
}
KeyCode::Right if has_shift => {
// Shift+Right: change track down
app.ui.drum_ctrl_track = (t + 1) % NUM_DRUM_TRACKS;
app.ui.drum_cursor_track = app.ui.drum_ctrl_track;
}
KeyCode::Left => {
app.ui.drum_ctrl_field = app.ui.drum_ctrl_field.prev_knob();
}
KeyCode::Right => {
app.ui.drum_ctrl_field = app.ui.drum_ctrl_field.next_knob();
}
KeyCode::Up if has_shift || has_alt => {
KeyCode::Up => {
// Up always increases the selected knob value
adjust_drum_field(app, t, PARAM_INCREMENT);
if has_alt {
let _ = app.tx_to_audio.send(UiToAudio::TriggerDrum(TRACK_IDS[t]));
app.ui.flash_track(t);
}
}
KeyCode::Down if has_shift || has_alt => {
KeyCode::Down => {
// Down always decreases the selected knob value
adjust_drum_field(app, t, -PARAM_INCREMENT);
if has_alt {
let _ = app.tx_to_audio.send(UiToAudio::TriggerDrum(TRACK_IDS[t]));
app.ui.flash_track(t);
}
}
KeyCode::Up => {
// Move between row 1 (0-4) and row 2 (5-9), or change track at edge
if let Some(idx) = app.ui.drum_ctrl_field.knob_index() {
if idx >= 5 {
// Row 2 -> Row 1 (same column)
app.ui.drum_ctrl_field = crate::app::KNOB_FIELDS[idx - 5];
} else {
// Row 1 -> change track up
app.ui.drum_ctrl_track = if t == 0 { NUM_DRUM_TRACKS - 1 } else { t - 1 };
app.ui.drum_cursor_track = app.ui.drum_ctrl_track;
}
}
}
KeyCode::Down => {
if let Some(idx) = app.ui.drum_ctrl_field.knob_index() {
if idx < 5 {
// Row 1 -> Row 2 (same column)
app.ui.drum_ctrl_field = crate::app::KNOB_FIELDS[idx + 5];
} else {
// Row 2 -> change track down
app.ui.drum_ctrl_track = (t + 1) % NUM_DRUM_TRACKS;
app.ui.drum_cursor_track = app.ui.drum_ctrl_track;
}
}
}
_ => {}
}
}
Expand Down Expand Up @@ -1051,25 +1038,8 @@ fn handle_synth_controls(app: &mut App, key: KeyEvent, synth_id: SynthId) {
ui.ctrl_field = SYNTH_CTRL_ROWS[r][c + 1];
}
}
KeyCode::Up if has_shift || has_alt => {
adjust_synth_field(app, synth_id, PARAM_INCREMENT);
if has_alt {
let ui = synth_ui_mut(app, synth_id);
let note = ui.octave * 12 + 12;
ui.flash = 6;
let _ = app.tx_to_audio.send(UiToAudio::TriggerSynth(synth_id, note));
}
}
KeyCode::Down if has_shift || has_alt => {
adjust_synth_field(app, synth_id, -PARAM_INCREMENT);
if has_alt {
let ui = synth_ui_mut(app, synth_id);
let note = ui.octave * 12 + 12;
ui.flash = 6;
let _ = app.tx_to_audio.send(UiToAudio::TriggerSynth(synth_id, note));
}
}
KeyCode::Up => {
KeyCode::Up if has_shift => {
// Shift+Up: navigate to row above
let ui = synth_ui_mut(app, synth_id);
let (r, c) = find_synth_field_pos(ui.ctrl_field);
if r > 0 {
Expand All @@ -1078,7 +1048,8 @@ fn handle_synth_controls(app: &mut App, key: KeyEvent, synth_id: SynthId) {
ui.ctrl_field = new_row[new_c];
}
}
KeyCode::Down => {
KeyCode::Down if has_shift => {
// Shift+Down: navigate to row below
let ui = synth_ui_mut(app, synth_id);
let (r, c) = find_synth_field_pos(ui.ctrl_field);
if r + 1 < SYNTH_CTRL_ROWS.len() {
Expand All @@ -1087,6 +1058,26 @@ fn handle_synth_controls(app: &mut App, key: KeyEvent, synth_id: SynthId) {
ui.ctrl_field = new_row[new_c];
}
}
KeyCode::Up => {
// Up always increases the selected control's value
adjust_synth_field(app, synth_id, PARAM_INCREMENT);
if has_alt {
let ui = synth_ui_mut(app, synth_id);
let note = ui.octave * 12 + 12;
ui.flash = 6;
let _ = app.tx_to_audio.send(UiToAudio::TriggerSynth(synth_id, note));
}
}
KeyCode::Down => {
// Down always decreases the selected control's value
adjust_synth_field(app, synth_id, -PARAM_INCREMENT);
if has_alt {
let ui = synth_ui_mut(app, synth_id);
let note = ui.octave * 12 + 12;
ui.flash = 6;
let _ = app.tx_to_audio.send(UiToAudio::TriggerSynth(synth_id, note));
}
}
_ => {}
}
}
Expand Down
16 changes: 15 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,18 @@ use crossterm::terminal::{self, EnterAlternateScreen, LeaveAlternateScreen};
use ratatui::backend::CrosstermBackend;
use ratatui::Terminal;

/// Redirect all log output to `~/.local/share/textstep/textstep.log` so that
/// runtime diagnostics never corrupt the TUI display. Errors are silently
/// swallowed if the file cannot be created (e.g. read-only FS).
fn init_logger() {
use simplelog::{Config, LevelFilter, WriteLogger};
let log_dir = sequencer::project::data_dir();
let _ = std::fs::create_dir_all(&log_dir);
if let Ok(file) = std::fs::File::create(log_dir.join("textstep.log")) {
let _ = WriteLogger::init(LevelFilter::Warn, Config::default(), file);
}
}

/// Sets up cross-thread channels, starts the audio stream, initializes the
/// terminal, and enters the main UI loop. The audio stream is kept alive
/// until this function returns.
Expand All @@ -32,6 +44,8 @@ fn main() -> io::Result<()> {
return Ok(());
}

init_logger();

// Create channels for UI <-> Audio communication
let (tx_to_audio, rx_from_ui) = crossbeam_channel::bounded(64);
let (tx_to_ui, rx_from_audio) = crossbeam_channel::bounded(16);
Expand All @@ -43,7 +57,7 @@ fn main() -> io::Result<()> {
let _stream = match audio::start_audio_stream(rx_from_ui, tx_to_ui, Arc::clone(&display_buf)) {
Ok(s) => s,
Err(e) => {
eprintln!("Audio error: {e}");
log::error!("Audio error: {e}");
return Ok(());
}
};
Expand Down
6 changes: 3 additions & 3 deletions src/ui/help_overlay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,19 +72,19 @@ pub fn render_help(f: &mut Frame, area: Rect) {
"Shift+Tab", "Prev section",
"Shift+S", "Solo track"),
row3("- / =", "BPM -1 / +1",
"Arrows", "Move cursor",
"Arrows", "Move/adjust",
"Shift+V", "Master volume"),
row3("Shift+- / +","BPM -10 / +10",
"Enter", "Toggle step",
"Shift+C", "Compressor"),
row3("` (backtick)","Record on/off",
"Shift+Up/Dn","Adjust value",
"Up/Down", "Adjust value",
"Shift+T", "Tube saturator"),
row3("l", "Loop on/off",
"Alt+Up/Dn", "Adjust+audition",
"Alt+R", "Randomize"),
row3("Shift+L", "Loop length",
";", "Page SYN/AMP/FX",
"Shift+Up/Dn","Row nav (synth)",
"F2", "Toggle synths"),
row3("< / >", "Swing ±5%",
"~", "Spectrum/VU",
Expand Down
Loading