seq continues
This commit is contained in:
263
seq/src/main.rs
263
seq/src/main.rs
@@ -7,6 +7,7 @@ mod page;
|
||||
mod script;
|
||||
mod ui;
|
||||
mod views;
|
||||
mod widgets;
|
||||
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
@@ -24,6 +25,7 @@ use ratatui::prelude::CrosstermBackend;
|
||||
use ratatui::Terminal;
|
||||
|
||||
use app::{App, Focus, Modal};
|
||||
use audio::{SlotChange, MAX_SLOTS};
|
||||
use link::LinkState;
|
||||
use model::Project;
|
||||
use page::Page;
|
||||
@@ -36,12 +38,14 @@ fn main() -> io::Result<()> {
|
||||
link.enable();
|
||||
|
||||
let playing = Arc::new(AtomicBool::new(true));
|
||||
let playback_step = Arc::new(AtomicUsize::new(0));
|
||||
let event_count = Arc::new(AtomicUsize::new(0));
|
||||
let playback_bank = Arc::new(AtomicUsize::new(0));
|
||||
let playback_pattern = Arc::new(AtomicUsize::new(0));
|
||||
let queued_bank = Arc::new(AtomicUsize::new(usize::MAX));
|
||||
let queued_pattern = Arc::new(AtomicUsize::new(usize::MAX));
|
||||
|
||||
// Slot state shared between audio thread and UI
|
||||
let slot_steps: [Arc<AtomicUsize>; MAX_SLOTS] = std::array::from_fn(|_| Arc::new(AtomicUsize::new(0)));
|
||||
let slot_data: Arc<Mutex<[(bool, usize, usize); MAX_SLOTS]>> =
|
||||
Arc::new(Mutex::new([(false, 0, 0); MAX_SLOTS]));
|
||||
let slot_changes: Arc<Mutex<Vec<SlotChange>>> = Arc::new(Mutex::new(Vec::new()));
|
||||
|
||||
let mut app = App::new(TEMPO, QUANTUM);
|
||||
|
||||
let engine = Arc::new(Mutex::new(Engine::new(44100.0)));
|
||||
@@ -52,12 +56,12 @@ fn main() -> io::Result<()> {
|
||||
Arc::clone(&link),
|
||||
Arc::clone(&playing),
|
||||
Arc::clone(&project),
|
||||
Arc::clone(&playback_step),
|
||||
slot_steps.clone(),
|
||||
Arc::clone(&event_count),
|
||||
Arc::clone(&playback_bank),
|
||||
Arc::clone(&playback_pattern),
|
||||
Arc::clone(&queued_bank),
|
||||
Arc::clone(&queued_pattern),
|
||||
Arc::clone(&slot_data),
|
||||
Arc::clone(&slot_changes),
|
||||
Arc::clone(&app.variables),
|
||||
Arc::clone(&app.rng),
|
||||
);
|
||||
|
||||
{
|
||||
@@ -75,7 +79,6 @@ fn main() -> io::Result<()> {
|
||||
loop {
|
||||
app.update_from_link(&link);
|
||||
app.playing = playing.load(Ordering::Relaxed);
|
||||
app.playback_step = playback_step.load(Ordering::Relaxed);
|
||||
app.event_count = event_count.load(Ordering::Relaxed);
|
||||
|
||||
{
|
||||
@@ -89,14 +92,19 @@ fn main() -> io::Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
app.playback_bank = playback_bank.load(Ordering::Relaxed);
|
||||
app.playback_pattern = playback_pattern.load(Ordering::Relaxed);
|
||||
// Sync slot state from audio thread
|
||||
{
|
||||
let sd = slot_data.lock().unwrap();
|
||||
app.slot_data = *sd;
|
||||
}
|
||||
for (i, step_atomic) in slot_steps.iter().enumerate() {
|
||||
app.slot_steps[i] = step_atomic.load(Ordering::Relaxed);
|
||||
}
|
||||
|
||||
if app.queued_bank.is_some() {
|
||||
queued_bank.store(app.queued_bank.unwrap(), Ordering::Relaxed);
|
||||
queued_pattern.store(app.queued_pattern.unwrap(), Ordering::Relaxed);
|
||||
app.queued_bank = None;
|
||||
app.queued_pattern = None;
|
||||
// Push queued changes to audio thread
|
||||
if !app.queued_changes.is_empty() {
|
||||
let mut changes = slot_changes.lock().unwrap();
|
||||
changes.extend(app.queued_changes.drain(..));
|
||||
}
|
||||
|
||||
{
|
||||
@@ -111,11 +119,21 @@ fn main() -> io::Result<()> {
|
||||
app.clear_status();
|
||||
|
||||
match &mut app.modal {
|
||||
Modal::ConfirmQuit => match key.code {
|
||||
Modal::ConfirmQuit { ref mut selected } => match key.code {
|
||||
KeyCode::Char('y') | KeyCode::Char('Y') => break,
|
||||
KeyCode::Char('n') | KeyCode::Char('N') | KeyCode::Esc => {
|
||||
app.modal = Modal::None;
|
||||
}
|
||||
KeyCode::Left | KeyCode::Right => {
|
||||
*selected = !*selected;
|
||||
}
|
||||
KeyCode::Enter => {
|
||||
if *selected {
|
||||
break;
|
||||
} else {
|
||||
app.modal = Modal::None;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Modal::SaveAs(path) => match key.code {
|
||||
@@ -152,56 +170,42 @@ fn main() -> io::Result<()> {
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Modal::PatternPicker { ref mut cursor } => {
|
||||
match key.code {
|
||||
KeyCode::Enter => {
|
||||
let selected = *cursor;
|
||||
app.modal = Modal::None;
|
||||
app.select_edit_pattern(selected);
|
||||
}
|
||||
KeyCode::Esc => {
|
||||
app.modal = Modal::None;
|
||||
}
|
||||
KeyCode::Left => {
|
||||
*cursor = (*cursor + 15) % 16;
|
||||
}
|
||||
KeyCode::Right => {
|
||||
*cursor = (*cursor + 1) % 16;
|
||||
}
|
||||
KeyCode::Up => {
|
||||
*cursor = (*cursor + 12) % 16;
|
||||
}
|
||||
KeyCode::Down => {
|
||||
*cursor = (*cursor + 4) % 16;
|
||||
}
|
||||
_ => {}
|
||||
Modal::RenameBank { bank, name } => match key.code {
|
||||
KeyCode::Enter => {
|
||||
let bank_idx = *bank;
|
||||
let new_name = if name.trim().is_empty() { None } else { Some(name.clone()) };
|
||||
app.project.banks[bank_idx].name = new_name;
|
||||
app.modal = Modal::None;
|
||||
}
|
||||
}
|
||||
Modal::BankPicker { ref mut cursor } => {
|
||||
match key.code {
|
||||
KeyCode::Enter => {
|
||||
let selected = *cursor;
|
||||
app.modal = Modal::None;
|
||||
app.select_edit_bank(selected);
|
||||
}
|
||||
KeyCode::Esc => {
|
||||
app.modal = Modal::None;
|
||||
}
|
||||
KeyCode::Left => {
|
||||
*cursor = (*cursor + 15) % 16;
|
||||
}
|
||||
KeyCode::Right => {
|
||||
*cursor = (*cursor + 1) % 16;
|
||||
}
|
||||
KeyCode::Up => {
|
||||
*cursor = (*cursor + 12) % 16;
|
||||
}
|
||||
KeyCode::Down => {
|
||||
*cursor = (*cursor + 4) % 16;
|
||||
}
|
||||
_ => {}
|
||||
KeyCode::Esc => {
|
||||
app.modal = Modal::None;
|
||||
}
|
||||
}
|
||||
KeyCode::Backspace => {
|
||||
name.pop();
|
||||
}
|
||||
KeyCode::Char(c) => {
|
||||
name.push(c);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Modal::RenamePattern { bank, pattern, name } => match key.code {
|
||||
KeyCode::Enter => {
|
||||
let (bank_idx, pattern_idx) = (*bank, *pattern);
|
||||
let new_name = if name.trim().is_empty() { None } else { Some(name.clone()) };
|
||||
app.project.banks[bank_idx].patterns[pattern_idx].name = new_name;
|
||||
app.modal = Modal::None;
|
||||
}
|
||||
KeyCode::Esc => {
|
||||
app.modal = Modal::None;
|
||||
}
|
||||
KeyCode::Backspace => {
|
||||
name.pop();
|
||||
}
|
||||
KeyCode::Char(c) => {
|
||||
name.push(c);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Modal::None => {
|
||||
let ctrl = key.modifiers.contains(KeyModifiers::CONTROL);
|
||||
|
||||
@@ -213,12 +217,20 @@ fn main() -> io::Result<()> {
|
||||
app.page.right();
|
||||
continue;
|
||||
}
|
||||
if ctrl && key.code == KeyCode::Up {
|
||||
app.page.up();
|
||||
continue;
|
||||
}
|
||||
if ctrl && key.code == KeyCode::Down {
|
||||
app.page.down();
|
||||
continue;
|
||||
}
|
||||
|
||||
match app.page {
|
||||
Page::Main => match app.focus {
|
||||
Focus::Sequencer => match key.code {
|
||||
KeyCode::Char('q') => {
|
||||
app.modal = Modal::ConfirmQuit;
|
||||
app.modal = Modal::ConfirmQuit { selected: false };
|
||||
}
|
||||
KeyCode::Char(' ') => {
|
||||
app.toggle_playing();
|
||||
@@ -230,16 +242,6 @@ fn main() -> io::Result<()> {
|
||||
KeyCode::Up => app.step_up(),
|
||||
KeyCode::Down => app.step_down(),
|
||||
KeyCode::Enter => app.toggle_step(),
|
||||
KeyCode::Char('p') => {
|
||||
app.modal =
|
||||
Modal::PatternPicker { cursor: app.edit_pattern };
|
||||
}
|
||||
KeyCode::Char('b') => {
|
||||
app.modal = Modal::BankPicker { cursor: app.edit_bank };
|
||||
}
|
||||
KeyCode::Char('g') => {
|
||||
app.queue_current_for_playback();
|
||||
}
|
||||
KeyCode::Char('s') => {
|
||||
let default = app
|
||||
.file_path
|
||||
@@ -253,6 +255,14 @@ fn main() -> io::Result<()> {
|
||||
}
|
||||
KeyCode::Char('+') | KeyCode::Char('=') => app.tempo_up(&link),
|
||||
KeyCode::Char('-') => app.tempo_down(&link),
|
||||
KeyCode::Char('<') | KeyCode::Char(',') => {
|
||||
app.length_decrease()
|
||||
}
|
||||
KeyCode::Char('>') | KeyCode::Char('.') => {
|
||||
app.length_increase()
|
||||
}
|
||||
KeyCode::Char('[') => app.speed_decrease(),
|
||||
KeyCode::Char(']') => app.speed_increase(),
|
||||
KeyCode::Char('c') if ctrl => app.copy_step(),
|
||||
KeyCode::Char('v') if ctrl => app.paste_step(),
|
||||
_ => {}
|
||||
@@ -268,9 +278,81 @@ fn main() -> io::Result<()> {
|
||||
}
|
||||
},
|
||||
},
|
||||
Page::Patterns => {
|
||||
use app::PatternsViewLevel;
|
||||
match key.code {
|
||||
KeyCode::Left => {
|
||||
app.patterns_cursor = (app.patterns_cursor + 15) % 16;
|
||||
}
|
||||
KeyCode::Right => {
|
||||
app.patterns_cursor = (app.patterns_cursor + 1) % 16;
|
||||
}
|
||||
KeyCode::Up => {
|
||||
app.patterns_cursor = (app.patterns_cursor + 12) % 16;
|
||||
}
|
||||
KeyCode::Down => {
|
||||
app.patterns_cursor = (app.patterns_cursor + 4) % 16;
|
||||
}
|
||||
KeyCode::Esc | KeyCode::Backspace => {
|
||||
match app.patterns_view_level {
|
||||
PatternsViewLevel::Banks => {
|
||||
app.page.down();
|
||||
}
|
||||
PatternsViewLevel::Patterns { .. } => {
|
||||
app.patterns_view_level = PatternsViewLevel::Banks;
|
||||
app.patterns_cursor = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
KeyCode::Enter => {
|
||||
match app.patterns_view_level {
|
||||
PatternsViewLevel::Banks => {
|
||||
let bank = app.patterns_cursor;
|
||||
app.patterns_view_level =
|
||||
PatternsViewLevel::Patterns { bank };
|
||||
app.patterns_cursor = 0;
|
||||
}
|
||||
PatternsViewLevel::Patterns { bank } => {
|
||||
let pattern = app.patterns_cursor;
|
||||
app.select_edit_bank(bank);
|
||||
app.select_edit_pattern(pattern);
|
||||
app.patterns_view_level = PatternsViewLevel::Banks;
|
||||
app.patterns_cursor = 0;
|
||||
app.page.down();
|
||||
}
|
||||
}
|
||||
}
|
||||
KeyCode::Char(' ') => {
|
||||
if let PatternsViewLevel::Patterns { bank } =
|
||||
app.patterns_view_level
|
||||
{
|
||||
let pattern = app.patterns_cursor;
|
||||
app.toggle_pattern_playback(bank, pattern);
|
||||
}
|
||||
}
|
||||
KeyCode::Char('q') => {
|
||||
app.modal = Modal::ConfirmQuit { selected: false };
|
||||
}
|
||||
KeyCode::Char('r') => {
|
||||
match app.patterns_view_level {
|
||||
PatternsViewLevel::Banks => {
|
||||
let bank = app.patterns_cursor;
|
||||
let current_name = app.project.banks[bank].name.clone().unwrap_or_default();
|
||||
app.modal = Modal::RenameBank { bank, name: current_name };
|
||||
}
|
||||
PatternsViewLevel::Patterns { bank } => {
|
||||
let pattern = app.patterns_cursor;
|
||||
let current_name = app.project.banks[bank].patterns[pattern].name.clone().unwrap_or_default();
|
||||
app.modal = Modal::RenamePattern { bank, pattern, name: current_name };
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Page::Audio => match key.code {
|
||||
KeyCode::Char('q') => {
|
||||
app.modal = Modal::ConfirmQuit;
|
||||
app.modal = Modal::ConfirmQuit { selected: false };
|
||||
}
|
||||
KeyCode::Char('h') => {
|
||||
engine.lock().unwrap().hush();
|
||||
@@ -290,6 +372,29 @@ fn main() -> io::Result<()> {
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Page::Doc => {
|
||||
let topic_count = views::doc_view::topic_count();
|
||||
match key.code {
|
||||
KeyCode::Char('j') | KeyCode::Down => {
|
||||
app.doc_topic = (app.doc_topic + 1) % topic_count;
|
||||
app.doc_scroll = 0;
|
||||
}
|
||||
KeyCode::Char('k') | KeyCode::Up => {
|
||||
app.doc_topic = (app.doc_topic + topic_count - 1) % topic_count;
|
||||
app.doc_scroll = 0;
|
||||
}
|
||||
KeyCode::PageDown => {
|
||||
app.doc_scroll = app.doc_scroll.saturating_add(10);
|
||||
}
|
||||
KeyCode::PageUp => {
|
||||
app.doc_scroll = app.doc_scroll.saturating_sub(10);
|
||||
}
|
||||
KeyCode::Char('q') => {
|
||||
app.modal = Modal::ConfirmQuit { selected: false };
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user