mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
remodularize 3
This commit is contained in:
parent
d38dc14e84
commit
113e7b0bad
24 changed files with 262 additions and 370 deletions
109
Cargo.lock
generated
109
Cargo.lock
generated
|
|
@ -23,55 +23,6 @@ version = "0.2.21"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "approx"
|
||||
version = "0.5.1"
|
||||
|
|
@ -181,46 +132,6 @@ version = "1.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
|
||||
|
||||
[[package]]
|
||||
name = "clojure-reader"
|
||||
version = "0.3.0"
|
||||
|
|
@ -230,12 +141,6 @@ dependencies = [
|
|||
"ordered-float",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
|
||||
|
||||
[[package]]
|
||||
name = "compact_str"
|
||||
version = "0.8.1"
|
||||
|
|
@ -509,12 +414,6 @@ dependencies = [
|
|||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.13.0"
|
||||
|
|
@ -1409,9 +1308,7 @@ name = "tek"
|
|||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"clap",
|
||||
"livi",
|
||||
"once_cell",
|
||||
"palette",
|
||||
"rand",
|
||||
"symphonia",
|
||||
|
|
@ -1590,12 +1487,6 @@ version = "0.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.11.0"
|
||||
|
|
|
|||
42
Cargo.toml
42
Cargo.toml
|
|
@ -10,14 +10,13 @@ tek_time = { path = "./time" }
|
|||
tek_midi = { path = "./midi" }
|
||||
|
||||
backtrace = "0.3.72"
|
||||
clap = { version = "4.5.4", features = [ "derive" ] }
|
||||
livi = "0.7.4"
|
||||
once_cell = "1.19.0"
|
||||
palette = { version = "0.7.6", features = [ "random" ] }
|
||||
rand = "0.8.5"
|
||||
symphonia = { version = "0.5.4", features = [ "all" ] }
|
||||
toml = "0.8.12"
|
||||
wavers = "1.4.3"
|
||||
#once_cell = "1.19.0"
|
||||
#no_deadlocks = "1.3.2"
|
||||
#suil-rs = { path = "../suil" }
|
||||
#vst = "0.4.0"
|
||||
|
|
@ -27,44 +26,5 @@ wavers = "1.4.3"
|
|||
[features]
|
||||
default = []
|
||||
|
||||
[[bin]]
|
||||
name = "tek"
|
||||
path = "bin/tek.rs"
|
||||
|
||||
#[[bin]]
|
||||
#name = "tek_arranger"
|
||||
#path = "bin/cli_arranger.rs"
|
||||
|
||||
#[[bin]]
|
||||
#name = "tek_sequencer"
|
||||
#path = "bin/cli_sequencer.rs"
|
||||
|
||||
#[[bin]]
|
||||
#name = "tek_groovebox"
|
||||
#path = "bin/cli_groovebox.rs"
|
||||
|
||||
#[[bin]]
|
||||
#name = "tek_clock"
|
||||
#path = "bin/cli_clock.rs"
|
||||
|
||||
#[[bin]]
|
||||
#name = "tek_sampler"
|
||||
#path = "bin/cli_sampler.rs"
|
||||
|
||||
#[[bin]]
|
||||
#name = "tek_mixer"
|
||||
#path = "src/cli_mixer.rs"
|
||||
|
||||
#[[bin]]
|
||||
#name = "tek_track"
|
||||
#path = "src/cli_track.rs"
|
||||
|
||||
#[[bin]]
|
||||
#name = "tek_plugin"
|
||||
#path = "src/cli_plugin.rs"
|
||||
|
||||
[lib]
|
||||
path = "src/lib.rs"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
|
|
|
|||
2
Justfile
2
Justfile
|
|
@ -132,4 +132,4 @@ test:
|
|||
cargo test
|
||||
|
||||
cloc:
|
||||
for src in {edn,input,jack,midi,output,time,tui,.}; do echo $src; cloc $src/src; done
|
||||
for src in {cli/src,edn/src,examples,input/src,jack/src,midi/src,output/src,time/src,tui/src,src}; do echo $src; cloc $src; done
|
||||
|
|
|
|||
8
cli/Cargo.toml
Normal file
8
cli/Cargo.toml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
[package]
|
||||
name = "tek_cli"
|
||||
edition = "2021"
|
||||
version = "0.2.0"
|
||||
|
||||
[dependencies]
|
||||
tek = { path = ".." }
|
||||
clap = { version = "4.5.4", features = [ "derive" ] }
|
||||
|
|
@ -13,6 +13,6 @@ impl HasPhrases for ExamplePhrases {
|
|||
|
||||
fn main () -> Usually<()> {
|
||||
let mut phrases = ExamplePhrases(vec![]);
|
||||
PhrasePoolCommand::Import(0, String::from("./example.mid")).execute(&mut phrases)?;
|
||||
MidiPoolCommand::Import(0, String::from("./example.mid")).execute(&mut phrases)?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,3 +11,35 @@ tek_time = { path = "../time" }
|
|||
jack = { path = "../rust-jack" }
|
||||
midly = "0.5"
|
||||
uuid = { version = "1.10.0", features = [ "v4" ] }
|
||||
|
||||
#[[bin]]
|
||||
#name = "tek_arranger"
|
||||
#path = "bin/cli_arranger.rs"
|
||||
|
||||
#[[bin]]
|
||||
#name = "tek_sequencer"
|
||||
#path = "bin/cli_sequencer.rs"
|
||||
|
||||
#[[bin]]
|
||||
#name = "tek_groovebox"
|
||||
#path = "bin/cli_groovebox.rs"
|
||||
|
||||
#[[bin]]
|
||||
#name = "tek_clock"
|
||||
#path = "bin/cli_clock.rs"
|
||||
|
||||
#[[bin]]
|
||||
#name = "tek_sampler"
|
||||
#path = "bin/cli_sampler.rs"
|
||||
|
||||
#[[bin]]
|
||||
#name = "tek_mixer"
|
||||
#path = "src/cli_mixer.rs"
|
||||
|
||||
#[[bin]]
|
||||
#name = "tek_track"
|
||||
#path = "src/cli_track.rs"
|
||||
|
||||
#[[bin]]
|
||||
#name = "tek_plugin"
|
||||
#path = "src/cli_plugin.rs"
|
||||
|
|
|
|||
|
|
@ -1,22 +1,22 @@
|
|||
mod midi_pool; pub(crate) use midi_pool::*;
|
||||
mod midi_clip; pub(crate) use midi_clip::*;
|
||||
mod midi_launch; pub(crate) use midi_launch::*;
|
||||
mod midi_player; pub(crate) use midi_player::*;
|
||||
mod midi_in; pub(crate) use midi_in::*;
|
||||
mod midi_out; pub(crate) use midi_out::*;
|
||||
mod midi_pool; pub use midi_pool::*;
|
||||
mod midi_clip; pub use midi_clip::*;
|
||||
mod midi_launch; pub use midi_launch::*;
|
||||
mod midi_player; pub use midi_player::*;
|
||||
mod midi_in; pub use midi_in::*;
|
||||
mod midi_out; pub use midi_out::*;
|
||||
|
||||
mod midi_pitch; pub(crate) use midi_pitch::*;
|
||||
mod midi_range; pub(crate) use midi_range::*;
|
||||
mod midi_point; pub(crate) use midi_point::*;
|
||||
mod midi_view; pub(crate) use midi_view::*;
|
||||
mod midi_editor; pub(crate) use midi_editor::*;
|
||||
mod midi_select; pub(crate) use midi_select::*;
|
||||
mod midi_pitch; pub use midi_pitch::*;
|
||||
mod midi_range; pub use midi_range::*;
|
||||
mod midi_point; pub use midi_point::*;
|
||||
mod midi_view; pub use midi_view::*;
|
||||
mod midi_editor; pub use midi_editor::*;
|
||||
mod midi_select; pub use midi_select::*;
|
||||
|
||||
mod piano_h; pub(crate) use self::piano_h::*;
|
||||
mod piano_h_cursor; pub(crate) use self::piano_h_cursor::*;
|
||||
mod piano_h_keys; pub(crate) use self::piano_h_keys::*;
|
||||
mod piano_h_notes; pub(crate) use self::piano_h_notes::*;
|
||||
mod piano_h_time; pub(crate) use self::piano_h_time::*;
|
||||
mod piano_h; pub use self::piano_h::*;
|
||||
mod piano_h_cursor; pub use self::piano_h_cursor::*;
|
||||
mod piano_h_keys; pub use self::piano_h_keys::*;
|
||||
mod piano_h_notes; pub use self::piano_h_notes::*;
|
||||
mod piano_h_time; pub use self::piano_h_time::*;
|
||||
|
||||
pub(crate) use ::tek_time::*;
|
||||
pub(crate) use ::tek_jack::{*, jack::*};
|
||||
|
|
|
|||
|
|
@ -21,29 +21,29 @@ pub trait HasPlayer {
|
|||
/// Contains state for playing a phrase
|
||||
pub struct MidiPlayer {
|
||||
/// State of clock and playhead
|
||||
pub(crate) clock: Clock,
|
||||
pub clock: Clock,
|
||||
/// Start time and phrase being played
|
||||
pub(crate) play_phrase: Option<(Moment, Option<Arc<RwLock<MidiClip>>>)>,
|
||||
pub play_phrase: Option<(Moment, Option<Arc<RwLock<MidiClip>>>)>,
|
||||
/// Start time and next phrase
|
||||
pub(crate) next_phrase: Option<(Moment, Option<Arc<RwLock<MidiClip>>>)>,
|
||||
pub next_phrase: Option<(Moment, Option<Arc<RwLock<MidiClip>>>)>,
|
||||
/// Play input through output.
|
||||
pub(crate) monitoring: bool,
|
||||
pub monitoring: bool,
|
||||
/// Write input to sequence.
|
||||
pub(crate) recording: bool,
|
||||
pub recording: bool,
|
||||
/// Overdub input to sequence.
|
||||
pub(crate) overdub: bool,
|
||||
pub overdub: bool,
|
||||
/// Send all notes off
|
||||
pub(crate) reset: bool, // TODO?: after Some(nframes)
|
||||
pub reset: bool, // TODO?: after Some(nframes)
|
||||
/// Record from MIDI ports to current sequence.
|
||||
pub midi_ins: Vec<Port<MidiIn>>,
|
||||
pub midi_ins: Vec<Port<MidiIn>>,
|
||||
/// Play from current sequence to MIDI ports
|
||||
pub midi_outs: Vec<Port<MidiOut>>,
|
||||
pub midi_outs: Vec<Port<MidiOut>>,
|
||||
/// Notes currently held at input
|
||||
pub(crate) notes_in: Arc<RwLock<[bool; 128]>>,
|
||||
pub notes_in: Arc<RwLock<[bool; 128]>>,
|
||||
/// Notes currently held at output
|
||||
pub(crate) notes_out: Arc<RwLock<[bool; 128]>>,
|
||||
pub notes_out: Arc<RwLock<[bool; 128]>>,
|
||||
/// MIDI output buffer
|
||||
pub note_buf: Vec<u8>,
|
||||
pub note_buf: Vec<u8>,
|
||||
}
|
||||
impl MidiPlayer {
|
||||
pub fn new (
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ pub trait HasPhrases {
|
|||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum PhrasePoolCommand {
|
||||
pub enum MidiPoolCommand {
|
||||
Add(usize, MidiClip),
|
||||
Delete(usize),
|
||||
Swap(usize, usize),
|
||||
|
|
@ -26,9 +26,9 @@ pub enum PhrasePoolCommand {
|
|||
SetColor(usize, ItemColor),
|
||||
}
|
||||
|
||||
impl<T: HasPhrases> Command<T> for PhrasePoolCommand {
|
||||
impl<T: HasPhrases> Command<T> for MidiPoolCommand {
|
||||
fn execute (self, model: &mut T) -> Perhaps<Self> {
|
||||
use PhrasePoolCommand::*;
|
||||
use MidiPoolCommand::*;
|
||||
Ok(match self {
|
||||
Add(mut index, phrase) => {
|
||||
let phrase = Arc::new(RwLock::new(phrase));
|
||||
|
|
|
|||
|
|
@ -185,7 +185,7 @@ command!(|self: ArrangerCommand, state: Arranger|match self {
|
|||
undo
|
||||
},
|
||||
// reload phrase in editor to update color
|
||||
PoolCommand::Phrase(PhrasePoolCommand::SetColor(index, _)) => {
|
||||
PoolCommand::Phrase(MidiPoolCommand::SetColor(index, _)) => {
|
||||
let undo = cmd.delegate(&mut state.pool, Self::Phrases)?;
|
||||
state.editor.set_phrase(Some(state.pool.phrase()));
|
||||
undo
|
||||
|
|
|
|||
151
src/file.rs
151
src/file.rs
|
|
@ -1,151 +0,0 @@
|
|||
use crate::*;
|
||||
/// Browses for phrase to import/export
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FileBrowser {
|
||||
pub cwd: PathBuf,
|
||||
pub dirs: Vec<(OsString, String)>,
|
||||
pub files: Vec<(OsString, String)>,
|
||||
pub filter: String,
|
||||
pub index: usize,
|
||||
pub scroll: usize,
|
||||
pub size: Measure<TuiOut>
|
||||
}
|
||||
/// Commands supported by [FileBrowser]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum FileBrowserCommand {
|
||||
Begin,
|
||||
Cancel,
|
||||
Confirm,
|
||||
Select(usize),
|
||||
Chdir(PathBuf),
|
||||
Filter(Arc<str>),
|
||||
}
|
||||
render!(TuiOut: (self: FileBrowser) => /*Stack::down(|add|{
|
||||
let mut i = 0;
|
||||
for (_, name) in self.dirs.iter() {
|
||||
if i >= self.scroll {
|
||||
add(&Tui::bold(i == self.index, name.as_str()))?;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
for (_, name) in self.files.iter() {
|
||||
if i >= self.scroll {
|
||||
add(&Tui::bold(i == self.index, name.as_str()))?;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
add(&format!("{}/{i}", self.index))?;
|
||||
Ok(())
|
||||
})*/"todo");
|
||||
impl FileBrowser {
|
||||
pub fn new (cwd: Option<PathBuf>) -> Usually<Self> {
|
||||
let cwd = if let Some(cwd) = cwd { cwd } else { std::env::current_dir()? };
|
||||
let mut dirs = vec![];
|
||||
let mut files = vec![];
|
||||
for entry in std::fs::read_dir(&cwd)? {
|
||||
let entry = entry?;
|
||||
let name = entry.file_name();
|
||||
let decoded = name.clone().into_string().unwrap_or_else(|_|"<unreadable>".to_string());
|
||||
let meta = entry.metadata()?;
|
||||
if meta.is_dir() {
|
||||
dirs.push((name, format!("📁 {decoded}")));
|
||||
} else if meta.is_file() {
|
||||
files.push((name, format!("📄 {decoded}")));
|
||||
}
|
||||
}
|
||||
Ok(Self {
|
||||
cwd,
|
||||
dirs,
|
||||
files,
|
||||
filter: "".to_string(),
|
||||
index: 0,
|
||||
scroll: 0,
|
||||
size: Measure::new(),
|
||||
})
|
||||
}
|
||||
pub fn len (&self) -> usize {
|
||||
self.dirs.len() + self.files.len()
|
||||
}
|
||||
pub fn is_dir (&self) -> bool {
|
||||
self.index < self.dirs.len()
|
||||
}
|
||||
pub fn is_file (&self) -> bool {
|
||||
self.index >= self.dirs.len()
|
||||
}
|
||||
pub fn path (&self) -> PathBuf {
|
||||
self.cwd.join(if self.is_dir() {
|
||||
&self.dirs[self.index].0
|
||||
} else if self.is_file() {
|
||||
&self.files[self.index - self.dirs.len()].0
|
||||
} else {
|
||||
unreachable!()
|
||||
})
|
||||
}
|
||||
pub fn chdir (&self) -> Usually<Self> {
|
||||
Self::new(Some(self.path()))
|
||||
}
|
||||
}
|
||||
command!(|self: FileBrowserCommand, state: PoolModel|{
|
||||
use PoolMode::*;
|
||||
use FileBrowserCommand::*;
|
||||
let mode = &mut state.mode;
|
||||
match mode {
|
||||
Some(Import(index, ref mut browser)) => match self {
|
||||
Cancel => { *mode = None; },
|
||||
Chdir(cwd) => { *mode = Some(Import(*index, FileBrowser::new(Some(cwd))?)); },
|
||||
Select(index) => { browser.index = index; },
|
||||
Confirm => if browser.is_file() {
|
||||
let index = *index;
|
||||
let path = browser.path();
|
||||
*mode = None;
|
||||
PhrasePoolCommand::Import(index, path).execute(state)?;
|
||||
} else if browser.is_dir() {
|
||||
*mode = Some(Import(*index, browser.chdir()?));
|
||||
},
|
||||
_ => todo!(),
|
||||
},
|
||||
Some(Export(index, ref mut browser)) => match self {
|
||||
Cancel => { *mode = None; },
|
||||
Chdir(cwd) => { *mode = Some(Export(*index, FileBrowser::new(Some(cwd))?)); },
|
||||
Select(index) => { browser.index = index; },
|
||||
_ => unreachable!()
|
||||
},
|
||||
_ => unreachable!(),
|
||||
};
|
||||
None
|
||||
});
|
||||
input_to_command!(FileBrowserCommand: |state: PoolModel, input: Event|{
|
||||
use FileBrowserCommand::*;
|
||||
use KeyCode::{Up, Down, Left, Right, Enter, Esc, Backspace, Char};
|
||||
if let Some(PoolMode::Import(_index, browser)) = &state.mode {
|
||||
match input {
|
||||
kpat!(Up) => Select(browser.index.overflowing_sub(1).0
|
||||
.min(browser.len().saturating_sub(1))),
|
||||
kpat!(Down) => Select(browser.index.saturating_add(1)
|
||||
% browser.len()),
|
||||
kpat!(Right) => Chdir(browser.cwd.clone()),
|
||||
kpat!(Left) => Chdir(browser.cwd.clone()),
|
||||
kpat!(Enter) => Confirm,
|
||||
kpat!(Char(_)) => { todo!() },
|
||||
kpat!(Backspace) => { todo!() },
|
||||
kpat!(Esc) => Cancel,
|
||||
_ => return None
|
||||
}
|
||||
} else if let Some(PoolMode::Export(_index, browser)) = &state.mode {
|
||||
match input {
|
||||
kpat!(Up) => Select(browser.index.overflowing_sub(1).0
|
||||
.min(browser.len())),
|
||||
kpat!(Down) => Select(browser.index.saturating_add(1)
|
||||
% browser.len()),
|
||||
kpat!(Right) => Chdir(browser.cwd.clone()),
|
||||
kpat!(Left) => Chdir(browser.cwd.clone()),
|
||||
kpat!(Enter) => Confirm,
|
||||
kpat!(Char(_)) => { todo!() },
|
||||
kpat!(Backspace) => { todo!() },
|
||||
kpat!(Esc) => Cancel,
|
||||
_ => return None
|
||||
}
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
});
|
||||
|
|
@ -7,7 +7,7 @@ use KeyCode::{Char, Delete, Tab, Up, Down, Left, Right};
|
|||
use ClockCommand::{Play, Pause};
|
||||
use GrooveboxCommand as Cmd;
|
||||
use MidiEditCommand::*;
|
||||
use PhrasePoolCommand::*;
|
||||
use MidiPoolCommand::*;
|
||||
|
||||
pub struct Groovebox {
|
||||
_jack: Arc<RwLock<JackConnection>>,
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@
|
|||
#![feature(associated_type_defaults)]
|
||||
|
||||
pub mod arranger; pub use self::arranger::*;
|
||||
pub mod file; pub use self::file::*;
|
||||
pub mod focus; pub use self::focus::*;
|
||||
pub mod groovebox; pub use self::groovebox::*;
|
||||
pub mod meter; pub use self::meter::*;
|
||||
pub mod mixer; pub use self::mixer::*;
|
||||
|
|
@ -42,16 +40,16 @@ pub(crate) use ::tek_tui::{
|
|||
pub(crate) use std::cmp::{Ord, Eq, PartialEq};
|
||||
pub(crate) use std::collections::BTreeMap;
|
||||
pub(crate) use std::error::Error;
|
||||
pub(crate) use std::ffi::OsString;
|
||||
pub(crate) use std::fmt::{Debug, Display, Formatter};
|
||||
pub(crate) use std::io::{Stdout, stdout};
|
||||
pub(crate) use std::marker::PhantomData;
|
||||
pub(crate) use std::ops::{Add, Sub, Mul, Div, Rem};
|
||||
pub(crate) use std::path::PathBuf;
|
||||
pub(crate) use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::{self, *}};
|
||||
pub(crate) use std::sync::{Arc, Mutex, RwLock};
|
||||
pub(crate) use std::thread::{spawn, JoinHandle};
|
||||
pub(crate) use std::time::Duration;
|
||||
pub(crate) use std::path::PathBuf;
|
||||
pub(crate) use std::ffi::OsString;
|
||||
|
||||
//#[cfg(test)] mod test_focus {
|
||||
//use super::focus::*;
|
||||
|
|
|
|||
80
src/pool.rs
80
src/pool.rs
|
|
@ -37,7 +37,7 @@ pub enum PoolMode {
|
|||
pub enum PoolCommand {
|
||||
Show(bool),
|
||||
/// Update the contents of the phrase pool
|
||||
Phrase(PhrasePoolCommand),
|
||||
Phrase(MidiPoolCommand),
|
||||
/// Select a phrase from the phrase pool
|
||||
Select(usize),
|
||||
/// Rename a phrase
|
||||
|
|
@ -121,7 +121,7 @@ fn to_phrases_command (state: &PoolModel, input: &Event) -> Option<PoolCommand>
|
|||
kpat!(Char('t')) => Cmd::Length(PhraseLengthCommand::Begin),
|
||||
kpat!(Char('m')) => Cmd::Import(FileBrowserCommand::Begin),
|
||||
kpat!(Char('x')) => Cmd::Export(FileBrowserCommand::Begin),
|
||||
kpat!(Char('c')) => Cmd::Phrase(PhrasePoolCommand::SetColor(index, ItemColor::random())),
|
||||
kpat!(Char('c')) => Cmd::Phrase(MidiPoolCommand::SetColor(index, ItemColor::random())),
|
||||
kpat!(Char('[')) | kpat!(Up) => Cmd::Select(
|
||||
index.overflowing_sub(1).0.min(state.phrases().len() - 1)
|
||||
),
|
||||
|
|
@ -130,32 +130,32 @@ fn to_phrases_command (state: &PoolModel, input: &Event) -> Option<PoolCommand>
|
|||
),
|
||||
kpat!(Char('<')) => if index > 1 {
|
||||
state.set_phrase_index(state.phrase_index().saturating_sub(1));
|
||||
Cmd::Phrase(PhrasePoolCommand::Swap(index - 1, index))
|
||||
Cmd::Phrase(MidiPoolCommand::Swap(index - 1, index))
|
||||
} else {
|
||||
return None
|
||||
},
|
||||
kpat!(Char('>')) => if index < count.saturating_sub(1) {
|
||||
state.set_phrase_index(state.phrase_index() + 1);
|
||||
Cmd::Phrase(PhrasePoolCommand::Swap(index + 1, index))
|
||||
Cmd::Phrase(MidiPoolCommand::Swap(index + 1, index))
|
||||
} else {
|
||||
return None
|
||||
},
|
||||
kpat!(Delete) => if index > 0 {
|
||||
state.set_phrase_index(index.min(count.saturating_sub(1)));
|
||||
Cmd::Phrase(PhrasePoolCommand::Delete(index))
|
||||
Cmd::Phrase(MidiPoolCommand::Delete(index))
|
||||
} else {
|
||||
return None
|
||||
},
|
||||
kpat!(Char('a')) | kpat!(Shift-Char('A')) => Cmd::Phrase(PhrasePoolCommand::Add(count, MidiClip::new(
|
||||
kpat!(Char('a')) | kpat!(Shift-Char('A')) => Cmd::Phrase(MidiPoolCommand::Add(count, MidiClip::new(
|
||||
"Clip", true, 4 * PPQ, None, Some(ItemPalette::random())
|
||||
))),
|
||||
kpat!(Char('i')) => Cmd::Phrase(PhrasePoolCommand::Add(index + 1, MidiClip::new(
|
||||
kpat!(Char('i')) => Cmd::Phrase(MidiPoolCommand::Add(index + 1, MidiClip::new(
|
||||
"Clip", true, 4 * PPQ, None, Some(ItemPalette::random())
|
||||
))),
|
||||
kpat!(Char('d')) | kpat!(Shift-Char('D')) => {
|
||||
let mut phrase = state.phrases()[index].read().unwrap().duplicate();
|
||||
phrase.color = ItemPalette::random_near(phrase.color, 0.25);
|
||||
Cmd::Phrase(PhrasePoolCommand::Add(index + 1, phrase))
|
||||
Cmd::Phrase(MidiPoolCommand::Add(index + 1, phrase))
|
||||
},
|
||||
_ => return None
|
||||
})
|
||||
|
|
@ -201,3 +201,67 @@ impl PoolModel {
|
|||
}
|
||||
}
|
||||
}
|
||||
command!(|self: FileBrowserCommand, state: PoolModel|{
|
||||
use PoolMode::*;
|
||||
use FileBrowserCommand::*;
|
||||
let mode = &mut state.mode;
|
||||
match mode {
|
||||
Some(Import(index, ref mut browser)) => match self {
|
||||
Cancel => { *mode = None; },
|
||||
Chdir(cwd) => { *mode = Some(Import(*index, FileBrowser::new(Some(cwd))?)); },
|
||||
Select(index) => { browser.index = index; },
|
||||
Confirm => if browser.is_file() {
|
||||
let index = *index;
|
||||
let path = browser.path();
|
||||
*mode = None;
|
||||
MidiPoolCommand::Import(index, path).execute(state)?;
|
||||
} else if browser.is_dir() {
|
||||
*mode = Some(Import(*index, browser.chdir()?));
|
||||
},
|
||||
_ => todo!(),
|
||||
},
|
||||
Some(Export(index, ref mut browser)) => match self {
|
||||
Cancel => { *mode = None; },
|
||||
Chdir(cwd) => { *mode = Some(Export(*index, FileBrowser::new(Some(cwd))?)); },
|
||||
Select(index) => { browser.index = index; },
|
||||
_ => unreachable!()
|
||||
},
|
||||
_ => unreachable!(),
|
||||
};
|
||||
None
|
||||
});
|
||||
input_to_command!(FileBrowserCommand: |state: PoolModel, input: Event|{
|
||||
use FileBrowserCommand::*;
|
||||
use KeyCode::{Up, Down, Left, Right, Enter, Esc, Backspace, Char};
|
||||
if let Some(PoolMode::Import(_index, browser)) = &state.mode {
|
||||
match input {
|
||||
kpat!(Up) => Select(browser.index.overflowing_sub(1).0
|
||||
.min(browser.len().saturating_sub(1))),
|
||||
kpat!(Down) => Select(browser.index.saturating_add(1)
|
||||
% browser.len()),
|
||||
kpat!(Right) => Chdir(browser.cwd.clone()),
|
||||
kpat!(Left) => Chdir(browser.cwd.clone()),
|
||||
kpat!(Enter) => Confirm,
|
||||
kpat!(Char(_)) => { todo!() },
|
||||
kpat!(Backspace) => { todo!() },
|
||||
kpat!(Esc) => Cancel,
|
||||
_ => return None
|
||||
}
|
||||
} else if let Some(PoolMode::Export(_index, browser)) = &state.mode {
|
||||
match input {
|
||||
kpat!(Up) => Select(browser.index.overflowing_sub(1).0
|
||||
.min(browser.len())),
|
||||
kpat!(Down) => Select(browser.index.saturating_add(1)
|
||||
% browser.len()),
|
||||
kpat!(Right) => Chdir(browser.cwd.clone()),
|
||||
kpat!(Left) => Chdir(browser.cwd.clone()),
|
||||
kpat!(Enter) => Confirm,
|
||||
kpat!(Char(_)) => { todo!() },
|
||||
kpat!(Backspace) => { todo!() },
|
||||
kpat!(Esc) => Cancel,
|
||||
_ => return None
|
||||
}
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use ClockCommand::{Play, Pause};
|
|||
use KeyCode::{Tab, Char};
|
||||
use SequencerCommand as Cmd;
|
||||
use MidiEditCommand::*;
|
||||
use PhrasePoolCommand::*;
|
||||
use MidiPoolCommand::*;
|
||||
/// Root view for standalone `tek_sequencer`.
|
||||
pub struct Sequencer {
|
||||
pub _jack: Arc<RwLock<JackConnection>>,
|
||||
|
|
@ -57,6 +57,25 @@ impl Sequencer {
|
|||
let pool = Pull::y(1, Fill::y(Align::e(PoolView(self.pool.visible, &self.pool))));
|
||||
Fixed::x(pool_w, Align::e(Fill::y(PoolView(self.compact, &self.pool))))
|
||||
}
|
||||
fn help () -> impl Content<TuiOut> {
|
||||
let single = |binding, command|row!(" ", col!(
|
||||
Tui::fg(TuiTheme::yellow(), binding),
|
||||
command
|
||||
));
|
||||
let double = |(b1, c1), (b2, c2)|col!(
|
||||
row!(" ", Tui::fg(TuiTheme::yellow(), b1), " ", c1,),
|
||||
row!(" ", Tui::fg(TuiTheme::yellow(), b2), " ", c2,),
|
||||
);
|
||||
Tui::fg_bg(TuiTheme::g(255), TuiTheme::g(50), row!(
|
||||
single("SPACE", "play/pause"),
|
||||
double(("▲▼▶◀", "cursor"), ("Ctrl", "scroll"), ),
|
||||
double(("a", "append"), ("s", "set note"),),
|
||||
double((",.", "length"), ("<>", "triplet"), ),
|
||||
double(("[]", "phrase"), ("{}", "order"), ),
|
||||
double(("q", "enqueue"), ("e", "edit"), ),
|
||||
double(("c", "color"), ("", ""),),
|
||||
))
|
||||
}
|
||||
}
|
||||
audio!(|self:Sequencer, client, scope|{
|
||||
// Start profiling cycle
|
||||
|
|
@ -178,29 +197,10 @@ from!(|state:&Sequencer|SequencerStatus = {
|
|||
}
|
||||
});
|
||||
render!(TuiOut: (self: SequencerStatus) => Fixed::y(2, lay!(
|
||||
Self::help(),
|
||||
Sequencer::help(),
|
||||
Fill::xy(Align::se(Tui::fg_bg(TuiTheme::orange(), TuiTheme::g(25), self.stats()))),
|
||||
)));
|
||||
impl SequencerStatus {
|
||||
fn help () -> impl Content<TuiOut> {
|
||||
let single = |binding, command|row!(" ", col!(
|
||||
Tui::fg(TuiTheme::yellow(), binding),
|
||||
command
|
||||
));
|
||||
let double = |(b1, c1), (b2, c2)|col!(
|
||||
row!(" ", Tui::fg(TuiTheme::yellow(), b1), " ", c1,),
|
||||
row!(" ", Tui::fg(TuiTheme::yellow(), b2), " ", c2,),
|
||||
);
|
||||
Tui::fg_bg(TuiTheme::g(255), TuiTheme::g(50), row!(
|
||||
single("SPACE", "play/pause"),
|
||||
double(("▲▼▶◀", "cursor"), ("Ctrl", "scroll"), ),
|
||||
double(("a", "append"), ("s", "set note"),),
|
||||
double((",.", "length"), ("<>", "triplet"), ),
|
||||
double(("[]", "phrase"), ("{}", "order"), ),
|
||||
double(("q", "enqueue"), ("e", "edit"), ),
|
||||
double(("c", "color"), ("", ""),),
|
||||
))
|
||||
}
|
||||
fn stats (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
row!(&self.cpu, &self.size)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,10 +19,13 @@ mod tui_theme; pub use self::tui_theme::*;
|
|||
mod tui_border; pub use self::tui_border::*;
|
||||
mod tui_field; pub use self::tui_field::*;
|
||||
mod tui_buffer; pub use self::tui_buffer::*;
|
||||
mod tui_file; pub use self::tui_file::*;
|
||||
|
||||
pub(crate) use std::sync::{Arc, RwLock, atomic::{AtomicUsize, AtomicBool, Ordering::*}};
|
||||
pub(crate) use std::io::{stdout, Stdout};
|
||||
pub(crate) use std::error::Error;
|
||||
pub(crate) use std::path::PathBuf;
|
||||
pub(crate) use std::ffi::OsString;
|
||||
|
||||
/// Standard result type.
|
||||
pub type Usually<T> = Result<T, Box<dyn Error>>;
|
||||
|
|
|
|||
87
tui/src/tui_file.rs
Normal file
87
tui/src/tui_file.rs
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
use crate::*;
|
||||
/// Browses for phrase to import/export
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FileBrowser {
|
||||
pub cwd: PathBuf,
|
||||
pub dirs: Vec<(OsString, String)>,
|
||||
pub files: Vec<(OsString, String)>,
|
||||
pub filter: String,
|
||||
pub index: usize,
|
||||
pub scroll: usize,
|
||||
pub size: Measure<TuiOut>
|
||||
}
|
||||
/// Commands supported by [FileBrowser]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum FileBrowserCommand {
|
||||
Begin,
|
||||
Cancel,
|
||||
Confirm,
|
||||
Select(usize),
|
||||
Chdir(PathBuf),
|
||||
Filter(Arc<str>),
|
||||
}
|
||||
render!(TuiOut: (self: FileBrowser) => /*Stack::down(|add|{
|
||||
let mut i = 0;
|
||||
for (_, name) in self.dirs.iter() {
|
||||
if i >= self.scroll {
|
||||
add(&Tui::bold(i == self.index, name.as_str()))?;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
for (_, name) in self.files.iter() {
|
||||
if i >= self.scroll {
|
||||
add(&Tui::bold(i == self.index, name.as_str()))?;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
add(&format!("{}/{i}", self.index))?;
|
||||
Ok(())
|
||||
})*/"todo");
|
||||
impl FileBrowser {
|
||||
pub fn new (cwd: Option<PathBuf>) -> Usually<Self> {
|
||||
let cwd = if let Some(cwd) = cwd { cwd } else { std::env::current_dir()? };
|
||||
let mut dirs = vec![];
|
||||
let mut files = vec![];
|
||||
for entry in std::fs::read_dir(&cwd)? {
|
||||
let entry = entry?;
|
||||
let name = entry.file_name();
|
||||
let decoded = name.clone().into_string().unwrap_or_else(|_|"<unreadable>".to_string());
|
||||
let meta = entry.metadata()?;
|
||||
if meta.is_dir() {
|
||||
dirs.push((name, format!("📁 {decoded}")));
|
||||
} else if meta.is_file() {
|
||||
files.push((name, format!("📄 {decoded}")));
|
||||
}
|
||||
}
|
||||
Ok(Self {
|
||||
cwd,
|
||||
dirs,
|
||||
files,
|
||||
filter: "".to_string(),
|
||||
index: 0,
|
||||
scroll: 0,
|
||||
size: Measure::new(),
|
||||
})
|
||||
}
|
||||
pub fn len (&self) -> usize {
|
||||
self.dirs.len() + self.files.len()
|
||||
}
|
||||
pub fn is_dir (&self) -> bool {
|
||||
self.index < self.dirs.len()
|
||||
}
|
||||
pub fn is_file (&self) -> bool {
|
||||
self.index >= self.dirs.len()
|
||||
}
|
||||
pub fn path (&self) -> PathBuf {
|
||||
self.cwd.join(if self.is_dir() {
|
||||
&self.dirs[self.index].0
|
||||
} else if self.is_file() {
|
||||
&self.files[self.index - self.dirs.len()].0
|
||||
} else {
|
||||
unreachable!()
|
||||
})
|
||||
}
|
||||
pub fn chdir (&self) -> Usually<Self> {
|
||||
Self::new(Some(self.path()))
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue