seq
This commit is contained in:
304
seq/src/main.rs
Normal file
304
seq/src/main.rs
Normal file
@@ -0,0 +1,304 @@
|
||||
mod app;
|
||||
mod audio;
|
||||
mod file;
|
||||
mod link;
|
||||
mod model;
|
||||
mod page;
|
||||
mod script;
|
||||
mod ui;
|
||||
mod views;
|
||||
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
|
||||
use crossterm::event::{self, Event, KeyCode, KeyModifiers};
|
||||
use crossterm::terminal::{
|
||||
disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
|
||||
};
|
||||
use crossterm::ExecutableCommand;
|
||||
use doux::Engine;
|
||||
use ratatui::prelude::CrosstermBackend;
|
||||
use ratatui::Terminal;
|
||||
|
||||
use app::{App, Focus, Modal};
|
||||
use link::LinkState;
|
||||
use model::Project;
|
||||
use page::Page;
|
||||
|
||||
const TEMPO: f64 = 120.0;
|
||||
const QUANTUM: f64 = 4.0;
|
||||
|
||||
fn main() -> io::Result<()> {
|
||||
let link = Arc::new(LinkState::new(TEMPO, QUANTUM));
|
||||
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));
|
||||
let mut app = App::new(TEMPO, QUANTUM);
|
||||
|
||||
let engine = Arc::new(Mutex::new(Engine::new(44100.0)));
|
||||
let project = Arc::new(Mutex::new(Project::default()));
|
||||
|
||||
let (_stream, sample_rate) = audio::build_stream(
|
||||
Arc::clone(&engine),
|
||||
Arc::clone(&link),
|
||||
Arc::clone(&playing),
|
||||
Arc::clone(&project),
|
||||
Arc::clone(&playback_step),
|
||||
Arc::clone(&event_count),
|
||||
Arc::clone(&playback_bank),
|
||||
Arc::clone(&playback_pattern),
|
||||
Arc::clone(&queued_bank),
|
||||
Arc::clone(&queued_pattern),
|
||||
);
|
||||
|
||||
{
|
||||
let mut eng = engine.lock().unwrap();
|
||||
eng.sr = sample_rate;
|
||||
eng.isr = 1.0 / sample_rate;
|
||||
}
|
||||
|
||||
enable_raw_mode()?;
|
||||
io::stdout().execute(EnterAlternateScreen)?;
|
||||
|
||||
let backend = CrosstermBackend::new(io::stdout());
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
|
||||
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);
|
||||
|
||||
{
|
||||
let eng = engine.lock().unwrap();
|
||||
app.active_voices = eng.active_voices;
|
||||
app.peak_voices = app.peak_voices.max(eng.active_voices);
|
||||
app.cpu_load = eng.metrics.load.get_load();
|
||||
app.schedule_depth = eng.schedule.len();
|
||||
for (i, s) in app.scope.iter_mut().enumerate() {
|
||||
*s = eng.output.get(i * 2).copied().unwrap_or(0.0);
|
||||
}
|
||||
}
|
||||
|
||||
app.playback_bank = playback_bank.load(Ordering::Relaxed);
|
||||
app.playback_pattern = playback_pattern.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;
|
||||
}
|
||||
|
||||
{
|
||||
let mut proj = project.lock().unwrap();
|
||||
proj.banks = app.project.banks.clone();
|
||||
}
|
||||
|
||||
terminal.draw(|frame| ui::render(frame, &mut app))?;
|
||||
|
||||
if event::poll(Duration::from_millis(16))? {
|
||||
if let Event::Key(key) = event::read()? {
|
||||
app.clear_status();
|
||||
|
||||
match &mut app.modal {
|
||||
Modal::ConfirmQuit => match key.code {
|
||||
KeyCode::Char('y') | KeyCode::Char('Y') => break,
|
||||
KeyCode::Char('n') | KeyCode::Char('N') | KeyCode::Esc => {
|
||||
app.modal = Modal::None;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Modal::SaveAs(path) => match key.code {
|
||||
KeyCode::Enter => {
|
||||
let save_path = PathBuf::from(path.as_str());
|
||||
app.modal = Modal::None;
|
||||
app.save(save_path);
|
||||
}
|
||||
KeyCode::Esc => {
|
||||
app.modal = Modal::None;
|
||||
}
|
||||
KeyCode::Backspace => {
|
||||
path.pop();
|
||||
}
|
||||
KeyCode::Char(c) => {
|
||||
path.push(c);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Modal::LoadFrom(path) => match key.code {
|
||||
KeyCode::Enter => {
|
||||
let load_path = PathBuf::from(path.as_str());
|
||||
app.modal = Modal::None;
|
||||
app.load(load_path);
|
||||
}
|
||||
KeyCode::Esc => {
|
||||
app.modal = Modal::None;
|
||||
}
|
||||
KeyCode::Backspace => {
|
||||
path.pop();
|
||||
}
|
||||
KeyCode::Char(c) => {
|
||||
path.push(c);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
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::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;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Modal::None => {
|
||||
let ctrl = key.modifiers.contains(KeyModifiers::CONTROL);
|
||||
|
||||
if ctrl && key.code == KeyCode::Left {
|
||||
app.page.left();
|
||||
continue;
|
||||
}
|
||||
if ctrl && key.code == KeyCode::Right {
|
||||
app.page.right();
|
||||
continue;
|
||||
}
|
||||
|
||||
match app.page {
|
||||
Page::Main => match app.focus {
|
||||
Focus::Sequencer => match key.code {
|
||||
KeyCode::Char('q') => {
|
||||
app.modal = Modal::ConfirmQuit;
|
||||
}
|
||||
KeyCode::Char(' ') => {
|
||||
app.toggle_playing();
|
||||
playing.store(app.playing, Ordering::Relaxed);
|
||||
}
|
||||
KeyCode::Tab => app.toggle_focus(),
|
||||
KeyCode::Left => app.prev_step(),
|
||||
KeyCode::Right => app.next_step(),
|
||||
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
|
||||
.as_ref()
|
||||
.map(|p| p.display().to_string())
|
||||
.unwrap_or_else(|| "project.buboseq".to_string());
|
||||
app.modal = Modal::SaveAs(default);
|
||||
}
|
||||
KeyCode::Char('l') => {
|
||||
app.modal = Modal::LoadFrom(String::new());
|
||||
}
|
||||
KeyCode::Char('+') | KeyCode::Char('=') => app.tempo_up(&link),
|
||||
KeyCode::Char('-') => app.tempo_down(&link),
|
||||
KeyCode::Char('c') if ctrl => app.copy_step(),
|
||||
KeyCode::Char('v') if ctrl => app.paste_step(),
|
||||
_ => {}
|
||||
},
|
||||
Focus::Editor => match key.code {
|
||||
KeyCode::Tab | KeyCode::Esc => app.toggle_focus(),
|
||||
KeyCode::Char('e') if ctrl => {
|
||||
app.save_editor_to_step();
|
||||
app.compile_current_step();
|
||||
}
|
||||
_ => {
|
||||
app.editor.input(Event::Key(key));
|
||||
}
|
||||
},
|
||||
},
|
||||
Page::Audio => match key.code {
|
||||
KeyCode::Char('q') => {
|
||||
app.modal = Modal::ConfirmQuit;
|
||||
}
|
||||
KeyCode::Char('h') => {
|
||||
engine.lock().unwrap().hush();
|
||||
}
|
||||
KeyCode::Char('p') => {
|
||||
engine.lock().unwrap().panic();
|
||||
}
|
||||
KeyCode::Char('r') => {
|
||||
app.peak_voices = 0;
|
||||
}
|
||||
KeyCode::Char('t') => {
|
||||
engine.lock().unwrap().evaluate("sin 440 * 0.3");
|
||||
}
|
||||
KeyCode::Char(' ') => {
|
||||
app.toggle_playing();
|
||||
playing.store(app.playing, Ordering::Relaxed);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
disable_raw_mode()?;
|
||||
io::stdout().execute(LeaveAlternateScreen)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user